|
- #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;
- }
|