The problem
Service technicians and riders need to see what the BMS is doing: cell voltage, temperature, active loads and alarms. A good BMS display shows the most important information first, lets the user drill into detail and never hides an alarm. Small OLEDs are common in battery packs and chargers for this purpose.
Objectives
- Bring up the BMS OLED over I2C and write a small graphics layer for text, bars and icons
- Read cell voltage, temperature and load state on the BMS unit
- Build a page-based menu driven by the six navigation buttons
- Show alarms with priority and let the user switch loads from a control page
What you need
From the EV Embedded Development Kit
- Second STM32F103C8T6 as the Smart BMS unit
- BMS power switch, OLED and six navigation buttons
- 1S battery with connector and T-sense input
- Load section: three switchable resistive loads with LEDs
- Charge/discharge control relay
Software
- STM32CubeIDE with HAL drivers
- Embedded C
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
- Monochrome OLED framebuffer
- Small OLEDs are commonly driven from a RAM framebuffer of one bit per pixel that is sent to the display controller over I2C. Confirm the controller and resolution from the kit documentation or the module marking.
- Page-based menu
- A list of screens with up and down to move between them, OK to enter detail or control, and back to return. A table of page functions keeps it simple.
- Button debouncing
- A mechanical button bounces for a few milliseconds; sampling it regularly and requiring a stable state filters out false presses.
- Alarm priority
- An alarm overrides the current page until acknowledged, and a small indicator remains while the condition persists.
Step-by-step guide
- 1
Plan the pages
List the pages: overview (voltage, SOC bar, temperature, alarm icon), cell detail (voltage, minimum and maximum since reset), temperature detail, loads control, alarm history and settings. Assign the six buttons, for example up, down, left, right, OK and back, checking the labels on the board. Sketch each page at the OLED's resolution. Decide which page appears at power-up and after a period of inactivity.
Checkpoint: Page sketches and a button map are agreed by the team.
- 2
Board setup
Enable the DIP switches for the BMS unit, OLED, buttons, battery, loads and relay. In CubeMX set the BMS controller to 72 MHz, configure I2C at 400 kHz for the OLED, the ADC with DMA for cell voltage and T-sense, the button GPIOs as inputs with pull-ups as the schematic requires, and load and relay GPIOs as outputs. Run an I2C scan to find the OLED address. Cell safety: check polarity and never short the cell.
Checkpoint: The I2C scan finds the OLED, and button presses print over UART or toggle an LED.
- 3
OLED driver and graphics
Write an initialisation sequence for the OLED controller from its datasheet, a framebuffer in RAM and a routine that sends the buffer over I2C, using DMA if possible so the CPU is free. Add drawing functions: pixel, horizontal and vertical lines, rectangles, a small font and a larger number font. Keep fonts in flash as const arrays. Keep fonts small to save flash.
Checkpoint: Text in two sizes, a rectangle and a filled bar display correctly, and a full refresh time is measured.
- 4
Measurements
Calibrate the cell voltage against a multimeter using the divider ratio on the schematic. Convert T-sense to temperature with the sensor's characteristic. Track minimum and maximum voltage and temperature since reset. Estimate SOC from resting voltage with a lookup table, noting it is approximate under load. Run measurements on a fixed timer, independent of the display, so readings stay regular even while the screen is redrawing, and filter voltage with a short average to steady the display.
Checkpoint: Voltage agrees with a multimeter within 10 mV and temperature agrees with a thermometer.
- 5
Button handling
Sample buttons every 5 ms in a timer interrupt and debounce with a stable count. Generate events for short press, long press and auto-repeat when held. Put events into a small queue so the menu code processes them in order. Keep the timing constants in one place so they can be tuned. Test with quick taps and long holds on every button, and check that two buttons pressed together are handled in a defined way rather than causing unexpected menu jumps.
Checkpoint: Each button produces exactly one event per press, and holding up or down auto-repeats.
- 6
Menu engine
Implement the menu as a table of pages, each with a draw function and an event handler. Up and down move between pages, OK enters a page's detail or edit mode and back returns. Show a page indicator such as dots at the bottom. Redraw only when data or page changes, or at a fixed slow rate. Return to the overview automatically after a period with no presses.
Checkpoint: The user can reach every page and return to the overview with the back button.
- 7
Load control page
On the loads page, let the user select each load with left and right and toggle it with OK. Show the state with icons and match the load LEDs. Block load changes if an alarm such as under-voltage is active, and say why on the screen. Ask for confirmation before switching on all three loads at once, and show the expected voltage drop so the user understands the effect of each load before applying it.
Checkpoint: Loads switch from the menu and the screen, LEDs and measured voltage drop agree.
- 8
Alarms
Define alarms for under-voltage, over-voltage and over-temperature with thresholds and hysteresis. An alarm opens a full-screen message until acknowledged with OK, then a blinking icon remains on the overview. Keep the last few alarms with a time since start-up on the history page. Make sure the alarm check runs in the measurement loop, not in the display code, so an alarm is raised even if the user is in a menu. Test each alarm with a threshold lowered in a test build.
Checkpoint: A test threshold triggers the alarm screen, acknowledgement returns to the page and the history records it.
- 9
Test and document
Test every page, button and alarm. Ask someone unfamiliar with the project to find a set of information and time them. Document the page map, driver, menu engine and test results with photographs of each page. Note which information was hardest to find and change the page order or labels if needed. Include the measured refresh time and the CPU time used by the display, so a reader can judge the cost of the graphics layer.
Checkpoint: A test table and usability notes are in the report.
Testing & validation
- Voltage and temperature readings checked against a multimeter and thermometer
- Every page reachable and every button event handled
- Alarm shown over any page and recorded in history
- Display refresh time and CPU load measured
Expected results
A navigable BMS dashboard on the Smart BMS unit's OLED with live readings, load control, alarms and history, built on a reusable graphics and menu layer.
Take it further
- Plot a voltage history graph on a trend page
- Add an INA219 add-on and show current and power
- Store alarm history with RTC timestamps
- Mirror the dashboard to the main controller's LCD over the link between controllers
Viva and review questions
Why use a framebuffer?
Drawing into RAM and sending the whole buffer avoids partial updates and lets us compose text, bars and icons before display.
How did you stop one button press counting twice?
By sampling every 5 ms and only accepting a change once the state has been stable for several samples.
Why block load changes during an alarm?
Switching in more load during under-voltage would discharge the cell further, so the menu refuses and explains why.