|
CAN Bus Analyzer in Practice: Car Diagnostic Tool DIY, Understanding the Network on Wheels

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?

ItemModel/SpecificationPrice
MicrocontrollerSTM32F103C8T6 (Blue Pill)¥15
CAN Bus TransceiverTJA1050 CAN Transceiver¥8
OBD-II Connector16-pin OBD Female Connector¥12
Display0.96-inch OLED I2C¥10
Power ModuleAMS1117 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:

  1. CAN_H and CAN_L must never be reversed, otherwise communication will fail or the module may be damaged

  2. Pin 4 and Pin 5 of the OBD-II interface are both GND, you can connect to either one

  3. 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:

  1. Initialize CAN bus and OLED display in setup(), configure baud rate to 500Kbps

  2. In loop(), continuously call CAN.read() to receive CAN data frames, and count with canMsgCount

  3. Use millis() to implement non-blocking timed OLED refresh, avoiding delay() blocking the main loop

  4. Output CAN data in CSV format to serial port via Serial.print(), convenient for host computer analysis

  5. LED indicator blinks once per second, indicating the device is running normally

Testing Steps:

  1. Connect the device to the computer via USB, open the serial monitor to confirm successful initialization

  2. 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:

  1. Add GPS module to record vehicle location and travel trajectory

  2. Support automatic identification of multiple CAN protocols (CAN 2.0A/B, CAN FD)

  3. Add SD card data logging function for offline analysis

  4. Transmit data to mobile APP in real-time via Bluetooth/WiFi

  5. 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