3182 字
16 分钟
阅读量:

Qt6基础教程:串口通信与Qt Serial Port模块详解

🤖AI 摘要
AI

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 += serialport

Qt 串口的通信协议比较简单,Qt Serial Port模块中只有两个类:QSerialPortInfoQSerialPort

💡注意: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 环境):

Terminal window
=== 可用串口列表 ===
-----------------------------
端口名: "COM1"
描述: "通信端口"
制造商: "(标准端口类型)"
系统位置: "\\\\.\\COM1"
-----------------------------
端口名: "COM2"
描述: "Virtual Serial Port (Eltima Software)"
制造商: "ELTIMA Software"
系统位置: "\\\\.\\COM2"
-----------------------------
端口名: "COM3"
描述: "Virtual Serial Port (Eltima Software)"
制造商: "ELTIMA Software"
系统位置: "\\\\.\\COM3"
=== 标准波特率 ===
110
300
...
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,同时设置输入和输出方向。也可单独设置 InputOutput(很少用到)。

获取函数:

qint32 baudRate(QSerialPort::Directions directions = AllDirections) const;

返回当前波特率数值,可直接用于显示或比较。

1.2.1 数据位#

设置函数:

bool setDataBits(QSerialPort::DataBits dataBits);

参数 dataBitsQSerialPort::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 = 8

QSerialPort::DataBits 枚举常量与数值的对应关系如下:

ConstantValueDescription
QSerialPort::Data55数据帧里一个字符的数据位数为5
QSerialPort::Data66数据帧里一个字符的数据位数为6
QSerialPort::Data77数据帧里一个字符的数据位数为7
QSerialPort::Data88数据帧里一个字符的数据位数为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 = 1

QSerialPort::StopBits 枚举常量与数值的对应关系如下:

ConstantValueDescription
QSerialPort::OneStop11个停止位。
QSerialPort::OneAndHalfStop3表示 1.5 个停止位。
QSerialPort::TwoStop22个停止位。
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 = 0

QSerialPort::Parity 枚举常量与数值的对应关系如下:

ConstantValueDescription
QSerialPort::NoParity0无校验
QSerialPort::EvenParity2偶校验
QSerialPort::OddParity3奇校验
QSerialPort::SpaceParity4校验位始终为0
QSerialPort::MarkParity5校验位始终为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::ReadOnlyQIODeviceBase::WriteOnlyQIODeviceBase::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 程序异步(信号槽)避免界面卡顿,程序响应性好
控制台程序/后台服务同步(阻塞)代码逻辑简单,易于理解
需要实时性/频繁收发异步 + 多线程可将串口操作放在独立线程,主线程保持响应
Qt6基础教程:串口通信与Qt Serial Port模块详解
https://www.daitcc.top/posts/4b316c3d/
作者
Dait
发布于
2026-03-29
许可协议
CC BY-NC-SA 4.0
如果这篇文章对你有帮助或启发,可以请作者喝杯咖啡 ☕️