当前位置: 首页 > 编程日记 > 正文

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: Commvoid 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 属性 ==> 高级。

USB串口端口设置

USB串口的高级设置

将图中红色方框内的延迟计时器的值设为 1即可。
当把串口的延时计时器设为1ms时,实时检测串口的类在发送数据时就不会与下位机冲突。

总结

Qt 的串口类使用起来很方便,一般不需要用到阻塞式的方法 waitForReadyRead,它已经提供了异步非阻塞的信号 readyRead,因此实际上采用 信号+定时器 方式对串口进行实时监测也是有可能的。

为了方便,将适用于全双工模式的串口类和适用于半双工模式的串口类进行抽象,提取了抽象的串口操作基类,最开始认为好像多做了很多事情,后来发现对代码的整体结构和可扩展性都有很大的帮助。增加虚拟串口类用于模拟下位机发送数据时,只需要让虚拟串口类继承抽象通讯基类,实现基类中定义的纯虚方法,在工厂模式下添加虚拟串口类的例化即可。增加网络通讯类时,也可以方便的继承抽象通讯基类(用到的 QTcpSocket 也是继承自 QIODevice),并且不用修改现有代码。

另外值得一提的是 Qt 的多线程机制,虽然和大多数编程语言的多线程有所区别,但是使用起来非常方便,需要注意的是 QThread 的功能更像是一个线程管理句柄,有这个句柄才能对其它线程作出调度。

转载于:https://www.cnblogs.com/brifuture/p/9113091.html

相关文章:

navicat for mysql如何在更新记录时自动记录更新时间

如图所示 步骤 添加属性recordTime(任意)设置属性类型为timestamps勾选 根据当前时间戳更新默认栏填 CURRENT_TIMESTAMP 效果

ASP.NET Web API自身对CORS的支持:从实例开始

在《通过扩展让ASP.NET Web API支持W3C的CORS规范》中我们通过自定义的HttpMessageHandler为ASP.NET Web API赋予了跨域资源共享的能力&#xff0c;具体来讲&#xff0c;这个自定义的CorsMessageHandler的自由主要体现在如下两个方面&#xff1a;其一&#xff0c;为简单跨域请求…

2021第12届蓝桥杯省赛 -- 填空题:试题B:直线

试题B&#xff1a;直线 问题描述 在平面直角坐标系中&#xff0c;两点可以确定一条直线。如果有多点在一条直线上&#xff0c; 那么这些点中任意两点确定的直线是同一条。 给定平面上 2 3 个整点(x,y)∣0≤x<2,0≤y<3,x∈Z,y∈Z{(x, y)|0 \leq x < 2, 0 \leq y &l…

Markdown快速上手

基本语法 标题 #标题名共六级&#xff0c;依次“#”数量增加&#xff0c;字体减小 加粗文字 哈喽 两个“* ”文字内容 再两个 斜体文字 哈喽 一个“*” 文字内容 再一个 无序列表 -列表内容回车两次退出此编辑模式 有序列表 1.列表内容回车两次退出此编辑模式 插入链接 百度链接…

复杂SELECT语句执行过程

通过FROM子句中找到需要查询的表通过WHERE子句进行分组函数筛选判断通过GROUP BY子句完成分组操作通过HAVING子句完成组函数筛选判断通过SELECT子句选择显示的列或表达式及组函数通过ORDER BY子句进行排序操作 书写时按照这个顺序:5 1 2 3 4 6 出处&#xff1a;东软Java实训

MTD NANDFLASH驱动相关知识介绍

转&#xff1a;http://blog.csdn.net/zhouzhuan2008/article/details/11053877 目录 MTD总概述MTD数据结构 MTD相关层实现MTD&#xff0c;Memory Technology Device即内存技术设备字符设备和块设备的区别在于前者只能被顺序读写&#xff0c;后者可以随机访问&#xff1b;同时&a…

Spring Boot轻松理解动态注入,删除bean

原文地址&#xff1a;http://412887952-qq-com.iteye.com/blog/2348445 ​ 我们通过getBean来获得对象,但这些对象都是事先定义好的,我们有时候要在程序中动态的加入对象.因为如果采用配置文件或者注解&#xff0c;我们要加入对象的话,还要重启服务,如果我们想要避免这一情况就…

