CAN总线学习笔记

一、基础概念

  1. 物理连接​:

    • TX/RX无需交叉连接(高速CAN默认)
    • 采用差分信号传输(抗干扰性强)
  2. 通信方式​:

    • 广播式通信:发送方主动广播数据(数据帧)
    • 请求式通信:接收方主动请求数据(遥控帧)
    • 接收方通过拉低总线电平(显性电平0)确认接收(类似停止位)
  3. 关键术语​:

    • DLC​:数据长度码(1~8字节)
    • IDE​:标识符扩展位(0=标准帧11位ID,1=扩展帧29位ID)
    • RTR​:远程传输请求位(0=数据帧,1=遥控帧)
    • r0/r1​:保留位(为协议升级预留)

二、帧类型与结构

帧类型用途
数据帧主动广播数据(发送设备→所有设备)
遥控帧请求数据(接收设备→发送设备,无数据段)
错误帧设备检测错误时通知总线(破坏当前通信)
过载帧接收设备未准备好时延缓发送方
帧间隔分隔数据帧/遥控帧与其他帧
数据帧结构​(标准格式):
字段功能
SOF (帧起始)显性电平0,标志传输开始
ID (标识符)11位,区分功能并决定优先级(值越小优先级越高)
RTR0=数据帧
IDE0=标准帧
DLC4位,指定数据段长度(0~8字节)
Data实际数据(0~8字节)
CRC15位循环冗余校验 + 1位界定符(隐性1
ACK发送方发隐性1,接收方拉低为显性0确认
EOF (帧结束)7位隐性1,标志传输结束

扩展帧差异​:

  • 29位ID(11位基础ID + 18位扩展ID)
  • 新增SRR位(固定隐性1,保证标准帧优先)

三、核心机制

  1. 非破坏性仲裁​:

    • 线与特性​:总线显性0覆盖隐性10 & x = 01 & 1 = 1
    • 回读机制​:设备发送后回读总线电平,确认是否发送成功
    • 仲裁规则​:
      • 逐位比较ID,显性0优先级高
      • ID相同时:数据帧 > 遥控帧,标准帧 > 扩展帧
  2. 位填充规则​:

    • 发送方:连续5个相同电平后插入一个反向电平(填充位)
    • 接收方:移除填充位恢复原始数据
    • 作用​:提供定时同步、区分错误帧/过载帧、保持总线活跃
  3. 再同步机制​:

    • 位时间划分:同步段(SS) + 传播段(PTS) + 相位缓冲段1(PBS1) + 相位缓冲段2(PBS2)
    • 接收方通过调整PBS1/PBS2(±SJW值)对齐采样点

四、错误处理

  1. 错误类型​:

    错误类型触发条件
    位错误发送电平与总线电平不一致
    填充错误连续检测到6个相同电平(违反填充规则)
    CRC错误接收方计算的CRC与帧内CRC不匹配
    格式错误固定格式字段(如界定符、EOF)电平错误
    ACK错误发送方未检测到ACK显性0(无设备接收)
  2. 错误状态转换​:

    状态行为
    主动错误可正常通信,检测错误时发送主动错误帧(破坏总线)
    被动错误可正常通信,检测错误时发送被动错误帧(不破坏总线)
    总线关闭禁止通信(需检测128次连续11位隐性1恢复)
  3. 错误计数器​:

    • TEC​(发送错误计数器):错误发送时增加,成功发送时减少
    • REC​(接收错误计数器):接收错误时增加,成功接收时减少
    • 状态转换阈值:
      • TEC/REC < 128 → 主动错误
      • TEC/REC ≥ 128 → 被动错误
      • TEC > 255 → 总线关闭

五、物理层特性

  • 无时钟线​:设备约定波特率,通过位时序同步
  • 采样点​:位于数据位中心附近(通过再同步调整)
  • 总线空闲​:连续11位隐性1(设备仅此时可发起发送)

六、STM32 CAN外设简介

  1. 特性​:

    • 支持CAN 2.0A/B
    • 最高波特率1 Mbps
    • 3个发送邮箱(可配置优先级)
    • 2个接收FIFO(3级深度)
    • 14个过滤器组(互联型28个)
  2. 关键功能​:

    • 自动重传控制
    • 时间触发通信模式
    • 自动离线恢复
    • 接收FIFO溢出处理可配置

关键总结表

机制核心规则
优先级仲裁ID值小者优先 → 数据帧 > 遥控帧 → 标准帧 > 扩展帧
位填充连续5相同电平 → 插入1反向电平
总线空闲判定连续11位隐性1
错误状态切换TEC/REC ≥ 128 → 被动错误;TEC > 255 → 总线关闭
ACK机制发送方发1,接收方拉低为0确认

CAN总线仲裁机制示例

下面是一个用C语言实现的简化版CAN总线仲裁模拟程序,配合波形图解释CAN总线的核心机制。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>

// CAN节点结构体
typedef struct {
    int id;         // 节点ID
    bool active;    // 节点是否活跃
    int state;      // 0:发送显性位, 1:发送隐性位
    int bit_index;  // 当前发送的位索引
    char frame[14]; // 要发送的帧(SOF+ID+RTR+IDE+控制段)
} CAN_Node;

// 总线状态枚举
typedef enum {
    RECESSIVE,  // 隐性 (逻辑1)
    DOMINANT    // 显性 (逻辑0)
} BusState;

// 模拟CAN总线
void simulate_can_bus() {
    // 创建三个节点
    CAN_Node nodes[3];
    
    // 初始化节点 (ID越小优先级越高)
    nodes[0] = (CAN_Node){0x123, true, -1, 0, {0,0,0,1,0,0,1,0,0,0,1,1,0,0}}; // ID: 0x123
    nodes[1] = (CAN_Node){0x124, true, -1, 0, {0,0,0,1,0,0,1,0,0,1,0,0,0,0}}; // ID: 0x124
    nodes[2] = (CAN_Node){0x122, true, -1, 0, {0,0,0,1,0,0,1,0,0,0,1,0,0,0}}; // ID: 0x122 (最高优先级)
    
    printf("CAN总线仲裁模拟 - 节点ID: 0x%X, 0x%X, 0x%X\n\n", 
           nodes[0].id, nodes[1].id, nodes[2].id);
    
    BusState bus = RECESSIVE;
    bool arbitration_complete = false;
    int winner_id = -1;
    
    // 逐位模拟总线传输
    for (int bit_pos = 0; bit_pos < 14; bit_pos++) {
        // 重置总线为隐性
        bus = RECESSIVE;
        
        // 每个节点发送当前位
        for (int i = 0; i < 3; i++) {
            if (nodes[i].active && bit_pos < strlen(nodes[i].frame)) {
                // 发送显性位会覆盖隐性位
                if (nodes[i].frame[bit_pos] == '0') {
                    bus = DOMINANT;
                }
            }
        }
        
        // 节点检测总线状态
        for (int i = 0; i < 3; i++) {
            if (nodes[i].active && bit_pos < strlen(nodes[i].frame)) {
                // 回读机制:比较发送值与总线实际值
                if (nodes[i].frame[bit_pos] == '1' && bus == DOMINANT) {
                    // 仲裁失败:发送隐性位但读到显性位
                    nodes[i].active = false;
                    printf("位 %2d: 节点 0x%X 仲裁失败\n", bit_pos, nodes[i].id);
                }
            }
        }
        
        // 检查仲裁是否完成
        int active_count = 0;
        for (int i = 0; i < 3; i++) {
            if (nodes[i].active) {
                active_count++;
                winner_id = nodes[i].id;
            }
        }
        
        // 打印当前总线状态
        printf("位 %2d: 总线状态: %s | 发送节点: ", 
               bit_pos, bus == DOMINANT ? "显性(0)" : "隐性(1)");
        
        for (int i = 0; i < 3; i++) {
            if (nodes[i].active && bit_pos < strlen(nodes[i].frame)) {
                printf("0x%X:%c ", nodes[i].id, nodes[i].frame[bit_pos]);
            }
        }
        printf("\n");
        
        // 仲裁完成判断
        if (!arbitration_complete && active_count == 1) {
            printf("\n>>> 仲裁完成! 胜出节点: 0x%X <<<\n\n", winner_id);
            arbitration_complete = true;
        }
        
        // 位之间延迟
        usleep(300000);
    }
    
    printf("\n传输结束\n");
}

int main() {
    simulate_can_bus();
    return 0;
}

程序输出解析

运行上述程序会显示类似以下输出:

CAN总线仲裁模拟 - 节点ID: 0x123, 0x124, 0x122

位  0: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0 
位  1: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0 
位  2: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0 
位  3: 总线状态: 显性(0) | 发送节点: 0x123:1 0x124:1 0x122:1 
位  4: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0 
位  5: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0 
位  6: 总线状态: 显性(0) | 发送节点: 0x123:1 0x124:1 0x122:1 
位  7: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0 
位  8: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:0 0x122:0 
位  9: 总线状态: 显性(0) | 发送节点: 0x123:0 0x124:1 0x122:0 
位 10: 总线状态: 显性(0) | 发送节点: 0x123:1 0x124:0 0x122:1 
位 11: 总线状态: 显性(0) | 发送节点: 0x122:0 
位 12: 总线状态: 显性(0) | 发送节点: 0x122:0 
位 13: 总线状态: 显性(0) | 发送节点: 0x122:0 

传输结束

CAN总线波形图与仲裁过程

位位置: 0   1   2   3   4   5   6   7   8   9   10  11  12  13
        -----------------------------------------------------
节点0x122: 0   0   0   1   0   0   1   0   0   0   1   0   0   0
节点0x123: 0   0   0   1   0   0   1   0   0   0   1   1   0   0
节点0x124: 0   0   0   1   0   0   1   0   0   1   0   0   0   0
        -----------------------------------------------------
总线状态: 0   0   0   1   0   0   1   0   0   0   1   0   0   0
        ↑                       ↑   ↑       ↑
        SOF                     |   |      仲裁结束
                              差异点 仲裁失败点

关键机制解释:

  1. 线与特性​:

    • 当任何节点发送显性位(0)时,总线表现为显性(0)
    • 只有当所有节点都发送隐性位(1)时,总线才表现为隐性(1)
  2. 非破坏性仲裁​:

    • 所有节点从SOF(显性位)开始同时发送
    • 逐位比较ID(高位先发送)
    • 在位9:节点0x123发送0(显性),节点0x124发送1(隐性),总线为0
    • 节点0x124检测到自己发送1但总线为0 → 仲裁失败,停止发送
    • 在位10:节点0x122发送1(隐性),节点0x123发送1(隐性),总线为1
    • 在位11:节点0x122发送0(显性),节点0x123发送1(隐性) → 节点0x123仲裁失败
  3. 优先级规则​:

    • ID值越小优先级越高(0x122 < 0x123 < 0x124)
    • 前8位所有节点ID相同(00100100)
    • 第9位:0x122和0x123发送0(显性),0x124发送1(隐性) → 0x124失败
    • 第10位:所有节点发送1(隐性) → 无冲突
    • 第11位:0x122发送0(显性),0x123发送1(隐性) → 0x123失败
  4. 帧结构​:

    • 位0:SOF(帧起始),固定为显性(0)
    • 位1-11:11位标识符(ID)
    • 位12:RTR(远程传输请求),0表示数据帧
    • 位13:IDE(标识符扩展),0表示标准帧

波形图详解:

位位置: 0   1   2   3   4   5   6   7   8   9   10  11  12  13
        -----------------------------------------------------
总线波形: __  __  __  --  __  __  --  __  __  __  --  __  __  __
          0   0   0   1   0   0   1   0   0   0   1   0   0   0

图例: "__" = 显性(0), "--" = 隐性(1)
  1. SOF(位0)​​:显性起始位,标志帧开始
  2. ID字段(位1-11)​​:
    • 前8位(位1-8):00100100 (所有节点相同)
    • 位9:0(显性) → 淘汰ID高位为1的节点
    • 位10:1(隐性) → 无冲突
    • 位11:0(显性) → 淘汰发送1的节点
  3. 控制段(位12-13)​​:RTR=0(数据帧),IDE=0(标准帧)

通过这个示例,可以看到CAN总线通过非破坏性仲裁实现多节点无冲突通信,优先级高的节点能够无损完成传输,而优先级低的节点会自动退出发送等待下次机会。

这里推荐江科大的CAN总线教学视频,我觉得讲得特别好

CAN总线入门教程-全面细致 面包板教学 多机通信_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值