STM32 内部集成的 CAN 控制器被称为 bxCAN (Basic Extended CAN)。它的设计目标是以最小的 CPU 负荷来处理大量的报文收发。我们要想写出高效的代码,必须先理解它内部的"物流体系"。
1. 核心架构:三大核心组件
bxCAN 外设主要由以下三个部分组成:
-
发送邮箱 (Transmit Mailboxes): 用于暂存等待发送的报文。
-
接收 FIFO (Receive FIFOs): 用于暂存从总线上接收到的、且通过了过滤器筛选的报文。
-
验收筛选器 (Acceptance Filters): 这是 CAN 的"安检员",决定哪些报文该进 FIFO,哪些该直接丢弃。
1.1 发送邮箱:谁先发,硬件说了算
STM32 提供了 3 个发送邮箱。这意味着 CPU 可以一次性"一口气"丢给 CAN 控制器 3 条报文。
-
优先级判定: 如果 3 个邮箱都塞满了,硬件如何决定发送顺序?
-
ID 优先级: 默认情况下,CAN 硬件会根据报文的标识符(ID)来决定。ID 越小(显性位多),优先级越高,越先被推向总线。
-
时间顺序: 你也可以通过设置
CAN_MCR寄存器的TXFP位,强制让邮箱按照"先进先出(FIFO)"的顺序发送。
-
-
工程陷阱: 如果你的系统中存在大量高频率、低优先级的报文,可能会导致高优先级的邮箱一直被挂起。理解邮箱调度是解决"总线拥堵"的第一步。
1.2 接收 FIFO:防止丢包的缓冲带
STM32 有 2 个接收 FIFO(FIFO0 和 FIFO1) ,每个 FIFO 都有 3 级深度。
这意味着,即便 CPU 忙于处理其他中断,暂时没空理会 CAN,硬件也能帮你缓存 6 条(2x3)完整的报文。
-
溢出风险: 如果 3 个位置都占满了,第 4 条报文又来了,会发生什么?
-
默认:新报文会被丢弃。
-
可配置:新报文覆盖旧报文(
RFLM位配置)。 -
建议: 永远通过中断方式及时把 FIFO 里的数据读走,不要在主循环里轮询,这是保证不丢包的金科玉律。
-
2. 时间份额(Time Quantum)与波特率的艺术
这是本章最硬核的部分。很多新手配置 CAN 失败,90% 都是因为波特率算错了。CAN 协议不是简单的"位传输",它把一个"位(Bit)"拆成了多个细小的时间份额(Tq)。
2.1 位时间(Bit Time)的四个段
一个完整的位时间由以下四部分组成:
-
同步段 (Sync_Seg): 固定为 1 个 Tq。用于信号同步。
-
传播段 (Prop_Seg): 补偿电缆中的物理延迟。
-
相位缓冲段 1 (Phase_Seg1): 用于补偿信号边缘的相位提前。
-
相位缓冲段 2 (Phase_Seg2): 用于补偿信号边缘的相位滞后。
在 STM32 的 HAL 库中,传播段 和相位段 1 被合并成了 TimeSeg1(TS1),相位段 2 被称为 TimeSeg2(TS2)。
2.2 采样点(Sample Point):决定稳定性的生死线
采样点是控制器读取总线电平的时刻,它位于 TS1 结束、TS2 开始的地方。
-
采样点计算公式:
SamplePoint = (1 + TS1) / (1 + TS1 + TS2) -
工业标准: * 对于 500k、1M 的高速 CAN,建议采样点在 75% ~ 80%。
-
对于低速或超长距离,采样点可以适当靠后。
-
如果采样点设置不一致: 不同的设备之间即便波特率一样,也可能出现周期性的 CRC 错误或丢包。
-
2.3 实战:手把手教你计算波特率
假设你使用的是 STM32F103 ,APB1 时钟(f_pclk1)为 36MHz ,你需要配置 500kbps 的波特率。
-
确定分频系数 (Prescaler): 我们先试分频系数为 4。
Tq = Prescaler / f_pclk1 = 4 / 36,000,000 = 111.11 ns
-
确定位总时长:
一个位的总时长 = 1 / 500,000 = 2000 ns。
总 Tq 数 = 2000 / 111.11 = 18 个 Tq。
-
分配 TS1 和 TS2:
根据"总 Tq = 1 (Sync_Seg) + TS1 + TS2 = 18",得出 TS1 + TS2 = 17。
为了让采样点接近 80%:
设 TS1 = 13,TS2 = 4。
采样点 = (1+13) / 18 = 77.7% 完美!
3. STM32 CAN 的工作模式:调试中的"救命稻草"
bxCAN 有四种工作模式,在开发专栏时,一定要教会读者善用它们。
-
正常模式 (Normal): 接入总线,收发自如。
-
回环模式 (Loopback): 控制器发出的数据不经过物理总线,直接在芯片内部传给接收端。
- 用途: 你的代码死活收不到数?先切到回环模式。如果能收到,说明软件逻辑和波特率配置是对的,问题出在收发器、线缆或电阻上。
-
静默模式 (Silent): 只收不发。
- 用途: 也就是"监听模式"。在不干扰总线的前提下抓包。
-
静默回环模式: 用于自我测试,不干扰总线。
4. 软件实现:基于 HAL 库的初始化模板
在这一节,我们要展示如何将上述理论转化为代码。
CAN_HandleTypeDef hcan;
void MX_CAN_Init(void) {
hcan.Instance = CAN1;
hcan.Init.Prescaler = 4; // 分频系数
hcan.Init.Mode = CAN_MODE_NORMAL; // 正常模式
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_13TQ; // TS1 = 13
hcan.Init.TimeSeg2 = CAN_BS2_4TQ; // TS2 = 4
hcan.Init.TimeTriggeredMode = DISABLE;
hcan.Init.AutoBusOff = ENABLE; // 自动离线恢复(重要!)
hcan.Init.AutoWakeUp = DISABLE;
hcan.Init.AutoRetransmission = ENABLE; // 自动重发
hcan.Init.ReceiveFifoLocked = DISABLE;
hcan.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan) != HAL_OK) {
Error_Handler();
}
}
重点参数解析:AutoBusOff = ENABLE
在工业现场,如果总线受到强干扰,CAN 核心可能会因为错误计数器溢出而进入"Bus-Off(离线)"状态。
-
如果设为 DISABLE,你需要手动写代码去重启 CAN 模块。
-
如果设为 ENABLE ,一旦总线空闲,硬件会自动尝试重新加入网络。这对于提高系统的自愈能力至关重要。
5. 常见工程痛点与避坑指南
5.1 时钟源的坑
STM32 的 CAN 通常挂在 APB1 总线上。注意,F4 或 H7 系列的时钟树非常复杂,APB1 的频率可能并不是主频。配置波特率前,必须确认 HAL_RCC_GetPCLK1Freq() 的真实返回值。
5.2 忘记启动 CAN
很多初学者调用了 HAL_CAN_Init 后就去发数据,结果报错。
记住:STM32 的 CAN 初始化后处于"初始化模式",必须调用 HAL_CAN_Start(&hcan) 才能进入工作状态。
5.3 滤波器没开,万事皆空
即便你只想接收所有数据,也必须至少配置并激活一个过滤器。如果没有配置过滤器,STM32 会默认拦截总线上所有的报文,你的 FIFO 永远是空的。(这是我们下一章要深挖的主题)。
本章总结
理解了邮箱、FIFO 以及精细到 Tq 的时间段配置,你就已经掌握了 STM32 CAN 的"内功"。波特率不再是盲目尝试的数字,而是基于采样点和时钟频率的严谨推导。