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. redis配置文件常用配置介绍

    博客内容首发地址 参数说明 redis.conf 配置项说明如下: Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程 daemonize no 当Redis以守护进 ...

  2. Windows Services windows服务如何删除服务

    如何删除服务 一.为何要删除服务: 1.当服务文件丢失时,在服务里还会显示. 2.现在好多都会有服务,你看那个不顺眼就可以把它干掉.前提是不用的软件. 二.使用: 使用sc.exe 这个是window ...

  3. Data Base oracle简单使用及管理工具使用

    oracle简单使用及管理工具使用 一.常用工具: 1.sqldeveloper 2.navicat for oracle 3.PLSQL Developer 4.toad

  4. duddo在xml里面出现红叉的解决方法

    原因是没有加入dubbo.xsd window-Preferences-输入xml-xmltacalog....... 配置离线约束:http://code.alibabatech.com/schem ...

  5. P3749 [六省联考2017]寿司餐厅 最小割

    \(\color{#0066ff}{ 题目描述 }\) Kiana 最近喜欢到一家非常美味的寿司餐厅用餐. 每天晚上,这家餐厅都会按顺序提供 \(n\) 种寿司,第 \(i\) 种寿司有一个代号 \( ...

  6. 状压DP【洛谷P1879】 [USACO06NOV]玉米田Corn Fields

    P1879 [USACO06NOV]玉米田Corn Fields 农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形 ...

  7. Codeforces Round #521 (Div. 3) C. Good Array

    C. Good Array time limit per test 1 second memory limit per test 256 megabytes input standard input ...

  8. 查看详细linux系统信息的命令和方法

    查看内存大小: cat /proc/meminfo |grep MemTotaluname -a # 查看内核/操作系统/CPU信息的linux系统信息命令head -n 1 /etc/issue # ...

  9. react 的安装和案列Todolist

    react 的安装和案列Todolist 1.react的安装和环境的配置 首先检查有没有安装node.js和npm node -v npm -v 查看相关版本 2.安装脚手架工具 2.构建:crea ...

  10. JAVA Font类

    java.awt.Font 设计字体显示效果 Font mf = new Font(String 字体,int 风格,int 字号); 字体:TimesRoman, Courier, Arial等 风 ...