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 串口连接的更多相关文章

  1. Qt串口通信

    1. Qt串口通信类QSerialPort 在Qt5的的更新中,新增了串口通信的相关接口类QSerialPort,这使得在开发者在使用Qt进行UI开发时,可以更加简单有效地实现串口通信的相关功能. 开 ...

  2. QT5入门之23 -QT串口编程(转)

    QT5入门之23 -QT串口编程   QT5有专门的串口类: QSerialPort:提供访问串口的功能 QSerialPortInfo:提供系统中存在的串口的信息 具体使用方法: 1.在pro文件中 ...

  3. Qt 串口类QSerialPort 使用笔记

    Qt 串口类QSerialPort 使用笔记 虽然现在大多数的家用PC机上已经不提供RS232接口了.但是由于RS232串口操作简单.通讯可靠,在工业领域中仍然有大量的应用.Qt以前的版本中,没有提供 ...

  4. QT串口助手(三):数据接收

    作者:zzssdd2 E-mail:zzssdd2@foxmail.com 一.前言 开发环境:Qt5.12.10 + MinGW 实现的功能 串口数据的接收 ascii字符形式显示与hex字符形式显 ...

  5. 【嵌入式开发】嵌入式 开发环境 (远程登录 | 文件共享 | NFS TFTP 服务器 | 串口连接 | Win8.1 + RedHat Enterprise 6.3 + Vmware11)

    作者 : 万境绝尘 博客地址 : http://blog.csdn.net/shulianghan/article/details/42254237 一. 相关工具下载 嵌入式开发工具包 : -- 下 ...

  6. linux下的qt串口通信

    1.linux下的qt串口通信跟windows唯一的差别就是端口号的名字,windows下面是COM,而linux是ttyUSB0的路径 2.一般情况下linux插上USB转串口线就可以在/dev/目 ...

  7. 【转】QT 串口QSerialPort + 解决接收数据不完整问题

    类:QSerialPort 例程:Examples\Qt-5.9.1\serialport\terminal,该例子完美展示了qt串口收发过程,直接在这上面修改就可以得到自己的串口软件.核心方法 // ...

  8. Linux 虚拟串口及 Qt 串口通信实例

    Linux 虚拟串口及 Qt 串口通信实例 2011-06-22 17:49 佚名 互联网 字号:T | T Linux 虚拟串口及 Qt 串口通信实例是本文所要介绍的内容,在实现过程中,打开了两个伪 ...

  9. 【记录】恢复win7与ARM开发板TQ2440的串口连接

    1.给板子上电. 2.接好物理上的串口连接,板子那端就是普通的RS232串口,电脑这端是USB转串口的线的USB这头,连到电脑上,然后在Win7系统下,先去看看,当前连接的USB虚拟出来的串口是哪个口 ...

随机推荐

  1. Docker 的优势

    下面我们主要从Docker对业务架构和生产实践的角度来分析. 随着业务规模的逐渐扩大,产品复杂度也随着增加,企业需要解决快速迭代.高可靠和高可用等问题,一个自然的选择是服务化的拆分,把一个单体架构拆分 ...

  2. Note: Transparent data deduplication in the cloud

    What Design and implement ClearBox which allows a storage service provider to transparently attest t ...

  3. 正经学C#_布尔运算[布尔值与其布尔运算符]:《c#入门经典》

    前面几个章节简述了 C#中得常用得算术运算符.这一章节说布尔值与其布尔运算符. 布尔值在c#中表示方式是 bool类型,这个类型可以储存两个值,true或者false,或者真或者假,可以说0或者1. ...

  4. UINavigationController + UIScrollView组合,视图尺寸的设置探秘(二)

    承接上文,我想把view布局修改为如下模式,让ScrollView长在NavigationBar的下方,这总不会有遮挡的问题了吧: story board内容如下,主要是右侧视图蓝色区域添加了Scro ...

  5. 多层mvc,thikphp进阶

    程序员,是一种生活态度. 我尽忠恪守,我努力进取,热衷于解决问题,希望得到同样的回报. 我遇到问题,将所有的力量用在解决问题,而不是抱怨,推卸责任上. ------------------------ ...

  6. 云端搭建Linux学习环境 链接https://edu.aliyun.com/article/19 (阿里云ECS服务器 )课堂

    云端搭建Linux学习环境 链接https://edu.aliyun.com/article/19 1. 开通云服务器 2 1.包年包月   按量付费(适合测试数据的时候) 2 2.地域   服务器数 ...

  7. jzoj4916. 【GDOI2017模拟12.9】完全背包问题 (背包+最短路)

    题面 题解 考场上蠢了--这么简单的东西都想不到-- 首先排序加去重. 先来考虑一下,形如 \[a_1x_1+a_2x_2+...a_nx_n=w,a_1<a_2<...<a_n,x ...

  8. Oracle中merge into语法

    merge into 语句就是insert和update的一个封装,简单来说就是: 有则更新,无则插入 下面说怎么使用 MERGE INTO table_Name  T1(匿名) using (另外一 ...

  9. 在SQLSERVER中创建聚集索引

    CREATE CLUSTERED INDEX CLUSTER_id ON TABLE_name(ID)------批量

  10. virturalenv 虚拟环境

    一.window系统 1.virtualenv的使用 2.pycharm使用 环境变量,path的作用:命令行中执行的命令,他们的路径,必须在path路径中,如果命令行找不到该命令,就是说path没写 ...