目录

第1章 Qt 串行通讯    1

1.1 配置.pro文件    1

1.2 查询串口信息    1

1.3 配置、打开串口    3

1.4 setRequestToSend在Windows上的BUG    5

1.5 读取串口数据    6

1.6 发送串口数据    7

1.7 同步读取    7

1.8 本文示例代码    8

1.9 Qt 示例代码    10

第1章 Qt 串行通讯

最近要在 Android 手机上开发串行通讯程序,为此学习了一下Qt的串行通讯。本文中,Qt的版本为 5.7.0。

1.1 配置.pro文件

使用 Qt 5.7.0 创建"Qt Widgets Application"类型的项目,然后修改.pro文件,如下图所示:

图1.1

给变量 QT 增加serialport,说明程序里将使用串行通讯相关的类。

1.2 查询串口信息

本节将通过代码查找系统里的串口,然后填入下图所示的下拉列表框中。

图1.2

函数 QSerialPortInfo::availablePorts 会返回系统所有的串口,它的使用请参考如下代码:

#include <QSerialPortInfo>

#include <map>

std::map<int,QString> mapPort;

QString sPort;

int nPort;

foreach (const QSerialPortInfo &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 表示错误

\***************************************************************/

int GetIntInStr(const QString&s)

{

bool    bOK    =    false;     //是否发现了数字

int        n        =    0;

int        nLenS    =    s.length(); //字符串长度

ushort    c        =    0;

for(int i = 0;i < nLenS;++i)

{

c    =    s[i].unicode();

if(c >= '0' && c <= '9')

{

bOK    =    true;

n        =    n * 10 + (c - '0');

}

}

if(!bOK)

{

n    =    -1;//没有数字,返回 -1

}

return n;

}

根据 std::map<int,QString> mapPort填充下拉列表框QComboBox cboPort的代码如下:

ui->cboPort->clear();

for(std::map<int,QString>::iterator it = mapPort.begin();

it != mapPort.end();++it)

{

ui->cboPort->addItem(it->second);

}

1.3 配置、打开串口

配置、打开串口的代码如下:

#include <QSerialPort>

m_port = new QSerialPort();

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.4 setRequestToSend在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的安装目录)

bool QSerialPortPrivate::setRequestToSend(bool set)

{

if (!::EscapeCommFunction(handle, set ? SETRTS : CLRRTS)) {

setError(getSystemError());

return false;

}

return true;

}

上面的代码调用EscapeCommFunction(handle,SETRTS)似乎没什么问题,但是请看下面的代码:

bool QSerialPortPrivate::setFlowControl(QSerialPort::FlowControl flowControl)

