The problem
Safety-relevant EV control units such as BMS and motor controllers must prove that every task meets its deadline. Measurement-based timing can miss rare worst-case paths, while static analysis can be so pessimistic that it wastes processor capacity. Engineers need evidence of how far apart the two are on small, cache-less microcontrollers like the Cortex-M3.
Objectives
- Port a representative EV task set (BMS sampling, protection logic, CAN transmit, HMI update) to FreeRTOS on the main controller.
- Instrument each task with the DWT cycle counter and GPIO markers to capture execution-time distributions.
- Derive analytical WCET bounds for selected tasks from control-flow analysis and instruction timing.
- Apply fixed-priority response-time analysis including kernel overheads and blocking, and compare with measured response times.
- Quantify the gap between measured, statistical (extreme value) and analytical worst cases.
What you need
From the EV Embedded Development Kit
- STM32F103C8T6 microcontroller unit (Arm Cortex-M3)
- Analog potentiometer input
- 1S battery with connector and T-sense input
- CAN bus interface
- 16×2 LCD
- RGB LEDs and a buzzer
- Serial UART interface
Software
- STM32CubeIDE
- Embedded C with HAL/LL drivers
- FreeRTOS
- Python (pandas, NumPy, SciPy, matplotlib) for timing analysis
- Logic analyser capture software
Extra parts
- A USB logic analyser (8 channels, 24 MHz class or better) — To timestamp GPIO task markers independently of the CPU being measured, so the instrumentation itself can be validated.
This project uses the kit's lithium cell. Respect polarity, never short the terminals, and don't leave the cell charging unattended.
Key concepts
- Worst-case execution time (WCET)
- The longest time a piece of code can take on given hardware for any input and state. Measured maxima are lower bounds; static analysis gives safe upper bounds if its hardware model is correct.
- Response-time analysis (RTA)
- An iterative formula for fixed-priority scheduling: a task's response time is its own WCET plus blocking plus interference from all higher-priority tasks released during that window.
- DWT cycle counter
- The Cortex-M3 Data Watchpoint and Trace unit has a 32-bit CYCCNT register that counts core clock cycles, giving about 14 ns resolution at 72 MHz with almost no overhead.
- Flash wait states and prefetch
- At 72 MHz the F103 flash needs two wait states; the prefetch buffer hides most of them on straight-line code but not on taken branches, which makes timing input-dependent.
- Extreme value theory (EVT)
- A statistical method that fits the tail of measured execution times to estimate a probabilistic WCET, for example a time exceeded with probability below 10^-9 per run.
Step-by-step guide
- 1
Define the task set and timing requirements
Write a task table for a small EV controller: a 10 ms cell-sampling task (ADC on the battery and T-sense), a 10 ms protection task that drives the charge/discharge relay, a 100 ms CAN status transmit, a 200 ms LCD update and a sporadic keypad handler. Give each a period, deadline and priority under rate-monotonic ordering. Record which tasks share data and need a mutex, because blocking terms enter the analysis later. Keep the code paths realistic, including fault branches that run rarely.
Checkpoint: A reviewed task table with periods, deadlines, priorities and shared resources, and a target total utilisation below the Liu–Layland bound for comparison.
- 2
Configure the board and clocks
In CubeMX set HSE with PLL to 72 MHz, APB1 at 36 MHz and two flash wait states with prefetch enabled. Enable the ADC with DMA, CAN, a UART at 921600 baud for trace output and four spare GPIOs as task markers. Turn on only the DIP switches for the battery, potentiometer, CAN, LCD and UART blocks so stray peripherals do not load the bus. Set the FreeRTOS tick to 1 kHz and use static allocation so heap behaviour does not vary between runs.
Checkpoint: The board boots, the system clock is confirmed as 72 MHz by toggling a pin from a timer and measuring it, and FreeRTOS runs an idle task.
- 3
Build the cycle-accurate instrumentation
Enable the DWT counter by setting TRCENA in CoreDebug->DEMCR and CYCCNTENA in DWT->CTRL. Wrap each task body in macros that read DWT->CYCCNT at entry and exit and store the difference in a per-task ring buffer, and set a GPIO high and low at the same points. Use FreeRTOS trace hooks (traceTASK_SWITCHED_IN and traceTASK_SWITCHED_OUT) to subtract preemption time so you record execution time, not elapsed time. Stream buffers to the PC over UART from the lowest-priority task.
Checkpoint: The measured cost of the instrumentation itself (an empty wrapped function) is known in cycles and is constant across 10,000 runs.
- 4
Validate measurements against the logic analyser
Capture the GPIO markers with the logic analyser at its highest sample rate while the firmware logs CYCCNT. Align the two traces using a start marker and compare per-job execution times. Differences should be within the analyser's sample period plus the known instrumentation overhead. This step matters because a peer reviewer will ask how you know the counter is trustworthy when interrupts and context switches are happening.
Checkpoint: For at least 1,000 jobs per task, CYCCNT and logic-analyser durations agree within one analyser sample period after overhead correction.
- 5
Drive the tasks through their worst paths
Execution time depends on inputs, so design stimulus that exercises each branch. Sweep the potentiometer, switch the three resistive loads, force over-temperature by warming the T-sense input gently with a fingertip, flood the CAN transmit queue and hold keys on the keypad. Also run a randomised campaign of several hours. Record path coverage with a simple branch counter so you can show which paths the measurements actually hit. Never leave the cell charging unattended during long campaigns.
Checkpoint: Branch coverage of at least 95% of the instrumented decision points, and a histogram of execution times per task with the observed maximum identified.
- 6
Compute analytical WCET bounds
For the two or three most critical tasks, extract the control-flow graph from the disassembly (arm-none-eabi-objdump), bound every loop, and assign cycle costs from the Cortex-M3 Technical Reference Manual, adding flash wait-state penalties for taken branches and loads from flash. Solve for the longest path by implicit path enumeration (an integer linear program in Python with SciPy) or by tree-based summation for simple code. Document every assumption, especially about the prefetch buffer and bus contention with DMA.
Checkpoint: An analytical WCET for each chosen task that is greater than or equal to its observed maximum; if it is lower, an assumption is wrong and must be found.
- 7
Measure kernel overheads and blocking
Response-time analysis needs the cost of the tick interrupt, context switch, and mutex lock and unlock. Measure each with CYCCNT in isolation and under load. Measure the longest critical section in each task that holds a shared mutex, because with priority inheritance that is the blocking term for higher-priority tasks. Also measure the worst interrupt latency of the ADC DMA and CAN receive handlers, since ISRs preempt every task.
Checkpoint: A table of overhead costs (minimum, mean, maximum in cycles) and the blocking term for every task.
- 8
Run response-time analysis and compare
Implement the RTA recurrence in Python: R = C + B + sum over higher-priority tasks of ceil(R / T) × (C + overhead), iterated to a fixed point. Run it three times, with measured maxima, with EVT-based estimates and with analytical bounds. Compare each predicted response time with the worst response time actually observed on the board, measured from job release (the tick) to completion. Then raise utilisation step by step by adding a synthetic load task until the analysis and the hardware first disagree on schedulability.
Checkpoint: A table of predicted versus observed worst response times for every task under all three WCET sources, and the utilisation at which each method declares the set unschedulable.
- 9
Apply extreme value analysis
Fit a generalised extreme value or generalised Pareto distribution to block maxima of the measured execution times using SciPy. Check the independence and identical-distribution assumptions with a runs test and autocorrelation, because EVT is invalid if they fail. Report the estimated execution time at exceedance probabilities of 10^-6 and 10^-9 with confidence intervals, and compare them with the analytical bound.
Checkpoint: Goodness-of-fit plots and pWCET estimates that sit between the observed maximum and the analytical bound, or a documented explanation if they do not.
- 10
Write up the testbed and findings
Package the firmware, the Python analysis notebooks and the raw traces so another researcher can reproduce every number. Write the thesis chapter around three findings: how much pessimism static analysis adds on a cache-less Cortex-M3, how much measurement alone can underestimate, and how kernel overheads change schedulability. Include threats to validity such as compiler optimisation level, instrumentation intrusion and input coverage.
Checkpoint: A reproducibility package that regenerates every figure from raw logs with one script, and a draft chapter reviewed by your guide.
Testing & validation
- CYCCNT and logic-analyser durations agree within one sample period after overhead correction.
- Analytical WCET is never below the observed maximum for any analysed task.
- RTA with analytical bounds never predicts a deadline met that is missed on hardware.
- Results are repeated at -O0, -O2 and -Os to show the effect of compiler optimisation.
Expected results
A reproducible timing testbed and a dataset of execution-time traces for a realistic EV task set, with tables and plots showing measured, statistical and analytical WCET side by side and the schedulability margin each method gives.
Research angle for your PhD
Research questions: on a cache-less Cortex-M3 with flash wait states, how large is the gap between measured, probabilistic and static WCET for typical EV control code, and which hardware features cause it? Does measurement-based probabilistic timing analysis give safe bounds when inputs are physically constrained, as in a BMS? Metrics: pessimism ratio (bound / observed maximum), path coverage, schedulable utilisation, overhead share of response time. Baselines: high-water-mark measurement with a safety margin, static analysis, EVT. Publishable contributions would be a validated, open testbed and dataset for a low-end automotive-class MCU, a characterised model of F103 flash and prefetch timing, or evidence of when EVT assumptions break for control code.
Take it further
- Add DMA-heavy workloads and quantify bus contention on the AHB.
- Compare FreeRTOS with a bare-metal cyclic executive on the same task set.
- Repeat on the second STM32 (the Smart BMS unit) running its own firmware to study a two-ECU system.
- Integrate an open-source static WCET tool if one supports the Cortex-M3 and compare with your manual analysis.
Viva and review questions
Why is the observed maximum execution time not a safe WCET?
Testing only shows the paths and hardware states you happened to exercise. A rarely taken branch or an unlucky flash alignment can exceed it, so the observed maximum is only a lower bound.
Why does the Cortex-M3 make static analysis easier than a Cortex-A core?
The F103 has no data or instruction cache and an in-order three-stage pipeline, so instruction timing is close to deterministic. The main variable is flash wait states, which the prefetch buffer only partly hides.
How did you stop your measurement code from distorting the result?
We measured the instrumentation's own cost and subtracted it, kept it constant-time, streamed data only from the lowest-priority task, and cross-checked against an external logic analyser.
What does the blocking term in RTA represent?
The longest time a task can wait for a lower-priority task holding a shared resource. With priority inheritance it is bounded by the longest relevant critical section.