首页 > 代码库 > Qt5串口编程详解【新版】
Qt5串口编程详解【新版】
Qt5的串口比Qt4的好用得多,Qt4的貌似没有集成官方库。
之前我也写过Qt5的串口,不过有一些缺陷,这次试图改进。转载请保留链接:http://blog.csdn.net/qq363692146/article/details/26049355
本文发表于2014.5.17。
如果在linux下,记得使用root权限,可以用root权限打开可执行文件,或者用root权限打开Qt Creator。(原因是串口常常需要特权,有些串口有特权也只能度而不能写,这个可能是Qt本身的问题)【至于安卓端,其实没试过。】【用root打开Qt Creator似乎是一个一劳永逸的方法,这样每次运行都以root权限,但是一些配置文件也会被改变为root权限。下一次再以普通权限打开Qt Creator就会提示错误,需要输入这样命令 sudo chmod 777 `find 对应目录`,这样能将目录的每个文件去掉权限限制】
好了,准备开始。
先建立Qt Widgets Application,只要注意尽量不用中文路径即可,一直下一步,直到完成。
接着修改“项目名.pro”文件,增加这一单独行。(这个项目文件可以根据需要裁剪和增加,以增快编译效率和设置选项,而这一行表示“我想用到串口”)
QT += serialport
进入mainwindow.ui,给它一个Combo Box(用于选择哪一个串口,一台电脑常常有多个串口)、一个label(用于显示串口是否正常)、一个Text Edit(用于接收数据)、一个lineEdit(用于发送数据)、一个Push Button(当按下一次,就发送一次),这里为了方便读者理解,与博文统一,请读者先别急着修改它们的ObeectName,至于布局,请随意。
给Combo Box一个槽,来改变串口(右键,转到槽,选择currentIndexChange(QString))。//当改变选择的选项则触发,可得到选项名
给Push Button一个槽,用于发送(右键,选择槽,选择第一个函数)。//当按下触发
在mainWindow.h文件中:
加入头文件:(这里QSerialPort是必须的,而QSerialPortInfo是可选的,它能提供本机可用串口列表(列表的每一个元素都自动获取了对应的端口号、波特率、校验位等信息!),如果不使用它则需要手动编码,设置对应的串口、波特率等信息,最后完成之后单独拿出来讲,见下面的黄色阴影字体部分)
#include <QSerialPort> #include <QSerialPortInfo>接着,给它一个private变量(表示串口及其操作函数)、一个private函数(用于初始化)、一个连接槽,(用于接收串口过来的数据,发送是不需要槽的,原因是发送是主动、随时的,而接收是被动、等待的):
private变量:
QSerialPort serial;
一个private函数:
void initSeialPort();
连接槽:
private slots: void serialRead();
进入mainWindow.cpp中:
在构造函数中:
在ui->setupUi(this);之后加入这一句:
initSeialPort();
在析构函数中:
在delete ui;之前加入这一句:
serial.close();
接下来补齐这些没有函数体为空的函数,为了方便可以直接复制(仔细浏览,看看它们已经定义了没有,或者是否ui触发的槽选择错误,如果有问题,请投诉):
initSerialPort函数
先连接槽,然后获取可用的infos(所有本地存在串口信息),如果都不可用,提示“无效”,如果存在,则为Combo Box添加选项,第一项是“串口”(只是提示,由于combo Box的选项从无到有,将触发一次 on_comboBox_currentIndexChanged(const QString &arg1) 槽 ,见下面橙色阴影字体),这个选项之后是其他的串口的端口号作为选项。
void MainWindow::initSeialPort() { connect(&serial,SIGNAL(readyRead()),this,SLOT(serialRead())); //连接槽 //get name for choose QList<QSerialPortInfo> infos = QSerialPortInfo::availablePorts(); if(infos.isEmpty()) { ui->comboBox->addItem("无效"); return; } ui->comboBox->addItem("串口"); foreach (QSerialPortInfo info, infos) { ui->comboBox->addItem(info.portName()); } }
当每次combo Box选项变化,将触发下面的函数:
arg1为该选择的Combo Box选项名称(第一次触发为“串口”,之后被用户选择为需要的串口号,根据上面的addItem函数可以逆推)
根据arg1查找可用的串口列表,如果找不到(例如arg1为“串口”这个字符串,当然找不到)则提示“[出错]”,否则打开并提示"[已开启]"。
这里serial先关后开,防止先前的串口忘了关闭(因为每改变一次选项就要开启一次,那么当然要把上一次的串口给关了才行)。
void MainWindow::on_comboBox_currentIndexChanged(const QString &arg1) { QSerialPortInfo info; QList<QSerialPortInfo> infos = QSerialPortInfo::availablePorts(); int i = 0; foreach (info, infos) { if(info.portName() == arg1) break; i++; } if(i != infos.size ()){//can find ui->label->setText("[已开启]"); serial.close(); serial.setPort(info); serial.open(QIODevice::ReadWrite); //读写打开 } else { serial.close(); ui->label->setText("[出错]"); } }
下面两个函数便不详述:
void MainWindow::serialRead() { ui->textEdit->append(serial.readAll()); }
toLatin1可用转换QString到QByteArray类型:
void MainWindow::on_pushButton_clicked() { if(ui->lineEdit->text().isEmpty()) return; serial.write(ui->lineEdit->text().toLatin1()); }
好已经完成全部工作,测试一下吧。
补充一下自己摸索的其他方法,作为阅读即可:
问题1:如何去掉QSerialPortInfo头文件,并手动设置串口信息,这里举个例子:
//注意这里serial是指针形式,而且下面的顺序有一定要求: //下面这些设置都是针对单片机11.0592的晶振的,请根据具体情况设置: serial = new QSerialPort("COM4"); //串口号,一定要对应好,大写!!!串口号一般要修改。 serial->open(QIODevice::ReadWrite); //读写打开 serial->setBaudRate(QSerialPort::Baud9600); //波特率 serial->setDataBits(QSerialPort::Data8); //数据位 serial->setParity(QSerialPort::NoParity); //无奇偶校验 serial->setStopBits(QSerialPort::OneStop); //无停止位 serial->setFlowControl(QSerialPort::NoFlowControl); //无控制
问题2:如何解决传接收断断续续的问题(在某些情况串口无意地经常把一条数据拆成多条数据,这时候多次触发了serialRead槽,得到不想要的结果):
解决方法:
1.对于固定长度的数据,假如有数据长度不正确,就请求重发。
2.对于不固定长度的,可以在数据后面加上特殊符号作为识别,这里将举例(当特殊符号为‘~‘)【不过这个代码没考虑多线程:static变量存在着】:
void MainWindow::serialRead() { static QByteArray allData; //静态变量!!在串口只得到一部分的时候用来累加数据 QByteArray dataTemp; //本次接收的数据,可能是前半部分数据或者后半部分数据(甚至是被分割为多段数据的其中一部分数据),或者也可能是完整数据。 while (!serial->atEnd()) { QByteArray dataTemp = serial->readAll(); //因为串口是不稳定的,也许读到的是部分数据而已,但也可能是全部数据 if( dataTemp.data()[dataTemp.length() - 1] == ‘~‘){ //当临时数据最后一位是‘~‘,代表一条数据读完了 allData += dataTemp; //总数据加上本次数据 allData.resize(allData.size() - 1); //删除结尾的~符号 qCritical() << allData; //这时候allData将是你要的数据 allData.clear(); //清除数据!!!!! } else //当最后一位数据不是‘~‘,即未读完 allData += dataTemp; //每次累加这部分数据,因为还没发完 } }