qt5-serialport解析

📅 2026/7/11 20:48:20
qt5-serialport解析
qt5-serialport是 Qt5 专门用于串行通信的附加模块封装了 RS-232 / RS-485 / USB 转串口等底层操作提供QSerialPort收发数据和QSerialPortInfo枚举串口信息两个核心类自 Qt 5.1 起正式引入。它继承自QIODevice因此可以像文件读写一样操作串口并天然融入 Qt 的事件循环体系配合信号槽实现异步非阻塞通信。模块定位与跨平台支持平台串口设备路径示例底层驱动WindowsCOM1、COM3Win32 APILinux/dev/ttyS0、/dev/ttyUSB0termiosmacOS/dev/cu.usbserial-*IOKit同一套 Qt 代码可在上述三个平台运行无需改动。模块归属为 Qt Add-Ons不在qt5-base中需要单独通过 vcpkg 安装。两个核心类的职责划分setPort(info) 传入QSerialPort 数据收发setPortName 配置端口setBaudRate 等参数配置open 打开串口write / readAll 收发数据readyRead 信号异步通知QSerialPortInfo 信息探测availablePorts 静态枚举portName / descriptionmanufacturer / serialNumbervendorIdentifier / productIdentifierisBusy 判断占用QSerialPortInfo负责发现有哪些串口QSerialPort负责打开某个串口并通信前者可作为后者的构造或setPort()参数。核心配置参数枚举对照QSerialPort提供五组关键串口参数每组对应一个枚举类型参数枚举类型可选值常用默认波特率BaudRateBaud1200 / Baud2400 / Baud4800 /Baud9600/ Baud19200 / Baud38400 / Baud57600 /Baud115200/ UnknownBaud9600 或 115200数据位DataBitsData5 / Data6 / Data7 /Data8/ UnknownDataBitsData8校验位ParityNoParity/ EvenParity / OddParity / SpaceParity / MarkParity / UnknownParityNoParity停止位StopBitsOneStop/ OneAndHalfStop / TwoStop / UnknownStopBitsOneStop流控FlowControlNoFlowControl/ HardwareControlRTS/CTS/ SoftwareControlXON/XOFF/ UnknownFlowControlNoFlowControl工业设备常用 9600/19200 波特率嵌入式开发板多为 115200参数必须与对端设备完全一致否则会出现乱码或无法通信。关键信号与异步机制QSerialPort继承自QIODevice提供以下信号支撑事件驱动通信信号触发时机典型用途readyRead()接收缓冲区有新数据到达异步读取数据最常用bytesWritten(qint64)数据已写入串口确认发送完成errorOccurred(SerialPortError)发生通信错误错误处理与重连baudRateChanged()/dataBitsChanged()等属性变更信号参数被修改动态参数响应对于阻塞式场景Qt 也提供waitForReadyRead(ms)和waitForBytesWritten(ms)两个同步等待函数。SerialPortError枚举包含常见错误类型NoError、DeviceNotFoundError、PermissionError、OpenError、ResourceError参数不匹配常报此错等。完整工程示例串口通信管理类下面是一个封装了枚举—配置—打开—收发—错误处理完整流程的SerialManager类。SerialManager.h#ifndefSERIALMANAGER_H#defineSERIALMANAGER_H#includeQObject#includeQSerialPort#includeQSerialPortInfo#includeQByteArrayclassSerialManager:publicQObject{Q_OBJECTpublic:explicitSerialManager(QObject*parentnullptr);~SerialManager();// 1. 枚举可用串口staticQListQSerialPortInfolistAvailablePorts();// 2. 配置并打开串口boolopenPort(constQStringportName,qint32 baudRateQSerialPort::Baud115200,QSerialPort::DataBits dataBitsQSerialPort::Data8,QSerialPort::Parity parityQSerialPort::NoParity,QSerialPort::StopBits stopBitsQSerialPort::OneStop,QSerialPort::FlowControl flowControlQSerialPort::NoFlowControl);voidclosePort();boolisOpen()const;// 3. 发送数据qint64sendData(constQByteArraydata);signals:// 4. 对外暴露的信号voiddataReceived(constQByteArraydata);voiderrorOccurred(constQStringerrorString);voidportOpened(constQStringportName);voidportClosed();privateslots:voidhandleReadyRead();voidhandleError(QSerialPort::SerialPortError error);private:QSerialPort*m_serialPort;QByteArray m_receiveBuffer;// 接收缓冲区解决分包问题};#endifSerialManager.cpp#includeSerialManager.h#includeQDebugSerialManager::SerialManager(QObject*parent):QObject(parent),m_serialPort(newQSerialPort(this)){connect(m_serialPort,QSerialPort::readyRead,this,SerialManager::handleReadyRead);connect(m_serialPort,QSerialPort::errorOccurred,this,SerialManager::handleError);}SerialManager::~SerialManager(){closePort();}// 1. 枚举系统所有可用串口QListQSerialPortInfoSerialManager::listAvailablePorts(){returnQSerialPortInfo::availablePorts();}// 2. 配置并打开boolSerialManager::openPort(constQStringportName,qint32 baudRate,QSerialPort::DataBits dataBits,QSerialPort::Parity parity,QSerialPort::StopBits stopBits,QSerialPort::FlowControl flowControl){if(m_serialPort-isOpen())m_serialPort-close();m_serialPort-setPortName(portName);m_serialPort-setBaudRate(baudRate);m_serialPort-setDataBits(dataBits);m_serialPort-setParity(parity);m_serialPort-setStopBits(stopBits);m_serialPort-setFlowControl(flowControl);m_serialPort-setReadBufferSize(1024);// 设置接收缓冲区if(m_serialPort-open(QIODevice::ReadWrite)){m_receiveBuffer.clear();emitportOpened(portName);returntrue;}returnfalse;}voidSerialManager::closePort(){if(m_serialPort-isOpen()){m_serialPort-close();emitportClosed();}}boolSerialManager::isOpen()const{returnm_serialPort-isOpen();}// 3. 发送qint64SerialManager::sendData(constQByteArraydata){if(!m_serialPort-isOpen())return-1;returnm_serialPort-write(data);}// 4. 异步接收累积缓冲区按协议解析完整帧voidSerialManager::handleReadyRead(){while(m_serialPort-bytesAvailable()0){m_receiveBuffer.append(m_serialPort-readAll());}// 假设协议格式帧头 0xAA 长度(1B) 数据 CRC(2B) 帧尾 0x55while(m_receiveBuffer.size()5){if(quint8(m_receiveBuffer.at(0))!0xAA){m_receiveBuffer.remove(0,1);// 丢弃非帧头字节continue;}intframeLenquint8(m_receiveBuffer.at(1))4;// 长度字段帧头帧尾CRCif(m_receiveBuffer.size()frameLen)break;// 数据未收全等待下次 readyReadQByteArray framem_receiveBuffer.left(frameLen);if(quint8(frame.at(frameLen-1))0x55){emitdataReceived(frame);}m_receiveBuffer.remove(0,frameLen);}}voidSerialManager::handleError(QSerialPort::SerialPortError error){if(error!QSerialPort::NoError){emiterrorOccurred(m_serialPort-errorString());}}调用示例main.cpp#includeQCoreApplication#includeSerialManager.hintmain(intargc,char*argv[]){QCoreApplicationa(argc,argv);SerialManager manager;// 打印所有可用串口for(constautoinfo:SerialManager::listAvailablePorts()){qDebug()info.portName()info.description()info.manufacturer()info.serialNumber();}// 打开 COM3115200 8N1if(manager.openPort(COM3,QSerialPort::Baud115200)){qDebug()Opened successfully;QObject::connect(manager,SerialManager::dataReceived,[](constQByteArraydata){qDebug()Frame received:data.toHex( );});manager.sendData(Hello MCU\r\n);}returna.exec();}项目集成.pro文件中添加QT serialportCMake 中使用find_package(Qt5 COMPONENTS SerialPort REQUIRED)并链接Qt5::SerialPort。常见问题排查① readyRead 多次触发导致数据分包这是串口通信最常见的问题。readyRead信号只表示有数据到达不保证一次性收到完整一帧——Linux TTY 驱动可能把多帧合并为一次通知Windows 可能把一帧拆成多次。错误做法是直接readAll()并立即解析正确做法是用成员变量QByteArray累积缓冲再按帧头/长度/帧尾或超时机制提取完整帧。② 串口打不开 / PermissionErrorWindows串口已被其他程序如串口调试助手占用关闭占用进程即可。Linux普通用户无权限访问/dev/ttyUSB*需将用户加入dialout组sudo usermod -aG dialout $USER或配置 udev 规则。③ 收到乱码或 ResourceError多为参数不匹配——波特率、校验位、数据位任一与设备不一致都会导致。例如外设用 Even 校验而 Qt 设为 None每帧第 9 位会被误判为数据产生乱码。打开串口后应通过port-error()校验若返回ResourceError通常是参数问题而非权限问题。④ 高波特率下丢包Qt5 默认内核缓冲区仅 4096 字节1Mbps 速率下 10ms 未读即溢出。解决方案分三级启用硬件流控HardwareControl需外设支持 RTS/CTS、软件流控SoftwareControlXON/XOFF、或在应用层readyRead中检测bytesAvailable() 8192时暂停读取形成背压。⑤ 主线程阻塞导致 UI 卡顿不要在 GUI 线程直接做大数据量readAll()。正确做法是将QSerialPort实例通过moveToThread()移到工作线程且QSerialPort必须与调用readAll()的线程同属一个QThread。工程化封装建议配置层把串口参数端口名、波特率、校验位等封装为结构体支持从配置文件或 UI 读取。通信层SerialManager负责 raw 字节收发与缓冲管理对外只暴露dataReceived(QByteArray)信号不涉及业务协议。协议层在dataReceived之上做帧解析、CRC 校验、指令封装与通信层解耦。应用层根据解析结果更新 UI 或执行业务逻辑。这种分层结构便于复用同一套SerialManager可对接 Modbus、自定义协议、NMEA 等不同上层协议。