2. STM32 CAN外设:bxCAN控制器介绍、工作模式与位时序配置

各位同学,咱们今天聊聊STM32上的CAN外设。说实话,CAN总线在车载领域太常见了,几乎每台车上都有它。我最早接触CAN是在一个OBD诊断项目里,那时候对bxCAN的理解还很浅,结果调试时踩了不少坑。今天我把这些经验揉碎了讲给你听。

2.1 bxCAN控制器是什么?

bxCAN,全称是Basic Extended CAN。说白了,就是ST在STM32里集成的CAN控制器模块。它支持CAN 2.0A和2.0B协议,既能发标准帧(11位ID),也能发扩展帧(29位ID)。

我个人习惯把bxCAN看作一个“智能邮差”。你只需要把数据包丢给它,它自己会处理仲裁、错误检测、重发这些脏活累活。你想想看,要是每发一帧都得自己算CRC,那得多累人。

bxCAN有几个关键特性:

  • 3个发送邮箱:可以缓存3个待发送的报文
  • 2个接收FIFO:每个FIFO深度为3,总共能存6个报文
  • 28个滤波器组:可以按ID或掩码过滤报文,减轻CPU负担
  • 自动重发:发送失败后自动重试,直到成功或超时

重点提醒:bxCAN的滤波器组是它的灵魂。我在项目中遇到过CPU被CAN中断淹没的情况,后来发现是滤波器没配好,所有报文都进来了。配好滤波器后,CPU负载直接降了60%。

2.2 三种工作模式:Normal、Loopback、Silent

bxCAN支持三种工作模式。嗯,这里要注意,不同模式对应不同的应用场景,选错了调试起来会很痛苦。

2.2.1 Normal模式(正常模式)

这是最常用的模式。CAN控制器正常收发报文,需要外接CAN收发器(比如TJA1050)。所有总线上的节点都能看到你的报文,你也能收到别人的报文。

我建议在最终产品中都用这个模式。调试时可以先试试其他模式,但量产必须切回Normal。

2.2.2 Loopback模式(回环模式)

这个模式很有意思。发送的报文不会发到总线上,而是直接内部回环到接收端。说白了就是自己发自己收,不需要外接收发器。

我曾经在开发初期用这个模式测试CAN驱动。那时候收发器还没焊上,全靠Loopback模式验证了发送和接收逻辑。省了不少事。

实用技巧:Loopback模式非常适合做单元测试。你可以写一个测试用例,发一帧数据,然后检查是否收到相同的数据。这样能快速验证驱动层的正确性。

2.2.3 Silent模式(静默模式)

这个模式也叫监听模式。CAN控制器只接收,不发送。即使你调用发送函数,数据也不会出现在总线上。

你想想看,什么时候需要只监听不说话?对了,就是总线分析工具或者诊断设备。我在做CAN总线故障排查时,经常用Silent模式挂在一个节点上,看总线上的流量是否正常。

模式 发送 接收 典型应用
Normal 发到总线 从总线收 正常通信
Loopback 内部回环 收自己发的 驱动测试、自检
Silent 不发送 从总线收 总线监听、诊断

2.3 位时序配置:让CAN跑在正确的速率上

位时序配置是CAN开发中最容易出问题的地方。我见过太多人因为时序配错,导致通信时好时坏。说白了,位时序就是决定CAN总线波特率的参数。

CAN的一个位由4个段组成:

  • 同步段(Sync_Seg):固定为1个时间量子(Tq)
  • 传播段(Prop_Seg):补偿物理延迟
  • 相位缓冲段1(Phase_Seg1):采样点之前
  • 相位缓冲段2(Phase_Seg2):采样点之后

采样点通常设置在Phase_Seg1和Phase_Seg2之间。我个人习惯把采样点设在87.5%左右,这样对总线延迟的容忍度更高。

2.3.1 如何计算波特率?

公式很简单:

波特率 = 时钟频率 / (BRP + 1) / (Sync_Seg + Prop_Seg + Phase_Seg1 + Phase_Seg2)

其中BRP是波特率预分频器。举个例子,如果STM32的CAN时钟是36MHz,你想跑500kbps:

36MHz / (BRP + 1) / (1 + 2 + 3 + 2) = 500kbps
36MHz / (BRP + 1) / 8 = 500kbps
BRP + 1 = 9
BRP = 8

注意:我曾经在一个项目里把BRP算错了,结果CAN总线上的节点全部失步。排查了整整两天才发现是预分频器多除了一个2。所以算完后一定要用示波器或者CAN分析仪验证实际波特率。

2.3.2 采样点配置

采样点位置很重要。太靠前容易采到信号跳变沿,太靠后可能采到下一个位。我建议:

  • 高速CAN(1Mbps):采样点设在75%~80%
  • 中速CAN(500kbps):采样点设在80%~87.5%
  • 低速CAN(125kbps以下):采样点设在85%~90%

为什么这样?你想想看,高速时信号上升沿更陡,采样点可以稍微靠前。低速时总线延迟影响更大,采样点靠后更安全。

2.3.3 同步跳转宽度(SJW)

SJW决定了CAN控制器能容忍多大的时钟偏差。一般设置为1~2个Tq就够了。如果总线上的节点时钟精度较差,可以适当增大SJW。

我的经验:在车载项目中,ECU的晶振精度通常为±0.5%或更好。SJW设为1个Tq基本够用。但如果你的系统里混用了不同厂家的CAN控制器,建议把SJW设为2个Tq,留点余量。

2.4 实战配置示例

下面是一个典型的bxCAN初始化代码,配置为500kbps,Normal模式:

void CAN_Config(void)
{
    CAN_InitTypeDef CAN_InitStruct;
    
    // 使能CAN时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
    
    // 配置GPIO
    // ...(省略GPIO配置)
    
    // CAN初始化
    CAN_InitStruct.CAN_TTCM = DISABLE;      // 时间触发模式
    CAN_InitStruct.CAN_ABOM = ENABLE;       // 自动离线管理
    CAN_InitStruct.CAN_AWUM = ENABLE;       // 自动唤醒
    CAN_InitStruct.CAN_NART = DISABLE;      // 自动重发
    CAN_InitStruct.CAN_RFLM = DISABLE;      // 接收FIFO锁定
    CAN_InitStruct.CAN_TXFP = DISABLE;      // 发送优先级由ID决定
    CAN_InitStruct.CAN_Mode = CAN_Mode_Normal;  // Normal模式
    
    // 位时序:36MHz时钟,500kbps
    // Sync_Seg=1, Prop_Seg=2, Phase_Seg1=3, Phase_Seg2=2
    // SJW=1, BRP=8
    CAN_InitStruct.CAN_SJW = CAN_SJW_1tq;
    CAN_InitStruct.CAN_BS1 = CAN_BS1_5tq;   // Prop_Seg + Phase_Seg1 = 2+3=5
    CAN_InitStruct.CAN_BS2 = CAN_BS2_2tq;
    CAN_InitStruct.CAN_Prescaler = 8;       // BRP
    
    CAN_Init(CAN1, &CAN_InitStruct);
}

这段代码我用了很多年,基本没出过问题。唯一要注意的是,CAN_BS1的值是Prop_Seg和Phase_Seg1之和。很多新手在这里搞混,以为BS1只代表Phase_Seg1。

好了,关于bxCAN的介绍就到这里。下一章我们会深入讲解CAN的滤波器配置和中断处理,那才是真正体现水平的地方。