APP下载

嵌入式Qt实现串口数据读取的事件驱动方法

2022-05-30冯迅云利军

电脑知识与技术 2022年31期
关键词:接收数据字节串口

冯迅 云利军

摘要:事件驱动是提高串口数据接收效率的有效方式,但在Linux系统下一般都不支持该方式。在嵌入式Linux环境下,可借用Qt的QSocketNotifier模块来实现串口读取数据的事件驱动。实验证明,该方法是一种行之有效的方式,当串口收到有效数据时,模块就以事件方式通知主程序进行处理,不仅能提高串口的通信效率,还能有效降低接收数据丢失的情况,特别适用于接收任意长度的数据帧。

关键词:嵌入式;Linux;QSocketNotifier;串口;事件驱动

中图分类号:TP311      文献标识码:A

文章编号:1009-3044(2022)31-0084-03

在嵌入式Linux系统的图形界面设计中,比较常见的是通过Qt库来实现,而在进行Qt程序设计时,也经常会用到串口(UART) 通信[1]。现在基于Qt5.1以上的版本中,集成有串口模块(如QSerialPort) ,或者使用第三方开发的串口模块控件(如qextserialport等)[2]。但无论采用哪种方式,在Linux系统下对于串口的数据接收都只能使用查询(Polling) 的方式来实现,而在Windows系统下就可以使用效率较高的所谓事件驱动(Event Driven) 方式[3]。查询方式需要CPU反复对串口进行读取,看是否有发送来的可读数据,会消耗大量的CPU资源,一般的做法是把串口查询放到一个常驻线程中,以获得较高的效率。而事件方式则不同,只要串口接收到数据,就会以事件的方式通知CPU去执行相关的操作,在没有接收到数据时CPU可以做其他事情,所以效率较高,使用起来也更加便捷。

在Qt的官方文档中,并不推荐使用常驻线程的方式来处理接收数据,因此给出了替代的多种方案,其中之一就是使用QSocketNotifier方式,它为Linux下的Qt串口通信提供了较为适宜的解决方案[4]。以下就结合Qt中的QSocketNotifier模块,实现一个通用的、基于事件驱动的串口通信程序。该例子基于Qt4.8.7版本,结合Linux的串口模块编程,在Qt中通过QSocketNotifie类来实现串口信息的侦听和读取。

1 Qt版本

目前主要的Qt版本有Qt4和Qt5两类,其中Qt5是当前桌面应用程序中使用较为普遍的版本,Qt4则为稍早前的版本。由于Qt4相对比较成熟,虽然组件没有Qt5那么多,功能也没有Qt5全面,但Qt4基本能满足大多数图形界面开发的需求,包括界面美化、OpenGL等的支持[5]。而对于像嵌入式系统这类本身硬件资源就很有限的设备,选择Qt版本主要的一条原则是选择适合的而不是最新的,即够用就好,因此本例选择了Qt4的版本。

在Qt4系列版本中,Qt4.8.7是一个最终版本,它解决了以往Qt中存在的所有Bug,是一个非常稳定的版本,同时它也是Qt4中功能最丰富的版本,甚至包含有部分Qt5的新功能。同时,Qt4.8.7还是CPU占用率最小的版本,也是打包后需要携带的动态库最少的版本,非常适合于像S3C2416这类运行频率不是很高的单核CPU组成的嵌入式平台。

2 QSocketNotifier模块

在Qt4.0以上的版本中,新增加了一个名为QSocketNotifier的模块,通过它来侦听系统文件的操作,并把操作转换为Qt事件进入系统的消息循环队列,当发生相应的事件时会调用预先设置的事件接受函数来进行处理。QSocketNotifier一共设置了三类事件:Read、Write、Exception,具体如表1所示[6]。

QSocketNotifier类继承自QObject类,它封装了Linux内核对于底层端口的操作,用户不需要去了解底层的各种结构体,让程序开发变得更加容易。QSocketNotifier类支持监视文件描述符上的活动,它以文件描述符的方式将Qt的事件循环与其他事件循环进行集成。使用QSocketNotifier来处理串口接收数据时,只需要把它设置为Read属性即可,每个QSocketNotifie对象侦听一个事件,在使用open方法打开串口并设置好属性后,就可以创建套接字通知程序来监视文件描述符,创建成功后就可以使用QSocketNotifier来侦听串口数据了。它是基于事件驱动的,配合Qt的信号与槽机制,当有可读数据时,QSocketNotifier就会发射activated信号,主程序只需要创建一个槽连接到该信号,然后在槽函数中处理串口读到的数据即可[7]。通过这样的方式,读取串口的任务就不用新开辟线程来处理了,这就是Qt官方给出的串口接收数据的建议。

