3. 环境搭建与第一个Demo:Qt Creator配置串口模块、扫描可用串口、打开/关闭串口、发送Hello World

好,咱们正式开始动手了。这一章的目标很明确——让你在Qt Creator里把串口跑起来。不搞虚的,就是配置环境、扫描串口、打开关闭、发一条"Hello World"。

我刚开始学Qt串口编程的时候,踩过一个坑:明明代码写对了,串口就是打不开。后来发现是.pro文件里忘了加serialport模块。这种低级错误,我相信你肯定不想犯。所以咱们一步步来。

3.1 Qt Creator中启用串口模块

Qt的串口功能不是默认就有的。你需要手动告诉编译器:我要用这个模块。

3.1.1 在.pro文件中添加模块

打开你的.pro文件,找到QT那一行。默认大概是这样的:

QT       += core gui

改成:

QT       += core gui serialport

就这么简单。加上serialport,Qt就知道你要用串口相关的类了。

注意:如果你用的是Qt 5.0之前的版本,串口模块是单独的插件,需要额外安装。不过现在应该没人用那么老的版本了吧?我建议至少Qt 5.12以上。

3.1.2 包含必要的头文件

在需要使用串口的源文件里,加上这两行:

#include <QSerialPort>
#include <QSerialPortInfo>

QSerialPort是串口操作的核心类。QSerialPortInfo用来获取系统里有哪些可用串口。这两个类,基本够你完成90%的串口任务了。

3.2 扫描可用串口

写串口程序,第一步往往是让用户选串口。你不能硬编码一个"COM1"或者"/dev/ttyUSB0",因为不同机器上串口号不一样。

3.2.1 使用QSerialPortInfo扫描

代码很简单:

QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();
foreach (const QSerialPortInfo &info, ports) {
    qDebug() << "Port:" << info.portName();
    qDebug() << "Description:" << info.description();
    qDebug() << "Manufacturer:" << info.manufacturer();
}

这段代码会列出系统里所有可用的串口。每个串口对象info里还带着描述信息、厂商信息等。我个人习惯把这些信息显示在UI的下拉框里,方便用户选择。

小技巧:在Windows上,串口名通常是"COM1"、"COM2"这种。在Linux上,一般是"/dev/ttyUSB0"、"/dev/ttyS0"。Mac OS和Linux类似。你可以在下拉框里同时显示端口名和描述,比如"COM1 - USB Serial Port"。

3.2.2 处理扫描结果为空的情况

有时候扫描结果为空。可能是没插设备,也可能是驱动没装。我曾经在一个项目里遇到过:用户插了USB转串口,但系统没识别到。程序里扫描不到任何串口,用户就懵了。

我的建议是:如果扫描结果为空,弹个提示框,告诉用户检查硬件连接和驱动。别让用户干等。

if (ports.isEmpty()) {
    QMessageBox::warning(this, "提示", "未检测到可用串口,请检查设备连接和驱动。");
}

3.3 打开与关闭串口

扫描到串口之后,下一步就是打开它。打开串口需要设置一些参数,比如波特率、数据位、停止位、校验位等。

3.3.1 创建并配置串口对象

QSerialPort *serial = new QSerialPort(this);
serial->setPortName("COM1");  // 从下拉框获取
serial->setBaudRate(QSerialPort::Baud115200);
serial->setDataBits(QSerialPort::Data8);
serial->setParity(QSerialPort::NoParity);
serial->setStopBits(QSerialPort::OneStop);
serial->setFlowControl(QSerialPort::NoFlowControl);

这些参数要和你的下位机(比如单片机、传感器)匹配。不匹配的话,数据就是乱码,或者根本收不到。

核心参数对照表:
参数常用值说明
波特率9600, 115200, 460800速度越快,但稳定性可能下降
数据位8几乎都用8位
校验位None, Even, OddNone最常用
停止位1, 21位最常用
流控制None, RTS/CTS, XON/XOFF一般不用

3.3.2 打开串口

if (serial->open(QIODevice::ReadWrite)) {
    qDebug() << "串口打开成功";
} else {
    qDebug() << "串口打开失败:" << serial->errorString();
}

打开失败的原因很多:串口被占用、权限不够、设备不存在等。我建议你把错误信息显示给用户,方便排查。

3.3.3 关闭串口

serial->close();

关闭串口很简单。但要注意:程序退出时一定要关闭串口,否则下次可能打不开。我习惯在析构函数里加一句serial->close()。

3.4 发送"Hello World"

串口打开了,该发数据了。咱们发个经典的"Hello World"。

3.4.1 发送字符串

QByteArray data = "Hello World\r\n";
serial->write(data);

这里我加上了\r\n(回车换行)。很多下位机协议以回车换行作为一帧数据的结束。不加的话,对方可能收不到完整数据。

经验之谈:发送数据时,最好用QByteArray而不是直接传字符串。因为串口数据本质上是字节流,QByteArray更灵活,可以处理二进制数据。

3.4.2 检查发送是否成功

qint64 bytesWritten = serial->write(data);
if (bytesWritten == -1) {
    qDebug() << "发送失败:" << serial->errorString();
} else {
    qDebug() << "成功发送" << bytesWritten << "字节";
}

write()返回实际写入的字节数。如果返回-1,说明发送失败。这种情况不多见,但一旦出现,往往是串口已经关闭或者硬件故障。

3.5 完整Demo代码框架

把上面的代码串起来,就是一个完整的Demo。我贴一个简化版的:

// mainwindow.h
#include <QMainWindow>
#include <QSerialPort>
#include <QSerialPortInfo>

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    QSerialPort *serial;
    void scanPorts();
    void openPort(const QString &portName);
    void sendData(const QByteArray &data);
};

// mainwindow.cpp
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    serial = new QSerialPort(this);
    scanPorts();
    openPort("COM1");
    sendData("Hello World\r\n");
}

MainWindow::~MainWindow()
{
    serial->close();
}

void MainWindow::scanPorts()
{
    QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, ports) {
        qDebug() << info.portName();
    }
}

void MainWindow::openPort(const QString &portName)
{
    serial->setPortName(portName);
    serial->setBaudRate(QSerialPort::Baud115200);
    if (serial->open(QIODevice::ReadWrite)) {
        qDebug() << "打开成功";
    }
}

void MainWindow::sendData(const QByteArray &data)
{
    serial->write(data);
}

这个Demo虽然简单,但包含了串口编程的核心流程:扫描→打开→发送→关闭。你可以在它的基础上扩展,比如加上接收功能、UI界面等。

3.6 本章知识结构图

下面这张图帮你理清串口编程的基本流程:

串口编程核心流程 1. 配置环境 2. 扫描可用串口 3. 打开串口 4. 发送数据 5. 关闭串口 提示:每一步都可能出错,建议在代码中加入错误处理和日志输出

3.7 常见问题与避坑指南

  • 串口打不开? 检查.pro文件有没有加serialport,检查串口是否被其他程序占用(比如串口助手)。
  • 发送数据没反应? 检查波特率、数据位等参数是否匹配。我曾经调了半天,发现是波特率设成了9600,下位机是115200。
  • 扫描不到串口? 检查驱动。Windows上可以去设备管理器看有没有未知设备。Linux上可以用ls /dev/tty*查看。
  • 程序退出后串口还占着? 确保在析构函数或关闭事件里调用了serial->close()。
重要:串口操作是异步的。write()只是把数据写入发送缓冲区,并不保证数据已经发出。如果需要确认发送完成,可以连接bytesWritten信号。这个后面章节会详细讲。