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);//获取接收托管的回调指针 //初