Serial Port
Qt 提供了串口类,可以直接对串口访问。我们可以直接使用 Qt 的串口类编程即可,十分方便。Qt 串口类不仅在 Windows 能用,还能在 Linux 下用,虽然串口编程不是什么新鲜事儿,既然 Qt 提供了这方面的接口,我们就充分利用起来,这将会使我们的开发十分方便!其实 Qt也提供了相关的 Qt 串口的例子,我们也可以直接参考来编程,笔者根据实际情况,化繁为易,直接写了个简单的例子给大家参考
串口调试工具:
资源简介
在 STM32 开发板的出厂系统里,默认已经配置了三路串口可用。一路是调试串口 UART4(对应系统里的节点/dev/ttySTM0),一路是 UART3(对应系统里的节点/dev/ttySTM1),另一路是 UART5(对应系统里的节点/dev/ ttySTM2),由于 UART4 已经作为调试串口被使用。所以我们只能对 UART5/UART3 编程,(如需要使用多路串口,请自行设计底板与系统)。
应用实例
项目简介:Qt 串口的使用示例,应用到 STM32开发板上。Qt 串口编程(难度:一般)。在 .pro 里,我们需要使用串口,需要在 pro 项目文件中添加串口模块的支持,如下。
QT += serialport
需要的头文件:
#include <QSerialPort> // 串口配置
#include <QSerialPortInfo> // 串口信息
#include <QPushButton> // 按钮
#include <QTextBrowser> // 文本浏览框
#include <QTextEdit> // 文本编辑框
#include <QVBoxLayout> // 垂直布局
#include <QLabel> // 标签
#include <QComboBox> // 下拉选择盒子
#include <QGridLayout> // 网格布局
#include <QMessageBox> // 弹窗信息
#include <QDebug> // 调试信息
#include <QGuiApplication> // 纯代码界面实现
#include <QScreen> // 屏幕尺寸
#include <QRect> // 矩形
串口分析
QSerialPort是一个在Qt框架中使用的类,用于在计算机和串行设备之间进行通信。串行设备包括像传感器、打印机、调制解调器等等。它们通过串行通信协议来传输数据,而QSerialPort就是帮助我们在程序中与这些设备进行交互的工具。
UART(通用异步收发传输器)是一种通信接口,用于在计算机和外部设备之间传输数据。它描述了数据的传输方式和电气特性。而RS232是一种串行通信标准,它规定了UART通信中使用的电气特性和通信协议。
简单来说,UART是一种通信接口的概念,而RS232是UART所使用的一种具体的串行通信标准。在实际应用中,UART可以使用不同的串行通信标准,如RS232、RS485等。
RS232是一种传统的串行通信标准,通常用于较短的通信距离,比如计算机和外部设备之间的连接。它使用两根信号线(一个用于发送数据,一个用于接收数据)来传输数据,采用了特定的电压表示不同的逻辑状态。RS232定义了数据的传输格式、波特率和其他参数。
所以,当我们提到UART时,它是一个更广义的概念,表示通信接口;而RS232是UART使用的一种具体的串行通信标准,描述了UART通信的电气特性和通信规范。
RS232和RS485是两种常见的串行通信标准,它们定义了数据的传输方式和电气特性。让我们先来看看RS232。
RS232是一种传统的串行通信标准,通常用于较短的通信距离,比如计算机和外部设备之间的连接。它使用两根信号线(一个用于发送数据,一个用于接收数据)来传输数据。RS232使用电压的正负值来表示不同的逻辑状态,比如高电平表示逻辑1,低电平表示逻辑0。这种方式适用于短距离通信,但在长距离通信中可能会受到电压衰减的影响。
接下来是RS485,它是一种更现代化和适用于长距离通信的串行通信标准。RS485支持多点通信,这意味着你可以通过一个总线连接多个设备。它使用差分信号传输数据,其中一根信号线传输正向信号,另一根信号线传输反向信号。RS485还使用了特定的电气特性和通信协议,以提供可靠的通信和抗干扰能力。
总结一下,RS232适用于较短的通信距离,而RS485适用于较长的通信距离和多设备通信。在选择哪种标准时,你需要考虑通信距离、设备数量以及系统的可靠性和抗干扰能力要求。
## 项目源码
头文件:mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QPushButton>
#include <QTextBrowser>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QLabel>
#include <QComboBox>
#include <QGridLayout>
#include <QMessageBox>
#include <QDebug>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void sendPushButtonClicked();
void openSerialPortPushButtonClicked();
void serialPortReadyRead();
private:
QSerialPort *serialPort;
/* 用作接收数据 */
QTextBrowser *textBrowser;
/* 用作发送数据 */
QTextEdit *textEdit;
/* 按钮 */
QPushButton *pushButton[2];
/* 下拉选择盒子 */
QComboBox *comboBox[5];
/* 标签 */
QLabel *label[5];
/* 垂直布局 */
QVBoxLayout *vboxLayout;
/* 网络布局 */
QGridLayout *gridLayout;
/* 主布局 */
QWidget *mainWidget;
/* 设置功能区域 */
QWidget *funcWidget;
/* 布局初始化 */
void layoutInit();
/* 扫描系统可用串口 */
void scanSerialPort();
/* 波特率项初始化 */
void baudRateItemInit();
/* 数据位项初始化 */
void dataBitsItemInit();
/* 检验位项初始化 */
void parityItemInit();
/* 停止位项初始化 */
void stopBitsItemInit();
};
#endif // MAINWINDOW_H
源文件:mainwindow.cpp
#include "mainwindow.h"
#include <QGuiApplication>
#include <QScreen>
#include <QRect>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setWindowTitle("wx公众号search:Qt历险记");
/* 布局初始化 */
layoutInit();
/* 扫描系统的串口 */
scanSerialPort();
/* 波特率项初始化 */
baudRateItemInit();
/* 数据位项初始化 */
dataBitsItemInit();
/* 检验位项初始化 */
parityItemInit();
/* 停止位项初始化 */
stopBitsItemInit();
}
MainWindow::~MainWindow()
{
}
/* 布局初始化 */
void MainWindow::layoutInit()
{
/* 获取屏幕的分辨率,Qt 官方建议使用这
* 种方法获取屏幕分辨率,防上多屏设备导致对应不上
* 注意,这是获取整个桌面系统的分辨率
*/
QList <QScreen *> list_screen = QGuiApplication::screens();
/* 如果是 ARM 平台,直接设置大小为屏幕的大小 */
#if __arm__
/* 重设大小 */
this->resize(list_screen.at(0)->geometry().width(),
list_screen.at(0)->geometry().height());
#else
/* 否则则设置主窗体大小为 800x480 */
this->resize(800, 480);
#endif
/* 初始化 */
serialPort = new QSerialPort(this);
textBrowser = new QTextBrowser();
textEdit = new QTextEdit();
vboxLayout = new QVBoxLayout();
funcWidget = new QWidget();
mainWidget = new QWidget();
gridLayout = new QGridLayout();
/* QList 链表,字符串类型 */
QList <QString> list1;
list1<<"Serial port number:"<<"baudrate:"<<"Data bits:"<<"Parity bit:"<<"stop bit:";
for (int i = 0; i < 5; i++) {
label[i] = new QLabel(list1[i]);
/* 设置最小宽度与高度 */
label[i]->setMinimumSize(80, 30);
/* 自动调整 label 的大小 */
label[i]->setSizePolicy(
QSizePolicy::Expanding,
QSizePolicy::Expanding
);
/* 将 label[i]添加至网格的坐标(0, i) */
gridLayout->addWidget(label[i], 0, i); // 0行 ;列++
}
for (int i = 0; i < 5; i++) {
comboBox[i] = new QComboBox();
comboBox[i]->setMinimumSize(80, 30);
/* 自动调整 label 的大小 */
comboBox[i]->setSizePolicy(
QSizePolicy::Expanding,
QSizePolicy::Expanding
);
/* 将 comboBox[i]添加至网格的坐标(1, i) */
gridLayout->addWidget(comboBox[i], 1, i); // 1行 ;列++
}
/* QList 链表,字符串类型 */
QList <QString> list2;
list2<<"send"<<"Open serial port";
for (int i = 0; i < 2; i++) {
pushButton[i] = new QPushButton(list2[i]);
pushButton[i]->setMinimumSize(80, 30);
/* 自动调整 label 的大小 */
pushButton[i]->setSizePolicy(
QSizePolicy::Expanding,
QSizePolicy::Expanding
);
/* 将 pushButton[0]添加至网格的坐标(i, 5) */
gridLayout->addWidget(pushButton[i], i, 5); // 0行 5列 1行 5列
}
pushButton[0]->setEnabled(false);
/* 布局 */
vboxLayout->addWidget(textBrowser);
vboxLayout->addWidget(textEdit);
funcWidget->setLayout(gridLayout);
vboxLayout->addWidget(funcWidget);
mainWidget->setLayout(vboxLayout);
this->setCentralWidget(mainWidget);
/* 占位文本 */
textBrowser->setPlaceholderText("Received messages");
textEdit->setText(QString::fromUtf8("wx search Qt历险记"));
/* 信号槽连接 */
connect(pushButton[0], SIGNAL(clicked()),this, SLOT(sendPushButtonClicked()));
connect(pushButton[1], SIGNAL(clicked()),this, SLOT(openSerialPortPushButtonClicked()));
connect(serialPort, SIGNAL(readyRead()),this, SLOT(serialPortReadyRead()));
}
/* 扫描系统可用串口 */
void MainWindow::scanSerialPort()
{
/* 查找可用串口 */
foreach (const QSerialPortInfo &info,QSerialPortInfo::availablePorts()) {
comboBox[0]->addItem(info.portName());
}
}
/* 波特率项初始化 */
void MainWindow::baudRateItemInit()
{
/* QList 链表,字符串类型 */
QList <QString> list;
list<<"1200"<<"2400"<<"4800"<<"9600"
<<"19200"<<"38400"<<"57600"
<<"115200"<<"230400"<<"460800"
<<"921600";
for (int i = 0; i < 11; i++)
{
comboBox[1]->addItem(list[i]);
}
comboBox[1]->setCurrentIndex(7); // 115200
}
/* 数据位项初始化 */
void MainWindow::dataBitsItemInit()
{
/* QList 链表,字符串类型 */
QList <QString> list;
list<<"5"<<"6"<<"7"<<"8";
for (int i = 0; i < 4; i++) {
comboBox[2]->addItem(list[i]);
}
comboBox[2]->setCurrentIndex(3); // 8
}
/* 检验位项初始化 */
void MainWindow::parityItemInit()
{
/* QList 链表,字符串类型 */
QList <QString> list;
list<<"None"<<"Even"<<"Odd"<<"Space"<<"Mark";
for (int i = 0; i < 5; i++) {
comboBox[3]->addItem(list[i]);
}
comboBox[3]->setCurrentIndex(0); // None
}
/* 停止位项初始化 */
void MainWindow::stopBitsItemInit()
{
/* QList 链表,字符串类型 */
QList <QString> list;
list<<"1"<<"2";
for (int i = 0; i < 2; i++) {
comboBox[4]->addItem(list[i]);
}
comboBox[4]->setCurrentIndex(0); // 1
}
// 发送到串口
void MainWindow::sendPushButtonClicked()
{
/* 获取 textEdit 数据,转换成 utf8 格式的字节流 */
QByteArray data = textEdit->toPlainText().toUtf8();
serialPort->write(data);
}
void MainWindow::openSerialPortPushButtonClicked()
{
if (pushButton[1]->text() == "Open serial port") {
/* 设置串口名 */
serialPort->setPortName(comboBox[0]->currentText());
/* 设置波特率 */
serialPort->setBaudRate(comboBox[1]->currentText().toInt());
/* 设置数据位数 */
switch (comboBox[2]->currentText().toInt()) {
case 5:
serialPort->setDataBits(QSerialPort::Data5);
break;
case 6:
serialPort->setDataBits(QSerialPort::Data6);
break;
case 7:
serialPort->setDataBits(QSerialPort::Data7);
break;
case 8:
serialPort->setDataBits(QSerialPort::Data8);
break;
default: break;
}
/* 设置奇偶校验 */
switch (comboBox[3]->currentIndex()) {
case 0:
serialPort->setParity(QSerialPort::NoParity);
break;
case 1: // 每个字符中的1位(包括奇偶校验位)的数量总是偶数。
serialPort->setParity(QSerialPort::EvenParity);
break;
case 2: // 每个字符中的1位(包括奇偶校验位)的数量总是奇数
serialPort->setParity(QSerialPort::OddParity);
break;
case 3: // 空间奇偶性。奇偶校验位是在空间信号条件下发送的。它不提供错误检测信息。
serialPort->setParity(QSerialPort::SpaceParity);
break;
case 4: // 标记奇偶性。奇偶校验位总是被设置为标记信号条件(逻辑1)。它不提供错误检测信息。
serialPort->setParity(QSerialPort::MarkParity);
break;
default: break;
}
/* 设置停止位 */
switch (comboBox[4]->currentText().toInt()) {
case 1:
serialPort->setStopBits(QSerialPort::OneStop);
break;
case 2:
serialPort->setStopBits(QSerialPort::TwoStop);
break;
default: break;
}
/* 设置流控制 */
serialPort->setFlowControl(QSerialPort::NoFlowControl);
if (!serialPort->open(QIODevice::ReadWrite)) {
QMessageBox::about(NULL, "error",
"Serial port cannot be opened! Perhaps the serial port is already occupied!");
}
else { // 打开串口
for (int i = 0; i < 5; i++) {
comboBox[i]->setEnabled(false);
}
pushButton[1]->setText("close port");
pushButton[0]->setEnabled(true);
}
}
else { // 关闭串口
serialPort->close();
for (int i = 0; i < 5; i++) {
comboBox[i]->setEnabled(true);
}
pushButton[1]->setText("Open serial port");
pushButton[0]->setEnabled(false);
}
}
// 串口读取
void MainWindow::serialPortReadyRead()
{
/* 接收缓冲区中读取数据 */
QByteArray buf = serialPort->readAll();
textBrowser->insertPlainText(QString(buf));
}
效果演示
使用Launch Virtual Serial Port Driver创建虚拟端口
**如何模拟通信? **
【1】将当前项目创建两个窗口进行通信,一个COM6,一个COM7
【2】使用串口助手,这里我编写了一个简单的调试助手。
调试助手源码如下
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QSerialPort> //提供访问串口得到功能
#include <QSerialPortInfo> //提供系统中存在的串口信息
#include <QMessageBox> //信息弹出框
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
//使用枚举保存波特率
typedef enum
{
B1200 =1200,
B9600 = 9600,
B115200 = 115200,
B921600 = 921600
}Baud;
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
//接收串口数据
void SerialPortReadyRead(); //比作COM6
void Serial2PortReadyRead(); //比作COM7
private slots:
void on_PB_receive_clicked();
void on_PB_send_clicked();
void on_PB_detectserial_clicked();
void on_PB_openserial_clicked();
void on_comboBox_b_currentIndexChanged(int index);
void on_PB_openserial_2_clicked();
void on_PB_send_2_clicked();
private:
Ui::Widget *ui;
QSerialPort serial; //串口1
QSerialPort serial2; //串口2
};
#endif // WIDGET_H
// 源文件
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//连接信号与槽
QObject::connect(&serial, SIGNAL(readyRead()),this, SLOT(SerialPortReadyRead()));
QObject::connect(&serial2, SIGNAL(readyRead()),this, SLOT(Serial2PortReadyRead()));
//发送按钮不使能
ui->PB_send->setDisabled(true);
ui->PB_send_2->setDisabled(true);
}
Widget::~Widget()
{
delete ui;
}
//串口2接收串口1数据
void Widget::SerialPortReadyRead()
{
qDebug()<<"===============有数据==================";
//从缓冲区读取文件
QByteArray buffer = serial.readAll();
//获取界面已经读取的数据
QString recv = ui->plainTextEdit_2->toPlainText();
recv +=QString(buffer);
//显示全部
ui->plainTextEdit_2->appendPlainText(recv);
}
//串口1接收串口2数据
void Widget::Serial2PortReadyRead()
{
qDebug()<<"===============有数据2==================";
//从缓冲区读取文件
QByteArray buffer = serial2.readAll();
//获取界面已经读取的数据
QString recv = ui->plainTextEdit->toPlainText();
recv +=QString(buffer);
//显示全部
ui->plainTextEdit->appendPlainText(recv);
}
void Widget::on_PB_receive_clicked()
{
//清空接收框数据
ui->plainTextEdit->clear();
ui->plainTextEdit_2->clear();
}
//作为串口1
void Widget::on_PB_send_clicked()
{
//向串口写入数据
QByteArray data = ui->plainTextEdit_2->toPlainText().toUtf8();
qDebug()<<"发送数据 data = "<<data;
serial.write(data);
if(!serial.isWritable())
qDebug()<<"写入失败";
}
//作为串口2
void Widget::on_PB_send_2_clicked()
{
//向串口2写入数据
QByteArray data = ui->plainTextEdit->toPlainText().toUtf8();
qDebug()<<"发送数据 data2 = "<<data;
serial2.write(data);
if(!serial2.isWritable())
qDebug()<<"写入失败";
}
//检测串口
void Widget::on_PB_detectserial_clicked()
{
//清空串口id
ui->comboBox_id->clear();
ui->comboBox_id_2->clear();
//通过这个类查找可用串口1
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
qDebug()<<"info.portName() = "<<info.portName();
ui->comboBox_id->addItem(info.portName());
}
//通过这个类查找可用串口2
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
qDebug()<<"info.portName() = "<<info.portName();
ui->comboBox_id_2->addItem(info.portName());
}
}
void Widget::on_PB_openserial_clicked()
{
if(ui->PB_openserial->text() == QString("打开串口1"))
{
//设置串口名
serial.setPortName(ui->comboBox_id->currentText());
//设置波特率
serial.setBaudRate(ui->comboBox_b->currentText().toUInt());
//设置数据位
switch (ui->comboBox_data->currentIndex())
{
case 8:serial.setDataBits(QSerialPort::Data8);break;
default:break;
}
//设置奇偶校验 无
switch (ui->comboBox_crc->currentIndex())
{
case 0:serial.setParity(QSerialPort::NoParity);break;
default:break;
}
//设置停止位
switch (ui->comboBox_stop->currentIndex())
{
case 1:serial.setStopBits(QSerialPort::OneStop);break;
default:break;
}
//设置流控 无
serial.setFlowControl(QSerialPort::NoFlowControl);
//打开串口
if(!serial.open(QIODevice::ReadWrite))
{
QMessageBox::about(NULL,"提示","串口1打开失败");
}
if(serial.isOpen())
QMessageBox::about(NULL,"提示","串口1打开成功");
//下拉菜单控件失能
ui->comboBox_b->setEnabled(false);
ui->comboBox_id->setEnabled(false);
ui->comboBox_crc->setEnabled(false);
ui->comboBox_data->setEnabled(false);
ui->comboBox_stop->setEnabled(false);
ui->PB_openserial->setText(tr("关闭串口1"));
//发送按键使能
ui->PB_send->setEnabled(true);
}
else
{
//关闭串口
serial.close();
//下拉使能
ui->comboBox_b->setEnabled(true);
ui->comboBox_id->setEnabled(true);
ui->comboBox_crc->setEnabled(true);
ui->comboBox_data->setEnabled(true);
ui->comboBox_stop->setEnabled(true);
ui->PB_openserial->setText(tr("打开串口1"));
//发送失能
ui->PB_send->setEnabled(false);
}
}
//获取波特率索引
void Widget::on_comboBox_b_currentIndexChanged(int index)
{
if(ui->comboBox_b->itemText(index).toUInt() == B9600)
{
qDebug()<<"itemText = "<<ui->comboBox_b->itemText(index);
ui->comboBox_b->setCurrentIndex(index);
}
else if(ui->comboBox_b->itemText(index).toUInt() == B115200)
{
qDebug()<<"itemText = "<<ui->comboBox_b->itemText(index);
ui->comboBox_b->setCurrentIndex(index);
}
}
void Widget::on_PB_openserial_2_clicked()
{
if(ui->PB_openserial_2->text() == QString("打开串口2"))
{
//设置串口名
serial2.setPortName(ui->comboBox_id_2->currentText());
//设置波特率
serial2.setBaudRate(ui->comboBox_b->currentText().toUInt());
//设置数据位
switch (ui->comboBox_data->currentIndex())
{
case 8:serial2.setDataBits(QSerialPort::Data8);break;
default:break;
}
//设置奇偶校验 无
switch (ui->comboBox_crc->currentIndex())
{
case 0:serial2.setParity(QSerialPort::NoParity);break;
default:break;
}
//设置停止位
switch (ui->comboBox_stop->currentIndex())
{
case 1:serial2.setStopBits(QSerialPort::OneStop);break;
default:break;
}
//设置流控 无
serial2.setFlowControl(QSerialPort::NoFlowControl);
//打开串口
if(!serial2.open(QIODevice::ReadWrite))
{
QMessageBox::about(NULL,"提示","串口2打开失败");
}
if(serial2.isOpen())
QMessageBox::about(NULL,"提示","串口2打开成功");
//下拉菜单控件失能
ui->comboBox_b->setEnabled(false);
ui->comboBox_id_2->setEnabled(false);
ui->comboBox_crc->setEnabled(false);
ui->comboBox_data->setEnabled(false);
ui->comboBox_stop->setEnabled(false);
ui->PB_openserial_2->setText(tr("关闭串口2"));
//发送按键使能
ui->PB_send_2->setEnabled(true);
}
else
{
//关闭串口
serial2.close();
//下拉使能
ui->comboBox_b->setEnabled(true);
ui->comboBox_id_2->setEnabled(true);
ui->comboBox_crc->setEnabled(true);
ui->comboBox_data->setEnabled(true);
ui->comboBox_stop->setEnabled(true);
ui->PB_openserial_2->setText(tr("打开串口2"));
//发送失能
ui->PB_send_2->setEnabled(false);
}
}
ui界面设计:
整体效果演示:
===END===