目录

# CAN 总线分析仪实战:汽车诊断工具 DIY,读懂车轮上的网络

作为一名嵌入式开发者,你是否好奇汽车内部是如何通信的?今天我们就来动手制作一台 CAN 总线分析仪,不仅能读取汽车 OBD-II 数据,还能用于工业 CAN 网络调试。花不到 200 元,就能拥有专业级诊断工具的核心功能!

## 需要准备什么?

物品 型号/规格 价格
主控板 STM32F103C8T6 (Blue Pill) ¥15
CAN 模块 TJA1050 CAN 收发器 ¥8
OBD-II 接口 16 针 OBD 母头 ¥12
显示屏 0.96 寸 OLED I2C ¥10
杜邦线 公对公/公对母 ¥5
外壳 3D 打印/塑料盒 ¥20
稳压模块 AMS1117 3.3V ¥3
**总计** **¥73**

如果直接购买成品 CAN 分析仪,价格通常在 300-800 元。我们 DIY 的成本只有四分之一,而且还能完全掌控源代码!

## 步骤 1:理解 CAN 总线基础

在动手之前,我们先快速了解 CAN 总线的核心概念:

**CAN 是什么?**
– Controller Area Network,控制器局域网
– 由博世 1986 年开发,最初用于汽车
– 现在广泛应用于工业、医疗、航空航天

**关键特性:**
– 双线差分信号(CAN_H 和 CAN_L)
– 多主架构,无需主机
– 自带错误检测和重发机制
– 标准帧 11 位 ID,扩展帧 29 位 ID
– 常见波特率:125K/250K/500K/1M bps

**汽车 OBD-II 引脚定义:**
“`
OBD-II 接口(从上往下看):
┌─────────────────────────────────┐
│ ○ 1 ○ 2 ○ 3 ○ 4 ○ 5 ○ 6 ○ 7 ○ 8 │
│ ○ 9 ○10 ○11 ○12 ○13 ○14 ○15 ○16 │
└─────────────────────────────────┘

关键引脚:
– Pin 4: 底盘接地 (GND)
– Pin 5: 信号接地 (GND)
– Pin 6: CAN_H (ISO 15765-4)
– Pin 14: CAN_L (ISO 15765-4)
– Pin 16: 电池正极 (+12V)
“`

⚠️ **注意事项:** 汽车电瓶电压是 12V,但我们的 STM32 工作在 3.3V!必须使用稳压模块,否则会烧毁芯片。

## 步骤 2:硬件连接

按照下图连接电路:

“`
STM32F103C8T6 ←→ TJA1050 CAN 模块
─────────────────────────────────────
3.3V ←→ VCC
GND ←→ GND
PA11 (USB_DM) ←→ CAN_RX (实际是 TX)
PA12 (USB_DP) ←→ CAN_TX (实际是 RX)

TJA1050 ←→ OBD-II 接口
─────────────────────────────────────
CAN_H ←→ Pin 6
CAN_L ←→ Pin 14
GND ←→ Pin 4 或 Pin 5
+12V ←→ Pin 16 (经 AMS1117 稳压到 3.3V)

OLED 显示屏 ←→ STM32
─────────────────────────────────────
VCC ←→ 3.3V
GND ←→ GND
SCL ←→ PB6
SDA ←→ PB7
“`

**接线技巧:**
1. CAN_H 和 CAN_L 是差分对,走线尽量平行
2. 在 CAN_H 和 CAN_L 之间并联 120Ω终端电阻(如果总线上没有其他终端电阻)
3. 电源部分加 10μF 和 100nF 电容滤波

## 步骤 3:软件环境搭建

我们使用 Arduino IDE 开发,配合 CAN 库:

“`bash
# 安装 Arduino IDE(如果还没有)
sudo apt-get update
sudo apt-get install arduino arduino-core-avr

# 安装 STM32 核心(通过 Boards Manager)
# 1. 打开 Arduino IDE
# 2. 文件 → 首选项 → 附加开发板管理器 URL
# 3. 添加:https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json
# 4. 工具 → 开发板 → 开发板管理器 → 搜索”STM32″ → 安装

# 安装 CAN 库
# sketch → 加载库 → 管理库 → 搜索”CAN” → 安装”CAN” by sandeep mistry
“`

