/** * @file main.c * @brief 基于 uC/OS-II 的 Modbus RTU 从站主程序。 * @version 2.0 * @author zengbingjie * @date 2026-07-29 * @copyright 版权所有 (c) 2026 Xinje Electric。 * @note 修改记录:2.0 增加帧隔离、连接状态和可靠收发处理。 */ #include "main.h" #include "modbus.h" #define APP_MODBUS_STATUS_REGISTER_COUNT (8U) #define APP_MODBUS_FUNCTION_INDEX (1U) static OS_STK AppTaskStartStk[APP_TASK_START_STK_SIZE]; static OS_STK AppTaskModbusStk[APP_TASK_MODBUS_STK_SIZE]; static OS_STK AppTaskT35Stk[APP_TASK_T35_STK_SIZE]; UART_HandleTypeDef Uart1Handle; static MODBUS_SLAVE ModbusSlave; static OS_EVENT *ModbusFrameSem; static OS_EVENT *ModbusTxSem; static uint8_t ModbusRxBuffer[MODBUS_RX_BUF_SIZE]; static uint8_t ModbusTxBuffer[MODBUS_TX_BUF_SIZE]; static volatile uint16_t ModbusRxLength; static volatile uint16_t ModbusFrameLength; static volatile uint32_t ModbusLastRxTick; static volatile uint8_t ModbusFrameReady; static volatile uint8_t ModbusRxOverflow; static volatile uint32_t ModbusReceiveErrorCount; static volatile uint32_t ModbusSemaphoreErrorCount; static uint32_t ModbusLastActivityTick; static uint32_t ModbusOperationRxCount; static uint32_t ModbusOperationTxCount; static uint32_t ModbusProtocolErrorCount; static uint8_t ModbusHasActivity; static MODBUS_STATUS ModbusLastStatus; static GPIO_TypeDef *const AppOutputPorts[APP_MODBUS_OUTPUT_COIL_COUNT] = { PLC_Q0_PORT, PLC_Q1_PORT, PLC_Q2_PORT, PLC_Q3_PORT, PLC_Q4_PORT, PLC_Q5_PORT, PLC_Q6_PORT, PLC_Q7_PORT }; static const uint16_t AppOutputPins[APP_MODBUS_OUTPUT_COIL_COUNT] = { PLC_Q0_PIN, PLC_Q1_PIN, PLC_Q2_PIN, PLC_Q3_PIN, PLC_Q4_PIN, PLC_Q5_PIN, PLC_Q6_PIN, PLC_Q7_PIN }; void SystemClock_Config(void); static void AppTaskStart(void *argument); static void AppTaskModbus(void *argument); static void AppTaskT35(void *argument); static void AppInitGpio(void); static void AppInitUart1(void); static INT8U AppCreateTasks(void); static HAL_StatusTypeDef AppStartUartReception(void); static HAL_StatusTypeDef AppTransmitResponse(uint16_t txLength); static uint8_t AppSealReceivedFrame(void); static uint16_t AppGetReadyFrameLength(void); static INT8U AppPostModbusFrameSem(void); static uint8_t AppIsModbusStatusAccepted(MODBUS_STATUS status); static uint8_t AppIsModbusWriteFunction(uint8_t functionCode); static MODBUS_STATUS AppUpdateStatusRegisters(void); static MODBUS_STATUS AppSyncCoilsToGpio(void); static void AppProcessFrame(uint16_t rxLength); static void AppRecordIsrError(void); /** * @brief 应用程序入口。 * @return 该函数不返回。 */ int main(void) { INT8U osError; HAL_Init(); SystemClock_Config(); AppInitGpio(); AppInitUart1(); OSInit(); osError = OSTaskCreateExt( AppTaskStart, NULL, &AppTaskStartStk[APP_TASK_START_STK_SIZE - 1U], APP_TASK_START_PRIO, APP_TASK_START_PRIO, &AppTaskStartStk[0], APP_TASK_START_STK_SIZE, NULL, OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR); if (osError != OS_ERR_NONE) { Error_Handler(); } OSStart(); while (1) { } } /** * @brief 配置 STM32F407 的 168 MHz 系统时钟。 * @note 使用板载 12 MHz 外部晶振作为 PLL 时钟源。 */ void SystemClock_Config(void) { RCC_OscInitTypeDef oscillatorConfig = {0}; RCC_ClkInitTypeDef clockConfig = {0}; __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); oscillatorConfig.OscillatorType = RCC_OSCILLATORTYPE_HSE; oscillatorConfig.HSEState = RCC_HSE_ON; oscillatorConfig.PLL.PLLState = RCC_PLL_ON; oscillatorConfig.PLL.PLLSource = RCC_PLLSOURCE_HSE; oscillatorConfig.PLL.PLLM = 12U; oscillatorConfig.PLL.PLLN = 336U; oscillatorConfig.PLL.PLLP = RCC_PLLP_DIV2; oscillatorConfig.PLL.PLLQ = 7U; if (HAL_RCC_OscConfig(&oscillatorConfig) != HAL_OK) { Error_Handler(); } clockConfig.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; clockConfig.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; clockConfig.AHBCLKDivider = RCC_SYSCLK_DIV1; clockConfig.APB1CLKDivider = RCC_HCLK_DIV4; clockConfig.APB2CLKDivider = RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(&clockConfig, FLASH_LATENCY_5) != HAL_OK) { Error_Handler(); } } /** * @brief 初始化 XDM-60T4-E 的 Q0 至 Q7 输出引脚。 * @note 板卡输出为低电平有效,初始状态全部关闭。 */ static void AppInitGpio(void) { GPIO_InitTypeDef gpioConfig = {0}; __HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOI_CLK_ENABLE(); gpioConfig.Mode = GPIO_MODE_OUTPUT_PP; gpioConfig.Pull = GPIO_PULLUP; gpioConfig.Speed = GPIO_SPEED_FREQ_LOW; gpioConfig.Pin = PLC_Q0_PIN | PLC_Q1_PIN | PLC_Q2_PIN | PLC_Q3_PIN; HAL_GPIO_Init(GPIOF, &gpioConfig); gpioConfig.Pin = PLC_Q4_PIN; HAL_GPIO_Init(GPIOI, &gpioConfig); gpioConfig.Pin = PLC_Q5_PIN | PLC_Q6_PIN | PLC_Q7_PIN; HAL_GPIO_Init(GPIOE, &gpioConfig); HAL_GPIO_WritePin(GPIOF, PLC_Q0_PIN | PLC_Q1_PIN | PLC_Q2_PIN | PLC_Q3_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOI, PLC_Q4_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOE, PLC_Q5_PIN | PLC_Q6_PIN | PLC_Q7_PIN, GPIO_PIN_SET); } /** * @brief 初始化 Modbus RTU 使用的 USART1。 */ static void AppInitUart1(void) { Uart1Handle.Instance = USART1; Uart1Handle.Init.BaudRate = APP_MODBUS_BAUD_RATE; Uart1Handle.Init.WordLength = UART_WORDLENGTH_8B; Uart1Handle.Init.StopBits = UART_STOPBITS_1; Uart1Handle.Init.Parity = UART_PARITY_NONE; Uart1Handle.Init.Mode = UART_MODE_TX_RX; Uart1Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; Uart1Handle.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&Uart1Handle) != HAL_OK) { Error_Handler(); } } /** * @brief 创建 Modbus 工作任务与帧间隔检测任务。 * @return uC/OS-II 错误码。 */ static INT8U AppCreateTasks(void) { INT8U osError; osError = OSTaskCreateExt( AppTaskModbus, NULL, &AppTaskModbusStk[APP_TASK_MODBUS_STK_SIZE - 1U], APP_TASK_MODBUS_PRIO, APP_TASK_MODBUS_PRIO, &AppTaskModbusStk[0], APP_TASK_MODBUS_STK_SIZE, NULL, OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR); if (osError != OS_ERR_NONE) { return osError; } osError = OSTaskCreateExt( AppTaskT35, NULL, &AppTaskT35Stk[APP_TASK_T35_STK_SIZE - 1U], APP_TASK_T35_PRIO, APP_TASK_T35_PRIO, &AppTaskT35Stk[0], APP_TASK_T35_STK_SIZE, NULL, OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR); return osError; } /** * @brief 启动任务,创建同步对象并启动应用任务。 * @param[in] argument 未使用。 */ static void AppTaskStart(void *argument) { INT8U osError; (void)argument; ModbusFrameSem = OSSemCreate(0U); ModbusTxSem = OSSemCreate(0U); if ((ModbusFrameSem == NULL) || (ModbusTxSem == NULL)) { Error_Handler(); } osError = AppCreateTasks(); if (osError != OS_ERR_NONE) { Error_Handler(); } OSTaskSuspend(OS_PRIO_SELF); while (1) { } } /** * @brief 清空接收状态并启动 USART1 单字节中断接收。 * @return HAL 状态码。 */ static HAL_StatusTypeDef AppStartUartReception(void) { OS_CPU_SR cpu_sr = 0U; HAL_StatusTypeDef halStatus; OS_ENTER_CRITICAL(); ModbusRxLength = 0U; ModbusFrameLength = 0U; ModbusFrameReady = 0U; ModbusRxOverflow = 0U; ModbusLastRxTick = OSTimeGet(); OS_EXIT_CRITICAL(); halStatus = HAL_UART_Receive_IT(&Uart1Handle, &ModbusRxBuffer[0], 1U); return halStatus; } /** * @brief 通过 USART1 发送 Modbus 从站响应帧。 * @param[in] txLength 响应帧长度。 * @return HAL 状态码。 */ static HAL_StatusTypeDef AppTransmitResponse(uint16_t txLength) { HAL_StatusTypeDef halStatus; INT8U osError; if ((txLength == 0U) || (txLength > MODBUS_TX_BUF_SIZE)) { return HAL_ERROR; } halStatus = HAL_UART_Transmit_IT(&Uart1Handle, ModbusTxBuffer, txLength); if (halStatus != HAL_OK) { return halStatus; } OSSemPend(ModbusTxSem, APP_MODBUS_TX_TIMEOUT_TICKS, &osError); if (osError != OS_ERR_NONE) { (void)HAL_UART_AbortTransmit_IT(&Uart1Handle); return HAL_TIMEOUT; } return HAL_OK; } /** * @brief 根据 T3.5 静默时间将接收缓冲区封存为完整帧。 * @return 1 表示已封存完整帧;0 表示未满足封帧条件。 */ static uint8_t AppSealReceivedFrame(void) { OS_CPU_SR cpu_sr = 0U; HAL_StatusTypeDef halStatus; uint32_t currentTick; uint32_t elapsedTicks; uint8_t frameSealed; frameSealed = 0U; currentTick = OSTimeGet(); OS_ENTER_CRITICAL(); elapsedTicks = currentTick - ModbusLastRxTick; if ((ModbusRxLength > 0U) && (ModbusFrameReady == 0U) && (elapsedTicks >= APP_MODBUS_T35_TICKS)) { ModbusFrameLength = ModbusRxLength; ModbusFrameReady = 1U; frameSealed = 1U; } OS_EXIT_CRITICAL(); if (frameSealed == 0U) { return 0U; } halStatus = HAL_UART_AbortReceive_IT(&Uart1Handle); if (halStatus != HAL_OK) { AppRecordIsrError(); } if (AppPostModbusFrameSem() != OS_ERR_NONE) { AppRecordIsrError(); } return 1U; } /** * @brief 原子读取已封存 Modbus 帧的长度。 * @return 完整帧长度;无完整帧时返回 0。 */ static uint16_t AppGetReadyFrameLength(void) { OS_CPU_SR cpu_sr = 0U; uint16_t frameLength; frameLength = 0U; OS_ENTER_CRITICAL(); if (ModbusFrameReady != 0U) { frameLength = ModbusFrameLength; } OS_EXIT_CRITICAL(); return frameLength; } /** * @brief 通知 Modbus 任务处理完整帧或刷新通信状态。 * @return uC/OS-II 错误码。 */ static INT8U AppPostModbusFrameSem(void) { INT8U osError; osError = OSSemPost(ModbusFrameSem); return osError; } /** * @brief 判断协议处理结果是否代表已寻址的有效通信。 * @param[in] status Modbus 协议处理结果。 * @return 1 表示有效通信;0 表示无效通信。 */ static uint8_t AppIsModbusStatusAccepted(MODBUS_STATUS status) { if ((status == MODBUS_STATUS_OK) || (status == MODBUS_STATUS_BROADCAST) || (status == MODBUS_STATUS_EXCEPTION)) { return 1U; } return 0U; } /** * @brief 判断功能码是否为触摸屏的写操作。 * @param[in] functionCode Modbus 功能码。 * @return 写操作返回 1,否则返回 0。 */ static uint8_t AppIsModbusWriteFunction(uint8_t functionCode) { switch (functionCode) { case MODBUS_FC_WRITE_SINGLE_COIL: case MODBUS_FC_WRITE_SINGLE_REG: case MODBUS_FC_WRITE_MULTIPLE_COILS: case MODBUS_FC_WRITE_MULTIPLE_REGS: return 1U; default: return 0U; } } /** * @brief 更新供触摸屏读取的通信状态寄存器。 * @return 成功返回 MODBUS_STATUS_OK;失败返回错误状态。 */ static MODBUS_STATUS AppUpdateStatusRegisters(void) { MODBUS_STATUS status; uint32_t currentTick; uint8_t linkState; uint16_t registerValue; currentTick = OSTimeGet(); linkState = APP_MODBUS_LINK_DISCONNECTED; if ((ModbusHasActivity != 0U) && ((currentTick - ModbusLastActivityTick) < APP_MODBUS_LINK_TIMEOUT_TICKS)) { linkState = APP_MODBUS_LINK_CONNECTED; } status = ModbusSetHoldingRegister(&ModbusSlave, APP_MODBUS_REG_LINK_STATE, linkState); if (status != MODBUS_STATUS_OK) { return status; } registerValue = (uint16_t)(ModbusOperationRxCount & 0xFFFFU); status = ModbusSetHoldingRegister(&ModbusSlave, APP_MODBUS_REG_OPERATION_RX_COUNT, registerValue); if (status != MODBUS_STATUS_OK) { return status; } registerValue = (uint16_t)(ModbusOperationTxCount & 0xFFFFU); status = ModbusSetHoldingRegister(&ModbusSlave, APP_MODBUS_REG_OPERATION_TX_COUNT, registerValue); if (status != MODBUS_STATUS_OK) { return status; } registerValue = (uint16_t)((ModbusProtocolErrorCount + ModbusReceiveErrorCount + ModbusSemaphoreErrorCount) & 0xFFFFU); status = ModbusSetHoldingRegister(&ModbusSlave, APP_MODBUS_REG_ERROR_COUNT, registerValue); if (status != MODBUS_STATUS_OK) { return status; } status = ModbusSetHoldingRegister(&ModbusSlave, APP_MODBUS_REG_SYSTEM_TICK_LOW, (uint16_t)(currentTick & 0xFFFFU)); if (status != MODBUS_STATUS_OK) { return status; } status = ModbusSetHoldingRegister(&ModbusSlave, APP_MODBUS_REG_SYSTEM_TICK_HIGH, (uint16_t)(currentTick >> 16U)); if (status != MODBUS_STATUS_OK) { return status; } status = ModbusSetHoldingRegister(&ModbusSlave, APP_MODBUS_REG_LAST_STATUS, (uint16_t)ModbusLastStatus); if (status != MODBUS_STATUS_OK) { return status; } status = ModbusSetHoldingRegister(&ModbusSlave, APP_MODBUS_REG_LAST_FRAME_LENGTH, ModbusFrameLength); return status; } /** * @brief 将线圈 0 至 7 的状态同步到 PLC Q0 至 Q7 输出。 * @return 成功返回 MODBUS_STATUS_OK;失败返回错误状态。 */ static MODBUS_STATUS AppSyncCoilsToGpio(void) { MODBUS_STATUS status; uint16_t coilAddress; uint8_t coilValue; GPIO_PinState pinState; for (coilAddress = 0U; coilAddress < APP_MODBUS_OUTPUT_COIL_COUNT; coilAddress++) { status = ModbusGetCoil(&ModbusSlave, coilAddress, &coilValue); if (status != MODBUS_STATUS_OK) { return status; } pinState = (coilValue != 0U) ? GPIO_PIN_RESET : GPIO_PIN_SET; HAL_GPIO_WritePin(AppOutputPorts[coilAddress], AppOutputPins[coilAddress], pinState); } return MODBUS_STATUS_OK; } /** * @brief 处理已封存的 Modbus RTU 请求帧。 * @param[in] rxLength 请求帧长度。 */ static void AppProcessFrame(uint16_t rxLength) { HAL_StatusTypeDef halStatus; MODBUS_STATUS status; uint16_t txLength; uint8_t isWriteOperation; isWriteOperation = 0U; if ((rxLength == 0U) || (rxLength > MODBUS_RX_BUF_SIZE)) { ModbusLastStatus = MODBUS_STATUS_FRAME_ERROR; ModbusProtocolErrorCount++; return; } if (ModbusRxOverflow != 0U) { ModbusLastStatus = MODBUS_STATUS_FRAME_ERROR; ModbusProtocolErrorCount++; return; } status = ModbusProcessFrame(&ModbusSlave, ModbusRxBuffer, rxLength, ModbusTxBuffer, MODBUS_TX_BUF_SIZE, &txLength); ModbusLastStatus = status; if (AppIsModbusStatusAccepted(status) != 0U) { ModbusLastActivityTick = OSTimeGet(); ModbusHasActivity = 1U; if (AppIsModbusWriteFunction( ModbusRxBuffer[APP_MODBUS_FUNCTION_INDEX]) != 0U) { ModbusOperationRxCount++; isWriteOperation = 1U; } } else if (status != MODBUS_STATUS_IGNORED) { ModbusProtocolErrorCount++; } if ((status == MODBUS_STATUS_OK) || (status == MODBUS_STATUS_BROADCAST)) { status = AppSyncCoilsToGpio(); if (status != MODBUS_STATUS_OK) { ModbusProtocolErrorCount++; } } status = AppUpdateStatusRegisters(); if (status != MODBUS_STATUS_OK) { ModbusProtocolErrorCount++; } if (txLength > 0U) { halStatus = AppTransmitResponse(txLength); if (halStatus != HAL_OK) { ModbusProtocolErrorCount++; } else if (isWriteOperation != 0U) { ModbusOperationTxCount++; } } } /** * @brief 记录中断上下文中无法立即恢复的通信错误。 */ static void AppRecordIsrError(void) { ModbusReceiveErrorCount++; } /** * @brief Modbus 协议处理任务。 * @param[in] argument 未使用。 */ static void AppTaskModbus(void *argument) { HAL_StatusTypeDef halStatus; INT8U osError; uint16_t rxLength; (void)argument; if (ModbusSlaveInit(&ModbusSlave, MODBUS_SLAVE_ADDR_DEFAULT) != MODBUS_STATUS_OK) { Error_Handler(); } if (AppSyncCoilsToGpio() != MODBUS_STATUS_OK) { Error_Handler(); } if (AppUpdateStatusRegisters() != MODBUS_STATUS_OK) { Error_Handler(); } halStatus = AppStartUartReception(); if (halStatus != HAL_OK) { Error_Handler(); } while (1) { OSSemPend(ModbusFrameSem, 0U, &osError); if (osError != OS_ERR_NONE) { ModbusSemaphoreErrorCount++; continue; } rxLength = AppGetReadyFrameLength(); if (rxLength > 0U) { AppProcessFrame(rxLength); halStatus = AppStartUartReception(); if (halStatus != HAL_OK) { ModbusReceiveErrorCount++; } } else if (AppUpdateStatusRegisters() != MODBUS_STATUS_OK) { ModbusProtocolErrorCount++; } } } /** * @brief 定期检测接收静默时间并判断 Modbus RTU 帧结束。 * @param[in] argument 未使用。 */ static void AppTaskT35(void *argument) { INT8U osError; uint16_t updateElapsedTicks; (void)argument; updateElapsedTicks = 0U; while (1) { OSTimeDly(APP_MODBUS_T35_CHECK_TICKS); (void)AppSealReceivedFrame(); updateElapsedTicks += APP_MODBUS_T35_CHECK_TICKS; if (updateElapsedTicks >= APP_MODBUS_STATUS_UPDATE_TICKS) { updateElapsedTicks = 0U; osError = AppPostModbusFrameSem(); if (osError != OS_ERR_NONE) { ModbusSemaphoreErrorCount++; } } } } /** * @brief USART1 单字节接收完成回调。 * @param[in] uartHandle 触发回调的串口句柄。 */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *uartHandle) { HAL_StatusTypeDef halStatus; if ((uartHandle == NULL) || (uartHandle->Instance != USART1)) { return; } if (ModbusFrameReady != 0U) { return; } if (ModbusRxLength >= MODBUS_RX_BUF_SIZE) { ModbusRxOverflow = 1U; return; } ModbusRxLength++; ModbusLastRxTick = OSTimeGet(); if (ModbusRxLength >= MODBUS_RX_BUF_SIZE) { ModbusRxOverflow = 1U; return; } halStatus = HAL_UART_Receive_IT(&Uart1Handle, &ModbusRxBuffer[ModbusRxLength], 1U); if (halStatus != HAL_OK) { AppRecordIsrError(); } } /** * @brief USART1 通信错误回调。 * @param[in] uartHandle 触发回调的串口句柄。 */ void HAL_UART_ErrorCallback(UART_HandleTypeDef *uartHandle) { if ((uartHandle == NULL) || (uartHandle->Instance != USART1)) { return; } ModbusRxOverflow = 1U; ModbusLastRxTick = OSTimeGet(); AppRecordIsrError(); } /** * @brief USART1 中断发送完成回调。 * @param[in] uartHandle 触发回调的串口句柄。 */ void HAL_UART_TxCpltCallback(UART_HandleTypeDef *uartHandle) { INT8U osError; if ((uartHandle == NULL) || (uartHandle->Instance != USART1)) { return; } osError = OSSemPost(ModbusTxSem); if (osError != OS_ERR_NONE) { ModbusSemaphoreErrorCount++; } } /** * @brief 进入不可恢复错误状态。 */ void Error_Handler(void) { __disable_irq(); while (1) { } }