{

... ... ...

dcb.fRtsControl = RTS_CONTROL_DISABLE;

switch (flowControl) {

case QSerialPort::NoFlowControl:

break;

case QSerialPort::SoftwareControl:

break;

case QSerialPort::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)为什么又是正常的呢?看代码:

bool QSerialPortPrivate::setDataTerminalReady(bool set)

{

... ... ...

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将被调用,其代码如下:

void Widget::slotReadData()

{

QByteArray data;

const int nMax = 64 * 1024;

for(;;)

{

data = m_port->readAll(); //读取串口数据

if(data.isEmpty())

{//没有读取到串口数据就退出循环

break;

}

//读取到的串口数据,加入到QByteArray m_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"何时会发送给对方,那是操作系统的事情。操作系统不忙的时候,才会做此项工作。

参考如下代码:

char szData[1024];

memset(szData,'1',sizeof(szData));

szData[sizeof(szData)-1]='\0';

m_port->write(szData);

m_port->close();

m_port->write(szData);会把1023字节的'1'发送出去。假如波特率为1200,则这些数据需要9秒才能发送完毕。因为m_port->write是异步执行的,所以m_port->write(szData)只是把数据提交给了操作系统就立即返回了。操作系统克隆了一份串口数据szData,在空闲的时候发送,还没发送完毕m_port->close()就被执行了。结果就是大部分的串口数据丢失。

为了保证上述代码不丢失串口数据,需要将异步通讯更改为同步通讯:

char szData[1024];

memset(szData,'1',sizeof(szData));

szData[sizeof(szData)-1]='\0';

m_port->write(szData);

m_port->waitForBytesWritten(10000);

m_port->close();

就增加了一行代码m_port->waitForBytesWritten(10000);其含义为:操作系统把串口数据发送出去后,m_port->waitForBytesWritten才会返回。不过,总不能无限制等下去吧?10000就是等待时间的最大值,其单位为毫秒,10000毫秒就是10秒。

1.7 同步读取

异步通讯的效率比较高,但是代码结构比较复杂。有时,需要同步读取。如:给对方发送字符串 Volt,对方回应电压值 5。代码会这么写:

m_port->write("Volt");

m_port->waitForBytesWritten(5000);

QByteArray data;

for(;;)

{

data = m_port->readAll(); //读取串口数据

if(!data.isEmpty())

{//读到数据了,退出循环

break;

}

}

通过一个无限循环,将异步读取变成了同步读取。不过,上述代码运行时,CPU占用率将会达到100%(单核CPU)。为此,需要改进代码:

m_port->write("Volt");

m_port->waitForBytesWritten(5000);

QByteArray data;

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 服务器,具体如下:

https://github.com/hanford77/SerialPort

https://git.oschina.net/hanford/SerialPort

为了便于测试,可使用Virtual Serial Port Driver 7.1创建一个串口对COM100、COM200,如下图所示:

图1.4

上图的COM100、COM200是虚拟出来的。COM100发送的数据将会被COM200接收到,反之亦然。下图是测试界面:

图1.5

本文示例代码打开了COM100,另一个串行通讯程序打开了COM200。两者可以相互发送数据。

1.9 Qt 示例代码

安装完 Qt 5.7.0 后,Qt 串行通讯的示例代码也被安装了,如下图所示:

图1.6

C:\Qt\Qt5.7.0是笔者安装Qt 5.7.0时的安装目录。

需要进一步了解Qt串行通讯的可查看这些示例。如:creaderasync 表示异步读取;creadersync 表示同步读取……更多信息查看Qt帮助。

串行通讯之Qt的更多相关文章

  1. 【Arduino】使用C#实现Arduino与电脑进行串行通讯

    在给Arduino编程的时候,因为没有调试工具,经常要通过使用串口通讯的方式调用Serial.print和Serial.println输出Arduino运行过程中的相关信息,然后在电脑上用Arduin ...

  2. 串行通讯之UARTLoopback

    目录 第1章串行通讯之UARTLoopback    2 1 USB转串口    2 2 USB Accessory    2 3 连入手机    3 4 代码改进    4 5 打开串口    4 ...

  3. 串行通讯之.NET SerialPort

    第1章串行通讯之.NET SerialPort    2 1 枚举串口    2 2 打开/关闭串口    2 3 写数据    3 3.1 写二进制数据    3 3.2 写文本数据    4 4 ...

  4. COM口,串行通讯端口,RS-232接口 基础知识

    COM口即串行通讯端口. COM口的接口标准规范和总线标准规范是RS-232,有时候也叫做RS-232口.电脑上的com口多为9针,最大速率115200bps.通常用于连接鼠标(串口)及通讯设备(如连 ...

  5. STM32L476应用开发之三:串行通讯实验

    在我们的项目需求中,有两个串口应用需求,一个是与炭氢传感器的通讯,另一个是与显示屏的通讯.鉴于此,我们需要实验串行通讯. 1.硬件设计 串行通讯一个采用RS232接口,另一个直接采用TTL方式.我们在 ...

  6. 基于51的串行通讯原理及协议详解(uart)

    串行与并行通讯方式并行:控制简单,传输速度快.线多,长距离成本较高且同时接受困难.串行:将数据字节分成一位一位的行驶在一条传输线上进行传输.如图:   同步与异步串行通讯方式同步串行通讯方式:同步通讯 ...

  7. STM32学习笔记——SPI串行通讯(向原子哥学习)

    一.SPI  简介 SPI是 Serial Peripheral interface 的缩写,就是串行外围设备接口.SPI 接口主要应用在  EEPROM, FLASH,实时时钟,AD 转换器,还有数 ...

  8. boost库在工作(40)串行通讯

    现代的计算机技术进步很快,各种的通讯也日新月异,像USB.网络.蓝牙.WIFI等通讯技术飞速地出现,改变了整个计算机的通讯能力,速度已经达到GBit级别.但是有一种最原始的通讯方式,还是保留了30年, ...

  9. 串行通讯协议--起止式异步通讯协议(UART)

    起止式异步通讯协议: 特点与格式: 起止式异步协议的特点是一个字符一个字符传输,并且传送一个字符总是以起始位开始,以停止位结束,字符之间没有固定的时间间隔要求.其格式如图3 所示.每一个字符的前面都有 ...

随机推荐

  1. Linux crond定时任务

    第1章 Crond是什么? Crond是linux系统用来定期执行命令或指定程序任务的一种服务或软件.一般情况下,我们安装完Centos5/6linux操作系统之后,默认便会启动Crond任务调度服务 ...

  2. datagrid 重写属性

    $.fn.panel.defaults.loadMsg = '数据加载中请稍后--'; //设置默认的分页参数 if ($.fn.datagrid) { $.fn.datagrid.defaults. ...

  3. 2016年11月10日 星期四 --出埃及记 Exodus 20:1

    2016年11月10日 星期四 --出埃及记 Exodus 20:1 And God spoke all these words: 神吩咐这一切的话说,

  4. centos6.5用户管理

    一.centOS6.5用户管理命令 useradd 新增用户 userdel  删除用户 passwd  修改用户密码 二.命令的使用 useradd useradd admin userdel us ...

  5. shell中awk用法

    简介 awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大.简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再 ...

  6. config文件 反射方法

     string testPropertyName = "MainFormText"; Properties.Settings setting = Properties.Settin ...

  7. Java开发、网络爬虫、自然语言处理、数据挖掘简介

    一.java开发 (1) 应用开发,即Java SE开发,不属于java的优势所在,所以市场占有率很低,前途也不被看好. (2) web开发,即Java Web开发,主要是基于自有或第三方成熟框架的系 ...

  8. visual studio运行时库MT、MTd、MD、MDd的研究(转载)

    转载:http://blog.csdn.net/ybxuwei/article/details/9095067 转载:http://blog.sina.com.cn/s/blog_624485f701 ...

  9. UVA 11461 - Square Numbers

    题目:统计区间中的平方数个数. 分析: ... #include <stdio.h> #include <string.h> ]; int main() { int i, a, ...

  10. Python3基础 使用技巧:把代码的字体变大

    镇场诗:---大梦谁觉,水月中建博客.百千磨难,才知世事无常.---今持佛语,技术无量愿学.愿尽所学,铸一良心博客.------------------------------------------ ...