Skip to main content
emobility.academy
B.Tech / BECAN & vehicle networksIntermediate About 4 weeksKit + add-ons

CAN Bus Sniffer and Logger with PC Viewer

Build a CAN bus sniffer that listens to a bus without transmitting data, timestamps every frame and streams it over UART to a Python viewer that shows live traffic, message rates and decoded signals. A second kit or a USB-CAN adapter provides the traffic.

Uses: CAN · UART

The problem

Engineers debugging EV networks start with a bus monitor: they need to see which messages are present, how often they arrive and what they contain. Commercial tools are expensive for students. This project builds a working sniffer and viewer and teaches how CAN frames, timing and error states look on a real bus.

Objectives

  • Support two capture modes: silent (listen-only) for buses that already have two active nodes, and acknowledging monitor (normal mode, never transmits data) for a two-node bench.
  • Capture every frame with a microsecond timestamp without overflowing the receive FIFOs.
  • Stream frames to a PC in a compact text format at a sustainable UART rate.
  • Build a Python viewer with a live table, message rates and simple signal decoding.
  • Measure how much bus load the sniffer can handle before dropping frames.

What you need

From the EV Embedded Development Kit

  • STM32F103C8T6 microcontroller unit (Arm Cortex-M3)
  • CAN bus interface with screw terminal
  • Serial UART interface
  • 16×2 LCD with contrast control
  • RGB LEDs and a buzzer

Software

  • STM32CubeIDE
  • Embedded C with STM32 HAL
  • Python with pyserial, pandas and a GUI or plotting library such as tkinter or matplotlib

Extra parts

  • A second DIYguru EV Embedded Development Kit — A sniffer needs traffic. The second kit runs a configurable traffic generator. A USB-CAN adapter on a PC can serve the same purpose.
  • USB-to-UART adapter (if the kit's serial interface is not already connected to a PC) — To carry the frame stream from the board to the PC at a high baud rate.

Key concepts

Silent mode and acknowledgement
In silent mode bxCAN receives but never drives the bus, not even acknowledge bits or error frames. Every frame still needs at least one other node to acknowledge it, so on a bench with only a generator and the sniffer, the sniffer must run in normal mode and acknowledge without sending data.
Receive FIFOs
bxCAN has two 3-message receive FIFOs. If the CPU does not empty them fast enough, new frames are lost and the overrun flag is set.
Timestamping
Recording the time each frame arrives lets the viewer compute periods and jitter. A free-running 1 MHz timer gives microsecond resolution.
UART throughput
At 921600 baud a UART carries about 92 kB/s; a text line per frame of about 35 bytes limits the sustainable frame rate, which sets the maximum loggable bus load.

Step-by-step guide

  1. 1

    Set requirements

    Decide the bus rate (500 kbit/s), frame types to capture (standard and extended, data and remote), timestamp resolution (1 µs) and output format, for example one text line per frame: timestamp, ID, DLC and data bytes in hex. Calculate the maximum frame rate at 500 kbit/s (around 3,700 standard 8-byte frames per second at full load) and the UART rate needed to stream that.

    Checkpoint: A requirements note with the frame format and a throughput calculation showing the UART limit.

  2. 2

    Prepare the traffic source

    On the second kit, write a traffic generator that sends several messages at different periods (10 ms, 100 ms, 1 s), with standard and extended IDs and changing data such as counters and potentiometer readings. Wire the two screw terminals with twisted pair and check termination gives about 60 Ω with power off. Enable the CAN DIP switches on both kits.

    Checkpoint: The generator works in loopback mode; once wired to the sniffer in normal mode, its transmit-error counter stays at zero.

  3. 3

    Configure the sniffer

    On the sniffer kit configure bxCAN at 500 kbit/s with the same timing as the generator, and make the mode selectable at start-up: CAN_MODE_NORMAL (acknowledging monitor, with no transmit calls anywhere in the code) for the two-kit bench, and CAN_MODE_SILENT for buses that already have two active nodes. Configure a filter bank to accept all IDs into FIFO 0, and a second bank to send some into FIFO 1 to use both FIFOs. Start TIM2 as a free-running 32-bit microsecond counter by chaining or use a 16-bit timer with an overflow counter.

    Checkpoint: The sniffer starts without errors and its receive interrupt fires when the generator transmits.

  4. 4

    Capture without loss

    In the FIFO callbacks, read each frame with HAL_CAN_GetRxMessage, attach the timer value and push it into a RAM ring buffer of a few hundred entries. Keep the interrupt minimal. Watch the FIFO overrun flags and count overruns. The main loop drains the ring buffer to the UART. Size the ring buffer from the burst you expect: at full load a 10 ms UART stall would need room for about 40 frames. Use a single-producer, single-consumer design so the interrupt and main loop never need to disable interrupts for long.

    Checkpoint: With the generator at moderate load, overrun and ring-buffer overflow counters stay at zero over five minutes.

  5. 5

    Stream efficiently

    Format frames as compact hex text and send with HAL_UART_Transmit_DMA from a double buffer, so formatting the next block overlaps with the current transmission. Use the highest reliable baud rate (for example 921600). Add a periodic status line with frames captured, frames dropped and current bus-error state so the PC can report losses. Test with the generator's rolling counter so dropped frames show up as gaps on the PC.

    Checkpoint: The PC receives every frame the generator sends, verified by a rolling counter in the generator's data.

  6. 6

    Show status on the board

    Use the LCD to show frames per second and dropped-frame count, and RGB LEDs for state: green receiving, amber dropping frames, red bus error or no traffic. This makes the sniffer usable at a bench without a laptop for quick checks. Add a keypad key to clear the counters and another to switch between showing frame rate, dropped frames and the bus error counters. Update the LCD only a few times a second so it never takes time away from capture.

    Checkpoint: LCD frame rate matches the generator's configured rate within a few per cent.

  7. 7

    Build the Python viewer

    Read the serial stream in a background thread with pyserial and parse each line. Show a live table with one row per ID: last data, count, average period and jitter. Add a trace view that lists frames in time order and a save-to-CSV function. Add simple decoding: for a chosen ID, extract a 16-bit signal by byte position and scaling and plot it.

    Checkpoint: The viewer shows each generator message with its period within 1 per cent of the configured value.

  8. 8

    Stress test

    Increase the generator's traffic step by step towards full bus load, and record where the sniffer starts dropping frames and whether the limit is the CAN FIFOs, the ring buffer or the UART. Try a shorter binary format to see how far the limit moves. Document the maximum reliable bus load. Repeat the test with only standard IDs and then with extended IDs, since extended frames are longer and change both the bus load and the frames per second.

    Checkpoint: A plot of dropped-frame percentage against offered bus load with the limiting factor identified.

  9. 9

    Document and demonstrate

    Write up the architecture, frame format, timing, viewer features and stress-test results, with instructions for using the sniffer on another kit project. Demonstrate it by capturing a BMS-to-dashboard link from another project and explaining what each message does. Include the frame-format specification, the wiring for both capture modes, the stress-test plot and a short section on reading a trace: spotting a missing message, a wrong period or a node that has gone bus-off. Record a short demonstration capture and keep it in the repository with the viewer.

    Checkpoint: A user guide and a captured trace of another project's bus with each message explained.

Testing & validation

  • No dropped frames at moderate load, verified by rolling counters.
  • Measured message periods against generator settings.
  • Maximum loggable bus load and the limiting factor.
  • Monitor mode verified: the sniffer never transmits data frames, and in silent mode it does not acknowledge either.

Expected results

A low-cost CAN sniffer and PC viewer with microsecond timestamps, a measured capacity limit, and a captured trace of a real two-node link.

Take it further

  • Add DBC file decoding in the viewer using the Python cantools library.
  • Log to SPI flash for standalone recording with RTC timestamps.
  • Add a transmit mode to replay captured traces.
  • Detect and display error frames and bus-off events.

Viva and review questions

Why offer silent mode at all?

In silent mode the sniffer never drives the bus, not even acknowledge bits or error frames, so it cannot change the behaviour of an existing network it is observing.

Why can the sniffer not be silent on a two-kit bench?

Every frame must be acknowledged by at least one other node. With only the generator and a silent sniffer, nobody acknowledges, so the generator sees acknowledge errors and keeps retransmitting; the sniffer must run in normal mode and acknowledge.

What limits the sniffer's capacity?

Usually the UART throughput, because each frame takes many bytes of text; the receive FIFOs only hold three frames each, so the interrupt must also drain them quickly.

Related EV projects

All 100 EV projects