The problem
Protection logic is the most safety-relevant part of a BMS. Hand-coded state machines are prone to missed transitions, wrong debounce timing and faults that clear when they should stay latched. Designing the logic in Stateflow makes it reviewable as a diagram, testable in simulation and automatically converted to code, which is how many production BMS teams work.
Objectives
- Write numbered requirements for cell-level protection with thresholds and timings.
- Implement protection states, debounce and latching in Stateflow.
- Test every requirement with scripted simulation cases.
- Generate C code and compare its outputs with the model in software-in-the-loop.
- Review the generated code's structure and memory use.
What you need
Tools and toolboxes
- Simulink — Test harness, input signal generation and the model hosting the chart.
- Stateflow — Protection state machine with debounce timers, latching and recovery.
- Embedded Coder — C code generation, code report and software-in-the-loop simulation.
- MATLAB Coder — Required by Embedded Coder.
- Simulink Test (optional) — Test Manager for the requirement test cases; a MATLAB script with assert statements works without it.
Software
- MATLAB R2023b or later
- Simulink
- Stateflow
- A C compiler supported by MATLAB (for SIL)
Extra parts and data
None beyond the software above.
Key concepts
- Debounce
- A fault is declared only after the condition persists for a set time, for example 500 ms, which prevents noise or a brief spike from tripping protection.
- Latched fault
- Some faults, such as over-temperature, stay active until an explicit reset even after the condition clears, so the system cannot cycle in and out of a dangerous state.
- Hysteresis on recovery
- A warning clears at a threshold safely inside the trip level, for example under-voltage trips at 2.8 V and clears at 3.0 V.
- Software-in-the-loop
- The generated C code is compiled on the PC and run in place of the model with the same inputs, so any difference between model and code shows up in a direct comparison.
Step-by-step guide
- 1
Write the requirements
Write a numbered table using example values for a lithium-ion NMC pack: cell over-voltage trips at 4.25 V after 500 ms, under-voltage at 2.8 V after 1 s with recovery above 3.0 V, discharge over-current above 2C after 200 ms, over-temperature above 60 °C after 2 s and latched until reset. Define outputs: charge allowed, discharge allowed, fault code. Mark which faults latch.
Checkpoint: A requirements table where every row has a threshold, a time, a recovery rule and an output.
- 2
Design the chart structure
In Stateflow, create parallel (AND) states, one per protection function, so each runs independently. Inside each, use states Normal, Pending and Fault. Enter Pending when the condition becomes true and use after(0.5, sec) or a counter for the debounce before entering Fault. Return to Normal from Pending if the condition clears. Combine outputs so any active fault removes the relevant permission.
Checkpoint: A chart diagram with one parallel state per protection, readable on one page.
- 3
Implement latching and reset
For latched faults, leave Fault only on a reset input event and only if the condition has cleared. For non-latched faults, leave Fault when the recovery threshold is crossed. Use a discrete sample time of 10 ms for the chart and counters rather than absolute time if you plan to generate code for a simple scheduler. Make the reset input an edge-triggered event so holding the reset line high cannot repeatedly clear a fault, and record the chosen counter values in the parameter script.
Checkpoint: A manual test where over-temperature trips, the temperature returns to normal, and the fault stays until reset is pressed.
- 4
Build the test harness
Create a harness model with Signal Editor scenarios for each requirement: a voltage step above the trip level, a spike shorter than the debounce, a slow ramp through the recovery band, and combined faults. Log chart states and outputs. If you have Simulink Test, create a test harness and test cases in the Test Manager with pass criteria; otherwise write a script that runs each scenario with sim() and asserts on the outputs.
Checkpoint: Every requirement has at least one test case, and the list of tests maps back to requirement numbers.
- 5
Run and debug
Run all test cases. When one fails, use Stateflow animation and breakpoints on transitions to see why. Typical problems are debounce timers that do not reset when the condition clears briefly and outputs that change before the debounce completes. Fix the chart, not the test, unless the test itself is wrong. Keep each fixed defect as a regression test case, so the same mistake cannot return unnoticed when the chart is changed later. Record the date and cause of each defect in the log.
Checkpoint: All test cases pass, with a short log of the bugs found and fixed.
- 6
Check coverage of the chart
Make sure every state and transition was visited by at least one test. If Simulink Coverage is available, run the tests with coverage and read the decision and condition coverage report; otherwise inspect the logged active states for each test and tick off states and transitions by hand. Add tests for any missed transition. Record which test reaches each state, so a reviewer can see the evidence directly, and note any transition that is reachable only by a combination of faults.
Checkpoint: Every state and transition in the chart exercised by at least one test.
- 7
Configure code generation
Set the system target file to ert.tlc and make the protection subsystem atomic with a reusable function interface. Choose integer or single data types for inputs, for example voltages in millivolts as uint16, to suit a low-cost MCU. Generate code and open the report. Find the step function, the state variables and how the debounce counters appear. Set the code generation objective to efficiency or traceability as appropriate and keep the settings in a configuration reference so they are applied consistently.
Checkpoint: Generated code builds with no errors and the report lists the code's RAM and ROM use.
- 8
Run software-in-the-loop
Set the model's simulation mode to Software-in-the-Loop (SIL), or use a SIL block, and rerun every test case. Compare SIL outputs with normal-simulation outputs using the Simulation Data Inspector's comparison feature. They should match exactly for integer logic. Any difference points to a data-type or configuration issue. Record the compiler and MATLAB version used for SIL in the report. If outputs differ, compare data types of chart inputs and outputs between the two modes first, since mismatches there are the most common cause.
Checkpoint: SIL and model outputs identical across all test cases.
- 9
Review the code
Read the generated step function and explain to a reviewer how each requirement is implemented. Check there is no dynamic memory allocation and no recursion, and that variable names trace back to the chart. Note the size of the code and data. Discuss how this code would be called from a 10 ms task on a microcontroller. Also confirm that each debounce counter is bounded and cannot overflow, even if a fault condition persists for a very long time.
Checkpoint: An annotated code excerpt mapping key lines to requirement numbers.
- 10
Report
Present the requirements, chart design, tests and results, coverage, code generation settings, SIL comparison and code review. State the limitations: example thresholds, no cell balancing, no SOC-based limits and no hardware test. Note that real BMS protection needs hardware back-up protection and a safety analysis, which this project does not replace. Attach the test scripts and the code-generation report so a reviewer can rerun the tests and inspect the code themselves.
Checkpoint: A report with a requirements-to-test-to-code traceability table.
Testing & validation
- Each requirement covered by at least one passing simulation test.
- Spikes shorter than the debounce time do not trip faults.
- Latched faults persist until reset and only clear when the condition has cleared.
- Every state and transition exercised by tests.
- SIL outputs identical to model outputs for all tests.
Expected results
A Stateflow BMS protection design with requirement-based tests, full state and transition coverage, and generated C code verified against the model in software-in-the-loop.
Take it further
- Deploy the generated code on the DIYguru kit's STM32 with voltage from the kit's battery sense input.
- Add SOC- and temperature-dependent current limits.
- Prove properties with Simulink Design Verifier.
- Add a balancing request state machine.
Viva and review questions
Why use parallel states for each protection?
Each protection must be evaluated independently and at the same time, so parallel states keep them separate and stop one fault's logic from blocking another's.
Why must some faults latch?
For faults like over-temperature, automatically clearing as soon as the reading drops could let the system cycle into a hazardous condition repeatedly; latching forces a deliberate reset.
What does SIL testing prove?
That the generated C code produces the same outputs as the model for the same inputs, so the tests run on the model also apply to the code.
Learn it at DIYguru
These DIYguru courses and programmes teach what this project needs.
