基于Qt/libmodbus框架开发modbusRTU主机模块(一)- 插件开发
创始人
2025-06-01 00:02:03

基于Qt/libmodbus框架开发modbusRTU主机模块(一)- 插件开发

日期作者版本说明
2020.11.09Dog TaoV0.0开始撰写文档。
2020.11.14Dog TaoV0.1完成了文档的主体内容。
2020.11.20Dog TaoV0.21. 增加了GUI窗体与Modbus主机数据同步的例程。
2. 修复了WriteCR(), WriteHR()方法没有将IsExecuted 置1的bug。
2021.01.12Dog TaoV1.01. 修改了数据类型封装,增加了IsExecuted_P指针类型成员,解决了标志位IsExecuted在读写完成时不能被正确置1的问题。
2. 增加了GetPluginInfo的接口,可以获取当前插件的名称、编译时间、版本号、作者、单位等信息。
3. 增加了该插件当前运行线程ID号的debug输出,以便于监控测试。
4. 增加了GUI窗体与MB数据同步的独立模式开发说明。
5. 增加了MB数据同步操作的一些注意事项。
2021.02.02Dog TaoV1.1在“数据读写同步”小节中增加了“超时处理”与“重试操作”内容。
2021.02.14Dog TaoV1.2在“数据读写同步”小节中增加了“封装判断方法”与“调用判断方法”的内容。其核心实现原理是采用了C++的类模板std::function封装Lambda表达式并作为函数的参数传入。
2021.03.03Dog TaoV1.3在“使用指南”章节“GUI窗体与Modbus主机数据同步 - 耦合模式”中增加读写函数再封装 注意事项,用以强调这些函数传值的方式必须为指针或者为引用。
2021.03.14Dog TaoV2.0 ★1. 为六个寄存器读写缓冲队列增加了线程锁,修复了频繁读写寄存器操作可能造成插件崩溃的漏洞。
2. 大幅度优化了轮询逻辑,极大的提高了轮询效率(寄存器的读写队列为空时自动跳过当前等待时间)。
3. 增加了关于Modbus Master运行控制的API,例如连接/断开端口、设置轮询间隔时间、设置读写间隔时间等。
4. 升级了数据封装,增加了PreDevAddr,MBDevAddr两个成员,用以指示当前变量归属的从机与前置通讯单元。
5. 支持多从机数据轮询,通过设置MB变量的前置通讯单元地址与从机地址,可以将数据请求送至不同的通讯网络与从机。
6. 开放了借助已连接的modbus端口,发送任意数据的接口(需要修改libmodbus源码)。
7. 在主函数中增加了Modbus Master的功能测试例程(可将插件编译为可执行程序)。
2021.03.18Dog TaoV2.11. 增加了获取读写队列数量和清空读写队列缓存的接口。
2. 改进了主机轮询过程中的延时性能。
3. 将Modbus RTU主机的应用开发部分独立成篇。
2021.03.19Dog TaoV2.2修复了由于变量没有正确初始化而导致SetMbMultiSlaveState默认开启,进而造成了与低版本的兼容性问题。
2021.03.20Dog TaoV2.31. 增加了设置modbus响应超时的接口,以适应不同通讯速率的场景。
2. 修复了由于串口名参数未设置缓存而造成modbus经常连接不成功的bug。
2021.03.24Dog TaoV2.4 ★1. 增加了设置与获取读写寄存器队列最大容量的接口。一旦队列超出最大容量,最早的请求数据将被自动舍弃。
2. 增加了读写寄存器操作错误计数与清空计数的接口。通过读写寄存器操作错误计数可以了解到当前通讯的稳定性,监控系统工作状态。
3. 完善了读写寄存器操作错误的信号反馈机制,四种寄存器的读与两种寄存器的写出错时都有信号发射出来。
4. 完善了主机通讯的读写失败重试功能(MB数据封装增加了RetryTimes成员),通过设置合适的重试次数与重试延时,系统可以在通讯出错的时候尝试重新读写。
5. 采用回调函数注册操作方法的方式,对寄存器的读写操作进行了封装,简化了代码、优化了逻辑结构。
2021.03.29Dog TaoV2.5修复了一些情况下没有为IsExecuted_P设置正确的指向而造成IsExecuted误判的bug。
2021.04.02Dog TaoV2.61. 通讯出错时,Debug会输出重试次数的信息。
2. 通讯出错时,Debug与对应的信号会输出寄存器的详细信息。
2021.04.06Dog TaoV2.71. 修复了判断操作寄存器失败的bug。
2. 修复了写寄存器操作时,传入MBData_Bit/MBData_Float类型数据无法使用重试功能的bug。
2023.03.21Dog TaoV2.8将文档从notion平台转移到markdown。

文章目录

  • 基于Qt/libmodbus框架开发modbusRTU主机模块(一)- 插件开发
  • libmodbus库介绍
    • 官方介绍
    • 移植简介
  • 设计思路
    • 数据封装
      • float/uint16_t [2]转换
      • 封装modbus中的浮点数
      • 封装modbus中的开关量
    • 线程锁保护核心数据
    • 寄存器操作封装
    • 流程设计
    • 控制参数说明
    • 模块插件化
  • 核心源码
    • 工程文件
    • 接口文件
    • 头文件 - "modbusrtumaster.h"
    • 源文件
      • modbusmaster.cpp
      • modbusrtumaster.performance.cpp
      • modbusmaster.polling.cpp
      • modbusmaster.read.cpp
      • modbusmaster.write.cpp
    • libmodbus库源码修改
      • modbus.c新增内容
      • modbus.h新增内容
  • 使用指南
    • 基本操作
      • 实例化主机对象
      • 设置通讯端口参数
      • 连接通讯端口
      • 设置多从机与前置通讯状态
      • 设置轮询间隔时间、响应超时时间、重试间隔时间
      • 启动主机服务
      • 读寄存器
      • 写寄存器
      • 关闭主机服务

libmodbus库介绍

官方介绍

libmodbus

  • 官网上有源码、文档、例程。

ibmodbus is a free software library to send/receive data according to the Modbus protocol. This library is written in C and supports RTU (serial) and TCP (Ethernet) communications. The license of libmodbus is LGPL v2.1+ and the licence of programs in the tests directory is BSD 3-clause.

移植简介

设计思路

数据封装

设计数据封装时,主要考虑了一下几点:

  • modbus变量分为16位寄存器变量与开关量(1 bit)两种;
  • modbus变量可以逐个读写也可以连续读写;
  • modbus变量对应着(标准Modbus协议规定的)唯一的地址;
  • 不同的modbus变量可以拥有不同的从机设备地址与前置网络通讯地址(多从机扩展);

考虑到一般通用性,本模块采用使用最多的float类型数据对16位寄存器进行封装。由于modbus主机的硬件资源一般都足够,因此直接使用字节(uint8_t)数据表示开关量(在资源紧张的环境中,应当考虑使用位域/联合体对开关量类型数据进行封装)。

需要特别注意的是MBData_BitArrayMBData_FloatArray数据类型中的uint8_t *IsExecuted_P成员。设置这个成员的原因是:写MB寄存器的函数重载功能可导致传入参数是MBData_BitMBData_Float类型,而在函数内部都是需要转换为MBData_BitArrayMBData_FloatArray类型,才能加入写队列。因此为了使得IsExecuted标志量依然可用,需要额外的指针类型成员存储数据类型转换前的相应变量的指针。这种机制的内部处理方式举例如下:

void ModbusRTUMaster::RequestWriteHR(uint16_t address, uint16_t count, float *dataArray)
{mutex_WQ_HR.lock();MBData_FloatArray *mBData_FloatArray = new MBData_FloatArray(address,count);for(uint16_t i = 0; i < count; i++){mBData_FloatArray->ValueArray[i].Type_Float = dataArray[i];}mBData_FloatArray->IsExecuted = 0;WriteQueue_HoldingReg->append(mBData_FloatArray);mutex_WQ_HR.unlock();
}void ModbusRTUMaster::RequestWriteHR(uint16_t address, float data)
{RequestWriteHR(address,1,&data);
}void ModbusRTUMaster::RequestWriteHR(MBData_FloatArray *mBData_FloatArray)
{mutex_WQ_HR.lock();mBData_FloatArray->IsExecuted = 0;//mBData_FloatArray->IsExecuted指向mBData_FloatArray->IsExecuted_PmBData_FloatArray->IsExecuted_P = &mBData_FloatArray->IsExecuted;WriteQueue_HoldingReg->append(mBData_FloatArray);mutex_WQ_HR.unlock();
}void ModbusRTUMaster::RequestWriteHR(MBData_Float *mBData_Float)
{mutex_WQ_HR.lock();mBData_Float->IsExecuted = 0;MBData_FloatArray *mBData_FloatArray = new MBData_FloatArray(mBData_Float->PreDevAddr, mBData_Float->MBDevAddr, mBData_Float->Address, 1);mBData_FloatArray->ValueArray[0].Type_Float = mBData_Float->Value.Type_Float;//将mBData_Float->IsExecuted指向mBData_FloatArray->IsExecuted_PmBData_FloatArray->IsExecuted_P = &mBData_Float->IsExecuted;WriteQueue_HoldingReg->append(mBData_FloatArray);mutex_WQ_HR.unlock();
}

float/uint16_t [2]转换

浮点数与16位寄存器(2个)需要实现高效转化,使用联合体对这种类型的数据进行封装。

///构造了一个联合体,以实现float类型数据与16位数组的快速转换
typedef union
{float Type_Float;uint16_t Type_16BitArray[2];
} Float16BitArray_Union;

例如声明一个Float16BitArray_Union类型的变量MB_FloatValue,调用MB_FloatValue.Type_Float成员即可得到float类型数据,调用MB_FloatValue.Type_16BitArray成员即可得到一个16位的数组(长度为2)。

封装modbus中的浮点数

由于存在读/写单个或者多个寄存器的操作,因此需要定义两种数据类型以应对不同的需要。这些结构体都设置了几种不同参数的构造函数,因方便初始化此类型的变量。需要特别关注为了实现多从机或者多网络通讯而设置的uint16_t PreDevAddruint8_t MBDevAddr成员。V2.4版本以后,新增了RetryTimes成员,它用以指示通讯出错时的重试次数。RetryTimes = 1表示通讯出错后不重试,RetryTimes = 2表示首次通讯出错后,再尝试一次通讯。

