The problem
Service engineers and test riders need to watch live vehicle data without a cable to the controller. A simple, well-framed wireless telemetry link is the first step towards connected-vehicle features.
Objectives
- Fit and configure a Bluetooth serial module on the kit's Bluetooth header.
- Sample analog signals with the ADC and send them in a robust text or binary frame.
- Receive, parse and plot data live on a laptop, and save sessions to CSV.
- Measure link throughput, latency and packet loss.
What you need
From the EV Embedded Development Kit
- Bluetooth module header
- Serial UART interface
- Analog potentiometer input
- 1S battery with connector and T-sense input
- 16×2 LCD
- STM32F103C8T6 main controller
Software
- STM32CubeIDE with HAL drivers
- Embedded C
- Python 3 with pyserial, matplotlib (animation) and pandas
Extra parts
- HC-05 (or compatible) Bluetooth serial module — The kit has a header for a Bluetooth module but the module itself is not included.
This project uses the kit's lithium cell. Respect polarity, never short the terminals, and don't leave the cell charging unattended.
Key concepts
- Serial Port Profile (SPP)
- A classic Bluetooth profile that behaves like a wireless UART. Once paired, the laptop sees it as a serial port.
- Framing
- Wrapping each sample set with a start marker, length and checksum so the receiver can resynchronise after a lost or corrupted byte.
- ADC with DMA
- The ADC scans several channels into a memory buffer automatically, freeing the CPU from polling each conversion.
- Throughput budget
- At 9600 baud a UART carries about 960 bytes per second, which limits how many channels and how fast you can stream.
Step-by-step guide
- 1
Plan signals and the link budget
Choose the channels: potentiometer (as throttle), cell voltage and temperature where they are available to the main controller (check the schematic; otherwise read them on the BMS unit), plus a sample counter. Pick 10 Hz. Work out bytes per frame and confirm the rate fits comfortably within the module's baud rate, leaving at least 50 per cent headroom. Note any fields you may add later so the frame has spare room.
Checkpoint: A table of channels, scaling and a link budget showing bytes per second against the baud rate.
- 2
Fit and configure the Bluetooth module
With power off, plug the HC-05 into the Bluetooth header, matching VCC, GND, TX and RX as marked on the board. Check whether your module needs AT mode to change its name or baud rate (many HC-05 modules use 38400 baud in AT mode and 9600 in data mode by default). Set a recognisable name and keep the default PIN only for lab use.
Checkpoint: The laptop discovers and pairs with the module and a serial terminal on the new port shows characters typed from the STM32 side.
- 3
Configure the STM32 in CubeMX
Enable the USART connected to the Bluetooth header at the module's data-mode baud rate, and a second USART for the wired debug console if available. Enable ADC1 in scan mode with DMA for the analog channels, triggered by a timer at 10 Hz or simply started from a 100 ms timer interrupt. Set the DIP switches for the Bluetooth and analog blocks.
Checkpoint: The ADC buffer updates continuously and debug prints show sensible raw counts.
- 4
Convert readings to units
Convert raw counts to millivolts with the 3.3 V reference, then apply the divider ratio for the cell voltage and the sensor curve for temperature from the kit documentation. Average four to eight samples per channel to reduce noise. Keep values as integers (mV, 0.1 °C, per cent) to avoid software floating point. Write the conversion constants in one header file with comments stating where each came from, so calibration changes do not have to be hunted through the code.
Checkpoint: The LCD shows cell voltage within a reasonable tolerance of a multimeter reading and throttle moves from 0 to 100 per cent across the potentiometer.
- 5
Design the frame format
Start with a human-readable CSV line such as '$T,counter,throttle,mV,temp*CS' ending in a two-character XOR checksum and newline. It is easy to debug in a terminal. Document the format. Later you can try a compact binary frame with a start byte, length, payload and CRC-16 and compare throughput. Keep the line under 40 characters at 10 Hz so it uses well under half the bandwidth at 9600 baud, and put a version field at the start so the plotter can reject frames from older firmware.
Checkpoint: Frames appear in a terminal at 10 Hz and a hand-calculated checksum matches the transmitted one.
- 6
Transmit without blocking
Use HAL_UART_Transmit_DMA or HAL_UART_Transmit_IT so sampling is never delayed by transmission. Keep a flag so a new frame is not started while the previous one is still being sent, and count dropped frames if that ever happens. Prepare the next frame in a second buffer while the first is being sent (double buffering), so the DMA never reads a buffer that the sampling code is overwriting. Check the transmit-complete callback sets the ready flag again.
Checkpoint: Timing measured with a GPIO toggle shows the 10 Hz loop stays steady while transmitting.
- 7
Build the live plotter
Write a Python script that opens the Bluetooth serial port, reads lines, verifies the checksum, discards bad frames and appends good ones to rolling NumPy arrays. Use matplotlib's FuncAnimation to plot the last 30 seconds of each channel, and save every received frame with a PC timestamp to CSV. Run serial reading in a separate thread so the plot redraw never causes lost bytes, and show a frame-rate and error counter in the window title.
Checkpoint: Turning the potentiometer moves the throttle trace live on the laptop with no visible lag.
- 8
Measure link quality
Use the sample counter to compute packet loss. Measure latency by echoing a command from the PC and timing the reply. Repeat at different distances and with obstructions, and at 10 Hz versus 50 Hz. Record results in a table. Run each condition for at least five minutes so occasional drops are captured. Also test what happens when the link breaks and reconnects: the firmware should keep sampling, and the plotter should resume without being restarted, showing the gap in the data.
Checkpoint: A table of loss and latency for each test condition.
- 9
Add simple commands
Let the laptop send commands back, such as changing the sample rate or switching an RGB LED, and acknowledge each command. This shows a two-way service link. Use the same framing and checksum in the downlink direction, and reject any command that fails its checksum or is out of range. Keep commands to safe actions such as rate and indicator changes; do not allow wireless commands that switch the relay or loads without a confirmation step on the board.
Checkpoint: Commands from the plotter change the rate and the LED, and each one is acknowledged.
- 10
Document the project
Write up the hardware setup, frame format, link budget, plotter design and measured link quality. Include screenshots of the live plot and a sample CSV file. Add the pairing procedure and module settings so another team can reproduce the setup, the link budget calculation, a description of how the plotter resynchronises after errors, and a short list of what would be needed for a production link, such as authentication and a secure pairing method.
Checkpoint: The report includes the frame specification and the measured loss and latency results.
Testing & validation
- Checksum rejects deliberately corrupted frames.
- Packet loss and latency measured at several distances.
- Displayed voltage agrees with a multimeter reading within the stated tolerance.
- The 10 Hz sampling period stays stable during transmission.
Expected results
A wireless telemetry link that streams live kit data to a laptop plotter and CSV log, with measured throughput, loss and latency.
Take it further
- Build a phone app or use a Bluetooth terminal app to show the same data.
- Switch to a binary frame and compare maximum achievable sample rate.
- Log MPU6050 data and plot vehicle tilt live.
Viva and review questions
Why include a checksum and a counter in each frame?
The checksum detects corrupted frames and the counter reveals lost frames, so the plotter can discard bad data and report loss.
Why use DMA or interrupts for transmission?
A blocking transmit would stall the sampling loop while bytes are sent; DMA sends in the background.
What limits the maximum sample rate?
The UART baud rate and frame size. At 9600 baud only about 960 bytes per second can be sent.