The problem
Workshops and field technicians need to read live data and fault codes from an EV without opening the vehicle or carrying a dedicated CAN tool. A gateway that bridges the in-vehicle CAN bus to a short-range wireless link must not flood the radio, must not lose frames when the bus is busy, and must not let an unauthenticated phone write to the bus.
Objectives
- Receive and filter classic CAN frames with bxCAN hardware filters and interrupt-driven FIFOs
- Design a framed binary service protocol (read data, read and clear fault codes, stream, set filter) carried over Bluetooth SPP
- Add a simple access level so write services need an unlock step
- Measure end-to-end latency, frame loss and throughput against an ASCII streaming baseline at different bus loads
What you need
From the EV Embedded Development Kit
- STM32F103C8T6 microcontroller unit (Arm Cortex-M3)
- CAN bus interface
- Serial UART interface
- Bluetooth module header
- 16×2 LCD
- RGB LEDs and a buzzer
Software
- STM32CubeIDE with HAL CAN and UART drivers
- Embedded C
- Python with pyserial, pandas and matplotlib for the PC client and latency analysis
- A phone serial-terminal app for quick tests
Extra parts
- HC-05 (or similar) Bluetooth SPP module — The kit has a Bluetooth module header but no module; the gateway needs a serial-profile radio.
- USB-CAN adapter (or a second kit) — The board is one CAN node, so a second node is needed to generate BMS and motor-controller traffic and to load the bus.
Key concepts
- bxCAN filter banks
- The STM32F103 CAN controller has 14 filter banks that accept or reject identifiers in hardware, so the CPU only sees frames the gateway cares about.
- Service identifiers
- Borrowing the UDS idea, each request starts with a service byte (for example read data by identifier or read fault codes) and the reply echoes it with a positive or negative response code.
- Framing and CRC
- A serial stream has no message boundaries, so each packet carries a start byte, length, payload and CRC-16 so the receiver can resynchronise after a corrupted byte.
- Rate adaptation
- A busy CAN bus can produce more data than an SPP link carries, so the gateway decimates or summarises signals instead of forwarding every frame.
Step-by-step guide
- 1
Write the requirements and message list
List the signals the gateway must expose: cell voltage, cell temperature, relay state, a speed value and a fault-code table. Assign CAN identifiers and periods (for example 100 ms for BMS status, 20 ms for motor data), and define the services: read data by identifier, read fault codes, clear fault codes, start/stop stream and set filter. Decide that clear and set operations require an unlock step. Record latency and loss targets you will test later.
Checkpoint: A one-page message and service table that the whole team agrees on, with identifiers, periods and byte layouts.
- 2
Set up the board and clocks
In CubeMX select the STM32F103C8T6, set HSE and the PLL for a 72 MHz system clock so APB1 runs at 36 MHz. Enable CAN and check on the kit schematic which pins the transceiver uses (PA11/PA12 or the PB8/PB9 remap). Enable a USART for the Bluetooth header and another for the PC debug console. Set the kit's DIP switches so the CAN, UART and Bluetooth blocks are connected and unused blocks on shared pins are isolated.
Checkpoint: A blink-and-print build runs at 72 MHz and the debug UART prints the reset cause.
- 3
Bring up bxCAN at 500 kbit/s
With APB1 at 36 MHz, a prescaler of 4 and 18 time quanta (1 sync, 15 in BS1, 2 in BS2) give 500 kbit/s with a sample point near 89%. Configure one filter bank in mask mode, call HAL_CAN_Start and HAL_CAN_ActivateNotification for FIFO 0. In HAL_CAN_RxFifo0MsgPendingCallback read frames with HAL_CAN_GetRxMessage into a ring buffer. Use the USB-CAN adapter on the PC to send test frames, and check both ends are terminated with 120 ohms.
Checkpoint: Frames sent from the adapter appear on the debug console with correct identifier, length and data, and none are lost at a 10 ms send period.
- 4
Fit and configure the Bluetooth module
Plug the HC-05 into the Bluetooth header with the correct supply and logic level for the module you bought. Enter AT mode to set the name, pairing PIN and baud rate (the data-mode default is commonly 9600; raise it to 115200 if your module allows). Use HAL_UARTEx_ReceiveToIdle_DMA so variable-length packets arrive without per-byte interrupts. Pair with a laptop or phone and open the serial port.
Checkpoint: Text typed in the phone terminal echoes back through the STM32, and the module name and PIN are the ones you set.
- 5
Implement the packet layer
Define a packet: start byte 0xA5, length, service byte, payload, CRC-16/CCITT. Write an encoder and a byte-by-byte parser state machine that discards bad CRCs and resynchronises on the next start byte. Keep the parser independent of HAL so you can unit-test it on the PC with the same C file. Add negative responses for unknown service, wrong length and access denied.
Checkpoint: PC unit tests feed valid, truncated and corrupted packets, and the parser accepts only the valid ones.
- 6
Build the signal store and services
Decode incoming CAN frames into a signal table that holds the latest value, a timestamp and a freshness flag for each signal. Read services answer from this table instead of waiting for the next frame, which keeps latency low. Keep fault codes in a small array with status bits; clear removes them only after the unlock step. Show gateway state and the connected client on the 16×2 LCD.
Checkpoint: A read request for cell voltage returns the latest value within one Bluetooth round trip, and a stale signal is flagged once its source stops sending.
- 7
Add access control
Implement a seed-and-key unlock: the gateway sends a random seed (use a timer and ADC noise as entropy), the client returns a key computed with a shared secret, and only then are clear-fault-code and set-filter accepted. Lock again after a timeout or disconnect. Note in your report that this is a teaching-grade scheme and why production systems use proper cryptography.
Checkpoint: Write services fail with access denied before unlock and succeed after, and a wrong key three times triggers a lockout delay.
- 8
Add streaming with rate control
The stream service sends selected signals at a client-chosen rate. Estimate the Bluetooth link budget from the UART baud rate, packet overhead and payload size, and refuse or decimate any request that would exceed, say, 70% of it, replying with the rate actually granted. Prioritise fault events over periodic data in the transmit queue so a new fault code is never stuck behind telemetry. Implement the ASCII baseline as well: forward every received frame as a text line, as simple loggers do, so you can compare the two later.
Checkpoint: A 50 Hz stream of four signals runs for ten minutes with no CRC errors and no transmit-queue overflow.
- 9
Measure latency and loss
From the PC, send CAN frames with a sequence counter through the USB-CAN adapter at rising bus loads (10%, 30%, 50%) while the Python client streams over Bluetooth. Timestamp both ends on the PC clock to measure end-to-end latency and count missing sequence numbers. Repeat with the ASCII baseline. Record CPU load by toggling a GPIO in the idle loop or using the DWT cycle counter.
Checkpoint: A table and box plots of latency and loss for both methods at each bus load.
- 10
Document and demonstrate
Write up the protocol specification as a table of services, request and response layouts and negative response codes, followed by the parser and gateway state machines, the test method and the results. Discuss where frames were lost and why, for example UART DMA buffer size, radio throughput or queue policy, and what you changed in response. Finish with a live demonstration from a phone: read live data, read and clear a fault code after unlock, and show the lockout after three wrong keys.
Checkpoint: The report includes the protocol table, results with error bars and a recorded demonstration.
Testing & validation
- Parser unit tests with valid, truncated and corrupted packets
- End-to-end latency and frame loss versus CAN bus load for the binary protocol and the ASCII baseline
- Ten-minute streaming soak test with no CRC errors
- Access control test: write services rejected before unlock and after lockout
Expected results
A working CAN-to-Bluetooth diagnostic gateway with a documented service protocol, an unlock step for write services, and measured latency, loss and throughput showing how the binary, rate-controlled design compares with naive text forwarding.
Research angle for your M.Tech thesis
Research questions: how does gateway latency and loss scale with CAN bus load and stream rate, and which queueing policy (FIFO, priority by signal class, latest-value overwrite) minimises worst-case staleness of safety-relevant signals? Metrics: end-to-end latency percentiles, frame loss, signal age at the client, CPU load. Baseline: ASCII frame forwarding. A publishable contribution would be a quantified comparison of buffering policies on a constrained MCU, with a model that predicts loss from bus load and radio throughput and is validated on the hardware.
Take it further
- Add ISO-TP style segmentation so replies longer than one packet can be sent
- Replace the teaching unlock with an HMAC using a small software SHA-256 implementation and measure its cost
- Log all service requests with RTC timestamps to SPI flash for an audit trail
Viva and review questions
Why not forward every CAN frame over Bluetooth?
A busy CAN bus at 500 kbit/s can carry far more data than an SPP link reliably moves, so forwarding everything overflows buffers. Serving reads from a signal table and rate-limiting streams keeps the link within budget.
How do you set the bxCAN bit timing?
Bit rate is APB1 clock divided by prescaler times the number of time quanta. With 36 MHz, prescaler 4 and 18 quanta you get 500 kbit/s, and BS1 and BS2 place the sample point.
What stops a stranger's phone from clearing fault codes?
Pairing PIN on the module, plus a seed-and-key unlock in the gateway that must succeed before any write service is accepted, with lockout after repeated wrong keys.
Why use a CRC if Bluetooth already checks errors?
The UART link between the module and the MCU, buffer overruns and resynchronisation after a dropped byte can still corrupt packets, so an end-to-end CRC is needed.