Real-time plotting of DARCI pressure data

Overview

This post describes the development of a Windows software application that acquires real-time data from pressure sensor via a virtual COM port USB interface and plots the pressure measurements on a continuous strip-chart plot.

From a requirements perspective, the software application needs to:

  • Run as a standalone executable on Windows with standard GUI features including a resizable and movable plotting window.
  • Search, detect, and establish a USB virtual COM port connection with the pressure sensor.
  • Have USB virtual COM port settings (i.e., baud rate, number of data bits, parity bit options, number of stop bits, and flow control options) that match the standard options.
  • Plot data pressure data from the pressure sensor on a strip chart where the measured sequence moves across the plot area.
  • Pause plotting so that the plot and data can be reviewed.
  • Have a vertical plot axis that accommodates pressure measurements between -75 to 75 cm-H2O.
  • Receive data (text string of 5 characters) for each pressure measurement can range from -7500 to +7500 after prompting the pressure sensor with a request (“4\n”) for a measurement.
  • Support a reasonably fast measurement rate from the pressure sensor for a smooth plot. Notes: Normal recording rate from the pressure sensor is 125 samples/second. Testing will confirm the actual maximum rate.

Design Concept:

From a design concept perspective, it was definitely my intent to keep the GUI simple and its operation intuitive. The initial GUI provides a button to establish a USB port connection to the pressure sensor. The GUI ‘look-n-feel’ matches my current windows desktop theme (apparently, I like the really old desktop themes from the available themes on Windows).
dialog_01
Clicking on the ‘Connect to USB port’ button causes the software to search for a USB port connection to the pressure sensor every 0.5 second until a USB connection is established or the ‘Cancel search’ button is clicked.
dialog_02
After a USB connection is established, the ‘Start Plotting’ button is enabled.
dialog_03
Clicking on this button brings up the strip chart plotting window and starts plotting pressure measurements.
plot_01
In the current example program, a simple handshake is used:

  • Every 20 msec (i.e., 50 samples/second), transmit request (“4\n”) via USB port for a pressure measurement.
  • Receive character stream of 5 characters consisting of a positive/negative sign and four digits (-7500 to 7500). Dividing the received integer by 100 yields pressure in units of cm-H2O.
After a plot is started, the ‘Pause Plotting’ button is enabled. Plotting can be paused and restarted. The plotting window can be resized in real-time.
dialog_04
If the ‘Pause Plotting’ button is clicked, two options become enabled: ‘Break USB Connection’ and ‘Re-Start Plotting’. Clicking on the ‘Break USB Connection’ button will close the plotting window.
dialog_05

Selection of Software Development Platform:

Although other development platforms/languages were evaluated (e.g., MatLab, SCiPy, LabView), I opted for Qt and QCustomPlot to create the real-time pressure plotting application.

Qt is a cross-platform development environment that can be used for developing application software with GUIs. Software is primarily written in C++. The only unique concept in using Qt is the use of Signals and Slots to handle the events. Numerous examples are provided by Qt. The Serial Port Enumerator Example was the basis for detecting the specific USB port in this application.

QCustomPlot is a widget that works with Qt and excels at plotting and data visualization. It consists of two files (qcustomplot.cpp and qcustomplot.h) that are added to the software project. Plots are easy to code and have an extensive feature set for customization. Publication quality 2D plots, charts and graphs can be produced. Performance supports real-time data plotting and visualization applications. The Real-Time Data Demo was the basis for this example.

Software Architecture:

The software consists of two classes (Dialog and MainWindow). The software consists of three files (extensions: cpp, h, ui) for each class, a main.cpp file, and a project file (extension: pro). The two UI files contain the layout hierarchy, widget design details, and object names for use in the source code files.

Only some of the software is included and described in this blog. Mostly, I included the code that I find interesting. All of the source code and the Qt project file for this app has been placed on GitHub.

Dialog Class:

The Dialog class creates the GUI dialog with two buttons and a status field. This class is also responsible for searching and establishing a virtual COM port USB connection, and the timing functions for requesting and reading measurements from the pressure sensor. The constructor for the Dialog class sets up the dialog’s layout and initializes the button’s titles and disables the ‘Start Plotting’ button. The timer event for acquiring pressure measurements is defined in the constructor using the Signals and Slots syntax associated with Qt. The constructor code segment for the Dialog class is shown below:

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    dialog_ui(new Ui::Dialog)
{
    dialog_ui->setupUi(this);
    dialog_ui->usbStatus->setText("Status: USB port isn't established.");
    dialog_ui->connectUSB->setText("Connect to USB port.");
    dialog_ui->startPressurePlot->setText("Start Plotting");
    dialog_ui->startPressurePlot->setDisabled(true);
    rtpUSB_is_available = false;
    rtpUSB_cancel_enable = false;
    connect(&pressure_time, SIGNAL(timeout()), this, SLOT(request_pressure_slot()));
}