///定义了方便在modbus协议中使用的float类型变量
typedef struct _MBData_Float
{uint8_t IsExecuted;             //该变量是否已经被执行读写操作uint16_t PreDevAddr;            //Modbus硬件通讯介质地址(例如使用无线透传模块,其本身可能也会有一个地址);uint8_t MBDevAddr;              //Modbus从机设备地址,即Modbus协议规定的协议;uint16_t Address;               //变量的地址,16位地址Float16BitArray_Union Value;    //用以存储请求到的数据uint8_t RetryTimes;             //重试次数,如果请求读写数据失败,则重试_MBData_Float(uint16_t preDevAddr, uint8_t mBDevAddr, uint16_t address, float value, uint8_t retryTimes = 1):IsExecuted(0), PreDevAddr(preDevAddr), MBDevAddr(mBDevAddr), Address(address), RetryTimes(retryTimes){Value.Type_Float = value;};_MBData_Float(uint16_t address, float value):IsExecuted(0), PreDevAddr(0), MBDevAddr(0x01), Address(address), RetryTimes(1){Value.Type_Float = value;};_MBData_Float():IsExecuted(0), PreDevAddr(0), MBDevAddr(0x01), Address(0), RetryTimes(1){Value.Type_Float = 0;};
} MBData_Float;///定义了方便在modbus协议中使用的float类型数组(连续地址)
typedef struct _MBData_FloatArray
{uint8_t IsExecuted;////// \brief IsExecuted_P///     写寄存器提供了函数重载的功能,可以支持4种类型的参数传入,但是最终加入到写队列的数据类型,都统一转化为MBData_FloatArray/MBData_BitArray类型,///     因此为了能够使得不同数据类型的MB寄存器数据的IsExecuted成员的值都能够被顺利修改,需要一个指针分别指向不同数据类型的IsExecuted成员。uint8_t *IsExecuted_P;uint16_t PreDevAddr;            //Modbus硬件通讯介质地址(例如使用无线透传模块,其本身可能也会有一个地址);uint8_t MBDevAddr;              //Modbus从机设备地址,即Modbus协议规定的协议;uint16_t Address;               //变量的地址,16位地址uint16_t Count;Float16BitArray_Union ValueArray[MODBUS_WRITE_HR_FLOAT_MAX_NUM];uint8_t RetryTimes;             //重试次数,如果请求读写数据失败,则重试_MBData_FloatArray(uint16_t preDevAddr, uint8_t mBDevAddr, uint16_t address, uint16_t count, uint8_t retryTimes = 1):IsExecuted(0), IsExecuted_P(&IsExecuted), PreDevAddr(preDevAddr), MBDevAddr(mBDevAddr), Address(address), Count(count), RetryTimes(retryTimes){memset(ValueArray,0,sizeof (ValueArray));};_MBData_FloatArray(uint16_t address, uint16_t count):IsExecuted(0), IsExecuted_P(&IsExecuted), PreDevAddr(0), MBDevAddr(0x01), Address(address), Count(count), RetryTimes(1){memset(ValueArray,0,sizeof (ValueArray));};_MBData_FloatArray():IsExecuted(0), IsExecuted_P(&IsExecuted), PreDevAddr(0), MBDevAddr(0x01), Address(0), Count(0), RetryTimes(1){memset(ValueArray,0,sizeof (ValueArray));};
} MBData_FloatArray;

封装modbus中的开关量

由于存在读/写单个或者多个寄存器的操作,因此需要定义两种数据类型以应对不同的需要。这些结构体都设置了几种不同参数的构造函数,因方便初始化此类型的变量。需要特别关注为了实现多从机或者多网络通讯而设置的uint16_t PreDevAddruint8_t MBDevAddr成员。V2.4版本以后,新增了RetryTimes成员,它用以指示通讯出错时的重试次数。RetryTimes = 1表示通讯出错后不重试,RetryTimes = 2表示首次通讯出错后,再尝试一次通讯。

///定义了方便在modbus协议中使用的uint8_t (bool)类型变量
typedef struct _MBData_Bit
{uint8_t IsExecuted;uint16_t PreDevAddr;      //Modbus硬件通讯介质地址(例如使用无线透传模块,其本身可能也会有一个地址);uint8_t MBDevAddr;        //Modbus从机设备地址,即Modbus协议规定的协议;uint16_t Address;         //变量的地址,16位地址uint8_t Value;uint8_t RetryTimes;       //重试次数,如果请求读写数据失败,则重试_MBData_Bit(uint16_t preDevAddr, uint8_t mBDevAddr, uint16_t address, uint8_t value, uint8_t retryTimes = 1):IsExecuted(0), PreDevAddr(preDevAddr), MBDevAddr(mBDevAddr), Address(address), Value(value), RetryTimes(retryTimes){};_MBData_Bit(uint16_t address, uint8_t value):IsExecuted(0), PreDevAddr(0), MBDevAddr(0x01), Address(address), Value(value), RetryTimes(1){};_MBData_Bit():IsExecuted(0), PreDevAddr(0), MBDevAddr(0x01), Address(0), Value(0), RetryTimes(1){};
} MBData_Bit;///定义了方便在modbus协议中使用的uint8_t (bool)类型数组(连续地址)
typedef struct _MBData_BitArray
{uint8_t IsExecuted;////// \brief IsExecuted_P///     写寄存器提供了函数重载的功能,可以支持4种类型的参数传入,但是最终加入到写队列的数据类型,都统一转化为MBData_FloatArray/MBData_BitArray类型,///     因此为了能够使得不同数据类型的MB寄存器数据的IsExecuted成员的值都能够被顺利修改,需要一个指针分别指向不同数据类型的IsExecuted成员。uint8_t *IsExecuted_P;uint16_t PreDevAddr;      //Modbus硬件通讯介质地址(例如使用无线透传模块,其本身可能也会有一个地址);uint8_t MBDevAddr;        //Modbus从机设备地址,即Modbus协议规定的协议;uint16_t Address;         //变量的地址,16位地址uint16_t Count;uint8_t ValueArray[MODBUS_WRITE_CR_MAX_NUM];uint8_t RetryTimes;             //重试次数,如果请求读写数据失败,则重试_MBData_BitArray(uint16_t preDevAddr, uint8_t mBDevAddr, uint16_t address, uint16_t count, uint8_t retryTimes = 1):IsExecuted(0), IsExecuted_P(&IsExecuted), PreDevAddr(preDevAddr), MBDevAddr(mBDevAddr), Address(address), Count(count), RetryTimes(retryTimes){memset(ValueArray,0,sizeof (ValueArray));};_MBData_BitArray(uint16_t address, uint16_t count):IsExecuted(0), IsExecuted_P(&IsExecuted), PreDevAddr(0), MBDevAddr(0x01), Address(address), Count(count), RetryTimes(1){memset(ValueArray,0,sizeof (ValueArray));};_MBData_BitArray():IsExecuted(0), IsExecuted_P(&IsExecuted), PreDevAddr(0), MBDevAddr(0x01), Address(0), Count(0), RetryTimes(1){memset(ValueArray,0,sizeof (ValueArray));};
} MBData_BitArray;

线程锁保护核心数据

考虑到Modbus RTU主机的轮询是在一个新的线程中运行的,因此需要设置线程锁以保护核心队列数据不被其他线程意外的修改。以保持寄存器的读操作为例:

//定义线程锁,用以保护读写队列List
QMutex mutex_RQ_HR;
QMutex mutex_WQ_HR;//读HR入队操作需要加锁(此方法一般是在其他线程调用)
void ModbusRTUMaster::RequestReadHR(MBData_FloatArray *mBData_FloatArray_P)
{mutex_RQ_HR.lock();mBData_FloatArray_P->IsExecuted = 0;ReadQueue_HoldingReg->append(mBData_FloatArray_P);mutex_RQ_HR.unlock();
}//读HR出队操作需要加锁(此方法一般在轮询线程中调用)
int ModbusRTUMaster::ReadHR()
{int result = 0;mutex_RQ_HR.lock();if(!ReadQueue_HoldingReg->isEmpty()){result = OperateModbusRegisters(ReadQueue_HoldingReg->takeFirst(),modbus_read_registers);}mutex_RQ_HR.unlock();return result;
}

寄存器操作封装

modbus四种寄存器总共涉及六种读写操作,通过抽象了寄存器操作函数,并为每一种特定的读、写操作传入相应的处理方法即可大幅度减少ReadIR(), ReadHR(), ReadDR(), ReadCR(), WriteHR(), WriteCR()函数的代码重复,并使得逻辑更加清晰,维护升级更加方便。注意一下四个函数重载,仅仅是handler函数指针的类型不同。

