|
|
@@ -0,0 +1,666 @@ |
|
|
|
|
|
#include "modbus_rtu_slave.h" |
|
|
|
|
|
|
|
|
|
|
|
#include <string.h> |
|
|
|
|
|
|
|
|
|
|
|
/* Modbus RTU最大ADU为256字节。 */ |
|
|
|
|
|
#define MODBUS_RTU_ADU_SIZE_MAX 256U |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
* 当前串口为115200 bit/s。Modbus规范建议波特率高于19200时, |
|
|
|
|
|
* 帧间隔固定使用1.75ms,这里向上取整为2ms。 |
|
|
|
|
|
*/ |
|
|
|
|
|
#define MODBUS_RTU_FRAME_GAP_MS 2U |
|
|
|
|
|
|
|
|
|
|
|
#define MODBUS_BROADCAST_ADDRESS 0U |
|
|
|
|
|
|
|
|
|
|
|
#define MODBUS_FC_READ_COILS 0x01U |
|
|
|
|
|
#define MODBUS_FC_READ_HOLDING_REGS 0x03U |
|
|
|
|
|
#define MODBUS_FC_WRITE_SINGLE_REG 0x06U |
|
|
|
|
|
#define MODBUS_FC_WRITE_MULTIPLE_COILS 0x0FU |
|
|
|
|
|
#define MODBUS_FC_WRITE_MULTIPLE_REGS 0x10U |
|
|
|
|
|
|
|
|
|
|
|
#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 |
|
|
|
|
|
|
|
|
|
|
|
static UART_HandleTypeDef *s_uart; |
|
|
|
|
|
static uint8_t s_slave_address; |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
* DMA缓冲区只由DMA写入;帧缓冲区在接收回调中完成一次快照, |
|
|
|
|
|
* 随后由任务解析,避免DMA重启后覆盖尚未处理的数据。 |
|
|
|
|
|
*/ |
|
|
|
|
|
static uint8_t s_rx_dma_buffer[MODBUS_RTU_ADU_SIZE_MAX]; |
|
|
|
|
|
static uint8_t s_rx_frame[MODBUS_RTU_ADU_SIZE_MAX]; |
|
|
|
|
|
static uint8_t s_tx_frame[MODBUS_RTU_ADU_SIZE_MAX]; |
|
|
|
|
|
|
|
|
|
|
|
static volatile uint16_t s_rx_frame_length; |
|
|
|
|
|
static volatile uint8_t s_rx_frame_ready; |
|
|
|
|
|
static volatile uint8_t s_tx_busy; |
|
|
|
|
|
static volatile uint32_t s_rx_event_tick; |
|
|
|
|
|
static volatile uint32_t s_last_valid_frame_tick; |
|
|
|
|
|
static volatile uint8_t s_has_received_valid_frame; |
|
|
|
|
|
|
|
|
|
|
|
/* 10000个保持寄存器占20KB;10000个线圈按位存储,占1250字节。 */ |
|
|
|
|
|
static uint16_t s_holding_registers[MODBUS_MAP_ITEM_COUNT]; |
|
|
|
|
|
static uint8_t s_coils[(MODBUS_MAP_ITEM_COUNT + 7U) / 8U]; |
|
|
|
|
|
|
|
|
|
|
|
volatile ModbusSlaveStats g_modbus_slave_stats; |
|
|
|
|
|
|
|
|
|
|
|
static uint16_t Modbus_Crc16(const uint8_t *data, uint16_t length) |
|
|
|
|
|
{ |
|
|
|
|
|
uint16_t crc = 0xFFFFU; |
|
|
|
|
|
uint16_t index; |
|
|
|
|
|
uint8_t bit; |
|
|
|
|
|
|
|
|
|
|
|
for (index = 0U; index < length; index++) |
|
|
|
|
|
{ |
|
|
|
|
|
crc ^= data[index]; |
|
|
|
|
|
|
|
|
|
|
|
for (bit = 0U; bit < 8U; bit++) |
|
|
|
|
|
{ |
|
|
|
|
|
if ((crc & 0x0001U) != 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
crc = (uint16_t)((crc >> 1U) ^ 0xA001U); |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
crc >>= 1U; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return crc; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static uint16_t Modbus_GetU16Be(const uint8_t *data) |
|
|
|
|
|
{ |
|
|
|
|
|
return (uint16_t)(((uint16_t)data[0] << 8U) | data[1]); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static uint8_t Modbus_AddressRangeIsValid(uint16_t start, uint16_t quantity) |
|
|
|
|
|
{ |
|
|
|
|
|
if ((quantity == 0U) || (start >= MODBUS_MAP_ITEM_COUNT)) |
|
|
|
|
|
{ |
|
|
|
|
|
return 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* 先做减法再比较,避免start + quantity发生16位溢出。 */ |
|
|
|
|
|
return (quantity <= (MODBUS_MAP_ITEM_COUNT - start)) ? 1U : 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static uint8_t Modbus_CoilGetUnchecked(uint16_t address) |
|
|
|
|
|
{ |
|
|
|
|
|
uint8_t mask = (uint8_t)(1U << (address & 0x0007U)); |
|
|
|
|
|
|
|
|
|
|
|
return ((s_coils[address >> 3U] & mask) != 0U) ? 1U : 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void Modbus_CoilSetUnchecked(uint16_t address, uint8_t state) |
|
|
|
|
|
{ |
|
|
|
|
|
uint8_t mask = (uint8_t)(1U << (address & 0x0007U)); |
|
|
|
|
|
|
|
|
|
|
|
if (state != 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
s_coils[address >> 3U] |= mask; |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
s_coils[address >> 3U] &= (uint8_t)(~mask); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static HAL_StatusTypeDef Modbus_StartReceive(void) |
|
|
|
|
|
{ |
|
|
|
|
|
HAL_StatusTypeDef status; |
|
|
|
|
|
|
|
|
|
|
|
if ((s_uart == NULL) || (s_tx_busy != 0U)) |
|
|
|
|
|
{ |
|
|
|
|
|
return HAL_BUSY; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
status = HAL_UARTEx_ReceiveToIdle_DMA(s_uart, |
|
|
|
|
|
s_rx_dma_buffer, |
|
|
|
|
|
sizeof(s_rx_dma_buffer)); |
|
|
|
|
|
|
|
|
|
|
|
if ((status == HAL_OK) && (s_uart->hdmarx != NULL)) |
|
|
|
|
|
{ |
|
|
|
|
|
/* |
|
|
|
|
|
* 普通Modbus帧只应在IDLE或缓冲区满时交给应用。 |
|
|
|
|
|
* 关闭DMA半传输中断,避免长帧在一半位置被误认为完整帧。 |
|
|
|
|
|
*/ |
|
|
|
|
|
__HAL_DMA_DISABLE_IT(s_uart->hdmarx, DMA_IT_HT); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return status; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void Modbus_StopReceive(void) |
|
|
|
|
|
{ |
|
|
|
|
|
if (s_uart != NULL) |
|
|
|
|
|
{ |
|
|
|
|
|
(void)HAL_UART_AbortReceive(s_uart); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void Modbus_AppendCrc(uint8_t *frame, uint16_t payload_length) |
|
|
|
|
|
{ |
|
|
|
|
|
uint16_t crc = Modbus_Crc16(frame, payload_length); |
|
|
|
|
|
|
|
|
|
|
|
/* Modbus RTU在线路上传输CRC低字节在前、高字节在后。 */ |
|
|
|
|
|
frame[payload_length] = (uint8_t)(crc & 0x00FFU); |
|
|
|
|
|
frame[payload_length + 1U] = (uint8_t)(crc >> 8U); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static uint16_t Modbus_BuildException(uint8_t function, uint8_t exception) |
|
|
|
|
|
{ |
|
|
|
|
|
s_tx_frame[0] = s_slave_address; |
|
|
|
|
|
s_tx_frame[1] = (uint8_t)(function | 0x80U); |
|
|
|
|
|
s_tx_frame[2] = exception; |
|
|
|
|
|
Modbus_AppendCrc(s_tx_frame, 3U); |
|
|
|
|
|
|
|
|
|
|
|
return 5U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static uint16_t Modbus_ProcessReadCoils(const uint8_t *request, |
|
|
|
|
|
uint16_t request_length) |
|
|
|
|
|
{ |
|
|
|
|
|
uint16_t start; |
|
|
|
|
|
uint16_t quantity; |
|
|
|
|
|
uint16_t index; |
|
|
|
|
|
uint8_t byte_count; |
|
|
|
|
|
|
|
|
|
|
|
if (request_length != 8U) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_value_count++; |
|
|
|
|
|
return Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
start = Modbus_GetU16Be(&request[2]); |
|
|
|
|
|
quantity = Modbus_GetU16Be(&request[4]); |
|
|
|
|
|
|
|
|
|
|
|
if ((quantity == 0U) || (quantity > MODBUS_READ_COILS_MAX)) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_value_count++; |
|
|
|
|
|
return Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (Modbus_AddressRangeIsValid(start, quantity) == 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_address_count++; |
|
|
|
|
|
return Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
byte_count = (uint8_t)((quantity + 7U) / 8U); |
|
|
|
|
|
s_tx_frame[0] = s_slave_address; |
|
|
|
|
|
s_tx_frame[1] = MODBUS_FC_READ_COILS; |
|
|
|
|
|
s_tx_frame[2] = byte_count; |
|
|
|
|
|
(void)memset(&s_tx_frame[3], 0, byte_count); |
|
|
|
|
|
|
|
|
|
|
|
/* Modbus规定请求的第一个线圈放在响应数据首字节的最低位。 */ |
|
|
|
|
|
for (index = 0U; index < quantity; index++) |
|
|
|
|
|
{ |
|
|
|
|
|
if (Modbus_CoilGetUnchecked((uint16_t)(start + index)) != 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
s_tx_frame[3U + (index >> 3U)] |= |
|
|
|
|
|
(uint8_t)(1U << (index & 0x0007U)); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Modbus_AppendCrc(s_tx_frame, (uint16_t)(3U + byte_count)); |
|
|
|
|
|
return (uint16_t)(5U + byte_count); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static uint16_t Modbus_ProcessReadHolding(const uint8_t *request, |
|
|
|
|
|
uint16_t request_length) |
|
|
|
|
|
{ |
|
|
|
|
|
uint16_t start; |
|
|
|
|
|
uint16_t quantity; |
|
|
|
|
|
uint16_t index; |
|
|
|
|
|
uint16_t value; |
|
|
|
|
|
|
|
|
|
|
|
if (request_length != 8U) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_value_count++; |
|
|
|
|
|
return Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
start = Modbus_GetU16Be(&request[2]); |
|
|
|
|
|
quantity = Modbus_GetU16Be(&request[4]); |
|
|
|
|
|
|
|
|
|
|
|
if ((quantity == 0U) || (quantity > MODBUS_READ_REGS_MAX)) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_value_count++; |
|
|
|
|
|
return Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (Modbus_AddressRangeIsValid(start, quantity) == 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_address_count++; |
|
|
|
|
|
return Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
s_tx_frame[0] = s_slave_address; |
|
|
|
|
|
s_tx_frame[1] = MODBUS_FC_READ_HOLDING_REGS; |
|
|
|
|
|
s_tx_frame[2] = (uint8_t)(quantity * 2U); |
|
|
|
|
|
|
|
|
|
|
|
for (index = 0U; index < quantity; index++) |
|
|
|
|
|
{ |
|
|
|
|
|
value = s_holding_registers[start + index]; |
|
|
|
|
|
s_tx_frame[3U + index * 2U] = (uint8_t)(value >> 8U); |
|
|
|
|
|
s_tx_frame[4U + index * 2U] = (uint8_t)(value & 0x00FFU); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Modbus_AppendCrc(s_tx_frame, (uint16_t)(3U + quantity * 2U)); |
|
|
|
|
|
return (uint16_t)(5U + quantity * 2U); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static uint16_t Modbus_ProcessWriteSingleRegister(const uint8_t *request, |
|
|
|
|
|
uint16_t request_length, |
|
|
|
|
|
uint8_t is_broadcast) |
|
|
|
|
|
{ |
|
|
|
|
|
uint16_t address; |
|
|
|
|
|
uint16_t value; |
|
|
|
|
|
|
|
|
|
|
|
if (request_length != 8U) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_value_count++; |
|
|
|
|
|
return (is_broadcast != 0U) ? 0U : |
|
|
|
|
|
Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
address = Modbus_GetU16Be(&request[2]); |
|
|
|
|
|
value = Modbus_GetU16Be(&request[4]); |
|
|
|
|
|
|
|
|
|
|
|
if (address >= MODBUS_MAP_ITEM_COUNT) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_address_count++; |
|
|
|
|
|
return (is_broadcast != 0U) ? 0U : |
|
|
|
|
|
Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
s_holding_registers[address] = value; |
|
|
|
|
|
|
|
|
|
|
|
if (is_broadcast != 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
return 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* 0x06的正常响应必须原样回显请求的前6个字节。 */ |
|
|
|
|
|
(void)memcpy(s_tx_frame, request, 6U); |
|
|
|
|
|
Modbus_AppendCrc(s_tx_frame, 6U); |
|
|
|
|
|
return 8U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static uint16_t Modbus_ProcessWriteMultipleCoils(const uint8_t *request, |
|
|
|
|
|
uint16_t request_length, |
|
|
|
|
|
uint8_t is_broadcast) |
|
|
|
|
|
{ |
|
|
|
|
|
uint16_t start; |
|
|
|
|
|
uint16_t quantity; |
|
|
|
|
|
uint16_t index; |
|
|
|
|
|
uint8_t byte_count; |
|
|
|
|
|
uint8_t expected_byte_count; |
|
|
|
|
|
|
|
|
|
|
|
if (request_length < 9U) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_value_count++; |
|
|
|
|
|
return (is_broadcast != 0U) ? 0U : |
|
|
|
|
|
Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
start = Modbus_GetU16Be(&request[2]); |
|
|
|
|
|
quantity = Modbus_GetU16Be(&request[4]); |
|
|
|
|
|
byte_count = request[6]; |
|
|
|
|
|
expected_byte_count = (uint8_t)((quantity + 7U) / 8U); |
|
|
|
|
|
|
|
|
|
|
|
if ((quantity == 0U) || |
|
|
|
|
|
(quantity > MODBUS_WRITE_COILS_MAX) || |
|
|
|
|
|
(byte_count != expected_byte_count) || |
|
|
|
|
|
(request_length != (uint16_t)(9U + byte_count))) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_value_count++; |
|
|
|
|
|
return (is_broadcast != 0U) ? 0U : |
|
|
|
|
|
Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (Modbus_AddressRangeIsValid(start, quantity) == 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_address_count++; |
|
|
|
|
|
return (is_broadcast != 0U) ? 0U : |
|
|
|
|
|
Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (index = 0U; index < quantity; index++) |
|
|
|
|
|
{ |
|
|
|
|
|
Modbus_CoilSetUnchecked((uint16_t)(start + index), |
|
|
|
|
|
(uint8_t)((request[7U + (index >> 3U)] >> (index & 0x0007U)) & 0x01U)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (is_broadcast != 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
return 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
s_tx_frame[0] = s_slave_address; |
|
|
|
|
|
s_tx_frame[1] = MODBUS_FC_WRITE_MULTIPLE_COILS; |
|
|
|
|
|
(void)memcpy(&s_tx_frame[2], &request[2], 4U); |
|
|
|
|
|
Modbus_AppendCrc(s_tx_frame, 6U); |
|
|
|
|
|
return 8U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static uint16_t Modbus_ProcessWriteMultipleRegisters(const uint8_t *request, |
|
|
|
|
|
uint16_t request_length, |
|
|
|
|
|
uint8_t is_broadcast) |
|
|
|
|
|
{ |
|
|
|
|
|
uint16_t start; |
|
|
|
|
|
uint16_t quantity; |
|
|
|
|
|
uint16_t index; |
|
|
|
|
|
uint8_t byte_count; |
|
|
|
|
|
|
|
|
|
|
|
if (request_length < 9U) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_value_count++; |
|
|
|
|
|
return (is_broadcast != 0U) ? 0U : |
|
|
|
|
|
Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
start = Modbus_GetU16Be(&request[2]); |
|
|
|
|
|
quantity = Modbus_GetU16Be(&request[4]); |
|
|
|
|
|
byte_count = request[6]; |
|
|
|
|
|
|
|
|
|
|
|
if ((quantity == 0U) || |
|
|
|
|
|
(quantity > MODBUS_WRITE_REGS_MAX) || |
|
|
|
|
|
(byte_count != (uint8_t)(quantity * 2U)) || |
|
|
|
|
|
(request_length != (uint16_t)(9U + byte_count))) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_value_count++; |
|
|
|
|
|
return (is_broadcast != 0U) ? 0U : |
|
|
|
|
|
Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (Modbus_AddressRangeIsValid(start, quantity) == 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.illegal_address_count++; |
|
|
|
|
|
return (is_broadcast != 0U) ? 0U : |
|
|
|
|
|
Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (index = 0U; index < quantity; index++) |
|
|
|
|
|
{ |
|
|
|
|
|
s_holding_registers[start + index] = |
|
|
|
|
|
Modbus_GetU16Be(&request[7U + index * 2U]); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (is_broadcast != 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
return 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
s_tx_frame[0] = s_slave_address; |
|
|
|
|
|
s_tx_frame[1] = MODBUS_FC_WRITE_MULTIPLE_REGS; |
|
|
|
|
|
(void)memcpy(&s_tx_frame[2], &request[2], 4U); |
|
|
|
|
|
Modbus_AppendCrc(s_tx_frame, 6U); |
|
|
|
|
|
return 8U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static uint16_t Modbus_ProcessRequest(const uint8_t *request, |
|
|
|
|
|
uint16_t request_length) |
|
|
|
|
|
{ |
|
|
|
|
|
uint16_t calculated_crc; |
|
|
|
|
|
uint16_t received_crc; |
|
|
|
|
|
uint8_t is_broadcast; |
|
|
|
|
|
|
|
|
|
|
|
if (request_length < 4U) |
|
|
|
|
|
{ |
|
|
|
|
|
return 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
calculated_crc = Modbus_Crc16(request, (uint16_t)(request_length - 2U)); |
|
|
|
|
|
received_crc = (uint16_t)(request[request_length - 2U] | |
|
|
|
|
|
((uint16_t)request[request_length - 1U] << 8U)); |
|
|
|
|
|
|
|
|
|
|
|
if (calculated_crc != received_crc) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.crc_error_count++; |
|
|
|
|
|
return 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ((request[0] != s_slave_address) && |
|
|
|
|
|
(request[0] != MODBUS_BROADCAST_ADDRESS)) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.ignored_address_count++; |
|
|
|
|
|
return 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
is_broadcast = (request[0] == MODBUS_BROADCAST_ADDRESS) ? 1U : 0U; |
|
|
|
|
|
g_modbus_slave_stats.valid_frame_count++; |
|
|
|
|
|
s_last_valid_frame_tick = HAL_GetTick(); |
|
|
|
|
|
s_has_received_valid_frame = 1U; |
|
|
|
|
|
|
|
|
|
|
|
switch (request[1]) |
|
|
|
|
|
{ |
|
|
|
|
|
case MODBUS_FC_READ_COILS: |
|
|
|
|
|
/* 广播请求不允许读取,从站不作响应。 */ |
|
|
|
|
|
return (is_broadcast != 0U) ? 0U : |
|
|
|
|
|
Modbus_ProcessReadCoils(request, request_length); |
|
|
|
|
|
|
|
|
|
|
|
case MODBUS_FC_READ_HOLDING_REGS: |
|
|
|
|
|
return (is_broadcast != 0U) ? 0U : |
|
|
|
|
|
Modbus_ProcessReadHolding(request, request_length); |
|
|
|
|
|
|
|
|
|
|
|
case MODBUS_FC_WRITE_SINGLE_REG: |
|
|
|
|
|
return Modbus_ProcessWriteSingleRegister(request, |
|
|
|
|
|
request_length, |
|
|
|
|
|
is_broadcast); |
|
|
|
|
|
|
|
|
|
|
|
case MODBUS_FC_WRITE_MULTIPLE_COILS: |
|
|
|
|
|
return Modbus_ProcessWriteMultipleCoils(request, |
|
|
|
|
|
request_length, |
|
|
|
|
|
is_broadcast); |
|
|
|
|
|
|
|
|
|
|
|
case MODBUS_FC_WRITE_MULTIPLE_REGS: |
|
|
|
|
|
return Modbus_ProcessWriteMultipleRegisters(request, |
|
|
|
|
|
request_length, |
|
|
|
|
|
is_broadcast); |
|
|
|
|
|
|
|
|
|
|
|
default: |
|
|
|
|
|
g_modbus_slave_stats.illegal_function_count++; |
|
|
|
|
|
return (is_broadcast != 0U) ? 0U : |
|
|
|
|
|
Modbus_BuildException(request[1], MODBUS_EX_ILLEGAL_FUNCTION); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
HAL_StatusTypeDef ModbusSlave_Init(UART_HandleTypeDef *huart, |
|
|
|
|
|
uint8_t slave_address) |
|
|
|
|
|
{ |
|
|
|
|
|
if ((huart == NULL) || |
|
|
|
|
|
(slave_address == MODBUS_BROADCAST_ADDRESS) || |
|
|
|
|
|
(slave_address > 247U)) |
|
|
|
|
|
{ |
|
|
|
|
|
return HAL_ERROR; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
s_uart = huart; |
|
|
|
|
|
s_slave_address = slave_address; |
|
|
|
|
|
s_rx_frame_length = 0U; |
|
|
|
|
|
s_rx_frame_ready = 0U; |
|
|
|
|
|
s_tx_busy = 0U; |
|
|
|
|
|
s_has_received_valid_frame = 0U; |
|
|
|
|
|
|
|
|
|
|
|
(void)memset(s_holding_registers, 0, sizeof(s_holding_registers)); |
|
|
|
|
|
(void)memset(s_coils, 0, sizeof(s_coils)); |
|
|
|
|
|
(void)memset((void *)&g_modbus_slave_stats, 0, |
|
|
|
|
|
sizeof(g_modbus_slave_stats)); |
|
|
|
|
|
|
|
|
|
|
|
s_holding_registers[HMI_REG_DEVICE_ID] = 0xF407U; |
|
|
|
|
|
|
|
|
|
|
|
return Modbus_StartReceive(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void ModbusSlave_Poll(void) |
|
|
|
|
|
{ |
|
|
|
|
|
uint16_t response_length; |
|
|
|
|
|
|
|
|
|
|
|
if ((s_rx_frame_ready == 0U) || (s_tx_busy != 0U)) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
* IDLE事件在约1个字符静默后产生;再等待到2ms, |
|
|
|
|
|
* 满足当前115200波特率下的Modbus RTU帧间隔要求。 |
|
|
|
|
|
*/ |
|
|
|
|
|
if ((HAL_GetTick() - s_rx_event_tick) < MODBUS_RTU_FRAME_GAP_MS) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
response_length = Modbus_ProcessRequest(s_rx_frame, s_rx_frame_length); |
|
|
|
|
|
|
|
|
|
|
|
if (response_length > 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
/* |
|
|
|
|
|
* 发送期间停止接收,避免自动收发电路可能产生的本机回波 |
|
|
|
|
|
* 被误当成下一条主站请求。 |
|
|
|
|
|
*/ |
|
|
|
|
|
Modbus_StopReceive(); |
|
|
|
|
|
s_tx_busy = 1U; |
|
|
|
|
|
|
|
|
|
|
|
if (HAL_UART_Transmit_DMA(s_uart, s_tx_frame, response_length) == HAL_OK) |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.tx_frame_count++; |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
s_tx_busy = 0U; |
|
|
|
|
|
g_modbus_slave_stats.uart_error_count++; |
|
|
|
|
|
(void)Modbus_StartReceive(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
* 处理期间保持ready=1,使中断不会覆盖当前帧; |
|
|
|
|
|
* 到这里再释放帧槽。 |
|
|
|
|
|
*/ |
|
|
|
|
|
s_rx_frame_ready = 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void ModbusSlave_OnRxEvent(UART_HandleTypeDef *huart, uint16_t size) |
|
|
|
|
|
{ |
|
|
|
|
|
if ((huart != s_uart) || (s_uart == NULL)) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
g_modbus_slave_stats.rx_event_count++; |
|
|
|
|
|
|
|
|
|
|
|
if ((size > 0U) && (size <= MODBUS_RTU_ADU_SIZE_MAX)) |
|
|
|
|
|
{ |
|
|
|
|
|
if (s_rx_frame_ready == 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
(void)memcpy(s_rx_frame, s_rx_dma_buffer, size); |
|
|
|
|
|
s_rx_frame_length = size; |
|
|
|
|
|
s_rx_event_tick = HAL_GetTick(); |
|
|
|
|
|
s_rx_frame_ready = 1U; |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
/* 单帧槽尚未处理完成,丢弃新帧而不覆盖旧帧。 */ |
|
|
|
|
|
g_modbus_slave_stats.dropped_frame_count++; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
g_modbus_slave_stats.dropped_frame_count++; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* DMA普通模式在IDLE事件后已经停止,需要显式重新启动。 */ |
|
|
|
|
|
if (s_tx_busy == 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
(void)Modbus_StartReceive(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void ModbusSlave_OnTxComplete(UART_HandleTypeDef *huart) |
|
|
|
|
|
{ |
|
|
|
|
|
if ((huart != s_uart) || (s_uart == NULL)) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
s_tx_busy = 0U; |
|
|
|
|
|
(void)Modbus_StartReceive(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void ModbusSlave_OnUartError(UART_HandleTypeDef *huart) |
|
|
|
|
|
{ |
|
|
|
|
|
if ((huart != s_uart) || (s_uart == NULL)) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
g_modbus_slave_stats.uart_error_count++; |
|
|
|
|
|
s_tx_busy = 0U; |
|
|
|
|
|
(void)HAL_UART_Abort(huart); |
|
|
|
|
|
(void)Modbus_StartReceive(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
uint8_t ModbusSlave_SetHoldingRegister(uint16_t address, uint16_t value) |
|
|
|
|
|
{ |
|
|
|
|
|
if (address >= MODBUS_MAP_ITEM_COUNT) |
|
|
|
|
|
{ |
|
|
|
|
|
return 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
s_holding_registers[address] = value; |
|
|
|
|
|
return 1U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
uint8_t ModbusSlave_GetHoldingRegister(uint16_t address, uint16_t *value) |
|
|
|
|
|
{ |
|
|
|
|
|
if ((address >= MODBUS_MAP_ITEM_COUNT) || (value == NULL)) |
|
|
|
|
|
{ |
|
|
|
|
|
return 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
*value = s_holding_registers[address]; |
|
|
|
|
|
return 1U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
uint8_t ModbusSlave_SetCoil(uint16_t address, uint8_t state) |
|
|
|
|
|
{ |
|
|
|
|
|
if (address >= MODBUS_MAP_ITEM_COUNT) |
|
|
|
|
|
{ |
|
|
|
|
|
return 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Modbus_CoilSetUnchecked(address, state); |
|
|
|
|
|
return 1U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
uint8_t ModbusSlave_GetCoil(uint16_t address, uint8_t *state) |
|
|
|
|
|
{ |
|
|
|
|
|
if ((address >= MODBUS_MAP_ITEM_COUNT) || (state == NULL)) |
|
|
|
|
|
{ |
|
|
|
|
|
return 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
*state = Modbus_CoilGetUnchecked(address); |
|
|
|
|
|
return 1U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
uint8_t ModbusSlave_IsConnected(uint32_t timeout_ms) |
|
|
|
|
|
{ |
|
|
|
|
|
if (s_has_received_valid_frame == 0U) |
|
|
|
|
|
{ |
|
|
|
|
|
return 0U; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return ((HAL_GetTick() - s_last_valid_frame_tick) <= timeout_ms) ? 1U : 0U; |
|
|
|
|
|
} |