**开发板配置:**
– 开发板:Generic STM32F1 series
– 型号:STM32F103C8
– Upload method: STM32CubeProgrammer (SWD)
– CPU Speed: 72MHz
– Optimize: Smallest (-Os)

## 步骤 4:核心代码实现

“`cpp
#include
#include
#include
#include

// OLED 配置
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// CAN 配置
#define CAN_RX_PIN PA11
#define CAN_TX_PIN PA12
#define CAN_BAUDRATE 500000 // 500Kbps (汽车常用)

// 按钮引脚(可选,用于切换页面)
#define BUTTON_PIN PA0

unsigned long lastMsgTime = 0;
int currentPage = 0;
unsigned long canMsgCount = 0;

// OBD-II PID 请求
const uint8_t OBD_REQUEST[] = {0x02, 0x01, 0x0C}; // 请求发动机转速

void setup() {
Serial.begin(115200);
while (!Serial);

// 初始化 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 分析仪初始化…”);
display.display();

// 初始化 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 初始化失败!”);
display.display();
Serial.println(“Starting CAN failed!”);
while (1);
}

display.println(“CAN 初始化成功!”);
display.print(“波特率:”);
display.print(CAN_BAUDRATE / 1000);
display.println(“Kbps”);
display.display();

Serial.println(“CAN 总线分析仪就绪”);
Serial.print(“波特率:”);
Serial.println(CAN_BAUDRATE);

// 发送 OBD-II 初始化请求(模式 01)
sendOBDRequest(0x01, 0x00);
delay(100);
}

void loop() {
// 接收 CAN 消息
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(); } else { CAN.read(); // 丢弃多余数据 } } // 显示在 OLED 上 displayCanMessage(canId, data, dataLen); // 串口输出(用于电脑分析) printCanMessage(canId, data, dataLen); lastMsgTime = millis(); } // 如果 5 秒没有消息,显示待机界面 if (millis() - lastMsgTime > 5000) {
displayStandbyScreen();
}

// 每秒发送一次 OBD 请求
if (millis() % 1000 < 50) { sendOBDRequest(0x01, 0x0C); // 请求发动机转速 } delay(10); } // 发送 OBD-II 请求 void sendOBDRequest(uint8_t mode, uint8_t pid) { CAN.beginTransmission(0x7DF); // OBD 请求地址 CAN.write(0x02); // 数据长度 CAN.write(mode); CAN.write(pid); CAN.write(0x00); // 填充字节 CAN.write(0x00); CAN.write(0x00); CAN.write(0x00); CAN.write(0x00); CAN.endTransmission(); } // 在 OLED 上显示 CAN 消息 void displayCanMessage(uint32_t canId, uint8_t* data, int len) { display.clearDisplay(); display.setCursor(0, 0); // 显示 CAN ID display.print("ID: 0x"); display.println(canId, HEX); // 显示数据 display.print("DATA: "); for (int i = 0; i < len; i++) { if (data[i] < 0x10) display.print("0"); display.print(data[i], HEX); display.print(" "); } display.println(); // 显示消息计数 display.print("计数:"); display.println(canMsgCount); // 解析 OBD-II 响应 if (canId == 0x7E8 && len >= 3) {
display.println(“— OBD 响应 —“);
parseOBDResponse(data, len);
}

display.display();
}

// 解析 OBD-II 响应
void parseOBDResponse(uint8_t* data, int len) {
if (len >= 3 && data[1] == 0x41) { // 模式 01 响应
uint8_t pid = data[2];

if (pid == 0x0C && len >= 5) { // 发动机转速
// 转速 = (A*256 + B) / 4
int rpm = ((data[3] * 256) + data[4]) / 4;
display.print(“转速:”);
display.print(rpm);
display.println(” RPM”);
}
else if (pid == 0x0D && len >= 4) { // 车速
uint8_t speed = data[3];
display.print(“车速:”);
display.print(speed);
display.println(” km/h”);
}
else if (pid == 0x0F && len >= 4) { // 冷却液温度
int temp = data[3] – 40;
display.print(“水温:”);
display.print(temp);
display.println(” °C”);
}
}
}

// 串口输出 CAN 消息(CSV 格式,方便导入电脑分析)
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(); } // 待机屏幕 void displayStandbyScreen() { static unsigned long lastBlink = 0; if (millis() - lastBlink > 500) {
display.clearDisplay();
display.setCursor(0, 0);
display.println(“等待 CAN 消息…”);
display.print(“总计数:”);
display.println(canMsgCount);
display.display();
lastBlink = millis();
}
}
“`

