一、前言
qextseriaport类的使用都是针对Windows平台的,既然Qt是跨平台的,那么qextseriaport也肯定是支持Linux平台的,在网上找了一下,找到一个针对Linux系统下应用qextseriaport的博文,基于linux (fedora 17)的QT串口通信实例,本文主要是针对这篇文章,根据在实验过程中出现的一些问题的总结,进一步详细说明在Linux系统下使用qextseriaport类,进行串口数据读取。
二、问题背景
大致为:有一个发送端周期性的往本机串口上发送数据,本机需要定时读取串口数据,解析,获取自己想要的信息。实际描述为:由于是做智能驾驶的,需要读取车辆速度,通过OBD设备读取并解析车辆速度,然后通过蓝牙发送给PC串口(也是通过一个蓝牙模块接收),PC端串口定时接收串口数据并解析。
在上述过程中,有一个问题需要注意:发送方是周期性的不停的发送数据,也就是说PC机串口一直有数据进来,最常见的是发送端频率比接收端频率高,即准备读取串口数据时总可以读取的数据,但也也导致了另一个问题,那就是怎么处理接收端串口缓冲,因为你每次的读取操作都必须要把当前串口缓冲区中数据全部读取出来,不能只读一部分,不然发送端一直有数据进来,而你每次都不读玩,就会造成缓冲区溢出。
三、串口的实质是什么
但凡接触过Linux系统知识或者了解计算机系统原理,那你一定通过一句话“在任何操作系统下,一切都是文件”,Linux系统就是这句话的最完美的实现,即在Linux系统下,你完全可以把所有东西当成是一个文件、即所有东西都可以按照“打开——设置——读写——关闭”的模式进行,同样,在Linux下串口也可以看成是一种文件(其实任何操作系统下都可以这样认为),你要读写串口数据,就必须先打开、设置串口,然后读写,读写完毕,就必须要关闭串口。
四、Ubantu下Qt 串口数据读取程序
首先,你可以先浏览一下基于linux (fedora 17)的QT串口通信实例,知道一个大致的流程,并按照要求下载qextseriaport源码,将相应文件拷贝到自己的工程目录下,就可以按照他说的编写代码了,其实他这个只是一个简单的demo,可以自己写一个稍微复杂一点的。
看了他的代码之后,可能你已经大致明白了,他的操作流程完全是按照“打开——设置——读写——关闭”的操作模式来操作串口的,其“打开和设置”过程都是在MainWindow的构造函数里面进行的,“读取”操作其实就是一个函数readAll(),为了实现定时读取,需要与一个QTimer connect,其实你完全可以不用定时器(在他的说明里面说的是必须要用定时器),定时器的功能是什么?不就是定时触发其所关联的函数吗?其实质是你要定期调用readAll()函数,你完全可以自己控制这个定时操作,即可以在你的主函数里面周期性调用readAll()函数就行了,最常见的,如果你要一直度,就在while(1)循环里面调用readAll()函数,“关闭”操作是在MainWindow的析构函数里面调用的。
readAll()函数,并没有与QTimer进行关联,因为我有一个主控程序(其实也就是mian函数啊),所以就在主控程序里面调用的readAll()函数,实现周期性的读取串口数据,我的读取车辆速度文件如下:
ReadCarSpeed.h
#ifndef READCARSPEED_H
#define READCARSPEED_H
void OpenCom(); //打开、设置串口
void ReadSpeed(); //读取、解析数据,关闭操作也是在退出界面时调用
#endif // READCARSPEED_H
ReadCarSpeed.cpp
#include "ReadCarSpeed.h"
#include "posix_qextserialport.h"
extern int carSpeed;
Posix_QextSerialPort *myCom;
void OpenCom()
{
//定义串口对象,并传递参数,在构造函数里对其进行初始化
myCom = new Posix_QextSerialPort("/dev/ttyUSB1",QextSerialBase::Polling);
myCom ->open(QIODevice::ReadWrite); //以读写方式打开串口
//设置串口参数
myCom->setBaudRate(BAUD9600); //波特率设置,我们设置为9600
myCom->setDataBits(DATA_8); //数据位设置,我们设置为8位数据位
myCom->setParity(PAR_NONE); //奇偶校验设置,我们设置为无校验
myCom->setStopBits(STOP_1); //停止位设置,我们设置为1位停止位
myCom->setFlowControl(FLOW_OFF); //数据流控制设置,我们设置为无数据流控制
myCom->setTimeout(50); //延时设置,设置为延时20ms
}
void ReadSpeed()
{
QByteArray temp = myCom->readAll(); //读取串口缓冲区的所有数据给临时变量temp
//把临时变量temp转换成整型变量carSpeed
QByteArray c = "KM/H"; //发送端发送的数据为“NKM/H”,其中N表示实际车速,由于发送端频率比接收端频率快,所以每次调用readAll()读取的数据中是多个连续的“NKM/H”字符串,形如“NKM/HNKM/HNKM/HNKM/HNKM/HNKM/HNKM/H”
//由于我需要的是最新的车辆速度,所以只需要当前读取字符串中最后一个速度字符子串,使用QByteArray的成员函数lastIndexOf就可以实现,其返回值是最后一个KM/H的位置,这属于C++字符串搜索知识点
//博友可以自行翻阅C++Primer(第五版P325)
int index = temp.lastIndexOf(c); //返回最后一个c匹配位置
QByteArray speedstr = temp.mid(index-1,1); //获取速度信息
carSpeed = speedstr.toInt(); //转换为整型,赋值给全局变量carSpeed(是一个全局变量)
}
(1)打开串口:
在ReadCarSpeed.cpp文件中,OpenCom()函数负责打开和设置串口,这里有一点需要啰嗦一下:myCom->setTimeout(50);延时设置函数,我这里设置的是50ms,对于这个延时,我的理解是本机串口的接收频率,注意这个接收频率是不同于你读取的频率的,这里的接收频率是本机串口缓冲区接收数据的延时,即没50ms就有一个数据进去接收缓冲区队列(要理解数据缓冲区实际就是一个队列,先进先出),而你读取的频率是你调用readAll()函数的频率,如我的读取频率就是主控程序的频率(大约为500ms),所以每次大约读取10个速度信息。
(2)数据读取与解析:
readAll()函数读取缓冲区中全部数据,然后解析数据,上面说过,每次读取操作大约读取了10个速度信息(或者更多),而我需要的是最新的车速信息,而缓冲区队列中最新的车速信息是队列中的最后一个速度,所以只提取QByteArray中的最后一个速度,这通过C++的字符搜索(匹配)实现,具体可以查阅C++Primer(第五版P325)。这里QByteArray就是一个字节数组,可以存储任何数据,它的用法可以查看Qt Assistant文档。
总之,在我的应用中,首先是调用一次OpenCom函数,打开并设置串口,然后周期性的调用ReadSpeed函数(其实质就是周期性的调用readAll函数)就能读取最新的车速信息。