mysql数据库常见进阶使用

事务 1&#xff09;mysql中的工作单元&#xff0c;由一个或者多个sql语句组成&#xff0c;“不成功便成仁”&#xff0c;要么全部执行成功&#xff0c;要么全部执行失败&#xff0c;以此来保证数据的一致性。 2&#xff09;事务的回滚&#xff1a;如果事务中的任何一个sql执行失…

[转]web打印实现方案 Lodop6.034 使用方法总结

本文转自&#xff1a;https://www.cnblogs.com/tiger8000/archive/2011/09/19/2181365.html 官文下载&#xff1a; http://mtsoftware.v053.gokao.net/download.html 本地 Lodop6.034 版本下载&#xff1a;/Files/tiger8000/Lodop6.034.rar 假设你的 lodop 打印控件放在你项目的…

图片的另一种展现—将后台图片编码直接展现为图片

1、应用场景 开发过程中&#xff0c;遇到这样的需求&#xff1a;需要将服务器上的图片展现在页面上&#xff0c;但是图片所在服务器不是对外的&#xff0c;图片所在服务器与应用服务器也不在同一台机器上&#xff0c;这时候就需要在开发中先将图片读出来&#xff0c;返回给应用…

电子学会青少年编程等级考试Python一级题目解析12

Python一级题目解析 1、题目&#xff08;2021.03&#xff09; 写一个计算长方形面积的程序&#xff0c;并对每行代码进行相应的注释&#xff0c;要求如下&#xff1a; &#xff08;1&#xff09;采用多行注释&#xff0c;说明程序的功能&#xff08;如下&#xff09;&#x…

Swing基础知识(更新中)

Swing是什么 做桌面应用程序的界面&#xff0c;GUI。 组件和容器&#xff1a;容器是特殊的组件。 布局管理器&#xff1a; 一般放中间容器&#xff0c;用来控制容器中组件的排列方式。 常见&#xff1a; ① FlowLayout 流布局(默认布局) 左上是起点&#xff0c;按组件加入容…

timesten 修改最大连接数

修改完/var/Timesten/sys.odbc.ini里面的connections之后 重启TT&#xff1a;ttdaemonadmin -restart 报错&#xff1a;15019: Only the instance admin may alter the Connections attribute Command> Command> connect dsn的名称;15019: Only the instance admin may a…

青少年编程竞赛交流群第050次活动录播

背景介绍 把电子学会的青少年编程能力等级测评作为游戏的关卡&#xff0c;带着小朋友们升级打怪&#xff0c;这个想法来自于 我从邵慧宁身上得到的启发。 升级打怪&#xff1a; 电子学会考评中心&#xff1a;http://www.qceit.org.cn/bos/default.html 知识内容&#xff1a…

JDBC连接mysql数据的7个步骤(讲解+源码)

