Qt 串行通讯161109.docx
《Qt 串行通讯161109.docx》由会员分享,可在线阅读,更多相关《Qt 串行通讯161109.docx(12页珍藏版)》请在冰豆网上搜索。
Qt串行通讯161109
Qt串行通讯
Hanford
2016年11月09日
目录
第1章Qt串行通讯1
1.1配置.pro文件1
1.2查询串口信息1
1.3配置、打开串口3
1.4setRequestToSend在Windows上的BUG5
1.5读取串口数据6
1.6发送串口数据7
1.7同步读取7
1.8本文示例代码8
1.9Qt示例代码10
第1章Qt串行通讯
最近要在Android手机上开发串行通讯程序,为此学习了一下Qt的串行通讯。
本文中,Qt的版本为5.7.0。
1.1配置.pro文件
使用Qt5.7.0创建“QtWidgetsApplication”类型的项目,然后修改.pro文件,如下图所示:
图1.1
给变量QT增加serialport,说明程序里将使用串行通讯相关的类。
1.2查询串口信息
本节将通过代码查找系统里的串口,然后填入下图所示的下拉列表框中。
图1.2
函数QSerialPortInfo:
:
availablePorts会返回系统所有的串口,它的使用请参考如下代码:
#include
std:
mapmapPort;
QStringsPort;
intnPort;
foreach(constQSerialPortInfo&info,QSerialPortInfo:
availablePorts())
{//foreach遍历QSerialPortInfo:
availablePorts()的返回值
sPort=info.portName();//串口名称,如:
COM5
nPort=GetIntInStr(sPort);//根据串口名称获取串口号,如:
5
if(nPort>=0)
{
mapPort[nPort]=sPort;//根据串口号排序,加入map
}
函数GetIntInStr根据串口名称(如COM5)获取串口号(如:
5),其代码如下:
/***************************************************************\
从字符串里提取整数
s[in]字符串
返回:
提取出来的整数,-1表示错误
\***************************************************************/
intGetIntInStr(constQString&s)
boolbOK=false;//是否发现了数字
intn=0;
intnLenS=s.length();//字符串长度
ushortc=0;
for(inti=0;i{c=s[i].unicode();if(c>='0'&&c<='9'){bOK=true;n=n*10+(c-'0');}}if(!bOK){n=-1;//没有数字,返回-1}returnn;}根据std::mapmapPort填充下拉列表框QComboBoxcboPort的代码如下:ui->cboPort->clear();for(std::map::iteratorit=mapPort.begin();it!=mapPort.end();++it){ui->cboPort->addItem(it->second);}1.3配置、打开串口配置、打开串口的代码如下:#includem_port=newQSerialPort();m_port->setPortName("COM1");//打开COM1m_port->setBaudRate(9600);//波特率:9600m_port->setParity(QSerialPort::NoParity);//校验法:无m_port->setDataBits(QSerialPort::Data8);//数据位:8m_port->setStopBits(QSerialPort::OneStop);//停止位:1m_port->setFlowControl(QSerialPort::NoFlowControl);//流控制:无if(m_port->open(QIODevice::ReadWrite)){//成功打开串口m_port->setRequestToSend(true);//设置RTS为高电平m_port->setDataTerminalReady(true);//设置DTR为高电平}首先new一个QSerialPort对象,然后设置该对象的串行通讯参数,最后调用QSerialPort::open函数打开串口。这里需要说明一下流控制。通讯的双方A和B,假如A给B发送数据时,B反应过慢,A不管不顾的不停发送数据,结果会导致数据丢失。为了防止这种情况发生,可使用流控制(也叫握手)。软件流控制(XON/XOFF):通讯的一方(B)如果不能及时处理串口数据,会给对方(A)发送XOFF字符,对方接收到这个字符后,会停止发送数据;B不再忙的时候,会给A发送XON字符,A接收到这个字符后,会接着发送数据。软件流控制最大的问题就是不能传输XON和XOFF。硬件流控制(RTS/CTS):硬件流控制需要按下图连接两个串口设备的RTS和CTS。图1.3通讯的一方(B)如果不能及时处理串口数据,会设置自己的RTS为低电平,B的RTS连着对方(A)的CTS,A发现自己的CTS为低电平,将停止发送数据;B不再忙的时候,会设置自己的RTS为高电平,A发现自己的CTS为高电平,将接着发送数据。上面的代码中,设置流控制为无,其含义为:不管对方是否能够反应过来,这边只管发送数据。当流控制为硬件时,系统会自动管理RTS和DTR的状态。否则,应该设置RTS和DTR为高电平,通知对方可以发送串口数据了。1.4setRequestToSend在Windows上的BUG经测试,流控制为无时,调用m_port->setRequestToSend(true);是没有任何效果的。下面的Qt源代码节选自文件C:\Qt\Qt5.7.0\5.7\Src\qtserialport\src\serialport\qserialport_win.cpp(C:\Qt\Qt5.7.0是Qt的安装目录)boolQSerialPortPrivate::setRequestToSend(boolset){if(!::EscapeCommFunction(handle,set?SETRTS:CLRRTS)){setError(getSystemError());returnfalse;}returntrue;}上面的代码调用EscapeCommFunction(handle,SETRTS)似乎没什么问题,但是请看下面的代码:boolQSerialPortPrivate::setFlowControl(QSerialPort::FlowControlflowControl){.........dcb.fRtsControl=RTS_CONTROL_DISABLE;switch(flowControl){caseQSerialPort::NoFlowControl:break;caseQSerialPort::SoftwareControl:break;caseQSerialPort::HardwareControl:dcb.fRtsControl=RTS_CONTROL_HANDSHAKE;break;.........硬件流控制时dcb.fRtsControl为RTS_CONTROL_HANDSHAKE,这个没问题。问题出在dcb.fRtsControl=RTS_CONTROL_DISABLE上,它直接禁用了RTS,所以EscapeCommFunction(handle,SETRTS)并不能设置RTS为高电平。那么m_port->setDataTerminalReady(true)为什么又是正常的呢?看代码:boolQSerialPortPrivate::setDataTerminalReady(boolset){.........dcb.fDtrControl=set?DTR_CONTROL_ENABLE:DTR_CONTROL_DISABLE;.........}set为true时,dcb.fDtrControl为DTR_CONTROL_ENABLE,所以可以设置DTR为高电平。1.5读取串口数据m_port->readAll(QIODevice::readAll)用来读取串口数据。不过,它是异步执行的。什么是异步呢?那就是即使对方还没有发送串口数据,m_port->readAll也会立即返回,而不是傻傻的等着对方发送数据过来后再返回。既然是异步的,那么何时读取串口数据就成为了关键。Qt提供的方案就是使用信号、槽。connect(m_port,SIGNAL(readyRead()),this,SLOT(slotReadData()));当对方发送串口数据后,将触发m_port的信号QIODevice::readyRead。上面的代码将信号readyRead与槽函数slotReadData连接了起来,因此槽函数slotReadData将被调用,其代码如下:voidWidget::slotReadData(){QByteArraydata;constintnMax=64*1024;for(;;){data=m_port->readAll();//读取串口数据if(data.isEmpty()){//没有读取到串口数据就退出循环break;}//读取到的串口数据,加入到QByteArraym_dataComm_dataCom.append(data);if(m_dataCom.size()>nMax){//防止m_dataCom过长m_dataCom=m_dataCom.right(nMax);}}ui->txtRecv->setText(m_dataCom);//将m_dataCom显示到文本框ui->txtRecv->moveCursor(QTextCursor::End);//移动文本框内的插入符}1.6发送串口数据m_port->write(QIODevice::write)用来发送串口数据,不过它也是异步的。也就是说:代码m_port->write("123");会立即返回,至于数据"123"何时会发送给对方,那是操作系统的事情。操作系统不忙的时候,才会做此项工作。参考如下代码:charszData[32];strcpy(szData,"123");m_port->write(szData);m_port->write(szData);这行代码会立即返回。操作系统在空闲时才会将szData里的数据发送给对方。问题是:m_port->write返回后,变量szData有可能已经被析构掉了,此时操作系统再访问szData所指向的内存,结果不可预料。为此,需要改进上述代码,将异步通讯更改为同步通讯:charszData[32];strcpy(szData,"123");m_port->write(szData);m_port->waitForBytesWritten(5000);就增加了一行代码m_port->waitForBytesWritten(5000);其含义为:操作系统把串口数据szData发送出去后,m_port->waitForBytesWritten才会返回。不过,总不能无限制等下去吧?5000就是等待时间的最大值,其单位为毫秒,5000毫秒就是5秒。1.7同步读取异步通讯的效率比较高,但是代码结构比较复杂。有时,需要同步读取。如:给对方发送字符串Volt,对方回应电压值5。代码会这么写:m_port->write("Volt");m_port->waitForBytesWritten(5000);QByteArraydata;for(;;){data=m_port->readAll();//读取串口数据if(!data.isEmpty()){//读到数据了,退出循环break;}}通过一个无限循环,将异步读取变成了同步读取。不过,上述代码运行时,CPU占用率将会达到100%(单核CPU)。为此,需要改进代码:m_port->write("Volt");m_port->waitForBytesWritten(5000);QByteArraydata;while(m_port->waitForReadyRead(3000)){data=m_port->readAll();//读取串口数据if(!data.isEmpty()){//读到数据了,退出循环break;}}修改了一行代码m_port->waitForReadyRead(3000),其含义为等待对方发送串口数据过来。如果对方发送串口数据过来了,它返回true,然后使用m_port->readAll读取串口数据;如果对方在3秒内都没有发送串口数据过来,它返回false,退出循环。1.8本文示例代码本文示例代码已上传至git服务器,具体如下:为了便于测试,可使用VirtualSerialPortDriver7.1创建一个串口对COM100、COM200,如下图所示:图1.4上图的COM100、COM200是虚拟出来的。COM100发送的数据将会被COM200接收到,反之亦然。下图是测试界面:图1.5本文示例代码打开了COM100,另一个串行通讯程序打开了COM200。两者可以相互发送数据。1.9Qt示例代码安装完Qt5.7.0后,Qt串行通讯的示例代码也被安装了,如下图所示:图1.6C:\Qt\Qt5.7.0是笔者安装Qt5.7.0时的安装目录。需要进一步了解Qt串行通讯的可查看这些示例。如:creaderasync表示异步读取;creadersync表示同步读取……更多信息查看Qt帮助。
c=s[i].unicode();
if(c>='0'&&c<='9')
bOK=true;
n=n*10+(c-'0');
if(!
bOK)
n=-1;//没有数字,返回-1
returnn;
根据std:
mapmapPort填充下拉列表框QComboBoxcboPort的代码如下:
ui->cboPort->clear();
for(std:
map:
iteratorit=mapPort.begin();
it!
=mapPort.end();++it)
ui->cboPort->addItem(it->second);
1.3配置、打开串口
配置、打开串口的代码如下:
m_port=newQSerialPort();
m_port->setPortName("COM1");//打开COM1
m_port->setBaudRate(9600);//波特率:
9600
m_port->setParity(QSerialPort:
NoParity);//校验法:
无
m_port->setDataBits(QSerialPort:
Data8);//数据位:
8
m_port->setStopBits(QSerialPort:
OneStop);//停止位:
1
m_port->setFlowControl(QSerialPort:
NoFlowControl);//流控制:
if(m_port->open(QIODevice:
ReadWrite))
{//成功打开串口
m_port->setRequestToSend(true);//设置RTS为高电平
m_port->setDataTerminalReady(true);//设置DTR为高电平
首先new一个QSerialPort对象,然后设置该对象的串行通讯参数,最后调用QSerialPort:
open函数打开串口。
这里需要说明一下流控制。
通讯的双方A和B,假如A给B发送数据时,B反应过慢,A不管不顾的不停发送数据,结果会导致数据丢失。
为了防止这种情况发生,可使用流控制(也叫握手)。
软件流控制(XON/XOFF):
通讯的一方(B)如果不能及时处理串口数据,会给对方(A)发送XOFF字符,对方接收到这个字符后,会停止发送数据;B不再忙的时候,会给A发送XON字符,A接收到这个字符后,会接着发送数据。
软件流控制最大的问题就是不能传输XON和XOFF。
硬件流控制(RTS/CTS):
硬件流控制需要按下图连接两个串口设备的RTS和CTS。
图1.3
通讯的一方(B)如果不能及时处理串口数据,会设置自己的RTS为低电平,B的RTS连着对方(A)的CTS,A发现自己的CTS为低电平,将停止发送数据;B不再忙的时候,会设置自己的RTS为高电平,A发现自己的CTS为高电平,将接着发送数据。
上面的代码中,设置流控制为无,其含义为:
不管对方是否能够反应过来,这边只管发送数据。
当流控制为硬件时,系统会自动管理RTS和DTR的状态。
否则,应该设置RTS和DTR为高电平,通知对方可以发送串口数据了。
1.4setRequestToSend在Windows上的BUG
经测试,流控制为无时,调用m_port->setRequestToSend(true);是没有任何效果的。
下面的Qt源代码节选自文件C:
\Qt\Qt5.7.0\5.7\Src\qtserialport\src\serialport\qserialport_win.cpp(C:
\Qt\Qt5.7.0是Qt的安装目录)
boolQSerialPortPrivate:
setRequestToSend(boolset)
EscapeCommFunction(handle,set?
SETRTS:
CLRRTS)){
setError(getSystemError());
returnfalse;
returntrue;
上面的代码调用EscapeCommFunction(handle,SETRTS)似乎没什么问题,但是请看下面的代码:
setFlowControl(QSerialPort:
FlowControlflowControl)
.........
dcb.fRtsControl=RTS_CONTROL_DISABLE;
switch(flowControl){
caseQSerialPort:
NoFlowControl:
break;
SoftwareControl:
HardwareControl:
dcb.fRtsControl=RTS_CONTROL_HANDSHAKE;
硬件流控制时dcb.fRtsControl为RTS_CONTROL_HANDSHAKE,这个没问题。
问题出在dcb.fRtsControl=RTS_CONTROL_DISABLE上,它直接禁用了RTS,所以EscapeCommFunction(handle,SETRTS)并不能设置RTS为高电平。
那么m_port->setDataTerminalReady(true)为什么又是正常的呢?
看代码:
setDataTerminalReady(boolset)
dcb.fDtrControl=set?
DTR_CONTROL_ENABLE:
DTR_CONTROL_DISABLE;
set为true时,dcb.fDtrControl为DTR_CONTROL_ENABLE,所以可以设置DTR为高电平。
1.5读取串口数据
m_port->readAll(QIODevice:
readAll)用来读取串口数据。
不过,它是异步执行的。
什么是异步呢?
那就是即使对方还没有发送串口数据,m_port->readAll也会立即返回,而不是傻傻的等着对方发送数据过来后再返回。
既然是异步的,那么何时读取串口数据就成为了关键。
Qt提供的方案就是使用信号、槽。
connect(m_port,SIGNAL(readyRead()),this,SLOT(slotReadData()));
当对方发送串口数据后,将触发m_port的信号QIODevice:
readyRead。
上面的代码将信号readyRead与槽函数slotReadData连接了起来,因此槽函数slotReadData将被调用,其代码如下:
voidWidget:
slotReadData()
QByteArraydata;
constintnMax=64*1024;
for(;;)
data=m_port->readAll();//读取串口数据
if(data.isEmpty())
{//没有读取到串口数据就退出循环
//读取到的串口数据,加入到QByteArraym_dataCom
m_dataCom.append(data);
if(m_dataCom.size()>nMax)
{//防止m_dataCom过长
m_dataCom=m_dataCom.right(nMax);
ui->txtRecv->setText(m_dataCom);//将m_dataCom显示到文本框
ui->txtRecv->moveCursor(QTextCursor:
End);//移动文本框内的插入符
1.6发送串口数据
m_port->write(QIODevice:
write)用来发送串口数据,不过它也是异步的。
也就是说:
代码m_port->write("123");会立即返回,至于数据"123"何时会发送给对方,那是操作系统的事情。
操作系统不忙的时候,才会做此项工作。
参考如下代码:
charszData[32];
strcpy(szData,"123");
m_port->write(szData);
m_port->write(szData);这行代码会立即返回。
操作系统在空闲时才会将szData里的数据发送给对方。
问题是:
m_port->write返回后,变量szData有可能已经被析构掉了,此时操作系统再访问szData所指向的内存,结果不可预料。
为此,需要改进上述代码,将异步通讯更改为同步通讯:
m_port->waitForBytesWritten(5000);
就增加了一行代码m_port->waitForBytesWritten(5000);其含义为:
操作系统把串口数据szData发送出去后,m_port->waitForBytesWritten才会返回。
不过,总不能无限制等下去吧?
5000就是等待时间的最大值,其单位为毫秒,5000毫秒就是5秒。
1.7同步读取
异步通讯的效率比较高,但是代码结构比较复杂。
有时,需要同步读取。
如:
给对方发送字符串Volt,对方回应电压值5。
代码会这么写:
m_port->write("Volt");
data.isEmpty())
{//读到数据了,退出循环
通过一个无限循环,将异步读取变成了同步读取。
不过,上述代码运行时,CPU占用率将会达到100%(单核CPU)。
为此,需要改进代码:
while(m_port->waitForReadyRead(3000))
修改了一行代码m_port->waitForReadyRead(3000),其含义为等待对方发送串口数据过来。
如果对方发送串口数据过来了,它返回true,然后使用m_port->readAll读取串口数据;如果对方在3秒内都没有发送串口数据过来,它返回false,退出循环。
1.8本文示例代码
本文示例代码已上传至git服务器,具体如下:
为了便于测试,可使用VirtualSerialPortDriver7.1创建一个串口对COM100、COM200,如下图所示:
图1.4
上图的COM100、COM200是虚拟出来的。
COM100发送的数据将会被COM200接收到,反之亦然。
下图是测试界面:
图1.5
本文示例代码打开了COM100,另一个串行通讯程序打开了COM200。
两者可以相互发送数据。
1.9Qt示例代码
安装完Qt5.7.0后,Qt串行通讯的示例代码也被安装了,如下图所示:
图1.6
C:
\Qt\Qt5.7.0是笔者安装Qt5.7.0时的安装目录。
需要进一步了解Qt串行通讯的可查看这些示例。
creaderasync表示异步读取;creadersync表示同步读取……更多信息查看Qt帮助。
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1