VC++实现的ModBus-RTU主机接口函数(采用回调方式)
扫描二维码
随时随地手机看文章
计算机上面使用Modbus读取传感器或相关设备还是比较常用的,之前写的Modbus-RTU协议将串口封装到了协议栈内,使用的时候遇到短板了,比如我最新需要使用TCP来读取Modbus设备,就不好用了,通过回调函数可以很简单的解决这个问题。
//modbus-rtu.c
/*************************************************************************************************************
* 文件名: MODBUS_RTU.c
* 功能: MODBUS_RTU通信协议层
* 作者: cp1300@139.com
* 创建时间: 2014-03-24
* 最后修改时间: 2016-11-04
* 详细: MODBUS RTU通信协议层
2016-03-21:增加防止接收数据过短导致异常
2016-11-04:增加回调接口,将数据收发接口使用回调函数
*************************************************************************************************************/
#include "StdAfx.h"
#include "MODBUS_RTU.h"
#include "windows.h"
using namespace System;
/*************************************************************************************************************************
* 函数 : bool MODBUS_RTU::ReadMultRegPack(BYTE *pPackBuff, DWORD *pPackLen, READ_REG_TYPE RegType, u8 SlaveAddr, u16 RegAddr, u8 RegNum, char **pError)
* 功能 : 主机读取从机指定多个连续寄存器数据打包
* 参数 : pPackBuff:打包缓冲区,pPackLen:打包的数据长度;RegType:读取的寄存器类型;SlaveAddr:从机地址;RegAddr:需读取的寄存器地址;RegNum:寄存器数量;pError:返回错误说明
* 返回 : true:成功;false:错误
* 依赖 : 底层通信驱动
* 作者 : cp1300@139.com
* 时间 : 2014-03-24
* 最后修改时间 : 2016-11-04
* 说明 : 数据打包,不涉及发送
*************************************************************************************************************************/
bool MODBUS_RTU::ReadMultRegPack(BYTE *pPackBuff, DWORD *pPackLen, READ_REG_TYPE RegType, u8 SlaveAddr, u16 RegAddr, u8 RegNum, char **pError)
{
MRTU_READ_FRAME *pFrame; //发送数据帧格式
u16 crc16;
if (pPackBuff == nullptr) //缓冲区无效
{
*pPackLen = 0;
if (pError != nullptr) *pError = "缓冲区无效!";
return false; //句柄无效
}
pFrame = (MRTU_READ_FRAME *)pPackBuff;
//数据结构填充
pFrame->addr = SlaveAddr; //从机地址
pFrame->fun = (u8)RegType; //功能码,读取
pFrame->StartReg = SWAP16(RegAddr); //寄存器起始地址
if ((RegNum > 127) || (RegNum == 0))
{
if (pError != nullptr) *pError = "一次读取的寄存器数量超出范围!";
return false; //寄存器数量错误
}
pFrame->RegNum = SWAP16(RegNum); //需要读取的寄存器数量
crc16 = usMBCRC16(pPackBuff, 6); //计算CRC16
pFrame->CRC16 = crc16; //crc16
*pPackLen = 6+2;
if (pError != nullptr) *pError = "打包成功!";
return true;
}
/*************************************************************************************************************************
* 函数 : MRTU_ERROR MODBUS_RTU::ReadMultRegUnpack(BYTE *pPackBuff, DWORD PackLen, READ_REG_TYPE RegType, u8 SlaveAddr, u16 RegAddr, u8 RegNum, u16 pRegData[], char **pError)
* 功能 : 主机读取从机指定多个连续寄存器数据解包
* 参数 : pPackBuff:数据包缓冲区;PackLen:数据包大小;SlaveAddr:从机地址;RegAddr:需读取的寄存器地址;RegNum:寄存器数量;pRegData:返回寄存器的值,至少为RegNum的2倍
返回的寄存器的值按照循序存放在pRegData中
* 返回 : MRTU_ERROR:通信状态
* 依赖 : 底层通信驱动
* 作者 : cp1300@139.com
* 时间 : 2014-03-24
* 最后修改时间 : 2016-11-04
* 说明 : 数据包解析
*************************************************************************************************************************/
MRTU_ERROR MODBUS_RTU::ReadMultRegUnpack(BYTE *pPackBuff, DWORD PackLen, READ_REG_TYPE RegType, u8 SlaveAddr, u16 RegAddr, u8 RegNum, u16 pRegData[], char **pError)
{
MRTU_RETURN_FRAME *pReFrame; //返回数据帧格式
MRTU_UNU_FRAME *pUnuFrame; //返回的异常数据帧格式
u16 crc16;
u16 i;
if (pPackBuff == nullptr) //缓冲区无效
{
if (pError != nullptr) *pError = "缓冲区无效!";
return MRTU_HANDLE_ERROR; //句柄无效
}
if (PackLen < 3)
{
if (pError != nullptr) *pError = "返回数据长度过短!";
return MRTU_LEN_ERROR; //返回数据长度错误
}
pReFrame = (MRTU_RETURN_FRAME *)pPackBuff;
//检查地址
if (pReFrame->addr != SlaveAddr)
{
if (pError != nullptr) *pError = "返回的从机地址错误!";
return MRTU_ADDR_ERROR;
}
//对接受的数据进行CRC校验
crc16 = usMBCRC16(pPackBuff, PackLen - 2);//计算CRC16
if ((pPackBuff[PackLen - 1] != (crc16 >> 8)) || (pPackBuff[PackLen - 2] != (crc16 & 0xff)))
{
if (pError != nullptr) *pError = "CRC校验错误";
return MRTU_CRC_ERROR; //返回CRC校验错误
}
//返回的功能码不一致
if (pReFrame->fun != (u8)RegType)
{
pUnuFrame = (MRTU_UNU_FRAME *)pPackBuff; //异常数据帧
if (pUnuFrame->ErrorFun == ((u8)RegType | 0x80)) //返回有异常
{
switch (pUnuFrame->unu)
{
case 1: //异常码1
{
if (pError != nullptr) *pError = "返回异常码1!";
return MRTU_UNUS1_ERROR;
}
case 2: //异常码2
{
if (pError != nullptr) *pError = "返回异常码2!";
return MRTU_UNUS2_ERROR;
}
case 3: //异常码3
{
if (pError != nullptr) *pError = "返回异常码3!";
return MRTU_UNUS3_ERROR;
}
case 4: //异常码4
{
if (pError != nullptr) *pError = "返回异常码4!";
return MRTU_UNUS4_ERROR;
}
case 5://异常码5
{
if (pError != nullptr) *pError = "返回异常码5!";
return MRTU_UNUS5_ERROR;
}
case 6://异常码6
{
if (pError != nullptr) *pError = "返回异常码6!";
return MRTU_UNUS6_ERROR;
}
default:
{
if (pError != nullptr) *pError = "返回未知异常码!";
return MRTU_OTHER_ERROR;
}
}
}
else
{
if (pError != nullptr) *pError = "返回功能码错误!";
return MRTU_FUNR_ERROR;
}
}
//判断数据长度
if (pReFrame->DataLen != (RegNum * 2))
{
if (pError != nullptr) *pError = "返回数据长度错误,长度小于需要读取的寄存器数量x2!";
return MRTU_LEN_ERROR; //返回数据长度错误
}
//获取返回的寄存器的值
for (i = 0; i < RegNum; i++)
{
pRegData[i] = pReFrame->DataBuff[i * 2];
pRegData[i] <DataBuff[i * 2 + 1];
}
if (pError != nullptr) *pError = "读取成功!";
return MRTU_OK; //返回成功
}
/*************************************************************************************************************************
* 函数 : bool WriteOnetRegPack(BYTE *pPackBuff, DWORD *pPackLen, u8 SlaveAddr, u16 RegAddr, u16 RegData, char **pError)
* 功能 : 主机写从机一个指定寄存器数据打包
* 参数 : pPackBuff:打包缓冲区,pPackLen:打包的数据长度;SlaveAddr:从机地址;RegAddr:写寄存器地址;RegData:寄存器的值;pError:错误提示
* 返回 : true:成功;false:错误
* 依赖 : 底层通信驱动
* 作者 : cp1300@139.com
* 时间 : 2014-03-24
* 最后修改时间 : 2016-11-04
* 说明 : 数据打包,不涉及发送
*************************************************************************************************************************/
bool MODBUS_RTU::WriteOnetRegPack(BYTE *pPackBuff, DWORD *pPackLen, u8 SlaveAddr, u16 RegAddr, u16 RegData, char **pError)
{
MRTU_WRITE_FRAME *pFrame;//发送数据帧格式
u16 crc16;
if (pPackBuff == nullptr) //缓冲区无效
{
*pPackLen = 0;
if (pError != nullptr) *pError = "缓冲区无效!";
return false; //句柄无效
}
pFrame = (MRTU_WRITE_FRAME *)pPackBuff;
//数据结构填充
pFrame->addr = SlaveAddr; //从机地址
pFrame->fun = (u8)MRTU_FUN_WRITE; //功能码,预置单个寄存器
pFrame->StartReg = SWAP16(RegAddr); //寄存器起始地址
pFrame->RegData = SWAP16(RegData); //写入寄存器内容
pFrame->crc16 = usMBCRC16(pPackBuff, 6); //计算CRC16
*pPackLen = 6+2;
if (pError != nullptr) *pError = "打包成功!";
return true;
}
/*************************************************************************************************************************
* 函数 : MRTU_ERROR MODBUS_RTU::WriteOneRegUnpack(BYTE *pPackBuff, DWORD PackLen, u8 SlaveAddr, u16 RegAddr, u16 RegData, char **pError)
* 功能 : 主机写从机一个指定寄存器数据解包
* 参数 : pPackBuff:打包缓冲区,pPackLen:打包的数据长度;SlaveAddr:从机地址;RegAddr:写寄存器地址;RegData:寄存器的值;RegData:需要写入的值;pError:错误说明
* 返回 : MRTU_ERROR:通信状态
* 依赖 : 底层通信驱动
* 作者 : cp1300@139.com
* 时间 : 2014-03-24
* 最后修改时间 : 2016-11-04
* 说明 : 数据包解包
*************************************************************************************************************************/
MRTU_ERROR MODBUS_RTU::WriteOneRegUnpack(BYTE *pPackBuff, DWORD PackLen, u8 SlaveAddr, u16 RegAddr, u16 RegData, char **pError)
{
MRTU_WRITE_FRAME *pReFrame;//发送数据帧格式
MRTU_UNU_FRAME *pUnuFrame; //返回的异常数据帧格式
u16 crc16;
if (pPackBuff == nullptr) //缓冲区无效
{
if (pError != nullptr) *pError = "缓冲区无效!";
return MRTU_HANDLE_ERROR; //句柄无效
}
if (PackLen < 3)
{
if (pError != nullptr) *pError = "返回数据长度过短!";
return MRTU_LEN_ERROR; //返回数据长度错误
}
pReFrame = (MRTU_WRITE_FRAME *)pPackBuff;
//检查地址
if (pReFrame->addr != SlaveAddr)
{
if (pError != nullptr) *pError = "返回的从机地址错误!";
return MRTU_ADDR_ERROR;
}
//对接受的数据进行CRC校验
crc16 = usMBCRC16(pPackBuff, PackLen - 2);//计算CRC16
if ((pPackBuff[PackLen - 1] != (crc16 >> 8)) || (pPackBuff[PackLen - 2] != (crc16 & 0xff)))
{
if (pError != nullptr) *pError = "CRC校验错误";
return MRTU_CRC_ERROR; //返回CRC校验错误
}
//返回的功能码不一致
if (pReFrame->fun != (u8)MRTU_FUN_WRITE)
{
pUnuFrame = (MRTU_UNU_FRAME *)pPackBuff; //异常数据帧
if (pUnuFrame->ErrorFun == ((u8)MRTU_FUN_WRITE | 0x80))//返回有异常
{
switch (pUnuFrame->unu)
{
case 1: //异常码1
{
if (pError != nullptr) *pError = "返回异常码1!";
return MRTU_UNUS1_ERROR;
}
case 2: //异常码2
{
if (pError != nullptr) *pError = "返回异常码2!";
return MRTU_UNUS2_ERROR;
}
case 3: //异常码3
{
if (pError != nullptr) *pError = "返回异常码3!";
return MRTU_UNUS3_ERROR;
}
case 4: //异常码4
{
if (pError != nullptr) *pError = "返回异常码4!";
return MRTU_UNUS4_ERROR;
}
case 5://异常码5
{
if (pError != nullptr) *pError = "返回异常码5!";
return MRTU_UNUS5_ERROR;
}
case 6://异常码6
{
if (pError != nullptr) *pError = "返回异常码6!";
return MRTU_UNUS6_ERROR;
}
default:
{
if (pError != nullptr) *pError = "返回未知异常码!";
return MRTU_OTHER_ERROR;
}
}
}
else
{
if (pError != nullptr) *pError = "返回功能码错误!";
return MRTU_FUNR_ERROR;
}
}
//判断数据是否写入
if (SWAP16(pReFrame->StartReg) != RegAddr) //返回的寄存器地址不一致
{
if (pError != nullptr) *pError = "返回寄存器地址错误!";
return MRTU_REG_ERROR; //返回寄存器错误
}
if (SWAP16(pReFrame->RegData) != RegData)
{
if (pError != nullptr) *pError = "数据写入错误,没有写入成功!";
return MRTU_WRITE_ERROR; //写入数据错误
}
if (pError != nullptr) *pError = "写入成功!";
return MRTU_OK; //返回成功
}
/*************************************************************************************************************************
* 函数 : MRTU_ERROR MODBUS_RTU::WriteMultRegPack(BYTE *pPackBuff, DWORD *pPackLen, u8 SlaveAddr, u16 RegAddr, u16 pRegData[], u8 RegNum, , char **pError)
* 功能 : 主机写从机多个指定寄存器数据打包
* 参数 : pPackBuff:打包缓冲区,pPackLen:打包的数据长度;SlaveAddr:从机地址;RegAddr:写寄存器地址;pRegData:需要写入的寄存器的值;RegNum:寄存器数量;pError:错误说明
* 返回 : true:成功;false:错误
* 依赖 : 底层通信驱动
* 作者 : cp1300@139.com
* 时间 : 2014-03-24
* 最后修改时间 : 2016-11-04
* 说明 : 写多个寄存器数据打包
写入寄存器的值按照循序排列,使用小端格式,大小必须为RegNum*2
最大只能一次写入不超过127个寄存器
*************************************************************************************************************************/
bool MODBUS_RTU::WriteMultRegPack(BYTE *pPackBuff, DWORD *pPackLen, u8 SlaveAddr, u16 RegAddr, u16 pRegData[], u8 RegNum, char **pError)
{
MRTU_WRITE_MULT_FRAME *pFrame; //发送数据帧格式
DWORD i;
WORD crc16;
if (pPackBuff == nullptr) //缓冲区无效
{
*pPackLen = 0;
if (pError != nullptr) *pError = "缓冲区无效!";
return false; //句柄无效
}
pFrame = (MRTU_WRITE_MULT_FRAME *)pPackBuff;
//数据结构填充
pFrame->addr = SlaveAddr; //从机地址
pFrame->fun = (u8)MRTU_FUN_MWRITE; //功能码,预置多个寄存器
pFrame->StartReg = SWAP16(RegAddr); //寄存器起始地址
if ((RegNum > 127) || (RegNum == 0))
{
*pPackLen = 0;
if (pError != nullptr) *pError = "一次写入寄存器数量过多!";
return FALSE; //寄存器数量错误
}
pFrame->RegNum = SWAP16(RegNum); //写入寄存器数量
pFrame->DataLen = 2 * RegNum; //数据长度
//循环写入数据
for (i = 0; i < RegNum; i++)
{
pFrame->DataBuff[2 * i] = pRegData[i] >> 8; //高位
pFrame->DataBuff[2 * i + 1] = pRegData[i] & 0xff; //低位
}
crc16 = usMBCRC16(pPackBuff, 7 + pFrame->DataLen); //计算CRC16,高低位对调过
pFrame->DataBuff[pFrame->DataLen] = crc16 & 0xff; //高位
pFrame->DataBuff[pFrame->DataLen + 1] = crc16 >> 8; //低位
*pPackLen = 7 + pFrame->DataLen + 2;
if (pError != nullptr) *pError = "打包成功!";
return true;
}
/*************************************************************************************************************************
* 函数 : MRTU_ERROR MODBUS_RTU::WriteMultRegUnpack(BYTE *pPackBuff, DWORD PackLen, u8 SlaveAddr, u16 RegAddr, u8 RegNum, char **pError)
* 功能 : 主机写从机多个指定寄存器数据解包
* 参数 : pPackBuff:打包缓冲区,pPackLen:打包的数据长度;SlaveAddr:从机地址;RegAddr:写寄存器地址;RegNum:寄存器数量;pError:错误说明
* 返回 : MRTU_ERROR
* 依赖 : 底层通信驱动
* 作者 : cp1300@139.com
* 时间 : 2014-03-24
* 最后修改时间: 2016-11-04
* 说明 : 写多个寄存器数据解包
*************************************************************************************************************************/
MRTU_ERROR MODBUS_RTU::WriteMultRegUnpack(BYTE *pPackBuff, DWORD PackLen, u8 SlaveAddr, u16 RegAddr, u8 RegNum, char **pError)
{
MRTU_WRIT_EMULT_RFRAME *pReFrame; //返回数据帧格式
MRTU_UNU_FRAME *pUnuFrame; //返回的异常数据帧格式
u16 crc16;
u8 i;
if (pPackBuff == nullptr) //缓冲区无效
{
if (pError != nullptr) *pError = "缓冲区无效!";
return MRTU_HANDLE_ERROR; //句柄无效
}
if (PackLen < 3)
{
if (pError != nullptr) *pError = "返回数据长度过短!";
return MRTU_LEN_ERROR; //返回数据长度错误
}
pReFrame = (MRTU_WRIT_EMULT_RFRAME *)pPackBuff;
//检查地址
if (pReFrame->addr != SlaveAddr)
{
if (pError != nullptr) *pError = "返回的从机地址错误!";
return MRTU_ADDR_ERROR;
}
//对接受的数据进行CRC校验
crc16 = usMBCRC16(pPackBuff, PackLen - 2);//计算CRC16
if ((pPackBuff[PackLen - 1] != (crc16 >> 8)) || (pPackBuff[PackLen - 2] != (crc16 & 0xff)))
{
if (pError != nullptr) *pError = "CRC校验错误";
return MRTU_CRC_ERROR; //返回CRC校验错误
}
//返回的功能码不一致
if (pReFrame->fun != (u8)MRTU_FUN_MWRITE)
{
pUnuFrame = (MRTU_UNU_FRAME *)pPackBuff; //异常数据帧
if (pUnuFrame->ErrorFun == ((u8)MRTU_FUN_MWRITE | 0x80))//返回有异常
{
switch (pUnuFrame->unu)
{
case 1: //异常码1
{
if (pError != nullptr) *pError = "返回异常码1!";
return MRTU_UNUS1_ERROR;
}
case 2: //异常码2
{
if (pError != nullptr) *pError = "返回异常码2!";
return MRTU_UNUS2_ERROR;
}
case 3: //异常码3
{
if (pError != nullptr) *pError = "返回异常码3!";
return MRTU_UNUS3_ERROR;
}
case 4: //异常码4
{
if (pError != nullptr) *pError = "返回异常码4!";
return MRTU_UNUS4_ERROR;
}
case 5://异常码5
{
if (pError != nullptr) *pError = "返回异常码5!";
return MRTU_UNUS5_ERROR;
}
case 6://异常码6
{
if (pError != nullptr) *pError = "返回异常码6!";
return MRTU_UNUS6_ERROR;
}
default:
{
if (pError != nullptr) *pError = "返回未知异常码!";
return MRTU_OTHER_ERROR;
}
}
}
else
{
if (pError != nullptr) *pError = "返回功能码错误!";
return MRTU_FUNR_ERROR;
}
}
//判断数据是否写入
if (SWAP16(pReFrame->StartReg) != RegAddr) //返回的寄存器地址不一致
{
if (pError != nullptr) *pError = "返回寄存器地址错误!";
return MRTU_REG_ERROR; //返回寄存器错误
}
if (SWAP16(pReFrame->RegNum) != RegNum)
{
if (pError != nullptr) *pError = "数据写入错误,返回的寄存器数量不一致!";
return MRTU_WRITE_ERROR; //写入数据错误
}
if (pError != nullptr) *pError = "写入成功!";
return MRTU_OK; //返回成功
}
/*************************************************************************************************************************
* 函数 : MRTU_ERROR MODEBUS_HOST_ReadMultReg(MODEBUS_HANDLE *pHandle, READ_REG_TYPE RegType, u8 SlaveAddr, u16 RegAddr, u8 RegNum, u16 pRegData[])
* 功能 : 主机读取从机指定多个连续寄存器(需要初始化回调通信接口)
* 参数 : RegType:读取的寄存器类型;SlaveAddr:从机地址;RegAddr:需读取的寄存器地址;RegNum:寄存器数量;pRegData:返回寄存器的值,至少为RegNum的2倍;pError:错误信息
* 返回 : MRTU_ERROR:通信状态
* 依赖 : 底层通信驱动
* 作者 : cp1300@139.com
* 时间 : 2014-03-24
* 最后修改时间 : 2016-11-04
* 说明 : 输入输出的数据都为小端模式
返回的寄存器的值按照循序存放在pRegData中
需要先初始化通信接口,并且会申请 MODBUS_RTU_PACK_MAX_SIZE+1 字节堆内存用于支持可重入
*************************************************************************************************************************/
MRTU_ERROR MODBUS_RTU::ReadMultReg(READ_REG_TYPE RegType, u8 SlaveAddr, u16 RegAddr, u8 RegNum, u16 pRegData[], char **pError)
{
BYTE PackBuff[MODBUS_RTU_PACK_MAX_SIZE+1];
DWORD len;
if (ReadMultRegPack(PackBuff, &len, RegType, SlaveAddr, RegAddr, RegNum, pError) == false)
{
return MRTU_HANDLE_ERROR;
}
//调用回调进行
if (this->pSendDataCollBack == nullptr)
{
if (pError == nullptr) *pError = "发送回调函数无效!";
return MRTU_HANDLE_ERROR;
}
try
{
if (this->pSendDataCollBack(PackBuff, len) == false)
{
if (pError == nullptr) *pError = "发送数据失败!";
return MRTU_SEND_ERROR;
}
}
catch (Exception^ e)
{
if (pError == nullptr) *pError = "发送数据发生了异常!";
return MRTU_SEND_ERROR;
}
//发送完成了,调用接收回调进行数据接收
if (this->pReadDataCollBack == nullptr)
{
if (pError == nullptr) *pError = "接收回调函数无效!";
return MRTU_READ_ERROR;
}
try
{
if (this->pReadDataCollBack(PackBuff, &len) == false)
{
if (pError == nullptr) *pError = "接收数据失败!";
return MRTU_READ_ERROR;
}
}
catch (Exception^ e)
{
if (pError == nullptr) *pError = "接收数据发生了异常!";
return MRTU_READ_ERROR;
}
if (len == 0)
{
if (pError == nullptr) *pError = "接收数据超时!";
return MRTU_TIME_OUT;
}
if (len > (256 + 7))
{
if (pError == nullptr) *pError = "接收数据溢出!";
return MRTU_OVER_ERROR;
}
//数据接收完成了,开始解析
return ReadMultRegUnpack(PackBuff, len, RegType, SlaveAddr,RegAddr, RegNum, pRegData, pError);
}
/*************************************************************************************************************************
* 函数 : MRTU_ERROR MODBUS_RTU::WriteOnetReg(u8 SlaveAddr, u16 RegAddr, u16 RegData, char **pError)
* 功能 : 主机写入从机一个寄存器(需要初始化回调通信接口)
* 参数 : SlaveAddr:从机地址;RegAddr:写寄存器地址;RegData:寄存器的值;pError:错误提示
* 返回 : MRTU_ERROR:通信状态
* 依赖 : 底层通信驱动
* 作者 : cp1300@139.com
* 时间 : 2014-03-24
* 最后修改时间 : 2016-11-04
* 说明 : 输入输出的数据都为小端模式
需要先初始化通信接口,并且会申请 MODBUS_RTU_PACK_MAX_SIZE+1 字节堆内存用于支持可重入
*************************************************************************************************************************/
MRTU_ERROR MODBUS_RTU::WriteOnetReg(u8 SlaveAddr, u16 RegAddr, u16 RegData, char **pError)
{
BYTE PackBuff[MODBUS_RTU_PACK_MAX_SIZE + 1];
DWORD len;
if (WriteOnetRegPack(PackBuff, &len, SlaveAddr, RegAddr, RegData, pError) == false)
{
return MRTU_HANDLE_ERROR;
}
//调用回调进行
if (this->pSendDataCollBack == nullptr)
{
if (pError == nullptr) *pError = "发送回调函数无效!";
return MRTU_HANDLE_ERROR;
}
try
{
if (this->pSendDataCollBack(PackBuff, len) == false)
{
if (pError == nullptr) *pError = "发送数据失败!";
return MRTU_SEND_ERROR;
}
}
catch (Exception^ e)
{
if (pError == nullptr) *pError = "发送数据发生了异常!";
return MRTU_SEND_ERROR;
}
//发送完成了,调用接收回调进行数据接收
if (this->pReadDataCollBack == nullptr)
{
if (pError == nullptr) *pError = "接收回调函数无效!";
return MRTU_READ_ERROR;
}
try
{
if (this->pReadDataCollBack(PackBuff, &len) == false)
{
if (pError == nullptr) *pError = "接收数据失败!";
return MRTU_READ_ERROR;
}
}
catch (Exception^ e)
{
if (pError == nullptr) *pError = "接收数据发生了异常!";
return MRTU_READ_ERROR;
}
if (len == 0)
{
if (pError == nullptr) *pError = "接收数据超时!";
return MRTU_TIME_OUT;
}
if (len > (256 + 7))
{
if (pError == nullptr) *pError = "接收数据溢出!";
return MRTU_OVER_ERROR;
}
//数据接收完成了,开始解析
return WriteOneRegUnpack(PackBuff, len, SlaveAddr, RegAddr, RegData, pError);
}
/*************************************************************************************************************************
* 函数 : MRTU_ERROR MODBUS_RTU::WriteMultReg(u8 SlaveAddr, u16 RegAddr, u16 pRegData[],u8 RegNum, char **pError)
* 功能 : 主机写从机多个指定寄存器(需要初始化回调通信接口)
* 参数 : SlaveAddr:从机地址;RegAddr:写寄存器地址;RegNum:寄存器数量;pError:错误说明
* 返回 : MRTU_ERROR
* 依赖 : 底层通信驱动
* 作者 : cp1300@139.com
* 时间 : 2014-03-24
* 最后修改时间: 2016-11-04
* 说明 : 写多个寄存器数据解包
*************************************************************************************************************************/
MRTU_ERROR MODBUS_RTU::WriteMultReg(u8 SlaveAddr, u16 RegAddr, u16 pRegData[],u8 RegNum, char **pError)
{
BYTE PackBuff[MODBUS_RTU_PACK_MAX_SIZE + 1];
DWORD len;
if (WriteMultRegPack(PackBuff, &len, SlaveAddr, RegAddr, pRegData, RegNum, pError) == false)
{
return MRTU_HANDLE_ERROR;
}
//调用回调进行
if (this->pSendDataCollBack == nullptr)
{
if (pError == nullptr) *pError = "发送回调函数无效!";
return MRTU_HANDLE_ERROR;
}
try
{
if (this->pSendDataCollBack(PackBuff, len) == false)
{
if (pError == nullptr) *pError = "发送数据失败!";
return MRTU_SEND_ERROR;
}
}
catch (Exception^ e)
{
if (pError == nullptr) *pError = "发送数据发生了异常!";
return MRTU_SEND_ERROR;
}
//发送完成了,调用接收回调进行数据接收
if (this->pReadDataCollBack == nullptr)
{
if (pError == nullptr) *pError = "接收回调函数无效!";
return MRTU_READ_ERROR;
}
try
{
if (this->pReadDataCollBack(PackBuff, &len) == false)
{
if (pError == nullptr) *pError = "接收数据失败!";
return MRTU_READ_ERROR;
}
}
catch (Exception^ e)
{
if (pError == nullptr) *pError = "接收数据发生了异常!";
return MRTU_READ_ERROR;
}
if (len == 0)
{
if (pError == nullptr) *pError = "接收数据超时!";
return MRTU_TIME_OUT;
}
if (len > (256 + 7))
{
if (pError == nullptr) *pError = "接收数据溢出!";
return MRTU_OVER_ERROR;
}
//数据接收完成了,开始解析
return WriteMultRegUnpack(PackBuff, len, SlaveAddr, RegAddr, RegNum, pError);
}
//MODBUS CRC16计算
//结果为大端模式
BIG_U16 MODBUS_RTU::usMBCRC16( u8 * pucFrame, u16 usLen )
{
static const u8 aucCRCHi[] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40
};
static const u8 aucCRCLo[] = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
0x41, 0x81, 0x80, 0x40
};
u8 ucCRCHi = 0xFF;
u8 ucCRCLo = 0xFF;
int iIndex;
while( usLen-- )
{
iIndex = ucCRCLo ^ *( pucFrame++ );
ucCRCLo = ( u8 )( ucCRCHi ^ aucCRCHi[iIndex] );
ucCRCHi = aucCRCLo[iIndex];
}
return ( u16 )( ucCRCHi << 8 | ucCRCLo );
}//modbus-rtu.h
/*************************************************************************************************************
* 文件名: MODBUS_RTU.h
* 功能: MODBUS_RTU通信协议层
* 作者: cp1300@139.com
* 创建时间: 2014-03-24
* 最后修改时间: 2016-11-04
* 详细: MODBUS RTU通信协议层
2016-03-21:增加防止接收数据过短导致异常
2016-11-04:增加回调接口,将数据收发接口使用回调函数
*************************************************************************************************************/
#ifndef _MODBUS_RTU_H_
#define _MODBUS_RTU_H_
#include "windows.h"
//基本数据类型定义
#ifndef u8
#define u8 BYTE
#endif //u8
#ifndef u16
#define u16 WORD
#endif //u16
#ifndef u32
#define u32 DWORD
#endif //u32
#ifndef s16
#define s16 INT16
#endif //s16
#ifndef s32
#define s32 int
#endif //s32
//16位整形数高低对调
#define SWAP16(x) (((x & 0xff00) >> 8) | ((x & 0xff) << 8))
//最大数据包大小
#define MODBUS_RTU_PACK_MAX_SIZE 300
//支持的功能码
#define MRTU_FUN_READ_HOLD 0x03 //读保持寄存器,可读写寄存器为保持寄存器
#define MRTU_FUN_READ_INPUT 0x04 //读输入寄存器,为只读寄存器
#define MRTU_FUN_WRITE 0x06 //写单个保持寄存器
#define MRTU_FUN_MWRITE 0x10 //写多个保持寄存器
//大端数据标记
#define BIG_U16 u16 //16位整形数,需要转换为大端模式,兼容modubus
//读取寄存器类型选择
typedef enum
{
HOLD_REG = MRTU_FUN_READ_HOLD, //保持寄存器
INPUT_REG = MRTU_FUN_READ_INPUT, //输入寄存器
} READ_REG_TYPE;
//数据读取 主机数据帧,主机读取从机的数据帧
typedef struct
{
u8 addr; //地址 address
u8 fun; //功能码 function
BIG_U16 StartReg; //数据起始地址
BIG_U16 RegNum; //需要读取的寄存器个数
BIG_U16 CRC16; //CRC16
} MRTU_READ_FRAME; //MODBUS RTU master Read Reg Frame
//预置单个保持寄存器,主机写从机单个寄存器的数据帧
//从机返回数据帧与主机预置单个寄存器数据帧一样
typedef struct
{
u8 addr; //地址 address
u8 fun; //功能码 function
BIG_U16 StartReg; //数据起始地址
BIG_U16 RegData; //数据值
BIG_U16 crc16; //CRC校验值
} MRTU_WRITE_FRAME; //MODBUS RTU master Write Reg Frame
//预置多个保持寄存器,主机写从机多个寄存器的数据帧
typedef struct
{
u8 addr; //地址 address
u8 fun; //功能码 function
BIG_U16 StartReg; //数据起始地址
BIG_U16 RegNum; //寄存器数量
u8 DataLen; //数据长度
u8 DataBuff[2]; //寄存器的值
} MRTU_WRITE_MULT_FRAME;
//预置多个保持寄存器后返回数据帧,从机返回主机的数据帧
typedef struct
{
u8 addr; //地址 address
u8 fun; //功能码 function
BIG_U16 StartReg; //数据起始地址
BIG_U16 RegNum; //寄存器数量
BIG_U16 crc16; //CRC校验值
} MRTU_WRIT_EMULT_RFRAME;
//读取从机返回数据帧格式,从机返回给主机的数据帧
typedef struct
{
u8 addr; //地址 address
u8 fun; //功能码 function
u8 DataLen; //数据长度
u8 DataBuff[2]; //数据区,CRC16放在最后结尾处
//MRTU_REG16 CRC16; //CRC16
} MRTU_RETURN_FRAME; //MODBUS RTU master Read Reg Frame
//从机返回的异常数据帧,从机返回的异常数据帧
typedef struct
{
u8 addr; //地址 address
u8 ErrorFun; //错误功能码 function+0x80
u8 unu; //异常码
u8 crc16H; //CRC16放在最后结尾处
u8 crc16L; //CRC16放在最后结尾处
} MRTU_UNU_FRAME;
//从机数据包解析后的相关信息
typedef struct
{
u8 SlaveAddr; //主机发送的从机地址
u8 RegNum; //主机需要读取从机的寄存器数量
u8 fun; //主机发送给从机的功能码
u16 StartReg; //主机需要读写的从机寄存器地址
} MRTU_SLAVE_INFO;
//异常码定义
typedef enum
{
MRTU_UNUS1 = 0x01, //异常码1,无效的操作码
MRTU_UNUS2 = 0x02, //异常码2,无效的数据地址
MRTU_UNUS3 = 0x03, //异常码3,无效的数据值
MRTU_UNUS4 = 0x04, //异常码4,无效操作
MRTU_UNUS5 = 0x05, //异常码5
MRTU_UNUS6 = 0x06, //异常码6
} MRTU_UNUS;
//错误状态
typedef enum
{
MRTU_OK = 0, //OK
MRTU_TIME_OUT = 1, //超时
MRTU_OVER_ERROR = 2, //溢出
MRTU_CRC_ERROR = 3, //CRC错误
MRTU_ADDR_ERROR = 4, //地址错误,返回地址不一致
MRTU_REG_ERROR = 5, //寄存器地址错误,返回寄存器地址不一致
MRTU_FUNR_ERROR = 6, //功能码错误,返回功能码不一致或者不支持的功能码
MRTU_HANDLE_ERROR = 7, //通信回调接口错误,或缓冲区错误
MRTU_REGN_ERROR = 8, //寄存器数量错误
MRTU_LEN_ERROR = 9, //返回数据长度错误
MRTU_WRITE_ERROR = 10, //写寄存器错误,写入与读取不一致
MRTU_SEND_ERROR = 11, //发送数据失败
MRTU_READ_ERROR = 12, //读取数据失败
MRTU_UNUS1_ERROR = 0x81, //异常码1,无效的操作码
MRTU_UNUS2_ERROR = 0x82, //异常码2,无效的数据地址
MRTU_UNUS3_ERROR = 0x83, //异常码3,无效的数据值
MRTU_UNUS4_ERROR = 0x84, //异常码4,无效操作
MRTU_UNUS5_ERROR = 0x85, //异常码5
MRTU_UNUS6_ERROR = 0x86, //异常码6
MRTU_OTHER_ERROR = 0xff
} MRTU_ERROR;
//发送回调函数定义
typedef bool(*MODBUS_SendDataCollBack)(BYTE *, DWORD); //发送缓冲区与发送数据大小;返回:发送成功返回true,发送失败返回false
//接收回调函数定义
typedef bool(*MODBUS_ReadDataCollBack)(BYTE *, DWORD *); //接收缓冲区与接收数据长度;返回:读取成功返回true,读取失败,接口错误返回false,返回true后数据长度为0算作超时,返回false一般都是接口错误
class MODBUS_RTU
{
private:
MODBUS_SendDataCollBack pSendDataCollBack; //发送回调函数指针
MODBUS_ReadDataCollBack pReadDataCollBack; //接收回调函数指针
u16 usMBCRC16(u8 * pucFrame, u16 usLen); //crc计算
public:
//数据包打包与解包接口-不涉及到数据发送与接收
bool ReadMultRegPack(BYTE *pPackBuff, DWORD *pPackLen, READ_REG_TYPE RegType, u8 SlaveAddr, u16 RegAddr, u8 RegNum, char **pError);
MRTU_ERROR ReadMultRegUnpack(BYTE *pPackBuff, DWORD PackLen, READ_REG_TYPE RegType, u8 SlaveAddr, u16 RegAddr, u8 RegNum, u16 pRegData[], char **pError);
bool WriteOnetRegPack(BYTE *pPackBuff, DWORD *pPackLen, u8 SlaveAddr, u16 RegAddr, u16 RegData, char **pError);
MRTU_ERROR WriteOneRegUnpack(BYTE *pPackBuff, DWORD PackLen, u8 SlaveAddr, u16 RegAddr, u16 RegData, char **pError);
bool WriteMultRegPack(BYTE *pPackBuff, DWORD *pPackLen, u8 SlaveAddr, u16 RegAddr, u16 pRegData[], u8 RegNum, char **pError);
MRTU_ERROR WriteMultRegUnpack(BYTE *pPackBuff, DWORD PackLen, u8 SlaveAddr, u16 RegAddr, u8 RegNum, char **pError);
//带回调模式通信接口初始化
//pSendDataCollBack:发送回调函数指针;pReadDataCollBack:接收回调函数指针;TimeOut:接收超时时间
void MODBUS_RTU::InterfaceInit(MODBUS_SendDataCollBack pSendDataCollBack, MODBUS_ReadDataCollBack pReadDataCollBack)
{
this->pSendDataCollBack = pSendDataCollBack; //发送回调函数指针
this->pReadDataCollBack = pReadDataCollBack; //接收回调函数指针
}
MRTU_ERROR ReadMultReg(READ_REG_TYPE RegType, u8 SlaveAddr, u16 RegAddr, u8 RegNum, u16 pRegData[], char **pError);
MRTU_ERROR WriteOnetReg(u8 SlaveAddr, u16 RegAddr, u16 RegData, char **pError);
MRTU_ERROR WriteMultReg(u8 SlaveAddr, u16 RegAddr, u16 pRegData[], u8 RegNum, char **pError);
//构造函数
MODBUS_RTU()
{
this->pSendDataCollBack = nullptr;
this->pReadDataCollBack = nullptr;
}
//析构函数
~MODBUS_RTU()
{
}
} ;
#endif /*_MODBUS_RTU_H_*/
//托管回调定义
delegate bool ProcessDelegateSend(BYTE *, DWORD); //定义发送数据回调函数托管 delegate bool ProcessDelegateRead(BYTE *, DWORD *); //定义发接收据回调函数托管 ProcessDelegateSend ^SendCallBack; //声明发送回调函数托管 ProcessDelegateRead ^ReadCallBack; //声明接收回调函数托管
this->SendCallBack = gcnew ProcessDelegateSend(this, &温湿度采集::Form1::Uart_Send); this->ReadCallBack = gcnew ProcessDelegateRead(this, &温湿度采集::Form1::Uart_WaitAndRead);
//接口定义
//串口发送函数-用于回调
bool Uart_Send(BYTE *pData, DWORD DataLen)
{
this->mUart->UART_ClearRxBuff(this->mHandle); //清空接收缓冲区
this->mUart->UART_ClearTxBuff(this->mHandle); //清空发送缓冲区
return this->mUart->UART_SendData(this->mHandle, pData, DataLen); //调用串口发送数据
}
//串口接收函数-用于回调
bool Uart_WaitAndRead(BYTE *pData, DWORD *pDataLen)
{
DWORD len;
len = UartWait(20, 500);
if (len)
{
if (len > MODBUS_RTU_PACK_MAX_SIZE) len = MODBUS_RTU_PACK_MAX_SIZE; //必须限制数据包大小
if (this->mUart->UART_ReadData(this->mHandle, pData, len) <= 0) //读取串口接收到的数据
{
*pDataLen = 0;
return false; //通信接口错误
}
else
{
*pDataLen = len;
return true; //读取成功了
}
}
else
{
*pDataLen = 0; //长度为0,没有读取到数据
return true; //通信接口没有发生异常
}
}
//等待串口接收完成
DWORD UartWait(WORD ByteTimeOut, WORD RxTimeOut)
{
DWORD cnt = 0;
DWORD i, j = RxTimeOut / ByteTimeOut + 1;
for (i = 0; i < j;i ++)
{
cnt = this->mUart->UART_GetRxCnt(this->mHandle);
Sleep(ByteTimeOut);
if ((cnt > 0) && cnt == (this->mUart->UART_GetRxCnt(this->mHandle)))
{
return cnt;
}
}
return 0;
}
//初始化回调接口道modbus-RTU
IntPtr pvFun1, pvFun2; pvFun1 = Marshal::GetFunctionPointerForDelegate(this->SendCallBack);//获取发送托管的回调指针 pvFun2 = Marshal::GetFunctionPointerForDelegate(this->ReadCallBack);//获取接收托管的回调指针 //初





