LabWired The easy way to build hardware

Building a Complete OBD-II Scanner Without a Car or Hardware

Category: Automotive firmware

This project runs scanner firmware and vehicle electronic control unit (ECU) firmware on a virtual test bench. The scanner runs on an nRF52840. It uses an external MCP2515 controller for Controller Area Network (CAN) communication, an SSD1306 display, and radio telemetry shaped like Bluetooth Low Energy (BLE) data. An STM32F103 runs the vehicle ECU firmware.

The test verifies processor instructions, peripheral drivers, On-Board Diagnostics II (OBD-II) messages, ISO-TP transport, value decoding, display writes, and radio payload bytes.

virtual OBD-II bench
Conceptual illustration of the fully virtual OBD-II bench

Run the complete virtual scanner

This is the two-MCU lab. It runs the nRF52840 scanner firmware against the STM32F103 vehicle ECU over the modeled MCP2515 and shared CAN bus. Watch the decoded vehicle data reach the OLED and BLE telemetry without connecting a car or development board.

Run the scanner and observe CAN traffic, decoded OBD-II values, OLED output, and BLE telemetry. Open the OBD-II lab full-screen →

The scanner and its finish line

The scanner must complete these operations:

The native end-to-end gate produced this summary:

RPM 3000, speed 88, coolant 90, VIN LWOBD2SIM00000001,
DTC clear confirmed, CAN frames 21, BLE transmissions 4

The committed native end-to-end gate reads exported firmware state, inspects CAN history, sends a Mode 04 clear request, and checks the radio bytes.

Why the nRF52840 needs a CAN controller

The nRF52840 does not contain a CAN controller. Its SPIM2 peripheral drives an MCP2515 instead.

The connections are:

Firmware configures the MCP2515 transmit and receive buffers, standard-ID masks, filters, and interrupts. The model connects these buffers to the shared virtual CAN bus.

The MCP2515 is only the CAN controller. A physical design also needs an automotive CAN transceiver between the controller and CAN-H/CAN-L.

The virtual bench

The system runs two Cortex-M firmware images:

nRF52840 MCP2515 architecture
Schematic illustration of the virtual scanner architecture; it is not captured runtime evidence. Select it to open the full-size image.

The lab source defines both machines and the shared environment. The scanner firmware is in the nRF52840 firmware crate.

Real OBD-II request and response frames

The scanner sends eight-byte functional requests on CAN ID 0x7DF. The ECU replies on 0x7E8.

Engine speed:

0x7DF  02 01 0C 00 00 00 00 00
0x7E8  04 41 0C 2E E0 00 00 00

Road speed:

0x7DF  02 01 0D 00 00 00 00 00
0x7E8  03 41 0D 58 00 00 00 00

Coolant temperature:

0x7DF  02 01 05 00 00 00 00 00
0x7E8  03 41 05 82 00 00 00 00

The ECU host test checks these exact bytes. The scanner decoder consumes the same bytes.

OBD-II CAN and ISO-TP flow
Conceptual illustration of the OBD-II CAN and ISO-TP sequence; it is not captured runtime evidence. Select it to open the full-size image.

Decoding values, VIN, and trouble codes

SAE J1979 defines the value formulas:

The Mode 09 PID 02 VIN response is longer than one CAN frame. The ECU sends an ISO-TP first frame, waits for flow control on 0x7E0, and then sends two consecutive frames. The scanner reconstructs the 17-character VIN LWOBD2SIM00000001.

Mode 03 initially returns DTCs P0133 and U0123. ECU host tests and scanner decoder unit tests check these identities. The end-to-end gate checks that the scanner sees two DTCs. It then sends Mode 04, receives the 0x44 positive response, and confirms that the next Mode 03 response contains no DTCs.

Displaying the state

The nRF52840 writes to the SSD1306 through TWIM0. Firmware creates one scanner snapshot with RPM, speed, coolant temperature, DTC count, and status. The display and radio paths use the same snapshot.

The native gate checks the decoded scanner state. A separate browser WasmWorld test boots both ELF files and checks that the SSD1306 framebuffer is not blank. That browser test does not check exact pixels or text. No runtime browser screenshot is included in this article.

Broadcasting the state

The radio payload contains nine bytes: version, status flags, little-endian RPM, speed, coolant temperature, DTC count, and a little-endian generation counter.

After the DTC clear, the fixed bytes are:

01 01 B8 0B 58 5A 00

B8 0B is 3000 in little-endian form. 58 is 88. 5A is 90. The last shown byte is a zero DTC count.

The native gate observed four transmissions and checked the packet bytes. This test validates raw nRF RADIO packets with a BLE-shaped application record. It does not validate a complete BLE Generic Access Profile (GAP) advertising stack or physical radio qualification.

Faults and deterministic CI

Focused tests cover malformed responses, wrong CAN IDs and PIDs, negative replies, truncated payloads, unsupported requests, ISO-TP flow control and timing, and stale scanner values.

The native end-to-end test checks both ELF files, CAN history, exported scanner state, radio packets, and the two-to-zero DTC clear sequence. The browser WasmWorld test checks shared CAN traffic, a nonblank OLED framebuffer, and radio telemetry in the air trace.

What carries over, and what does not

The test validates firmware modules, OBD request and response handling, the ISO-TP state machine, MCP2515 register interactions, display formatting, and telemetry encoding. These parts can carry over to a board that uses the same pins and peripherals.

A physical product still needs:

This virtual test provides protocol and firmware evidence. It does not provide electrical, power, RF, environmental, or regulatory evidence.

Andrii Shylenko
Andrii Shylenko

Founder, LabWired.