C++ Qt 串行端口 - 一致地读取数据

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15103599/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 19:01:45  来源:igfitidea点击:

Qt Serial Port - Reading data consistently

c++qtserial-portqtserialport

提问by Alex Hendren

I am sending (writing) bytes to a device via my serial port. I am using the QSerialPort (http://qt-project.org/wiki/QtSerialPort) module to instantiate device IO support. When I send messages to my INSTEON modem (serial), upon reading my message the device sends back a copy of my message + 0x06 (ACK Byte) followed by a status message.

我正在通过串行端口向设备发送(写入)字节。我正在使用 QSerialPort ( http://qt-project.org/wiki/QtSerialPort) 模块来实例化设备 IO 支持。当我向 INSTEON 调制解调器(串行)发送消息时,在读取我的消息时,设备发回我的消息副本 + 0x06(ACK 字节),然后是状态消息。

I have tested my message using DockLight (http://www.docklight.de/). I send the following message to query the state of the device:

我已经使用 DockLight ( http://www.docklight.de/)测试了我的消息。我发送以下消息来查询设备的状态:

    02 62 1D E9 4B 05 19 00

Using Docklight, I receive the response:

使用 Docklight,我收到响应:

    02 62 1D E9 4B 05 19 00 06 02 50 20 CB CF 1E DA F7 21 00 FF

The returned message indicates exactly what I would expect, that the device is on. If off, the modem would send back 0x00 in the last byte position if the device was off. Now, my problem - I must not have my function setup properly to send and then receive the response bytes. I have tried many different examples and configurations, currently I am using the following:

返回的消息准确地表明了我的期望,即设备已开启。如果关闭,如果设备关闭,调制解调器将在最后一个字节位置发回 0x00。现在,我的问题 - 我不能正确设置我的函数来发送然后接收响应字节。我尝试了许多不同的示例和配置,目前我正在使用以下内容:

Setup signal-slot connections:

设置信号槽连接:

QObject::connect(&thread, SIGNAL(sendResponse(QByteArray)), 
    this, SLOT(handleResponse(QByteArray)));
QObject::connect(&thread, SIGNAL(error(QString)), 
    this, SLOT(processError(QString)));
QObject::connect(&thread, SIGNAL(timeout(QString)), 
    this, SLOT(processTimeout(QString)));

Function used to iterate through QList of devices. If device is desired type ("Light"), then we format the device ID to the intended QByteArray message structure. Pass message to thread for sending. (Thread modified from QSerialPort BlockingMasterexample.

用于遍历设备 QList 的函数。如果设备是所需类型(“Light”),那么我们将设备 ID 格式化为预期的 QByteArray 消息结构。将消息传递给线程进行发送。(从 QSerialPortBlockingMaster示例修改的线程。

void Device::currentStatus(QList<Device *> * deviceList){
    QString devID, updateQry;
    int devStatus, updateStatus;
    updateStatus=0;
    QSqlQuery query;
    for(int i=0; i<deviceList->size(); i++){
        if(deviceList->at(i)->type == "Light"){
            devStatus = deviceList->at(i)->status;
            devID = deviceList->at(i)->deviceID;
            QByteArray msg;
            bool msgStatus;
            msg.resize(8);

            msg[0] = 0x02;
            msg[1] = 0x62;
            msg[2] = 0x00;
            msg[3] = 0x00;
            msg[4] = 0x00;
            msg[5] = 0x05;
            msg[6] = 0x19;
            msg[7] = 0x00;
            msg.replace(2, 3, QByteArray::fromHex( devID.toLocal8Bit() ) );
            qDebug() << "Has device " << deviceList->at(i)->name << "Changed?";
            //send(msg,&msgStatus, &updateStatus);
            //msg.clear();
            thread.setupPort("COM3",500,msg);
            if(devStatus!=updateStatus){
                qDebug() << deviceList->at(i)->name << " is now: " << updateStatus;
                updateStatus = !updateStatus;
            }
        }
    }
}

SetupThreadfunction used to set local thread variables and executes (runs) thread.

SetupThread函数用于设置本地线程变量并执行(运行)线程。

void serialThread::setupPort(const QString &portName, int waitTimeout, const QByteArray &msg){
    qDebug() << "Send Message " << msg.toHex();
    QMutexLocker locker(&mutex);
    this->portName = portName;
    this->waitTimeout = waitTimeout;
    this->msg = msg;
    if(!isRunning())
        start();
    else
        cond.wakeOne();
}

RunFunction - Handled sending and receiving

Run功能 - 处理发送和接收

void serialThread::run(){
    bool currentPortNameChanged = false;
    qDebug() << "Thread executed";
    mutex.lock();
    QString currentPortName;
    if(currentPortName != portName){
        currentPortName = portName;
        currentPortNameChanged = true;
    }

    int currentWaitTimeout = waitTimeout;
    QByteArray sendMsg = msg;
    mutex.unlock();
    QSerialPort serial;

    while(!quit){
        if(currentPortNameChanged){
            serial.close();
            serial.setPortName("COM3");

            if (!serial.open(QIODevice::ReadWrite)) {
                emit error(tr("Can't open %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setBaudRate(QSerialPort::Baud19200)) {
                emit error(tr("Can't set baud rate 9600 baud to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setDataBits(QSerialPort::Data8)) {
                emit error(tr("Can't set 8 data bits to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setParity(QSerialPort::NoParity)) {
                emit error(tr("Can't set no patity to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setStopBits(QSerialPort::OneStop)) {
                emit error(tr("Can't set 1 stop bit to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setFlowControl(QSerialPort::NoFlowControl)) {
                emit error(tr("Can't set no flow control to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }
        }

        //write request
        serial.write(msg);
        if (serial.waitForBytesWritten(waitTimeout)) {
            //! [8] //! [10]
            // read response
            if (serial.waitForReadyRead(currentWaitTimeout)) {
                QByteArray responseData = serial.readAll();
                while (serial.waitForReadyRead(10)){
                    responseData += serial.readAll();
                }

                QByteArray response = responseData;
                //! [12]
                emit this->sendResponse(response);
                //! [10] //! [11] //! [12]
            } else {
                emit this->timeout(tr("Wait read response timeout %1")
                             .arg(QTime::currentTime().toString()));
            }
            //! [9] //! [11]
        } else {
            emit timeout(tr("Wait write request timeout %1")
                         .arg(QTime::currentTime().toString()));
        }
        mutex.lock();
        cond.wait(&mutex);
        if (currentPortName != portName) {
            currentPortName = portName;
            currentPortNameChanged = true;
        } else {
            currentPortNameChanged = false;
        }
        currentWaitTimeout = waitTimeout;
        sendMsg = msg;
        mutex.unlock();
    }
    serial.close();
}

handleResponsefunction, SLOT which receives response signal

handleResponse函数,接收响应信号的SLOT

void Device::handleResponse(const QByteArray &msg){
    qDebug() << "Read: " << msg.toHex();
}

I receive the following output:

我收到以下输出:

Has device  "Living Room Light" Changed? 
Send Message  "02621de94b051900" 
Has device  "Bedroom Light" Changed? 
Send Message  "026220cbcf051900" 
Thread executed 
Read:  "026220cbcf05190006" 
Polling for changes... 
Has device  "Living Room Light" Changed? 
Send Message  "02621de94b051900" 
Has device  "Bedroom Light" Changed? 
Send Message  "026220cbcf051900" 
Read:  "025020cbcf1edaf721000002621de94b05190006" 
Polling for changes... 
Has device  "Living Room Light" Changed? 
Send Message  "02621de94b051900" 
Has device  "Bedroom Light" Changed? 
Send Message  "026220cbcf051900" 
Read:  "02501de94b1edaf72100ff02621de94b05190006" 

Two issues here.

这里有两个问题。

  1. I never receive any response regarding the second device (Bedroom Light), this is the message that is sent second. It seems that the send is being blocked, how would you recommend I format my sending so that I send after the response is received for the first send? There is only 1 COM port that can be used to send/receive. I believe I should Send message to Device 1, receive Device 1 response, Send to Device 2, receive Device 2. Could I end up seeing a huge traffic jam with a lot of devices and using wait conditions, ie. wait for device 1 communication process to finish before executing comm process for device 2?

  2. The very first read contains the appropriate 1st half of the receive. Read: "026220cbcf05190006"The second receive contains the 2nd half of the 1st response followed by the 1st half of the second response: Read 2 - Read: "025020cbcf1edaf721000002621de94b05190006"The appropriate full response is 02621DE94B05190006 025020CBCF1EDAF72100FF(note 20CBCFis Device 2's ID in the full response example)

  1. 我从未收到有关第二个设备(卧室灯)的任何响应,这是第二个发送的消息。似乎发送被阻止了,您建议我如何格式化我的发送,以便在第一次发送收到响应后发送?只有 1 个 COM 端口可用于发送/接收。我相信我应该向设备 1 发送消息,接收设备 1 响应,发送到设备 2,接收设备 2。我是否最终会看到大量设备和使用等待条件的巨大交通拥堵,即。在执行设备 2 的通信进程之前等待设备 1 通信进程完成?

  2. 第一次读取包含接收的适当的第一半。Read: "026220cbcf05190006"第二个接收包含第一个响应的第二个部分,然后是第二个响应的第一个部分:读取 2 -Read: "025020cbcf1edaf721000002621de94b05190006"适当的完整响应是02621DE94B05190006 025020CBCF1EDAF72100FF(注意20CBCF是完整响应示例中的设备 2 的 ID)

What corrections should be made to the way I am receiving data from the serial port? Thank you!

我从串口接收数据的方式应该做哪些更正?谢谢!

采纳答案by Alex Hendren

I believe my problems have shifted from the scope of this question. With help from Kuzulis I have implemented Write/Read functions to successfully send and read serial messages consistently. Kuzulis recommended using the Synchronous blocking communication pattern, however it was later decided that the Asynchronous Non-Blocking method would be best fit for my application.

我相信我的问题已经从这个问题的范围转移了。在 Kuzulis 的帮助下,我实现了写入/读取功能,以一致地成功发送和读取串行消息。Kuzulis 推荐使用同步阻塞通信模式,但后来决定异步非阻塞方法最适合我的应用程序。

My implementation closely follows the "Master" example provided with the QSerialPort source files.

我的实现紧跟 QSerialPort 源文件提供的“Master”示例。

I use CurrentStatusto iterate through a QList of Deviceobjects. For each Light in the Device list, I format an 8 Byte message to query the current status of the device (ON/OFF).

CurrentStatus用来遍历Device对象的 QList 。对于设备列表中的每个灯,我格式化一个 8 字节的消息来查询设备的当前状态(开/关)。

void Device::currentStatus(QList<Device *> * deviceList){
    QString devID, updateQry;
    int devStatus, updateStatus;
    updateStatus=0;
    QSqlQuery query;
    for(int i=0; i<deviceList->size(); i++){
        if(deviceList->at(i)->type == "Light"){
            devStatus = deviceList->at(i)->status;
            devID = deviceList->at(i)->deviceID;
            QByteArray msg;
            msg.resize(8);

            msg[0] = 0x02;
            msg[1] = 0x62;
            msg[2] = 0x00;
            msg[3] = 0x00;
            msg[4] = 0x00;
            msg[5] = 0x05;
            msg[6] = 0x19;
            msg[7] = 0x00;
            msg.replace(2, 3, QByteArray::fromHex( devID.toLocal8Bit() ) );
            qDebug() << "Has device " << deviceList->at(i)->name << "Changed?";

            emit writeRequest(msg);

            if(devStatus!=updateStatus){
                qDebug() << deviceList->at(i)->name << " is now: " << updateStatus;
                updateStatus = !updateStatus;
            }
        }
    }
}

In the Device class constructor, I connect the signals and slots:

在 Device 类构造函数中,我连接了信号和插槽:

Device::Device(){

    serialTimer.setSingleShot(true);
    QObject::connect(&serial, SIGNAL(readyRead()),
                     this, SLOT(handleResponse()));
    QObject::connect(&serialTimer, SIGNAL(timeout()),
                     this, SLOT(processTimeout()));
    QObject::connect(this, SIGNAL(writeRequest(QByteArray)),
                     this, SLOT(writeSerial(QByteArray)));
}

After the message to send in currentStatushas been prepared, emit writeRequest(msg);is called. This dispatches a signal that is connected to the slot writeRequest. writeRequest is used to setup and actually write the message to the serial port.

currentStatus准备好要发送的消息后,emit writeRequest(msg);调用。这会发送一个连接到 slot 的信号writeRequest。writeRequest 用于设置并将消息实际写入串行端口。

void Device::writeSerial(const QByteArray &msg){
    if (serial.portName() != "COM3") {
        serial.close();
        serial.setPortName("COM3");

        if (!serial.open(QIODevice::ReadWrite)) {
            processError(tr("Can't open %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setBaudRate(QSerialPort::Baud19200)) {
            processError(tr("Can't set rate 19200 baud to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setDataBits(QSerialPort::Data8)) {
            processError(tr("Can't set 8 data bits to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setParity(QSerialPort::NoParity)) {
            processError(tr("Can't set no patity to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setStopBits(QSerialPort::OneStop)) {
            processError(tr("Can't set 1 stop bit to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setFlowControl(QSerialPort::NoFlowControl)) {
            processError(tr("Can't set no flow control to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }
    }
    qDebug() << "Message written";
    this->msgRequest = msg;
    serial.write(msgRequest);
    serialTimer.start(400);
}

After setting up the serial port, I save the current message to msgRequest. This may have to be used to resend the message if there is an error. After serial.write()is called, I setup a timer for 400ms. Once this timer expires, I check what was read from the serial port.

设置好串口后,我将当前消息保存到msgRequest. 如果出现错误,这可能必须用于重新发送消息。之后serial.write()被调用时,我设置为400毫秒的定时器。一旦这个计时器到期,我检查从串行端口读取的内容。

handleResponse()is a slot that is called everytime QSerialPort emits the readyRead()signal. readyRead()appends any available data to the QByteArray response.

handleResponse()是每次 QSerialPort 发出readyRead()信号时调用的插槽。readyRead()将任何可用数据附加到 QByteArray response

void Device::handleResponse(){
    response.append(serial.readAll());  
}

After 400ms, serialTimer (one shot Timer) will emit a timeout()signal. serialTimerwas started right after writing our requested message to the serial port. processTimeout()is where we finally check the response received from the PowerLinc Modem after sending our message. When messages are sent to the INSTEON PowerLinc Modem (PLM), the PLM echoes back the message and appends either 0x06 (Positive ACK) or 0x15 (NACK). In processTimeout()I check to make sure the last byte received is the ACK byte, if not - resend our originally requested message.

400ms后,serialTimer(one shot Timer)会发出timeout()信号。serialTimer在将我们请求的消息写入串行端口后立即启动。processTimeout()是我们在发送消息后最终检查从 PowerLinc 调制解调器收到的响应的地方。当消息发送到 INSTEON PowerLinc 调制解调器 (PLM) 时,PLM 会回显消息并附加 0x06(肯定 ACK)或 0x15(NACK)。在processTimeout()我检查以确保收到的最后一个字节是 ACK 字节,如果不是 - 重新发送我们最初请求的消息。

void Device::processTimeout(){
    qDebug() << "Read: " << response.toHex();
    int msgLength = this->msgRequest.length();
    if(response.at(msgLength)!=0x06){
        qDebug() << "Error, resend.";
        emit writeRequest(msgRequest);
    }
    response.clear();
}

I used the Serial Port Monitor 4.0 (Eltima Software) to verify the write and read transactions on the serial port. Below, you can see the log printout for 1 sample transaction.

我使用串行端口监视器 4.0(Eltima 软件)来验证串行端口上的写入和读取事务。下面,您可以看到 1 个示例交易的日志打印输出。

20:44:30:666 STATUS_SUCCESS 02 62 1d e9 4b 05 19 00   <--- Send
20:44:30:669 STATUS_SUCCESS 02 62 1d e9 4b 05 19 00 06   <--- Receive
20:44:30:875 STATUS_SUCCESS 02  <--- Receive
20:44:30:881 STATUS_SUCCESS 50 1d e9 4b 1e da f7 21 00 ff   <--- Receive

For 20 sends, I received the same response. Thus, I can safely say my issues with inconsistent data arrival have been resolved. Now I am struggling with multiple write requests, but I believe that is a separate question to be investigated. I appreciate everyone's support.

对于 20 次发送,我收到了相同的响应。因此,我可以肯定地说,我的数据到达不一致的问题已经解决。现在我正在为多个写入请求而苦苦挣扎,但我相信这是一个需要调查的单独问题。我感谢大家的支持。

回答by kuzulis

  1. See the BlockingMaster example in the repository and read the documentation about the blocking I/O. Also, do not use blocking I/O unnecessarily.

  2. Use bytesAvailable() to get the number of available data for reading, because not the fact that you immediately receive a complete response package.

  1. 请参阅存储库中的 BlockingMaster 示例并阅读有关阻塞 I/O 的文档。另外,不要不必要地使用阻塞 I/O。

  2. 使用 bytesAvailable() 获取可供读取的可用数据的数量,因为不是您立即收到完整响应包的事实。