Qt 串口连接
Qt 串口连接
使用 Qt 开发上位机程序时,经常需要用到串口,在 Qt 中访问串口比较简单,因为 Qt 已经提供了 QSerialPort 和 QSerialPortInfo 这两个类用于访问串口。
使用 QSerialPort
Qt 提供的 QSerialPort 类继承于 QIODevice,也就是说,除了少数几个串口特有的属性需要单独设置外,可以像一般的 IO 设备(最常见的是文件)一样访问串口。
在项目中加入对串口的支持,先在 .pro 项目工程文件中加入
QT += serialport
然后在程序中包含 QSerialPort 的头文件,即可使用该串口类:
// file name: comm.cpp
// class: Comm
#include "comm.h"
#include <QSerialPort>
...
QSerialPort port;
connect( port, &QIODevice::readyRead, this, &Comm::onRead ); // 异步方法,连接 readyRead 信号和数据响应处理槽函数
port.setPortName( "COM1" ); // 设置串口
port.open( QIODevice::ReadWrite ); //读写方式打开
成功打开串口后,当上位机从串口接收到数据时就会发出 readyRead 信号,由 Qt 的事件派遣机制调用响应的 onRead 槽函数,在 onRead 函数中对接收到的数据进行处理即可。
出现问题
Qt 中使用串口比较简单,上下位机通讯是采用的 RS485 协议,当下位机发送数据到上位机时,上位机能够正常接收并处理。
但是在实际使用过程中却发现一些问题:
但如果此时对上位机程序进行操作,如向下位机发送一些指令,将会导致 RS485 线上的电平信号出现异常,导致上位机无法正常收到一些数据,而下位机也没有正确接收到上位机的指令,因此不会回复上位机。
问题的原因在于 RS485 串行协议是半双工协议,即通讯双方无法同时发送数据,若同时使用数据通道,将会导致通道上的电平异常,进而导致数据异常(乱码)。
半双工协议不支持通讯双方同时发送数据,那么只要在发送数据前检测当前串口线路是否被另一方占用,等待串口线路空闲时再发送数据就不会发生冲突。
检测线路是否被占用,在上位机这边处理要方便些,具体做法是实时检测串口,当串口中接收数据时,认为此时不能发送数据;而超过若干 ms 后仍未接收到下位机的数据时,认为此时可以发送数据。
解决方法是,在上述代码中添加一个计时器,超过 5ms (波特率为9600时,5ms 大约可以发送 4 个字节)未接收到数据时认为 RS485 线路空闲。
然而添加计时器后仍然无法获取准确的串口线路状态。并且在示波器上观察的结果显示,上位机发送下位机指令的时机有时会在下位机停止发送数据后 3ms 开始发送数据,有时会在下位机停止发送数据后十多毫秒内开始发送数据,总之上位机发送数据的时机是不受控的。
最开始猜测可能是 Qt 的事件循环机制对于事件处理不及时导致无法实时检测串口线路状态,查阅 QSerialPort (QIODevice)的 API 后,发现接收数据有阻塞的 API waitForReadyRead
,因此尝试使用多线程+阻塞式方式检测串口状态。
多线程与阻塞式
Qt 的多线程较其他的编程语言有些不同,用起来其实非常方便,用法如下:
// file name: commmgr.cpp
// class CommMgr
#include <QObject>
#include <QThread>
...
// obj 必须是 QObject 或其子类的实例指针,并且不能传入 parent 参数,Comm 是
// QObject 的子类并使用了 Q_OBJECT 宏
Comm *obj = new Comm;
// 实例化 thread 时可以传入 parent 参数。
QThread *thread = new QThread( this );
obj->moveToThread( thread )
// 不能直接在主线程中调用 obj 的方法,若直接调用,则该方法将会在主线程中执行
// obj->init();
// 要使 init 方法在子线程 thread 中执行,可以通过 Qt 的元对象提供的 invokeMethod 调用
// 该方法,或者连接某个信号到 obj 的 "init" 槽中,通过发射信号调用 init 槽函数。
QMetaObject::invokeMethod( obj, "init" );
// connect( this, &CommMgr::init, obj, &Comm::init );
需要注意的是要置入子线程的对象 obj
不能设置 parent,否则在运行时就会出现错误提示,另外要注意的是 QIODevice 及其子类只能在例化它的线程中使用,如果 Comm 的构造函数中就例化了串口,那么在运行时也会得到错误提示。所以这里用到了一个 init 函数,在 obj
例化并移动至子线程之后,在子线程中执行 init
方法,这样就能避免运行时出错。
为了控制子线程,这里引入了一个线程管理器类 CommMgr
,并在该管理器中增加与 Comm
相应的信号和槽,以便转发其它组件的信号到 Comm
或从 Comm
中接收数据转发给其它组件。
接下来修改 onRead 槽函数,使用阻塞式方法读取串口数据:
// file name: comm.cpp
// class: Comm
void Comm::onRead() {
QByteArray data;
while( m_serial->waitForReadyRead( m_waitTime ) ) {
data = m_serial->readAll();
emit receiveRawData( data );
data.clear();
}
// 未接收到数据时,认为串口处于空闲状态,可以向串口发送数据
handleQuery();
// 调用 QCoreApplication 的事件处理方法,用于分发其他线程发送的信号
QCoreApplication::processEvents();
}
只要能够从串口中接收到数据,就认为串口被占用,那么就会读取串口中收到的所有数据并发送给其它组件。
只有当 waitFroReadyRead 方法超时后才可以执行查询,向串口发送数据,即 handleQuery()
方法。
对 onRead()
方法的调用在打开串口后,使用 while 循环进行重复调用,因此每次执行完一遍 onRead 方法后,需要调用 Qt 的事件处理方法,否则子线程可能就无法结束。
采用多线程+阻塞式方法对串口后,观察单片机处的串口线路电平发现仍然会出现上下位机通讯时发生冲突的情况。
解决问题
半双工通讯的模式失败后,又采用了 Windows 提供的串口 API 队串口进行监测,并在接受到数据时,打印出时间。调试后发现不论从串口中接收到多少个字节的数据,每一行数据都会相差 16ms 才会被上位机接收到。因此断定,问题的原因既不在于 Windows 系统,也不在于 Qt 的事件循环机制。
由于采用的 USB-RS485 转接线中是将 RS485 中的数据转接到 USB,猜测可能是由于转接线上有延迟导致无法对串口进行实时监测。
查看 Windows 设备管理器,发现 USB-RS485 转接器采用的驱动是 FT232R 驱动,搜索后找到一篇关于该转接器的介绍,notes-on-ftdi-latency-with-arduino 详细的描述了这一问题并且给出了解决方法,其中一种是:进入设备管理器,找到 USB Serial Port 属性 ==> 高级。
将图中红色方框内的延迟计时器的值设为 1即可。
当把串口的延时计时器设为1ms时,实时检测串口的类在发送数据时就不会与下位机冲突。
总结
Qt 的串口类使用起来很方便,一般不需要用到阻塞式的方法 waitForReadyRead,它已经提供了异步非阻塞的信号 readyRead,因此实际上采用 信号+定时器
方式对串口进行实时监测也是有可能的。
为了方便,将适用于全双工模式的串口类和适用于半双工模式的串口类进行抽象,提取了抽象的串口操作基类,最开始认为好像多做了很多事情,后来发现对代码的整体结构和可扩展性都有很大的帮助。增加虚拟串口类用于模拟下位机发送数据时,只需要让虚拟串口类继承抽象通讯基类,实现基类中定义的纯虚方法,在工厂模式下添加虚拟串口类的例化即可。增加网络通讯类时,也可以方便的继承抽象通讯基类(用到的 QTcpSocket 也是继承自 QIODevice),并且不用修改现有代码。
另外值得一提的是 Qt 的多线程机制,虽然和大多数编程语言的多线程有所区别,但是使用起来非常方便,需要注意的是 QThread 的功能更像是一个线程管理句柄,有这个句柄才能对其它线程作出调度。
Qt 串口连接的更多相关文章
- Qt串口通信
1. Qt串口通信类QSerialPort 在Qt5的的更新中,新增了串口通信的相关接口类QSerialPort,这使得在开发者在使用Qt进行UI开发时,可以更加简单有效地实现串口通信的相关功能. 开 ...
- QT5入门之23 -QT串口编程(转)
QT5入门之23 -QT串口编程 QT5有专门的串口类: QSerialPort:提供访问串口的功能 QSerialPortInfo:提供系统中存在的串口的信息 具体使用方法: 1.在pro文件中 ...
- Qt 串口类QSerialPort 使用笔记
Qt 串口类QSerialPort 使用笔记 虽然现在大多数的家用PC机上已经不提供RS232接口了.但是由于RS232串口操作简单.通讯可靠,在工业领域中仍然有大量的应用.Qt以前的版本中,没有提供 ...
- QT串口助手(三):数据接收
作者:zzssdd2 E-mail:zzssdd2@foxmail.com 一.前言 开发环境:Qt5.12.10 + MinGW 实现的功能 串口数据的接收 ascii字符形式显示与hex字符形式显 ...
- 【嵌入式开发】嵌入式 开发环境 (远程登录 | 文件共享 | NFS TFTP 服务器 | 串口连接 | Win8.1 + RedHat Enterprise 6.3 + Vmware11)
作者 : 万境绝尘 博客地址 : http://blog.csdn.net/shulianghan/article/details/42254237 一. 相关工具下载 嵌入式开发工具包 : -- 下 ...
- linux下的qt串口通信
1.linux下的qt串口通信跟windows唯一的差别就是端口号的名字,windows下面是COM,而linux是ttyUSB0的路径 2.一般情况下linux插上USB转串口线就可以在/dev/目 ...
- 【转】QT 串口QSerialPort + 解决接收数据不完整问题
类:QSerialPort 例程:Examples\Qt-5.9.1\serialport\terminal,该例子完美展示了qt串口收发过程,直接在这上面修改就可以得到自己的串口软件.核心方法 // ...
- Linux 虚拟串口及 Qt 串口通信实例
Linux 虚拟串口及 Qt 串口通信实例 2011-06-22 17:49 佚名 互联网 字号:T | T Linux 虚拟串口及 Qt 串口通信实例是本文所要介绍的内容,在实现过程中,打开了两个伪 ...
- 【记录】恢复win7与ARM开发板TQ2440的串口连接
1.给板子上电. 2.接好物理上的串口连接,板子那端就是普通的RS232串口,电脑这端是USB转串口的线的USB这头,连到电脑上,然后在Win7系统下,先去看看,当前连接的USB虚拟出来的串口是哪个口 ...
随机推荐
- WndProc和hook区别
1)WndProc函数作用:主要在程序中拦截并处理系统消息和自定义消息 比如:windows程序会产生很多消息,比如你单击鼠标,移动窗口都会产生消息.这个函数就是默认的消息处理函数.你可以重载这个函数 ...
- HeadFirst设计模式中的笔记
1.『策略模式』 定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户. OO基础:抽象 封装 多态 继承 OO原则:封装变化 多用组合,小用继承 针对接口编 ...
- HBase - 伪分布式安装过程
环境 - hadoop - 没有zookeeper(用hbase自带的zookeeper,当然后期我会改用独立的zookeeper) HBase介绍 参考:hbase是什么? hbase下载 地址:h ...
- [Windows] 程序生成出现语法错误: 意外的令牌“标识符”,预期的令牌为“类型说明符”
程序生成出现语法错误: 意外的令牌“标识符”,预期的令牌为“类型说明符” 将平台工具集改为VS 2015 (v140) ,重新编译即可
- SpringAOP使用
AspectJ 注解: 1.@Aspect.@Pointcut.Advice @Aspect @Component public class SecurityAspect { @Autowired A ...
- cuda&vs2010的属性配置
平时总需要新建工程,但是却总忘记该修改哪里,于是寻找了官方的项目,截下其中的属性修改图. 1 2 3 4 5 6 7 8 9 10 11
- kindedtor的基本使用
首先需要进入官网下载kineditor相关文件: 然后写代码: <!DOCTYPE html> <html> <head> <meta charset=&qu ...
- 了解Linux系统
++++++++++++++++++++++++++++++++++++++++++++++++++++ 有用的参考链接: 带你初识Linux操作系统:https://www.linuxidc.com ...
- HDU 5934 (强连同分量+缩点)
题意: 给出n个炸弹的信息 :坐标x , 坐标y , 爆炸半径 , 成本: 如果一个炸弹被引爆那这个范围的都爆炸 , 问最小的成本是多少? 题意:首先先来个n^2 暴力出某个炸弹爆炸波及的其他炸弹,用 ...
- POJ 3659 Cell Phone Network 最小支配集模板题(树形dp)
题意:有以个 有 N 个节点的树形地图,问在这些顶点上最少建多少个电话杆,可以使得所有顶点被覆盖到,一个节点如果建立了电话杆,那么和它直接相连的顶点也会被覆盖到. 分析:用最少的点覆盖所有的点,即为求 ...