前言
Qt写上位机时,串口通信是个常用功能,在Qt4的时候有第三方模块QextSerialPort,到了Qt5.1官方提供了QSerialPort模块。
目录
补充:16进制文本(如“0A 13 EF”)转16进制数据(如0x0A 0x13 0xEF)
使用该模块需要在pro文件中添加:QT += serialport
主要使用两个类:QSerialPort和QSerialPortInfo
获取串口信息:QSerialPortInfo
获取串口名列表
-
QStringList slist;
-
foreach (
const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
-
//检测是否可用
-
f(!info.isBusy())
-
slist<<info.portName();
-
}
除了串口名还能获取其他的相关信息,可以看文档,或者该链接 https://blog.csdn.net/mcu_tian/article/details/43527385
串口IO操作:QSerialPort
串口IO的主要操作有参数设置/开/关/读/写等
-
//[1]串口设置
-
QSerialPort *serialIo=
new QSerialPort;
-
serialIo->setPortName(
"COM4");
//串口名
-
serialIo->setBaudRate(
115200);
//波特率
-
serialIo->setDataBits(QSerialPort::Data8);
//数据位
-
serialIo->setParity(QSerialPort::NoParity);
//校验位
-
serialIo->setStopBits(QSerialPort::OneStop);
//停止位
-
serialIo->setFlowControl(QSerialPort::NoFlowControl);
//流控制一般没用
-
-
//[2]串口开启
-
if(serialIo->open(QIODevice::ReadWrite)){
-
qDebug()<<
"串口已打开,读写模式";
-
}
else{
-
qDebug()<<
"串口打开异常"<<serialIo->errorString();
-
serialIo->clearError();
-
}
-
-
//[3]数据发送
-
const QByteArray send_data=ui->textSend->toPlainText().toUtf8();
//一般没发字符串,特别是中文
-
if(serialIo->isOpen()){
-
serialIo->write(send_data);
-
qDebug()<<
"已发送:"<<QString::fromUtf8(send_data);
-
}
else{
-
qDebug()<<
"发送失败,串口未打开";
-
}
-
if(!serialIo->waitForBytesWritten(
30000)){
-
qDebug()<<
"命令发送异常"<<serialIo->errorString();
-
serialIo->clearError();
-
}
-
-
//[4]数据接收
-
connect(serialIo,&QSerialPort::readyRead,
this,[
this](){
-
if (serialIo->bytesAvailable()) {
-
//串口收到的数据可能不是连续的,需要的话应该把数据缓存下来再进行协议解析,类似tcp数据处理
-
const QByteArray recv_data=serialIo->readAll();
-
//接收发送要一致,如果是处理字节数据,可以把QByteArray当数组一样取下标,或者用data()方法转为char*形式
-
ui->textRecv->append(QString::fromUtf8(recv_data));
//显示
-
qDebug()<<
"已接收:"<<QString::fromUtf8(recv_data);
-
}
-
});
-
-
//[5]串口关闭
-
serialIo->clear();
-
serialIo->close();
实例操作
为了方便,ui我直接用的设计师拖得,工程文件我把百度云链接放在了最后,需要的可以下载
头文件MainWidget.h(记得pro里引入serialport模块)
-
#ifndef MAINWIDGET_H
-
#define MAINWIDGET_H
-
-
#include <QWidget>
-
-
class QSerialPort;
-
-
namespace Ui {
-
class MainWidget;
-
}
-
-
class MainWidget :
public QWidget
-
{
-
Q_OBJECT
-
-
public:
-
explicit MainWidget(QWidget *parent = 0);
-
~MainWidget();
-
-
void initSerial();
//初始化串口设置
-
void initMainUi();
//初始化界面操作
-
-
void openSerial();
//槽函数-打开串口
-
void closeSerial();
//槽函数-关闭串口
-
void refreshSerial();
//槽函数-刷新串口名列表
-
-
private:
-
QStringList getSerialPortNames();
//获取串口名列表
-
void setSerialEnable(bool enabled);
//串口开启时就不能动ui配置了
-
void sendData();
//槽函数-发送数据
-
void recvData();
//槽函数-接收数据
-
-
private:
-
Ui::MainWidget *ui;
-
-
QSerialPort *serialIo;
//串口io,一般可以把串口io扔到线程里去,避免阻塞ui
-
};
-
-
#endif // MAINWIDGET_H
实现文件MainWidget.cpp
-
#include "MainWidget.h"
-
#include "ui_MainWidget.h"
-
-
#include <QSerialPort>
-
#include <QSerialPortInfo>
-
#include <QListView>
-
-
#include <QDebug>
-
-
MainWidget::MainWidget(QWidget *parent) :
-
QWidget(parent),
-
ui(
new Ui::MainWidget)
-
{
-
ui->setupUi(
this);
-
initSerial();
-
initMainUi();
-
}
-
-
MainWidget::~MainWidget()
-
{
-
delete ui;
-
}
-
-
void MainWidget::initSerial()
-
{
-
//[1]创建串口io对象
-
serialIo=
new QSerialPort(
this);
-
//数据接收处理
-
connect(serialIo,&QSerialPort::readyRead,
this,&MainWidget::recvData);
-
//[2]界面初始化
-
//注:items的选项值是根据文档中的枚举来写的
-
//串口名
-
refreshSerial();
-
ui->boxPortName->setView(
new QListView(
this));
-
//波特率
-
QStringList baudrateList;
-
baudrateList<<
"1200"<<
"2400"<<
"4800"<<
"9600"<<
"19200"<<
"38400"<<
"57600"<<
"115200";
-
ui->boxBaudRate->addItems(baudrateList);
//添加下拉列表选项
-
ui->boxBaudRate->setEditable(
true);
//串口波特率可编辑
-
ui->boxBaudRate->setCurrentText(
"115200");
//界面中初始值
-
ui->boxBaudRate->setView(
new QListView(
this));
//该设置是配合qss的,不然item行高设置没效果
-
//数据位
-
QStringList databitList;
-
databitList<<
"5"<<
"6"<<
"7"<<
"8";
-
ui->boxDataBits->addItems(databitList);
-
ui->boxDataBits->setCurrentText(
"8");
-
ui->boxDataBits->setView(
new QListView(
this));
-
//校验位
-
QStringList parityList;
-
parityList<<
"No"<<
"Even偶"<<
"Odd奇"<<
"Space"<<
"Mark";
-
ui->boxParity->addItems(parityList);
-
ui->boxParity->setCurrentText(
"No");
-
ui->boxParity->setView(
new QListView(
this));
-
//停止位
-
QStringList stopbitList;
-
stopbitList<<
"1"<<
"1.5"<<
"2";
-
ui->boxStopBits->addItems(stopbitList);
-
ui->boxStopBits->setCurrentText(
"1");
-
ui->boxStopBits->setView(
new QListView(
this));
-
//流控制
-
QStringList flowctrlList;
-
flowctrlList<<
"No"<<
"Hardware"<<
"Software";
-
ui->boxFlowControl->addItems(flowctrlList);
-
ui->boxFlowControl->setCurrentText(
"No");
-
ui->boxFlowControl->setView(
new QListView(
this));
-
}
-
-
void MainWidget::initMainUi()
-
{
-
//点击串口[开启]/[关闭]按钮
-
connect(ui->btnOpen,&QPushButton::clicked,
this,[
this](){
-
if(ui->btnOpen->text()==
"打开"){
-
openSerial();
-
}
else{
-
closeSerial();
-
}
-
});
-
//点击串口[刷新]按钮-刷新串口名列表
-
connect(ui->btnRefresh,&QPushButton::clicked,
this,&MainWidget::refreshSerial);
-
//点击数据[发送]按钮
-
connect(ui->btnSend,&QPushButton::clicked,
this,&MainWidget::sendData);
-
}
-
-
void MainWidget::openSerial()
-
{
-
const QString portnameStr=ui->boxPortName->currentText();
-
if(!portnameStr.isEmpty()){
-
QSerialPortInfo info(portnameStr);
-
if(info.isBusy()){
-
qDebug()<<
"当前串口繁忙,可能已被占用,请确认后再连接"<<portnameStr;
-
return;
-
}
-
//
-
qint32 baudrate=ui->boxBaudRate->currentText().toInt();
-
QSerialPort::DataBits databit;
-
switch (ui->boxDataBits->currentIndex()) {
-
case
0:databit=QSerialPort::Data5;
break;
-
case
1:databit=QSerialPort::Data6;
break;
-
case
2:databit=QSerialPort::Data7;
break;
-
case
3:databit=QSerialPort::Data8;
break;
-
default:databit=QSerialPort::Data8;
break;
-
}
-
QSerialPort::Parity parity;
-
switch (ui->boxParity->currentIndex()) {
-
case
0:parity=QSerialPort::NoParity;
break;
-
case
1:parity=QSerialPort::EvenParity;
break;
-
case
2:parity=QSerialPort::OddParity;
break;
-
case
3:parity=QSerialPort::SpaceParity;
break;
-
case
4:parity=QSerialPort::MarkParity;
break;
-
default:parity=QSerialPort::NoParity;
break;
-
}
-
QSerialPort::StopBits stopbit;
-
switch (ui->boxStopBits->currentIndex()) {
-
case
0:stopbit=QSerialPort::OneStop;
break;
-
case
1:stopbit=QSerialPort::OneAndHalfStop;
break;
-
case
2:stopbit=QSerialPort::TwoStop;
break;
-
default:stopbit=QSerialPort::OneStop;
break;
-
}
-
QSerialPort::FlowControl flowcontrol;
-
switch (ui->boxFlowControl->currentIndex()) {
-
case
0:flowcontrol=QSerialPort::NoFlowControl;
break;
-
case
1:flowcontrol=QSerialPort::HardwareControl;
break;
-
case
2:flowcontrol=QSerialPort::SoftwareControl;
break;
-
default:flowcontrol=QSerialPort::NoFlowControl;
break;
-
}
-
//串口配置设置
-
serialIo->setPortName(portnameStr);
-
serialIo->setBaudRate(baudrate);
-
serialIo->setDataBits(databit);
-
serialIo->setParity(parity);
-
serialIo->setStopBits(stopbit);
-
serialIo->setFlowControl(flowcontrol);
//这个我一般没用
-
if(serialIo->open(QIODevice::ReadWrite)){
-
qDebug()<<
"串口已打开,读写模式";
-
setSerialEnable(
false);
//改变ui状态
-
}
else{
-
qDebug()<<
"串口打开异常"<<portnameStr<<serialIo->errorString();
-
serialIo->clearError();
-
setSerialEnable(
true);
-
}
-
}
else{
-
qDebug()<<
"未找到可用串口,请确认串口连接正常后点击刷新";
-
}
-
}
-
-
void MainWidget::closeSerial()
-
{
-
serialIo->clear();
-
serialIo->close();
-
qDebug()<<
"串口已关闭";
-
setSerialEnable(
true);
-
}
-
-
void MainWidget::refreshSerial()
-
{
-
ui->boxPortName->clear();
-
ui->boxPortName->addItems(getSerialPortNames());
-
}
-
-
QStringList MainWidget::getSerialPortNames()
-
{
-
QStringList slist;
-
foreach (
const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
-
//检测是否可用
-
if(!info.isBusy())
-
slist<<info.portName();
-
}
-
if(slist.isEmpty()){
-
qDebug()<<
"未找到可用串口,请确认串口连接正常后点击刷新";
-
}
-
return slist;
-
}
-
-
void MainWidget::setSerialEnable(
bool enabled)
-
{
-
//打开成功就false不能再修改配置,关闭状态true可以进行设置
-
ui->btnRefresh->setEnabled(enabled);
-
ui->btnOpen->setText(enabled?QString(
"打开"):QString(
"关闭"));
-
//可以把btn和配置分在两个widget里,这样直接设置widget的enable就没这么麻烦了
-
ui->boxPortName->setEnabled(enabled);
-
ui->boxBaudRate->setEnabled(enabled);
-
ui->boxDataBits->setEnabled(enabled);
-
ui->boxParity->setEnabled(enabled);
-
ui->boxStopBits->setEnabled(enabled);
-
ui->boxFlowControl->setEnabled(enabled);
-
}
-
-
void MainWidget::sendData()
-
{
-
//注意收发的编码问题,我一般只是发命令吗和字节数据,没怎么发字符串,用latin1就行了
-
const QByteArray send_data=ui->textSend->toPlainText().toUtf8();
-
if(send_data.size()<=
0)
-
return;
-
if(serialIo->isOpen()){
-
serialIo->write(send_data);
-
qDebug()<<
"已发送:"<<QString::fromUtf8(send_data);
-
}
else{
-
qDebug()<<
"发送失败,串口未打开";
-
return;
-
}
-
//Qt新版本默认值是30 000
-
if(!serialIo->waitForBytesWritten(
30000)){
-
qDebug()<<
"命令发送异常"<<serialIo->errorString();
-
serialIo->clearError();
-
}
-
}
-
-
void MainWidget::recvData()
-
{
-
if (serialIo->bytesAvailable()) {
-
//串口收到的数据可能不是连续的,需要的话应该把数据缓存下来再进行协议解析,类似tcp数据处理
-
const QByteArray recv_data=serialIo->readAll();
-
//接收发送要一致,如果是处理字节数据,可以把QByteArray当数组一样取下标,或者用data()方法转为char*形式
-
ui->textRecv->append(QString::fromUtf8(recv_data));
-
qDebug()<<
"已接收:"<<QString::fromUtf8(recv_data);
-
}
-
}
由于时间有限,所以有两个点没有写,一是一般我把串口IO放在子线程中避免阻塞ui;二是串口协议得解析,可以参照TCP数据的处理,先缓存起来再循环判断,一般一帧数据有帧头、帧长度、帧命令码、帧数据、帧尾/CRC校验等字段。
时间仓促,可能代码有bug,望大佬纠正。
附上百度云链接 https://pan.baidu.com/s/1gzHC6Q4fBZ_G1Db7tMcIaw
提取码 inxh 文件名TestSerialPort.rar
补充:16进制文本(如“0A 13 EF”)转16进制数据(如0x0A 0x13 0xEF)
有时候需要这种功能,输入十六进制文本,发送的是对应的十六进制数据,Qt中通过QByteArray类可以轻松的搞定。
-
QString str=
"0A 13 EF";
//假设为文本框读取到的字符串
-
QByteArray temp=QByteArray::fromHex(str.toLatin1());
-
qDebug()<<str<<temp;
(输出结果中的十六进制因为被Qt做了转义,所以0x0A是个换行符)
当然,也可以把十六进制数据转为十六进制文本。
-
const
char arr[]={
0x0A,
0x13,
0xEF};
-
QByteArray temp=QByteArray(arr,
3);
//假设为收到的数据
-
QString str=temp.toHex(
' ').toUpper();
//老版本Qt的toHex没有参数设置,需要自己分割
-
qDebug()<<temp<<str;
(2019-07-24晨)