步骤 源码 DBUtils类 package com.csu.db;import java.sql.*;public class DBUtils {public static Connection getConnection(){try {//[1/7] 加载JDBC的驱动Class.forName("com.mysql.cj.jdbc.Driver");//[2/7] 定义url连接参数String url "jdbc:mysql://l…

UML:概要设计,用什么画我的类图?

背景 做过需求之后&#xff0c;很少使用 UML 画概要设计&#xff0c;这几天尝试的用了几个工具&#xff0c;最总还是选择了 VisualStudio。 Edraw 详细信息很难编辑&#xff0c;如&#xff1a;签名。 Viso 添加成员太麻烦了。 VisualStudio 图形不支持着色。 备注 使用 VisualS…

RN Exception: Before building your project, you need to accept the license agreements and comp le...

异常 * What went wrong: A problem occurred configuring project :app. > You have not accepted the license agreements of the following SDK components: [Android SDK Platform 23, Android SDK Build-Tools 23.0.1]. Before building your project, you need to acc…

【组队学习】【35期】李宏毅机器学习(含深度学习)

李宏毅机器学习&#xff08;含深度学习&#xff09; 航路开辟者&#xff1a;王茂霖、陈安东&#xff0c;刘峥嵘&#xff0c;李玲领航员&#xff1a;梁家晖航海士&#xff1a;程浩伟、周小要、吴昌广 基本信息 开源内容&#xff1a;https://linklearner.com/datawhale-homepa…

git关键原理简介

集中化版本控制 缺点&#xff1a;1中央仓库得相当稳定&#xff0c;出问题可能每个人那里都没完整备份 2 只能在线使用&#xff08;今天网络已经不是问题&#xff09; 分布式版本控制 改进&#xff1a;每台客户机在本地都维护一份仓库 主要特点 保存数据和文件的主要方式 每个…

项目管理和缺陷跟踪工具Redmine

官网&#xff1a; http://www.redmine.org/ http://demo.redmine.org/ 下载&#xff1a; http://www.redmine.org/projects/redmine/wiki/Download Redmine 是一个开源的、基于Web的项目管理和缺陷跟踪工具。它用日历和甘特图辅助项目及进度可视化显示。同时它又支持多项目管理…

Redis (二)_ jedis的使用

Jedis 是 Redis 官方首选的 Java 客户端开发包 虚拟机设置 查看虚拟机的ipifconfig 将虚拟机的6379端口打开#运行下面的命令 如果是新建的一个新的 文件&#xff0c;你需要先安装 iptables&#xff0c;再打开 vim /etc/sysconfig/iptables## 安装命令 yum install -y iptables-…

【组队学习】【35期】数据可视化(Matplotlib)

数据可视化&#xff08;Matplotlib&#xff09; 航路开辟者&#xff1a;杨剑砺、杨煜、耿远昊、李运佳、居凤霞领航员&#xff1a;武帅航海士&#xff1a;叶庭云、李显、郭棉昇 基本信息 开源内容&#xff1a;https://github.com/datawhalechina/fantastic-matplotlib开源内…

解决日常bug的正确姿势

bug的错误分两种情况&#xff1a; ① 控制台报错->可以定位到错误位置(比较想看到的) ② 控制台不报错却达不到想要的功能->设计代码的逻辑有问题 问题解决方案的优先级(由高到低) ① 自己解决 a. 分析控制台的错误信息描述->定位问题的代码行数->分析该行代码前后…

02-NLP-01-python正则表达式

Python正则表达式 by 寒小阳(hanxiaoyang.mlgmail.com) 正则表达式是处理字符串的强大工具&#xff0c;拥有独特的语法和独立的处理引擎。 我们在大文本中匹配字符串时&#xff0c;有些情况用str自带的函数(比如find, in)可能可以完成&#xff0c;有些情况会稍稍复杂一些(比如说…

【组队学习】【35期】SQL编程语言

SQL编程语言 航路开辟者&#xff1a;王复振、杨煜、闫钟峰、杨梦迪、苏鹏领航员&#xff1a;庞永生航海士&#xff1a;王彦淳、木卷、丁一超 基本信息 开源内容&#xff1a;https://github.com/datawhalechina/wonderful-sql开源内容&#xff1a;https://hub.fastgit.org/da…

神经网络基础知识梳理

神经网络是什么 说明&#xff1a; 我们在机器学习中谈论的神经网络是指“神经网络学习”&#xff0c;即机器学习与神经网络这两个学科领域的交叉部分。 生物学意义上神经网络的最基本成分是神经元&#xff0c;计算机科学中的神经网络的最基本成分是神经元模型。 最广泛的一种…

Xcode 5 Error CertUIFramework.axbundle

Xcode 5 新建项目&#xff0c;运行总是出现错误&#xff1a; Cannot find executable forCFBundle0x8a7c7a0</Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/AccessibilityBundle…

【组队学习】【35期】深入浅出Pytorch

深入浅出Pytorch 航路开辟者&#xff1a;李嘉骐、牛志康、刘洋、陈安东领航员&#xff1a;朱松青航海士&#xff1a;管柯琴、宋泽山、林旭升 基本信息 开源内容&#xff1a;https://github.com/datawhalechina/thorough-pytorch内容属性&#xff1a;深度学习&#xff08;实践…

环形动画加载视图AnimatedCircleLoadingView​​​​​​​

2019独角兽企业重金招聘Python工程师标准>>> 环形动画加载视图AnimatedCircleLoadingView AnimatedCircleLoadingView是基于Android手表动画android-watch-loading-animation衍生出来的动画加载视图。该视图采用循环的环形动画显示加载进度。同时&#xff0c;开发者…