通讯模块Communicator

通讯模块是整个项目设计中位于最底层的模块,用于处理与串口或网络等设备的通讯,所有设备的通讯通过CommManager类完成,上层软件设计时需要根据comm模块(主要是CommManager)提供的接口访问设备。通讯组件实现类的重要类是 QIODevice,所有通讯过程中发送/接收的字符都经过QIODevice发送到设备中。

通讯模块的测试和API 文档 基本完成,生成文档可使用 qdoc 工具,将代码中的注释转换成网页格式的文档,文档图如下:

Communicator层中提供了Comm 命名空间简化使用该库的过程,要使用该库文件,可以参考下列方式:

1.         通讯设备:串口或网络

磁罗盘通过USB-RS485转接线连接到PC上位机,从而实现的上下位机通讯,由于半双工的RS485无法同时收发数据,若在下位机发送数据时,上位机向下位机发送指令,将会导致RS485上电平异常,通讯失败,上位机接收数据时出现乱码(由于下位机程序设计的原因,不能同时收发数据,所以此时下位机一般不会接收数据)。为了找到下位机发送语句的间隔而找到合适的发送指令时机,通讯代码使用多线程+阻塞式的方法监听串口,在通讯接口空闲时上位机才发送数据(实际上RS485是通过FT232r芯片转接到USB上连接电脑,所以其实是通过USB端口监听RS485串口)。

若设备的连接方式支持全双工模式,可以设置串口的工作模式为全双工,全双工模式和半双工的模式区别在于:全双工在接收到管理器的查询指令后将会立即将指令写入到设备中;半双工则会监听设备,在线路空闲时发送指令。

为实现远程操控下位机,通讯设备中新增加了网络通讯模式。网络通讯模式与串口通讯模式差别不大,主要差别在于网络通讯要指定端口,而串口通讯要指定波特率停止位等。

2.         控制器CommManager

项目中串口控制器的类为CommManager,该类的作用有:

  • 聚合协议和串口;
  • 处理来自协议的查询请求;
  • 根据协议的部分参数进行请求;
  • 接收来自串口的数据并分发给各个协议;
  • 对串口/网络子线程进行控制。

关于CommManager的更多具体用法,可以参考API文档。

上位机串口通信若采用信号/槽类型的异步接收数据方式在主线程中接收并处理数据,不需要让通讯类在一个独立的子线程中运行,但半双工模式下需要通过阻塞方式实时监听串口,因此需要通过一个串口线程控制器来操作子线程的运行和停止。

接收、发送数据及字符串拼接的工作在子线程中执行。

图 1 CommManager的部分接口

CommManager的主要目的是隐藏具体的多线程实现,统一了编程接口,使得其它类可以直接调用控制器中相应的方法,而不需要通过连接多线程对象中的信号与槽(即信号槽的方式)调用通讯器的方法。因为Qt的多线程机制,在控制器中采用了反射或信号等方式,通过CommManager隐藏多线程的实现,简化其它部分的代码。

注:Qt的Thread多线程机制与其它编程语言的多线程稍有不同,不同之处在于QThread更确切的来说应该是一个线程控制器而非一个具体的线程。而将QObject子类对象通过moveToThread方法放置到子线程中后,若直接调用该对象的foo方法,则foo方法将会在主线程中执行,为了避免这种情况的发生,需要使用Qt的信号槽机制,利用信号来调用foo槽函数,使槽函数在所在的子线程中执行,即不会阻塞UI线程。

串口通讯需要有相应的协议来实现,通过CommManager对象的addProtocol接口添加协议,CommManager将会自动绑定信号和槽。接收到来自下位机的数据(以行为单位)时,调用接口AbstractProtocol实现类的processData回调函数对接收到的数据进行处理。接收到来自协议的请求时,从协议队列中选取一个协议并取出命令,将命令的内容发送到下位机。

CommManager计算指令的执行时间(从上位机Qt程序中发送到串口至下位机响应的时间),根据AbstractProtocol实现类的 cmdExecuteTime值决定命令的重发次数。管理器在接收到协议发送的信号后,会优先查询高优先级协议,并获取高优先级的AbstractProtocol类中的待查讯指令进行查询。默认情况下,最先注册到CommManager中的协议有最高的优先级,当该协议中的指令全部查询结束后才会查询次优先级协议中的指令。

3.         通讯接口及部分实现类

项目中所有的串口/网络通讯设备都继承自AbstractComm接口并实现AbstractComm中定义的纯虚方法。AbstractComm定义了与CommManager之间的接口。

图 2 AbstractCom 的部分接口

若要调用 AbstractCom 的实现类中的方法,应当采用信号槽机制或这QMetaObject::invokeMethod 方法来调用,这样才可以保证AbstractCom实现类中的槽函数全部运行在子线程中。对于AbstractCom实现类中的标志位和查询指令可以直接通过函数调用来修改(需要加锁保证线程安全)。

ComFullDuplex 通讯器实现类为工作于全双工模式下的串口,通过连接 QSerialPort 实例的 readyRead 信号和自身的onRead槽函数对串口发送的信息进行处理。使用 readyRead 信号进行响应,异步执行数据处理,尽管这个类的方法可以在主线程中执行,但为了统一控制器和串口之间的接口,仍将其放在子线程中执行。在这个实现类中上位机不会控制发送指令的时间(认为该类是工作于全双工模式,上位机和下位机可以同时发送信息,不会导致信道电平异常产生乱码),而是在接收到来自用户的操作后立即向下位机发送指令,但若在RS485串口中错误使用该类,将可能导致在半双工模式下的RS485总线中产生乱码无法正常收到回复。若单片机使用RS485半双工连接上位机时,建议不要选择该类发送串口数据(若上位机不需要发送指令,则不会导致乱码)。而单片机使用RS232全双工连接时可以使用该模式。

