#ifndef MODBUS_RTU_SLAVE_H #define MODBUS_RTU_SLAVE_H #include "stm32f4xx_hal.h" #include #ifdef __cplusplus extern "C" { #endif /** * @brief Modbus 数据模型的最大地址 * * 任务要求规定线圈和保持寄存器地址范围均为 0~0x270F, * 因此每种数据共有 10000 项 */ #define MODBUS_MAP_LAST_ADDRESS (0x270FU) // 保持寄存器或线圈地址 #define MODBUS_MAP_ITEM_COUNT (0x2710U) // 保持寄存器或线圈数量 #define MODBUS_SLAVE_DEFAULT_ADDRESS (1U) #define MODBUS_CONNECTION_TIMEOUT_MS (1000U) /* Read-only vendor diagnostics. The request uses the same start/quantity * layout as function 0x03; diagnostic words are described below. */ #define MODBUS_RUNTIME_DIAGNOSTICS_FUNCTION (0x47U) #define MODBUS_RUNTIME_DIAGNOSTICS_SIGNATURE (0x4D42U) #define MODBUS_RUNTIME_DIAGNOSTICS_VERSION (1U) #define MODBUS_RUNTIME_DIAGNOSTICS_WORDS (40U) #define MODBUS_RUNTIME_FLAG_INITIALIZED (1U << 0U) #define MODBUS_RUNTIME_FLAG_VALID_FRAME_SEEN (1U << 1U) #define MODBUS_RUNTIME_FLAG_CONNECTED (1U << 2U) #define MODBUS_RUNTIME_FLAG_TX_BUSY (1U << 3U) #define MODBUS_RUNTIME_FLAG_RX_FRAME_READY (1U << 4U) #define MODBUS_RUNTIME_FLAG_RX_ASSEMBLY_INVALID (1U << 5U) #define MODBUS_RUNTIME_FLAG_RX_RESTART_OK (1U << 6U) /** * @brief 与 TouchWin 触摸屏联调使用的演示地址 * * 可以直接访问以下地址 */ #define HMI_REG_DEVICE_ID (0U) #define HMI_REG_UPTIME_SECONDS (1U) #define HMI_REG_RX_FRAME_COUNT (2U) #define D3 (3U) #define D4 (4U) #define D5 (5U) #define D6 (6U) #define D7 (7U) #define HMI_REG_WRITE_TEST (10U) #define HMI_COIL_WRITE_TEST (0U) #define MODBUS_RETAINED_D_START (100U) #define MODBUS_RETAINED_D_END (120U) #define MODBUS_RETAINED_D_COUNT (21U) #define MODBUS_BACKUP_MAGIC (0x44313030UL) /** * @brief 掉电保持寄存器在Backup SRAM中的存储格式 */ typedef struct { uint32_t magic; ///< 掉电保持数据有效标志 uint16_t retainedD[MODBUS_RETAINED_D_COUNT]; ///< D100~D120保持值 } MODBUS_BACKUP_DATA; /** * @brief Modbus 从站通信统计信息 */ typedef struct { uint32_t rxEventCount; ///< 串口接收事件总数 uint32_t validFrameCount; ///< CRC 和从站地址均有效的帧数 uint32_t txFrameCount; ///< 成功启动 DMA 发送的帧数 uint32_t crcErrorCount; ///< CRC 校验错误帧数 uint32_t ignoredAddressCount; ///< 因从站地址不匹配而忽略的帧数 uint32_t illegalFunctionCount; ///< 非法功能码计数 uint32_t illegalAddressCount; ///< 非法数据地址计数 uint32_t illegalValueCount; ///< 非法数据值计数 uint32_t droppedFrameCount; ///< 接收槽占用或长度错误导致的丢帧数 uint32_t uartErrorCount; ///< HAL 串口错误计数 } MODBUS_SLAVE_STATS; /** * @brief Read-only snapshot of the Modbus RTU runtime state. * * Every aligned 32-bit-or-smaller field is read without tearing on Cortex-M4. * The getter does not mask motion or UART interrupts, so the complete structure * may intentionally span adjacent UART events and is not a transaction-wide * atomic snapshot. */ typedef struct { MODBUS_SLAVE_STATS statistics; uint32_t currentTick; uint32_t lastValidFrameTick; uint32_t lastInterFrameGapCycles; uint32_t receiveRestartAttemptCount; uint32_t receiveRestartFailureCount; uint32_t lastUartErrorCode; uint16_t rxAssemblyLength; uint16_t rxFrameLength; uint8_t lastReceiveStartStatus; uint8_t initialized; uint8_t hasReceivedValidFrame; uint8_t connected; uint8_t txBusy; uint8_t rxFrameReady; uint8_t rxAssemblyInvalid; } MODBUS_SLAVE_RUNTIME_DIAGNOSTICS; /** @brief Modbus 从站通信统计数据 */ extern volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics; /** * @brief 初始化 Modbus 从站并启动 USART DMA 空闲接收 * @param[in] huart 已由 HAL 初始化完成的串口句柄 * @param[in] slaveAddress 从站地址,有效范围为 1~247 * @retval HAL_OK 初始化成功且 DMA 接收已启动 * @retval HAL_ERROR 输入参数无效 * @retval HAL_BUSY 串口或 DMA 当前忙 */ HAL_StatusTypeDef ModbusSlaveInit(UART_HandleTypeDef *huart, uint8_t slaveAddress); /** * @brief 校验并处理一帧待处理请求 * * 本函数应在 uC/OS-II 任务中周期调用中断只负责接收数据, * CRC 校验、协议解析和响应发送均在任务上下文中执行 */ void ModbusSlavePoll(void); /** * @brief 处理 HAL 串口 DMA 接收事件 * @param[in] huart 产生接收事件的串口句柄 * @param[in] size 本次接收到的字节数 */ void ModbusSlaveOnRxEvent(UART_HandleTypeDef *huart, uint16_t size); /** * @brief 处理 HAL 串口 DMA 发送完成事件 * @param[in] huart 完成发送的串口句柄 */ void ModbusSlaveOnTxComplete(UART_HandleTypeDef *huart); /** * @brief 处理 HAL 串口错误事件并恢复接收 * @param[in] huart 发生错误的串口句柄 */ void ModbusSlaveOnUartError(UART_HandleTypeDef *huart); /** * @brief 写入一个保持寄存器 * @param[in] address 保持寄存器地址 * @param[in] value 待写入的 16 位数据 * @retval 1 写入成功 * @retval 0 保持寄存器地址无效 */ uint8_t ModbusSlaveSetHoldingRegister(uint16_t address, uint16_t value); /** * @brief 读取一个保持寄存器 * @param[in] address 保持寄存器地址 * @param[out] value 用于保存读取结果的指针 * @retval 1 读取成功 * @retval 0 保持寄存器地址无效或输出指针为空 */ uint8_t ModbusSlaveGetHoldingRegister(uint16_t address, uint16_t *value); /** * @brief 设置一个线圈的状态 * @param[in] address 线圈地址 * @param[in] state 0 表示复位,非 0 表示置位 * @retval 1 设置成功 * @retval 0 线圈地址无效 */ uint8_t ModbusSlaveSetCoil(uint16_t address, uint8_t state); /** * @brief 读取一个线圈的状态 * @param[in] address 线圈地址 * @param[out] state 用于保存线圈状态的指针,结果为 0 或 1 * @retval 1 读取成功 * @retval 0 线圈地址无效或输出指针为空 */ uint8_t ModbusSlaveGetCoil(uint16_t address, uint8_t *state); /** * @brief 判断指定时间内是否收到过有效请求 * @param[in] timeoutMs 连接超时时间,单位为毫秒 * @retval 1 从站在指定时间内收到过有效请求 * @retval 0 尚未收到有效请求或连接已经超时 */ uint8_t ModbusSlaveIsConnected(uint32_t timeoutMs); /** * @brief Copy all RTU diagnostics with tear-free individual scalar fields. * @param[in] timeoutMs Age used to calculate the connected flag. * @param[out] diagnostics Destination for the complete snapshot. * @retval 1 Snapshot returned. * @retval 0 diagnostics is NULL. */ uint8_t ModbusSlaveGetRuntimeDiagnostics( uint32_t timeoutMs, MODBUS_SLAVE_RUNTIME_DIAGNOSTICS *diagnostics); void ModbusRetainedRegistersLoad(void); void ModbusRetainedRegistersPoll(void); #ifdef __cplusplus } #endif #endif /* MODBUS_RTU_SLAVE_H */