在使用QSocketNotifier要注意,如果需要同时监视同一文件描述符的读取和写入,则必须创建两个套接字通知程序,不能在同一个套接字上安装两个相同类型(读、写、异常)的套接字通知程序。

在Linux系统中,所有对设备的操作都是通过对设备节点文件的操作来进行的,这一机制正好符合QsocketNotifie侦听系统文件变化的机制。特别是在嵌入式Linux系统中,使用QSocketNotifie来处理串口数据的接收,可谓珠联璧合、相得益彰。

3 串口初始化

在Linux系统中,所有的设备文件都位于/dev目录下,以tty为前缀的文件是终端设备节点文件。以tty加大写S开头的即为串口设备节点文件,如ttyS0即为串口0(即COM0) 。但在嵌入式Linux中,串口一般是以ttySAC為前缀的,如ttySAC0表示串口0。在Linux系统下要对文件进行相应的操作,必须先打开它,串口设备文件也不例外,所以需要调用open函数来打开对应的串口端口。在打开文件的同时,要设置好串口的属性,如可读可写、不将设备分配为控制终端、无延时模式等[8]。然后,对打开的串口进行波特率、数据位数、停止位位数、奇偶校验、硬件流量控制等配置。在Linux系统中,这些串口的配置项目由一个名为termios 结构体来实现[8],termios提供了一个常规的终端接口,用于控制非同步通信端口。通过termios 结构体,再结合前面的打开串口系统调用API函数open,可以在Qt下写成一个串口初始化函数,如下:

int MainWindow::UART_Init(void)

{

struct termios opt;

fdUart = open("/dev/ttySAC0", O_RDWR | O_NOCTTY | O_NDELAY);

tcgetattr(fdUart, &opt);

opt.c_cflag &= ~(PARENB | CSTOPB | CSIZE | OPOST);

opt.c_cflag |= (CS8 | CLOCAL | CREAD);

cfsetispeed(&opt, B9600);

cfsetospeed(&opt, B9600);

opt.c_cc[VMIN] = 0;

opt.c_cc[VTIME] = 0;

tcflush(fdUart,TCIOFLUSH);

if (tcsetattr(fdUart, TCSANOW, &opt) < 0)

return -1;

return fdUart;

}

经过上述初始化程序,串口0被设置为:可读可写、不将设备分配为控制终端、无延时模式、无校验、1位停止位、8位数据位、波特率9600等模式。在实际应用中应该根据项目本身的具体情况来进行配置。

4 数据接收及处理

在进行Qt程序设计时,先调用上面的串口初始化函数,然后创建一个QSocketNotifier类的对象并将其实例化,最后建立信号与槽的连接函数,如下:

MainWindow::MainWindow(QWidget *parent) :

QMainWindow(parent),

ui(new Ui::MainWindow)

{

ui->setupUi(this);

readTimer = new QTimer(this);

UART_Init();

QSocketNotifier *com0_notifier;

com0_notifier = new QSocketNotifier(fdUart, QSocketNotifier::Read, this);

connect(com0_notifier, SIGNAL(activated(int)), this, SLOT(dataIncoming (void)));

connect(readTimer,SIGNAL(timeout()),this,SLOT(readCom()));

}

上述程序中,com0_notifier即為实例化的QSocketNotifier对象,而第一个connect函数就是把该对象发射的activated信号与自定义的数据接收槽函数dataIncoming进行连接。程序运行后,只要串口接收到数据,就会触发dataIncoming函数去对串口数据进行处理。但在嵌入式Linux系统中,受硬件缓冲区大小的限制,串口一般一次只能读取8个字节数据,大于8个字节的数据需要分次读取,这在设计程序时要给予充分考虑,以免造成数据丢失[9]。这里给出一种方法,需要针对接收到的整帧数据是小于8字节、等于8字节还是大于8字节三种情况分别进行讨论。

在接收到的数据小于8字节时,又分两种情况进行考虑:第一种情况是整帧数据本身就不满8字节,这时只需要直接返回这8个字节数据即可;第二种情况是可能在此次接收之前就已经接收到至少一次8字节的数据,现在接收到的是这帧数据的最末部分且未满8字节,这时就需要把当前收到的数据追加到前面已接收数据的最末尾,然后再一次性返回所有接收到的数据。