The ‘usbConnectionDataSlot’ method is called every half second from the ‘usbConnectionTimer’, as needed, as the event when the ‘Connect to USB port’ button is clicked. If a virtual COM port USB connection isn’t established yet, then each of the available ports are examined in sequence to test if the port has the specific USB Vendor ID (VID). If found, then the port is configured for baud rate of 115,200, 8 data bits, no parity, 1 stop bit, and hardware flow control. Since the pressure sensor returns a pressure measurement in response to a request, the USB port is configured for read and write. After the USB connection is established, the ‘usbConnectionTimer’ is stopped.

void Dialog::usbConnectionDataSlot()
{
    if (rtpUSB_is_available == false)
    {
        foreach(const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts())
        {
            if(serialPortInfo.hasVendorIdentifier())
            {
                if(serialPortInfo.vendorIdentifier() == arduino_due_vendor_id)
                {
                    rtpUSB_is_available = true;
                    rtpUSB_cancel_enable = false;
                    rtpUSB_port_name = serialPortInfo.portName();
                    rtpUSB->setPort(serialPortInfo);
                    rtpUSB->setBaudRate(QSerialPort::Baud115200);
                    rtpUSB->setDataBits(QSerialPort::Data8);
                    rtpUSB->setParity(QSerialPort::NoParity);
                    rtpUSB->setStopBits(QSerialPort::OneStop);
                    rtpUSB->setFlowControl(QSerialPort::HardwareControl);
                    rtpUSB->open(QIODevice::ReadWrite);
               }
            }
        }
    } else {
        dialog_ui->startPressurePlot->setEnabled(true);
        dialog_ui->usbStatus->setText("Status: USB port established.");
        dialog_ui->connectUSB->setText("Break USB connection.");
        dialog_ui->connectUSB->setEnabled(true);
        button_state = false;
        usbConnectionTimer.stop();
    }
}

After a USB connection is established, the ‘Start Plotting’ button is enabled. Clicking on this button brings up the strip chart plotting window and starts plotting pressure measurements. Since plotting can be paused and restarted with the same button event, the method checks to see if the window already exists by checking the state of a flag. If a plot window doesn’t exist yet, then a plot window is created and displayed. In addition, a signal event that prompts the plot window method realtimeDataSlot of a change in successive pressurement measurements updates the plot using the Signals and Slots syntax associated with Qt. The 20 msec timer is started when the ‘Start Plotting’ button is clicked and stopped when the ‘Pause Plotting’ button is clicked.

void Dialog::on_startPressurePlot_released()
{
    if (window_flag == false) {
        plotWindow2 = new PlotWindow();
        plotWindow2->show();
        connect(this, &Dialog::get_pressure_data, plotWindow2, &PlotWindow::realtimeDataSlot);
        window_flag = true;
    }
    button_state = !button_state;
    if (button_state) {       // plotting started
        dialog_ui->connectUSB->setDisabled(true);
        pressureData = 0.0;
        first_read = true;
        pressure_time.start(20);
        plotWindow2->startPlotTimer();
        dialog_ui->startPressurePlot->setText("Pause Plotting");
        dialog_ui->usbStatus->setText("Status: Receiving pressure data for plotting.");
    } else {                  // plotting paused
        dialog_ui->connectUSB->setDisabled(false);
        pressure_time.stop();
        dialog_ui->startPressurePlot->setText("Re-Start Plotting");
        dialog_ui->usbStatus->setText("Status: Paused plotting pressure data.");
    }
}

A simple request/reply handshake protocol method is used to acquire the measurements from the pressure sensor. The request is the text string “4\n” and the reply is received in the form of a text string of 5 characters in length, representing a number from -7500 to +7500 (i.e., -75 to +75 cm-H2O). For plotting fidelity and smoothness, 50 samples/sec (timer interval of 20 milliseconds) is an adequate measurement rate. In order to maximize the available time between the request and reply, the software waits until the next time tick (most of 20 msec) to process the reply. The extra QString processing accounts for the possibility of additional characters or an empty buffer.

void Dialog::request_pressure_slot()
{
    //-----------------------------------------------------
    // Reading pressure value from previous request allows
    // max time for pressure measurement to be received.
    // Skip read prior to first request.
    //-----------------------------------------------------
    if (first_read == false) {
        serialBuffer = rtpUSB->readAll();
        int chop_point = serialBuffer.indexOf("\n") - 1;
        serialBuffer1 = serialBuffer.left(chop_point);
        if (!serialBuffer1.isEmpty()) {
            pressureData = serialBuffer1.toDouble()/100;
            serialBuffer.clear();
            serialBuffer1.clear();
            emit get_pressure_data(pressureData);
        }
    } else {
        rtpUSB->clear();
        serialBuffer.clear();
        serialBuffer1.clear();
        first_read = false;
    }
    //-----------------------------------------------------
    // request a pressure measurement by sending cmd
    //-----------------------------------------------------
    rtpUSB->write("4\n");
}