4. 代码及文档

文档:https://brifuture.github.io/qt_components/basic_communicator/docs/

代码:https://github.com/BriFuture/qt_components/tree/master/basic_communicator

自定义Qt组件-通讯模块(P1)的更多相关文章

  1. 自定义Qt组件-通讯模块(P3)

    1.   半双工模式实时检测串口 ComHalfDuplex类是为了解决上位机发送控制指令和下位机发送数据会在半双工RS485总线中产生冲突引起乱码而引入的(v0.010版本引入). 解决冲突的原理主 ...

  2. 自定义Qt组件-通讯模块(P2)

    1.  抽象协议AbstractProtocol 抽象协议AbstractProtocol定义CommManager与协议之间的接口.AbstractProtocol中的一些属性(如enabled)用 ...

  3. C/C++ Qt TableDelegate 自定义代理组件

    TableDelegate 自定义代理组件的主要作用是对原有表格进行调整,例如默认情况下Table中的缺省代理就是一个编辑框,我们只能够在编辑框内输入数据,而有时我们想选择数据而不是输入,此时就需要重 ...

  4. SSIS自定义数据流组件开发(血路)

    由于特殊的原因(怎么特殊不解释),需要开发自定义数据流组件处理. 查了很多资料,用了不同的版本,发现各种各样的问题没有找到最终的解决方案. 遇到的问题如下: 用VS2015编译出来的插件,在SSDTB ...

  5. Android Studio开发基础之自定义View组件

    一般情况下,不直接使用View和ViewGroup类,而是使用使用其子类.例如要显示一张图片可以用View类的子类ImageView,开发自定义View组件可分为两个主要步骤: 一.创建一个继承自an ...

  6. [UE4]自定义MovementComponent组件

    自定义Movement组件 目的:实现自定义轨迹如抛物线,线性,定点等运动方式,作为组件控制绑定对象的运动. 基类:UMovementComponent 过程: 1.创建UCustomMovement ...

  7. Qt组件中的双缓冲无闪烁绘图

      双缓冲绘图在Qt4中,所有的窗口部件默认都使用双缓冲进行绘图.使用双缓冲,可以减轻绘制的闪烁感.在有些情况下,用户要关闭双缓冲,自己管理绘图.下面的语句设置了窗口部件的Qt::WA_PaintOn ...

  8. 【转】Android学习基础自定义Checkbox组件

    原文网址:http://forum.maiziedu.com/thread-515-1-1.html heckbox组件是一种可同时选中多项的基础控件,即复选框,在android学习中,Checkbo ...

  9. 自定义Qt按钮

    转自:http://blog.csdn.net/starcloud_zxt/article/details/5185556 Qt自带的PushButton样式比较单一,在开发的时候往往按钮的形状各异, ...

随机推荐

  1. Chrom去掉"未选择任何文件"

    <style> input[type="file"] { color: transparent; } </style>

  2. D. 代码填空:LIS

    LIS是最长上升子序列.什么是最长上升子序列? 就是给你一个序列,请你在其中求出一段最长严格上升的部分,它不一定要连续. 就像这样:22, 33, 44, 77 和 22, 33, 44, 66 就是 ...

  3. 洛谷 P3586 [POI2015]LOG

    P3586 [POI2015]LOG 题目描述 维护一个长度为n的序列,一开始都是0,支持以下两种操作:1.U k a 将序列中第k个数修改为a.2.Z c s 在这个序列上,每次选出c个正数,并将它 ...

  4. java插入图片到数据库(可以批量)

    package sundun.zfpt.gg.web; import java.io.File; import java.io.FileInputStream; import java.sql.Con ...

  5. db2联邦数据库

    目标机器:192.168.0.16 本地机器:192.168.0.18 .登陆本地数据库 db2 connect to dwmm user dainst using dainst ## 打开联邦数据库 ...

  6. P3943 星空 区间异或差分

    \(\color{#0066ff}{ 题目描述 }\) 逃不掉的那一天还是来了,小 F 看着夜空发呆. 天上空荡荡的,没有一颗星星--大概是因为天上吹不散的乌云吧. 心里吹不散的乌云,就让它在那里吧, ...

  7. 树链剖分【洛谷P2590】 [ZJOI2008]树的统计

    P2590 [ZJOI2008]树的统计 题目描述 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w. 我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把 ...

  8. IDEA 一些 莫名其妙的错误....解决办法...

    1.  如果 一直 update indices......... windows : 删除 c 盘 用户目录下 .IntelliJIdea2017.3/system/caches  目录 ..... ...

  9. django 访问静态资源

    urlpatterns = patterns('', url(r'^$', views.show, name='index'), url(r'^static/(?P<path>.*)', ...

  10. 什么是Uboot

    U-Boot的全称是Universal Boot Loader,遵循GPL条款的开放源码项目. U-Boot的作用是系统引导. U-Boot目前不仅仅支持嵌入式Linux系统的引导(对Linux的支持 ...