在接收到的数据正好为8字节时,也需要分两种情况进行考虑:第一种情况是虽然当前收到了8字节数据,但后续可能还会有接收数据,这时需要对该8字节数据进行暂存处理,然后等待再次接收后续数据;第二种情况是所接收的整帧数据刚好就是8字节,或本帧数据的最末部分正好是8字节,此时返回所有接收到的数据即可。但如何区分这两种情况却很困难,即如何判断要接收的数据的长度是只有8个字节还是8的整数倍个字节。一般的做法是引入超时机制,即在串口收到一次8字节数据后立刻开启定时器进行计时,当超出预定时间之后还没有收到下一次数据时,就可认为当前帧的数据已经全部接收完成了。超时的时长一般设定为几十个毫秒,具体值会依据嵌入式硬件环境的不同而不同,需要通过实验进行验证确定。在前面程序中的第二个connect函数就是用来实现超时连接的,当预设的定时时间到了之后,会发射timeout信号,该信号会触发自定义槽函数readCom,在该函数中获取串口接收到的完整数据。

根据上面讨论的接收数据的处理方法,可写出串口数据读取的槽函数dataIncoming,如下:

void MainWindow::dataIncoming(void)

{

char buff[8]={0};

int length=0;

length = read(fdUart,buff,8);

if(length>0 && length<8)

{ //本次只接收到小于8个字节的数据

if(count == 0)

{ //整帧数据小于8字节

data_length = length;

for(int i=0;i

data[i] = buff[i];

}

else

{ //上次已经接收到至少8字节数据,本次为最后的小于8字节数据

readTimer->stop();

data_length = data_length + length;

for(int i=0;i

data[i+8*count] = buff[i];

}

readCom(); //接收数据少于8字节,立即完成接收

}

else if(length>0)

{ //接收了8个字节,本帧数据很可能不止8字节

readTimer->stop();

data_length = data_length + length;

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

data[i+8*count] = buff[i];

count++;

readTimer->start(70); //启用超时

}

}

上述程序只是串口收到有效数据时触发的槽函数,在实际使用时,还需要再写一个获取完整数据的函数readCom。在该函数中,需要先关闭定时器readTimer,然后再通过全局变量data和data_length获取串口接收到的整帧数据内容及其长度,在数据使用结束之后,还需要对这两个变量进行清零,否则会影响下一帧数据的正确接收。

5 结束语

通过上述方法,实现了在嵌入式Qt下串口数据读取的事件驱动及任意长度数据帧的接收。经过实验证明,在S3C2416+Linux3.6.6+Qt4.8.7所组成的嵌入式系统中[10],超时选择60毫秒的情况下,波特率从1200到115200,字符数量从1个到100个,使用上述方法都实现了串口数据的正常接收,未发生数据丢失的情况,大大提高了串口数据通信的灵活性和可靠性。

參考文献:

[1] 郑忠楷,蒋学程,罗志灶.基于QT的串口通信程序设计[J].电子技术与软件工程,2019(24):236-238.

[2] 张新村,严殊.基于ARM的Linux系统下Qt串口助手的设计[J].软件导刊,2011,10(8):64-66.

[3] 刘依晗,丑武胜,董明杰.基于QT/E串口通信的手持监控器[J].现代电子技术,2013,36(20):110-112.

[4] 陈旭红,高文学.Qt/Embedded串口类的设计及应用[J].湖北汽车工业学院学报,2010,24(4):51-53,58.

[5] 殷立峰.Qt C++跨平台图形界面程序设计基础[M].北京:清华大学出版社,2014.

[6] The Qt Company Ltd[EB/OL].[2021-10-20].https://doc.qt.io/archives/qtjambi-4.5.2_01/com/trolltech/qt/core/QSocketNotifier.html.

[7] 胡军锋,尤泽萌.QSocketNotifier在端口监听中的应用[J].科技视界,2014(29):7,13.

[8] 屈志强,乔静,姚青.Linux终端设备连接属性控制及应用[J].电脑知识与技术,2010,6(24):6881-6883.

[9] 邱尚明,罗拥华,李冬睿.嵌入式Linux环境下的串口通信研究[J].福建电脑,2014,30(5):69-71,150.

[10] 冯迅,云利军.实用嵌入式Linux实验装置:CN210167009U[P].20200320.

【通联编辑:梁书】

收稿日期:2022-06-15

作者简介:冯迅(1974—) ,男,云南开远人,讲师,硕士,主要研究方向为单片机与嵌入式系统;云利军(1973—) ,男,内蒙古呼和浩特人,教授,博士,主要研究方向为视频图像处理与物联网技术。

猜你喜欢

接收数据字节串口
No.8 字节跳动将推出独立出口电商APP
冲激噪声背景下基于幅度预处理的测向新方法*
浅谈AB PLC串口跟RFID传感器的通讯应用
No.10 “字节跳动手机”要来了?
低复杂度多输入多输出雷达目标角度估计方法
简谈MC7字节码
单片机模拟串口数据接收程序的实现及优化
USB接口的多串口数据并行接收方法探索
基于蓝牙串口适配器的GPS接收机与AutoCAD的实时无线通信
基于并行控制的FPGA多串口拓展实现