Qt6基础教程:串口通信与Qt Serial Port模块详解
Qt6基础教程:串口通信与Qt Serial Port模块详解
Qt Serial Port 模块用于串口通信编程,主要提供了包括配置、I/O操作、RS-232引脚控制信号的获取和设置。如果要在一个项目中使用Qt Serial Port模块,需要在项目的配置文件中加入如下语句:
头文件中添加:
#include <QSerialPortInfo> //串口信息#include <QSerialPort> //串口使用cmake时:
find_package(Qt6 REQUIRED COMPONENTS SerialPort)target_link_libraries(mytarget PRIVATE Qt6::SerialPort)使用qmake时:
QT += serialportQt 串口的通信协议比较简单,Qt Serial Port模块中只有两个类:QSerialPortInfo和QSerialPort。
💡注意:Qt Serial Port 不支持 如回声、控制CR/LF等功能
一、QSerialPortInfo
1.QSerialPortInfo 类介绍
QSerialPortInfo 是一个辅助类,用于获取系统中已连接的串口设备信息。它不直接用于通信,而是用来查询串口的名称、描述、制造商等属性,帮助用户选择合适的串口。
通过阅读官方文档可以看到QSerialPortInfo类 有两个静态方法,用于获取系统串口信息:
| 静态方法 | 说明 |
|---|---|
QList<QSerialPortInfo> availablePorts() | 返回当前系统中所有可用串口的信息列表 |
QList<qint32> standardBaudRates() | 返回系统支持的标准波特率列表(单位:bps) |
每个 QSerialPortInfo 对象代表一个具体的串口设备,其成员函数可获取该设备的详细信息:
| 成员函数 | 说明 |
|---|---|
portName() | 串口名称(如 COM1、/dev/ttyUSB0),打开串口时必须使用此值 |
description() | 设备描述文字,适合显示给用户 |
manufacturer() | 制造商名称 |
systemLocation() | 系统路径(不同平台格式不同) |
vendorIdentifier() | USB 设备的供应商 ID(VID),仅 USB 设备有效 |
productIdentifier() | USB 设备的产品 ID(PID) |
serialNumber() | 设备序列号(若有) |
hasVendorIdentifier() / hasProductIdentifier() | 判断是否存在 VID/PID(虚拟串口通常没有) |
isNull() | 判断串口是否为空(无效) |
swap() | 交换两个 QSerialPortInfo 对象的内容 |
2. 跨平台注意事项
- 打开端口:始终使用
portName(),这是唯一跨平台可靠的标识符。 - 设备标识:如需持久化识别设备(如自动连接特定硬件),推荐组合使用
vendorIdentifier()+productIdentifier()+serialNumber()。虚拟串口或非 USB 串口可能缺少这些信息。 - 用户显示:
description()和manufacturer()提供了对用户友好的描述,适合在下拉列表中展示。 - 避免使用
systemLocation():该值在不同平台差异很大,不宜作为设备标识。
3. 实战示例:枚举并显示串口信息
以下代码演示了如何获取所有可用串口,并输出其详细信息(实际应用中通常用于填充下拉列表)。
#include <QCoreApplication>#include <QDebug>#include <QSerialPortInfo>
int main(int argc, char *argv[]){ QCoreApplication a(argc, argv);
qDebug() << "=== 可用串口列表 ==="; const QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts(); // 值拷贝 const QList<QSerialPortInfo> &ports = QSerialPortInfo::availablePorts(); // 引用绑定 const auto ports = QSerialPortInfo::availablePorts(); //使用auto 简化书写 for (const QSerialPortInfo &info : ports) { qDebug() << "-----------------------------"; qDebug() << "端口名:" << info.portName(); qDebug() << "描述:" << info.description(); qDebug() << "制造商:" << info.manufacturer();
// 只有真实 USB 设备才存在 VID/PID,虚拟串口通常没有 if (info.hasVendorIdentifier()) { qDebug() << "VID:" << QString::asprintf("0x%04X", info.vendorIdentifier()); qDebug() << "PID:" << QString::asprintf("0x%04X", info.productIdentifier()); }
qDebug() << "系统位置:" << info.systemLocation(); if (!info.serialNumber().isEmpty()) qDebug() << "序列号:" << info.serialNumber(); }
qDebug() << "=== 标准波特率 ==="; const auto baudRates = QSerialPortInfo::standardBaudRates(); for (qint32 baud : baudRates) { qDebug() << baud; }
return 0;}运行结果示例(Windows 环境):
=== 可用串口列表 ===-----------------------------端口名: "COM1"描述: "通信端口"制造商: "(标准端口类型)"系统位置: "\\\\.\\COM1"-----------------------------端口名: "COM2"描述: "Virtual Serial Port (Eltima Software)"制造商: "ELTIMA Software"系统位置: "\\\\.\\COM2"-----------------------------端口名: "COM3"描述: "Virtual Serial Port (Eltima Software)"制造商: "ELTIMA Software"系统位置: "\\\\.\\COM3"=== 标准波特率 ===110300...115200提示:
QString::asprintf("0x%04X", value)用于将整数格式化为 4 位十六进制字符串,方便查看 VID/PID。格式说明符%04X表示输出至少 4 位十六进制数字,不足左侧补 0。
二、QSerialPort
1.QSerialPort类介绍
QSerialPort 是访问具体某个串口的类,它可以设置串口通信的参数,打开串口后就可以读写串口数据。通过官方文档可以看出QSerialPort 的父类是QIODevice,所以它属于I/O设备类。
QSerialPort的基本使用流程:
设置串口通信参数
打开/关闭串口
进行串口数据的读/写
1.1 设置串口通信参数
串口通信参数主要只考虑波特率、数据位个数、停止位个数和奇偶校验位,在Qt中通过以下函数设置
| 参数 | 设置函数 | 获取函数 |
|---|---|---|
| 波特率 | bool setBaudRate(qint32 baudRate, Directions dir = AllDirections) | qint32 baudRate(Directions dir = AllDirections) const |
| 数据位 | bool setDataBits(QSerialPort::DataBits dataBits) | QSerialPort::DataBits dataBits() const |
| 停止位 | bool setStopBits(QSerialPort::StopBits stopBits) | QSerialPort::StopBits stopBits() const |
| 校验位 | bool setParity(QSerialPort::Parity parity) | QSerialPort::Parity parity() const |
| 流控 | bool setFlowControl(QSerialPort::FlowControl flow) | QSerialPort::FlowControl flowControl() const |
注意:波特率获取函数返回的是
qint32类型,可直接用于显示或比较;其他参数返回对应的枚举类型。
1.1.1 波特率
设置函数:
bool setBaudRate(qint32 baudRate, QSerialPort::Directions directions = AllDirections);baudRate:可直接指定数值(如 9600、115200),也可使用QSerialPort预定义的常量(如QSerialPort::Baud115200)。directions:默认AllDirections,同时设置输入和输出方向。也可单独设置Input或Output(很少用到)。
获取函数:
qint32 baudRate(QSerialPort::Directions directions = AllDirections) const;返回当前波特率数值,可直接用于显示或比较。
1.2.1 数据位
设置函数:
bool setDataBits(QSerialPort::DataBits dataBits);参数 dataBits 是 QSerialPort::DataBits 枚举类型。有两种使用方式:
// 方式一:使用枚举常量(推荐,可读性好)comPort.setDataBits(QSerialPort::Data8); // 8位数据
// 方式二:使用数值构造(等效)comPort.setDataBits(QSerialPort::DataBits(8)); // 8位数据获取函数:
QSerialPort::DataBits dataBits() const;返回当前数据位的枚举值。可通过与枚举常量比较或转换为整型来获取实际位数:
if (comPort.dataBits() == QSerialPort::Data8) { qDebug() << "当前为 8 位数据";}int bits = static_cast<int>(comPort.dataBits()); // bits = 8QSerialPort::DataBits 枚举常量与数值的对应关系如下:
| Constant | Value | Description |
|---|---|---|
QSerialPort::Data5 | 5 | 数据帧里一个字符的数据位数为5 |
QSerialPort::Data6 | 6 | 数据帧里一个字符的数据位数为6 |
QSerialPort::Data7 | 7 | 数据帧里一个字符的数据位数为7 |
QSerialPort::Data8 | 8 | 数据帧里一个字符的数据位数为8 |
1.3.1 停止位
设置函数:
bool setStopBits(QSerialPort::StopBits stopBits);两种使用方式:
// 方式一:使用枚举常量comPort.setStopBits(QSerialPort::OneStop); // 1个停止位
// 方式二:使用数值构造comPort.setStopBits(QSerialPort::StopBits(1)); // 1个停止位获取函数:
QSerialPort::StopBits stopBits() const;返回停止位枚举值,可通过与枚举常量比较或转换为整型:
if (comPort.stopBits() == QSerialPort::OneStop) { qDebug() << "停止位为 1";}int stop = static_cast<int>(comPort.stopBits()); // stop = 1QSerialPort::StopBits 枚举常量与数值的对应关系如下:
| Constant | Value | Description |
|---|---|---|
QSerialPort::OneStop | 1 | 1个停止位。 |
QSerialPort::OneAndHalfStop | 3 | 表示 1.5 个停止位。 |
QSerialPort::TwoStop | 2 | 2个停止位。 |
1.4.1 校验位
设置函数:
bool setParity(QSerialPort::Parity parity);两种使用方式:
// 方式一:使用枚举常量comPort.setParity(QSerialPort::NoParity); // 无校验
// 方式二:使用数值构造comPort.setParity(QSerialPort::Parity(0)); // 无校验获取函数:
QSerialPort::Parity parity() const;返回校验位枚举值,可通过与枚举常量比较或转换为整型:
if (comPort.parity() == QSerialPort::NoParity) { qDebug() << "无校验";}int parityVal = static_cast<int>(comPort.parity()); // parityVal = 0QSerialPort::Parity 枚举常量与数值的对应关系如下:
| Constant | Value | Description |
|---|---|---|
QSerialPort::NoParity | 0 | 无校验 |
QSerialPort::EvenParity | 2 | 偶校验 |
QSerialPort::OddParity | 3 | 奇校验 |
QSerialPort::SpaceParity | 4 | 校验位始终为0 |
QSerialPort::MarkParity | 5 | 校验位始终为1 |
💡注意:在串口通信中 通常默认使用 8个数据位,1个停止位,无奇偶校验位。
1.5.1 设置示例
以下代码展示了两种设置串口参数的方式,两者效果完全相同:
方式一:使用枚举常量(推荐)
QSerialPort comPort;comPort.setPortName("COM3");comPort.setBaudRate(QSerialPort::Baud115200); // 预定义常量comPort.setDataBits(QSerialPort::Data8); // 枚举常量comPort.setStopBits(QSerialPort::OneStop); // 枚举常量comPort.setParity(QSerialPort::NoParity); // 枚举常量方式二:使用数值构造
QSerialPort comPort;comPort.setPortName("COM3");comPort.setBaudRate(115200); // 直接数值comPort.setDataBits(QSerialPort::DataBits(8)); // 数值构造comPort.setStopBits(QSerialPort::StopBits(1)); // 数值构造comPort.setParity(QSerialPort::Parity(0)); // 数值构造两种方式完全等效,可以根据代码风格选择任一种。通常推荐使用枚举常量,因为可读性更好;而数值构造在某些需要动态设置参数的场景下更为灵活。
1.2 打开串口
void setPort(const QSerialPortInfo &serialPortInfo) //设置串口,通过serialPortInfo 设置,void setPortName(const QString &name) //设置串口,通过串口名virtual void close() override //关闭virtual bool open(QIODeviceBase::OpenMode mode) override //打开其中 函数
setPort()以一个QSerialPortInfo 类型变量作为参数。setPortName()以串口名称作为参数, 串口名称来自**QSerialPortInfo::portName()**函数的返回值。
使用open()打开一个串口,参数mode设置打开的模式,只能设置为QIODeviceBase::ReadOnly、 QIODeviceBase::WriteOnly 或 QIODeviceBase::ReadWrite,不能设置为其他模式。
注意,串口总是 以独占方式打开的,也就是其他进程或线程无法访问一个已经被打开的串口。
串口使用结束后,不再需要使用串口通信时,要调用函数close()关闭串口。
1.3 数据读写
QSerialPort 支持 Qt 标准的 I/O 接口:
- 写数据:
write(const QByteArray &data)返回实际写入的字节数。 - 读数据:
readAll()读取所有可用数据,或使用read(char *data, qint64 maxSize)指定读取长度。
接收数据:串口有数据到达时会发射 readyRead() 信号,通常在此信号对应的槽函数中读取数据。
发送数据:直接调用 write(),无需等待信号。
注意:
write()只是将数据放入系统缓冲区,并不保证数据立即发出。如需确保数据发送完成,可调用flush()或waitForBytesWritten()。
1.3 数据读写
打开串口后,就可以通过 QSerialPort 的读写函数进行数据收发。Qt 提供了**异步(非阻塞)和同步(阻塞)**两种方式。在 GUI 程序中推荐使用异步方式,避免界面卡顿;在非 GUI 程序或独立线程中可使用同步方式。
1.3.1 异步读写方式
异步方式下,读写操作立即返回,数据收发完成后通过信号通知应用程序。常用函数和信号如下:
| 类别 | 函数/信号 | 说明 |
|---|---|---|
| 读相关 | qint64 bytesAvailable() | 返回接收缓冲区中等待读取的字节数 |
QByteArray read(qint64 maxSize) | 读取最多 maxSize 个字节 | |
QByteArray readAll() | 读取缓冲区内的全部数据 | |
bool canReadLine() | 判断是否有完整的行数据(以换行符结束) | |
QByteArray readLine(qint64 maxSize = 0) | 读取一行数据,最多 maxSize 字节 | |
| 写相关 | qint64 write(const char *data, qint64 maxSize) | 将 data 的前 maxSize 字节写入串口 |
qint64 write(const char *data) | 写入以 \0 结尾的字符串 | |
qint64 write(const QByteArray &data) | 写入字节数组 | |
| 信号 | void readyRead() | 接收缓冲区有新数据时发射 |
void bytesWritten(qint64 bytes) | 发送缓冲区的数据实际写入串口后发射 |
数据接收:当串口有数据到达时,QSerialPort 会发射 readyRead() 信号。通常在该信号对应的槽函数中调用 read() 或 readAll() 读取数据。
connect(serial, &QSerialPort::readyRead, this, &MyClass::onReadyRead);
void MyClass::onReadyRead(){ QByteArray data = serial->readAll(); // 读取所有可用数据 // 处理数据...}注意:
readyRead()信号不会在每个字节到达时都发射,而是在收到一定量数据或数据流稳定后发射。因此,下位机编程时也应避免逐字节发送,建议攒够一批数据再发送,便于上位机处理。
数据发送:调用 write() 函数将数据放入发送缓冲区,函数立即返回,不会等待数据实际发送完毕。当数据真正通过串口发出后,会发射 bytesWritten() 信号(如果需要确认发送完成,可连接该信号)。
QByteArray sendData = "Hello\n";qint64 written = serial->write(sendData);if (written == sendData.size()) { // 数据已放入发送缓冲区,发送完成后会发射 bytesWritten()}1.3.2 同步(阻塞)读写方式
QSerialPort 提供了两个阻塞式等待函数,用于同步等待数据发送或接收完成:
bool waitForBytesWritten(int msecs = 30000); // 最多等待 msecs 毫秒,直到数据发送完毕bool waitForReadyRead(int msecs = 30000); // 最多等待 msecs 毫秒,直到有数据可读waitForBytesWritten()会阻塞当前线程,直到发送缓冲区中的数据全部写入串口(即bytesWritten信号发射)或超时。waitForReadyRead()会阻塞当前线程,直到接收缓冲区中有新数据到达(即readyRead信号发射)或超时。
使用示例:
serial->write("AT\r\n");if (serial->waitForBytesWritten(1000)) { qDebug() << "数据已发送";}
if (serial->waitForReadyRead(2000)) { QByteArray response = serial->readAll(); qDebug() << "接收到响应:" << response;}重要提示:这两个函数会阻塞当前线程的事件循环。如果在 GUI 线程中调用,可能导致界面无响应。因此,阻塞式读写应在非 GUI 线程(如
QThread)中使用,或仅在控制台程序中使用。
1.3.3 选择建议
| 应用场景 | 推荐方式 | 说明 |
|---|---|---|
| GUI 程序 | 异步(信号槽) | 避免界面卡顿,程序响应性好 |
| 控制台程序/后台服务 | 同步(阻塞) | 代码逻辑简单,易于理解 |
| 需要实时性/频繁收发 | 异步 + 多线程 | 可将串口操作放在独立线程,主线程保持响应 |