|
|
|
@@ -1,33 +1,22 @@ |
|
|
|
/** |
|
|
|
* @file modbus_rtu_slave.c |
|
|
|
* @brief Modbus RTU 从站协议处理模块。 |
|
|
|
*/ |
|
|
|
|
|
|
|
#include "modbus_rtu_slave.h" |
|
|
|
|
|
|
|
#include <string.h> |
|
|
|
|
|
|
|
/** @brief Modbus RTU 最大 ADU 长度,单位为字节。 */ |
|
|
|
#define MODBUS_RTU_ADU_SIZE_MAX (256U) |
|
|
|
#define MODBUS_RTU_ADU_SIZE_MAX (256U)//Modbus RTU 最大 ADU 长度,单位为字节 |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* @brief 当前通信参数下使用的 RTU 帧间隔。 |
|
|
|
* |
|
|
|
* 当前波特率为 115200 bit/s。Modbus 规范建议波特率高于 |
|
|
|
* 19200 bit/s 时采用固定 1.75 ms 帧间隔,此处向上取整为 2 ms。 |
|
|
|
*/ |
|
|
|
#define MODBUS_RTU_FRAME_GAP_MS (2U) |
|
|
|
|
|
|
|
#define MODBUS_BROADCAST_ADDRESS (0U) |
|
|
|
#define MODBUS_BROADCAST_ADDRESS (0U)//广播地址 |
|
|
|
|
|
|
|
#define MODBUS_EX_ILLEGAL_FUNCTION (0x01U) |
|
|
|
#define MODBUS_EX_ILLEGAL_ADDRESS (0x02U) |
|
|
|
#define MODBUS_EX_ILLEGAL_VALUE (0x03U) |
|
|
|
#define MODBUS_EX_ILLEGAL_FUNCTION (0x01U)//非法功能码的异常码 |
|
|
|
#define MODBUS_EX_ILLEGAL_ADDRESS (0x02U)//非法地址的异常码 |
|
|
|
#define MODBUS_EX_ILLEGAL_VALUE (0x03U)//非法数据的异常码 |
|
|
|
|
|
|
|
#define MODBUS_READ_COILS_MAX (2000U) |
|
|
|
#define MODBUS_READ_REGS_MAX (125U) |
|
|
|
#define MODBUS_WRITE_COILS_MAX (1968U) |
|
|
|
#define MODBUS_WRITE_REGS_MAX (123U) |
|
|
|
#define MODBUS_READ_COILS_MAX (2000U)//一次ADU最大线圈读取数量 |
|
|
|
#define MODBUS_READ_REGS_MAX (125U) //一次ADU最大寄存器读取数量 |
|
|
|
#define MODBUS_WRITE_COILS_MAX (1968U)//一次ADU最大线圈写入数量 |
|
|
|
#define MODBUS_WRITE_REGS_MAX (123U) //一次ADU最大寄存器写入数量 |
|
|
|
|
|
|
|
#define MODBUS_COIL_VALUE_ON (0xFF00U) |
|
|
|
#define MODBUS_COIL_VALUE_OFF (0x0000U) |
|
|
|
@@ -37,12 +26,13 @@ static uint8_t ModbusSlaveAddress; |
|
|
|
|
|
|
|
/** |
|
|
|
* DMA 缓冲区只由 DMA 写入;帧缓冲区在接收回调中完成一次快照, |
|
|
|
* 随后由任务解析,避免 DMA 重启后覆盖尚未处理的数据。 |
|
|
|
* 随后由任务解析,避免 DMA 重启后覆盖尚未处理的数据 |
|
|
|
*/ |
|
|
|
static uint8_t ModbusRxDmaBuffer[MODBUS_RTU_ADU_SIZE_MAX]; |
|
|
|
static uint8_t ModbusRxFrame[MODBUS_RTU_ADU_SIZE_MAX]; |
|
|
|
static uint8_t ModbusTxFrame[MODBUS_RTU_ADU_SIZE_MAX]; |
|
|
|
|
|
|
|
/* D100~D120 上一次已保存的值,用于检测数据是否变化 */ |
|
|
|
static uint16_t ModbusRetainedSnapshot[MODBUS_RETAINED_D_COUNT]; |
|
|
|
static volatile uint16_t ModbusRxFrameLength; |
|
|
|
static volatile uint8_t ModbusRxFrameReady; |
|
|
|
static volatile uint8_t ModbusTxBusy; |
|
|
|
@@ -52,18 +42,19 @@ static volatile uint8_t ModbusHasReceivedValidFrame; |
|
|
|
|
|
|
|
/** |
|
|
|
* 10000 个保持寄存器占用 20000 字节;10000 个线圈按位存储, |
|
|
|
* 占用 1250 字节。 |
|
|
|
* 占用 1250 字节 |
|
|
|
*/ |
|
|
|
static uint16_t ModbusHoldingRegisters[MODBUS_MAP_ITEM_COUNT]; |
|
|
|
|
|
|
|
static uint8_t ModbusCoils[(MODBUS_MAP_ITEM_COUNT + 7U) / 8U]; |
|
|
|
|
|
|
|
volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics; |
|
|
|
|
|
|
|
/** |
|
|
|
* @brief 计算 Modbus RTU CRC16 校验值。 |
|
|
|
* @param[in] data 待校验数据。 |
|
|
|
* @param[in] length 待校验数据长度。 |
|
|
|
* @return CRC16 校验值。 |
|
|
|
* @brief 计算 Modbus RTU CRC16 校验值 |
|
|
|
* @param[in] data 待校验数据 |
|
|
|
* @param[in] length 待校验数据长度 |
|
|
|
* @return CRC16 校验值 |
|
|
|
*/ |
|
|
|
static uint16_t ModbusCrc16(const uint8_t *data, uint16_t length) |
|
|
|
{ |
|
|
|
@@ -92,9 +83,9 @@ static uint16_t ModbusCrc16(const uint8_t *data, uint16_t length) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @brief 从网络字节序数据中读取一个 16 位无符号整数。 |
|
|
|
* @param[in] data 至少包含两个字节的数据地址。 |
|
|
|
* @return 转换后的 16 位无符号整数。 |
|
|
|
* @brief 提取一个 16 位无符号整数 |
|
|
|
* @param[in] data 两个字节的数据地址 |
|
|
|
* @return 转换后的 16 位无符号整数 |
|
|
|
*/ |
|
|
|
static uint16_t ModbusGetU16Be(const uint8_t *data) |
|
|
|
{ |
|
|
|
@@ -105,17 +96,15 @@ static uint16_t ModbusGetU16Be(const uint8_t *data) |
|
|
|
* @brief 检查连续数据地址范围是否合法。 |
|
|
|
* @param[in] start 起始地址。 |
|
|
|
* @param[in] quantity 数据项数量。 |
|
|
|
* @retval 1 地址范围合法。 |
|
|
|
* @retval 0 地址范围非法。 |
|
|
|
*/ |
|
|
|
static uint8_t ModbusAddressRangeIsValid(uint16_t start, uint16_t quantity) |
|
|
|
{ |
|
|
|
if ((quantity == 0U) || (start >= MODBUS_MAP_ITEM_COUNT)) |
|
|
|
if (start >= MODBUS_MAP_ITEM_COUNT) |
|
|
|
{ |
|
|
|
return 0U; |
|
|
|
} |
|
|
|
|
|
|
|
/* 先做减法再比较,避免 start + quantity 发生 16 位溢出。 */ |
|
|
|
// 先做减法再比较,避免溢出 |
|
|
|
return (quantity <= (MODBUS_MAP_ITEM_COUNT - start)) ? 1U : 0U; |
|
|
|
} |
|
|
|
|
|
|
|
@@ -214,14 +203,14 @@ static uint16_t ModbusBuildException(uint8_t function, uint8_t exception) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @brief 处理读线圈功能码 0x01。 |
|
|
|
* @param[in] request RTU 请求帧。 |
|
|
|
* @param[in] requestLength 请求帧长度。 |
|
|
|
* @return 待发送响应长度,异常请求返回异常响应长度。 |
|
|
|
* @brief 处理读线圈功能码 0x01 |
|
|
|
* @param[in] request RTU 请求帧 |
|
|
|
* @param[in] requestLength 请求帧长度 |
|
|
|
* @return 待发送响应长度,异常请求返回异常响应长度 |
|
|
|
*/ |
|
|
|
static uint16_t ModbusProcessReadCoils(const uint8_t *request, |
|
|
|
uint16_t requestLength) |
|
|
|
static uint16_t ModbusProcessReadCoils(const uint8_t *request, uint16_t requestLength) |
|
|
|
{ |
|
|
|
|
|
|
|
uint16_t start; |
|
|
|
uint16_t quantity; |
|
|
|
uint16_t index; |
|
|
|
@@ -236,13 +225,13 @@ static uint16_t ModbusProcessReadCoils(const uint8_t *request, |
|
|
|
start = ModbusGetU16Be(&request[2]); |
|
|
|
quantity = ModbusGetU16Be(&request[4]); |
|
|
|
|
|
|
|
if ((quantity == 0U) || (quantity > MODBUS_READ_COILS_MAX)) |
|
|
|
if ((quantity == 0U) || (quantity > MODBUS_READ_COILS_MAX))//数量0或者数量大于最大值 |
|
|
|
{ |
|
|
|
ModbusSlaveStatistics.illegalValueCount++; |
|
|
|
return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); |
|
|
|
} |
|
|
|
|
|
|
|
if (ModbusAddressRangeIsValid(start, quantity) == 0U) |
|
|
|
if (ModbusAddressRangeIsValid(start, quantity) == 0U)//地址检查 |
|
|
|
{ |
|
|
|
ModbusSlaveStatistics.illegalAddressCount++; |
|
|
|
return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); |
|
|
|
@@ -254,13 +243,12 @@ static uint16_t ModbusProcessReadCoils(const uint8_t *request, |
|
|
|
ModbusTxFrame[2] = byteCount; |
|
|
|
(void)memset(&ModbusTxFrame[3], 0, byteCount); |
|
|
|
|
|
|
|
/* Modbus 规定第一个线圈放在响应数据首字节的最低位。 */ |
|
|
|
for (index = 0U; index < quantity; index++) |
|
|
|
{ |
|
|
|
if (ModbusCoilGetUnchecked((uint16_t)(start + index)) != 0U) |
|
|
|
{ |
|
|
|
ModbusTxFrame[3U + (index >> 3U)] |= |
|
|
|
(uint8_t)(1U << (index & 0x0007U)); |
|
|
|
ModbusTxFrame[3U + (index >> 3U)] |= (uint8_t)(1U << (index & 0x0007U)); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@@ -269,13 +257,13 @@ static uint16_t ModbusProcessReadCoils(const uint8_t *request, |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @brief 处理读保持寄存器功能码 0x03。 |
|
|
|
* @param[in] request RTU 请求帧。 |
|
|
|
* @param[in] requestLength 请求帧长度。 |
|
|
|
* @return 待发送响应长度,异常请求返回异常响应长度。 |
|
|
|
* @brief 处理读保持寄存器功能码 0x03 |
|
|
|
* @param[in] request RTU 请求帧 |
|
|
|
* @param[in] requestLength 请求帧长度 |
|
|
|
* @return 待发送响应长度,异常请求返回异常响应长度 |
|
|
|
*/ |
|
|
|
static uint16_t ModbusProcessReadHolding(const uint8_t *request, |
|
|
|
uint16_t requestLength) |
|
|
|
static uint16_t ModbusProcessReadHolding(const uint8_t *request,uint16_t requestLength) |
|
|
|
|
|
|
|
{ |
|
|
|
uint16_t start; |
|
|
|
uint16_t quantity; |
|
|
|
@@ -291,13 +279,13 @@ static uint16_t ModbusProcessReadHolding(const uint8_t *request, |
|
|
|
start = ModbusGetU16Be(&request[2]); |
|
|
|
quantity = ModbusGetU16Be(&request[4]); |
|
|
|
|
|
|
|
if ((quantity == 0U) || (quantity > MODBUS_READ_REGS_MAX)) |
|
|
|
if ((quantity == 0U) || (quantity > MODBUS_READ_REGS_MAX))//数量0或者数量大于最大值 |
|
|
|
{ |
|
|
|
ModbusSlaveStatistics.illegalValueCount++; |
|
|
|
return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); |
|
|
|
} |
|
|
|
|
|
|
|
if (ModbusAddressRangeIsValid(start, quantity) == 0U) |
|
|
|
if (ModbusAddressRangeIsValid(start, quantity) == 0U)//地址检查 |
|
|
|
{ |
|
|
|
ModbusSlaveStatistics.illegalAddressCount++; |
|
|
|
return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); |
|
|
|
@@ -560,8 +548,7 @@ static uint16_t ModbusProcessWriteMultipleRegisters( |
|
|
|
* @param[in] requestLength 请求帧长度。 |
|
|
|
* @return 待发送响应长度;无需响应时返回 0。 |
|
|
|
*/ |
|
|
|
static uint16_t ModbusProcessRequest(const uint8_t *request, |
|
|
|
uint16_t requestLength) |
|
|
|
static uint16_t ModbusProcessRequest(const uint8_t *request,uint16_t requestLength) |
|
|
|
{ |
|
|
|
uint16_t calculatedCrc; |
|
|
|
uint16_t receivedCrc; |
|
|
|
@@ -577,20 +564,20 @@ static uint16_t ModbusProcessRequest(const uint8_t *request, |
|
|
|
(uint16_t)(request[requestLength - 2U] |
|
|
|
| ((uint16_t)request[requestLength - 1U] << 8U)); |
|
|
|
|
|
|
|
if (calculatedCrc != receivedCrc) |
|
|
|
if (calculatedCrc != receivedCrc)//CRC校验 |
|
|
|
{ |
|
|
|
ModbusSlaveStatistics.crcErrorCount++; |
|
|
|
return 0U; |
|
|
|
} |
|
|
|
|
|
|
|
if ((request[0] != ModbusSlaveAddress) |
|
|
|
&& (request[0] != MODBUS_BROADCAST_ADDRESS)) |
|
|
|
&& (request[0] != MODBUS_BROADCAST_ADDRESS))//非法从站地址 |
|
|
|
{ |
|
|
|
ModbusSlaveStatistics.ignoredAddressCount++; |
|
|
|
return 0U; |
|
|
|
} |
|
|
|
|
|
|
|
isBroadcast = (request[0] == MODBUS_BROADCAST_ADDRESS) ? 1U : 0U; |
|
|
|
isBroadcast = (request[0] == MODBUS_BROADCAST_ADDRESS) ? 1U : 0U;//是否广播请求 |
|
|
|
ModbusSlaveStatistics.validFrameCount++; |
|
|
|
ModbusLastValidFrameTick = HAL_GetTick(); |
|
|
|
ModbusHasReceivedValidFrame = 1U; |
|
|
|
@@ -625,7 +612,7 @@ static uint16_t ModbusProcessRequest(const uint8_t *request, |
|
|
|
return ModbusProcessWriteMultipleRegisters(request, |
|
|
|
requestLength, |
|
|
|
isBroadcast); |
|
|
|
|
|
|
|
|
|
|
|
default://未知功能码 |
|
|
|
ModbusSlaveStatistics.illegalFunctionCount++; |
|
|
|
return (isBroadcast != 0U) ? 0U : |
|
|
|
@@ -794,3 +781,54 @@ uint8_t ModbusSlaveIsConnected(uint32_t timeoutMs) |
|
|
|
? 1U |
|
|
|
: 0U; |
|
|
|
} |
|
|
|
|
|
|
|
//上电恢复函数 |
|
|
|
void ModbusRetainedRegistersLoad(void) |
|
|
|
{ |
|
|
|
uint16_t index; |
|
|
|
|
|
|
|
if (backupData->magic == MODBUS_BACKUP_MAGIC) |
|
|
|
{ |
|
|
|
for (index = 0U; index < MODBUS_RETAINED_D_COUNT;index++) |
|
|
|
{ |
|
|
|
ModbusHoldingRegisters[MODBUS_RETAINED_D_START + index] = |
|
|
|
backupData->retainedD[index]; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
for (index = 0U;index < MODBUS_RETAINED_D_COUNT;index++) |
|
|
|
{ |
|
|
|
ModbusHoldingRegisters[ |
|
|
|
MODBUS_RETAINED_D_START + index] = 0U; |
|
|
|
|
|
|
|
backupData->retainedD[index] = 0U; |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* 数据初始化完成后最后写magic,避免初始化中途断电 |
|
|
|
* 却把不完整数据标记成有效。 |
|
|
|
*/ |
|
|
|
backupData->magic = MODBUS_BACKUP_MAGIC; |
|
|
|
} |
|
|
|
} |
|
|
|
void ModbusRetainedRegistersPoll() |
|
|
|
{ |
|
|
|
uint16_t index; |
|
|
|
uint16_t value; |
|
|
|
|
|
|
|
for(index=0;index<MODBUS_RETAINED_D_COUNT;index++) |
|
|
|
{ |
|
|
|
value=ModbusHoldingRegisters[MODBUS_RETAINED_D_START +index]; |
|
|
|
if(value != ModbusRetainedSnapshot[index]) |
|
|
|
{ |
|
|
|
backupData->retainedD[index]=value; |
|
|
|
} |
|
|
|
|
|
|
|
ModbusRetainedSnapshot[index] = value; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |