Wiring the Car's Nervous System: CAN, the BMS, and the Motor Controller
Developing CAN 2.0 communication between our custom PCBs, the battery-management system, and the PM100DX motor controller, on STM32, where a dropped frame is a safety problem.
An electric FSAE car is a distributed system: custom PCBs, a battery-management system watching every cell, and a 3-phase motor controller that turns pack energy into lap time. As Software System Lead I developed the CAN 2.0 communication that lets all of them share one picture of the car, running on STM32 microcontrollers across our custom boards.

The interesting part of vehicle networking isn’t sending frames, it’s agreeing on what they mean. Before any of it was code, it was a drawing: every node that needed to talk, which bus it sat on, and where the two buses had to meet.
Splitting the network was the first real decision. A sensor bus can tolerate a dropped frame; a control bus carrying pedal position and vehicle state cannot, because those messages have deadlines. Keeping everything on one bus would have put the BMS broadcast in the same arbitration queue as the pedal signal.
The cost of splitting is a transceiver on every node that has to see both, plus a firewall crossing that becomes the single most important connector on the car.

The work spanned three interfaces, and each one is a contract with someone else’s hardware:
- Our own PCBs, where the message set defines how pedal position, vehicle state, and fault flags arrive predictably.
- The BMS, where pack voltages, temperatures, and fault status get decoded so the rest of the car can react to the accumulator’s health.
- The PM100DX motor controller, where command and telemetry framing drives a 3-phase inverter and a misunderstood message doesn’t log an error, it moves the car.

The state machine is the part that has to be boring. goTSA() arms the tractive
system, goRTD() puts the car in ready-to-drive, and nothing between them is
allowed to be implicit.
When a car doesn’t move at competition, the first question is always which state it thinks it’s in. A car that can’t answer that costs you a session, so every transition is written to the dash and to the log before anything else happens.

The BMS exists to keep a high-voltage pack inside its safe envelope, and the car’s response to a BMS fault travels over this bus. That framing shaped the engineering: predictable IDs and priorities, defined behavior when a node goes quiet, and bench testing with bus captures before anything touched the car.
Nothing gets plugged into 400 volts to find out whether the firmware is right. CANdb++ and PCAN-View lived permanently on my second monitor.

A schematic finds out whether it was honest when the harness gets built. Node count on paper is cheap; node count in a loom is length, weight, connectors, and one more thing that can be unplugged wrong at three in the morning.

This network is also where I learned to lead. Ten-plus software members build against these interfaces, so the message set became documentation, code review became teaching, and CI pipelines in GitHub Actions became how we keep five codebases honest.
The bus is the contract between every subsystem on the car, and contracts are only useful when everyone can read them.