最近在做一个商用车的网关项目,涉及多路CAN总线的隔离和故障诊断。踩了不少坑,把这块知识系统整理一下。文章会从CAN总线基础讲起,重点放在隔离设计和故障诊断的实现上。
前言
CAN总线在汽车电子里的地位不用多说,从发动机ECU到车身控制器,几乎所有节点都挂在CAN上。但CAN总线有个特点:一个节点出问题,可能把整条总线拖垮。
我之前就遇到过一个案例:某个传感器模块内部短路,把CAN_H拉低,结果整车通信全挂了。后来加了隔离设计才解决。
本文会详细讲解:
- CAN总线物理层原理
- 为什么需要隔离以及怎么做隔离
- 常见故障类型和诊断方法
- 完整的故障诊断代码实现
一、CAN总线基础回顾
1.1 物理层特性
CAN总线采用差分信号传输,两根线分别是CAN_H和CAN_L:
显性电平(Dominant) 隐性电平(Recessive)
CAN_H: 3.5V ───────── 2.5V ─────────
CAN_L: 1.5V ───────── 2.5V ─────────
差分电压: 2.0V 0V
逻辑值: 0 1
几个关键参数:
| 参数 | 高速CAN | 低速CAN |
|---|---|---|
| 速率 | 125k~1Mbps | 10k~125kbps |
| 总线长度 | <40m@1Mbps | <1000m@40kbps |
| 终端电阻 | 120Ω×2 | 无(有斜率电阻) |
| 容错能力 | 无 | 单线工作 |
汽车上最常见的是高速CAN(500kbps),用于动力总成;低速CAN用于车身舒适系统。
1.2 报文结构
标准CAN帧结构:
┌─────┬─────────┬───┬──────┬─────────────┬─────┬─────┬───┬───────┐
│ SOF │ 11bit │RTR│ IDE │ 4bit DLC │0-8B │ CRC │ACK│ EOF │
│ │ 标识符 │ │ │ │数据 │ │ │ │
└─────┴─────────┴───┴──────┴─────────────┴─────┴─────┴───┴───────┘
1b 11b 1b 1b 4b 0-64b 15b 2b 7b
扩展帧把标识符扩展到29位,其他基本一样。
1.3 位时序与采样点
CAN的位时序分成几个段:
一个位时间(Bit Time)
├─────────────────────────────────────────┤
│ SYNC │ PROP │ PHASE1 │ PHASE2 │
│ 1Tq │ 1-8Tq │ 1-8Tq │ 1-8Tq │
↑
采样点
采样点位置很关键,一般设在75%~80%:
c
// 500kbps位时序配置示例(假设CAN时钟36MHz)
// Tq = (BRP+1) / 36MHz
// 位时间 = (1 + PROP + PHASE1 + PHASE2) * Tq = 1/500k = 2us
#define CAN_BRP 3 // 分频系数 -> Tq = 4/36M = 111ns
#define CAN_SJW 1 // 同步跳转宽度
#define CAN_PROP 5 // 传播段
#define CAN_PHASE1 6 // 相位段1
#define CAN_PHASE2 6 // 相位段2
// 总Tq数 = 1+5+6+6 = 18
// 位时间 = 18 * 111ns = 2us -> 500kbps
// 采样点 = (1+5+6)/18 = 66.7%
采样点不对会导致通信不稳定,尤其是总线较长或节点较多时。
二、CAN总线隔离设计
2.1 为什么需要隔离
直接把多个CAN节点挂在一起,风险很大:
- 故障扩散:一个节点短路会影响整条总线
- 地电位差:不同ECU的地电位可能不同,产生地环流
- EMC问题:高压系统(如电驱)干扰低压系统
- 功能安全:关键系统需要和非关键系统隔离
特别是新能源车,动力电池、电机控制器的地和车身地是隔离的,CAN通信必须做电气隔离。
2.2 隔离方案对比
| 方案 | 隔离电压 | 速率 | 成本 | 典型芯片 |
|---|---|---|---|---|
| 光耦隔离 | 2.5~5kV | <1Mbps | 低 | 6N137+TJA1050 |
| 磁耦隔离 | 2.5~5kV | <1Mbps | 中 | ADuM1201 |
| 集成隔离CAN | 2.5kV | 1Mbps | 中 | ISO1050 |
| 容耦隔离 | 5kV | >10Mbps | 高 | ISO7741 |
我现在项目用的比较多的是ISO1050,集成度高,外围简单。
2.3 光耦隔离方案
传统方案,成本低但速率受限:
隔离栅
MCU侧 │ 总线侧
│
┌──────────────│──────────────────┐
│ │ │
TXD ─┤→ 光耦(发送) ─│─→ TXD ┐ │
│ │ ├─ CAN收发器 ─┬─ CAN_H
RXD ←┤← 光耦(接收) ←│── RXD ┘ │ └─ CAN_L
│ │ │
VCC1 │ │ VCC2│
GND1 │ │ GND2│
└──────────────│──────────────────┘
│
电路设计要点:
MCU_TXD ────┬──── R1(330Ω) ──── LED(+) ──┐
│ │ 光耦1
GND LED(-) ─┘ (6N137)
│
隔离栅 │
│
VCC2 ─┬─ R2(4.7k) ─┬─ 收发器TXD
│ │
└── OUT ─────┘
收发器RXD ──┬──── R3(330Ω) ──── LED(+) ──┐
│ │ 光耦2
GND2 LED(-) ─┘
│
隔离栅 │
│
VCC1 ─┬─ R4(4.7k) ─┬─ MCU_RXD
│ │
└── OUT ─────┘
光耦的上升沿和下降沿不对称,高速时波形会畸变。6N137极限大概600kbps,再快就得用专用高速光耦了。
2.4 集成隔离方案(推荐)
ISO1050这类芯片把隔离和收发器做在一起了:
ISO1050
┌─────────────────┐
│ │
TXD ─────┤TXD CANH├───── CAN_H
│ 隔离栅 │
RXD ─────┤RXD CANL├───── CAN_L
│ │
VCC1 ────┤VCC1 VCC2 ├───── VCC2(隔离电源)
│ │
GND1 ────┤GND1 GND2 ├───── GND2
│ │
└─────────────────┘
应用电路:
c
/*
* ISO1050典型应用电路
*
* ┌─────────────┐
* MCU │ ISO1050 │ CAN总线
* │ │
* PA11(TXD) ──────────┤TXD CANH├─────┬───── CAN_H
* │ │ │
* PA12(RXD) ──────────┤RXD CANL├──┬──┼───── CAN_L
* │ │ │ │
* 3.3V ──┬─ C1 ────┤VCC1 VCC2 ├──┼──┼─┬─ 5V_ISO
* │ 100nF │ │ │ │ │
* GND ───┴─────────┤GND1 GND2 ├──┼──┼─┴─ GND_ISO
* │ │ │ │
* └─────────────┘ │ │
* │ │
* C2 ───────┴──┘ (100nF)
* │
* R_term ───┴─── 120Ω终端电阻
* (可选) (总线两端各一个)
*/
2.5 隔离电源设计
隔离CAN需要隔离电源,常用方案:
- DC-DC隔离模块:如B0505S,简单但EMC差
- 反激变换器:成本低,需要设计变压器
- 推挽变换器:效率高,用于大功率场合
我一般用现成的隔离DC-DC模块,省事:
5V_IN ──────┬──────┐
│ │
─┴─ │
GND ┌─┴─┐
│ │ B0505S-1W
│ │
GND_ISO─┴─┘
│ │
│ └──── 5V_ISO
─┴─
GND_ISO
注意加共模电感和Y电容做EMC处理:
5V_IN ──┬── L1 ──┬── B0505S ──┬── L2 ──┬── 5V_ISO
│ │ │ │
─┴─ ─┴─ ─┴─ ─┴─
C1 C2(Y) C3(Y) C4
│ │ │ │
GND ───┴────────┴── 隔离 ────┴────────┴── GND_ISO
三、CAN总线故障类型分析
3.1 物理层故障
| 故障类型 | 现象 | 原因 |
|---|---|---|
| CAN_H对地短路 | 无法发送显性电平 | 线束破损、接插件进水 |
| CAN_L对地短路 | 差分电压异常 | 同上 |
| CAN_H对电源短路 | 总线电压过高 | 线束混线 |
| CAN_H/L短接 | 差分电压为0 | 接插件针脚弯曲 |
| 开路 | 部分节点无法通信 | 线束断裂 |
| 终端电阻异常 | 波形振铃、误码率高 | 电阻脱落或错配 |
3.2 协议层故障
| 故障类型 | 错误帧类型 | 原因 |
|---|---|---|
| 位错误 | 主动/被动错误帧 | 采样点不对、干扰 |
| 填充错误 | 主动/被动错误帧 | 位时序配置错误 |
| CRC错误 | 主动/被动错误帧 | EMC干扰、线束问题 |
| 格式错误 | 主动/被动错误帧 | 协议实现bug |
| ACK错误 | 主动/被动错误帧 | 无其他节点、总线故障 |
3.3 CAN控制器错误状态
CAN控制器内部有两个错误计数器:TEC(发送错误计数)和REC(接收错误计数)。根据计数值,控制器有三种状态:
TEC或REC > 127
┌──────────┐ ───────────────→ ┌──────────────┐
│ 主动错误 │ │ 被动错误 │
│ (Error │ ←─────────────── │ (Error │
│ Active) │ TEC和REC < 128 │ Passive) │
└────┬─────┘ └──────┬───────┘
│ │
│ 正常通信 │ TEC > 255
│ 成功则计数-1 │
↓ ↓
┌──────────┐ ┌──────────────┐
│ 正常 │ │ 总线关闭 │
│ │ │ (Bus Off) │
└──────────┘ └──────────────┘
│
│ 检测到128×11
│ 个隐性位
↓
恢复到主动错误
Bus Off是最严重的状态,控制器完全停止收发,需要软件干预恢复。
四、故障诊断实现
4.1 诊断架构设计
我设计的诊断系统分三层:
┌─────────────────────────────────────────────────────────────┐
│ 应用层诊断 │
│ - 节点在线监测 │
│ - 报文超时检测 │
│ - 报文内容校验 │
└─────────────────────────────┬───────────────────────────────┘
│
┌─────────────────────────────┴───────────────────────────────┐
│ 协议层诊断 │
│ - 错误帧统计 │
│ - TEC/REC监测 │
│ - Bus Off检测与恢复 │
└─────────────────────────────┬───────────────────────────────┘
│
┌─────────────────────────────┴───────────────────────────────┐
│ 物理层诊断 │
│ - 总线电压监测 │
│ - 终端电阻检测 │
│ - 短路/开路检测 │
└─────────────────────────────────────────────────────────────┘
4.2 数据结构定义
c
#ifndef CAN_DIAG_H
#define CAN_DIAG_H
#include <stdint.h>
#include <stdbool.h>
// 最大监控节点数
#define MAX_MONITORED_NODES 32
// 最大监控报文数
#define MAX_MONITORED_MESSAGES 64
// CAN通道定义
typedef enum {
CAN_CH_1 = 0,
CAN_CH_2,
CAN_CH_3,
CAN_CH_MAX
} CAN_Channel_t;
// 总线状态
typedef enum {
BUS_STATE_OK = 0, // 正常
BUS_STATE_ERROR_ACTIVE, // 主动错误
BUS_STATE_ERROR_PASSIVE, // 被动错误
BUS_STATE_BUS_OFF, // 总线关闭
BUS_STATE_DISCONNECTED // 物理断开
} BusState_t;
// 故障类型
typedef enum {
FAULT_NONE = 0x0000,
// 物理层故障
FAULT_CANH_SHORT_GND = 0x0001, // CAN_H对地短路
FAULT_CANL_SHORT_GND = 0x0002, // CAN_L对地短路
FAULT_CANH_SHORT_VCC = 0x0004, // CAN_H对电源短路
FAULT_CANL_SHORT_VCC = 0x0008, // CAN_L对电源短路
FAULT_CANH_CANL_SHORT = 0x0010, // CAN_H/L短接
FAULT_BUS_OPEN = 0x0020, // 总线开路
FAULT_TERM_RESIST_ERR = 0x0040, // 终端电阻异常
// 协议层故障
FAULT_BIT_ERROR = 0x0100, // 位错误
FAULT_STUFF_ERROR = 0x0200, // 填充错误
FAULT_CRC_ERROR = 0x0400, // CRC错误
FAULT_FORM_ERROR = 0x0800, // 格式错误
FAULT_ACK_ERROR = 0x1000, // ACK错误
// 应用层故障
FAULT_NODE_OFFLINE = 0x2000, // 节点离线
FAULT_MSG_TIMEOUT = 0x4000, // 报文超时
FAULT_MSG_CONTENT_ERR = 0x8000 // 报文内容错误
} FaultType_t;
// 错误统计
typedef struct {
uint32_t tx_count; // 发送计数
uint32_t rx_count; // 接收计数
uint32_t tx_error_count; // 发送错误计数
uint32_t rx_error_count; // 接收错误计数
uint32_t bit_error_count; // 位错误计数
uint32_t stuff_error_count; // 填充错误计数
uint32_t crc_error_count; // CRC错误计数
uint32_t form_error_count; // 格式错误计数
uint32_t ack_error_count; // ACK错误计数
uint32_t bus_off_count; // Bus Off次数
uint32_t overrun_count; // 溢出计数
} ErrorStatistics_t;
// 节点监控配置
typedef struct {
uint32_t node_id; // 节点ID(用某个报文ID代表)
uint32_t timeout_ms; // 超时时间
bool enabled; // 是否启用监控
} NodeMonitorConfig_t;
// 节点状态
typedef struct {
uint32_t node_id;
bool online;
uint32_t last_seen_time;
uint32_t offline_count;
uint32_t message_count;
} NodeStatus_t;
// 报文监控配置
typedef struct {
uint32_t msg_id; // 报文ID
uint32_t expected_cycle_ms; // 期望周期
uint32_t timeout_ms; // 超时时间
uint8_t expected_dlc; // 期望数据长度
bool check_dlc; // 是否检查DLC
bool enabled;
} MessageMonitorConfig_t;
// 报文状态
typedef struct {
uint32_t msg_id;
uint32_t last_rx_time;
uint32_t rx_count;
uint32_t timeout_count;
uint32_t dlc_error_count;
uint32_t actual_cycle_ms; // 实际周期
bool timeout_flag;
} MessageStatus_t;
// 通道诊断信息
typedef struct {
CAN_Channel_t channel;
BusState_t bus_state;
uint32_t fault_code; // 故障码组合
uint8_t tec; // 发送错误计数器
uint8_t rec; // 接收错误计数器
ErrorStatistics_t stats;
// 物理层测量值
float canh_voltage; // CAN_H电压
float canl_voltage; // CAN_L电压
float diff_voltage; // 差分电压
float bus_load_percent; // 总线负载率
// 节点状态
NodeStatus_t nodes[MAX_MONITORED_NODES];
uint8_t node_count;
uint8_t online_node_count;
// 报文状态
MessageStatus_t messages[MAX_MONITORED_MESSAGES];
uint8_t message_count;
} ChannelDiagInfo_t;
// 全局诊断句柄
typedef struct {
ChannelDiagInfo_t channels[CAN_CH_MAX];
uint32_t last_update_time;
bool initialized;
} CANDiagnostics_t;
#endif // CAN_DIAG_H
4.3 协议层诊断实现
c
#include "can_diag.h"
#include "can_driver.h"
#include <string.h>
static CANDiagnostics_t g_diag;
// 初始化诊断模块
void CAN_Diag_Init(void)
{
memset(&g_diag, 0, sizeof(g_diag));
for(int i = 0; i < CAN_CH_MAX; i++) {
g_diag.channels[i].channel = (CAN_Channel_t)i;
g_diag.channels[i].bus_state = BUS_STATE_OK;
}
g_diag.initialized = true;
}
// 更新错误计数器(从CAN控制器读取)
static void update_error_counters(CAN_Channel_t ch)
{
ChannelDiagInfo_t *diag = &g_diag.channels[ch];
// 从硬件读取TEC/REC
// 这里以STM32为例,其他平台需要适配
CAN_TypeDef *can = (ch == CAN_CH_1) ? CAN1 : CAN2;
diag->tec = (can->ESR >> 16) & 0xFF;
diag->rec = (can->ESR >> 24) & 0xFF;
// 解析错误类型
uint32_t esr = can->ESR;
if(esr & CAN_ESR_BOFF) {
diag->bus_state = BUS_STATE_BUS_OFF;
diag->fault_code |= FAULT_BIT_ERROR; // Bus Off通常由累积位错误导致
}
else if(esr & CAN_ESR_EPVF) {
diag->bus_state = BUS_STATE_ERROR_PASSIVE;
}
else if(esr & CAN_ESR_EWGF) {
diag->bus_state = BUS_STATE_ERROR_ACTIVE;
}
else {
diag->bus_state = BUS_STATE_OK;
}
// 统计最后一次错误类型
uint8_t lec = (esr >> 4) & 0x07;
switch(lec) {
case 1: // Stuff Error
diag->stats.stuff_error_count++;
diag->fault_code |= FAULT_STUFF_ERROR;
break;
case 2: // Form Error
diag->stats.form_error_count++;
diag->fault_code |= FAULT_FORM_ERROR;
break;
case 3: // ACK Error
diag->stats.ack_error_count++;
diag->fault_code |= FAULT_ACK_ERROR;
break;
case 4: // Bit Recessive Error
case 5: // Bit Dominant Error
diag->stats.bit_error_count++;
diag->fault_code |= FAULT_BIT_ERROR;
break;
case 6: // CRC Error
diag->stats.crc_error_count++;
diag->fault_code |= FAULT_CRC_ERROR;
break;
default:
break;
}
}
// Bus Off恢复处理
static void handle_bus_off_recovery(CAN_Channel_t ch)
{
ChannelDiagInfo_t *diag = &g_diag.channels[ch];
if(diag->bus_state != BUS_STATE_BUS_OFF) {
return;
}
diag->stats.bus_off_count++;
// 方案1:自动恢复(等待128×11个隐性位)
// 大多数CAN控制器默认就是这样
// 方案2:软件控制恢复(更安全)
// 先等待一段时间再尝试恢复,避免反复进入Bus Off
static uint32_t last_recovery_time[CAN_CH_MAX] = {0};
uint32_t now = get_system_time_ms();
if(now - last_recovery_time[ch] > 1000) { // 至少间隔1秒
last_recovery_time[ch] = now;
// 重新初始化CAN控制器
CAN_TypeDef *can = (ch == CAN_CH_1) ? CAN1 : CAN2;
// 请求进入初始化模式
can->MCR |= CAN_MCR_INRQ;
while(!(can->MSR & CAN_MSR_INAK));
// 退出初始化模式
can->MCR &= ~CAN_MCR_INRQ;
while(can->MSR & CAN_MSR_INAK);
// 清除错误标志
can->ESR = 0;
}
}
// CAN错误中断回调
void CAN_Error_IRQHandler(CAN_Channel_t ch)
{
ChannelDiagInfo_t *diag = &g_diag.channels[ch];
CAN_TypeDef *can = (ch == CAN_CH_1) ? CAN1 : CAN2;
uint32_t esr = can->ESR;
uint32_t msr = can->MSR;
// 接收溢出
if(msr & CAN_MSR_RXM) {
diag->stats.overrun_count++;
}
// 更新错误计数器
update_error_counters(ch);
// 清除中断标志
can->MSR = CAN_MSR_ERRI;
}
4.4 节点在线监测
c
// 添加节点监控
int CAN_Diag_AddNodeMonitor(CAN_Channel_t ch, uint32_t node_id, uint32_t timeout_ms)
{
ChannelDiagInfo_t *diag = &g_diag.channels[ch];
if(diag->node_count >= MAX_MONITORED_NODES) {
return -1;
}
NodeStatus_t *node = &diag->nodes[diag->node_count];
node->node_id = node_id;
node->online = false;
node->last_seen_time = 0;
node->offline_count = 0;
node->message_count = 0;
diag->node_count++;
return 0;
}
// 报文接收回调(在CAN接收中断中调用)
void CAN_Diag_OnMessageReceived(CAN_Channel_t ch, uint32_t msg_id,
uint8_t *data, uint8_t dlc)
{
ChannelDiagInfo_t *diag = &g_diag.channels[ch];
uint32_t now = get_system_time_ms();
diag->stats.rx_count++;
// 更新节点状态
for(int i = 0; i < diag->node_count; i++) {
if(diag->nodes[i].node_id == msg_id) {
NodeStatus_t *node = &diag->nodes[i];
if(!node->online) {
node->online = true;
diag->online_node_count++;
}
node->last_seen_time = now;
node->message_count++;
break;
}
}
// 更新报文状态
for(int i = 0; i < diag->message_count; i++) {
if(diag->messages[i].msg_id == msg_id) {
MessageStatus_t *msg = &diag->messages[i];
// 计算实际周期
if(msg->last_rx_time > 0) {
msg->actual_cycle_ms = now - msg->last_rx_time;
}
msg->last_rx_time = now;
msg->rx_count++;
msg->timeout_flag = false;
break;
}
}
}
// 周期性检查(建议10-100ms调用一次)
void CAN_Diag_PeriodicCheck(void)
{
uint32_t now = get_system_time_ms();
for(int ch = 0; ch < CAN_CH_MAX; ch++) {
ChannelDiagInfo_t *diag = &g_diag.channels[ch];
// 更新错误计数器
update_error_counters((CAN_Channel_t)ch);
// Bus Off恢复
handle_bus_off_recovery((CAN_Channel_t)ch);
// 检查节点超时
for(int i = 0; i < diag->node_count; i++) {
NodeStatus_t *node = &diag->nodes[i];
// 假设超时时间为500ms
if(node->online && (now - node->last_seen_time > 500)) {
node->online = false;
node->offline_count++;
diag->online_node_count--;
diag->fault_code |= FAULT_NODE_OFFLINE;
}
}
// 检查报文超时
for(int i = 0; i < diag->message_count; i++) {
MessageStatus_t *msg = &diag->messages[i];
if(!msg->timeout_flag && msg->last_rx_time > 0) {
// 超时时间通常设为周期的3倍
uint32_t timeout = 500; // 默认500ms
if(now - msg->last_rx_time > timeout) {
msg->timeout_flag = true;
msg->timeout_count++;
diag->fault_code |= FAULT_MSG_TIMEOUT;
}
}
}
}
g_diag.last_update_time = now;
}
4.5 物理层诊断
物理层诊断需要额外的硬件支持,一般用ADC测量总线电压:
c
// 物理层诊断(需要硬件支持)
typedef struct {
// ADC通道配置
uint8_t adc_ch_canh;
uint8_t adc_ch_canl;
// 分压系数(假设用电阻分压后送ADC)
float voltage_divider;
// 阈值
float canh_normal_min;
float canh_normal_max;
float canl_normal_min;
float canl_normal_max;
} PhysicalDiagConfig_t;
static PhysicalDiagConfig_t phy_config = {
.adc_ch_canh = 0,
.adc_ch_canl = 1,
.voltage_divider = 2.0f, // 10k/10k分压
.canh_normal_min = 2.0f,
.canh_normal_max = 4.0f,
.canl_normal_min = 1.0f,
.canl_normal_max = 3.0f
};
// 读取ADC并转换为电压
static float read_bus_voltage(uint8_t adc_channel)
{
uint16_t adc_value = read_adc(adc_channel);
float voltage = (adc_value / 4096.0f) * 3.3f * phy_config.voltage_divider;
return voltage;
}
// 物理层故障诊断
uint32_t CAN_Diag_CheckPhysicalLayer(CAN_Channel_t ch)
{
uint32_t fault = FAULT_NONE;
ChannelDiagInfo_t *diag = &g_diag.channels[ch];
// 读取电压
diag->canh_voltage = read_bus_voltage(phy_config.adc_ch_canh);
diag->canl_voltage = read_bus_voltage(phy_config.adc_ch_canl);
diag->diff_voltage = diag->canh_voltage - diag->canl_voltage;
// 静态检测(总线空闲时)
// 正常空闲:CAN_H ≈ 2.5V, CAN_L ≈ 2.5V, 差分 ≈ 0V
// CAN_H对地短路
if(diag->canh_voltage < 0.5f) {
fault |= FAULT_CANH_SHORT_GND;
}
// CAN_L对地短路
if(diag->canl_voltage < 0.5f) {
fault |= FAULT_CANL_SHORT_GND;
}
// CAN_H对电源短路
if(diag->canh_voltage > 4.5f) {
fault |= FAULT_CANH_SHORT_VCC;
}
// CAN_L对电源短路
if(diag->canl_voltage > 4.5f) {
fault |= FAULT_CANL_SHORT_VCC;
}
// CAN_H/L短接(差分一直为0且电压在中间)
if(fabsf(diag->diff_voltage) < 0.1f &&
diag->canh_voltage > 2.0f && diag->canh_voltage < 3.0f) {
// 需要结合通信情况判断
// 如果同时有ACK错误,很可能是短接
if(diag->stats.ack_error_count > 0) {
fault |= FAULT_CANH_CANL_SHORT;
}
}
// 总线开路检测
// 开路时,发送会持续失败(ACK错误)
// 且如果用带失效保护的收发器,会读到特定电平
if(diag->stats.ack_error_count > 10 && diag->stats.rx_count == 0) {
fault |= FAULT_BUS_OPEN;
}
diag->fault_code |= fault;
return fault;
}
// 终端电阻检测
// 原理:在总线空闲时测量CAN_H和CAN_L之间的电阻
// 需要专门的测量电路,这里给出软件判断逻辑
bool CAN_Diag_CheckTermination(CAN_Channel_t ch)
{
ChannelDiagInfo_t *diag = &g_diag.channels[ch];
/*
* 终端电阻异常的表现:
* 1. 波形振铃严重 -> 误码率高
* 2. 没有终端电阻 -> 总线电压异常
*
* 软件只能间接判断,精确检测需要示波器或专用电路
*/
// 统计单位时间内的错误率
static uint32_t last_check_time = 0;
static uint32_t last_error_count = 0;
uint32_t now = get_system_time_ms();
uint32_t total_errors = diag->stats.bit_error_count +
diag->stats.stuff_error_count +
diag->stats.crc_error_count;
if(now - last_check_time > 1000) {
uint32_t error_rate = total_errors - last_error_count;
// 如果每秒错误超过阈值,可能是终端电阻问题
if(error_rate > 50 && diag->bus_state != BUS_STATE_BUS_OFF) {
diag->fault_code |= FAULT_TERM_RESIST_ERR;
return false;
}
last_check_time = now;
last_error_count = total_errors;
}
return true;
}
4.6 总线负载率计算
c
// 总线负载率计算
typedef struct {
uint32_t bits_transmitted;
uint32_t measurement_start_time;
float load_percent;
} BusLoadMeasurement_t;
static BusLoadMeasurement_t bus_load[CAN_CH_MAX];
// 报文发送/接收时调用,累计传输的位数
void CAN_Diag_AddTransmittedBits(CAN_Channel_t ch, uint8_t dlc, bool is_extended)
{
/*
* CAN帧位数计算:
* 标准帧:1(SOF) + 11(ID) + 1(RTR) + 1(IDE) + 1(r0) + 4(DLC) + 8*dlc(Data)
* + 15(CRC) + 1(CRC Del) + 1(ACK) + 1(ACK Del) + 7(EOF) + 3(IFS)
* = 47 + 8*dlc (不含填充位)
*
* 考虑位填充(最坏情况约20%额外开销)
*/
uint32_t bits;
if(is_extended) {
bits = 67 + 8 * dlc; // 扩展帧
} else {
bits = 47 + 8 * dlc; // 标准帧
}
// 加上约20%的填充位
bits = bits * 120 / 100;
bus_load[ch].bits_transmitted += bits;
}
// 周期性计算负载率(建议1秒调用一次)
float CAN_Diag_CalculateBusLoad(CAN_Channel_t ch, uint32_t baudrate)
{
uint32_t now = get_system_time_ms();
uint32_t elapsed_ms = now - bus_load[ch].measurement_start_time;
if(elapsed_ms < 100) {
return bus_load[ch].load_percent; // 返回上次结果
}
// 计算负载率
// 负载率 = 实际传输位数 / (波特率 * 时间)
float max_bits = (float)baudrate * elapsed_ms / 1000.0f;
float load = (float)bus_load[ch].bits_transmitted / max_bits * 100.0f;
if(load > 100.0f) load = 100.0f;
bus_load[ch].load_percent = load;
bus_load[ch].bits_transmitted = 0;
bus_load[ch].measurement_start_time = now;
g_diag.channels[ch].bus_load_percent = load;
return load;
}
五、UDS诊断协议实现
汽车上的故障诊断通常遵循UDS(统一诊断服务)协议,ISO 14229标准。这里实现几个常用服务:
5.1 UDS基础结构
c
#ifndef UDS_H
#define UDS_H
#include <stdint.h>
#include <stdbool.h>
// UDS服务ID
typedef enum {
UDS_SID_DIAGNOSTIC_SESSION_CONTROL = 0x10,
UDS_SID_ECU_RESET = 0x11,
UDS_SID_CLEAR_DTC = 0x14,
UDS_SID_READ_DTC_INFO = 0x19,
UDS_SID_READ_DATA_BY_ID = 0x22,
UDS_SID_READ_MEMORY_BY_ADDRESS = 0x23,
UDS_SID_SECURITY_ACCESS = 0x27,
UDS_SID_COMMUNICATION_CONTROL = 0x28,
UDS_SID_WRITE_DATA_BY_ID = 0x2E,
UDS_SID_IO_CONTROL = 0x2F,
UDS_SID_ROUTINE_CONTROL = 0x31,
UDS_SID_REQUEST_DOWNLOAD = 0x34,
UDS_SID_REQUEST_UPLOAD = 0x35,
UDS_SID_TRANSFER_DATA = 0x36,
UDS_SID_TRANSFER_EXIT = 0x37,
UDS_SID_TESTER_PRESENT = 0x3E,
UDS_SID_CONTROL_DTC_SETTING = 0x85
} UDS_ServiceID_t;
// 否定响应码
typedef enum {
UDS_NRC_GENERAL_REJECT = 0x10,
UDS_NRC_SERVICE_NOT_SUPPORTED = 0x11,
UDS_NRC_SUBFUNCTION_NOT_SUPPORTED = 0x12,
UDS_NRC_INCORRECT_MSG_LENGTH = 0x13,
UDS_NRC_BUSY_REPEAT_REQUEST = 0x21,
UDS_NRC_CONDITIONS_NOT_CORRECT = 0x22,
UDS_NRC_REQUEST_SEQUENCE_ERROR = 0x24,
UDS_NRC_REQUEST_OUT_OF_RANGE = 0x31,
UDS_NRC_SECURITY_ACCESS_DENIED = 0x33,
UDS_NRC_INVALID_KEY = 0x35,
UDS_NRC_EXCEEDED_NUMBER_OF_ATTEMPTS = 0x36,
UDS_NRC_UPLOAD_DOWNLOAD_NOT_ACCEPTED = 0x70,
UDS_NRC_TRANSFER_SUSPENDED = 0x71,
UDS_NRC_GENERAL_PROGRAMMING_FAILURE = 0x72,
UDS_NRC_SERVICE_NOT_SUPPORTED_IN_SESSION = 0x7F
} UDS_NRC_t;
// DTC状态掩码
typedef struct {
uint8_t test_failed : 1;
uint8_t test_failed_this_cycle : 1;
uint8_t pending_dtc : 1;
uint8_t confirmed_dtc : 1;
uint8_t test_not_completed_since_clear : 1;
uint8_t test_failed_since_clear : 1;
uint8_t test_not_completed_this_cycle : 1;
uint8_t warning_indicator_requested : 1;
} DTC_StatusMask_t;
// DTC记录
typedef struct {
uint32_t dtc_code; // 3字节DTC码
DTC_StatusMask_t status;
uint32_t occurrence_count;
uint32_t first_occurrence_time;
uint32_t last_occurrence_time;
uint8_t snapshot_data[16]; // 冻结帧数据
} DTC_Record_t;
#define MAX_DTC_RECORDS 50
#endif // UDS_H
5.2 DTC管理实现
c
#include "uds.h"
#include "can_diag.h"
#include <string.h>
// DTC存储
static DTC_Record_t dtc_storage[MAX_DTC_RECORDS];
static uint8_t dtc_count = 0;
// CAN故障对应的DTC码定义(按照SAE J2012标准)
// 格式:P(动力) U(网络) B(车身) C(底盘) + 4位数字
static const struct {
FaultType_t fault;
uint32_t dtc_code;
const char *description;
} fault_to_dtc[] = {
{ FAULT_CANH_SHORT_GND, 0xU0100, "CAN_H short to ground" },
{ FAULT_CANL_SHORT_GND, 0xU0101, "CAN_L short to ground" },
{ FAULT_CANH_SHORT_VCC, 0xU0102, "CAN_H short to battery" },
{ FAULT_CANL_SHORT_VCC, 0xU0103, "CAN_L short to battery" },
{ FAULT_CANH_CANL_SHORT, 0xU0104, "CAN_H/L shorted together" },
{ FAULT_BUS_OPEN, 0xU0105, "CAN bus open circuit" },
{ FAULT_BIT_ERROR, 0xU0110, "CAN bit error" },
{ FAULT_CRC_ERROR, 0xU0111, "CAN CRC error" },
{ FAULT_ACK_ERROR, 0xU0112, "CAN no ACK" },
{ FAULT_NODE_OFFLINE, 0xU0120, "CAN node offline" },
{ FAULT_MSG_TIMEOUT, 0xU0121, "CAN message timeout" },
};
// 查找或创建DTC记录
static DTC_Record_t* find_or_create_dtc(uint32_t dtc_code)
{
// 查找已存在的
for(int i = 0; i < dtc_count; i++) {
if(dtc_storage[i].dtc_code == dtc_code) {
return &dtc_storage[i];
}
}
// 创建新记录
if(dtc_count < MAX_DTC_RECORDS) {
DTC_Record_t *dtc = &dtc_storage[dtc_count++];
memset(dtc, 0, sizeof(DTC_Record_t));
dtc->dtc_code = dtc_code;
dtc->first_occurrence_time = get_system_time_ms();
return dtc;
}
return NULL;
}
// 根据故障码设置DTC
void DTC_SetFault(FaultType_t fault)
{
for(size_t i = 0; i < sizeof(fault_to_dtc)/sizeof(fault_to_dtc[0]); i++) {
if(fault_to_dtc[i].fault == fault) {
DTC_Record_t *dtc = find_or_create_dtc(fault_to_dtc[i].dtc_code);
if(dtc) {
dtc->status.test_failed = 1;
dtc->status.test_failed_this_cycle = 1;
dtc->status.pending_dtc = 1;
dtc->status.test_failed_since_clear = 1;
dtc->occurrence_count++;
dtc->last_occurrence_time = get_system_time_ms();
// 如果故障持续,确认DTC
if(dtc->occurrence_count >= 3) {
dtc->status.confirmed_dtc = 1;
}
// 捕获冻结帧
capture_snapshot(dtc);
}
break;
}
}
}
// 捕获冻结帧数据
static void capture_snapshot(DTC_Record_t *dtc)
{
// 记录故障发生时的关键参数
// 这里简化处理,实际项目会记录更多信息
dtc->snapshot_data[0] = (get_system_time_ms() >> 24) & 0xFF;
dtc->snapshot_data[1] = (get_system_time_ms() >> 16) & 0xFF;
dtc->snapshot_data[2] = (get_system_time_ms() >> 8) & 0xFF;
dtc->snapshot_data[3] = get_system_time_ms() & 0xFF;
// 可以添加:车速、发动机转速、电压等
}
// 清除所有DTC
void DTC_ClearAll(void)
{
memset(dtc_storage, 0, sizeof(dtc_storage));
dtc_count = 0;
// 同时清除诊断模块的故障码
for(int ch = 0; ch < CAN_CH_MAX; ch++) {
g_diag.channels[ch].fault_code = FAULT_NONE;
memset(&g_diag.channels[ch].stats, 0, sizeof(ErrorStatistics_t));
}
}
// 获取DTC数量
uint8_t DTC_GetCount(uint8_t status_mask)
{
uint8_t count = 0;
for(int i = 0; i < dtc_count; i++) {
uint8_t status = *(uint8_t*)&dtc_storage[i].status;
if(status & status_mask) {
count++;
}
}
return count;
}
// 读取DTC信息
int DTC_ReadByStatus(uint8_t status_mask, uint8_t *buffer, uint16_t max_len)
{
int offset = 0;
for(int i = 0; i < dtc_count && offset < max_len - 4; i++) {
uint8_t status = *(uint8_t*)&dtc_storage[i].status;
if(status & status_mask) {
// DTC高字节
buffer[offset++] = (dtc_storage[i].dtc_code >> 16) & 0xFF;
// DTC中字节
buffer[offset++] = (dtc_storage[i].dtc_code >> 8) & 0xFF;
// DTC低字节
buffer[offset++] = dtc_storage[i].dtc_code & 0xFF;
// 状态字节
buffer[offset++] = status;
}
}
return offset;
}
5.3 UDS服务处理
c
// UDS请求处理
typedef struct {
uint8_t *request;
uint16_t req_len;
uint8_t *response;
uint16_t *resp_len;
uint16_t resp_max_len;
} UDS_Context_t;
// 发送否定响应
static void send_negative_response(UDS_Context_t *ctx, uint8_t sid, UDS_NRC_t nrc)
{
ctx->response[0] = 0x7F;
ctx->response[1] = sid;
ctx->response[2] = nrc;
*ctx->resp_len = 3;
}
// 0x19 - 读取DTC信息
static void handle_read_dtc_info(UDS_Context_t *ctx)
{
if(ctx->req_len < 2) {
send_negative_response(ctx, UDS_SID_READ_DTC_INFO, UDS_NRC_INCORRECT_MSG_LENGTH);
return;
}
uint8_t sub_func = ctx->request[1];
switch(sub_func) {
case 0x01: // 按状态掩码读取DTC数量
{
if(ctx->req_len < 3) {
send_negative_response(ctx, UDS_SID_READ_DTC_INFO, UDS_NRC_INCORRECT_MSG_LENGTH);
return;
}
uint8_t status_mask = ctx->request[2];
uint8_t dtc_count = DTC_GetCount(status_mask);
ctx->response[0] = UDS_SID_READ_DTC_INFO + 0x40; // 肯定响应
ctx->response[1] = sub_func;
ctx->response[2] = status_mask;
ctx->response[3] = 0x00; // DTC格式
ctx->response[4] = (dtc_count >> 8) & 0xFF;
ctx->response[5] = dtc_count & 0xFF;
*ctx->resp_len = 6;
break;
}
case 0x02: // 按状态掩码读取DTC
{
if(ctx->req_len < 3) {
send_negative_response(ctx, UDS_SID_READ_DTC_INFO, UDS_NRC_INCORRECT_MSG_LENGTH);
return;
}
uint8_t status_mask = ctx->request[2];
ctx->response[0] = UDS_SID_READ_DTC_INFO + 0x40;
ctx->response[1] = sub_func;
ctx->response[2] = status_mask;
int dtc_len = DTC_ReadByStatus(status_mask, &ctx->response[3], ctx->resp_max_len - 3);
*ctx->resp_len = 3 + dtc_len;
break;
}
case 0x04: // 读取冻结帧数据
{
// 简化实现,读取第一个匹配DTC的冻结帧
if(ctx->req_len < 6) {
send_negative_response(ctx, UDS_SID_READ_DTC_INFO, UDS_NRC_INCORRECT_MSG_LENGTH);
return;
}
uint32_t target_dtc = ((uint32_t)ctx->request[2] << 16) |
((uint32_t)ctx->request[3] << 8) |
ctx->request[4];
ctx->response[0] = UDS_SID_READ_DTC_INFO + 0x40;
ctx->response[1] = sub_func;
// 查找DTC
bool found = false;
for(int i = 0; i < dtc_count; i++) {
if(dtc_storage[i].dtc_code == target_dtc) {
ctx->response[2] = (target_dtc >> 16) & 0xFF;
ctx->response[3] = (target_dtc >> 8) & 0xFF;
ctx->response[4] = target_dtc & 0xFF;
ctx->response[5] = *(uint8_t*)&dtc_storage[i].status;
// 复制冻结帧数据
memcpy(&ctx->response[6], dtc_storage[i].snapshot_data, 16);
*ctx->resp_len = 22;
found = true;
break;
}
}
if(!found) {
send_negative_response(ctx, UDS_SID_READ_DTC_INFO, UDS_NRC_REQUEST_OUT_OF_RANGE);
}
break;
}
default:
send_negative_response(ctx, UDS_SID_READ_DTC_INFO, UDS_NRC_SUBFUNCTION_NOT_SUPPORTED);
break;
}
}
// 0x14 - 清除DTC
static void handle_clear_dtc(UDS_Context_t *ctx)
{
if(ctx->req_len < 4) {
send_negative_response(ctx, UDS_SID_CLEAR_DTC, UDS_NRC_INCORRECT_MSG_LENGTH);
return;
}
uint32_t group = ((uint32_t)ctx->request[1] << 16) |
((uint32_t)ctx->request[2] << 8) |
ctx->request[3];
// 0xFFFFFF表示清除所有DTC
if(group == 0xFFFFFF) {
DTC_ClearAll();
ctx->response[0] = UDS_SID_CLEAR_DTC + 0x40;
*ctx->resp_len = 1;
} else {
// 清除特定组(简化处理)
DTC_ClearAll();
ctx->response[0] = UDS_SID_CLEAR_DTC + 0x40;
*ctx->resp_len = 1;
}
}
// UDS主处理函数
void UDS_ProcessRequest(uint8_t *request, uint16_t req_len,
uint8_t *response, uint16_t *resp_len, uint16_t resp_max_len)
{
UDS_Context_t ctx = {
.request = request,
.req_len = req_len,
.response = response,
.resp_len = resp_len,
.resp_max_len = resp_max_len
};
if(req_len < 1) {
return;
}
uint8_t sid = request[0];
switch(sid) {
case UDS_SID_READ_DTC_INFO:
handle_read_dtc_info(&ctx);
break;
case UDS_SID_CLEAR_DTC:
handle_clear_dtc(&ctx);
break;
case UDS_SID_TESTER_PRESENT:
response[0] = UDS_SID_TESTER_PRESENT + 0x40;
response[1] = request[1]; // Sub-function
*resp_len = 2;
break;
default:
send_negative_response(&ctx, sid, UDS_NRC_SERVICE_NOT_SUPPORTED);
break;
}
}
六、ISO-TP传输层实现
UDS报文可能超过单帧8字节限制,需要ISO-TP(ISO 15765-2)做分段传输:
c
// ISO-TP帧类型
typedef enum {
ISOTP_FRAME_SF = 0, // 单帧
ISOTP_FRAME_FF = 1, // 首帧
ISOTP_FRAME_CF = 2, // 连续帧
ISOTP_FRAME_FC = 3 // 流控帧
} ISOTP_FrameType_t;
// 流控状态
typedef enum {
FC_CTS = 0, // 继续发送
FC_WAIT = 1, // 等待
FC_OVERFLOW = 2 // 溢出
} FC_Status_t;
// ISO-TP会话
typedef struct {
// 接收状态
uint8_t rx_buffer[4096];
uint16_t rx_len;
uint16_t rx_expected_len;
uint8_t rx_sequence_num;
bool rx_in_progress;
// 发送状态
uint8_t tx_buffer[4096];
uint16_t tx_len;
uint16_t tx_offset;
uint8_t tx_sequence_num;
uint8_t tx_block_size;
uint8_t tx_st_min;
bool tx_in_progress;
// 配置
uint32_t rx_id;
uint32_t tx_id;
uint8_t block_size;
uint8_t st_min;
// 回调
void (*rx_complete_callback)(uint8_t *data, uint16_t len);
} ISOTP_Session_t;
static ISOTP_Session_t isotp_session;
// 初始化ISO-TP
void ISOTP_Init(uint32_t rx_id, uint32_t tx_id,
void (*callback)(uint8_t*, uint16_t))
{
memset(&isotp_session, 0, sizeof(isotp_session));
isotp_session.rx_id = rx_id;
isotp_session.tx_id = tx_id;
isotp_session.block_size = 0; // 0表示不限制
isotp_session.st_min = 10; // 10ms
isotp_session.rx_complete_callback = callback;
}
// 处理接收到的CAN帧
void ISOTP_RxIndication(uint32_t can_id, uint8_t *data, uint8_t dlc)
{
if(can_id != isotp_session.rx_id) {
return;
}
ISOTP_FrameType_t frame_type = (data[0] >> 4) & 0x0F;
switch(frame_type) {
case ISOTP_FRAME_SF: // 单帧
{
uint8_t sf_len = data[0] & 0x0F;
if(sf_len > 0 && sf_len <= 7) {
memcpy(isotp_session.rx_buffer, &data[1], sf_len);
isotp_session.rx_len = sf_len;
if(isotp_session.rx_complete_callback) {
isotp_session.rx_complete_callback(
isotp_session.rx_buffer,
isotp_session.rx_len);
}
}
break;
}
case ISOTP_FRAME_FF: // 首帧
{
uint16_t ff_len = ((data[0] & 0x0F) << 8) | data[1];
if(ff_len > sizeof(isotp_session.rx_buffer)) {
// 缓冲区溢出,发送FC(Overflow)
send_flow_control(FC_OVERFLOW, 0, 0);
return;
}
isotp_session.rx_expected_len = ff_len;
isotp_session.rx_len = 6;
memcpy(isotp_session.rx_buffer, &data[2], 6);
isotp_session.rx_sequence_num = 1;
isotp_session.rx_in_progress = true;
// 发送流控帧
send_flow_control(FC_CTS, isotp_session.block_size, isotp_session.st_min);
break;
}
case ISOTP_FRAME_CF: // 连续帧
{
if(!isotp_session.rx_in_progress) {
return;
}
uint8_t sn = data[0] & 0x0F;
// 检查序列号
if(sn != isotp_session.rx_sequence_num) {
// 序列号错误,中止接收
isotp_session.rx_in_progress = false;
return;
}
// 复制数据
uint16_t remaining = isotp_session.rx_expected_len - isotp_session.rx_len;
uint16_t copy_len = (remaining < 7) ? remaining : 7;
memcpy(&isotp_session.rx_buffer[isotp_session.rx_len], &data[1], copy_len);
isotp_session.rx_len += copy_len;
isotp_session.rx_sequence_num = (isotp_session.rx_sequence_num + 1) & 0x0F;
// 检查是否完成
if(isotp_session.rx_len >= isotp_session.rx_expected_len) {
isotp_session.rx_in_progress = false;
if(isotp_session.rx_complete_callback) {
isotp_session.rx_complete_callback(
isotp_session.rx_buffer,
isotp_session.rx_len);
}
}
break;
}
case ISOTP_FRAME_FC: // 流控帧(发送时用)
{
FC_Status_t fc_status = data[0] & 0x0F;
if(fc_status == FC_CTS) {
isotp_session.tx_block_size = data[1];
isotp_session.tx_st_min = data[2];
// 继续发送
ISOTP_ContinueTx();
} else if(fc_status == FC_WAIT) {
// 等待下一个FC
} else {
// 溢出或其他错误,中止发送
isotp_session.tx_in_progress = false;
}
break;
}
}
}
// 发送流控帧
static void send_flow_control(FC_Status_t status, uint8_t bs, uint8_t st_min)
{
uint8_t fc_data[8] = {0};
fc_data[0] = (ISOTP_FRAME_FC << 4) | status;
fc_data[1] = bs;
fc_data[2] = st_min;
CAN_Transmit(isotp_session.tx_id, fc_data, 8);
}
// 发送数据
int ISOTP_Transmit(uint8_t *data, uint16_t len)
{
if(len == 0 || len > sizeof(isotp_session.tx_buffer)) {
return -1;
}
if(isotp_session.tx_in_progress) {
return -2; // 正在发送
}
memcpy(isotp_session.tx_buffer, data, len);
isotp_session.tx_len = len;
isotp_session.tx_offset = 0;
uint8_t can_data[8] = {0};
if(len <= 7) {
// 单帧
can_data[0] = (ISOTP_FRAME_SF << 4) | len;
memcpy(&can_data[1], data, len);
CAN_Transmit(isotp_session.tx_id, can_data, 8);
} else {
// 首帧
can_data[0] = (ISOTP_FRAME_FF << 4) | ((len >> 8) & 0x0F);
can_data[1] = len & 0xFF;
memcpy(&can_data[2], data, 6);
CAN_Transmit(isotp_session.tx_id, can_data, 8);
isotp_session.tx_offset = 6;
isotp_session.tx_sequence_num = 1;
isotp_session.tx_in_progress = true;
// 等待FC
}
return 0;
}
// 继续发送连续帧
void ISOTP_ContinueTx(void)
{
if(!isotp_session.tx_in_progress) {
return;
}
uint8_t frames_sent = 0;
while(isotp_session.tx_offset < isotp_session.tx_len) {
uint8_t can_data[8] = {0};
can_data[0] = (ISOTP_FRAME_CF << 4) | isotp_session.tx_sequence_num;
uint16_t remaining = isotp_session.tx_len - isotp_session.tx_offset;
uint16_t copy_len = (remaining < 7) ? remaining : 7;
memcpy(&can_data[1], &isotp_session.tx_buffer[isotp_session.tx_offset], copy_len);
CAN_Transmit(isotp_session.tx_id, can_data, 8);
isotp_session.tx_offset += copy_len;
isotp_session.tx_sequence_num = (isotp_session.tx_sequence_num + 1) & 0x0F;
frames_sent++;
// 检查Block Size
if(isotp_session.tx_block_size > 0 && frames_sent >= isotp_session.tx_block_size) {
// 等待下一个FC
return;
}
// STmin延时
if(isotp_session.tx_st_min > 0) {
delay_ms(isotp_session.tx_st_min);
}
}
// 发送完成
isotp_session.tx_in_progress = false;
}
七、调试工具与技巧
7.1 CAN报文打印
调试必备的报文打印函数:
c
#include <stdio.h>
void CAN_PrintMessage(uint32_t id, bool is_extended, uint8_t *data, uint8_t dlc)
{
printf("[CAN] %s ID=0x%08X DLC=%d Data=",
is_extended ? "EXT" : "STD", id, dlc);
for(int i = 0; i < dlc; i++) {
printf("%02X ", data[i]);
}
printf("\n");
}
void CAN_PrintDiagInfo(CAN_Channel_t ch)
{
ChannelDiagInfo_t *diag = &g_diag.channels[ch];
printf("\n========== CAN%d Diagnostics ==========\n", ch + 1);
printf("Bus State: %s\n",
diag->bus_state == BUS_STATE_OK ? "OK" :
diag->bus_state == BUS_STATE_ERROR_ACTIVE ? "Error Active" :
diag->bus_state == BUS_STATE_ERROR_PASSIVE ? "Error Passive" :
diag->bus_state == BUS_STATE_BUS_OFF ? "BUS OFF" : "Disconnected");
printf("TEC: %d, REC: %d\n", diag->tec, diag->rec);
printf("Fault Code: 0x%04X\n", diag->fault_code);
printf("Statistics:\n");
printf(" TX: %lu, RX: %lu\n", diag->stats.tx_count, diag->stats.rx_count);
printf(" Bit Errors: %lu\n", diag->stats.bit_error_count);
printf(" Stuff Errors: %lu\n", diag->stats.stuff_error_count);
printf(" CRC Errors: %lu\n", diag->stats.crc_error_count);
printf(" ACK Errors: %lu\n", diag->stats.ack_error_count);
printf(" Bus Off Count: %lu\n", diag->stats.bus_off_count);
printf("Physical Layer:\n");
printf(" CAN_H: %.2fV, CAN_L: %.2fV, Diff: %.2fV\n",
diag->canh_voltage, diag->canl_voltage, diag->diff_voltage);
printf(" Bus Load: %.1f%%\n", diag->bus_load_percent);
printf("Nodes Online: %d/%d\n", diag->online_node_count, diag->node_count);
printf("==========================================\n");
}
7.2 故障注入测试
测试诊断功能需要能模拟各种故障:
c
// 故障注入模块(仅用于测试)
typedef struct {
bool enabled;
FaultType_t inject_fault;
uint32_t inject_duration_ms;
uint32_t inject_start_time;
} FaultInjector_t;
static FaultInjector_t fault_injector;
void FaultInject_Start(FaultType_t fault, uint32_t duration_ms)
{
fault_injector.enabled = true;
fault_injector.inject_fault = fault;
fault_injector.inject_duration_ms = duration_ms;
fault_injector.inject_start_time = get_system_time_ms();
printf("[FaultInject] Started: 0x%04X for %lu ms\n", fault, duration_ms);
}
void FaultInject_Stop(void)
{
fault_injector.enabled = false;
printf("[FaultInject] Stopped\n");
}
// 在诊断周期中检查
void FaultInject_Process(void)
{
if(!fault_injector.enabled) {
return;
}
uint32_t elapsed = get_system_time_ms() - fault_injector.inject_start_time;
if(elapsed >= fault_injector.inject_duration_ms) {
FaultInject_Stop();
return;
}
// 注入故障
g_diag.channels[CAN_CH_1].fault_code |= fault_injector.inject_fault;
DTC_SetFault(fault_injector.inject_fault);
}
7.3 常用调试命令
我一般会实现一个简单的命令行接口方便调试:
c
void CMD_Process(const char *cmd)
{
if(strncmp(cmd, "can diag", 8) == 0) {
CAN_PrintDiagInfo(CAN_CH_1);
}
else if(strncmp(cmd, "can clear", 9) == 0) {
DTC_ClearAll();
printf("DTC cleared\n");
}
else if(strncmp(cmd, "can inject ", 11) == 0) {
uint32_t fault;
sscanf(cmd + 11, "%x", &fault);
FaultInject_Start((FaultType_t)fault, 5000);
}
else if(strncmp(cmd, "dtc list", 8) == 0) {
printf("DTC Count: %d\n", DTC_GetCount(0xFF));
uint8_t buffer[256];
int len = DTC_ReadByStatus(0xFF, buffer, sizeof(buffer));
for(int i = 0; i < len; i += 4) {
printf(" DTC: %02X%02X%02X Status: %02X\n",
buffer[i], buffer[i+1], buffer[i+2], buffer[i+3]);
}
}
else {
printf("Unknown command: %s\n", cmd);
}
}
八、实际应用案例
8.1 项目背景
去年做的那个商用车网关项目,需求是这样的:
- 3路高速CAN(500kbps),分别连接动力域、底盘域、车身域
- 各域之间物理隔离
- 实时监测总线状态,故障时自动隔离故障域
- 支持UDS诊断
8.2 硬件架构
┌───────────────────┐
│ 网关ECU │
│ │
动力域CAN ────ISO1050──┤ CAN1 CAN3 ├──ISO1050──── 车身域CAN
│ │ │ │
EMS、TCU ┌┤ CAN2 ├┐ BCM、IPC
││ STM32F4 ││
底盘域CAN ───ISO1050─┘│ │└─隔离电源
│ │ │
ABS、ESP └───────────────────┘
8.3 关键代码
网关转发逻辑:
c
// 路由表
typedef struct {
uint32_t src_id;
CAN_Channel_t src_ch;
uint32_t dst_id;
CAN_Channel_t dst_ch;
bool enabled;
} RouteEntry_t;
#define MAX_ROUTES 100
static RouteEntry_t route_table[MAX_ROUTES];
static uint8_t route_count = 0;
// 添加路由
int Gateway_AddRoute(uint32_t src_id, CAN_Channel_t src_ch,
uint32_t dst_id, CAN_Channel_t dst_ch)
{
if(route_count >= MAX_ROUTES) {
return -1;
}
route_table[route_count].src_id = src_id;
route_table[route_count].src_ch = src_ch;
route_table[route_count].dst_id = dst_id;
route_table[route_count].dst_ch = dst_ch;
route_table[route_count].enabled = true;
route_count++;
return 0;
}
// 报文转发
void Gateway_Forward(CAN_Channel_t src_ch, uint32_t msg_id,
uint8_t *data, uint8_t dlc)
{
// 检查源通道状态
if(g_diag.channels[src_ch].bus_state == BUS_STATE_BUS_OFF) {
return; // 故障域不转发
}
for(int i = 0; i < route_count; i++) {
if(route_table[i].src_ch == src_ch &&
route_table[i].src_id == msg_id &&
route_table[i].enabled) {
// 检查目标通道状态
CAN_Channel_t dst_ch = route_table[i].dst_ch;
if(g_diag.channels[dst_ch].bus_state == BUS_STATE_BUS_OFF) {
continue; // 跳过故障通道
}
// 转发
CAN_Transmit_Ch(dst_ch, route_table[i].dst_id, data, dlc);
}
}
}
// 域隔离控制
void Gateway_IsolateDomain(CAN_Channel_t ch)
{
printf("[Gateway] Isolating CAN%d domain\n", ch + 1);
// 禁用所有相关路由
for(int i = 0; i < route_count; i++) {
if(route_table[i].src_ch == ch || route_table[i].dst_ch == ch) {
route_table[i].enabled = false;
}
}
// 记录DTC
DTC_SetFault(FAULT_NODE_OFFLINE);
}
// 域恢复
void Gateway_RecoverDomain(CAN_Channel_t ch)
{
printf("[Gateway] Recovering CAN%d domain\n", ch + 1);
for(int i = 0; i < route_count; i++) {
if(route_table[i].src_ch == ch || route_table[i].dst_ch == ch) {
route_table[i].enabled = true;
}
}
}
九、总结
CAN总线隔离和故障诊断这块内容比较多,总结一下要点:
- 隔离设计:新能源车必须做电气隔离,推荐用集成隔离CAN芯片
- 故障分层:物理层、协议层、应用层分别处理
- 状态机:Bus Off恢复要有策略,不能无限重试
- DTC管理:按照UDS标准实现,方便售后诊断
- 调试工具:故障注入、报文打印很重要
代码我都在实际项目跑过,细节可能需要根据具体芯片适配。有问题可以评论区讨论。
参考资料:
- ISO 11898-1 CAN数据链路层和物理层
- ISO 15765-2 诊断通信传输层(ISO-TP)
- ISO 14229-1 统一诊断服务(UDS)
- SAE J2012 诊断故障码定义
- TI应用笔记:SLLA270 "Controller Area Network Physical Layer Requirements"
这篇文章断断续续写了一周,内容比较多。如果觉得有帮助,点赞收藏支持一下。后续计划再写一篇CAN FD和车载以太网的内容。