一. 串口(QSerialPort)

1. 定义

串口(UART,通用异步收发传输器),是硬件异步串行通信接口,通过 TX 发送引脚、RX 接收引脚,按字节逐位收发数据。
Qt 中使用QSerialPort类实现串口收发,QSerialPortInfo用来查询电脑上所有可用串口。

常用于:单片机 (STM32/51) ↔ PC 上位机通信,典型硬件:CH340、PL2303 USB 转串口芯片。

2. 工作模式

1. 发送数据工作流程

    1. 配置串口参数:端口名、波特率、数据位、停止位、校验位。单片机和 Qt 上位机参数必须完全一致。
    1. 打开串口(读写模式)。
    1. 将待发送字符串 / 字节数组写入串口缓冲区。
    1. 硬件自动按位从 TX 引脚输出数据,发送给单片机。
    1. 发送完成后缓冲区清空,可继续下一次发送。

2. 接收数据工作流程

    1. 同样配置串口参数,打开串口。
    1. 单片机从 TX 引脚发送数据到电脑串口 RX 引脚。
    1. 串口收到数据,存入 Qt 内部接收缓冲区,触发readyRead()信号。
    1. 在readyRead()槽函数调用读取接口,取出缓冲区数据。
    1. 处理数据(显示、解析)。

3. 常用函数

常用函数

函数作用
setPortName(QString name)设置串口名,Windows如COM3
setBaudRate(qint32 baud)设置波特率(9600、115200)
setDataBits(DataBits)设置数据位,常用Data8(8位)
setStopBits(StopBits)设置停止位,OneStop(1位)
setParity(Parity)校验位,NoParity无校验
open(QIODevice::ReadWrite)打开串口,读写模式
close()关闭串口,释放资源
write(QByteArray)发送数据,写入串口
readAll()接收数据,一次性读取缓冲区全部字节
isOpen()bool,判断串口是否打开

常用信号

信号触发时机
readyRead()串口收到数据,可以读取
errorOccurred()串口出错(串口被拔出、参数错误)

4. 代码示例

#include <QSerialPort>
#include <QSerialPortInfo>
#include <QMessageBox>

QSerialPort *serial;

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    serial = new QSerialPort(this);

    // 接收数据信号绑定
    connect(serial, &QSerialPort::readyRead, this, [=](){
        // 读取收到的串口数据
        QByteArray recvBuf = serial->readAll();
        // recvBuf就是单片机上传的数据
    });

    // 串口错误信号
    connect(serial, &QSerialPort::errorOccurred, this, [=](){
        QMessageBox::warning(this,"串口错误",serial->errorString());
    });
}

// 打开串口按钮槽函数
void MainWindow::on_openSerialBtn_clicked()
{
    serial->setPortName("COM3");         // 修改为你的设备串口号
    serial->setBaudRate(QSerialPort::Baud9600);
    serial->setDataBits(QSerialPort::Data8);
    serial->setStopBits(QSerialPort::OneStop);
    serial->setParity(QSerialPort::NoParity);

    bool ok = serial->open(QIODevice::ReadWrite);
    if(!ok)
    {
        QMessageBox::warning(this,"失败","串口打开失败!");
    }
}

// 发送按钮槽函数:Qt发送数据给单片机
void MainWindow::on_sendBtn_clicked()
{
    if(serial->isOpen())
    {
        serial->write("hello mcu\r\n");
    }
}

// 关闭串口按钮
void MainWindow::on_closeSerialBtn_clicked()
{
    serial->close();
}

二. QT启动新窗口

1. 新建文件

1. 右键创建新文件
1
2. 选择文件类型
2
3. 选择界面类型
1
4. 设置界面名称,此时,头文件,源文件,ui文件均会创建。
1
5. 点击完成
1
6. 完成创建
1

2. 定义

Qt 启动新窗口,就是在当前主窗口运行期间,创建一个新的窗口类对象并让它显示出来。新窗口可以继承三类基类:

基类特点显示方式
QWidget普通窗口只能 show()(非模态)
QDialog对话框show() 非模态 或 exec() 模态
QMainWindow独立主窗口(带菜单栏/状态栏)只能 show()

3. 常用函数

