Skip to main content
emobility.academy
B.Tech / BEEmbedded software architectureIntermediate About 6 weeksKit + add-ons

Dual-MCU Architecture: Main Controller and BMS Unit Working Together

Students make the kit's two STM32s work like separate vehicle ECUs: the Smart BMS unit measures and protects the cell, the main controller acts as a vehicle control unit, and they exchange status and commands over a framed UART link, with the main controller publishing vehicle data on CAN.

Uses: UART · CAN · GPIO

The problem

An EV is a network of control units, each owning its job. Splitting a system between two microcontrollers raises real design questions: who decides, what data is shared, how often, and what each side does if the other stops talking.

Objectives

  • Define each MCU's responsibilities and the interface between them.
  • Implement a framed, CRC-checked inter-MCU protocol with sequence numbers.
  • Detect loss of communication and move each side to a safe state.
  • Publish combined vehicle data on CAN from the main controller.
  • Measure link latency and failure detection time.

What you need

From the EV Embedded Development Kit

  • STM32F103C8T6 main controller
  • Smart BMS unit STM32F103C8T6
  • 1S battery with connector and T-sense input
  • Load section: three switchable resistive loads with LEDs
  • Charge/discharge control relay
  • BMS power switch, OLED and six navigation buttons
  • Serial UART interface
  • CAN bus interface with screw terminal
  • 16×2 LCD
  • 4×3 matrix keypad
  • Analog potentiometer input

Software

  • STM32CubeIDE with HAL drivers (two projects)
  • Embedded C
  • Python 3 with python-can and pyserial

Extra parts

  • Jumper wires — To connect a spare USART on each MCU (TX, RX and ground) if the kit does not already route a link between the two controllers; check the schematic first.
  • USB-CAN adapter — The board has one CAN node; the adapter acts as the rest of the vehicle network and acknowledges frames.

This project uses the kit's lithium cell. Respect polarity, never short the terminals, and don't leave the cell charging unattended.

Key concepts

Separation of responsibilities
The BMS owns cell protection and must act on its own; the vehicle controller decides driving modes and requests, but cannot override battery protection.
Inter-ECU protocol
A defined message set with framing, CRC and sequence numbers so each side can trust and order what it receives.
Communication timeout
Each side expects a message within a set time; if it does not arrive, it assumes the other node has failed and goes to a safe state.
Gateway role
The main controller combines its own data with BMS data and publishes it on CAN for other ECUs such as a dashboard.