MainWindow Class:

The MainWindow class creates and configures the strip chart plotting window and then plots the data in strip chart mode as it becomes available. In addition to calling the QCustomPlot method (setupUI) to create the initial plot window, the rest of the constructor code defines some of the features for the plot window including: flags for window frame buttons, initial location and size of plot window, and its title. The constructor code segment for the MainWindow class is shown below:

PlotWindow::PlotWindow(QWidget *parent) :
  QMainWindow(parent),
plot_ui(new Ui::PlotWindow)
{
  setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowMinMaxButtonsHint);
  plot_ui->setupUi(this);
  setGeometry(400, 250, 542, 390);
  setupRealtimeDataDemo(plot_ui->customPlot);
  setWindowTitle("Real-Time Pressure Measurements");
  statusBar()->clearMessage();
  plot_ui->customPlot->replot();
}

Application-specific configuration of the plot window is defined by the method (setupRealtimeDataDemo) which is called by the constructor. The line and dot colors are both set to blue. Tick labels on the horizontal axis are defined to be time in the hh:mm:ss format and displayed on the axis every two seconds. The vertical axis is set to -80 to 80 to cover the pressure range of -75 to 75 cm-H2O. The tick marks for both vertical and horizontal axes are duplicated on opposite sides of the plot window.

void PlotWindow::setupRealtimeDataDemo(QCustomPlot *customPlot)
{
  customPlot->addGraph(); // blue line
  customPlot->graph(0)->setPen(QPen(Qt::blue));
  customPlot->graph(0)->setBrush(QBrush(QColor(240, 255, 200)));
  customPlot->graph(0)->setAntialiasedFill(false);

  customPlot->addGraph(); // blue dot
  customPlot->graph(1)->setPen(QPen(Qt::blue));
  customPlot->graph(1)->setLineStyle(QCPGraph::lsNone);
  customPlot->graph(1)->setScatterStyle(QCPScatterStyle::ssDisc);

  customPlot->xAxis->setTickLabelType(QCPAxis::ltDateTime);
  customPlot->xAxis->setDateTimeFormat("hh:mm:ss");
  customPlot->xAxis->setAutoTickStep(false);
  customPlot->xAxis->setTickStep(2);
  customPlot->axisRect()->setupFullAxesBox(true);
  customPlot->yAxis->setRange(-80,80);
}

Finally, pressure measurement data is acquired every 20 msec and passed to the following method to be plotted to emulate a strip chart. The blue line (graph(0)) is extended by adding the new data. The previous blue dot (graph(1)) is cleared and one is created at the new data point. All line data (graph(0)) older than 10 seconds (i.e., extends beyond the left edge) is removed. And, finally, the plot range is set to place the current data point at just before the right edge of the plot and replotted with the updates.

void PlotWindow::realtimeDataSlot(double value0)
{
    double key = QDateTime::currentDateTime().toMSecsSinceEpoch()/1000.0;
    plot_ui->customPlot->graph(0)->addData(key, value0);
    plot_ui->customPlot->graph(1)->clearData();
    plot_ui->customPlot->graph(1)->addData(key, value0);
    plot_ui->customPlot->graph(0)->removeDataBefore(key-10);
    plot_ui->customPlot->xAxis->setRange(key+0.25, 10, Qt::AlignRight);
    plot_ui->customPlot->replot();
}

Deployed Software:

After the ‘Real-Time USB Pressure Monitor’ is compiled in a release configuration, it becomes an executable file but still requires a set of DLL files (located within the local Qt directory structure) to be able to run as a standalone software program on Windows. For this specific software program, the relevant DLL files listed below should be included in the same folder with the ‘Real-Time USB Pressure Monitor’ executable file (qt_tr_plotter.exe):

libEGL.dll

libgss_s_dw2-1.dll

libstdc++-6.dll

libwinpthread-1.dll

Qt5Core.dll

Qt5Gui.dll

Qt5PrintSupport.dll

Qt5SerialPort.dll

Qt5Widgets.dll

In addition, there is one DLL file that needs to be located in a subfolder named ‘platforms’:

qwindows.dll

Acknowledgements:

The logo used for the blog’s icon is the trademark of QCustomPlot (author: Emanuel Elchhammer).

2017-08-07T14:02:14+00:00 By |C/C++ Dev, Electronics, Medical|0 Comments

Leave A Comment