函数作用
show()非模态显示,不阻塞代码,三类窗口都能用
exec()模态显示,阻塞代码,仅 QDialog 可用,关闭后返回结果
open()窗口级模态,只阻塞父窗口,异步不阻塞
close()关闭窗口
accept()确定关闭,exec() 返回 Accepted
reject()取消关闭,exec() 返回 Rejected
setModal(bool)设置模态 / 非模态
deleteLater()窗口关闭后延迟销毁对象,防止内存泄漏

4. 流程

① 新建窗口类:NewWin 继承 QWidget/QDialog;.pro 加入 cpp/h/ui;ui 里拖控件
② 包含头文件:主窗口 .h 里 #include "newwin.h"
③ 声明槽函数:主窗口 .h 中 private slots: void openNewWin();
④ 实现槽:new NewWin(this) 再 show()(非模态)或 exec()(模态,仅 QDialog)
⑤ 绑定触发:按钮 clicked 信号 connect 到 openNewWin()
⑥ 运行结果:点击按钮 → 事件循环显示新窗口,程序不退出
1

5. 示例代码

方式一:非模态(新窗口继承 QWidget,可同时操作主窗口)

// ===== 新窗口类 NewWin.h =====
#include <QWidget>
class NewWin : public QWidget {
    Q_OBJECT
public:
    explicit NewWin(QWidget *parent = nullptr);
};
// NewWin.cpp 构造函数里 ui->setupUi(this) 即可

// ===== 主窗口 widget.h =====
#include "newwin.h"      // ② 包含头文件
...
private slots:
    void openNewWin();   // ③ 声明槽

// ===== 主窗口 widget.cpp =====
// 构造函数中绑定触发(⑤)
connect(ui->openBtn, &QPushButton::clicked, this, &Widget::openNewWin);

// ④ 实现槽:非模态
void Widget::openNewWin()
{
    NewWin *w = new NewWin(this);  // 指定父对象,自动回收
    w->show();                     // 非模态,不阻塞
}

方式二:模态(新窗口继承 QDialog,弹出后主窗口不能操作)

#include "loginDialog.h"
...
void Widget::openLogin()
{
    loginDialog dlg(this);          // 栈对象即可(exec 会阻塞)
    int ret = dlg.exec();           // 模态,代码阻塞到关闭
    if(ret == QDialog::Accepted){
        qDebug() << "点击确定";
    }else{
        qDebug() << "点击取消";
    }
}

6. 注意事项

显示方式创建方式原因
非模态 show()必须 new(堆)show() 不阻塞代码,函数一执行完就返回;如果用的是栈对象,对象立刻被销毁,窗口一闪而过
模态 exec()可以用栈对象exec() 会阻塞到窗口关闭才返回,函数执行期间栈对象一直存活,不会被销毁

三. 多线程

选择class,既会创建一个头文件,又会创建一个源文件。
1

1. 定义

多线程 = 一个进程内同时运行多个线程,每个线程独立执行一段代码。Qt 中主要靠继承 QThread、重写 run() 来实现子线程。
关键概念:

  • 主线程(GUI 线程):负责界面显示和事件循环,所有 UI 操作都在这里
  • 子线程(工作线程):负责耗时任务(网络收发、大文件读写、复杂计算)

2. 作用

  1. 耗时操作不卡界面:网络收发、文件读写、计算放到子线程,主窗口不会假死
  2. 并行执行:多个任务同时跑,提升效率
  3. 后台持续运行:持续监听数据、定时采集(如 socket 一直等 readyRead)
  4. UI 线程安全:界面只归主线程改,子线程通过信号把数据传回来

3. 常用函数

函数作用
start()启动线程,自动调用 run(),立即返回不阻塞
run()线程入口,子线程任务代码写在这里(必须重写)
quit()退出线程事件循环(线程里有 exec() 时用)
exit(int code)退出事件循环并返回退出码
wait()阻塞等待线程执行完毕
isRunning()bool,线程是否在运行
msleep(int)线程内静态休眠,毫秒
sleep(int)线程内静态休眠,秒

4. 流程

  1. new 线程对象(主线程):myThread *t = new myThread(socket);
  2. connect 线程信号 → 主窗口槽(主线程):connect(t, &myThread::sendToWidget, this, &Widget::threadSlot);
  3. t->start(); 启动线程(主线程):立即返回,run() 开始在新线程跑
  4. run() 执行任务(子线程):重写的 run() 里放耗时代码,如持续监听 readyRead
  5. emit 信号发数据(子线程):收到数据后 emit sendToWidget(ba);
  6. 槽函数更新 UI(主线程):threadSlot 执行,run 返回后发 finished 信号,线程退出

