基于Qt/libmodbus框架开发modbusRTU主机模块(一)- 插件开发
日期 | 作者 | 版本 | 说明 |
---|---|---|---|
2020.11.09 | Dog Tao | V0.0 | 开始撰写文档。 |
2020.11.14 | Dog Tao | V0.1 | 完成了文档的主体内容。 |
2020.11.20 | Dog Tao | V0.2 | 1. 增加了GUI窗体与Modbus主机数据同步的例程。 2. 修复了WriteCR(), WriteHR()方法没有将IsExecuted 置1的bug。 |
2021.01.12 | Dog Tao | V1.0 | 1. 修改了数据类型封装,增加了IsExecuted_P指针类型成员,解决了标志位IsExecuted在读写完成时不能被正确置1的问题。 2. 增加了GetPluginInfo的接口,可以获取当前插件的名称、编译时间、版本号、作者、单位等信息。 3. 增加了该插件当前运行线程ID号的debug输出,以便于监控测试。 4. 增加了GUI窗体与MB数据同步的独立模式开发说明。 5. 增加了MB数据同步操作的一些注意事项。 |
2021.02.02 | Dog Tao | V1.1 | 在“数据读写同步”小节中增加了“超时处理”与“重试操作”内容。 |
2021.02.14 | Dog Tao | V1.2 | 在“数据读写同步”小节中增加了“封装判断方法”与“调用判断方法”的内容。其核心实现原理是采用了C++的类模板std::function封装Lambda表达式并作为函数的参数传入。 |
2021.03.03 | Dog Tao | V1.3 | 在“使用指南”章节“GUI窗体与Modbus主机数据同步 - 耦合模式”中增加读写函数再封装 注意事项,用以强调这些函数传值的方式必须为指针或者为引用。 |
2021.03.14 | Dog Tao | V2.0 ★ | 1. 为六个寄存器读写缓冲队列增加了线程锁,修复了频繁读写寄存器操作可能造成插件崩溃的漏洞。 2. 大幅度优化了轮询逻辑,极大的提高了轮询效率(寄存器的读写队列为空时自动跳过当前等待时间)。 3. 增加了关于Modbus Master运行控制的API,例如连接/断开端口、设置轮询间隔时间、设置读写间隔时间等。 4. 升级了数据封装,增加了PreDevAddr,MBDevAddr两个成员,用以指示当前变量归属的从机与前置通讯单元。 5. 支持多从机数据轮询,通过设置MB变量的前置通讯单元地址与从机地址,可以将数据请求送至不同的通讯网络与从机。 6. 开放了借助已连接的modbus端口,发送任意数据的接口(需要修改libmodbus源码)。 7. 在主函数中增加了Modbus Master的功能测试例程(可将插件编译为可执行程序)。 |
2021.03.18 | Dog Tao | V2.1 | 1. 增加了获取读写队列数量和清空读写队列缓存的接口。 2. 改进了主机轮询过程中的延时性能。 3. 将Modbus RTU主机的应用开发部分独立成篇。 |
2021.03.19 | Dog Tao | V2.2 | 修复了由于变量没有正确初始化而导致SetMbMultiSlaveState默认开启,进而造成了与低版本的兼容性问题。 |
2021.03.20 | Dog Tao | V2.3 | 1. 增加了设置modbus响应超时的接口,以适应不同通讯速率的场景。 2. 修复了由于串口名参数未设置缓存而造成modbus经常连接不成功的bug。 |
2021.03.24 | Dog Tao | V2.4 ★ | 1. 增加了设置与获取读写寄存器队列最大容量的接口。一旦队列超出最大容量,最早的请求数据将被自动舍弃。 2. 增加了读写寄存器操作错误计数与清空计数的接口。通过读写寄存器操作错误计数可以了解到当前通讯的稳定性,监控系统工作状态。 3. 完善了读写寄存器操作错误的信号反馈机制,四种寄存器的读与两种寄存器的写出错时都有信号发射出来。 4. 完善了主机通讯的读写失败重试功能(MB数据封装增加了RetryTimes成员),通过设置合适的重试次数与重试延时,系统可以在通讯出错的时候尝试重新读写。 5. 采用回调函数注册操作方法的方式,对寄存器的读写操作进行了封装,简化了代码、优化了逻辑结构。 |
2021.03.29 | Dog Tao | V2.5 | 修复了一些情况下没有为IsExecuted_P设置正确的指向而造成IsExecuted误判的bug。 |
2021.04.02 | Dog Tao | V2.6 | 1. 通讯出错时,Debug会输出重试次数的信息。 2. 通讯出错时,Debug与对应的信号会输出寄存器的详细信息。 |
2021.04.06 | Dog Tao | V2.7 | 1. 修复了判断操作寄存器失败的bug。 2. 修复了写寄存器操作时,传入MBData_Bit/MBData_Float类型数据无法使用重试功能的bug。 |
2023.03.21 | Dog Tao | V2.8 | 将文档从notion平台转移到markdown。 |
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_BitArray
与MBData_FloatArray
数据类型中的uint8_t *IsExecuted_P
成员。设置这个成员的原因是:写MB寄存器的函数重载功能可导致传入参数是MBData_Bit
与MBData_Float
类型,而在函数内部都是需要转换为MBData_BitArray
与MBData_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_P
mBData_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_P
mBData_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 PreDevAddr
与uint8_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 PreDevAddr
与uint8_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;
}
流程设计
控制参数说明
模块插件化
可将此模块按照Qt Plugin的标准开发,以动态链接库的形式提供给外部调用,具体操作请参考《Qt Plugin插件开发指南》。本文涉及的核心源码都是严格按照插件开发的方式给出。
核心源码
工程文件
QT -= gui
CONFIG += 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.0
TARGET = EWhalesModbusRTUMaster
# TEMPLATE = lib 生成插件
# TEMPLATE = app 生成应用程序
TEMPLATE = lib
CONFIG += plugin
SOURCES += \
main.cpp \
modbusrtumaster.cpp \
modbusrtumaster.performance.cpp \
modbusrtumaster.polling.cpp \
modbusrtumaster.read.cpp \
modbusrtumaster.write.cpp
macx {
INCLUDEPATH += $$PWD/libmodbus-3.1.6/include/modbus
LIBS += -L$$PWD/libmodbus-3.1.6/lib -lmodbus
DISTFILES += \
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/inc
SOURCES += \
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.c
LIBS += -Llibmodbus-3.1.6-windows/dll -lws2_32
DISTFILES += 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 += target
HEADERS += \
imodbusrtumaster.h \
modbusrtumaster.h
接口文件
#ifndef IMODBUSRTUMASTER_H
#define IMODBUSRTUMASTER_H
#ifndef ISERIALPORT_H
#define ISERIALPORT_H
#include <QString>
#include <QtPlugin>
#include <QList>
/*
宏定义 接口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 failed
virtual 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 failed
virtual 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 <QObject>
#include <QThread>
#include <QMutex>
#include <QDebug>
#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 >= 0x050000
Q_PLUGIN_METADATA(IID QTPLUGIN_IMODBUSRTUMASTER_IID)
#endif
public:
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:
//线程锁,用以加锁核心的队列list
QMutex 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, 8
int _stopBit = 1; //1, 2
uint8_t mbSlaveAddr = 0x01; //Modbus协议中的从机地址, default value = 0x01
uint16_t preCommAddr = 0x0000; //前置从机地址(目标通讯模块地址), default value = 0x0000
uint8_t preCommAddr_size = 2; //前置从机地址位数(8位/16位), default value = 2
uint8_t preCommAddr_enable = 0; //是否使能发送前置从机地址, default value = 0
uint8_t multiMbSlave_enable = 0; //是否使能多modbus从机通讯机制, default value = 0
uint16_t pollingInterval = 50; //interval of polling each kinds of registers
uint16_t regRWInterval = 50; //interval of read and write registers
QList<MBData_FloatArray*> *WriteQueue_HoldingReg;
QList<MBData_BitArray*> *WriteQueue_CoilsReg;
QList<MBData_FloatArray*> *ReadQueue_HoldingReg;
QList<MBData_BitArray*> *ReadQueue_CoilsReg;
QList<MBData_FloatArray*> *ReadQueue_InputReg;
QList<MBData_BitArray*> *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 ms
void 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 <string.h>
#include <QDebug>
ModbusRTUMaster::ModbusRTUMaster()
{
//Constructs an empty list.
WriteQueue_HoldingReg = new QList<MBData_FloatArray *>();
WriteQueue_CoilsReg = new QList<MBData_BitArray *>();
ReadQueue_HoldingReg = new QList<MBData_FloatArray *>();
ReadQueue_CoilsReg = new QList<MBData_BitArray *>();
ReadQueue_InputReg = new QList<MBData_FloatArray *>();
ReadQueue_DiscreteReg = new QList<MBData_BitArray *>();
}
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; //s
t.tv_usec = ms % 1000 * 1000; //us
modbus_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响应时间为500ms
SetResponseTimeout(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 < 0x050000
Q_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 <QDebug>
void ModbusRTUMaster::Polling()
{
static uint8_t pollControl = 0;
while(true)
{
switch (pollControl)
{
case 0:
//something to poll
if(IR_Poll() != 0)
{
msleep(pollingInterval);
}
break;
case 1:
//something to poll
if(DR_Poll() != 0)
{
msleep(pollingInterval);
}
break;
case 2:
//something to poll
if(HR_Poll() != 0)
{
msleep(pollingInterval);
}
break;
case 3:
//something to poll
if(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 empty
if((!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 empty
if((!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 < 0x050000
Q_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_P
mBData_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_P
mBData_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_P
mBData_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_P
mBData_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<IModbusRTUMaster *>(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()<<modbusRTUMasterPluginPath<<"is loaded...";
qDebug()<<"Name:"<<modbusMaster->GetPluginInfo().name;
qDebug()<<"Date:"<<modbusMaster->GetPluginInfo().date;
qDebug()<<"Version:"<<modbusMaster->GetPluginInfo().version;
qDebug()<<"Author:"<<modbusMaster->GetPluginInfo().author;
qDebug()<<"Company:"<<modbusMaster->GetPluginInfo().company;
return true;
}
}
else
{
qDebug()<<modbusRTUMasterPluginPath<<"is loaded failed: "<<pluginLoader.errorString();
return false;
}
return false;
}
设置通讯端口参数
调用modbusMaster->SetPortParam
方法设置通讯端口参数。此函数原型为:void SetPortParam(const char *portName, int baudRate, char parity, int dataBit, int stopBit)
,其中:
portName
:串口名称,一般由串口库******提供的方法扫描得到,在不同的操作系统中表现的形式不一样。baudRate
:波特率,int
类型,直接填入需要设置的波特率就行,例如9600,19200,115200等值。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 == 1
float 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
方法关闭主机端口。