The problem
Missed brake, tyre and coolant checks shorten an EV's life and can make it unsafe. Reminders must work by both time and distance, survive power loss and be resettable only by someone authorised.
Objectives
- Keep date and time with the battery-backed RTC across power cycles.
- Emulate an odometer from the potentiometer throttle and accumulate distance.
- Store service items, intervals and last-service records in SPI flash.
- Raise reminders on the LCD and buzzer and allow a PIN-protected technician reset.
What you need
From the EV Embedded Development Kit
- RTC with CR2032 backup cell
- SPI flash memory
- 16×2 LCD
- 4×3 matrix keypad
- Buzzer
- RGB LEDs
- Analog potentiometer input
- STM32F103C8T6 main controller
Software
- STM32CubeIDE with HAL drivers
- Embedded C
- Python 3 with pyserial (optional, for reading the service history)
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
- Battery-backed RTC
- The CR2032 keeps the clock running when main power is off, so the controller knows how much time has passed since the last service.
- Dual-criterion intervals
- A service is due when either the time limit or the distance limit is reached, whichever comes first.
- Wear-aware storage
- Odometer updates are frequent, so they are appended to flash rather than rewritten in one place, spreading erase cycles.
- Emulated odometer
- With no wheel-speed sensor, distance is integrated from a speed derived from the potentiometer throttle.
Step-by-step guide
- 1
Define service items and rules
List items such as brake check, tyre check, general inspection and battery health check, each with a time interval (months) and distance interval (km), and a warning margin (for example warn 15 days or 200 km early). Decide what the rider sees, when the buzzer sounds and how a technician resets an item. Set short demonstration intervals in a test build so the reminder can be shown in minutes.
Checkpoint: A table of items with intervals, margins and alert behaviour, plus demo-mode values.
- 2
Configure peripherals
Set the DIP switches for the RTC, SPI flash, LCD, keypad, buzzer and RGB LEDs. In CubeMX enable the RTC (if your kit's RTC is the STM32's internal RTC on VBAT, enable the LSE crystal and HAL RTC; if it is an external chip, enable its bus instead), SPI for the flash with a GPIO chip-select, GPIO for the LCD in 4-bit mode and the keypad rows and columns, and ADC for the potentiometer.
Checkpoint: The LCD shows the RTC date and time, and it keeps counting correctly after power is removed for a minute.
- 3
Set and maintain the clock
Write a keypad menu to set date and time, and store a flag (in a backup register or in flash) so the clock is only set once. Convert RTC date and time to a day count or Unix time for easy interval arithmetic. Check leap-year handling with test dates. Validate entered values (month 1 to 12, valid day for the month) before writing them, and show the set date for confirmation before saving.
Checkpoint: Interval arithmetic gives the correct number of days across a month end and a leap day in unit tests.
- 4
Build the emulated odometer
Read the potentiometer every 100 ms, map it to speed with a simple first-order model, and integrate distance in metres using integer arithmetic. In demo mode, apply a scale factor so the odometer advances quickly. Show speed and total distance on the LCD. State clearly on the display and in the report that speed and distance are emulated. Keep the fractional metres in a separate accumulator so rounding errors do not build up over long runs, and only commit whole metres to the odometer total.
Checkpoint: Holding the throttle at a fixed position for a measured time gives the expected distance within rounding error.
- 5
Store data in SPI flash
Write a flash driver (JEDEC ID 0x9F, write enable 0x06, page program 0x02, sector erase 0x20, read 0x03, status 0x05). Use one sector for the service table and last-service records, and a separate log sector where odometer snapshots are appended every 100 m with a sequence number. On boot, read the newest valid snapshot. Erase and restart the log sector only when it fills.
Checkpoint: After many power cycles the odometer and service records resume exactly where they were.
- 6
Implement the due-check logic
Once a minute and at every power-up, compare elapsed days and distance since each item's last service with its intervals. Classify each item as OK, due soon or overdue. Keep the logic in one function with clear inputs so it can be unit-tested with fixed dates and distances. Return the item that is closest to due as well, so the display can show 'next service in 12 days' when nothing is overdue. Handle odometer and time values that go backwards (after a clock reset) as errors, not as negative intervals.
Checkpoint: Unit tests with chosen dates and distances return the expected status for each item, including the 'whichever first' rule.
- 7
Design the rider alerts
At start-up show any due-soon items for a few seconds with an amber LED; for overdue items show a persistent message, a red LED and a short buzzer pattern that the rider can acknowledge for the current trip but that returns next start. Keep messages short enough for 16 characters per line. Never show reminders while the emulated speed is above walking pace; queue them for the next stop so the rider is not distracted.
Checkpoint: In demo mode, items move from OK to due soon to overdue with the right LCD text, LED colour and buzzer behaviour.
- 8
Add the technician service menu
Behind a keypad PIN, provide a service menu to view each item, reset it (recording today's date and current odometer as the last service), and change intervals. Store the PIN as a salted hash rather than plain text and add a lock-out after several wrong attempts. Log every successful and failed PIN entry with the RTC time, and require confirmation before a reset is saved. Allow the PIN to be changed only from inside the service menu.
Checkpoint: Wrong PINs are rejected with lock-out, and a correct PIN reset clears the reminder and records the service.
- 9
Keep a service history
Append each reset as a history record (item, date, odometer) in flash. Add a UART command to dump the history as CSV for a service centre's records, and an optional Python script to read it. Give each history record a CRC so corrupted entries are skipped rather than shown, and keep the history in its own sector so normal odometer logging never erases it. When the history sector fills, decide whether to keep the most recent records or stop and warn, and document the choice.
Checkpoint: The history dump lists every reset made during testing with correct dates and distances.
- 10
Test and document
Test power loss during a flash write, a dead CR2032 (remove it and see how the firmware detects a lost clock and asks for the date), and long demo runs. Write up the rules, data layout, menus and test results. Run a long demo test of at least a few simulated months to check interval rollovers and flash wear. In the report include the item table, flash layout, menu map, the PIN handling design and every test result, and describe how a service centre would use the history dump.
Checkpoint: A test log covering power loss, clock loss and interval rollovers, included in the report.
Testing & validation
- Reminders trigger at the correct time and distance in demo mode for every item.
- Odometer and records survive repeated power cycles.
- The firmware detects a lost RTC time and prompts for it rather than giving false reminders.
- PIN lock-out works after the configured number of failures.
Expected results
A working time- and distance-based service reminder with persistent storage, rider alerts and a PIN-protected technician reset with history export.
Take it further
- Send due reminders to a phone over a Bluetooth module (HC-05 add-on).
- Adjust intervals by usage severity, for example more hard-braking events detected by the MPU6050 bring the brake check forward.
- Publish service status as a CAN frame for a dashboard node.
Viva and review questions
Why append odometer snapshots instead of rewriting one location?
Flash sectors have limited erase cycles; appending spreads wear and only erases when the log sector fills.
What happens if the coin cell dies?
The RTC loses time; the firmware detects an invalid or reset clock and asks the user to set it before evaluating reminders.
How is distance measured?
It is emulated by integrating a speed derived from the potentiometer, because the kit has no speed sensor.
Why hash the PIN?
So reading the flash does not reveal the PIN directly.