5. 示例代码

// ===== myThread.h =====
#include <QThread>
#include <QTcpSocket>

class myThread : public QThread
{
    Q_OBJECT
public:
    explicit myThread(QTcpSocket *s);
    void run();                      // 重写:子线程入口

signals:
    void sendToWidget(QByteArray b); // 自定义信号:往主线程传数据

public slots:
    void clientInfoSlot();

private:
    QTcpSocket *socket;
};

// ===== myThread.cpp =====
#include "mythread.h"

myThread::myThread(QTcpSocket *s)    // 构造函数:主线程执行
{
    socket = s;
}

void myThread::run()                 // 子线程执行
{
    connect(socket, &QTcpSocket::readyRead, this, &myThread::clientInfoSlot);
}

void myThread::clientInfoSlot()      // 子线程执行
{
    QByteArray ba = socket->readAll();
    emit sendToWidget(ba);           // 信号发回主线程
}

// ===== widget.cpp(主线程)=====
void Widget::newClientHandler()
{
    QTcpSocket *socket = server->nextPendingConnection();
    myThread *t = new myThread(socket);   // ① new 线程对象
    connect(t, &myThread::sendToWidget, this, &Widget::threadSlot); // ② 关联信号
    t->start();                           // ③ 启动线程
}

void Widget::threadSlot(QByteArray b)    // ⑥ 主线程更新 UI
{
    ui->mainLineEdit->setText(QString(b));
}

四. 自定义信号

1. 定义

用户在自己类里用 signals: 关键字声明的信号,配合 emit 发射、connect 连接槽函数,实现对象间通信。它是 Qt 信号槽机制的核心

2. 作用

  1. 跨线程传数据:子线程收数据 → emit 信号 → 主线程槽更新 UI
  2. 解耦通信:发送方不关心谁接收,接收方自己 connect 就行
  3. 一对多通知:一个信号可以连多个槽,发射一次全部触发
  4. 状态变化通知:数据到达、进度更新、错误发生、任务完成

3. 示例代码

// ===== myThread.h =====
#include <QThread>
#include <QTcpSocket>

class myThread : public QThread
{
    Q_OBJECT
public:
    explicit myThread(QTcpSocket *s);
    void run();

signals:
    void sendToWidget(QByteArray b);   // ① 声明自定义信号(带参数)

private:
    QTcpSocket *socket;
};

// ===== myThread.cpp =====
void myThread::run()
{
    connect(socket, &QTcpSocket::readyRead, this, &myThread::onReadyRead);
}

void myThread::onReadyRead()
{
    QByteArray ba = socket->readAll();
    emit sendToWidget(ba);              // ③ 发射信号,把数据带出去
}

// ===== widget.cpp =====
connect(t, &myThread::sendToWidget, this, &Widget::threadSlot);  // ② 连接
void Widget::threadSlot(QByteArray b)   // ⑤ 槽函数接收参数
{
    ui->mainLineEdit->setText(QString(b));
}

五. 文件结构

1. 编译文件

编译时,Qt Creator 自动创建的文件。
编译时把所有生成的中间文件、exe 都扔在这里,不污染源码文件夹
1

2. 编译文件内容

1
1

3. 生成release文件

参考步骤:
1

第 1 步:编译 Release 版

  1. 用 Qt Creator 打开 TCPClient 项目
  2. 左下角(竖着的套件选择器)点一下,把 Debug 切换成 Release
  3. 点绿色三角运行(或 Ctrl+Shift+B 构建)
  4. 等它编完,出现对应的release文件:
    1
    1

第 2 步:windeployqt 部署 DLL

打开 PowerShell,执行这两条:

cd "E:\QT_Project\Study_project\Study_files\QtOnline\build-TCPClient-Desktop_Qt_5_12_12_MinGW_64_bit-Release\release"
D:\Qt\Qt5.12.12\5.12.12\mingw73_64\bin\windeployqt.exe TCPClient.exe

出现了对应的dll文件,表明完成:
1

第 3 步:自测 + 打包发送

  1. 双击 TCPClient.exe,确认窗口正常弹出
  2. 把整个 release 文件夹压成 zip(连 DLL 一起,不是只发 exe!)
  3. 发给朋友 → 对方解压 → 双击 exe 就能用,不需要装 Qt
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