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的滤波器配置和中断处理,那才是真正体现水平的地方。