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 R3 | MCP2515 CAN bus module (with TJA1050) | Wire | Notes |
|---|---|---|---|
| — | 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) |
| 5V | VCC | red | The TJA1050 transceiver needs 5 V — a direct match for the Uno. |
| GND | GND | black | |
| D10 | CS | purple | |
| D11 | SI | green | SPI MOSI (into the module). |
| D12 | SO | blue | SPI MISO (out of the module) — 5 V, which the Uno reads directly. |
| D13 | SCK | yellow | |
| D2 | INT | orange | LOW 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_LISTENONLYmode: 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
BITRATEto 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 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();
}// 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
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 00Listening (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.