Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.

MCP2515 CAN module with Arduino Uno — read-only CAN bus sniffer

Wire an MCP2515 + TJA1050 CAN module to an Arduino Uno R3 on hardware SPI and log CAN frames in listen-only mode — your own vehicle or test bench only.

Arduino C++Verified — core avr 1.8.8, mcp_can 1.5.1, 3 Nov 2026Compile-only

Community draft — report a mistake

CAN is the bus that ECUs, dashboards and body modules use to talk to each other in cars, two-wheelers, solar inverters and industrial machines. An MCP2515 module with a TJA1050 transceiver lets an Uno listen in for about ₹200.

On the Uno it is an easy fit: the module is a 5 V part, so it connects directly to hardware SPI (D10–D13) with its INT line on D2. The sketch starts the controller at 500 kbit/s and immediately switches it to listen-only, so it logs every frame it sees without ever sending anything on the bus.

Use it on your own vehicle, a bench setup or equipment you own. Reading your own car’s bus is a great way to learn; sending frames into it is not.

Wiring

Arduino Uno R3Only the pins used are shown · 5 V logicMCP2515 CAN bus module (with TJA1050)5VGNDD2D10D11D12D13VCCGNDCSSOSISCKINTH → CAN-H (OBD-II pin 6 on a car, or your test bench)L → CAN-L (OBD-II pin 14)
Also needed: OBD-II male plug with screw terminals or pigtail (for your own car), Jumper wire from OBD-II pin 5 (signal ground) to the Uno GND.
Every connection
Arduino Uno R3MCP2515 CAN bus module (with TJA1050)WireNotes
—H—To CAN-H (OBD-II pin 6 on a car, or your test bench) (not drawn)
—L—To CAN-L (OBD-II pin 14) (not drawn)
5VVCCredThe TJA1050 transceiver needs 5 V — a direct match for the Uno.
GNDGNDblack
D10CSpurple
D11SIgreenSPI MOSI (into the module).
D12SOblueSPI MISO (out of the module) — 5 V, which the Uno reads directly.
D13SCKyellow
D2INTorangeLOW when a frame is waiting.

5 V part, 5 V board. The TJA1050 transceiver needs 5 V and the MCP2515 runs from the same 5 V, so SO is a 5 V output and the inputs want about 4 V for HIGH. Uno connects directly. On ESP32 use a bidirectional level shifter — or better, ESP32’s built-in TWAI controller with a 3.3 V SN65HVD230 transceiver.

Board quirks

  • Read-only, your own vehicle only. The sketch puts the MCP2515 in MCP_LISTENONLY mode: it never transmits, never acknowledges frames and so cannot disturb the car’s bus. Don’t change it to normal mode on a vehicle — a wrong frame can upset brakes, airbags or the engine ECU. Many newer cars put a gateway in front of the OBD-II port, so you may see nothing until a diagnostic request is sent; that is by design.
  • Check the crystal. Most blue modules have an 8 MHz crystal (MCP_8MHZ). Some have 16 MHz. The wrong setting halves or doubles the bit rate and you receive nothing.
  • Most cars run OBD-II CAN at 500 kbit/s; trucks and some ECUs at 250 kbit/s. Set BITRATE to match.
  • Termination: the module’s J1 jumper adds a 120 Ω resistor. A car’s bus is already terminated — remove J1 there. On a two-module bench bus, fit it on both ends.
  • Busy buses send thousands of frames a second. At 115200 baud the Uno can print only part of them; the sketch prints what it can and the MCP2515’s two receive buffers drop the rest. Filter by ID (init_Mask/init_Filt) to see one message cleanly.
  • Power the Uno from the laptop’s USB, not from the car. Join the module GND to the car’s signal ground (OBD-II pin 5).

Code

mcp2515-uno.inoArduino C++Open in PlaygroundOpen in PlaygroundFlash
// MCP2515 CAN module on an Arduino Uno R3 — LISTEN-ONLY sniffer for your own vehicle or test bench.
// VCC → 5V, GND → GND, CS → D10, SI → D11 (MOSI), SO → D12 (MISO), SCK → D13, INT → D2.
// Listen-only mode never sends a frame or an acknowledge, so it cannot disturb the bus.
#include <SPI.h>
#include <mcp_can.h>

const uint8_t CS_PIN = 10, INT_PIN = 2;
const uint8_t BITRATE = CAN_500KBPS;  // most cars' OBD-II CAN; try CAN_250KBPS for trucks / some ECUs
const uint8_t CRYSTAL = MCP_8MHZ;     // read the crystal can: 8.000 → MCP_8MHZ, 16.000 → MCP_16MHZ
MCP_CAN can(CS_PIN);
uint32_t frames = 0;

void setup() {
  Serial.begin(115200);  // CAN traffic is fast: 9600 baud would drop lines
  pinMode(INT_PIN, INPUT);
  if (can.begin(MCP_ANY, BITRATE, CRYSTAL) != CAN_OK) {
    Serial.println(F("MCP2515 init failed - check SPI wiring, 5V and the crystal setting"));
    while (true) delay(1000);
  }
  can.setMode(MCP_LISTENONLY);  // begin() leaves it in loopback; never switch to MCP_NORMAL on a car
  Serial.println(F("Listening (read-only)..."));
}

void loop() {
  if (digitalRead(INT_PIN) == HIGH) return;  // INT goes LOW when a frame is waiting
  unsigned long id;
  uint8_t len;
  uint8_t data[8];
  if (can.readMsgBuf(&id, &len, data) != CAN_OK) return;
  frames++;
  bool ext = id & 0x80000000UL;
  Serial.print(frames);
  Serial.print(ext ? F("  EXT 0x") : F("  STD 0x"));
  Serial.print(id & 0x1FFFFFFFUL, HEX);
  Serial.print(F("  ["));
  Serial.print(len);
  Serial.print(F("] "));
  for (uint8_t i = 0; i < len; i++) {
    if (data[i] < 0x10) Serial.print('0');
    Serial.print(data[i], HEX);
    Serial.print(' ');
  }
  Serial.println();
}

Library: mcp_can (coryjfowler) — compiled with mcp_can 1.5.1.

Expected serial output

Serial Monitor · 115200Output
Listening (read-only)...
1  STD 0x316  [8] 05 1C 0C 1C 0C 00 00 00
2  STD 0x329  [8] 40 8A 7B 12 00 00 00 00
3  STD 0x18F  [8] FE 33 00 00 00 00 00 00

Common errors

“MCP2515 init failed”

SPI wiring (SO and SI swapped is common), no 5 V, or CS not on D10. Check the crystal setting too.

Init OK but no frames at all

Wrong bit rate or crystal setting, CAN-H/CAN-L swapped, no common ground, or the car’s gateway blocks the OBD-II port while idle. Switch the ignition on.

Frames arrive, then stop

The serial port can’t keep up. Use ID filters, or print a count per second instead of every frame.

↑↓ to move · Enter to open