Linux下UART串口模拟失败排查:readyRead信号未触发
问题:Qt串口测试中readyRead信号未触发,虚拟串口通信失败
我在做一个课外项目,需要把单片机从CAN总线读取的数据通过UART发送到运行Arch Linux的BananaPi Zero M2。为了避免频繁部署测试,用socat创建了虚拟串口/tmp/banana和/tmp/tango来做Qt串口通信测试,但测试时readyRead信号始终不触发,接收端拿不到发送的消息。
测试代码
void Tst_serialport::sanityCheck(){ socat.startDetached("socat -d -d pty,rawer,b115200,link=/tmp/banana, pty,rawer,b115200,link=/tmp/tango"); sleep(1); _store = new store("/tmp/banana"); QCOMPARE(_store->dev,"/tmp/banana"); } void Tst_serialport::checkSendMessage(){ QSerialPort tango; tango.setPortName("/tmp/tango"); tango.setBaudRate(QSerialPort::Baud115200); tango.setDataBits(QSerialPort::Data8); tango.setParity(QSerialPort::NoParity); tango.setStopBits(QSerialPort::OneStop); tango.setFlowControl(QSerialPort::NoFlowControl); tango.open(QIODevice::ReadWrite); tango.write("Hello"); tango.waitForBytesWritten(); tango.close(); QCOMPARE(_store->lastMessage,"Hello"); } void Tst_serialport::closeHandle(){ socat.close(); } QTEST_MAIN(Tst_serialport)
串口接收逻辑代码
int store::setupSerial() { QSerialPort* serial= new QSerialPort(); serial->setPortName(this->dev); serial->setBaudRate(QSerialPort::Baud115200); serial->setDataBits(QSerialPort::Data8); serial->setStopBits(QSerialPort::OneStop); serial->setParity(QSerialPort::NoParity); serial->setFlowControl(QSerialPort::NoFlowControl); if (!serial->open(QIODevice::ReadOnly)) { qDebug() << "Can't open " << this->dev << ", error code" << serial->error(); return 1; } this->port = serial; connect(this->port, &QSerialPort::readyRead, this, &store::handleReadyRead); connect(this->port, &QSerialPort::errorOccurred, this, &store::handleError); return 0; } store::store( char * dev, QObject *parent ): QObject(parent){ if (dev == nullptr){ // TODO: fix this(use a better function preferably one handled by QT) int len = sizeof(char)*strlen(DEFAULT_DEVICE)+1; this->dev = (char*)malloc(len); strcpy(this->dev,DEFAULT_DEVICE); } //copy dev to this->dev else{ int len = sizeof(char)*strlen(dev)+1; this->dev = (char*)malloc(len); strcpy(this->dev,dev); } setupSerial(); } void store::handleReadyRead(){ bufferMessage=port->readAll(); serialLog.append(bufferMessage); //can be optimized using pointers or even a variable as a "bookmark" wether a int or pointer lastMessage.append(bufferMessage); uint32_t size = (int)lastMessage[0] | (int)lastMessage[1] << 8 | (int)lastMessage[2] << 16 | (int)lastMessage[3] << 24; int8_t eof = 0x00; if((bool)((long unsigned int)lastMessage.size() == size+sizeof(size)+sizeof(eof))&& ((bool) lastMessage[lastMessage.size()-1] == eof)){ parseJson(); //clear lastMessage() lastMessage.clear(); } } //... some other code here
测试输出
11:23:40: Starting /home/micron/sav/Trabalhos/2022-2023/FormulaStudent/VolanteAlphaQT/build-VolanteAlphaQT-Desktop-Testing/bin/VolanteAlphaQT_testes... ********* Start testing of Tst_serialport ********* Config: Using QtTest library 5.15.8, Qt 5.15.8 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 12.2.1 20230201), arch unknown PASS : Tst_serialport::initTestCase() 2023/02/15 11:23:40 socat[6248] N PTY is /dev/pts/2 2023/02/15 11:23:40 socat[6248] N PTY is /dev/pts/3 2023/02/15 11:23:40 socat[6248] N starting data transfer loop with FDs [5,5] and [7,7] PASS : Tst_serialport::sanityCheck() FAIL! : Tst_serialport::checkSendMessage() Compared values are not the same Actual (_store->lastMessage): "" Expected ("Hello") : Hello Loc: [../VolanteAlphaQT_1/test/tst_serialport.cpp(35)] PASS : Tst_serialport::closeHandle() PASS : Tst_serialport::cleanupTestCase() Totals: 4 passed, 1 failed, 0 skipped, 0 blacklisted, 1005ms ********* Finished testing of Tst_serialport ********* 11:23:41: /home/micron/sav/Trabalhos/2022-2023/FormulaStudent/VolanteAlphaQT/build-VolanteAlphaQT-Desktop-Testing/bin/VolanteAlphaQT_testes exited with code 1
疑问
问题出在哪里?为何readyRead信号未触发?是否有更优的串口模拟方案?
问题分析与解决
核心问题点
- Qt事件循环未运行:Qt的信号槽机制依赖事件循环,你的测试代码是同步执行的,发送"Hello"后立刻关闭串口并断言,此时接收端的
readyRead信号还没来得及被处理——事件循环没机会运行。 - 接收逻辑的格式判断拦截了测试数据:你发送的"Hello"不符合正式消息格式(无4字节长度前缀、无0x00结尾),即使
readyRead触发,lastMessage也会因为不满足判断条件而无法保留数据。 - socat命令的冗余逗号:命令中
link=/tmp/banana,后面多了个逗号,虽不影响功能,但可能引发潜在问题。
解决步骤
1. 让测试代码运行Qt事件循环
在发送数据后,给事件循环留时间处理信号:
void Tst_serialport::checkSendMessage(){ QSerialPort tango; tango.setPortName("/tmp/tango"); tango.setBaudRate(QSerialPort::Baud115200); tango.setDataBits(QSerialPort::Data8); tango.setParity(QSerialPort::NoParity); tango.setStopBits(QSerialPort::OneStop); tango.setFlowControl(QSerialPort::NoFlowControl); tango.open(QIODevice::ReadWrite); tango.write("Hello"); tango.waitForBytesWritten(); // 让事件循环运行,处理readyRead信号 QCoreApplication::processEvents(QEventLoop::AllEvents, 500); tango.close(); QCOMPARE(_store->lastMessage,"Hello"); }
2. 临时调整接收逻辑适配测试数据
注释掉格式判断代码,先验证接收功能:
void store::handleReadyRead(){ bufferMessage=port->readAll(); serialLog.append(bufferMessage); lastMessage.append(bufferMessage); // 临时注释格式判断,先验证接收 /* uint32_t size = (int)lastMessage[0] | (int)lastMessage[1] << 8 | (int)lastMessage[2] << 16 | (int)lastMessage[3] << 24; int8_t eof = 0x00; if((bool)((long unsigned int)lastMessage.size() == size+sizeof(size)+sizeof(eof))&& ((bool) lastMessage[lastMessage.size()-1] == eof)){ parseJson(); lastMessage.clear(); } */ }
测试通过后,恢复正式格式判断,同时测试时发送符合格式的消息。
3. 优化socat命令
去掉冗余逗号,用更简洁的参数:
socat -d -d pty,rawer,b115200,link=/tmp/banana pty,rawer,b115200,link=/tmp/tango
更优的串口模拟方案
- Qt内部模拟:跳过虚拟串口,直接在测试代码中用信号槽连接发送端和接收端,适合单元测试,不需要外部工具。
- tty0tty驱动:Linux专用虚拟串口驱动,创建真正的串口对(如
/dev/ttyACM0和/dev/ttyACM1),比socat更稳定,无需额外进程维持。 - 优化socat参数:用
raw,echo=0替代rawer,减少终端处理干扰:socat -d -d pty,raw,echo=0,b115200,link=/tmp/banana pty,raw,echo=0,b115200,link=/tmp/tango
内容的提问来源于stack exchange,提问作者Imeguras
相关产品推荐
相关产品推荐