int ModbusRTUMaster::OperateModbusRegisters(MBData_FloatArray *mBData_FloatArray,int (*handler)(modbus_t *ctx, int addr, int nb, uint16_t *dest))
{int result = 0;for(int i = 0; i < mBData_FloatArray->RetryTimes; i++){//处理多从机切换ProcessMultiSlaveSwitch(mBData_FloatArray->MBDevAddr);//处理前置通讯地址ProcessPreCommAddr(mBData_FloatArray->PreDevAddr);result = handler(mb,mBData_FloatArray->Address, mBData_FloatArray->Count*2, mBData_FloatArray->ValueArray[0].Type_16BitArray);//如果读写成功,则退出循环if(result != -1){break;}//如果读写失败,则延时后重试else{msleep(RetryDelayTime);}}mBData_FloatArray->IsExecuted = 1;return result;
}int ModbusRTUMaster::OperateModbusRegisters(MBData_FloatArray *mBData_FloatArray,int (*handler)(modbus_t *ctx, int addr, int nb, const uint16_t *dest))
{int result = 0;for(int i = 0; i < mBData_FloatArray->RetryTimes; i++){//处理多从机切换ProcessMultiSlaveSwitch(mBData_FloatArray->MBDevAddr);//处理前置通讯地址ProcessPreCommAddr(mBData_FloatArray->PreDevAddr);result = handler(mb,mBData_FloatArray->Address, mBData_FloatArray->Count*2, mBData_FloatArray->ValueArray[0].Type_16BitArray);//如果读写成功,则退出循环if(result != -1){break;}//如果读写失败,则延时后重试else{msleep(RetryDelayTime);}}mBData_FloatArray->IsExecuted = 1;return result;
}int ModbusRTUMaster::OperateModbusRegisters(MBData_BitArray *mBData_BitArray,int (*handler)(modbus_t *ctx, int addr, int nb, uint8_t *dest))
{int result = 0;for(int i = 0; i < mBData_BitArray->RetryTimes; i++){//处理多从机切换ProcessMultiSlaveSwitch(mBData_BitArray->MBDevAddr);//处理前置通讯地址ProcessPreCommAddr(mBData_BitArray->PreDevAddr);result = handler(mb, mBData_BitArray->Address, mBData_BitArray->Count, mBData_BitArray->ValueArray);//如果读写成功,则退出循环if(result != -1){break;}//如果读写失败,则延时后重试else{msleep(RetryDelayTime);}}mBData_BitArray->IsExecuted = 1;return result;
}int ModbusRTUMaster::OperateModbusRegisters(MBData_BitArray *mBData_BitArray,int (*handler)(modbus_t *ctx, int addr, int nb, const uint8_t *dest))
{int result = 0;for(int i = 0; i < mBData_BitArray->RetryTimes; i++){//处理多从机切换ProcessMultiSlaveSwitch(mBData_BitArray->MBDevAddr);//处理前置通讯地址ProcessPreCommAddr(mBData_BitArray->PreDevAddr);result = handler(mb, mBData_BitArray->Address, mBData_BitArray->Count, mBData_BitArray->ValueArray);//如果读写成功,则退出循环if(result != -1){break;}//如果读写失败,则延时后重试else{msleep(RetryDelayTime);}}mBData_BitArray->IsExecuted = 1;return result;
}

流程设计

%E5%9F%BA%E4%BA%8EQt%20libmodbus%E6%A1%86%E6%9E%B6%E5%BC%80%E5%8F%91modbusRTU%E4%B8%BB%E6%9C%BA%E6%A8%A1%E5%9D%97%EF%BC%88%E4%B8%80%EF%BC%89-%20%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%2068d4f0df54984679add328d6163761fc/Untitled.png

控制参数说明

mb_parameters

模块插件化

可将此模块按照Qt Plugin的标准开发,以动态链接库的形式提供给外部调用,具体操作请参考《Qt Plugin插件开发指南》。本文涉及的核心源码都是严格按照插件开发的方式给出。

核心源码

工程文件

QT -= guiCONFIG += c++11 console
CONFIG -= app_bundle# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0TARGET = EWhalesModbusRTUMaster# TEMPLATE = lib 生成插件
# TEMPLATE = app 生成应用程序
TEMPLATE = lib
CONFIG  += pluginSOURCES += \main.cpp \modbusrtumaster.cpp \modbusrtumaster.performance.cpp \modbusrtumaster.polling.cpp \modbusrtumaster.read.cpp \modbusrtumaster.write.cppmacx {
INCLUDEPATH += $$PWD/libmodbus-3.1.6/include/modbus
LIBS += -L$$PWD/libmodbus-3.1.6/lib -lmodbusDISTFILES += \libmodbus-3.1.6/lib/libmodbus.5.dylib \libmodbus-3.1.6/lib/libmodbus.dylib \libmodbus-3.1.6/lib/pkgconfig/libmodbus.pc#ICON = Icon.icns
}win32 {INCLUDEPATH += $$PWD/libmodbus-3.1.6-windows/incSOURCES += \libmodbus-3.1.6-windows/src/modbus.c \libmodbus-3.1.6-windows/src/modbus-data.c \libmodbus-3.1.6-windows/src/modbus-rtu.c \libmodbus-3.1.6-windows/src/modbus-tcp.cLIBS += -Llibmodbus-3.1.6-windows/dll -lws2_32DISTFILES += libmodbus-3.1.6-windows/dll/ws2_32.dll#RC_ICONS = whales.ico# 指定生成目录CONFIG(debug,debug|release){DESTDIR = $$PWD/../../Plugins/}else{DESTDIR = $$PWD/../../Plugins/}InterfaceFile = $$PWD/imodbusrtumaster.h#将LibFile中的"/"替换为"\"InterfaceFile = $$replace(InterfaceFile, /, \\)#输出目录也是一样,要将"/"替换为"\"OutLibFile = $$PWD/../../Plugins/OutLibFile = $$replace(OutLibFile, /, \\)system("copy $$InterfaceFile $$OutLibFile")
}# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += targetHEADERS += \imodbusrtumaster.h \modbusrtumaster.h

接口文件

#ifndef IMODBUSRTUMASTER_H
#define IMODBUSRTUMASTER_H#ifndef ISERIALPORT_H
#define ISERIALPORT_H
#include 
#include 
#include /*
宏定义 接口IID,用来唯一标记该接口类。实际开发中,IID的名称为了避免重复,推荐采用本例所示的方式命名
*/
#define QTPLUGIN_IMODBUSRTUMASTER_IID       "ewhales.plugin.interface.modbusrtumaster"#define MODBUS_WRITE_HR_FLOAT_MAX_NUM       50                                       //最大连续写浮点数数量
#define MODBUS_WRITE_HR_MAX_NUM             (MODBUS_WRITE_HR_FLOAT_MAX_NUM*2)        //最大连续写保持寄存器数量
#define MODBUS_WRITE_CR_MAX_NUM             100                                      //最大连续写线圈寄存器数量//防止重复定义PluginInfo数据类型
#ifndef PLUGININFO_TYPEDEF
#define PLUGININFO_TYPEDEF
typedef struct
{char name[50];char date[50];char version[50];char author[50];char company[50];
} PluginInfo;
#endif///构造了一个联合体,以实现float类型数据与16位数组的快速转换
typedef union
{float Type_Float;uint16_t Type_16BitArray[2];
} Float16BitArray_Union;///定义了方便在modbus协议中使用的float类型变量
typedef struct _MBData_Float
{uint8_t IsExecuted;             //该变量是否已经被执行读写操作uint16_t PreDevAddr;            //Modbus硬件通讯介质地址(例如使用无线透传模块,其本身可能也会有一个地址);uint8_t MBDevAddr;              //Modbus从机设备地址,即Modbus协议规定的协议;uint16_t Address;               //变量的地址,16位地址Float16BitArray_Union Value;    //用以存储请求到的数据uint8_t RetryTimes;             //重试次数,如果请求读写数据失败,则重试_MBData_Float(uint16_t preDevAddr, uint8_t mBDevAddr, uint16_t address, float value, uint8_t retryTimes = 1):IsExecuted(0), PreDevAddr(preDevAddr), MBDevAddr(mBDevAddr), Address(address), RetryTimes(retryTimes){Value.Type_Float = value;};_MBData_Float(uint16_t address, float value):IsExecuted(0), PreDevAddr(0), MBDevAddr(0x01), Address(address), RetryTimes(1){Value.Type_Float = value;};_MBData_Float():IsExecuted(0), PreDevAddr(0), MBDevAddr(0x01), Address(0), RetryTimes(1){Value.Type_Float = 0;};
} MBData_Float;///定义了方便在modbus协议中使用的float类型数组(连续地址)
typedef struct _MBData_FloatArray
{uint8_t IsExecuted;////// \brief IsExecuted_P///     写寄存器提供了函数重载的功能,可以支持4种类型的参数传入,但是最终加入到写队列的数据类型,都统一转化为MBData_FloatArray/MBData_BitArray类型,///     因此为了能够使得不同数据类型的MB寄存器数据的IsExecuted成员的值都能够被顺利修改,需要一个指针分别指向不同数据类型的IsExecuted成员。uint8_t *IsExecuted_P;uint16_t PreDevAddr;            //Modbus硬件通讯介质地址(例如使用无线透传模块,其本身可能也会有一个地址);uint8_t MBDevAddr;              //Modbus从机设备地址,即Modbus协议规定的协议;uint16_t Address;               //变量的地址,16位地址uint16_t Count;Float16BitArray_Union ValueArray[MODBUS_WRITE_HR_FLOAT_MAX_NUM];uint8_t RetryTimes;             //重试次数,如果请求读写数据失败,则重试_MBData_FloatArray(uint16_t preDevAddr, uint8_t mBDevAddr, uint16_t address, uint16_t count, uint8_t retryTimes = 1):IsExecuted(0), IsExecuted_P(&IsExecuted), PreDevAddr(preDevAddr), MBDevAddr(mBDevAddr), Address(address), Count(count), RetryTimes(retryTimes){memset(ValueArray,0,sizeof (ValueArray));};_MBData_FloatArray(uint16_t address, uint16_t count):IsExecuted(0), IsExecuted_P(&IsExecuted), PreDevAddr(0), MBDevAddr(0x01), Address(address), Count(count), RetryTimes(1){memset(ValueArray,0,sizeof (ValueArray));};_MBData_FloatArray():IsExecuted(0), IsExecuted_P(&IsExecuted), PreDevAddr(0), MBDevAddr(0x01), Address(0), Count(0), RetryTimes(1){memset(ValueArray,0,sizeof (ValueArray));};
} MBData_FloatArray;///定义了方便在modbus协议中使用的uint8_t (bool)类型变量
typedef struct _MBData_Bit
{uint8_t IsExecuted;uint16_t PreDevAddr;      //Modbus硬件通讯介质地址(例如使用无线透传模块,其本身可能也会有一个地址);uint8_t MBDevAddr;        //Modbus从机设备地址,即Modbus协议规定的协议;uint16_t Address;         //变量的地址,16位地址uint8_t Value;uint8_t RetryTimes;             //重试次数,如果请求读写数据失败,则重试_MBData_Bit(uint16_t preDevAddr, uint8_t mBDevAddr, uint16_t address, uint8_t value, uint8_t retryTimes = 1):IsExecuted(0), PreDevAddr(preDevAddr), MBDevAddr(mBDevAddr), Address(address), Value(value), RetryTimes(retryTimes){};_MBData_Bit(uint16_t address, uint8_t value):IsExecuted(0), PreDevAddr(0), MBDevAddr(0x01), Address(address), Value(value), RetryTimes(1){};_MBData_Bit():IsExecuted(0), PreDevAddr(0), MBDevAddr(0x01), Address(0), Value(0), RetryTimes(1){};
} MBData_Bit;///定义了方便在modbus协议中使用的uint8_t (bool)类型数组(连续地址)
typedef struct _MBData_BitArray
{uint8_t IsExecuted;////// \brief IsExecuted_P///     写寄存器提供了函数重载的功能,可以支持4种类型的参数传入,但是最终加入到写队列的数据类型,都统一转化为MBData_FloatArray/MBData_BitArray类型,///     因此为了能够使得不同数据类型的MB寄存器数据的IsExecuted成员的值都能够被顺利修改,需要一个指针分别指向不同数据类型的IsExecuted成员。uint8_t *IsExecuted_P;uint16_t PreDevAddr;      //Modbus硬件通讯介质地址(例如使用无线透传模块,其本身可能也会有一个地址);uint8_t MBDevAddr;        //Modbus从机设备地址,即Modbus协议规定的协议;uint16_t Address;         //变量的地址,16位地址uint16_t Count;uint8_t ValueArray[MODBUS_WRITE_CR_MAX_NUM];uint8_t RetryTimes;             //重试次数,如果请求读写数据失败,则重试_MBData_BitArray(uint16_t preDevAddr, uint8_t mBDevAddr, uint16_t address, uint16_t count, uint8_t retryTimes = 1):IsExecuted(0), IsExecuted_P(&IsExecuted), PreDevAddr(preDevAddr), MBDevAddr(mBDevAddr), Address(address), Count(count), RetryTimes(retryTimes){memset(ValueArray,0,sizeof (ValueArray));};_MBData_BitArray(uint16_t address, uint16_t count):IsExecuted(0), IsExecuted_P(&IsExecuted), PreDevAddr(0), MBDevAddr(0x01), Address(address), Count(count), RetryTimes(1){memset(ValueArray,0,sizeof (ValueArray));};_MBData_BitArray():IsExecuted(0), IsExecuted_P(&IsExecuted), PreDevAddr(0), MBDevAddr(0x01), Address(0), Count(0), RetryTimes(1){memset(ValueArray,0,sizeof (ValueArray));};
} MBData_BitArray;/*
接口需要定义成纯虚函数
*/
class IModbusRTUMaster
{
public:virtual PluginInfo GetPluginInfo()=0;virtual void SetPortParam(const char *portName, int baudRate, char parity, int dataBit, int stopBit)=0;virtual void SetSlaveAddr(uint8_t slaveAddr)=0;virtual int GetMasterState()=0;/*** @brief SetMasterPollingInterval*      Interval of polling of different registers.*      e.g. IR_Poll -> interval -> DR_Poll -> interval -> HR_Poll -> interval -> CR_Poll -> ...* @param ms interval time, unit is ms.*/virtual void SetMasterPollingInterval(uint16_t ms)=0;/*** @brief SetMasterRegRWInterval*      Interval of read and write registers.*      e.g. In HR_Poll, ReadHR -> interval -> WriteHR* @param ms interval time, unit is ms.*/virtual void SetMasterRegRWInterval(uint16_t ms)=0;/*** @brief SetResponseTimeout*      The full confirmation response must be received before expiration of the response timeout.* @param ms timeout of the response (millisecond).*/virtual void SetResponseTimeout(uint16_t ms)=0;/*** @brief InitModbus Try to initialize of the modbus*     1. Creat new  modbus_rtu instance.*     2. Set parameters of the modbus, such as slave address.*     3. Connect the modbus port.* @return result of initialization of the modbus*     @arg 0: Initialization successfully.*     @arg -1: Create modbus content failed.*     @arg -2: Set modbus slave address failed.*     @arg -3: Open modbus port failed.*/virtual int InitModbus()=0;/*** @brief CloseModbus*     Close and free modbus.*/virtual void CloseModbus()=0;/*** @brief StartMaster*     Start modbus master poll to handle write/read request.*/virtual void StartMaster()=0;/*** @brief StopMaster*     Stop modbus master poll.*/virtual void StopMaster()=0;//注意,此接口的重载不支持前置地址与从机地址设定,在后面的版本中可能废弃,应当尽量避免使用。virtual void RequestWriteHR(uint16_t address, uint16_t count, float *dataArray)=0;virtual void RequestWriteHR(MBData_FloatArray *mBData_FloatArray)=0;//注意,此接口的重载不支持前置地址与从机地址设定,在后面的版本中可能废弃,应当尽量避免使用。virtual void RequestWriteHR(uint16_t address, float data)=0;virtual void RequestWriteHR(MBData_Float *mBData_Float)=0;//注意,此接口的重载不支持前置地址与从机地址设定,在后面的版本中可能废弃,应当尽量避免使用。virtual void RequestWriteCR(uint16_t address, uint16_t count, uint8_t* dataArray)=0;virtual void RequestWriteCR(MBData_BitArray *mBData_BitArray)=0;//注意,此接口的重载不支持前置地址与从机地址设定,在后面的版本中可能废弃,应当尽量避免使用。virtual void RequestWriteCR(uint16_t address, uint8_t data)=0;virtual void RequestWriteCR(MBData_Bit *mBData_Bit)=0;virtual void RequestReadIR(MBData_FloatArray *mBData_FloatArray_P)=0;virtual void RequestReadHR(MBData_FloatArray *mBData_FloatArray_P)=0;virtual void RequestReadDR(MBData_BitArray *mBData_BitArray_P)=0;virtual void RequestReadCR(MBData_BitArray *mBData_BitArray_P)=0;//开放发送任意数据的接口,但不由于没有限制与保护机制,因此应尽量避免调用virtual void WriteFreeMsg(uint8_t *msg, int msg_length)=0;//设置是否使能前置通讯地址功能(考虑到ModbusRTU协议可能使用一些通讯模块,它们本身也存在一个地址)virtual void SetPreCommAddrState(uint8_t state)=0;virtual void SetPreCommAddrWidth(uint8_t width)=0;//设置是否使能多modbus从机的功能virtual void SetMbMultiSlaveState(uint8_t state)=0;//获取读写队列缓冲数据量,可以用它们表示当前负载,并据此调整读写轮询速率virtual int Get_WriteCR_QueueCount() = 0;virtual int Get_WriteHR_QueueCount() = 0;virtual int Get_ReadCR_QueueCount() = 0;virtual int Get_ReadHR_QueueCount() = 0;virtual int Get_ReadDR_QueueCount() = 0;virtual int Get_ReadIR_QueueCount() = 0;//设置读写队列最大缓冲数量(超出最大缓冲数量后,将移除最早的数据)virtual void Set_WriteCR_QueueMaxCount(uint32_t count) = 0;virtual void Set_WriteHR_QueueMaxCount(uint32_t count) = 0;virtual void Set_ReadCR_QueueMaxCount(uint32_t count) = 0;virtual void Set_ReadHR_QueueMaxCount(uint32_t count) = 0;virtual void Set_ReadDR_QueueMaxCount(uint32_t count) = 0;virtual void Set_ReadIR_QueueMaxCount(uint32_t count) = 0;//清空读写队列缓冲数据virtual void Clear_WriteCR_Queue() = 0;virtual void Clear_WriteHR_Queue() = 0;virtual void Clear_ReadCR_Queue() = 0;virtual void Clear_ReadHR_Queue() = 0;virtual void Clear_ReadDR_Queue() = 0;virtual void Clear_ReadIR_Queue() = 0;//获取读写队列通讯错误计数virtual uint32_t Get_WriteCR_Queue_ErrCount() = 0;virtual uint32_t Get_WriteHR_Queue_ErrCount() = 0;virtual uint32_t Get_ReadCR_Queue_ErrCount() = 0;virtual uint32_t Get_ReadHR_Queue_ErrCount() = 0;virtual uint32_t Get_ReadDR_Queue_ErrCount() = 0;virtual uint32_t Get_ReadIR_Queue_ErrCount() = 0;//清空读写队列通讯错误计数virtual void Clear_WriteCR_Queue_ErrCount() = 0;virtual void Clear_WriteHR_Queue_ErrCount() = 0;virtual void Clear_ReadCR_Queue_ErrCount() = 0;virtual void Clear_ReadHR_Queue_ErrCount() = 0;virtual void Clear_ReadDR_Queue_ErrCount() = 0;virtual void Clear_ReadIR_Queue_ErrCount() = 0;//设置重试延时。当数据读写失败时,延时delay毫秒后重发数据请求virtual void Set_RetryDelayTime(uint16_t delay) = 0;///Override as signals.//注意,此信号的效果等同于Get_IRData_Failed信号,在后面的版本中可能废弃,应当尽量避免使用。virtual void lostConnection(QString errorInfo) = 0;//Read modbus registers failedvirtual void Get_IRData_Failed(QString errorInfo) = 0;virtual void Get_HRData_Failed(QString errorInfo) = 0;virtual void Get_DRData_Failed(QString errorInfo) = 0;virtual void Get_CRData_Failed(QString errorInfo) = 0;//Write modbus registers failedvirtual void Set_HRData_Failed(QString errorInfo) = 0;virtual void Set_CRData_Failed(QString errorInfo) = 0;
};/*
为了能够在运行时查询插件是否实现给定的接口,我们必须使用宏Q_DECLARE_INTERFACE(),该宏的第一参数为接口类的名称,第二个参数是一个字符串,用于唯一标记该接口类。
*/
Q_DECLARE_INTERFACE (IModbusRTUMaster, QTPLUGIN_IMODBUSRTUMASTER_IID)#endif // EW_SERIALPORT_INTERFACE#endif // IMODBUSRTUMASTER_H

头文件 - “modbusrtumaster.h”

#ifndef MODBUSRTUMASTER_H
#define MODBUSRTUMASTER_H#include 
#include 
#include 
#include #include "imodbusrtumaster.h"
#include "modbus.h"#define IMODBUSRTUMASTER_NAME     "IModbusRTUMaster"
#define IMODBUSRTUMASTER_VERSION  "V2.5.1Beta"
#define IMODBUSRTUMASTER_AUTHOR   "Tao Wang"
#define IMODBUSRTUMASTER_COMPANY  "Alwhales Tech."class ModbusRTUMaster : public QThread, public IModbusRTUMaster
{Q_OBJECT/*使用Q_INTERFACES声明:类支持ISerialPort*/Q_INTERFACES(IModbusRTUMaster)/*Qt4与Qt5的插件开发方式略有差异,此处采用条件编译可以实现版本兼容*/#if QT_VERSION >= 0x050000Q_PLUGIN_METADATA(IID QTPLUGIN_IMODBUSRTUMASTER_IID)#endifpublic:ModbusRTUMaster();~ModbusRTUMaster();modbus_t *mb = nullptr;void ProcessPreCommAddr(uint16_t preAddr);void ProcessMultiSlaveSwitch(uint8_t slaveAddr);PluginInfo GetPluginInfo() Q_DECL_OVERRIDE;void SetPortParam(const char *portName, int baudRate, char parity, int dataBit, int stopBit) Q_DECL_OVERRIDE;void SetSlaveAddr(uint8_t slaveAddr) Q_DECL_OVERRIDE;int GetMasterState() Q_DECL_OVERRIDE;void SetMasterPollingInterval(uint16_t ms) Q_DECL_OVERRIDE;void SetMasterRegRWInterval(uint16_t ms) Q_DECL_OVERRIDE;void SetResponseTimeout(uint16_t ms) Q_DECL_OVERRIDE;int InitModbus() Q_DECL_OVERRIDE;void CloseModbus() Q_DECL_OVERRIDE;void StartMaster() Q_DECL_OVERRIDE;void StopMaster() Q_DECL_OVERRIDE;void RequestWriteHR(uint16_t address, uint16_t count, float *dataArray) Q_DECL_OVERRIDE;void RequestWriteHR(MBData_FloatArray *mBData_FloatArray) Q_DECL_OVERRIDE;void RequestWriteHR(uint16_t address, float data) Q_DECL_OVERRIDE;void RequestWriteHR(MBData_Float *mBData_Float) Q_DECL_OVERRIDE;void RequestWriteCR(uint16_t address, uint16_t count, uint8_t* dataArray) Q_DECL_OVERRIDE;void RequestWriteCR(MBData_BitArray *mBData_BitArray) Q_DECL_OVERRIDE;void RequestWriteCR(uint16_t address, uint8_t data) Q_DECL_OVERRIDE;void RequestWriteCR(MBData_Bit *mBData_Bit) Q_DECL_OVERRIDE;void RequestReadIR(MBData_FloatArray *mBData_FloatArray_P) Q_DECL_OVERRIDE;void RequestReadHR(MBData_FloatArray *mBData_FloatArray_P) Q_DECL_OVERRIDE;void RequestReadDR(MBData_BitArray *mBData_BitArray_P) Q_DECL_OVERRIDE;void RequestReadCR(MBData_BitArray *mBData_BitArray_P) Q_DECL_OVERRIDE;//开放发送任意数据的接口void WriteFreeMsg(uint8_t *msg, int msg_length) Q_DECL_OVERRIDE;//设置是否使能前置通讯地址功能(考虑到ModbusRTU协议可能使用一些通讯模块,它们本身也存在一个地址)void SetPreCommAddrState(uint8_t state) Q_DECL_OVERRIDE;void SetPreCommAddrWidth(uint8_t width) Q_DECL_OVERRIDE;//设置是否使能多modbus从机的功能以及切换从机的状态void SetMbMultiSlaveState(uint8_t state) Q_DECL_OVERRIDE;//获取读写队列缓冲数据量,可以用它们表示当前负载,并据此调整读写轮询速率int Get_WriteCR_QueueCount() Q_DECL_OVERRIDE;int Get_WriteHR_QueueCount() Q_DECL_OVERRIDE;int Get_ReadCR_QueueCount() Q_DECL_OVERRIDE;int Get_ReadHR_QueueCount() Q_DECL_OVERRIDE;int Get_ReadDR_QueueCount() Q_DECL_OVERRIDE;int Get_ReadIR_QueueCount() Q_DECL_OVERRIDE;void Set_WriteCR_QueueMaxCount(uint32_t count) Q_DECL_OVERRIDE;void Set_WriteHR_QueueMaxCount(uint32_t count) Q_DECL_OVERRIDE;void Set_ReadCR_QueueMaxCount(uint32_t count) Q_DECL_OVERRIDE;void Set_ReadHR_QueueMaxCount(uint32_t count) Q_DECL_OVERRIDE;void Set_ReadDR_QueueMaxCount(uint32_t count) Q_DECL_OVERRIDE;void Set_ReadIR_QueueMaxCount(uint32_t count) Q_DECL_OVERRIDE;//清空读写队列缓冲数据void Clear_WriteCR_Queue() Q_DECL_OVERRIDE;void Clear_WriteHR_Queue() Q_DECL_OVERRIDE;void Clear_ReadCR_Queue() Q_DECL_OVERRIDE;void Clear_ReadHR_Queue() Q_DECL_OVERRIDE;void Clear_ReadDR_Queue() Q_DECL_OVERRIDE;void Clear_ReadIR_Queue() Q_DECL_OVERRIDE;//获取读写队列通讯错误计数uint32_t Get_WriteCR_Queue_ErrCount() Q_DECL_OVERRIDE;uint32_t Get_WriteHR_Queue_ErrCount() Q_DECL_OVERRIDE;uint32_t Get_ReadCR_Queue_ErrCount() Q_DECL_OVERRIDE;uint32_t Get_ReadHR_Queue_ErrCount() Q_DECL_OVERRIDE;uint32_t Get_ReadDR_Queue_ErrCount() Q_DECL_OVERRIDE;uint32_t Get_ReadIR_Queue_ErrCount() Q_DECL_OVERRIDE;//清空读写队列通讯错误计数void Clear_WriteCR_Queue_ErrCount() Q_DECL_OVERRIDE;void Clear_WriteHR_Queue_ErrCount() Q_DECL_OVERRIDE;void Clear_ReadCR_Queue_ErrCount() Q_DECL_OVERRIDE;void Clear_ReadHR_Queue_ErrCount() Q_DECL_OVERRIDE;void Clear_ReadDR_Queue_ErrCount() Q_DECL_OVERRIDE;void Clear_ReadIR_Queue_ErrCount() Q_DECL_OVERRIDE;//设置重试延时。当数据读写失败时,延时delay毫秒后重发数据请求void Set_RetryDelayTime(uint16_t delay) Q_DECL_OVERRIDE;//操作modbus寄存器的抽象函数,通过传入对应的寄存器读写方法操作不同的寄存器int OperateModbusRegisters(MBData_FloatArray *mBData_FloatArray, int (*handler)(modbus_t *, int, int, uint16_t *));int OperateModbusRegisters(MBData_BitArray *mBData_BitArray, int (*handler)(modbus_t *, int, int, uint8_t *));static void FloatToBit16Array(float *floatArray, uint16_t *bitArray, uint16_t floatNum);static void Bit16ArrayToFloat(uint16_t *bit16Array, float *floatArray, uint16_t floatNum);int OperateModbusRegisters(MBData_FloatArray *mBData_FloatArray, int (*handler)(modbus_t *, int, int, const uint16_t *));int OperateModbusRegisters(MBData_BitArray *mBData_BitArray, int (*handler)(modbus_t *, int, int, const uint8_t *));
protected:private://线程锁,用以加锁核心的队列listQMutex mutex_WQ_HR;QMutex mutex_WQ_CR;QMutex mutex_RQ_HR;QMutex mutex_RQ_CR;QMutex mutex_RQ_IR;QMutex mutex_RQ_DR;//    modbus_t *mb;QMutex mutex_RunCtrl;int runControl = 0;int isModbusRun = 0;char _portName[50];                 //存储Port name的字节数组,注意不能使用指针,因为传入的临时变量可能会被释放int _baudRate = 115200;char _parity = 'N';                 //'N', 'E', 'O'int _dataBit = 8;                   //7, 8int _stopBit = 1;                   //1, 2uint8_t mbSlaveAddr = 0x01;         //Modbus协议中的从机地址, default value = 0x01uint16_t preCommAddr = 0x0000;      //前置从机地址(目标通讯模块地址), default value = 0x0000uint8_t preCommAddr_size = 2;       //前置从机地址位数(8位/16位), default value = 2uint8_t preCommAddr_enable = 0;     //是否使能发送前置从机地址, default value = 0uint8_t multiMbSlave_enable = 0;    //是否使能多modbus从机通讯机制, default value = 0uint16_t pollingInterval = 50;      //interval of polling each kinds of registersuint16_t regRWInterval = 50;        //interval of read and write registersQList *WriteQueue_HoldingReg;QList *WriteQueue_CoilsReg;QList *ReadQueue_HoldingReg;QList *ReadQueue_CoilsReg;QList *ReadQueue_InputReg;QList *ReadQueue_DiscreteReg;uint32_t ErrorCount_WriteCR_Queue = 0;uint32_t ErrorCount_WriteHR_Queue = 0;uint32_t ErrorCount_ReadCR_Queue = 0;uint32_t ErrorCount_ReadHR_Queue = 0;uint32_t ErrorCount_ReadDR_Queue = 0;uint32_t ErrorCount_ReadIR_Queue = 0;uint32_t MaxCount_WriteCR_Queue = 100;uint32_t MaxCount_WriteHR_Queue = 100;uint32_t MaxCount_ReadCR_Queue = 100;uint32_t MaxCount_ReadHR_Queue = 100;uint32_t MaxCount_ReadDR_Queue = 100;uint32_t MaxCount_ReadIR_Queue = 100;uint16_t RetryDelayTime = 10;           //重试延时时间,默认10 msvoid run() Q_DECL_OVERRIDE;void stop();void Polling();int IR_Poll();int HR_Poll();int DR_Poll();int CR_Poll();int ReadIR();int ReadHR();int ReadDR();int ReadCR();int WriteHR();int WriteCR();void ParseIR();void ParseHR();void ParseDR();void ParseCR();//    void ProcessPreCommAddr(uint16_t preAddr);
//    void ProcessMultiSlaveSwitch(uint8_t slaveAddr);signals:void lostConnection(QString errorInfo) Q_DECL_OVERRIDE;void Get_IRData_Failed(QString errorInfo) Q_DECL_OVERRIDE;void Get_HRData_Failed(QString errorInfo) Q_DECL_OVERRIDE;void Get_DRData_Failed(QString errorInfo) Q_DECL_OVERRIDE;void Get_CRData_Failed(QString errorInfo) Q_DECL_OVERRIDE;void Set_HRData_Failed(QString errorInfo) Q_DECL_OVERRIDE;void Set_CRData_Failed(QString errorInfo) Q_DECL_OVERRIDE;};#endif // MODBUSRTUMASTER_H

源文件

按照功能将源文件拆分为5个源文件,分别为***”modbusmaster.cpp“***, “modbusrtumaster.performance.cpp“, ”modbusrtumaster.polling.cpp“, ”modbusrtumaster.read.cpp“, ”modbusrtumaster.write.cpp“

modbusmaster.cpp

#include "modbusrtumaster.h"
#include 
#include ModbusRTUMaster::ModbusRTUMaster()
{//Constructs an empty list.WriteQueue_HoldingReg = new QList();WriteQueue_CoilsReg = new QList();ReadQueue_HoldingReg = new QList();ReadQueue_CoilsReg = new QList();ReadQueue_InputReg = new QList();ReadQueue_DiscreteReg = new QList();
}ModbusRTUMaster::~ModbusRTUMaster()
{stop();quit();wait();
}PluginInfo ModbusRTUMaster::GetPluginInfo()
{PluginInfo pluginInfo;sprintf(pluginInfo.name, IMODBUSRTUMASTER_NAME);sprintf(pluginInfo.date, "%s %s", __DATE__, __TIME__);sprintf(pluginInfo.version, "%s", IMODBUSRTUMASTER_VERSION);sprintf(pluginInfo.author, "%s", IMODBUSRTUMASTER_AUTHOR);sprintf(pluginInfo.company, "%s", IMODBUSRTUMASTER_COMPANY);return pluginInfo;
}void ModbusRTUMaster::run()
{qDebug() << "\tModbus RTU host thread ID: " << currentThreadId();Polling();isModbusRun = 0;runControl = 0;
}void ModbusRTUMaster::stop()
{mutex_RunCtrl.lock();runControl = 0;mutex_RunCtrl.unlock();
}void ModbusRTUMaster::SetPortParam(const char *portName, int baudRate, char parity, int dataBit, int stopBit)
{strcpy(this->_portName, portName);this->_baudRate = baudRate;this->_parity = parity;this->_dataBit = dataBit;this->_stopBit = stopBit;
}void ModbusRTUMaster::SetSlaveAddr(uint8_t addr)
{mbSlaveAddr = addr;modbus_set_slave(mb, mbSlaveAddr);
}int ModbusRTUMaster::GetMasterState()
{return isModbusRun;
}void ModbusRTUMaster::SetMasterPollingInterval(uint16_t ms)
{pollingInterval = ms;
}void ModbusRTUMaster::SetMasterRegRWInterval(uint16_t ms)
{regRWInterval = ms;
}void ModbusRTUMaster::SetResponseTimeout(uint16_t ms)
{//设置modbus响应超时时间struct timeval t;t.tv_sec = ms / 1000;       //st.tv_usec = ms % 1000 * 1000; //usmodbus_set_response_timeout(mb, t.tv_sec, t.tv_usec);
}void ModbusRTUMaster::StartMaster()
{runControl = 1;isModbusRun = 1;start();
}void ModbusRTUMaster::StopMaster()
{stop();wait();
}int ModbusRTUMaster::InitModbus()
{QString errorMsg;//创建modbus实例//mb = modbus_new_rtu(_mainWindow->Port_Name.toStdString().c_str(), _mainWindow->Port_BaudRate, _mainWindow->Port_Parity, _mainWindow->Port_DataBit, _mainWindow->Port_StopBit);mb = modbus_new_rtu(_portName, _baudRate, _parity, _dataBit, _stopBit);if (NULL == mb){errorMsg = QString("Create modbus content failed: %1\n").arg(modbus_strerror(errno));emit lostConnection(errorMsg);return -1;}//设置modbus从机地址if (modbus_set_slave(mb, mbSlaveAddr) == -1){errorMsg = QString("Set modbus slave address failed: %1\n").arg(modbus_strerror(errno));emit lostConnection(errorMsg);return -2;}//设置modbus响应时间为500msSetResponseTimeout(500);//连接modbus端口if(modbus_connect(mb) == -1){errorMsg = QString("Open modbus port failed: %1\n").arg(modbus_strerror(errno));emit lostConnection(errorMsg);return -3;}//    modbus_set_debug(mb, 1);return 0;
}void ModbusRTUMaster::CloseModbus()
{modbus_close(mb);modbus_free(mb);
}void ModbusRTUMaster::ParseIR()
{}void ModbusRTUMaster::ParseHR()
{}void ModbusRTUMaster::ParseDR()
{}void ModbusRTUMaster::ParseCR()
{}void ModbusRTUMaster::WriteFreeMsg(uint8_t *msg, int msg_length)
{if(mb != nullptr){modbus_send_freemsg(mb, msg, msg_length);}
}void ModbusRTUMaster::SetPreCommAddrState(uint8_t state)
{preCommAddr_enable = state;
}void ModbusRTUMaster::SetPreCommAddrWidth(uint8_t width)
{preCommAddr_size = width;
}void ModbusRTUMaster::SetMbMultiSlaveState(uint8_t state)
{multiMbSlave_enable = state;
}void ModbusRTUMaster::ProcessPreCommAddr(uint16_t preAddr)
{uint8_t addr, addr1, addr2;//使能前置通讯地址功能if(preCommAddr_enable != 0){switch (preCommAddr_size){case 1:addr = (uint8_t)preAddr;modbus_send_freemsg(mb, &addr, 1);break;case 2:addr1 = (uint8_t)(preAddr >> 8);addr2 = (uint8_t)(preAddr >> 0);//一般是先发送高位,再发送低位modbus_send_freemsg(mb, &addr1, 1);modbus_send_freemsg(mb, &addr2, 1);break;default:break;}}
}void ModbusRTUMaster::ProcessMultiSlaveSwitch(uint8_t slaveAddr)
{//使能多从机切换功能if(multiMbSlave_enable != 0){SetSlaveAddr(slaveAddr);}
}int ModbusRTUMaster::OperateModbusRegisters(MBData_FloatArray *mBData_FloatArray, int (*handler)(modbus_t *ctx, int addr, int nb, uint16_t *dest))
{int result = 0;for(int i = 0; i < mBData_FloatArray->RetryTimes; i++){//处理多从机切换ProcessMultiSlaveSwitch(mBData_FloatArray->MBDevAddr);//处理前置通讯地址ProcessPreCommAddr(mBData_FloatArray->PreDevAddr);result = handler(mb, mBData_FloatArray->Address, mBData_FloatArray->Count * 2, mBData_FloatArray->ValueArray[0].Type_16BitArray);//如果读写成功,则退出循环if(result != -1){break;}//如果读写失败,则延时后重试else{qDebug() << QString("Retry do handler of MB registers: %1.").arg(i + 1);msleep(RetryDelayTime);}}*mBData_FloatArray->IsExecuted_P = 1;return result;
}int ModbusRTUMaster::OperateModbusRegisters(MBData_FloatArray *mBData_FloatArray, int (*handler)(modbus_t *ctx, int addr, int nb, const uint16_t *dest))
{int result = 0;for(int i = 0; i < mBData_FloatArray->RetryTimes; i++){//处理多从机切换ProcessMultiSlaveSwitch(mBData_FloatArray->MBDevAddr);//处理前置通讯地址ProcessPreCommAddr(mBData_FloatArray->PreDevAddr);result = handler(mb, mBData_FloatArray->Address, mBData_FloatArray->Count * 2, mBData_FloatArray->ValueArray[0].Type_16BitArray);//如果读写成功,则退出循环if(result != -1){break;}//如果读写失败,则延时后重试else{qDebug() << QString("Retry do handler of MB registers: %1.").arg(i + 1);msleep(RetryDelayTime);}}*mBData_FloatArray->IsExecuted_P = 1;return result;
}int ModbusRTUMaster::OperateModbusRegisters(MBData_BitArray *mBData_BitArray, int (*handler)(modbus_t *ctx, int addr, int nb, uint8_t *dest))
{int result = 0;for(int i = 0; i < mBData_BitArray->RetryTimes; i++){//处理多从机切换ProcessMultiSlaveSwitch(mBData_BitArray->MBDevAddr);//处理前置通讯地址ProcessPreCommAddr(mBData_BitArray->PreDevAddr);result = handler(mb, mBData_BitArray->Address, mBData_BitArray->Count, mBData_BitArray->ValueArray);//如果读写成功,则退出循环if(result != -1){break;}//如果读写失败,则延时后重试else{qDebug() << QString("Retry do handler of MB registers: %1.").arg(i + 1);msleep(RetryDelayTime);}}*mBData_BitArray->IsExecuted_P = 1;return result;
}int ModbusRTUMaster::OperateModbusRegisters(MBData_BitArray *mBData_BitArray, int (*handler)(modbus_t *ctx, int addr, int nb, const uint8_t *dest))
{int result = 0;for(int i = 0; i < mBData_BitArray->RetryTimes; i++){//处理多从机切换ProcessMultiSlaveSwitch(mBData_BitArray->MBDevAddr);//处理前置通讯地址ProcessPreCommAddr(mBData_BitArray->PreDevAddr);result = handler(mb, mBData_BitArray->Address, mBData_BitArray->Count, mBData_BitArray->ValueArray);//如果读写成功,则退出循环if(result != -1){break;}//如果读写失败,则延时后重试else{qDebug() << QString("Retry do handler of MB registers: %1.").arg(i + 1);msleep(RetryDelayTime);}}*mBData_BitArray->IsExecuted_P = 1;return result;
}void ModbusRTUMaster::FloatToBit16Array(float *floatArray, uint16_t *bit16Array, uint16_t floatNum)
{for(uint8_t i = 0; i < floatNum; i++){bit16Array[0 + 2 * i] = ((uint16_t *)(&floatArray[i]))[0];bit16Array[1 + 2 * i] = ((uint16_t *)(&floatArray[i]))[1];}
}void ModbusRTUMaster::Bit16ArrayToFloat(uint16_t *bit16Array, float *floatArray, uint16_t floatNum)
{for (uint8_t i = 0; i < floatNum; i++){((uint16_t *)(&floatArray[i]))[0] = bit16Array[0 + 2 * i];((uint16_t *)(&floatArray[i]))[1] = bit16Array[1 + 2 * i];}
}/*
Qt4与Qt5的插件开发方式略有差异,此处采用条件编译可以实现版本兼容。
导出Qt插件,第一参数为插件的IID,第二个参数为实现接口的类。
*/
#if QT_VERSION < 0x050000Q_EXPORT_PLUGIN2(QTPLUGIN_IMODBUSRTUMASTER_IID, ModbusRTUMaster)
#endif

modbusrtumaster.performance.cpp

#include "modbusrtumaster.h"int ModbusRTUMaster::Get_WriteCR_QueueCount()
{mutex_WQ_CR.lock();int count = WriteQueue_CoilsReg->count();mutex_WQ_CR.unlock();return count;
}int ModbusRTUMaster::Get_WriteHR_QueueCount()
{mutex_WQ_HR.lock();int count = WriteQueue_HoldingReg->count();mutex_WQ_HR.unlock();return count;
}int ModbusRTUMaster::Get_ReadCR_QueueCount()
{mutex_RQ_CR.lock();int count = ReadQueue_CoilsReg->count();mutex_RQ_CR.unlock();return count;
}int ModbusRTUMaster::Get_ReadHR_QueueCount()
{mutex_RQ_HR.lock();int count = ReadQueue_HoldingReg->count();mutex_RQ_HR.unlock();return count;
}int ModbusRTUMaster::Get_ReadDR_QueueCount()
{mutex_RQ_DR.lock();int count = ReadQueue_DiscreteReg->count();mutex_RQ_DR.unlock();return count;
}int ModbusRTUMaster::Get_ReadIR_QueueCount()
{mutex_RQ_IR.lock();int count = ReadQueue_InputReg->count();mutex_RQ_IR.unlock();return count;
}void ModbusRTUMaster::Set_WriteCR_QueueMaxCount(uint32_t count)
{MaxCount_WriteCR_Queue = count;
}void ModbusRTUMaster::Set_WriteHR_QueueMaxCount(uint32_t count)
{MaxCount_WriteHR_Queue = count;
}void ModbusRTUMaster::Set_ReadCR_QueueMaxCount(uint32_t count)
{MaxCount_ReadCR_Queue = count;
}void ModbusRTUMaster::Set_ReadHR_QueueMaxCount(uint32_t count)
{MaxCount_ReadHR_Queue = count;
}void ModbusRTUMaster::Set_ReadDR_QueueMaxCount(uint32_t count)
{MaxCount_ReadDR_Queue = count;
}void ModbusRTUMaster::Set_ReadIR_QueueMaxCount(uint32_t count)
{MaxCount_ReadIR_Queue = count;
}void ModbusRTUMaster::Clear_WriteCR_Queue()
{mutex_WQ_CR.lock();WriteQueue_CoilsReg->clear();mutex_WQ_CR.unlock();
}void ModbusRTUMaster::Clear_WriteHR_Queue()
{mutex_WQ_HR.lock();WriteQueue_HoldingReg->clear();mutex_WQ_HR.unlock();
}void ModbusRTUMaster::Clear_ReadCR_Queue()
{mutex_RQ_CR.lock();ReadQueue_CoilsReg->clear();mutex_RQ_CR.unlock();
}void ModbusRTUMaster::Clear_ReadHR_Queue()
{mutex_RQ_HR.lock();ReadQueue_HoldingReg->clear();mutex_RQ_HR.unlock();
}void ModbusRTUMaster::Clear_ReadDR_Queue()
{mutex_RQ_DR.lock();ReadQueue_DiscreteReg->clear();mutex_RQ_DR.unlock();
}void ModbusRTUMaster::Clear_ReadIR_Queue()
{mutex_RQ_IR.lock();ReadQueue_InputReg->clear();mutex_RQ_IR.unlock();
}uint32_t ModbusRTUMaster::Get_WriteCR_Queue_ErrCount()
{return ErrorCount_WriteCR_Queue;
}uint32_t ModbusRTUMaster::Get_WriteHR_Queue_ErrCount()
{return ErrorCount_WriteHR_Queue;
}uint32_t ModbusRTUMaster::Get_ReadCR_Queue_ErrCount()
{return ErrorCount_ReadCR_Queue;
}uint32_t ModbusRTUMaster::Get_ReadHR_Queue_ErrCount()
{return ErrorCount_ReadHR_Queue;
}uint32_t ModbusRTUMaster::Get_ReadDR_Queue_ErrCount()
{return ErrorCount_ReadDR_Queue;
}uint32_t ModbusRTUMaster::Get_ReadIR_Queue_ErrCount()
{return ErrorCount_ReadIR_Queue;
}void ModbusRTUMaster::Clear_WriteCR_Queue_ErrCount()
{ErrorCount_WriteCR_Queue = 0;
}void ModbusRTUMaster::Clear_WriteHR_Queue_ErrCount()
{ErrorCount_WriteHR_Queue = 0;
}void ModbusRTUMaster::Clear_ReadCR_Queue_ErrCount()
{ErrorCount_ReadCR_Queue = 0;
}void ModbusRTUMaster::Clear_ReadHR_Queue_ErrCount()
{ErrorCount_ReadHR_Queue = 0;
}void ModbusRTUMaster::Clear_ReadDR_Queue_ErrCount()
{ErrorCount_ReadDR_Queue = 0;
}void ModbusRTUMaster::Clear_ReadIR_Queue_ErrCount()
{ErrorCount_ReadIR_Queue = 0;
}void ModbusRTUMaster::Set_RetryDelayTime(uint16_t delay)
{RetryDelayTime = delay;
}

modbusmaster.polling.cpp

#include "modbusrtumaster.h"
#include void ModbusRTUMaster::Polling()
{static uint8_t pollControl = 0;while(true){switch (pollControl){case 0://something to pollif(IR_Poll() != 0){msleep(pollingInterval);}break;case 1://something to pollif(DR_Poll() != 0){msleep(pollingInterval);}break;case 2://something to pollif(HR_Poll() != 0){msleep(pollingInterval);}break;case 3://something to pollif(CR_Poll() != 0){msleep(pollingInterval);}break;default:pollControl = 0;break;}if(pollControl > 3 - 1)     //pollControl == 3{pollControl = 0;}else{pollControl++;}//Stop the thread.mutex_RunCtrl.lock();if(runControl == 0){mutex_RunCtrl.unlock();break;}mutex_RunCtrl.unlock();}
}/*** @brief ModbusRTUMaster::IR_Poll* @return 0: nothing to poll 1: something to poll*/
int ModbusRTUMaster::IR_Poll()
{//IR polling queue is empty.if(ReadQueue_InputReg->isEmpty()){return 0;}if (ReadIR() == -1){ErrorCount_ReadIR_Queue++;}else{ParseIR();}return 1;
}/*** @brief ModbusRTUMaster::HR_Poll* @return 0: nothing to poll 1: something to poll*/
int ModbusRTUMaster::HR_Poll()
{//HR polling is empty.if(WriteQueue_HoldingReg->isEmpty() && ReadQueue_HoldingReg->isEmpty()){return 0;}if (ReadHR() == -1){ErrorCount_ReadHR_Queue++;}else{ParseHR();}//Read and write queue are both not emptyif((!ReadQueue_HoldingReg->isEmpty()) && (!WriteQueue_HoldingReg->isEmpty())){msleep(regRWInterval);}if(WriteHR() == -1){ErrorCount_WriteHR_Queue++;}return 1;
}/*** @brief ModbusRTUMaster::DR_Poll* @return 0: nothing to poll 1: something to poll*/
int ModbusRTUMaster::DR_Poll()
{//DR polling queue is empty.if(ReadQueue_DiscreteReg->isEmpty()){return 0;}if (ReadDR() == -1){ErrorCount_ReadDR_Queue++;}else{ParseDR();}return 1;
}/*** @brief ModbusRTUMaster::CR_Poll* @return 0: nothing to poll 1: something to poll*/
int ModbusRTUMaster::CR_Poll()
{//CR polling is empty.if(WriteQueue_CoilsReg->isEmpty() && ReadQueue_CoilsReg->isEmpty()){return 0;}if (ReadCR() == -1){ErrorCount_ReadCR_Queue++;}else{ParseCR();}//Read and write queue are both not emptyif((!ReadQueue_CoilsReg->isEmpty()) && (!WriteQueue_CoilsReg->isEmpty())){msleep(regRWInterval);}if(WriteCR() == -1){ErrorCount_WriteCR_Queue++;}return 1;
}

modbusmaster.read.cpp

#include "modbusrtumaster.h"void ModbusRTUMaster::RequestReadIR(MBData_FloatArray *mBData_FloatArray_P)
{mutex_RQ_IR.lock();while(ReadQueue_InputReg->count() > MaxCount_ReadIR_Queue){ReadQueue_InputReg->takeFirst();}mBData_FloatArray_P->IsExecuted = 0;ReadQueue_InputReg->append(mBData_FloatArray_P);mutex_RQ_IR.unlock();
}void ModbusRTUMaster::RequestReadHR(MBData_FloatArray *mBData_FloatArray_P)
{mutex_RQ_HR.lock();while(ReadQueue_HoldingReg->count() > MaxCount_ReadHR_Queue){ReadQueue_HoldingReg->takeFirst();}mBData_FloatArray_P->IsExecuted = 0;ReadQueue_HoldingReg->append(mBData_FloatArray_P);mutex_RQ_HR.unlock();
}void ModbusRTUMaster::RequestReadDR(MBData_BitArray *mBData_BitArray_P)
{mutex_RQ_DR.lock();while(ReadQueue_DiscreteReg->count() > MaxCount_ReadDR_Queue){ReadQueue_DiscreteReg->takeFirst();}mBData_BitArray_P->IsExecuted = 0;ReadQueue_DiscreteReg->append(mBData_BitArray_P);mutex_RQ_DR.unlock();
}void ModbusRTUMaster::RequestReadCR(MBData_BitArray *mBData_BitArray_P)
{mutex_RQ_CR.lock();while(ReadQueue_CoilsReg->count() > MaxCount_ReadCR_Queue){ReadQueue_CoilsReg->takeFirst();}mBData_BitArray_P->IsExecuted = 0;ReadQueue_CoilsReg->append(mBData_BitArray_P);mutex_RQ_CR.unlock();
}int ModbusRTUMaster::ReadIR()
{int result = 0;MBData_FloatArray *mbData_FloatArray = nullptr;mutex_RQ_IR.lock();if(!ReadQueue_InputReg->isEmpty()){mbData_FloatArray = ReadQueue_InputReg->takeFirst();result = OperateModbusRegisters(mbData_FloatArray, modbus_read_input_registers);}mutex_RQ_IR.unlock();//Read input registers failed.if(result == -1){QString errorMsg = QString("Read I.R. failed: %1. ").arg(modbus_strerror(errno));QString errorRegInfo = QString("PreAddr: %1, DevAddr: %2, Addr: %3, Count: %4")\.arg(mbData_FloatArray->PreDevAddr).arg(mbData_FloatArray->MBDevAddr)\.arg(mbData_FloatArray->Address).arg(mbData_FloatArray->Count);qDebug() << errorMsg + errorRegInfo;emit Get_IRData_Failed(errorMsg + errorRegInfo);emit lostConnection(errorMsg + errorRegInfo);}return result;
}int ModbusRTUMaster::ReadHR()
{int result = 0;MBData_FloatArray *mbData_FloatArray = nullptr;mutex_RQ_HR.lock();if(!ReadQueue_HoldingReg->isEmpty()){mbData_FloatArray = ReadQueue_HoldingReg->takeFirst();result = OperateModbusRegisters(mbData_FloatArray, modbus_read_registers);}mutex_RQ_HR.unlock();//Read holding registers failed.if(result == -1){QString errorMsg = QString("Read H.R. failed: %1. ").arg(modbus_strerror(errno));QString errorRegInfo = QString("PreAddr: %1, DevAddr: %2, Addr: %3, Count: %4")\.arg(mbData_FloatArray->PreDevAddr).arg(mbData_FloatArray->MBDevAddr)\.arg(mbData_FloatArray->Address).arg(mbData_FloatArray->Count);qDebug() << errorMsg + errorRegInfo;emit Get_HRData_Failed(errorMsg + errorRegInfo);}return result;
}int ModbusRTUMaster::ReadDR()
{int result = 0;MBData_BitArray *mbData_BitArray = nullptr;mutex_RQ_DR.lock();if(!ReadQueue_DiscreteReg->isEmpty()){mbData_BitArray = ReadQueue_DiscreteReg->takeFirst();result = OperateModbusRegisters(mbData_BitArray, modbus_read_input_bits);}mutex_RQ_DR.unlock();//Read discrete registers failed.if(result == -1){QString errorMsg = QString("Read D.R. failed: %1. ").arg(modbus_strerror(errno));QString errorRegInfo = QString("PreAddr: %1, DevAddr: %2, Addr: %3, Count: %4")\.arg(mbData_BitArray->PreDevAddr).arg(mbData_BitArray->MBDevAddr)\.arg(mbData_BitArray->Address).arg(mbData_BitArray->Count);qDebug() << errorMsg + errorRegInfo;emit Get_DRData_Failed(errorMsg + errorRegInfo);}return result;
}int ModbusRTUMaster::ReadCR()
{int result = 0;MBData_BitArray *mbData_BitArray = nullptr;mutex_RQ_CR.lock();if(!ReadQueue_CoilsReg->isEmpty()){mbData_BitArray = ReadQueue_CoilsReg->takeFirst();result = OperateModbusRegisters(mbData_BitArray, modbus_read_bits);}mutex_RQ_CR.unlock();//Read coils registers failed.if(result == -1){QString errorMsg = QString("Read C.R. failed: %1. ").arg(modbus_strerror(errno));QString errorRegInfo = QString("PreAddr: %1, DevAddr: %2, Addr: %3, Count: %4")\.arg(mbData_BitArray->PreDevAddr).arg(mbData_BitArray->MBDevAddr)\.arg(mbData_BitArray->Address).arg(mbData_BitArray->Count);qDebug() << errorMsg + errorRegInfo;emit Get_CRData_Failed(errorMsg + errorRegInfo);}return result;
}/*
Qt4与Qt5的插件开发方式略有差异,此处采用条件编译可以实现版本兼容。
导出Qt插件,第一参数为插件的IID,第二个参数为实现接口的类。
*/
#if QT_VERSION < 0x050000Q_EXPORT_PLUGIN2(QTPLUGIN_IMODBUSRTUMASTER_IID, IModbusRTUMaster)
#endif

modbusmaster.write.cpp

#include "modbusrtumaster.h"void ModbusRTUMaster::RequestWriteHR(uint16_t address, uint16_t count, float *dataArray)
{mutex_WQ_HR.lock();MBData_FloatArray *mBData_FloatArray = new MBData_FloatArray(address, count);for(uint16_t i = 0; i < count; i++){mBData_FloatArray->ValueArray[i].Type_Float = dataArray[i];}while(WriteQueue_HoldingReg->count() > MaxCount_WriteHR_Queue){WriteQueue_HoldingReg->takeFirst();}*mBData_FloatArray->IsExecuted_P = 0;WriteQueue_HoldingReg->append(mBData_FloatArray);mutex_WQ_HR.unlock();
}void ModbusRTUMaster::RequestWriteHR(uint16_t address, float data)
{RequestWriteHR(address, 1, &data);
}void ModbusRTUMaster::RequestWriteHR(MBData_FloatArray *mBData_FloatArray)
{mutex_WQ_HR.lock();while(WriteQueue_HoldingReg->count() > MaxCount_WriteHR_Queue){WriteQueue_HoldingReg->takeFirst();}//mBData_FloatArray->IsExecuted指向mBData_FloatArray->IsExecuted_PmBData_FloatArray->IsExecuted_P = &mBData_FloatArray->IsExecuted;*mBData_FloatArray->IsExecuted_P = 0;WriteQueue_HoldingReg->append(mBData_FloatArray);mutex_WQ_HR.unlock();
}void ModbusRTUMaster::RequestWriteHR(MBData_Float *mBData_Float)
{mutex_WQ_HR.lock();while(WriteQueue_HoldingReg->count() > MaxCount_WriteHR_Queue){WriteQueue_HoldingReg->takeFirst();}MBData_FloatArray *mBData_FloatArray = new MBData_FloatArray(mBData_Float->PreDevAddr, mBData_Float->MBDevAddr, mBData_Float->Address, 1, mBData_Float->RetryTimes);mBData_FloatArray->ValueArray[0].Type_Float = mBData_Float->Value.Type_Float;//将mBData_Float->IsExecuted指向mBData_FloatArray->IsExecuted_PmBData_FloatArray->IsExecuted_P = &mBData_Float->IsExecuted;*mBData_FloatArray->IsExecuted_P = 0;WriteQueue_HoldingReg->append(mBData_FloatArray);mutex_WQ_HR.unlock();
}void ModbusRTUMaster::RequestWriteCR(uint16_t address, uint16_t count, uint8_t *dataArray)
{mutex_WQ_CR.lock();MBData_BitArray *mBData_BitArray = new MBData_BitArray(address, count);for(uint16_t i = 0; i < count; i++){mBData_BitArray->ValueArray[i] = dataArray[i];}while(WriteQueue_CoilsReg->count() > MaxCount_WriteCR_Queue){WriteQueue_CoilsReg->takeFirst();}*mBData_BitArray->IsExecuted_P = 0;WriteQueue_CoilsReg->append(mBData_BitArray);mutex_WQ_CR.unlock();
}void ModbusRTUMaster::RequestWriteCR(uint16_t address, uint8_t data)
{RequestWriteCR(address, 1, &data);
}void ModbusRTUMaster::RequestWriteCR(MBData_BitArray *mBData_BitArray)
{mutex_WQ_CR.lock();while(WriteQueue_CoilsReg->count() > MaxCount_WriteCR_Queue){WriteQueue_CoilsReg->takeFirst();}//mBData_BitArray->IsExecuted指向mBData_BitArray->IsExecuted_PmBData_BitArray->IsExecuted_P = &mBData_BitArray->IsExecuted;*mBData_BitArray->IsExecuted_P = 0;WriteQueue_CoilsReg->append(mBData_BitArray);mutex_WQ_CR.unlock();
}void ModbusRTUMaster::RequestWriteCR(MBData_Bit *mBData_Bit)
{mutex_WQ_CR.lock();MBData_BitArray *mBData_BitArray = new MBData_BitArray(mBData_Bit->PreDevAddr, mBData_Bit->MBDevAddr, mBData_Bit->Address, 1, mBData_Bit->RetryTimes);mBData_BitArray->ValueArray[0] = mBData_Bit->Value;while(WriteQueue_CoilsReg->count() > MaxCount_WriteCR_Queue){WriteQueue_CoilsReg->takeFirst();}//mBData_Bit->IsExecuted指向mBData_BitArray->IsExecuted_PmBData_BitArray->IsExecuted_P = &mBData_Bit->IsExecuted;*mBData_BitArray->IsExecuted_P = 0;WriteQueue_CoilsReg->append(mBData_BitArray);mutex_WQ_CR.unlock();
}int ModbusRTUMaster::WriteHR()
{int result = 0;MBData_FloatArray *mbData_FloatArray = nullptr;mutex_WQ_HR.lock();if(!WriteQueue_HoldingReg->isEmpty()){mbData_FloatArray = WriteQueue_HoldingReg->takeFirst();result = OperateModbusRegisters(mbData_FloatArray, modbus_write_registers);}mutex_WQ_HR.unlock();//Write holding registers failed.if(result == -1){QString errorMsg = QString("Write H.R. failed: %1. ").arg(modbus_strerror(errno));QString errorRegInfo = QString("PreAddr: %1, DevAddr: %2, Addr: %3, Count: %4")\.arg(mbData_FloatArray->PreDevAddr).arg(mbData_FloatArray->MBDevAddr)\.arg(mbData_FloatArray->Address).arg(mbData_FloatArray->Count);qDebug() << errorMsg + errorRegInfo;emit Set_HRData_Failed(errorMsg + errorRegInfo);}return result;
}int ModbusRTUMaster::WriteCR()
{int result = 0;MBData_BitArray *mbData_BitArray = nullptr;mutex_WQ_CR.lock();if(!WriteQueue_CoilsReg->isEmpty()){mbData_BitArray = WriteQueue_CoilsReg->takeFirst();result = OperateModbusRegisters(mbData_BitArray, modbus_write_bits);}mutex_WQ_CR.unlock();//Write coils registers failed.if(result == -1){QString errorMsg = QString("Write C.R. failed: %1. ").arg(modbus_strerror(errno));QString errorRegInfo = QString("PreAddr: %1, DevAddr: %2, Addr: %3, Count: %4")\.arg(mbData_BitArray->PreDevAddr).arg(mbData_BitArray->MBDevAddr)\.arg(mbData_BitArray->Address).arg(mbData_BitArray->Count);qDebug() << errorMsg + errorRegInfo;emit Set_CRData_Failed(errorMsg + errorRegInfo);}return result;
}

libmodbus库源码修改

由于前置通讯地址不属于标准Modbus协议的内容,因此需要修改libmodbus库的源码,增加一个发送任意数据的端口。以libmodbus库V3.1.6版本的源码为例:

modbus.c新增内容

/*** @brief modbus_send_freemsg 使用modbus端口发送任意数据* @param ctx modbus的一个实例* @param msg 数据指针* @param msg_length 数据长度* @return 发送的数据长度*/
int modbus_send_freemsg(modbus_t *ctx, uint8_t *msg, int msg_length)
{return ctx->backend->send(ctx, msg, msg_length);
}

modbus.h新增内容

///开放发送任意数据的API,增强库的可扩展性
MODBUS_API int modbus_send_freemsg(modbus_t *ctx, uint8_t *msg, int msg_length);

使用指南

可以考虑直接将ModbusRTU主机源码添加到项目工程中,也可以单独将将ModbusRTU主机源码编译成插件,再在其他项目中动态加载与调用。由于使用的接口都是一致的,这两种方式调用ModbusRTU主机的流程相同。以下仅介绍基本的操作说明,关于性能监控、故障诊断、信号绑定、GUI数据同步等实际常用的操作,请参考基于Qt/libmodbus框架开发modbusRTU主机模块(二)- 应用场景。

基本操作

实例化主机对象

  • 直接添加源码的情况下,modbusMaster即为实例化的对象。
ModbusRTUMaster *modbusMaster = new ModbusRTUMaster();
  • 使用Qt插件的情况下,modbusMaster即为实例化的对象。
bool MainWindow::loadModbusRTUMasterPlugin()
{QObject *obj = NULL;QString modbusRTUMasterPluginPath("EWhalesModbusRTUMaster.dll");QPluginLoader pluginLoader(modbusRTUMasterPluginPath);obj = pluginLoader.instance();if(obj != NULL){modbusMaster=qobject_cast(obj);if(modbusMaster){connect(obj,SIGNAL(Get_IRData(QString)),this,SLOT(on_modbusMaster_receiveData(QString)));connect(obj,SIGNAL(lostConnection(QString)),this,SLOT(on_modbusMaster_lostConnection(QString)));qDebug()<GetPluginInfo().name;qDebug()<<"Date:"<GetPluginInfo().date;qDebug()<<"Version:"<GetPluginInfo().version;qDebug()<<"Author:"<GetPluginInfo().author;qDebug()<<"Company:"<GetPluginInfo().company;return true;}}else{qDebug()<

设置通讯端口参数

调用modbusMaster->SetPortParam方法设置通讯端口参数。此函数原型为:void SetPortParam(const char *portName, int baudRate, char parity, int dataBit, int stopBit),其中:

  • portName:串口名称,一般由串口库******提供的方法扫描得到,在不同的操作系统中表现的形式不一样。
  • baudRate:波特率,int类型,直接填入需要设置的波特率就行,例如960019200115200等值。
  • parity:校验位,char类型,可填入**‘N’(**无校验), ‘E’(偶校验), ‘O’(奇校验)。
  • dataBit:数据位,int类型,可填入7(数据位7)或8(数据位8)。
  • stopBit:停止位,int类型,可填入1(停止位1)或2(停止位2)。

连接通讯端口

调用modbusMaster->InitModbus方法连接Modbus端口,创建实例。

设置多从机与前置通讯状态

调用modbusMaster->SetSlaveAddr方法设置从机的地址。

//Set the default address of modbus slave.
modbusMaster->SetSlaveAddr(0x01);
modbusMaster->SetMbMultiSlaveState(0);//Set pre-communication, the order of call functions can't be adjusted!
modbusMaster->SetPreCommAddrWidth(modbus_preAddrBitWidth);
modbusMaster->SetPreCommAddrState(modbus_preAddrEnable);

设置轮询间隔时间、响应超时时间、重试间隔时间

根据数据传输速度与主从机设备性能设置一个合适的轮询间隔时间与响应超时时间。

modbusMaster->SetMasterPollingInterval(100);    //Polling interval 100 ms.
modbusMaster->SetMasterRegRWInterval(100);      //Read and write interval 100 ms.
modbusMaster->SetResponseTimeout(150);          //Set response timeout 150 ms.
modbusMaster->Set_RetryDelayTime(20);           //Set time interval of retry 20 ms.

启动主机服务

调用modbusMaster->StartMaster方法启动主机服务。由于主机服务一般都是需要在一个循环中轮询Modbus寄存器,会造成线程的阻塞。因此调用此方法时,会自动创建一个新的线程,并在此线程上执行轮询操作。

读寄存器

四种寄存器均支持读出操作。通过数据的IsExecuted成员的值可判断此变量的读/写操作是否执行完成。

MBData_FloatArray *MBIR_F1_Read = new MBData_FloatArray(0x0000, 0x01, 999, 4, 3);    //前置网络地址0x0000, 从机地址0x01, 变量地址999, 长度4, Retry times is 3 (including the first request)
MBData_FloatArray *MBHR_F1_Read = new MBData_FloatArray(0x0000, 0x01, 999, 4, 3);
MBData_BitArray *****MBDR_F1_Read = new MBData_BitArray(0x0000, 0x01, 999, 4, 3);
MBData_BitArray *****MBCR_F1_Read = new MBData_BitArray(0x0000, 0x01, 999, 4, 3);modbusMaster->RequestReadIR(MBIR_F1_Read);
modbusMaster->RequestReadDR(MBDR_F1_Read);
modbusMaster->RequestReadHR(MBHR_F1_Read);
modbusMaster->RequestReadCR(MBCR_F1_Read);//wait until isExecuted == 1float data1 = MBHR_F1_Read->ValueArray[0].Type_Float;
float data2 = MBHR_F1_Read->ValueArray[1].Type_Float;
uint8_t data3 = MBCR_F1_Read->ValueArray[0];
uint8_t data4 = MBCR_F1_Read->ValueArray[1];

写寄存器

保持寄存器与线圈寄存器支持写入操作。通过数据的IsExecuted成员的值可判断此变量的读/写操作是否执行完成。

MBData_Float MBHR_F1_Write(0x0000, 0x01, 999, 100);
MBData_Bit MBCR_F1_Write(0x0000, 0x01, 999, 0);modbusMaster->RequestWriteHR(&MBHR_F1_Write);
modbusMaster->RequestWriteCR(&MBCR_F1_Write);

关闭主机服务

调用modbusMaster->StopMaster方法关闭主机服务(关闭寄存器轮询线程)。

调用modbusMaster->CloseModbus方法关闭主机端口。

相关内容

热门资讯

【实验报告】实验一 图像的... 实验目的熟悉Matlab图像运算的基础——矩阵运算;熟悉图像矩阵的显示方法࿰...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
大模型落地比趋势更重要,NLP... 全球很多人都开始相信,以ChatGPT为代表的大模型,将带来一场NLP领...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
kuernetes 资源对象分... 文章目录1. pod 状态1.1 容器启动错误类型1.2 ImagePullBackOff 错误1....
STM32实战项目-数码管 程序实现功能: 1、上电后,数码管间隔50ms计数; 2、...
TM1638和TM1639差异... TM1638和TM1639差异说明 ✨本文不涉及具体的单片机代码驱动内容,值针对芯...
Qt+MySql开发笔记:Qt... 若该文为原创文章,转载请注明原文出处 本文章博客地址:https://h...
Java内存模型中的happe... 第29讲 | Java内存模型中的happen-before是什么? Java 语言...
《扬帆优配》算力概念股大爆发,... 3月22日,9股封单金额超亿元,工业富联、鸿博股份、鹏鼎控股分别为3.0...
CF1763D Valid B... CF1763D Valid Bitonic Permutations 题目大意 拱形排列࿰...
SQL语法 DDL、DML、D... 文章目录1 SQL通用语法2 SQL分类3 DDL 数据定义语言3.1 数据库操作3.2 表操作3....
文心一言 VS ChatGPT... 3月16号,百度正式发布了『文心一言』,这是国内公司第一次发布类Chat...
CentOS8提高篇5:磁盘分...        首先需要在虚拟机中模拟添加一块新的硬盘设备,然后进行分区、格式化、挂载等...
Linux防火墙——SNAT、... 目录 NAT 一、SNAT策略及作用 1、概述 SNAT应用环境 SNAT原理 SNAT转换前提条...
部署+使用集群的算力跑CPU密... 我先在开头做一个总结,表达我最终要做的事情和最终环境是如何的,然后我会一...
Uploadifive 批量文... Uploadifive 批量文件上传_uploadifive 多个上传按钮_asing1elife的...
C++入门语法基础 文章目录:1. 什么是C++2. 命名空间2.1 域的概念2.2 命名...
2023年全国DAMA-CDG... DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义...
php实现助记词转TRX,ET... TRX助记词转地址网上都是Java,js或其他语言开发的示例,一个简单的...
【分割数据集操作集锦】毕设记录 1. 按要求将CSV文件转成json文件 有时候一些网络模型的源码会有data.json这样的文件里...
Postman接口测试之断言 如果你看文字部分还是不太理解的话,可以看看这个视频,详细介绍postma...
前端学习第三阶段-第4章 jQ... 4-1 jQuery介绍及常用API导读 01-jQuery入门导读 02-JavaScri...
4、linux初级——Linu... 目录 一、用CRT连接开发板 1、安装CRT调试工具 2、连接开发板 3、开机后ctrl+c...
Urban Radiance ... Urban Radiance Fields:城市辐射场 摘要:这项工作的目标是根据扫描...
天干地支(Java) 题目描述 古代中国使用天干地支来记录当前的年份。 天干一共有十个,分别为:...
SpringBoot雪花ID长... Long类型精度丢失 最近项目中使用雪花ID作为主键,雪花ID是19位Long类型数...
对JSP文件的理解 JSP是java程序。(JSP本质还是一个Servlet) JSP是&#...
【03173】2021年4月高... 一、单向填空题1、大量应用软件开发工具,开始于A、20世纪70年代B、20世纪 80年...
LeetCode5.最长回文子... 目录题目链接题目分析解题思路暴力中心向两边拓展搜索 题目链接 链接 题目分析 简单来说࿰...