The problem
HMI logic grows into a tangle of flags and nested if-statements that are hard to test and change. Model-based design with statecharts makes behaviour explicit, testable and reviewable, and automotive teams use it widely. The open question for small controllers is the cost: how much flash, RAM and speed does a model-driven approach use, and does it really reduce defects?
Objectives
- Model an EV HMI (start-up, drive modes, settings, clock, warnings) as a hierarchical statechart
- Implement it twice: from the model using a statechart framework or code generator, and hand-coded as a baseline
- Verify both against the same test suite derived from the model
- Quantify flash, RAM, event response time, defects found and development effort
What you need
From the EV Embedded Development Kit
- STM32F103C8T6 microcontroller unit (main controller)
- 16×2 LCD
- 4×3 matrix keypad
- RTC with CR2032 backup cell
- RGB LEDs and a buzzer
- Analog potentiometer input
- Serial UART interface
Software
- STM32CubeIDE with HAL drivers
- Embedded C
- A statechart tool: MATLAB Stateflow with Embedded Coder, or the open-source QP/C framework with the QM modelling tool, or an SCXML-to-C generator
- Python for the test harness
Extra parts
None. Everything this project needs is on the kit.
This project uses the kit's lithium cell. Respect polarity, never short the terminals, and don't leave the cell charging unattended.
Key concepts
- Hierarchical statechart
- A state machine in which states can contain sub-states, with entry and exit actions and transitions that apply to a whole group, reducing duplication compared with flat state machines.
- Run-to-completion
- Each event is processed fully before the next is taken, which keeps behaviour predictable and is the usual execution model for statecharts.
- Event queue
- Keypad presses, timer ticks and sensor thresholds are posted as events to a queue that the statechart processes, separating inputs from logic.
- Model-based testing
- Deriving test cases from the model, such as covering every transition, so the implementation is checked against the specified behaviour.
Step-by-step guide
- 1
Specify the HMI
Write the HMI specification: power-up self-test, idle, ready, drive with ECO, NORMAL and SPORT sub-states, a settings menu with clock setting and brightness, and a warning overlay that can interrupt any state. Define events: key presses, ticks, potentiometer speed thresholds and warning raise and clear. Choose metrics before implementation: flash, RAM, worst-case response time, defects and effort in hours. Write each requirement with an ID.
Checkpoint: A specification with an event list and a metrics plan reviewed by the guide.
- 2
Model the statechart
Draw the hierarchical statechart in the chosen tool. Use composite states for drive and settings, history states to return to the last drive mode, and an orthogonal region or a top-level transition for the warning overlay. Review the model for unreachable states and missing transitions, and simulate it in the tool if it supports simulation. Keep the model under version control so every change can be traced to a test result.
Checkpoint: The model simulates the main scenarios with no unreachable states.
- 3
Board setup and platform layer
Enable the DIP switches for LCD, keypad, RTC, LEDs, buzzer, potentiometer and UART. In CubeMX set SYSCLK to 72 MHz and configure the peripherals. Write a platform layer shared by both implementations: LCD driver, keypad scanner that posts events, RTC read and write over I2C, a 10 ms tick that posts timer events, and a UART trace of every event and state change.
Checkpoint: Key presses and ticks appear as events in the UART trace.
- 4
Model-derived implementation
Generate C code from the model with the tool's code generator, or implement it with a statechart framework such as QP/C that maps model states to handler functions. Keep generated code unchanged and connect it to the platform layer through event posting and action functions. Record effort in hours. Note any manual work needed around the generated code, such as glue for events, since it counts towards effort. Keep the build reproducible from the model with one command so that model changes always reach the target.
Checkpoint: The model-derived build runs the full HMI on the kit and the trace matches the model's simulation.
- 5
Hand-coded baseline
Implement the same HMI by hand as a team member would without a model, using a flat switch-case state machine with flags. Use the same platform layer and specification. Record effort. To keep the comparison fair, a different team member should write it without looking at the generated code. Use the same coding standard and review process for both builds, and keep the baseline in its own branch so its history shows how it was developed.
Checkpoint: The hand-coded build runs the full HMI on the kit.
- 6
Test suite from the model
Derive tests from the model: every transition at least once, every state's entry and exit actions, and scenarios crossing hierarchy such as a warning during settings. Write the tests as event sequences sent over UART from Python, with the expected state trace. Run both implementations against the same suite. Compare traces automatically and report any difference in state or output. Run the suite after every change to either build so that regressions are caught immediately.
Checkpoint: The test suite achieves full transition coverage on the model and runs automatically on both builds.
- 7
Defect tracking
Log every defect found in either implementation with its cause, the test that found it and the fix time. Classify defects, for example missing transition, wrong action, timing or display error. Do not fix defects in one implementation using knowledge from the other without logging it. Also log defects found in the model itself during review, since catching them before code is written is one of the claimed benefits of model-based design and should be counted.
Checkpoint: A defect log with classification exists for both builds.
- 8
Resource and timing measurement
Measure flash and RAM from the linker map file for each build. Measure response time from a key press interrupt to the LCD update with a GPIO toggle and a timer, over many presses, and report the worst case. Measure CPU load at idle and during busy scenarios. Build both with the same compiler and optimisation settings, and list what the framework or generated code contributes to flash so the overhead of the approach is visible separately.
Checkpoint: A table of flash, RAM, mean and worst-case response time and CPU load for both builds.
- 9
Change experiment
Introduce the same change request to both implementations, such as adding a REVERSE mode with a speed limit and a warning. Measure effort, lines changed and new defects. This tests the maintainability claim of model-based design. Ideally have someone who did not write either build make the change, to reduce the effect of familiarity, and run the full test suite on both builds afterwards to count regressions introduced by the change.
Checkpoint: Change effort and defects are recorded for both builds.
- 10
Analysis and thesis write-up
Compare the two approaches on all metrics with honest discussion of the small sample: one HMI, one team. Discuss threats to validity, such as team skill and familiarity with the tool. Present the statechart, the trace-based test method and the results. Include the statechart diagram, the trace format and enough detail of the test harness that another group could repeat the comparison with their own HMI or tool.
Checkpoint: A thesis chapter with the comparison tables and a threats-to-validity section.
Testing & validation
- Full transition coverage of the model by the test suite on both builds
- Trace equivalence between model simulation and on-target behaviour
- Flash and RAM from linker maps
- Worst-case key-to-display response time measured on target
- Change-request effort and defects for both builds
Expected results
A hierarchical statechart model of an EV HMI running on the kit from model-derived code, a hand-coded baseline, an automated trace-based test suite and a quantified comparison of resources, timing, defects and maintainability.
Research angle for your M.Tech thesis
Research questions: on a 64 KB Cortex-M3 controller, what does a model-based statechart implementation cost in flash, RAM and response time compared with hand-coded logic, and does it reduce defects and change effort? Metrics: flash, RAM, worst-case response, transition coverage, defects by class, effort and lines changed per change request. Baseline: hand-coded switch-case implementation. Results from a single team and HMI are a case study; strengthening them with several teams or a second HMI, and discussing threats to validity, would make the work suitable for an embedded software engineering venue.
Take it further
- Formally check model properties, such as 'a critical warning is always shown within a time bound', with a model checker
- Port the model to FreeRTOS with active objects for inputs and display
- Add CAN events from other nodes to the model
- Generate the test suite automatically from the model
Viva and review questions
What does hierarchy give you over a flat state machine?
Shared behaviour, such as a warning overlay or power-off from any drive mode, is written once on a parent state instead of being repeated in every sub-state.
Why use run-to-completion?
Each event is fully processed before the next, so the state is always consistent and behaviour does not depend on interrupt timing.
How did you keep the comparison fair?
Both builds used the same specification, platform layer and test suite, and different team members wrote them without seeing each other's code.
How did you measure response time?
A GPIO was set in the key interrupt and cleared after the LCD update, and a timer captured the interval over many presses to find the worst case.