#include "modbus_rtu_slave.h" #include "modbus_data_store.h" #include #define MODBUS_RTU_ADU_SIZE_MAX (256U) // Modbus RTU 最大 ADU 长度,单位为字节 #define MODBUS_RTU_T15_US (750UL) // 高波特率下固定T1.5时间,单位us #define MODBUS_RTU_T35_US (1750UL) // 高波特率下固定T3.5时间,单位us #define MODBUS_RTU_BITS_PER_CHAR (11UL) // 8E1包含11个传输位 #define MODBUS_RTU_HIGH_BAUD_LIMIT (19200UL) // 高低波特率计算方式的分界值 #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_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) static UART_HandleTypeDef *ModbusUart; static uint8_t ModbusSlaveAddress; static HAL_StatusTypeDef ModbusStartReceive(void); /*最近一次字节尾到字节头的间隔周期数*/ static volatile uint32_t ModbusLastInterFrameGapCycles; /** * DMA 缓冲区只由 DMA 写入;帧缓冲区在接收回调中完成一次快照, * 随后由任务解析,避免 DMA 重启后覆盖尚未处理的数据 */ static uint8_t ModbusRxDmaBuffer[MODBUS_RTU_ADU_SIZE_MAX]; /* 保存被UART IDLE事件分开的DMA片段,达到T3.5后再提交解析 */ static uint8_t ModbusRxAssemblyBuffer[MODBUS_RTU_ADU_SIZE_MAX]; static uint8_t ModbusRxFrame[MODBUS_RTU_ADU_SIZE_MAX]; static uint8_t ModbusTxFrame[MODBUS_RTU_ADU_SIZE_MAX]; static uint16_t ModbusWriteRegisterScratch[MODBUS_WRITE_REGS_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; static volatile uint16_t ModbusRxAssemblyLength; // 当前拼帧长度 static volatile uint8_t ModbusRxAssemblyInvalid; // 帧内间隔超过T1.5时置1 static volatile uint32_t ModbusRxLastByteCycle; // 上一片段末字节结束时刻 static uint32_t ModbusRtuT15Cycles; // T1.5对应的CPU周期数 static uint32_t ModbusRtuT35Cycles; // T3.5对应的CPU周期数 static uint32_t ModbusRtuCharCycles; // 一个UART字符对应的CPU周期数 static volatile uint32_t ModbusLastValidFrameTick; static volatile uint8_t ModbusHasReceivedValidFrame; static volatile uint32_t ModbusReceiveRestartAttemptCount; static volatile uint32_t ModbusReceiveRestartFailureCount; static volatile uint32_t ModbusNextReceiveRetryTick; static volatile uint32_t ModbusLastUartErrorCode; static volatile uint8_t ModbusLastReceiveStartStatus = (uint8_t)HAL_ERROR; static volatile MODBUS_BACKUP_DATA *ModbusBackupData = (volatile MODBUS_BACKUP_DATA *)BKPSRAM_BASE; /* Word and bit images are owned by modbus_data_store.c. Standard Modbus * coils are the protocol view of the PLC M device space. */ volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics; /** * @brief 计算 Modbus RTU CRC16 校验值 * @param[in] data 待校验数据 * @param[in] length 待校验数据长度 * @return CRC16 校验值 */ static uint16_t ModbusCrc16(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; } /** * @brief 提取一个 16 位无符号整数 * @param[in] data 两个字节的数据地址 * @return 转换后的 16 位无符号整数 */ static uint16_t ModbusGetU16Be(const uint8_t *data) { return (uint16_t)(((uint16_t)data[0] << 8U) | data[1]); } /** * @brief 提取一个 24 位无符号整数 * @param[in] data 3个字节的数据地址 * @return 转换后的 24 位无符号整数 */ static uint32_t ModbusGetU24Be(const uint8_t *data) { return (uint32_t)(((uint32_t)data[0] << 16U) | ((uint32_t)data[1] << 8U) | (uint32_t)data[2]); } /** * @brief 检查连续数据地址范围是否合法 * @param[in] start 起始地址 * @param[in] quantity 数据项数量 */ static uint8_t ModbusAddressRangeIsValid(uint16_t start, uint16_t quantity) { if (start >= MODBUS_MAP_ITEM_COUNT) { return 0U; } // 先做减法再比较,避免溢出 return (quantity <= (MODBUS_MAP_ITEM_COUNT - start)) ? 1U : 0U; } /** * @brief 读取已经确认地址合法的线圈 * @param[in] address 线圈地址 * @return 线圈状态,取值为 0 或 1 */ static uint8_t ModbusCoilGetUnchecked(uint16_t address) { uint8_t value = 0U; (void)ModbusDataReadBit(MODBUS_BIT_DEVICE_M, address, &value); return value; } /** * @brief 设置已经确认地址合法的线圈 * @param[in] address 线圈地址 * @param[in] state 0 表示复位,非 0 表示置位 */ static void ModbusCoilSetUnchecked(uint16_t address, uint8_t state) { (void)ModbusDataWriteBit(MODBUS_BIT_DEVICE_M, address, state); } /** * @brief 初始化用于RTU帧间隔测量的DWT周期计数器 * @param[in] baudRate 当前串口波特率 */ static void ModbusRtuTimingInit(uint32_t baudRate) { uint64_t coreClock; /* 开启DWT周期计数器,CYCCNT每经过一个CPU时钟周期自动加1 */ CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0U; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; coreClock = SystemCoreClock; /* 一个字符周期数=CPU频率*每字符位数/波特率 */ ModbusRtuCharCycles = (uint32_t)((coreClock * MODBUS_RTU_BITS_PER_CHAR) / baudRate); if (baudRate > MODBUS_RTU_HIGH_BAUD_LIMIT) { /* 高波特率使用固定750us和1750us,999999用于向上取整 */ ModbusRtuT15Cycles = (uint32_t)((coreClock * MODBUS_RTU_T15_US + 999999UL) / 1000000UL); ModbusRtuT35Cycles = (uint32_t)((coreClock * MODBUS_RTU_T35_US + 999999UL) / 1000000UL); } else { /* 低波特率按照1.5个字符时间和3.5个字符时间计算 */ ModbusRtuT15Cycles = (uint32_t)(((uint64_t)ModbusRtuCharCycles * 3UL + 1UL) / 2UL); ModbusRtuT35Cycles = (uint32_t)(((uint64_t)ModbusRtuCharCycles * 7UL + 1UL) / 2UL); } } /** * @brief 结束当前RTU接收组帧 * @note 调用本函数时必须保证不会与串口接收中断并发执行 */ static void ModbusRxAssemblyFinalize(void) { if (ModbusRxAssemblyLength == 0U) { return; } /*拼帧区是否有数据*/ if ((ModbusRxAssemblyInvalid == 0U) && (ModbusRxFrameReady == 0U)) { /* 当前帧未违反T1.5且解析缓冲区空闲时提交完整帧 */ (void)memcpy(ModbusRxFrame, ModbusRxAssemblyBuffer, ModbusRxAssemblyLength); ModbusRxFrameLength = ModbusRxAssemblyLength; ModbusRxFrameReady = 1U; } else { /* T1.5无效帧或上一帧尚未处理完成时丢弃 */ ModbusSlaveStatistics.droppedFrameCount++; } ModbusRxAssemblyLength = 0U; ModbusRxAssemblyInvalid = 0U; } /** * @brief 静默时间达到T3.5后,将接收数据交给协议解析任务 */ static void ModbusTryFinalizeReceive(void) { uint32_t now; if (ModbusRxAssemblyLength == 0U) { return; } /* DMA缓冲区出现新数据时,说明串口仍在接收当前片段 */ if ((ModbusUart->hdmarx != NULL) && /*串口DMA使能*/ ((ModbusUart->Instance->CR3 & USART_CR3_DMAR) != 0U) && (__HAL_DMA_GET_COUNTER(ModbusUart->hdmarx) < MODBUS_RTU_ADU_SIZE_MAX)) { return; } now = DWT->CYCCNT; /* 从末字节结束时刻开始计算静默时间,未达到T3.5时继续等待 */ if ((uint32_t)(now - ModbusRxLastByteCycle) < ModbusRtuT35Cycles) { return; } /* 静默达到T3.5,当前RTU帧结束,发送响应前停止接收DMA */ (void)HAL_UART_AbortReceive(ModbusUart); /* HAL_UART_AbortReceive has already stopped DMA/IDLE callbacks. The * finalize routine may copy a full RTU ADU, so it must not globally mask * the 100 kHz motion interrupts around that memcpy. */ ModbusRxAssemblyFinalize(); /* 无效帧被丢弃后,重新启动DMA接收。 */ if (ModbusRxFrameReady == 0U) { (void)ModbusStartReceive(); } } /** * @brief 启动 USART DMA 空闲接收 * @retval HAL_OK DMA 接收启动成功 * @retval HAL_BUSY 串口未配置或发送尚未结束 * @return 其他 HAL 状态表示 DMA 接收启动失败 */ static HAL_StatusTypeDef ModbusStartReceive(void) { HAL_StatusTypeDef status; ModbusReceiveRestartAttemptCount++; if (ModbusUart == NULL) { status = HAL_ERROR; ModbusReceiveRestartFailureCount++; ModbusLastReceiveStartStatus = (uint8_t)status; return status; } if (ModbusTxBusy != 0U) { status = HAL_BUSY; ModbusReceiveRestartFailureCount++; ModbusLastReceiveStartStatus = (uint8_t)status; return status; } status = HAL_UARTEx_ReceiveToIdle_DMA(ModbusUart, ModbusRxDmaBuffer, sizeof(ModbusRxDmaBuffer)); /* A duplicate start request can race with an already healthy DMA receiver. * Treat that specific HAL_BUSY case as operational, not as a recovery * failure; all other HAL_BUSY/HAL_ERROR results remain visible. */ if ((status == HAL_BUSY) && (ModbusUart->RxState == HAL_UART_STATE_BUSY_RX) && ((ModbusUart->Instance->CR3 & USART_CR3_DMAR) != 0U)) { status = HAL_OK; } if ((status == HAL_OK) && (ModbusUart->hdmarx != NULL)) { /* * 普通 Modbus 帧只应在 IDLE 或缓冲区满时交给应用 * 关闭 DMA 半传输中断,避免长帧在一半位置被误认为完整帧 */ __HAL_DMA_DISABLE_IT(ModbusUart->hdmarx, DMA_IT_HT); } if (status != HAL_OK) { ModbusReceiveRestartFailureCount++; ModbusLastUartErrorCode = ModbusUart->ErrorCode; ModbusNextReceiveRetryTick = HAL_GetTick() + 10UL; } else { ModbusNextReceiveRetryTick = 0UL; } ModbusLastReceiveStartStatus = (uint8_t)status; return status; } /** * @brief 在 RTU 帧末尾追加 CRC 低字节和高字节 * @param[in,out] frame 待追加 CRC 的帧缓冲区 * @param[in] payloadLength 不包含 CRC 的有效载荷长度 */ static void ModbusAppendCrc(uint8_t *frame, uint16_t payloadLength) { uint16_t crc = ModbusCrc16(frame, payloadLength); /* Modbus RTU 在线路上传输 CRC 低字节在前、高字节在后 */ frame[payloadLength] = (uint8_t)(crc & 0x00FFU); frame[payloadLength + 1U] = (uint8_t)(crc >> 8U); } /** * @brief 构造 Modbus 异常响应 * @param[in] function 请求功能码 * @param[in] exception 异常码 * @return 异常响应 ADU 长度 */ static uint16_t ModbusBuildException(uint8_t function, uint8_t exception) { ModbusTxFrame[0] = ModbusSlaveAddress; ModbusTxFrame[1] = (uint8_t)(function | 0x80U); ModbusTxFrame[2] = exception; ModbusAppendCrc(ModbusTxFrame, 3U); return 5U; } /** * @brief Build a bounded, read-only runtime diagnostic response (function 0x47). * * The request is: function, start word (BE), quantity (BE). Multi-word * 32-bit values use low word first, matching the PLSR diagnostic window. */ static uint16_t ModbusProcessRuntimeDiagnostics(const uint8_t *request, uint16_t requestLength) { MODBUS_SLAVE_RUNTIME_DIAGNOSTICS diagnostics; uint16_t words[MODBUS_RUNTIME_DIAGNOSTICS_WORDS]; uint32_t flags = 0U; uint32_t stats[10]; uint16_t start; uint16_t quantity; uint16_t index; uint16_t responseLength; if (requestLength != 8U) { ModbusSlaveStatistics.illegalValueCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } start = ModbusGetU16Be(&request[2]); quantity = ModbusGetU16Be(&request[4]); if ((quantity == 0U) || (start >= MODBUS_RUNTIME_DIAGNOSTICS_WORDS) || (quantity > (MODBUS_RUNTIME_DIAGNOSTICS_WORDS - start))) { ModbusSlaveStatistics.illegalAddressCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); } (void)ModbusSlaveGetRuntimeDiagnostics(MODBUS_CONNECTION_TIMEOUT_MS, &diagnostics); if (diagnostics.initialized != 0U) { flags |= MODBUS_RUNTIME_FLAG_INITIALIZED; } if (diagnostics.hasReceivedValidFrame != 0U) { flags |= MODBUS_RUNTIME_FLAG_VALID_FRAME_SEEN; } if (diagnostics.connected != 0U) { flags |= MODBUS_RUNTIME_FLAG_CONNECTED; } if (diagnostics.txBusy != 0U) { flags |= MODBUS_RUNTIME_FLAG_TX_BUSY; } if (diagnostics.rxFrameReady != 0U) { flags |= MODBUS_RUNTIME_FLAG_RX_FRAME_READY; } if (diagnostics.rxAssemblyInvalid != 0U) { flags |= MODBUS_RUNTIME_FLAG_RX_ASSEMBLY_INVALID; } if (diagnostics.lastReceiveStartStatus == (uint8_t)HAL_OK) { flags |= MODBUS_RUNTIME_FLAG_RX_RESTART_OK; } words[0] = MODBUS_RUNTIME_DIAGNOSTICS_SIGNATURE; words[1] = MODBUS_RUNTIME_DIAGNOSTICS_VERSION; words[2] = MODBUS_RUNTIME_DIAGNOSTICS_WORDS; words[3] = (uint16_t)flags; words[4] = (uint16_t)diagnostics.currentTick; words[5] = (uint16_t)(diagnostics.currentTick >> 16U); words[6] = (uint16_t)diagnostics.lastValidFrameTick; words[7] = (uint16_t)(diagnostics.lastValidFrameTick >> 16U); words[8] = (uint16_t)diagnostics.lastInterFrameGapCycles; words[9] = (uint16_t)(diagnostics.lastInterFrameGapCycles >> 16U); words[10] = (uint16_t)diagnostics.receiveRestartAttemptCount; words[11] = (uint16_t)(diagnostics.receiveRestartAttemptCount >> 16U); words[12] = (uint16_t)diagnostics.receiveRestartFailureCount; words[13] = (uint16_t)(diagnostics.receiveRestartFailureCount >> 16U); words[14] = (uint16_t)diagnostics.lastUartErrorCode; words[15] = (uint16_t)(diagnostics.lastUartErrorCode >> 16U); words[16] = diagnostics.lastReceiveStartStatus; words[17] = diagnostics.rxAssemblyLength; words[18] = diagnostics.rxFrameLength; words[19] = MODBUS_CONNECTION_TIMEOUT_MS; stats[0] = diagnostics.statistics.rxEventCount; stats[1] = diagnostics.statistics.validFrameCount; stats[2] = diagnostics.statistics.txFrameCount; stats[3] = diagnostics.statistics.crcErrorCount; stats[4] = diagnostics.statistics.ignoredAddressCount; stats[5] = diagnostics.statistics.illegalFunctionCount; stats[6] = diagnostics.statistics.illegalAddressCount; stats[7] = diagnostics.statistics.illegalValueCount; stats[8] = diagnostics.statistics.droppedFrameCount; stats[9] = diagnostics.statistics.uartErrorCount; for (index = 0U; index < 10U; index++) { words[20U + (index * 2U)] = (uint16_t)stats[index]; words[21U + (index * 2U)] = (uint16_t)(stats[index] >> 16U); } ModbusTxFrame[0] = ModbusSlaveAddress; ModbusTxFrame[1] = MODBUS_RUNTIME_DIAGNOSTICS_FUNCTION; ModbusTxFrame[2] = (uint8_t)(quantity * 2U); for (index = 0U; index < quantity; index++) { uint16_t value = words[start + index]; ModbusTxFrame[3U + (index * 2U)] = (uint8_t)(value >> 8U); ModbusTxFrame[4U + (index * 2U)] = (uint8_t)value; } responseLength = (uint16_t)(3U + (quantity * 2U)); ModbusAppendCrc(ModbusTxFrame, responseLength); return (uint16_t)(responseLength + 2U); } /** * @brief Process read coils (0x01) or discrete inputs (0x02). * @param[in] request RTU request frame. * @param[in] requestLength Request frame length. * @param[in] functionCode Function code echoed in the response. * @param[in] device Backing PLC bit-device image. * @return Response ADU length, including an exception response when invalid. */ static uint16_t ModbusProcessReadBits(const uint8_t *request, uint16_t requestLength, uint8_t functionCode, MODBUS_BIT_DEVICE device) { uint16_t start; uint16_t quantity; uint16_t index; uint8_t byteCount; if (requestLength != 8U) { ModbusSlaveStatistics.illegalValueCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } start = ModbusGetU16Be(&request[2]); quantity = ModbusGetU16Be(&request[4]); if ((quantity == 0U) || (quantity > MODBUS_READ_COILS_MAX)) // 数量0或者数量大于最大值 { ModbusSlaveStatistics.illegalValueCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } if (ModbusAddressRangeIsValid(start, quantity) == 0U) // 地址检查 { ModbusSlaveStatistics.illegalAddressCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); } byteCount = (uint8_t)((quantity + 7U) / 8U); ModbusTxFrame[0] = ModbusSlaveAddress; ModbusTxFrame[1] = functionCode; ModbusTxFrame[2] = byteCount; (void)memset(&ModbusTxFrame[3], 0, byteCount); for (index = 0U; index < quantity; index++) { uint8_t value = 0U; if ((ModbusDataReadBit(device, (uint32_t)start + index, &value) != 0U) && (value != 0U)) { ModbusTxFrame[3U + (index >> 3U)] |= (uint8_t)(1U << (index & 0x0007U)); } } ModbusAppendCrc(ModbusTxFrame, (uint16_t)(3U + byteCount)); return (uint16_t)(5U + byteCount); } /** * @brief 处理读保持寄存器功能码 0x03 * @param[in] request RTU 请求帧 * @param[in] requestLength 请求帧长度 * @return 待发送响应长度,异常请求返回异常响应长度 */ static uint16_t ModbusProcessReadHolding(const uint8_t *request, uint16_t requestLength) { uint16_t start; uint16_t quantity; uint16_t index; uint16_t value; if (requestLength != 8U) { ModbusSlaveStatistics.illegalValueCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } start = ModbusGetU16Be(&request[2]); quantity = ModbusGetU16Be(&request[4]); 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 ((start >= 20000U) && (start < 25000U) && (quantity > 0U) && (quantity <= (25000U - start))) goto tx; ModbusSlaveStatistics.illegalAddressCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); } tx: ModbusTxFrame[0] = ModbusSlaveAddress; ModbusTxFrame[1] = 0X03; ModbusTxFrame[2] = (uint8_t)(quantity * 2U); for (index = 0U; index < quantity; index++) { uint16_t address = start + index; /* D20000~D24999映射到D1、D3、D5……D9999 */ if (address >= 20000U) { address = (address - 20000U) * 2U + 1U; } if (ModbusDataReadWord(MODBUS_DATA_DEVICE_D, address, &value) == 0U) { ModbusSlaveStatistics.illegalAddressCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); } ModbusTxFrame[3U + index * 2U] = (uint8_t)(value >> 8U); ModbusTxFrame[4U + index * 2U] = (uint8_t)(value & 0x00FFU); } ModbusAppendCrc(ModbusTxFrame, (uint16_t)(3U + quantity * 2U)); return (uint16_t)(5U + quantity * 2U); } /** * @brief 处理写单个保持寄存器功能码 0x06 * @param[in] request RTU 请求帧 * @param[in] requestLength 请求帧长度 * @param[in] isBroadcast 非 0 表示当前请求为广播 * @return 单播响应长度;广播或无法响应时返回 0 */ static uint16_t ModbusProcessWriteSingleRegister(const uint8_t *request, uint16_t requestLength, uint8_t isBroadcast) { uint16_t address; uint16_t value; if (requestLength != 8U) { ModbusSlaveStatistics.illegalValueCount++; return (isBroadcast != 0U) ? 0U: ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } address = ModbusGetU16Be(&request[2]); value = ModbusGetU16Be(&request[4]); if (address >= MODBUS_MAP_ITEM_COUNT) { ModbusSlaveStatistics.illegalAddressCount++; return (isBroadcast != 0U) ? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); } (void)ModbusDataWriteWord(MODBUS_DATA_DEVICE_D, address, value); if (isBroadcast != 0U) { return 0U; } /* 0x06 的正常响应必须原样回显请求的前 6 个字节 */ (void)memcpy(ModbusTxFrame, request, 6U); ModbusAppendCrc(ModbusTxFrame, 6U); return 8U; } /** * @brief 处理写单个线圈功能码 0x05 * @param[in] request RTU 请求帧 * @param[in] requestLength 请求帧长度 * @param[in] isBroadcast 非 0 表示当前请求为广播 * @return 单播响应长度;广播或无法响应时返回 0 */ static uint16_t ModbusProcessWriteSingleCoil(const uint8_t *request, uint16_t requestLength, uint8_t isBroadcast) { uint16_t address; uint16_t value; if (requestLength != 8U) { ModbusSlaveStatistics.illegalValueCount++; return (isBroadcast != 0U)? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } address = ModbusGetU16Be(&request[2]); value = ModbusGetU16Be(&request[4]); /* * 0x05 只接受 0xFF00(线圈置位)和 0x0000(线圈复位); * 其他数值属于非法数据值 */ if ((value != MODBUS_COIL_VALUE_ON) && (value != MODBUS_COIL_VALUE_OFF)) { ModbusSlaveStatistics.illegalValueCount++; return (isBroadcast != 0U)? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } if (address >= MODBUS_MAP_ITEM_COUNT) { ModbusSlaveStatistics.illegalAddressCount++; return (isBroadcast != 0U) ? 0U: ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); } ModbusCoilSetUnchecked(address, (value == MODBUS_COIL_VALUE_ON) ? 1U : 0U); if (isBroadcast != 0U) { return 0U; } /* 0x05 正常应答原样回显请求的前 6 个字节,再追加 CRC */ (void)memcpy(ModbusTxFrame, request, 6U); ModbusAppendCrc(ModbusTxFrame, 6U); return 8U; } /** * @brief 处理写多个线圈功能码 0x0F * @param[in] request RTU 请求帧 * @param[in] requestLength 请求帧长度 * @param[in] isBroadcast 非 0 表示当前请求为广播 * @return 单播响应长度;广播或无法响应时返回 0 */ static uint16_t ModbusProcessWriteMultipleCoils(const uint8_t *request, uint16_t requestLength, uint8_t isBroadcast) { uint16_t start; uint16_t quantity; uint16_t index; uint8_t byteCount; uint8_t expectedByteCount; if (requestLength < 9U) { ModbusSlaveStatistics.illegalValueCount++; return (isBroadcast != 0U) ? 0U: ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } start = ModbusGetU16Be(&request[2]); quantity = ModbusGetU16Be(&request[4]); byteCount = request[6]; expectedByteCount = (uint8_t)((quantity + 7U) / 8U); if ((quantity == 0U) || (quantity > MODBUS_WRITE_COILS_MAX) || (byteCount != expectedByteCount) || (requestLength != (uint16_t)(9U + byteCount))) { ModbusSlaveStatistics.illegalValueCount++; return (isBroadcast != 0U)? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } if (ModbusAddressRangeIsValid(start, quantity) == 0U) { ModbusSlaveStatistics.illegalAddressCount++; return (isBroadcast != 0U) ? 0U: ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); } for (index = 0U; index < quantity; index++) { uint8_t state; state = (uint8_t)((request[7U + (index >> 3U)] >> (index & 0x0007U)) & 0x01U); ModbusCoilSetUnchecked((uint16_t)(start + index), state); } if (isBroadcast != 0U) { return 0U; } ModbusTxFrame[0] = ModbusSlaveAddress; ModbusTxFrame[1] = 0X0F; (void)memcpy(&ModbusTxFrame[2], &request[2], 4U); ModbusAppendCrc(ModbusTxFrame, 6U); return 8U; } /** * @brief 处理写多个保持寄存器功能码 0x10 * @param[in] request RTU 请求帧 * @param[in] requestLength 请求帧长度 * @param[in] isBroadcast 非 0 表示当前请求为广播 * @return 单播响应长度;广播或无法响应时返回 0 */ static uint16_t ModbusProcessWriteMultipleRegisters(const uint8_t *request, uint16_t requestLength, uint8_t isBroadcast) { uint16_t start; uint16_t quantity; uint16_t index; uint8_t byteCount; if (requestLength < 9U) { ModbusSlaveStatistics.illegalValueCount++; return (isBroadcast != 0U) ? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } start = ModbusGetU16Be(&request[2]); quantity = ModbusGetU16Be(&request[4]); byteCount = request[6]; if ((quantity == 0U) || (quantity > MODBUS_WRITE_REGS_MAX) || (byteCount != (uint8_t)(quantity * 2U)) || (requestLength != (uint16_t)(9U + byteCount))) { ModbusSlaveStatistics.illegalValueCount++; return (isBroadcast != 0U) ? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } if (ModbusAddressRangeIsValid(start, quantity) == 0U) { ModbusSlaveStatistics.illegalAddressCount++; return (isBroadcast != 0U) ? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); } for (index = 0U; index < quantity; index++) { ModbusWriteRegisterScratch[index] = ModbusGetU16Be(&request[7U + index * 2U]); } (void)ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, start, ModbusWriteRegisterScratch, quantity); if (isBroadcast != 0U) { return 0U; } ModbusTxFrame[0] = ModbusSlaveAddress; ModbusTxFrame[1] = 0X10; (void)memcpy(&ModbusTxFrame[2], &request[2], 4U); ModbusAppendCrc(ModbusTxFrame, 6U); return 8U; } /** * @brief 处理读取扩展地址保持寄存器功能码0x48 * @param[in] request RTU请求帧 * @param[in] requestLength 请求帧长度 * @return 待发送响应长度,异常请求返回异常响应长度 */ static uint16_t ModbusProcessReadBigHolding(const uint8_t *request, uint16_t requestLength) { uint32_t start; uint32_t currentAddress; uint16_t quantity; uint16_t index; uint16_t value; /* 请求帧:站号1 + 功能码1 + 地址3 + 数量2 + CRC2 */ if (requestLength != 9U) { ModbusSlaveStatistics.illegalValueCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } /* 提取24位起始地址和16位寄存器数量 */ start = ModbusGetU24Be(&request[2]); quantity = ModbusGetU16Be(&request[5]); /* 一次最多读取125个寄存器 */ if ((quantity == 0U) || (quantity > MODBUS_READ_REGS_MAX)) { ModbusSlaveStatistics.illegalValueCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } /* Function 0x48 retains the existing sparse range: SRAM 0..19999 and * CCMRAM 40000..69998. The unallocated 20000..39999 gap is rejected. */ if ((start >= 69999UL) || ((uint32_t)quantity > (69999UL - start)) || ((start < 40000UL) && ((start >= 20000UL) || ((start + quantity) > 20000UL)))) { ModbusSlaveStatistics.illegalAddressCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); } /* 组成正常响应帧头 */ ModbusTxFrame[0] = ModbusSlaveAddress; ModbusTxFrame[1] = 0x48U; ModbusTxFrame[2] = (uint8_t)(quantity * 2U); for (index = 0U; index < quantity; index++) { currentAddress = start + (uint32_t)index; /* The data-store layer validates the sparse physical range. */ if (ModbusDataReadLinear(currentAddress, &value) == 0U) { ModbusSlaveStatistics.illegalAddressCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); } /* 每个寄存器按照高字节、低字节装入响应帧 */ ModbusTxFrame[3U + index * 2U] = (uint8_t)(value >> 8U); ModbusTxFrame[4U + index * 2U] = (uint8_t)(value & 0x00FFU); } /* 添加CRC */ ModbusAppendCrc(ModbusTxFrame, (uint16_t)(3U + quantity * 2U)); return (uint16_t)(5U + quantity * 2U); } /** * @brief 校验并分发一帧 Modbus RTU 请求 * @param[in] request RTU 请求帧 * @param[in] requestLength 请求帧长度 * @return 待发送响应长度;无需响应时返回 0 */ static uint16_t ModbusProcessRequest(const uint8_t *request, uint16_t requestLength) { uint16_t calculatedCrc; uint16_t receivedCrc; uint8_t isBroadcast; if (requestLength < 4U) { return 0U; } calculatedCrc = ModbusCrc16(request, (uint16_t)(requestLength - 2U)); receivedCrc = (uint16_t)(request[requestLength - 2U] | ((uint16_t)request[requestLength - 1U] << 8U)); if (calculatedCrc != receivedCrc) // CRC校验 { ModbusSlaveStatistics.crcErrorCount++; return 0U; } if ((request[0] != ModbusSlaveAddress) && (request[0] != MODBUS_BROADCAST_ADDRESS)) // 非法从站地址 { ModbusSlaveStatistics.ignoredAddressCount++; return 0U; } isBroadcast = (request[0] == MODBUS_BROADCAST_ADDRESS) ? 1U : 0U; // 是否广播请求 ModbusSlaveStatistics.validFrameCount++; ModbusLastValidFrameTick = HAL_GetTick(); ModbusHasReceivedValidFrame = 1U; switch (request[1]) { case 0X01U: // 读线圈 // 广播请求不允许读取,从站不作响应 return (isBroadcast != 0U) ? 0U : ModbusProcessReadBits(request, requestLength, 0X01U, MODBUS_BIT_DEVICE_M); case 0X02U: /* Read discrete inputs: protocol view of PLC X. */ return (isBroadcast != 0U) ? 0U : ModbusProcessReadBits(request, requestLength, 0X02U, MODBUS_BIT_DEVICE_X); case 0X03U: // 读保持寄存器 return (isBroadcast != 0U) ? 0U : ModbusProcessReadHolding(request, requestLength); case 0x05U: // 写单个线圈 return ModbusProcessWriteSingleCoil(request, requestLength, isBroadcast); case 0x06U: // 写单个保持寄存器 return ModbusProcessWriteSingleRegister(request, requestLength, isBroadcast); case 0x0FU: // 写多个线圈 return ModbusProcessWriteMultipleCoils(request, requestLength, isBroadcast); case 0x10U: // 写多个保持寄存器 return ModbusProcessWriteMultipleRegisters(request, requestLength, isBroadcast); case MODBUS_RUNTIME_DIAGNOSTICS_FUNCTION: return (isBroadcast != 0U) ? 0U : ModbusProcessRuntimeDiagnostics(request, requestLength); case 0x48U: // 读大地址保持寄存器 return (isBroadcast != 0U) ? 0U : ModbusProcessReadBigHolding(request, requestLength); default: // 未知功能码 ModbusSlaveStatistics.illegalFunctionCount++; return (isBroadcast != 0U) ? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_FUNCTION); } } HAL_StatusTypeDef ModbusSlaveInit(UART_HandleTypeDef *huart, uint8_t slaveAddress) { if ((huart == NULL) || (slaveAddress == 0U) || (slaveAddress > 247U)) { return HAL_ERROR; } ModbusUart = huart; ModbusSlaveAddress = slaveAddress; /* 清空拼帧状态并根据当前波特率初始化T1.5和T3.5 */ ModbusRxAssemblyLength = 0U; ModbusRxAssemblyInvalid = 0U; ModbusRxFrameReady = 0U; ModbusTxBusy = 0U; ModbusNextReceiveRetryTick = 0UL; ModbusRtuTimingInit(huart->Init.BaudRate); return ModbusStartReceive(); } void ModbusSlavePoll(void) { uint16_t responseLength; /* Recover from a failed DMA rearm even when no later UART callback occurs. * Limit retries to 100 Hz so a persistent HAL fault cannot monopolize the * 1 ms application task. */ if ((ModbusUart != NULL) && (ModbusTxBusy == 0U) && (ModbusRxFrameReady == 0U) && ((ModbusUart->RxState != HAL_UART_STATE_BUSY_RX) || ((ModbusUart->Instance->CR3 & USART_CR3_DMAR) == 0U)) && ((ModbusNextReceiveRetryTick == 0UL) || ((int32_t)(HAL_GetTick() - ModbusNextReceiveRetryTick) >= 0))) { (void)ModbusStartReceive(); } /* 检查末字节后的静默时间是否已经达到T3.5 */ ModbusTryFinalizeReceive(); if ((ModbusRxFrameReady == 0U) || (ModbusTxBusy != 0U)) { return; } responseLength = ModbusProcessRequest(ModbusRxFrame, ModbusRxFrameLength); if (responseLength > 0U) { ModbusTxBusy = 1U; if (HAL_UART_Transmit_DMA(ModbusUart, ModbusTxFrame, responseLength) == HAL_OK) { ModbusSlaveStatistics.txFrameCount++; } else { ModbusTxBusy = 0U; ModbusSlaveStatistics.uartErrorCount++; ModbusLastUartErrorCode = ModbusUart->ErrorCode; (void)ModbusStartReceive(); // 重启DMA接收 } } else { (void)ModbusStartReceive(); } /* * 当前帧;处理完成后再释放帧槽 */ ModbusRxFrameReady = 0U; } void ModbusSlaveOnRxEvent(UART_HandleTypeDef *huart, uint16_t size) { HAL_UART_RxEventTypeTypeDef eventType; uint32_t now; uint32_t lastByteCycle; uint32_t firstByteCycle; uint32_t chunkCycles; uint32_t interFrameGap; ModbusSlaveStatistics.rxEventCount++; // 串口接收事件计数 if ((huart != ModbusUart) || (ModbusUart == NULL)) { return; } if ((size > 0U) && (size <= MODBUS_RTU_ADU_SIZE_MAX)) { now = DWT->CYCCNT; eventType = HAL_UARTEx_GetRxEventType(huart); /* IDLE事件比末字节结束晚约一个字符时间,减去字符时间得到末字节时刻 */ lastByteCycle = now; if (eventType == HAL_UART_RXEVENT_IDLE) { lastByteCycle -= ModbusRtuCharCycles; } /* 根据本次接收字节数反推DMA片段首字节的开始时刻 */ chunkCycles = (uint32_t)((uint64_t)size * ModbusRtuCharCycles); firstByteCycle = lastByteCycle - chunkCycles; if (ModbusRxAssemblyLength > 0U) { interFrameGap = (uint32_t)(firstByteCycle - ModbusRxLastByteCycle); ModbusLastInterFrameGapCycles = interFrameGap; if (interFrameGap >= ModbusRtuT35Cycles) { /* 间隔达到T3.5,结束上一帧,本片段作为新帧开始 */ ModbusRxAssemblyFinalize(); } else if (interFrameGap > ModbusRtuT15Cycles) { /* 帧内静默超过T1.5但不足T3.5,标记整帧无效 */ ModbusRxAssemblyInvalid = 1U; } else { /* 间隔不超过T1.5,当前片段继续拼入同一帧 */ } } if (size <= (uint16_t)(MODBUS_RTU_ADU_SIZE_MAX - ModbusRxAssemblyLength)) { (void)memcpy(&ModbusRxAssemblyBuffer[ModbusRxAssemblyLength], ModbusRxDmaBuffer, size); ModbusRxAssemblyLength += size; } else { ModbusRxAssemblyInvalid = 1U; } /* 保存末字节时刻并立即重启DMA,继续等待可能的后续片段 */ ModbusRxLastByteCycle = lastByteCycle; (void)ModbusStartReceive(); } else { // 长度异常帧 ModbusSlaveStatistics.droppedFrameCount++; ModbusRxAssemblyLength = 0U; ModbusRxAssemblyInvalid = 0U; (void)ModbusStartReceive(); } } void ModbusSlaveOnTxComplete(UART_HandleTypeDef *huart) { if ((huart != ModbusUart) || (ModbusUart == NULL)) { return; } ModbusTxBusy = 0U; (void)ModbusStartReceive(); // 重启DMA接收 } void ModbusSlaveOnUartError(UART_HandleTypeDef *huart) { if ((huart != ModbusUart) || (ModbusUart == NULL)) { return; } ModbusSlaveStatistics.uartErrorCount++; ModbusLastUartErrorCode = huart->ErrorCode; ModbusTxBusy = 0U; ModbusRxAssemblyLength = 0U; ModbusRxAssemblyInvalid = 0U; (void)HAL_UART_Abort(huart); // 立即终止这个串口当前正在进行的发送和接收操作 (void)ModbusStartReceive(); // 重启DMA接收 } uint8_t ModbusSlaveSetHoldingRegister(uint16_t address, uint16_t value) { if (address >= MODBUS_MAP_ITEM_COUNT) { return 0U; } return ModbusDataWriteWord(MODBUS_DATA_DEVICE_D, address, value); } uint8_t ModbusSlaveGetHoldingRegister(uint16_t address, uint16_t *value) { if ((address >= MODBUS_MAP_ITEM_COUNT) || (value == NULL)) { return 0U; } return ModbusDataReadWord(MODBUS_DATA_DEVICE_D, address, value); } uint8_t ModbusSlaveSetCoil(uint16_t address, uint8_t state) { if (address >= MODBUS_MAP_ITEM_COUNT) { return 0U; } ModbusCoilSetUnchecked(address, state); return 1U; } uint8_t ModbusSlaveGetCoil(uint16_t address, uint8_t *state) { if ((address >= MODBUS_MAP_ITEM_COUNT) || (state == NULL)) { return 0U; } *state = ModbusCoilGetUnchecked(address); return 1U; } uint8_t ModbusSlaveIsConnected(uint32_t timeoutMs) { if (ModbusHasReceivedValidFrame == 0U) { return 0U; } return ((HAL_GetTick() - ModbusLastValidFrameTick) <= timeoutMs) ? 1U : 0U; } uint8_t ModbusSlaveGetRuntimeDiagnostics( uint32_t timeoutMs, MODBUS_SLAVE_RUNTIME_DIAGNOSTICS *diagnostics) { if (diagnostics == NULL) { return 0U; } /* Every source field is naturally aligned and 32-bit-or-smaller on the * Cortex-M4. A diagnostic snapshot may span adjacent frame events, but * must never mask the 100 kHz motion/AB fast-gate interrupts. */ __DMB(); diagnostics->statistics.rxEventCount = ModbusSlaveStatistics.rxEventCount; diagnostics->statistics.validFrameCount = ModbusSlaveStatistics.validFrameCount; diagnostics->statistics.txFrameCount = ModbusSlaveStatistics.txFrameCount; diagnostics->statistics.crcErrorCount = ModbusSlaveStatistics.crcErrorCount; diagnostics->statistics.ignoredAddressCount = ModbusSlaveStatistics.ignoredAddressCount; diagnostics->statistics.illegalFunctionCount = ModbusSlaveStatistics.illegalFunctionCount; diagnostics->statistics.illegalAddressCount = ModbusSlaveStatistics.illegalAddressCount; diagnostics->statistics.illegalValueCount = ModbusSlaveStatistics.illegalValueCount; diagnostics->statistics.droppedFrameCount = ModbusSlaveStatistics.droppedFrameCount; diagnostics->statistics.uartErrorCount = ModbusSlaveStatistics.uartErrorCount; diagnostics->currentTick = HAL_GetTick(); diagnostics->lastValidFrameTick = ModbusLastValidFrameTick; diagnostics->lastInterFrameGapCycles = ModbusLastInterFrameGapCycles; diagnostics->receiveRestartAttemptCount = ModbusReceiveRestartAttemptCount; diagnostics->receiveRestartFailureCount = ModbusReceiveRestartFailureCount; diagnostics->lastUartErrorCode = ModbusLastUartErrorCode; diagnostics->rxAssemblyLength = ModbusRxAssemblyLength; diagnostics->rxFrameLength = ModbusRxFrameLength; diagnostics->lastReceiveStartStatus = ModbusLastReceiveStartStatus; diagnostics->initialized = (ModbusUart != NULL) ? 1U : 0U; diagnostics->hasReceivedValidFrame = ModbusHasReceivedValidFrame; diagnostics->connected = ((ModbusHasReceivedValidFrame != 0U) && ((diagnostics->currentTick - ModbusLastValidFrameTick) <= timeoutMs)) ? 1U : 0U; diagnostics->txBusy = ModbusTxBusy; diagnostics->rxFrameReady = ModbusRxFrameReady; diagnostics->rxAssemblyInvalid = ModbusRxAssemblyInvalid; __DMB(); return 1U; } // 上电恢复函数 void ModbusRetainedRegistersLoad(void) { uint16_t index; if (ModbusBackupData->magic == MODBUS_BACKUP_MAGIC) { for (index = 0U; index < MODBUS_RETAINED_D_COUNT; index++) { (void)ModbusDataWriteWord( MODBUS_DATA_DEVICE_D, MODBUS_RETAINED_D_START + index, ModbusBackupData->retainedD[index]); } } else { for (index = 0U; index < MODBUS_RETAINED_D_COUNT; index++) { (void)ModbusDataWriteWord(MODBUS_DATA_DEVICE_D, MODBUS_RETAINED_D_START + index, 0U); ModbusBackupData->retainedD[index] = 0U; } /* * 数据初始化完成后最后写magic,避免初始化中途断电 * 却把不完整数据标记成有效 */ ModbusBackupData->magic = MODBUS_BACKUP_MAGIC; } } void ModbusRetainedRegistersPoll(void) { uint16_t index; uint16_t value; for (index = 0; index < MODBUS_RETAINED_D_COUNT; index++) { (void)ModbusDataReadWord(MODBUS_DATA_DEVICE_D, MODBUS_RETAINED_D_START + index, &value); if (value != ModbusRetainedSnapshot[index]) { ModbusBackupData->retainedD[index] = value; } ModbusRetainedSnapshot[index] = value; } }