You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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信号未触发?是否有更优的串口模拟方案?


问题分析与解决

核心问题点

  1. Qt事件循环未运行:Qt的信号槽机制依赖事件循环,你的测试代码是同步执行的,发送"Hello"后立刻关闭串口并断言,此时接收端的readyRead信号还没来得及被处理——事件循环没机会运行。
  2. 接收逻辑的格式判断拦截了测试数据:你发送的"Hello"不符合正式消息格式(无4字节长度前缀、无0x00结尾),即使readyRead触发,lastMessage也会因为不满足判断条件而无法保留数据。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 19:50:17