从QT中的串口读取时出现异常

时间:2017-02-20 04:50:48

标签: c++ qt arduino arduino-uno

我使用QT和QCustomPlot创建实时绘图工具,并从Arduino UNO板读取绘图数据。我的应用程序成功绘制,而数据完全混乱。以下是我的代码(有些代码来自QCustomPlot网站):

void Dialog::realtimeDataSlot()
{
    bool currentPortNameChanged = false;

    QString currentPortName;
    if (currentPortName != portName) {
        currentPortName = portName;
        currentPortNameChanged = true;
    }

    QString currentRequest = request;
    QSerialPort serial;

    if (currentPortNameChanged) {
        serial.close();
        serial.setPortName(currentPortName);

        if (!serial.open(QIODevice::ReadOnly)) {
            return;
        }
    }



    static QTime time(QTime::currentTime());
    // calculate two new data points:
    double key = time.elapsed()/1000.0; 
    static double lastPointKey = 0;
    if (key-lastPointKey > 0.002) // at most add point every 2 ms
    {
        // add data to lines:
        if(serial.waitForReadyRead(-1)){
            data = serial.readAll();
            QTextStream(stdout) << "HERE:" << data.toDouble() << endl;
            customPlot->graph(0)->addData(key, data.toDouble());
            customPlot->graph(0)->rescaleValueAxis();   //rescale value   (vertical) axis to fit the current data:
            lastPointKey = key;
            customPlot->xAxis->setRange(key, 8, Qt::AlignRight);
            customPlot->replot();
            static double lastFpsKey;
            static int frameCount;
            ++frameCount;
            if (key-lastFpsKey > 2) // average fps over 2 seconds
            {
                lastFpsKey = key;
                frameCount = 0;
            }
        }
    }


    // calculate frames per second:

    if (currentPortName != portName) {
        currentPortName = portName;
        currentPortNameChanged = true;
    } else {
        currentPortNameChanged = false;
    }

}

当我尝试打印出从串口读取的数据时,我发现了以下内容:

HERE:1
HERE:15
HERE:150
HERE:149
HERE:149
HERE:149
HERE:150
HERE:150
HERE:15
HERE:150
HERE:149
HERE:49
HERE:150
HERE:150
HERE:1
HERE:150

150左右的值是正常的,而0,1的值不是。它也不能以稳定的速度打印出来。我不知道发生了什么事,感谢任何可能提供帮助的人,如果有更好的方法可以实现,我将不胜感激。

1 个答案:

答案 0 :(得分:1)

这里的问题是无法保证一次性接收串行传输。因此最好让串口在其他地方进行处理,例如:

// in the class definition
    QSerialPort serialPort;
private slots:
    void handleReadyRead();
private:
    QByteArray serialBuffer;
    volatile double lastSerialValue;

// In the initialization part (not the realtimeDataSlot function)
lastSerialValue = qQNaN();
serialPort.setPortName(currentPortName);
connect(&serialPort, &QSerialPort::readyRead, this, &Dialog::handleReadyRead, Qt::UniqueConnection);
if (!serialPort.open(QIODevice::ReadOnly)) {
    return;
}
serialBuffer.clear();

// Other functions:
void Dialog::realtimeDataSlot()
{
    ...
    if (key-lastPointKey > 0.002) // at most add point every 2 ms
    {
        if (!qIsNaN(lastSerialData))
        {
            // use lastSerialValue as the data.toDouble() you had before, then, at the end

            lastSerialValue = qQNaN();
        }
    ...
}

void Dialog::handleReadyRead()
{
    serialBuffer.append(serialPort.readAll());
    int serPos;
    while ((serPos = serialBuffer.indexOf('\n')) >= 0)
    {
        bool ok;
        double tempValue = QString::fromLatin1(serialBuffer.left(serPos)).toDouble(&ok);
        if (ok) lastSerialValue = tempValue;

        serialBuffer = serialBuffer.mid(serPos+1);
    }
}

说明:无论何时从arduino收到内容,都会将字节附加到缓冲区。然后解析字节数组寻找终结符,如果找到则拆分并分析字节数组。当另一个函数需要数据时,它只是拉动变量中保存的最新函数。

注1:我看到你使用了二进制传输。问题是您无法以这种方式确定数据的开始和结束位置。例如,如果你收到0x01 0x02 0x03 0x04并且你知道有3个字节,那么它们是01..03或02..04或03,04和缺少的一个还是......?我实现的版本要求您使用换行符(最简单的版本,只需在arduino代码中编写Serial.println(doubleValue);)以字符串格式发送数据,但如果您需要二进制版本,我可以给您一些提示

注2:我写的代码不是线程安全的。只有在同一个线程中调用realtimeDataSlot和handleReadyRead时,它才会起作用。请注意,如果它们属于同一个对象并通过信号调用,则可以保证。

现在,这应该有效。但我强烈反对你这样做。我不知道谁需要调用realtimeDataSlot(),但我认为最正确的版本是这样的:

// in the class definition
    QSerialPort serialPort;
private slots:
    void handleReadyRead();
    void receivedData(double val);
private:
    QByteArray serialBuffer;
signals:
    void newData(double data);

// In the initialization part (not the realtimeDataSlot function)
serialPort.setPortName(currentPortName);
connect(&serialPort, &QSerialPort::readyRead, this, &Dialog::handleReadyRead, Qt::UniqueConnection);
connect(this, &Dialog::newData, this, &Dialog::receivedData, Qt::UniqueConnection);
if (!serialPort.open(QIODevice::ReadOnly)) {
    return;
}
serialBuffer.clear();

// Other functions:
void Dialog::receivedData(double val)
{
    double key = time.elapsed()/1000.0;
    static double lastPointKey = 0;
    if (key-lastPointKey > 0.002) // at most add point every 2 ms
    {
        QTextStream(stdout) << "HERE:" << data.toDouble() << endl;
        customPlot->graph(0)->addData(key, data.toDouble());
        customPlot->graph(0)->rescaleValueAxis();
        ...
    }
}

void Dialog::handleReadyRead()
{
    serialBuffer.append(serialPort.readAll());
    int serPos;
    while ((serPos = serialBuffer.indexOf('\n')) >= 0)
    {
        bool ok;
        double tempValue = QString::fromLatin1(serialBuffer.left(serPos)).toDouble(&ok);
        if (ok) emit newData(tempValue);

        serialBuffer = serialBuffer.mid(serPos+1);
    }
}

因此,请保持图形响应事件(接收新数据)而不是计时器。

还有一件事:我故意删除了端口更改。我建议你以另一种方式处理它:按一个按钮来启动和停止串口,当串口启动时阻止用户更改端口名称。这样,当用户需要更改端口时,用户将明确需要将其关闭。但是,如果您需要您的版本,请不要将其包含在您的代码中,而是在需要更改端口名称时自行调用一个插槽:

void changeSerialPortName(QString newName)
{
    if (newName != serialPort.portName()) {
        if (serialPort.isOpen())
            serialPort.close();

        serialPort.setPortName(newName);

        if (!serialPort.open(QIODevice::ReadOnly)) {
            return;
        }
    }
}