Step-by-step guide

  1. 1

    Define the architecture

    Draw the system: the Smart BMS unit owns cell voltage, temperature, loads, the charge/discharge relay and the OLED; the main controller owns the throttle (potentiometer), keypad, LCD, drive mode and CAN. List the messages: BMS status (voltage, temperature, relay state, faults, allowed discharge level) every 100 ms; VCU command (drive mode, load request) every 100 ms; and event messages for faults. State the rule that the BMS can refuse any request that breaks its limits.

    Checkpoint: An architecture diagram and a message table with periods, contents, sender and timeout.

  2. 2

    Establish the physical link

    Check the kit schematic for an existing connection between the two MCUs. If there is none, choose a spare USART on each and connect TX to RX, RX to TX and ground with jumper wires on the header pins (both run at 3.3 V). Configure both at 115200 baud, 8N1, in CubeMX, with DMA or interrupt reception. Set DIP switches for the blocks each MCU uses.

    Checkpoint: A test byte stream sent from each MCU arrives intact at the other.

  3. 3

    Implement the frame layer

    Write a shared C module used by both projects: start byte, message ID, length, sequence number, payload and CRC-16. Receive with HAL_UARTEx_ReceiveToIdle_DMA or a byte interrupt into a ring buffer, and parse frames in the main loop. Count CRC errors, sequence gaps and timeouts on both sides. Version the shared module so both projects always build from the same copy.

    Checkpoint: A 30-minute soak test shows zero undetected errors and counters that match injected faults.

  4. 4

    Build the BMS unit firmware

    Sample cell voltage and temperature, run protection (open the relay and switch off loads outside limits), apply load requests from the VCU only if within limits, and send BMS status every 100 ms. Show local status on the OLED. Follow lithium safety: correct polarity, never short the terminals, supervise any charging. Test protection by lowering software thresholds, never by over-charging or over-discharging the cell.

    Checkpoint: The BMS runs alone and protects the cell even with the main controller switched off.

  5. 5

    Build the main controller firmware

    Read the throttle, keypad mode selection and the BMS status. Compute a load request (for example map throttle to one, two or three loads, as a stand-in for drive power) limited by the BMS's allowed level. Show SOC estimate, mode and faults on the LCD. If the BMS refuses or limits a request, show the reason on the LCD so the user understands why the output is lower than the throttle asks. Ramp requests so loads are not switched on and off rapidly.

    Checkpoint: Turning the throttle changes the requested loads, and the BMS applies them within its limits.

  6. 6

    Handle communication loss

    If the BMS gets no VCU command for 300 ms, it switches off all loads but keeps protecting. If the main controller gets no BMS status for 300 ms, it shows a communication fault, sets the request to zero and publishes a fault on CAN. Recovery requires a number of consecutive good frames. Show the fault state on both the LCD and the OLED.

    Checkpoint: Unplugging the link wire puts both sides in their safe state within the timeout, and reconnecting recovers cleanly.

  7. 7

    Publish on CAN

    Configure CAN on the main controller at 500 kbit/s, connect the USB-CAN adapter with termination, and publish a vehicle status frame (SOC, voltage, temperature, mode, fault bits) every 100 ms using HAL_CAN_AddTxMessage. Write a Python viewer with python-can that decodes and displays it. Document the frame in a DBC-style table (ID, byte order, scaling, offset, units), and add a rolling counter and checksum byte so receivers can detect stale or corrupted data. Check the bus with the adapter's error counters for a long run.

    Checkpoint: The PC viewer shows live values that match the LCD and OLED.

  8. 8

    Measure latency and detection time

    Toggle a GPIO when a fault is detected on the BMS and another when the main controller reacts or publishes it on CAN, and measure the delay with a timer capture or by timestamping both events and comparing. Measure link-loss detection time for both sides. Repeat each measurement many times and report minimum, typical and maximum, because the worst case is what matters for safety arguments.

    Checkpoint: A table of fault-to-reaction and link-loss detection times, compared with the timeouts set in step 1.

  9. 9

    Run integration tests

    Write test scenarios: normal throttle sweep, over-temperature (by lowering the software threshold), link loss, BMS reset during driving, and main controller reset. For each, record what both units and the CAN bus show. Write each scenario as a script with steps, expected results and actual results, and run the full set again after every firmware change. Capture CAN traffic with the Python viewer during each test as evidence, and note any scenario where the two units disagree about the system state.

    Checkpoint: Every scenario ends in the expected state on both MCUs and on CAN.

  10. 10

    Document the architecture

    Write up the architecture, message table, protocol, timeout behaviour, test results and what would change in a vehicle (CAN between ECUs, standardised diagnostics). Include the responsibility split and the reasons for it, the message table with periods and timeouts, the frame format, the fault-handling rules for each side, and the measured timings. Explain how the design would map onto a real vehicle network with a separate BMS, motor controller, VCU and dashboard.

    Checkpoint: A report containing the architecture diagram, interface specification and integration test record.

Testing & validation

  • BMS protection works with the main controller off.
  • Link loss detected within the defined timeout on both sides.
  • Soak test shows all corrupted frames detected.
  • CAN data matches the values on both displays.

Expected results

A two-ECU system on one board with a defined interface, robust inter-MCU messaging, safe behaviour on communication loss and vehicle data published on CAN.

Take it further

  • Replace the UART link with CAN between two kits.
  • Add a firmware version and compatibility check at start-up.
  • Move each side's firmware to FreeRTOS tasks.

Viva and review questions

Why should the BMS be able to refuse a VCU request?

Cell protection is the BMS's responsibility; it must act on its own limits even if the vehicle controller is wrong or has failed.

Why use a sequence number?

It shows lost or repeated frames, which a CRC alone cannot detect.

What happens when the link fails?

Each side times out, goes to its safe state and reports a communication fault until enough good frames arrive.

Why use a USB-CAN adapter?

The board has one CAN node; another node is needed to receive and acknowledge frames.

Related EV projects

All 100 EV projects