The problem
DC fast chargers and vehicles agree on voltage and current limits before any power flows, then keep exchanging status throughout the charge. Protocols such as CHAdeMO and GB/T 27930 use CAN for this. A handshake that misses a timeout or accepts an unsafe request can damage the battery. Students rarely see this logic at work; this project builds a simplified version of it.
Objectives
- Design a simplified charging protocol with message IDs, signals, timing and states
- Implement the vehicle side on the kit using bxCAN and the BMS readings
- Implement the charger side in Python with a USB-CAN adapter
- Test normal sessions, mismatched limits, timeouts and emergency stops
What you need
From the EV Embedded Development Kit
- STM32F103C8T6 microcontroller unit (main controller)
- Second STM32F103C8T6 as the Smart BMS unit
- CAN bus interface with screw terminal
- 1S battery with connector and T-sense input
- Battery charging unit with its own switch
- Charge/discharge control relay
- 16×2 LCD
- RGB LEDs and a buzzer
Software
- STM32CubeIDE with HAL drivers
- Embedded C
- Python with python-can
- A CAN bus monitor tool for the adapter
Extra parts
- USB-CAN adapter — The board has one CAN node, so a second node is needed to play the charger. A second kit can be used instead.
This project uses the kit's lithium cell. Respect polarity, never short the terminals, and don't leave the cell charging unattended.
Key concepts
- Handshake
- An ordered exchange in which each side states its capabilities, they agree operating limits and both confirm readiness before energy transfer begins.
- Cyclic messages and timeouts
- Status frames are sent at a fixed period. If a frame is not received within a set time, the receiver assumes the link has failed and stops the charge.
- bxCAN filters
- The STM32 CAN peripheral accepts only frames whose identifiers pass the configured filter banks, so the firmware sees only the messages it needs.
- Simplified protocol
- This project borrows the structure of real charging protocols but is not an implementation of any of them and must not be used with real chargers.
Step-by-step guide
- 1
Design the protocol
Write a protocol sheet. Define four or five message IDs: vehicle parameters (maximum voltage, maximum current, capacity), charger parameters (output limits), vehicle status (SOC, measured voltage, requested current, ready or stop flag), charger status (output on, measured output, fault bits) and stop request. Fix byte order, scaling and periods such as 100 ms, and timeouts such as 500 ms. Draw the session sequence: wake, parameter exchange, compatibility check, ready, charging, stopping, end.
Checkpoint: A protocol sheet with a signal table and a sequence diagram reviewed by the guide.
- 2
Board and bus setup
Enable the DIP switches for the CAN, battery, charger, relay, LCD and LED blocks. Wire the kit's CAN screw terminal to the USB-CAN adapter: CAN-H to CAN-H, CAN-L to CAN-L and a common ground, with termination at both ends of the bus. In CubeMX configure bxCAN at 250 or 500 kbit/s, matching the adapter, and set filters for the charger IDs. Enable the receive interrupt.
Checkpoint: A test frame sent from the kit with HAL_CAN_AddTxMessage appears in the PC monitor, and a frame from the PC triggers the kit's receive callback.
- 3
Signal packing library
Write pack and unpack functions in C for every message, with scaling and range checks, and the same in Python. Unit-test them: pack on one side, unpack on the other and compare. Include a 4-bit counter in the status frames so each side can tell when frames are repeated or lost. Keep the signal definitions in one shared table so the C and Python sides cannot drift apart, and generate both from it if possible.
Checkpoint: Round-trip tests in both directions pass for minimum, maximum and typical values.
- 4
Vehicle-side state machine
Implement the vehicle states on the main controller. In parameter exchange, send vehicle limits from the BMS readings and wait for charger parameters. Check compatibility: the charger's maximum voltage must cover the cell's needs and its current must not exceed what the vehicle allows. Only then send ready. During charging, request a current based on SOC and temperature and ask the BMS unit to close the relay.
Checkpoint: The vehicle moves through each state as the PC sends the expected frames, shown on the LCD and over UART.
- 5
Charger-side emulator in Python
Write the charger as a Python state machine using python-can. It sends its parameters, waits for ready, then reports a simulated output that follows the requested current within its limits. Add command-line options to choose limits, inject faults and stop the session. Log every frame with a timestamp to CSV. Print a readable decoded trace alongside the raw frames, so a reviewer can follow a session without decoding bytes by hand.
Checkpoint: A full session runs from wake to end with both sides logging the same sequence of states.
- 6
Link the BMS and the relay
Have the BMS unit report cell voltage and temperature to the main controller over the inter-controller link. The charge relay is closed only while the session is in charging state and the BMS limits are met. The kit's charging unit supplies the actual current; the emulated charger output is a model, and the report must say so. Display the emulated and estimated values side by side so the difference is clear.
Checkpoint: The relay closes only during the charging state and opens within the set time when a stop is requested.
- 7
Timeouts and fault handling
Implement receive timeouts on both sides. Handle a charger fault bit, a vehicle over-temperature, a counter that stops changing and a bus-off condition on the STM32. Every fault ends in a controlled stop: zero current request, relay open, stop frame sent and a fault code shown. Use the buzzer and a red LED for faults. After a fault, require a fresh handshake from the start before charging can resume, rather than jumping back into the charging state.
Checkpoint: Unplugging the CAN cable during charging stops the session within the timeout and shows the correct fault.
- 8
Test campaign
Write test cases: normal session, incompatible limits, charger fault, vehicle fault, lost frames, delayed frames and emergency stop from a kit button. Run each several times from the Python script, which can play scripted sequences. Measure the time from fault to relay open using timestamps from both logs. Include negative tests that send malformed frames, wrong lengths and out-of-range values, and confirm that the vehicle rejects them without entering an unsafe state or crashing.
Checkpoint: A test table lists every case with expected and observed behaviour and measured stop times.
- 9
Documentation
Write up the protocol, the state machines on both sides, the test results and the timing. Include CAN traces from the PC log for one normal and one fault session. Explain how a real protocol differs, for example in insulation checks, isolation monitoring and contactor welding detection, which the kit does not model. Add a short section on what you would change to move towards a real protocol implementation.
Checkpoint: The report includes the protocol sheet, both state diagrams, CAN traces and the test table.
Testing & validation
- Normal session from wake to end completed repeatedly
- Incompatible parameters correctly rejected before the relay closes
- Timeout and lost-frame detection on both sides with measured reaction times
- Emergency stop from the kit button and from the PC
- CAN bus load estimated from frame periods and confirmed on the monitor
Expected results
A working two-node CAN demonstrator in which the kit, as the vehicle, negotiates and supervises a simplified charging session with a PC charger emulator, with logged traces and a fault-test report.
Take it further
- Add a second kit as a real charger node instead of the PC
- Use extended 29-bit identifiers and a multi-frame transfer for vehicle information
- Add message authentication to the parameter exchange
- Model a charge profile that reduces current request near full charge
Viva and review questions
Why exchange parameters before charging?
Each side must confirm the other can operate within its limits; the vehicle must never be offered more voltage or current than it can accept.
What does the counter in the status frame do?
It changes with every frame, so a receiver can detect a stuck sender or lost frames even when frames keep arriving.
Why does the board need a USB-CAN adapter?
CAN needs at least two nodes to acknowledge frames, and the kit has one CAN interface, so the adapter is the second node playing the charger.
What happens if the bus goes into bus-off?
The vehicle treats it as a communication loss: it stops requesting current, opens the relay and reports a fault before any attempt to recover.