汽车CAN总线隔离设计与故障诊断:从原理到代码实战

最近在做一个商用车的网关项目,涉及多路CAN总线的隔离和故障诊断。踩了不少坑,把这块知识系统整理一下。文章会从CAN总线基础讲起,重点放在隔离设计和故障诊断的实现上。

前言

CAN总线在汽车电子里的地位不用多说,从发动机ECU到车身控制器,几乎所有节点都挂在CAN上。但CAN总线有个特点:一个节点出问题,可能把整条总线拖垮

我之前就遇到过一个案例:某个传感器模块内部短路,把CAN_H拉低,结果整车通信全挂了。后来加了隔离设计才解决。

本文会详细讲解:

  1. CAN总线物理层原理
  2. 为什么需要隔离以及怎么做隔离
  3. 常见故障类型和诊断方法
  4. 完整的故障诊断代码实现

一、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节点挂在一起,风险很大:

  1. 故障扩散:一个节点短路会影响整条总线
  2. 地电位差:不同ECU的地电位可能不同,产生地环流
  3. EMC问题:高压系统(如电驱)干扰低压系统
  4. 功能安全:关键系统需要和非关键系统隔离

特别是新能源车,动力电池、电机控制器的地和车身地是隔离的,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需要隔离电源,常用方案:

  1. DC-DC隔离模块:如B0505S,简单但EMC差
  2. 反激变换器:成本低,需要设计变压器
  3. 推挽变换器:效率高,用于大功率场合

我一般用现成的隔离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总线隔离和故障诊断这块内容比较多,总结一下要点:

  1. 隔离设计:新能源车必须做电气隔离,推荐用集成隔离CAN芯片
  2. 故障分层:物理层、协议层、应用层分别处理
  3. 状态机:Bus Off恢复要有策略,不能无限重试
  4. DTC管理:按照UDS标准实现,方便售后诊断
  5. 调试工具:故障注入、报文打印很重要

代码我都在实际项目跑过,细节可能需要根据具体芯片适配。有问题可以评论区讨论。


参考资料

  1. ISO 11898-1 CAN数据链路层和物理层
  2. ISO 15765-2 诊断通信传输层(ISO-TP)
  3. ISO 14229-1 统一诊断服务(UDS)
  4. SAE J2012 诊断故障码定义
  5. TI应用笔记:SLLA270 "Controller Area Network Physical Layer Requirements"

这篇文章断断续续写了一周,内容比较多。如果觉得有帮助,点赞收藏支持一下。后续计划再写一篇CAN FD和车载以太网的内容。

相关推荐
我爱烤冷面8 小时前
kotlin项目实现Java doc的方案:使用Dokka
java·开发语言·kotlin·dokka
智驱力人工智能8 小时前
从项目管理视角 拆解景区无人机人群密度分析系统的构建逻辑 无人机人员密度检测 无人机人群密度检测系统价格 低空人群密度统计AI优化方案
人工智能·深度学习·算法·安全·无人机·边缘计算
天途小编8 小时前
无人机测绘技术本科专业解读
无人机
历程里程碑8 小时前
C++ 4:内存管理
java·c语言·开发语言·数据结构·c++·笔记·算法
Likeadust8 小时前
视频推流平台EasyDSS无人机推流直播技术重塑新闻报道新模式
音视频·无人机
云卓SKYDROID8 小时前
无人机CAN接口技术解析
无人机·飞控·吊舱·高科技·云卓科技
LXS_3578 小时前
Day17 C++提高 之 类模板案例
开发语言·c++·笔记·算法·学习方法
leo__5208 小时前
基于MATLAB实现的鲁棒性音频数字水印系统
开发语言·matlab·音视频
2301_789015628 小时前
C++:多态(面向对象的主要手段之一)
c语言·开发语言·c++·多态