目录
1.ModbusRTU代码
ModbusRTU协议.c文件:
#include "ModbusRTU.h"
#include "mcu_config.h"
ModbusState ModbusStateRXD = MODBUS_RXD_WAIT;
ModbusRTUBuffStruct ModbusRTUBuffRXD = { 0 };
ModbusRTUBuffStruct ModbusRTUBuffTXD = { 0 };
uint8_t ModbusTick = 0;
uint8_t ReadRegMpa(uint16_t Adder, uint16_t *regData);
uint8_t WriteRegMap(uint16_t Adder, uint16_t Data);
/**
* @brief Modbus接收中断回调函数
* @param RxData
*/
void ModbusRTU_Receive(uint8_t RxData)
{
ModbusTick = 0;
if (ModbusStateRXD == MODBUS_RXD_WAIT) {
ModbusStateRXD = MODBUS_RXD_ON;
ModbusRTUBuffRXD.DataLen = 0;
ModbusRTUBuffRXD.DataBuff[ModbusRTUBuffRXD.DataLen] = RxData;
ModbusRTUBuffRXD.DataLen++;
}
else if (ModbusStateRXD == MODBUS_RXD_ON) {
if (ModbusRTUBuffRXD.DataLen >= MODBUSRTU_BUFF_MAX)
ModbusStateRXD = MODBUS_RXD_FINISH;
else {
ModbusRTUBuffRXD.DataBuff[ModbusRTUBuffRXD.DataLen] = RxData;
ModbusRTUBuffRXD.DataLen++;
}
}
}
/**
* @brief Modbus接收完成判断,基于串口通信数据帧的间隔(该函数在1ms中断中执行)
* @note 在接收状态下,如果串口接收的数据之间时间间隔大于最大超时时间,即认定一帧数据接收完成
* @param
*/
void ModbusRTU_RXDCompleteJudge(void)
{
if (ModbusStateRXD == MODBUS_RXD_ON) {
ModbusTick = (ModbusTick < UINT8_MAX) ? (ModbusTick + 1) : UINT8_MAX;
if (ModbusTick > MODBUSRTU_TIMEOUT_MAX)
ModbusStateRXD = MODBUS_RXD_FINISH;
}
}
/**
* @brief Modbus帧数据长度校验
* @param pData 接收缓冲区地址
* @param Len 数据长度
* @return 校验结果
* @retval MODBUS_OK:校验成功
* @retval MODBUS_ERR:校验失败
*/
static ModbusState ModbusRTU_FrameLenCheck(uint8_t *pData, uint8_t Len)
{
uint16_t lenData = 0;
switch (pData[1])
{
case MODBUS_FUNCTION_03: // 读多路保存寄存器数据
lenData = (uint16_t)(pData[4] << 8) | pData[5];
if (lenData > MODBUSRTU_REG_NUM_MAX) // 长度超出范围
return MODBUS_ERR;
if (Len != 8)
return MODBUS_ERR;
return MODBUS_OK;
break;
case MODBUS_FUNCTION_06: // 写单路寄存器
if (Len != 8) // 写单路寄存器指令,数据长度必须为8
return MODBUS_ERR;
else
return MODBUS_OK;
break;
case MODBUS_FUNCTION_10: // 写多个寄存器数据
lenData = (uint16_t)(pData[4] << 8) | pData[5];
if (lenData > MODBUSRTU_REG_NUM_MAX || Len != (pData[6] + 9) || ((lenData << 1) != pData[6]))
return MODBUS_ERR;
else
return MODBUS_OK;
break;
default:
return MODBUS_ERR;
break;
}
}
/**
* @brief 发送缓冲区初始化
* @param None
*/
static void ModbusRTU_TxDataInit(void)
{
ModbusRTUBuffTXD.DataLen = 0;
}
/**
* @brief 向发送缓冲区写入数据(但需要向主机发送数据时,不用考虑发送校验码操作)
* @param Data 数据
* @return 状态
* @retval MODBUS_ERR:发送缓冲区不足
* @retval MODBUS_OK:操作成功
*/
static ModbusState ModbusRTU_TxDataPush(uint8_t Data)
{
if (ModbusRTUBuffTXD.DataLen >= (MODBUSRTU_BUFF_MAX - 2))
return MODBUS_ERR;
ModbusRTUBuffTXD.DataBuff[ModbusRTUBuffTXD.DataLen] = Data;
ModbusRTUBuffTXD.DataLen++;
return MODBUS_OK;
}
/**
* @brief 数据发送,该函数将自动计算发送缓冲区数据的校验码(crc16校验,并将校验码数据写入到发送缓冲并进行发送)
* @param None
*/
static void ModbusRTU_SendData(void)
{
if (ModbusRTUBuffTXD.DataLen) {
// 计算校验码
uint16_t crc16Check = CRC_16((uint8_t *)&ModbusRTUBuffTXD.DataBuff[0], ModbusRTUBuffTXD.DataLen, 0xFFFF);
ModbusRTUBuffTXD.DataBuff[ModbusRTUBuffTXD.DataLen + 1] = (uint8_t)(crc16Check >> 8);
ModbusRTUBuffTXD.DataBuff[ModbusRTUBuffTXD.DataLen] = (uint8_t)(crc16Check & 0xFF);
ModbusRTUBuffTXD.DataLen += 2;
// 数据发送
// for (uint8_t index = 0;index < ModbusRTUBuffTXD.DataLen;index++) {
// }
API_UartSendData((uint8_t *)&ModbusRTUBuffTXD.DataBuff[0], ModbusRTUBuffTXD.DataLen);
// 数据发送完成,清除发送缓冲区
ModbusRTU_TxDataInit();
}
}
/**
* @brief 向主机发送错误信息
* @param Funtion 功能码
* @param Error 错误信息
*/
static void ModbusRTU_SendError(ModbusFunction Funtion, ModbusError Error)
{
ModbusRTU_TxDataInit();
ModbusRTU_TxDataPush(MODBUSRTU_ADDER); // 写入Modbus从机地址
ModbusRTU_TxDataPush((Funtion + 0x80));
ModbusRTU_TxDataPush(Error);
}
/**
* @brief Modbus初始化
* @param
*/
void ModbusRTU_Init(void)
{
}
/**
* @brief Modbus读寄存器指令,对应03指令
* @param pData 接收缓冲区数据
*/
static void ModbusRTU_ReadRegFunction(const uint8_t *pData)
{
// 03命令指令
uint16_t adderStart = (uint16_t)(pData[2] << 8) + (uint16_t)pData[3]; // 寄存器起始地址
uint16_t lenData = (uint16_t)(pData[4] << 8) + (uint16_t)pData[5]; // 需要读取的寄存器个数
ModbusRTU_TxDataPush(MODBUSRTU_ADDER); // 回复从机地址
ModbusRTU_TxDataPush(pData[1]); // 回复功能码 03
ModbusRTU_TxDataPush((lenData << 1)); // 待回复的数据长度
for (uint16_t index = 0;index < lenData;index++) {
uint16_t regData = 0;
if (ReadRegMpa((adderStart + index), ®Data)) {
ModbusRTU_TxDataPush((regData >> 8)); // 将数据放入发送缓冲区,高位优先
ModbusRTU_TxDataPush((regData & 0xFF));
}
else { // 非法访问
ModbusRTU_SendError((ModbusFunction)pData[1], MODBUS_ERR_UNADD);
return;
}
}
}
/**
* @brief 06指令:写单个寄存器指令
* @param pData 接收缓冲区数据
*/
static void ModbusRTU_WriteRegFunction(const uint8_t *pData)
{
// 06指令
uint16_t adderStart = (uint16_t)(pData[2] << 8) + (uint16_t)pData[3]; // 寄存器起始地址
uint16_t regData = (uint16_t)(pData[4] << 8) + (uint16_t)pData[5]; // 待写入的数据
// 发送的数据:从机地址、功能码、寄存器地址高位、寄存器地址低位、数据高位、数据低位、校验码高位、校验码低位
for (uint8_t index = 0;index < 6;index++) {
ModbusRTU_TxDataPush(pData[index]);
}
if (WriteRegMap(adderStart, regData)) { // 数据写入成功
return;
}
else { // 数据写入非法
ModbusRTU_SendError((ModbusFunction)pData[1], MODBUS_ERR_UNADD);
return;
}
}
/**
* @brief 10指令:写多个寄存器
* @param pData
*/
static void ModbusRTU_WriteMultiRegFunction(const uint8_t *pData)
{
// 0x10指令
uint16_t adderStart = (uint16_t)(pData[2] << 8) + (uint16_t)pData[3]; // 寄存器起始地址
uint16_t lenData = (uint16_t)(pData[4] << 8) + (uint16_t)pData[5]; // 需要读取的寄存器个数
// 发送的数据:从机地址、功能码、寄存器地址高位、寄存器地址低位、校验码高位、校验码低位
for (uint8_t index = 0;index < 6;index++) {
ModbusRTU_TxDataPush(pData[index]);
}
for (uint8_t index = 0;index < lenData;index++) {
uint16_t regData = (uint16_t)(pData[7 + index * 2] << 8) + (uint16_t)pData[7 + index * 2 + 1]; // 待写入的数据
if (WriteRegMap(adderStart + index, regData) == 0) { // 数据写入失败
ModbusRTU_SendError((ModbusFunction)pData[1], MODBUS_ERR_UNADD);
return;
}
}
}
/**
* @brief MobusRTU协议解析
* @param None
* @return
*/
ModbusState ModbusRTU_Handle(void)
{
if (ModbusStateRXD == MODBUS_RXD_FINISH) { // 主机数据接收完成,开始解析
if (ModbusRTUBuffRXD.DataLen < 3 || ModbusRTUBuffRXD.DataBuff[0] != MODBUSRTU_ADDER) { // 数据长度异常或地址错误
ModbusRTUBuffRXD.DataLen = 0;
ModbusStateRXD = MODBUS_RXD_WAIT;
return MODBUS_ERR;
}
// Modbus数据校验码计算
uint16_t modbusRXDCrc16 = 0;
uint16_t modbusCrc16 = CRC_16((uint8_t *)&ModbusRTUBuffRXD.DataBuff[0], (ModbusRTUBuffRXD.DataLen - 2), 0xFFFF);
modbusRXDCrc16 = (uint16_t)(ModbusRTUBuffRXD.DataBuff[ModbusRTUBuffRXD.DataLen - 1] << 8) +
(uint16_t)ModbusRTUBuffRXD.DataBuff[ModbusRTUBuffRXD.DataLen - 2];
if (modbusCrc16 != modbusRXDCrc16) { // 校验失败
ModbusRTUBuffRXD.DataLen = 0;
ModbusStateRXD = MODBUS_RXD_WAIT;
return MODBUS_ERR;
}
ModbusState state = ModbusRTU_FrameLenCheck((uint8_t *)&ModbusRTUBuffRXD.DataBuff[0], ModbusRTUBuffRXD.DataLen); // 帧数据长度校验
if (state != MODBUS_OK) {
ModbusRTU_SendError((ModbusFunction)ModbusRTUBuffRXD.DataBuff[1], state); // 发送错误故障码状态
ModbusRTU_SendData(); // 数据发送
ModbusStateRXD = MODBUS_RXD_WAIT;
return MODBUS_ERR;
}
switch ((ModbusFunction)ModbusRTUBuffRXD.DataBuff[1])
{
case MODBUS_FUNCTION_03: // 读取多个寄存器
ModbusRTU_ReadRegFunction((uint8_t *)&ModbusRTUBuffRXD.DataBuff[0]);
break;
case MODBUS_FUNCTION_06: // 写单路寄存器
ModbusRTU_WriteRegFunction((uint8_t *)&ModbusRTUBuffRXD.DataBuff[0]);
break;
case MODBUS_FUNCTION_10:
ModbusRTU_WriteMultiRegFunction((uint8_t *)&ModbusRTUBuffRXD.DataBuff[0]);
break;
default: // 异常回复,不存在功能码
ModbusRTU_SendError((ModbusFunction)ModbusRTUBuffRXD.DataBuff[1], MODBUS_ERR_NUFUN);
break;
}
ModbusRTU_SendData(); // 数据发送
ModbusRTUBuffRXD.DataLen = 0; // 接收缓冲区复位
ModbusStateRXD = MODBUS_RXD_WAIT; // 等待接收状态
return MODBUS_OK;
}
else
return MODBUS_OK;
}
///以下函数需要根据实际情况编写/
uint16_t SaveData[40] = {
0x0001,
0x0002,
0x00F0,
0x00F1,
0xFF00,
0x1234,
0x4321,
0x4567,
0x9876,
0x0000
};
/**
* @brief 读寄存器操作
* @param Adder 数据地址
* @param regData 读数据指针
* @return 0:读寄存器操作失败
* 1:读寄存器操作失败成功
*/
uint8_t ReadRegMpa(uint16_t Adder, uint16_t *regData)
{
// 允许读取,返回1,
// 不允许读取,返回0
if (Adder < 10) {
*regData = SaveData[Adder];
return 1;
}
else
return 0;
}
/**
* @brief 写寄存器操作
* @param Adder 数据地址
* @param Data 待写入的数据
* @return 0:写寄存器操作失败
* 1:读寄存器操作失败成功
*/
uint8_t WriteRegMap(uint16_t Adder, uint16_t Data)
{
if (Adder < 10) {
SaveData[Adder] = Data;
return 1;
}
else
return 0;
}
ModbusRTU协议.h文件:2
#ifndef _MODBUSRTU_H_
#define _MODBUSRTU_H_
#include <stdint.h>
#define MODBUSRTU_BUFF_MAX (200) // ModbusRTU缓冲区大小
#define MODBUSRTU_ADDER (0x01) // Modbus从机地址,固定地址
#define MODBUSRTU_REG_NUM_MAX (125) // 不允许大于125
#define MODBUSRTU_TIMEOUT_MAX (20 ) // 接收超时最大值
typedef enum {
MODBUS_OK,
MODBUS_ERR,
MODBUS_RXD_WAIT, // 数据接收等待
MODBUS_RXD_ON, // 数据接收中
MODBUS_RXD_FINISH, // 数据接收完成
MODBUS_RXD_BUFF_FULL, // 缓冲溢出
}ModbusState;
typedef enum {
MODBUS_FUNCTION_03 = 0x03, // 读保持寄存器功能码
MODBUS_FUNCTION_06 = 0x06, // 写单路寄存器
MODBUS_FUNCTION_10 = 0x10, // 写多个数据
}ModbusFunction;
typedef enum {
MODBUS_ERR_NUFUN = 1, // 非法功能
MODBUS_ERR_UNADD = 2, // 非法数据地址
MODBUS_ERR_UNDATA = 3, // 非法数据指
MODBUS_ERR_FAULT = 4, // 从站设备故障
MODBUS_ERR_ENSURE = 5, // 确认
MODBUS_ERR_BUY = 7, // 从属设备忙碌
MODBUS_ERR_OECHECK = 8, // 存储奇偶性差错
MODBUS_ERR_UNPATH = 0x0A, // 不可用网关路径
MODBUS_ERR_RESFAIL = 0x0B, // 响应失败
}ModbusError;
typedef struct ModbusRTU
{
uint8_t DataLen; // 缓冲区数据长度
uint8_t DataBuff[MODBUSRTU_BUFF_MAX];
}ModbusRTUBuffStruct;
void ModbusRTU_Receive(uint8_t RxData);
void ModbusRTU_RXDCompleteJudge(void);
void ModbusRTU_Init(void);
ModbusState ModbusRTU_Handle(void);
#endif
2.关于单片机接收上位机完整的一帧ModbusRTU数据
(1) 单片机具备UART空闲中断(STM32),使用空闲中断确定一帧数据接收完成;
(2) 单片机无空闲中断,使用定时实现UART空闲判断。需注意空闲时间设置,上述代码使用定时器实现一帧数据接收完成的判断。
3.关于ModbusRTU测试
主机软件:Modbus Poll;
从机软件:Modbus Slave;
使用教程与下载链接: