Learning Goals 5 min
Every modern car has 30+ electronic control units (ECUs) that all need to share data: engine RPM, ABS state, dashboard backlight, climate control. They talk on a Controller Area Network — CAN bus — designed by Bosch in 1983 specifically for this kind of robust, real-time, multi-talker environment. By the end of this lesson you will:
- Compare CAN to RS-485 / Modbus: peer-to-peer with priorities, automatic collision resolution, very robust against noise.
- Identify CAN hardware: MCP2515 controller + TJA1050 transceiver + your Arduino.
- Read a CAN frame: ID + DLC + 0..8 data bytes + CRC.
Warm-Up 10 min
Today is conceptual + light wiring. Optional hardware: 2 × MCP2515 + TJA1050 CAN module (the "CAN-BUS shield" or breakout pair).
Where CAN shows up
- Every car built after ~2008 (often the OBD-II port).
- Industrial robotics + CNC.
- Boat / RV electronics (NMEA 2000 is built on CAN).
- Some medical / aerospace gear.
If you ever want to read live data from your family car's ECU, you'll need CAN.
New Concept · The CAN protocol 25 min
Wires
Two: CAN_H and CAN_L (differential pair). Same robust differential signalling as RS-485. Plus a common ground (separate from the CAN_H/CAN_L pair, but typical).
Speeds: typically 125 kbps (low speed), 500 kbps (in-vehicle), 1 Mbps (max for classic CAN). CAN-FD up to 8 Mbps.
Frame format
| Field | Size | Purpose |
|---|---|---|
| Start-of-frame | 1 bit | Begins a frame |
| Identifier (11-bit standard or 29-bit extended) | 11 or 29 bits | What kind of message — lower = higher priority |
| RTR (remote-transmission-request) | 1 bit | Data frame or request frame |
| DLC (data length code) | 4 bits | How many data bytes follow (0..8) |
| Data | 0..8 bytes | The payload |
| CRC | 15 bits | Error check |
| ACK | 2 bits | Receivers ack the frame |
| End-of-frame | 7 bits | Frame complete |
Tiny by modern standards (max 8 data bytes per frame), but optimised for low-latency, high-reliability transmission.
Priority and arbitration
Identifiers are the priority. Lower identifier = higher priority. When two nodes start transmitting simultaneously, the bus "arbitrates":
- Both nodes start sending their ID bit by bit.
- If one node sees a "dominant" bit (logical 0) while it's sending "recessive" (1), it backs off.
- The winning node continues without retransmission.
The losing node retries automatically after the bus goes idle. No collisions, no "CSMA back-off random timeout" nonsense. Determinism = perfect for time-sensitive control.
No addresses
Unlike Modbus, CAN doesn't have device addresses. Frames are topic-based: ID 0x100 might mean "engine RPM" — every interested node listens for that ID. The sender broadcasts; the receivers filter.
This is closer to MQTT than to UART. Suits multi-listener telemetry.
Hardware
| Chip | Role |
|---|---|
| MCP2515 | CAN controller — handles frame buffering, CRC, arbitration. SPI to Arduino. |
| TJA1050 / SN65HVD230 | CAN transceiver — bridges MCP2515's logic to CAN_H/CAN_L differential wires. |
| CAN-BUS Shield (SparkFun, Seeed) | Arduino-shaped board with both chips + DB9 connector. |
Library: mcp_can
#include <mcp_can.h>
#include <SPI.h>
MCP_CAN CAN(10); // CS on D10
void setup() {
Serial.begin(115200);
while (CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) != CAN_OK) {
Serial.println("# CAN init fail, retry");
delay(500);
}
CAN.setMode(MCP_NORMAL);
Serial.println("# CAN ready");
}
void loop() {
long unsigned int id;
unsigned char len;
unsigned char buf[8];
if (CAN.checkReceive() == CAN_MSGAVAIL) {
CAN.readMsgBuf(&id, &len, buf);
Serial.print("ID 0x"); Serial.print(id, HEX);
Serial.print(" Len "); Serial.print(len);
Serial.print(" Data");
for (int i = 0; i < len; i++) {
Serial.print(" 0x"); Serial.print(buf[i], HEX);
}
Serial.println();
}
}Transmit:
unsigned char msg[2] = { 0x12, 0x34 };
CAN.sendMsgBuf(0x100, 0, 2, msg);Worked Example · Two-Arduino CAN demo 25 min
Two MCP2515 modules + two Arduinos. Wire CAN_H to CAN_H, CAN_L to CAN_L, GND together. Termination resistors at each end.
TX sketch
unsigned char msg[8];
unsigned int counter = 0;
void loop() {
msg[0] = counter >> 8;
msg[1] = counter & 0xFF;
CAN.sendMsgBuf(0x100, 0, 2, msg);
counter++;
delay(1000);
}RX sketch
The §3 receive loop. Prints "ID 0x100 Len 2 Data 0x00 0x01 ..."
Add priorities
Have TX send two IDs alternately:
CAN.sendMsgBuf(0x100, 0, 2, msg); // higher priority
delay(500);
CAN.sendMsgBuf(0x500, 0, 2, msg2); // lower priority
delay(500);Add a second TX board sending 0x300. RX prints all three but you'll see consistent ordering during arbitration.
What this is preparing for
If you ever read OBD-II from a car, you'd use the same MCP2515 wired into the OBD-II port's CAN_H / CAN_L. Listen for known IDs (engine RPM = 0x7E8 etc.); decode their data bytes per the OBD-II PID spec. Hobbyists make excellent dashboards this way.
Try It Yourself 15 min
Goal: Send a pot reading (2 bytes) every second. RX board lights an LED if the value is > 512.
Goal: Filter incoming CAN frames so the RX board only acts on a specific ID, ignoring all others. MCP2515 supports hardware filtering — much cheaper than checking in software.
Goal: Three nodes on the bus, all transmitting. Use IDs 0x100, 0x200, 0x300. Verify that even when all transmit simultaneously, no frame is lost (the arbiter resolves).
Mini-Challenge · Pick CAN vs Modbus vs MQTT 10 min
For each scenario, pick the right protocol:
- A water-treatment plant with 30 sensors on a 500 m field, polled by a central PLC.
- A racing car's 15 ECUs sharing real-time engine telemetry.
- A smart-home thermostat reporting temperature to a Raspberry Pi.
Reveal
- Modbus over RS-485 — classic industrial polling pattern, designed for exactly this.
- CAN — designed for in-vehicle real-time multi-talker telemetry.
- MQTT — internet-friendly pub/sub, fits the smart-home ecosystem.
Recap 5 min
CAN = differential 2-wire bus with built-in arbitration and topic-based (not address-based) frames. Standard in cars, industrial robotics, NMEA 2000 marine networks. MCP2515 controller + TJA1050 transceiver = the Arduino-friendly CAN interface. Tomorrow we ship Cluster D's capstone — a GPS + LoRa tracker.
- CAN bus
- Controller Area Network. Differential 2-wire, multi-talker, deterministic real-time. Used in cars, industrial robotics, ships.
- CAN_H / CAN_L
- The two differential wires. CAN_H = high, CAN_L = low (relative to dominant state).
- Frame ID
- 11-bit (standard) or 29-bit (extended) identifier prefixing each frame. Doubles as priority — lower = higher.
- DLC (Data Length Code)
- How many payload bytes follow. 0–8 for classic CAN; up to 64 for CAN-FD.
- Bit-wise arbitration
- The collision-free mechanism. Higher-priority frame "wins" bit-by-bit during ID transmission; loser retries.
- Topic-based addressing
- Frames identified by what they ARE, not by who sends or receives them. Multi-listener by design.
- MCP2515
- The dominant Arduino-side CAN controller. SPI to Arduino, talks to a TJA1050 transceiver.
- Termination 120 Ω
- Mandatory resistor across CAN_H/CAN_L at each end of the bus.
- OBD-II
- Standard car diagnostic port (since 1996). Most newer cars expose CAN here. Read at your own risk.
Homework 5 min
- If you have CAN hardware: build the two-Arduino bus and confirm frames are exchanged.
- Read ahead to ARD-L04-22 (GPS + LoRa Tracker). Bring a NEO-6M / NEO-M8 GPS module + LoRa modules from L04-18.