1. 半双工模式实时检测串口
ComHalfDuplex类是为了解决上位机发送控制指令和下位机发送数据会在半双工RS485总线中产生冲突引起乱码而引入的(v0.010版本引入)。
解决冲突的原理主要是实时检测串口,若一段时间内下位机不发送数据,则认为此时串口是空闲的,可以向下位机发送数据。
若在等待过程中接收到下位机发送数的据,则重置超时定时器。实时检测串口采用阻塞式方法waitForReadyRead等待串口接收到数据,
顾名思义,该方法其实就是等到readyRead信号到来时停止阻塞并继续运行代码,理论上和ComFullDuplex的实际做法是一样的,而如果要ComFullDuplex类实现实时检测串口,需要添加一个读取超时定时器并对超时做一定处理(未做处理)。
1.1. CommForWin (deprecated)
CommForWin类是利用Windows的C API进行串口的读取操作。主要原理及任务和ComHalfDuplex类似,用的同样是阻塞式方法读取串口,实现实时检测串口。
需要注意的是,程序运行时只会用到这三个类中的某一个类,其中CommForWin不推荐使用:
1、许多继承下来的方法只是一个空实现,需要用Windows API来实现代码;
2、这部分代码只能在Windows使用,不如使用Qt提供的API;
3. 需要将校验位、波特率等对应到Qt的校验位和波特率上,否则将会导致串口无法正确收发数据。
串口是线程独占的,无法在其它线程或进程中打开同一个串口(若跨线程调用方法将会出现警告)。
AbstractComm的这两个实现类ComHalfDuplex和CommForWin是为了解决无法实时检测串口的问题。因为串口收发数据时有16ms的间隔,所以半双工模式无法正常工作,关于FTR232的半双工工作模式问题可参考我的另一篇文章:https://www.cnblogs.com/brifuture/p/9113091.html
2. 控制器与设备的交互
每个协议中都应该维护一个待查讯的指令队列,若当前指令尚未执行完毕(成功执行或超时都视为执行完毕),后续需要查询的指令需要排队。当一个指令执行完毕时,CommManager会通知该命令所属的协议,取出该协议的指令队列中的下一条指令,并执行该指令。在当前协议中的所有命令都处理完毕后,再从其它的协议中选择。每当有指令的入队和出队操作时,CommManager发出cmdCountChanged信号,通知其它部件当前队列的长度有变化。
队首指令从队列中弹出时,将进入到子线程中等待查询,为了避免多线程竞争访问资源,在设置指令时需要进行加锁操作,保证线程安全。同样的,当指令成功执行后,对指令进行清除(防止不必要的重复查询)也需要进行加锁操作,操作完成后释放锁。
控制器在主线程(UI线程)中执行,串口操作在子线程中执行,控制器的主要工作是将来自串口的信号转发给其它部件:
图 Com中信号和槽的连接
对串口操作类的调用并没有用到信号和槽,而是直接使用了QMetaObject::invokeMethod方法进行调用,原理和信号槽机制是类似的,但不需要特意声明一个信号了,使用起来比较方便。
3. 网络接口
网络接口CommNetwork实现了AbstractComm 的接口,作为网络通讯的实现类,其与串口通讯的代码大同小异。内部实际负责通讯的对象为QTcpSocket,与QSerialPort类似(两者都是QIODevice的子类,所以接口大部分也相同)。
4. CommandObject与DataObject
CommandObject和DataObject是在CommManager和AbstractProtocol之间传递信息时用到的对象。使用CommandObject可以获取命令的内容和其它的信息,使用DataObject可以获取下位机发送的数据和其它信息。
比起直接在CommManager和AbstractProtocol之间传递QByteArray来说,使用这两种数据对象可以方便程序扩展,以后需要增加传递其它信息时添加这两种数据对象的接口即可。
5. 总结
最开始编写communicator库的目的是为了让已有的程序分层,communicator作为最底层负责处理的都是QByteArray字节,后来对代码进行优化之后程序的逻辑更加清楚,而且communicator层的代码基本上不需要改动,直接就将其编译成静态库文件,减少项目重新构建时所需的编译时间。
将communicator作为静态库构建的过程中也出现不少问题,之前并没有构建过静态库/动态库,对这方面不太了解。在实际项目中遇到过这样的问题:
General库(构建为动态库)引用了Communicator库,并在general库中调用Comm命令空间的初始化方法,应用程序引用general动态库和Communicator静态库,运行Demo程序时,发现经常出现内存访问错误的问题,经过调试后发现general库中虽然对Communicator进行了初始化,但demo应用程序中Communicator却没有初始化,Comm::manager 指针始终为空指针。
在程序中对Communicator库进行初始化操作后仍然不能解决问题,原因可能在于动态库和应用程序之间的全局变量不能共享。最后把general库改为静态链接库就可以解决这个问题。
另外关于Qt的应用程序构建还有很多值得学习的东西,在构建Communicator这个库时尝试使用.pri文件,它可以简化库文件的引用,
不过它更多的用处应该是可以把一个大的项目分成若干个小部分,方便项目的构建和测试。例如我在Communicator项目中添加一个Communicator.pri文件。
Communicator 的代码仓库:https://github.com/BriFuture/qt_components/tree/master/basic_communicator