# Modbus RTU Long-Connection Stability Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Keep the STM32 Modbus RTU slave responsive during 24-hour TouchWin communication without changing the existing Modbus contract. **Architecture:** Correct the uC/OS-II SysTick priority contract before any Modbus work runs. Make the Modbus task own all full receive restart operations: interrupt handlers record an error or request recovery, and the task retries the one-byte receive arm in bounded attempts. This avoids corrupting a complete frame and prevents an ignored HAL status from leaving USART1 unarmed. **Tech Stack:** STM32F407, STM32 HAL UART interrupt mode, TIM5 RTU timer, uC/OS-II, IAR Embedded Workbench, PowerShell regression checks, GCC host tests. ## Global Constraints - Preserve standard Modbus functions and private `0x41`, `0x42`, and `0x43` frames exactly. - Preserve current TouchWin PSW mapping and `0x42` RTC retention behavior. - Do not reintroduce D-register communication counters or history functions. - Keep all OS-aware interrupt priorities numerically greater than or equal to `CPU_CFG_KA_IPL_BOUNDARY`. - Do not overwrite unrelated dirty-worktree changes. --- ### Task 1: Add Runtime-Behavior Regression Test **Files:** - Create: `Core/Modbus/test_support/app_runtime/main.h` - Create: `Core/Modbus/app_runtime_test.c` - Modify: none - Test: `Core/Modbus/app_runtime_test.c` **Interfaces:** - Consumes: the real `Core/Src/main.c` and `Core/Src/stm32f4xx_it.c` through a fake HAL/uC-OS-II boundary. - Produces: exit code `0` only when SysTick uses an OS-aware priority and the receive recovery behavior is safe. - [ ] **Step 1: Write fake runtime support and a failing behavior test** Create `Core/Modbus/test_support/app_runtime/main.h` with the minimal HAL and uC/OS-II types, constants, GPIO/TIM register fakes, and function prototypes needed to compile the production sources. The fake APIs must record the SysTick priority, calls to `HAL_IncTick()` and `OS_CPU_SysTickHandler()`, scripted UART receive results, UART abort calls, and `OSSemPost()` calls. Create `Core/Modbus/app_runtime_test.c`. It must expose static functions only inside the test translation unit, then include the real production sources: ```c #define main AppFirmwareMain #define static #include "../Src/main.c" #undef static #undef main #include "../Src/stm32f4xx_it.c" ``` The test must assert the following observable behavior: ```c AppInitSystemTick(); assert(TestNvicIrq == SysTick_IRQn); assert(TestNvicPreemptPriority == CPU_CFG_KA_IPL_BOUNDARY); OSRunning = OS_TRUE; SysTick_Handler(); assert(TestHalTickCallCount == 1U); assert(TestOsCpuSysTickCallCount == 1U); TestReceiveResults[0U] = HAL_BUSY; TestReceiveResults[1U] = HAL_OK; assert(AppRecoverUartReception() == HAL_OK); assert(TestAbortReceiveCallCount == 1U); assert(TestReceiveCallCount == 2U); ``` A final test must call `HAL_UART_ErrorCallback()` with USART1 and an overrun error, then assert that it records a pending recovery and posts the frame semaphore without calling `HAL_UART_Receive_IT()` from the interrupt context. - [ ] **Step 2: Run the test and verify RED** Run: ```powershell gcc -std=c99 -Wall -Wextra -Werror -ICore/Modbus/test_support/app_runtime -ICore/Modbus Core/Modbus/app_runtime_test.c Core/Modbus/modbus.c -o app_runtime_test.exe .\app_runtime_test.exe ``` Expected: compilation fails because `AppInitSystemTick()`, `AppRecoverUartReception()`, and `ModbusReceptionNeedsRecovery` do not yet exist. This proves the test demands the missing runtime behavior. - [ ] **Step 3: Do not modify production sources in this task** The task is complete only when the real production translation units fail to compile against the intended new runtime behavior. ### Task 2: Fix the uC/OS-II SysTick Priority Contract **Files:** - Modify: `Core/Src/main.c:85-145` - Modify: `Core/Src/stm32f4xx_it.c:83-92` - Test: `Core/Modbus/app_runtime_test.c` **Interfaces:** - Consumes: `CPU_CFG_KA_IPL_BOUNDARY` from `app_cfg.h` and `OS_CPU_SysTickHandler()` from the uC/OS-II ARM port. - Produces: a SysTick handler at priority `4` that is allowed to invoke the uC/OS-II scheduler. - [ ] **Step 1: Add the application SysTick-priority initializer** Add this prototype with the other application initialization prototypes in `main.c`: ```c static void AppInitSystemTick(void); ``` Implement it immediately before `AppInitGpio()`: ```c static void AppInitSystemTick(void) { HAL_NVIC_SetPriority(SysTick_IRQn, CPU_CFG_KA_IPL_BOUNDARY, 0U); } ``` Call `AppInitSystemTick()` directly after `HAL_Init()` and before `SystemClock_Config()`. This overrides HAL's default priority `0` before the OS starts. - [ ] **Step 2: Replace the handwritten OS tick sequence** Replace the body of `SysTick_Handler()` with: ```c void SysTick_Handler(void) { HAL_IncTick(); if (OSRunning == OS_TRUE) { OS_CPU_SysTickHandler(); } } ``` Do not call `OSIntEnter()`, `OSTimeTick()`, or `OSIntExit()` directly from this handler after the replacement. - [ ] **Step 3: Run the runtime test and verify the SysTick behavior is green** Run: ```powershell gcc -std=c99 -Wall -Wextra -Werror -ICore/Modbus/test_support/app_runtime -ICore/Modbus Core/Modbus/app_runtime_test.c Core/Modbus/modbus.c -o app_runtime_test.exe .\app_runtime_test.exe ``` Expected: compilation may still fail only for the receive-rearm requirements; the SysTick-priority and SysTick-wrapper assertions must compile. ### Task 3: Make UART Receive Rearming Recoverable **Files:** - Modify: `Core/Src/main.c:43-110, 308-327, 732-786, 815-876` - Modify: `Core/Inc/main.h:37-54` - Test: `Core/Modbus/app_runtime_test.c` **Interfaces:** - Consumes: `HAL_UART_Receive_IT()`, `HAL_UART_AbortReceive_IT()`, `ModbusFrameSem`, and existing IAR debug fields. - Produces: `AppRecoverUartReception()` returning `HAL_OK` after one normal arm or one abort-and-rearm attempt; `ModbusReceptionNeedsRecovery` tells the Modbus task that an interrupt observed a rearm failure. - [ ] **Step 1: Add a pending-recovery state and recovery helper declaration** Beside the other volatile receive-state fields, add: ```c static volatile uint8_t ModbusReceptionNeedsRecovery; ``` Add this prototype with the receive helper declarations: ```c static HAL_StatusTypeDef AppRecoverUartReception(void); ``` - [ ] **Step 2: Implement a bounded task-context recovery helper** Keep `AppStartUartReception()` as the operation that clears frame state and arms byte zero. Add this helper immediately after it: ```c static HAL_StatusTypeDef AppRecoverUartReception(void) { HAL_StatusTypeDef halStatus; halStatus = AppStartUartReception(); if (halStatus != HAL_OK) { (void)HAL_UART_AbortReceive_IT(&Uart1Handle); halStatus = AppStartUartReception(); } AppModbusDebugRecoveryStatus = (int16_t)halStatus; return halStatus; } ``` This function is called only from `AppTaskModbus()`, never directly from a UART callback. - [ ] **Step 3: Request recovery from UART callbacks instead of silently failing** When either `HAL_UART_Receive_IT()` call in `HAL_UART_RxCpltCallback()` does not return `HAL_OK`, set `ModbusReceptionNeedsRecovery = 1U`, save the status to `AppModbusDebugRecoveryStatus`, and post `ModbusFrameSem` so the Modbus task can recover. Do not continue assembling the partial frame. In `HAL_UART_ErrorCallback()`, stop the RTU timer, save `uartHandle->ErrorCode`, set `ModbusReceptionNeedsRecovery = 1U`, and post `ModbusFrameSem`. Remove its direct calls to `HAL_UART_AbortReceive_IT()` and `AppStartUartReception()`; HAL has already ended the blocking receive transfer before invoking the error callback. - [ ] **Step 4: Make the Modbus task own retries** At startup, call `AppRecoverUartReception()` instead of `AppStartUartReception()`. In the task loop, after each frame processing and whenever `ModbusReceptionNeedsRecovery != 0U`, call the helper. On `HAL_OK`, clear `ModbusReceptionNeedsRecovery`. On failure, leave it set and delay one OS tick before the next semaphore-driven retry. Do not call `Error_Handler()` for a runtime receive-arm failure. - [ ] **Step 5: Run the runtime regression test and verify GREEN** Run: ```powershell gcc -std=c99 -Wall -Wextra -Werror -ICore/Modbus/test_support/app_runtime -ICore/Modbus Core/Modbus/app_runtime_test.c Core/Modbus/modbus.c -o app_runtime_test.exe .\app_runtime_test.exe ``` Expected: exit code `0`. ### Task 4: Build and Regression Verification **Files:** - Modify: none - Test: host protocol tests, RTC backup test, runtime behavior test, IAR build. **Interfaces:** - Consumes: final production sources and existing test harnesses. - Produces: evidence that the repair keeps protocol and RTC behavior intact. - [ ] **Step 1: Run existing host protocol tests** ```powershell gcc -std=c99 -Wall -Wextra -Werror -ICore/Modbus Core/Modbus/modbus.c Core/Modbus/modbus_test.c -o modbus_protocol_test.exe .\modbus_protocol_test.exe ``` Expected: exit code `0`. - [ ] **Step 2: Run RTC backup test** Use the existing test-support include paths and the same GCC command pattern already used by this project for `modbus_backup_test.c`. Expected: exit code `0` and preservation of only the extended register. - [ ] **Step 3: Build the IAR target** Open `EWARM/Modbus.ewp` in IAR Embedded Workbench and rebuild target `Modbus`. Expected: zero C compilation and link errors. - [ ] **Step 4: Inspect the final source diff** ```powershell git diff --check -- Core/Src/main.c Core/Src/stm32f4xx_it.c Core/Inc/main.h Core/Modbus/check_rtu_runtime_invariants.ps1 ``` Expected: no whitespace errors. ### Task 5: Hardware Long-Connection Acceptance **Files:** - Modify: none - Test: TouchWin and Modbus Poll against the rebuilt PLC. **Interfaces:** - Consumes: the final IAR binary, the existing TouchWin project, and Modbus Poll configured for the same RTU port. - Produces: a recorded stable long-duration TouchWin session. - [ ] **Step 1: Configure TouchWin cyclic requests** Keep the current serial settings and station `1`. Cycle at least one standard read (`0x01` or `0x03`), `0x41`, and `0x43`; periodically write and read back one test value with `0x42`. Do not use the screen Clear button as a PLC write. - [ ] **Step 2: Run the 24-hour TouchWin test** Start the cyclic TouchWin workload and leave the HMI and PLC powered for 24 hours. Record the start and finish times. The pass condition is zero HMI communication timeout/status errors and no restart of either device. - [ ] **Step 3: Perform Modbus Poll cross-check** After the TouchWin test, configure Modbus Poll for slave `1`, the same serial parameters, function `01`, scan rate `1000 ms`, and a safe coil read range. Run for one hour. Pass condition: `Err=0` throughout. - [ ] **Step 4: Capture failure evidence if a timeout occurs** Before restarting either device, halt IAR and record `AppModbusDebugSequence`, `AppModbusDebugUartErrorCode`, `AppModbusDebugRecoveryStatus`, `Uart1Handle.RxState`, and the program counter. This distinguishes application task lockup from a physical UART error.