**代码说明:**
1. 初始化 CAN 控制器和 OLED 显示屏
2. 持续监听 CAN 总线消息
3. 解析 OBD-II 协议响应(发动机转速、车速、水温等)
4. 实时显示在 OLED 屏幕上
5. 通过串口输出原始数据,方便电脑端分析

## 步骤 5:测试验证

**测试步骤:**

1. **台架测试(不接汽车):**
“`bash
# 使用另一个 CAN 模块作为发送端
# 发送测试帧
CAN.beginTransmission(0x123);
CAN.write(0xDE);
CAN.write(0xAD);
CAN.write(0xBE);
CAN.write(0xEF);
CAN.endTransmission();
“`

2. **实车测试:**
– 将设备插入汽车 OBD-II 接口(通常在方向盘下方)
– 打开汽车电源(不需要启动发动机)
– 观察 OLED 屏幕是否有数据
– 启动发动机,查看转速数据

**预期效果:**
– OLED 显示实时 CAN 消息
– 发动机启动后显示 RPM 数据
– 串口监视器输出 CSV 格式数据

## 常见问题排查

**问题 1:CAN 初始化失败**
– **原因:** 引脚配置错误或波特率不匹配
– **解决:** 检查 PA11/PA12 是否正确连接,尝试不同波特率(125K/250K/500K)

**问题 2:收不到任何消息**
– **原因:** 终端电阻缺失或接线错误
– **解决:** 在 CAN_H 和 CAN_L 之间添加 120Ω电阻,检查 OBD 引脚定义

**问题 3:显示乱码**
– **原因:** OLED 地址错误(常见 0x3C 或 0x3D)
– **解决:** 修改 `display.begin(SSD1306_SWITCHCAPVCC, 0x3C)` 中的地址

**问题 4:OBD 响应解析错误**
– **原因:** 不同车型的 OBD 协议可能不同
– **解决:** 先打印原始数据,根据实际响应调整解析逻辑

**问题 5:设备发热严重**
– **原因:** 稳压模块负载过大
– **解决:** 检查是否有短路,添加散热片,避免长时间从 OBD 取电

## 扩展功能建议

完成基础版本后,你可以考虑以下升级:

1. **添加 SD 卡模块** – 记录 CAN 日志用于后续分析
2. **蓝牙/WiFi 模块** – 无线传输数据到手机 APP
3. **GPS 模块** – 记录车辆位置轨迹
4. **彩色 TFT 屏幕** – 显示更丰富的信息
5. **多协议支持** – 兼容 K-Line、J1850 等其他汽车协议

## 总结

通过这个项目,我们:
– 理解了 CAN 总线的工作原理
– 掌握了 TJA1050 CAN 收发器的使用方法
– 学会了 OBD-II 协议的基本解析
– 制作了一台成本不到 100 元的 CAN 分析仪

这台设备不仅可以用于汽车诊断,还能用于工业 CAN 网络调试、智能家居系统分析等场景。更重要的是,你完全掌握了源代码,可以根据需求自由定制功能。

下一步,你可以尝试:
– 解析更多 OBD-II PID(故障码、油耗等)
– 开发电脑端分析软件(Python + PyQt)
– 制作精美外壳,变成便携式工具

希望这篇博客文章对您有所帮助!


**相关资源:**
– [CAN 总线协议详解](https://www.can-cia.org/can-knowledge)
– [OBD-II PID 列表](https://en.wikipedia.org/wiki/OBD-II_PIDs)
– [STM32 CAN 库文档](https://github.com/sandeepmistry/arduino-CAN)
– [本项目 GitHub 仓库](https://github.com/makeronsite/can-bus-analyzer)
– [TJA1050 数据手册](https://www.nxp.com/docs/en/data-sheet/TJA1050.pdf)

更多关于 的文章
关注创客出手公众号

关注创客出手