Embedded Development CAN Bus Analyzer in Practice: Car Diagnostic Tool DIY, Understanding the Network on Wheels
As an embedded developer, have you ever wondered how communication works inside a car? Today we’ll build a CAN bus analyzer that can not only read automotive OBD-II data but also be used for industrial CAN network debugging. Spend less than 200 yuan to get the core functions of a professional-grade diagnostic tool!
What Do You Need to Prepare?
| Item | Model/Specification | Price |
|---|---|---|
| Microcontroller | STM32F103C8T6 (Blue Pill) | ¥15 |
| CAN Bus Transceiver | TJA1050 CAN Transceiver | ¥8 |
| OBD-II Connector | 16-pin OBD Female Connector | ¥12 |
| Display | 0.96-inch OLED I2C | ¥10 |
| Power Module | AMS1117 3.3V | ¥3 |
| Total | ¥73 |
If you buy a finished CAN analyzer directly, the price is usually 300-800 yuan. Our DIY cost is only a quarter, and we can fully control the source code!
Step 1: Understanding CAN Bus Basics
Before getting hands-on, let’s quickly understand the core concepts of CAN bus:
What is CAN?
- Controller Area Network
- Developed by Bosch in 1986, originally for automotive use
- Now widely used in industrial, medical, and aerospace applications
Key Characteristics:
- Dual-wire differential signaling (CAN_H and CAN_L)
- Multi-master architecture, no master required
- Built-in error detection and retransmission mechanisms
- Standard frame 11-bit ID, extended frame 29-bit ID
- Common baud rates: 125K/250K/500K/1M bps
Automotive OBD-II Pin Definition:
OBD-II Interface (viewed from top):
┌─────────────────────────────────┐
│ ○ 1 ○ 2 ○ 3 ○ 4 ○ 5 ○ 6 ○ 7 ○ 8 │
│ ○ 9 ○10 ○11 ○12 ○13 ○14 ○15 ○16 │
└─────────────────────────────────┘
Key Pins:
- Pin 4: Chassis Ground (GND)
- Pin 5: Signal Ground (GND)
- Pin 6: CAN_H (ISO 15765-4)
- Pin 14: CAN_L (ISO 15765-4)
- Pin 16: Battery Positive (+12V)
⚠️ Note: Car battery voltage is 12V, but our STM32 operates at 3.3V! A voltage regulator module must be used, otherwise the chip will be burned.
Step 2: Hardware Connection
Connect the circuit according to the diagram below:
STM32F103C8T6 ←→ TJA1050 CAN Module
─────────────────────────────────────
3.3V ←→ VCC
GND ←→ GND
PA11 (USB_DM) ←→ CAN_RX (actually TX)
PA12 (USB_DP) ←→ CAN_TX (actually RX)
TJA1050 ←→ OBD-II Interface
─────────────────────────────────────
CAN_H ←→ Pin 6
CAN_L ←→ Pin 14
GND ←→ Pin 4 or Pin 5
+12V ←→ Pin 16 (regulated to 3.3V via AMS1117)
OLED Display ←→ STM32
─────────────────────────────────────
VCC ←→ 3.3V
GND ←→ GND
SCL ←→ PB6
SDA ←→ PB7
Wiring Tips:
-
CAN_H and CAN_L must never be reversed, otherwise communication will fail or the module may be damaged
-
Pin 4 and Pin 5 of the OBD-II interface are both GND, you can connect to either one
-
After drawing power from OBD Pin 16 (+12V), be sure to use AMS1117 to regulate to 3.3V before powering the MCU
We use Arduino IDE for development, with the CAN library:
# Install Arduino IDE (if not already installed)
sudo apt-get update
sudo apt-get install arduino arduino-core-avr
# Install STM32 core (via Boards Manager)
# 1. Open Arduino IDE
# 2. File → Preferences → Additional Boards Manager URLs
# 3. Add: https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json
# 4. Tools → Board → Boards Manager → Search "STM32" → Install
# Install CAN library
# Sketch → Include Library → Manage Libraries → Search "CAN" → Install "CAN" by sandeep mistry
Board Configuration:
- Board: Generic STM32F1 series
- Model: STM32F103C8
- Upload method: STM32CubeProgrammer (SWD)
- CPU Speed: 72MHz
- Optimize: Smallest (-Os)
Step 4: Core Code Implementation
#include <CAN.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// OLED Configuration
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// CAN Configuration
#define CAN_RX_PIN PA11
#define CAN_TX_PIN PA12
#define CAN_BAUDRATE 500000 // 500Kbps (commonly used in cars)
// Button pin (optional, for page switching)
#define BUTTON_PIN PA0
unsigned long lastMsgTime = 0;
int currentPage = 0;
unsigned long canMsgCount = 0;
// OBD-II PID Request
const uint8_t OBD_REQUEST[] = {0x02, 0x01, 0x0C}; // Request engine RPM
void setup() {
Serial.begin(115200);
while (!Serial);
// Initialize OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("CAN Analyzer Initializing...");
display.display();
// Initialize CAN
pinMode(CAN_RX_PIN, INPUT);
pinMode(CAN_TX_PIN, OUTPUT);
CAN.setPins(CAN_RX_PIN, CAN_TX_PIN);
if (!CAN.begin(CAN_BAUDRATE)) {
display.println("CAN Init Failed!");
display.display();
Serial.println("Starting CAN failed!");
while (1);
}
display.println("CAN Init Success!");
display.print("Baud Rate: ");
display.print(CAN_BAUDRATE / 1000);
display.println("Kbps");
display.display();
Serial.println("CAN Bus Analyzer Ready");
Serial.print("Baud Rate: ");
Serial.println(CAN_BAUDRATE);
// Send OBD-II initialization request (Mode 01)
sendOBDRequest(0x01, 0x00);
delay(100);
}
void loop() {
// Receive CAN messages
int packetSize = CAN.parsePacket();
if (packetSize) {
canMsgCount++;
uint32_t canId = CAN.packetId();
uint8_t data[8];
int dataLen = 0;
while (CAN.available()) {
if (dataLen < 8) {
data[dataLen++] = CAN.read();
}
}
// Display on OLED
if (millis() - lastMsgTime > 200) {
displayMessage(canId, data, dataLen);
lastMsgTime = millis();
}
// Output to serial port
printCanMessage(canId, data, dataLen);
// Parse OBD-II response
if (isOBDResponse(canId, data, dataLen)) {
parseOBDResponse(data, dataLen);
}
}
// Standby screen
if (millis() - lastMsgTime > 5000) {
displayStandbyScreen();
}
// Send OBD request once per second
if (millis() % 1000 < 50) {
sendOBDRequest(0x01, 0x0C); // Request RPM
}
}
// Display CAN message on OLED
void displayMessage(uint32_t canId, uint8_t* data, int len) {
display.clearDisplay();
display.setCursor(0, 0);
display.print("ID: 0x");
display.println(canId, HEX);
display.print("Data: ");
for (int i = 0; i < len && i < 8; i++) {
if (data[i] < 0x10) display.print("0");
display.print(data[i], HEX);
display.print(" ");
}
display.println();
display.print("Count: ");
display.println(canMsgCount);
// If OBD response
if (len >= 3 && data[1] == 0x41) {
display.println("--- OBD Response ---");
parseOBDResponse(data, len);
}
display.display();
}
// Parse OBD-II response
void parseOBDResponse(uint8_t* data, int len) {
if (len >= 3 && data[1] == 0x41) { // Mode 01 response
uint8_t pid = data[2];
if (pid == 0x0C && len >= 5) { // Engine RPM
// RPM = (A*256 + B) / 4
int rpm = ((data[3] * 256) + data[4]) / 4;
display.print("RPM: ");
display.print(rpm);
display.println(" RPM");
}
else if (pid == 0x0D && len >= 4) { // Vehicle speed
uint8_t speed = data[3];
display.print("Speed: ");
display.print(speed);
display.println(" km/h");
}
else if (pid == 0x0F && len >= 4) { // Coolant temperature
int temp = data[3] - 40;
display.print("Temp: ");
display.print(temp);
display.println(" °C");
}
}
}
// Serial output of CAN messages (CSV format, easy to import to computer for analysis)
void printCanMessage(uint32_t canId, uint8_t* data, int len) {
Serial.print(millis());
Serial.print(",");
Serial.print(canId, HEX);
Serial.print(",");
for (int i = 0; i < len; i++) {
if (data[i] < 0x10) Serial.print("0");
Serial.print(data[i], HEX);
if (i < len - 1) Serial.print(" ");
}
Serial.println();
}
// Send OBD-II request
void sendOBDRequest(uint8_t mode, uint8_t pid) {
CAN.beginPacket(0x7DF); // OBD-II broadcast address
CAN.write(0x02); // Data length
CAN.write(0x01); // Mode 01 (current data)
CAN.write(pid); // PID
CAN.endPacket();
}
// Check if it's an OBD-II response
bool isOBDResponse(uint32_t canId, uint8_t* data, int len) {
// OBD-II response ID range is 0x7E8-0x7EF
return (canId >= 0x7E8 && canId <= 0x7EF && len >= 3 && data[1] == 0x41);
}
// Display standby screen
void displayStandbyScreen() {
static unsigned long lastBlink = 0;
if (millis() - lastBlink > 500) {
display.clearDisplay();
display.setCursor(0, 0);
display.println("Waiting for CAN messages...");
display.print("Total count: ");
display.println(canMsgCount);
display.display();
lastBlink = millis();
}
}
Code Explanation:
-
Initialize CAN bus and OLED display in
setup(), configure baud rate to 500Kbps -
In
loop(), continuously callCAN.read()to receive CAN data frames, and count withcanMsgCount -
Use
millis()to implement non-blocking timed OLED refresh, avoidingdelay()blocking the main loop -
Output CAN data in CSV format to serial port via
Serial.print(), convenient for host computer analysis -
LED indicator blinks once per second, indicating the device is running normally
Testing Steps:
-
Connect the device to the computer via USB, open the serial monitor to confirm successful initialization
-
Insert the OBD-II interface into the car’s diagnostic port, after powering on observe if the OLED displays real-time CAN data
-
OLED displays real-time CAN messages
-
After engine start, displays RPM data
-
Serial monitor outputs CSV format data
Common Problem Troubleshooting
Problem 1: CAN initialization failed
-
Cause: Pin configuration error or baud rate mismatch
-
Solution: Check if PA11/PA12 are connected correctly, try different baud rates (125K/250K/500K)
Problem 2: No messages received
-
Cause: Missing termination resistor or wiring error
-
Solution: Add a 120Ω resistor between CAN_H and CAN_L, check OBD pin definitions
Problem 3: Display shows garbled text
-
Cause: OLED address error (commonly 0x3C or 0x3D)
-
Solution: Modify the address in
display.begin(SSD1306_SWITCHCAPVCC, 0x3C)
Problem 4: OBD response parsing error
-
Cause: Different car models may use different OBD protocols
-
Solution: First print raw data, adjust parsing logic based on actual response
Problem 5: Device overheats severely
-
Cause: Voltage regulator module overloaded
-
Solution: Check for short circuits, add heatsink, avoid drawing power from OBD for extended periods
Expansion Feature Suggestions
After completing the basic version, you can consider the following upgrades:
-
Add GPS module to record vehicle location and travel trajectory
-
Support automatic identification of multiple CAN protocols (CAN 2.0A/B, CAN FD)
-
Add SD card data logging function for offline analysis
-
Transmit data to mobile APP in real-time via Bluetooth/WiFi
-
Support OBD-II PID standard commands for universal fault code reading
Through this project, we:
-
Understood the working principle of CAN bus
-
Mastered the use of TJA1050 CAN transceiver
-
Learned basic parsing of OBD-II protocol
-
Built a CAN analyzer costing less than 100 yuan
This device can not only be used for automotive diagnostics but also for industrial CAN network debugging, smart home system analysis, and other scenarios. More importantly, you fully control the source code and can freely customize functions according to your needs.
Next, you can try:
-
Parsing more OBD-II PIDs (fault codes, fuel consumption, etc.)
-
Developing computer analysis software (Python + PyQt)
-
Creating a beautiful enclosure to turn it into a portable tool
Hope this blog post is helpful to you!
Related Resources:
-
CAN Bus Protocol Explained in Detail
-
OBD-II PID List
-
STM32 CAN Library Documentation
-
This Project’s GitHub Repository
-
TJA1050 Datasheet