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.
0x41, 0x42, and 0x43 frames exactly.0x42 RTC retention behavior.CPU_CFG_KA_IPL_BOUNDARY.Files:
Core/Modbus/test_support/app_runtime/main.hCore/Modbus/app_runtime_test.cCore/Modbus/app_runtime_test.cInterfaces:
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.
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:
#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:
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.
Run:
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.
The task is complete only when the real production translation units fail to
compile against the intended new runtime behavior.
Files:
Core/Src/main.c:85-145Core/Src/stm32f4xx_it.c:83-92Core/Modbus/app_runtime_test.cInterfaces:
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.
Add this prototype with the other application initialization prototypes in
main.c:
static void AppInitSystemTick(void);
Implement it immediately before AppInitGpio():
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.
Replace the body of SysTick_Handler() with:
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.
Run:
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.
Files:
Core/Src/main.c:43-110, 308-327, 732-786, 815-876Core/Inc/main.h:37-54Core/Modbus/app_runtime_test.cInterfaces:
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.
Beside the other volatile receive-state fields, add:
static volatile uint8_t ModbusReceptionNeedsRecovery;
Add this prototype with the receive helper declarations:
static HAL_StatusTypeDef AppRecoverUartReception(void);
Keep AppStartUartReception() as the operation that clears frame state and
arms byte zero. Add this helper immediately after it:
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.
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.
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.
Run:
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.
Files:
Interfaces:
Consumes: final production sources and existing test harnesses.
Produces: evidence that the repair keeps protocol and RTC behavior intact.
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.
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.
Open EWARM/Modbus.ewp in IAR Embedded Workbench and rebuild target
Modbus. Expected: zero C compilation and link errors.
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.
Files:
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.
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.
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.
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.
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.