1. STM32 CAN控制器硬件架构深度解析
CAN控制器是STM32系列微控制器中实现ISO 11898标准通信协议的核心外设,其设计并非简单的UART外设升级,而是一套高度集成、具备自主报文调度与过滤能力的专用通信子系统。理解其内部框图结构,是进行可靠CAN应用开发的前提。本节将基于STM32F103(Cortex-M3内核)的CAN控制器(兼容CAN 2.0B协议)展开分析,其架构逻辑同样适用于F4/F7系列,仅在寄存器地址与部分高级特性上存在差异;而H7系列因支持CAN FD(Flexible Data-rate),其控制器结构已发生根本性变化,本文暂不涉及。
1.1 主控制器与从控制器:双CAN通道的物理实现
STM32的CAN控制器在硬件层面被划分为两个逻辑独立的单元: 主CAN控制器(CAN1) 和 从CAN控制器(CAN2) 。这种划分并非简单的功能复制,而是反映了芯片内部总线拓扑与资源分配策略。
-
CAN1
是所有STM32型号(包括F103基础型)均标配的控制器,其寄存器基地址固定为
0x40006400,挂载于APB1总线上。它拥有完整的发送邮箱、接收FIFO及过滤器组,是绝大多数单CAN节点应用的唯一选择。 -
CAN2
并非全系标配。在F103系列中,仅互联型(Connectivity Line)产品(如STM32F107VC)才集成CAN2,其寄存器基地址为
0x40006800。F4/F7系列则普遍配备双CAN控制器。CAN2的引入,本质上是为了满足需要同时接入两个独立CAN网络的应用场景,例如汽车电子中动力总成网络(CAN-C)与车身舒适网络(CAN-B)的隔离通信。两个控制器在硬件上完全独立,拥有各自的时钟使能位、复位控制位、中断向量以及全部寄存器空间,软件上需分别初始化、分别配置、分别处理中断。
这一设计意味着,在F103基础型(如STM32F103C8T6)上尝试使用CAN2将直接导致硬件访问失败。工程师在项目选型初期,必须严格依据数据手册(Datasheet)中的“Peripheral counts”章节确认目标芯片是否具备所需数量的CAN控制器,这是避免后期硬件重构的根本性前提。
1.2 发送邮箱:三重缓冲与智能调度机制
CAN协议要求报文发送必须遵循严格的仲裁规则,而MCU的CPU执行速度远高于CAN总线速率,因此,控制器内部必须提供一个脱离CPU干预的、能够自主管理待发报文的硬件缓冲区。STM32的解决方案是 三个独立的发送邮箱(Mailbox) ,编号为0、1、2。
每个邮箱并非一个简单的FIFO队列,而是一个包含完整报文信息与状态机的硬件单元。其核心组成包括:
-
标识符寄存器(TXID)
:存储待发送报文的标准ID(11位)或扩展ID(29位)。
-
数据长度代码寄存器(DLC)
:定义数据场字节数(0-8)。
-
数据寄存器(TDATA[0-3])
:存放实际的8字节数据。
-
控制寄存器(TIR)
:包含RTR(远程传输请求)位、IDE(标识符扩展)位等控制标志。
-
状态寄存器(TSR)
:实时反映该邮箱的当前状态。
邮箱的状态机是理解发送流程的关键。一个邮箱在其生命周期中会经历以下五种状态:
1.
空闲(Idle)
:邮箱未被占用,可接受新的报文写入。
2.
挂起(Pending)
:CPU已将报文数据写入邮箱,并通过设置
TXRQ
位发起发送请求。此时邮箱内容已锁定,等待进入发送队列。
3.
预定发送(Scheduled)
:邮箱已被调度器选中,成为当前最高优先级的待发邮箱,但总线尚未空闲,处于等待仲裁阶段。
4.
发送中(Transmitting)
:总线空闲,邮箱开始将报文逐位发送至物理总线。
5.
空闲(Idle)
:发送成功(或失败)后,邮箱自动清空,恢复为初始状态,准备接收下一条报文。
调度器(Arbitration Logic) 是邮箱状态转换的核心。它并非由CPU软件实现,而是由CAN控制器内部的硬件逻辑完成。其仲裁依据严格遵循CAN协议规范: 以报文标识符(Identifier)的数值大小为唯一判据,数值越小,优先级越高 。这与以太网的CSMA/CD机制有本质区别。当多个邮箱同时处于“挂起”状态时,调度器会并行比较它们的ID值,将ID最小的邮箱提升至“预定发送”状态。若ID值完全相同,则依据邮箱编号(0 < 1 < 2)进行次级仲裁。这种纯硬件仲裁机制确保了报文发送的确定性与时效性,是CAN总线实现实时性的基石。
1.3 接收FIFO:双通道深度缓冲与溢出管理
与发送端的三个邮箱不同,接收端采用的是 两个深度为3的接收FIFO(FIFO0和FIFO1) ,这是一种面向高吞吐量与低CPU负载的设计。FIFO0通常作为默认接收通道,而FIFO1则常用于接收特定类型的报文(如远程帧或高优先级诊断报文),其路由由过滤器组决定。
FIFO的状态管理同样依赖于硬件状态机,但其逻辑与发送邮箱迥异:
-
空闲(Idle)
:FIFO中无有效报文。
-
挂号1(Full)
:接收到第一个有效报文,FIFO计数器为1。
-
挂号2(Full)
:第二个有效报文到达,计数器为2。
-
挂号3(Full)
:第三个有效报文到达,计数器为3,FIFO达到满载。
-
溢出(Overflow)
:当FIFO已满(计数器=3)且第四个有效报文到达时,即触发溢出。此时,控制器会根据
FOVR
(FIFO Overflow Flag)位的设置来决定丢弃策略:若
FOVR
置位,则新报文被丢弃;若
FOVR
清零,则最老的报文被覆盖。工程师必须在中断服务程序中及时读取FIFO,否则溢出将导致关键数据丢失。
一个常被忽视的关键点是“有效报文”的定义。它不仅要求物理层信号无误(CRC校验通过、无位填充错误、无格式错误),更要求 必须通过至少一个激活的过滤器组的筛选 。这意味着,即使总线上充斥着海量报文,只要它们的ID不匹配任何启用的过滤器,就不会对FIFO造成任何压力,CPU也无需为此消耗任何处理时间。这是CAN控制器硬件过滤能力带来的巨大效率优势。
1.4 过滤器组:14个可编程硬件筛子
STM32F103的CAN控制器配备了
14个全局共享的过滤器组(Filter Bank 0-13)
。这些过滤器并非绑定到某个特定的CAN控制器(CAN1/CAN2),而是作为一个统一的硬件资源池,由CAN1和CAN2共同使用。每个过滤器组由两个32位寄存器构成:
CAN_FiR0
和
CAN_FiR1
(其中
i
为过滤器组编号)。其工作模式由
CAN_FM1R
(过滤器模式寄存器)和
CAN_FS1R
(过滤器尺度寄存器)共同配置,形成四种组合:
| 模式 | 位宽 | 工作模式 | 功能描述 | 典型应用场景 |
|---|---|---|---|---|
| 32位屏蔽位模式 | 32位 | 屏蔽位(Mask) |
CAN_FiR0
存ID,
CAN_FiR1
存屏蔽码。屏蔽码为1的位必须精确匹配,为0的位可忽略。
|
筛选一个ID范围,如
0x100
到
0x1FF
。
|
| 32位列表模式 | 32位 | 列表(List) |
CAN_FiR0
和
CAN_FiR1
均存ID。可精确匹配两个不同的ID。
| 接收两个特定设备的报文。 |
| 16位屏蔽位模式 | 16位 | 屏蔽位(Mask) | 每个寄存器拆分为两个16位段,共可定义两组屏蔽规则。 |
筛选多个标准帧ID,如
0x101
,
0x102
,
0x103
。
|
| 16位列表模式 | 16位 | 列表(List) | 每个寄存器的两个16位段均存ID,共可定义四个ID。 | 白名单模式,只接收四个指定ID的报文。 |
过滤器的路由机制
是另一个核心概念。每个过滤器组在初始化时,必须通过
CAN_FA1R
(过滤器激活寄存器)和
CAN_FMR
(过滤器模式寄存器)明确指定其归属:是为CAN1服务还是为CAN2服务;是将匹配的报文送入FIFO0还是FIFO1。例如,若将过滤器组0配置为“CAN1 + FIFO0”,则所有经该过滤器匹配的报文都将被存入CAN1的FIFO0中,CPU只需轮询或中断响应FIFO0即可获取数据。这种灵活的路由能力,使得一个复杂的多节点网络可以被清晰地划分为逻辑子网,极大简化了软件架构。
2. CAN报文收发全流程:从寄存器操作到状态机演进
理解CAN控制器的静态架构是基础,而掌握其动态的报文处理流程才是工程实践的核心。本节将剥离HAL库的抽象,直击寄存器层面,详细剖析一条报文从CPU发出到总线上传输,以及从总线接收至CPU读取的完整生命周期。
2.1 发送流程:硬件调度下的零CPU干预
以向CAN总线发送一条标准数据帧(ID=0x123, DLC=2, Data=[0x01, 0x02])为例,其硬件流程如下:
-
邮箱选择与数据装载 :CPU首先查询
CAN_TSR(发送状态寄存器)的TME(Transmit Mailbox Empty)位,找到一个处于“空闲”状态的邮箱(假设为邮箱0)。随后,依次向CAN_TI0R(发送标识符寄存器)、CAN_TDT0R(发送数据长度寄存器)和CAN_TDL0R/CAN_TDH0R(发送数据寄存器低/高)写入ID、DLC和数据。此过程必须在TXRQ位清零时进行,否则写入无效。 -
发起发送请求 :CPU将
CAN_TSR的TXRQ0位置1。此操作是“发送请求”的唯一触发信号。一旦置位,邮箱0的状态立即由“空闲”跃迁至“挂起”。此后,CPU无需再关心该邮箱,可立即执行其他任务。 -
硬件仲裁与调度 :CAN控制器内部的仲裁逻辑持续扫描所有邮箱的
TXRQ位。当检测到邮箱0的TXRQ0为1时,即开始将其ID(0x123)与其他处于“挂起”状态的邮箱ID进行比较。由于ID值最小,邮箱0被选中,其状态变为“预定发送”。 -
总线监听与发送 :控制器持续监听总线上的
ACK(应答)和EOF(帧结束)位。当总线连续出现11个隐性位(逻辑1)时,判定为总线空闲。此时,邮箱0的状态切换为“发送中”,控制器自动将报文的起始位、仲裁段、控制段、数据段、CRC段、ACK段和EOF段,严格按照位时序,一位一位地驱动至TX引脚。 -
状态反馈与清理 :报文发送完成后,控制器根据总线上的ACK信号判断发送是否成功。若收到ACK,则在
CAN_TSR中置位RQCP0(Request Completed)和TXOK0(Transmit OK)位;若未收到ACK,则置位RQCP0和TERR0(Transmit Error)位。CPU可通过轮询或中断方式检测RQCP0位,一旦为1,即可读取TXOK0或tERR0位获知结果,并手动清除RQCP0位。此时,邮箱0的状态自动回归“空闲”,等待下一次装载。
整个流程中,CPU仅参与了最初的“装载”和最终的“结果查询”两个瞬间,中间的仲裁、等待、发送、应答检测等全部由硬件在毫秒甚至微秒级内自主完成。这正是CAN总线高实时性与低CPU占用率的根源所在。
2.2 接收流程:过滤器驱动的被动式数据获取
接收流程与发送流程呈镜像关系,其核心驱动力是外部总线上的报文流,而非CPU的主动请求。
-
物理层接收 :CAN_RX引脚持续采样总线电平。当检测到有效的起始位(显性位)时,控制器启动位同步逻辑,开始逐位采样后续的仲裁段、控制段等。
-
ID匹配与过滤 :在仲裁段接收完毕后,控制器立即将接收到的ID与所有 已激活(FAx=1)且归属本CAN控制器 的过滤器组进行并行比对。这是一个纯粹的硬件逻辑门电路操作,耗时极短。若ID匹配任何一个过滤器组的规则,则该报文被判定为“有效”。
-
FIFO路由与存储 :根据该匹配过滤器组所配置的FIFO路由(FIFO0或FIFO1),控制器将整条报文(包括ID、DLC、数据、时间戳等)写入对应的FIFO。写入操作会自动更新FIFO的计数器,并在
CAN_RF0R(接收FIFO0寄存器)或CAN_RF1R中置位FULL位(当计数器从2变为3时)或FOVR位(发生溢出时)。 -
CPU响应与数据读取 :CPU有两种方式获知新报文到达:
- 轮询方式 :周期性读取CAN_RF0R的FULL位。若为1,则说明FIFO0中有新数据,接着读取CAN_RI0R(接收标识符寄存器)、CAN_RDT0R(接收数据长度寄存器)和CAN_RDL0R/CAN_RDH0R(接收数据寄存器)获取完整报文,最后通过写CAN_RF0R的RFOM0位(Release FIFO Output Mailbox)来释放该报文占用的FIFO槽位,使计数器减1。
- 中断方式 :使能CAN_IER(中断使能寄存器)中的FM0IE(FIFO0 Message Pending Interrupt Enable)位。当FIFO0有新报文时,控制器触发CAN_RX0_IRQn中断。在中断服务函数(ISR)中,执行与轮询方式相同的读取与释放操作。
无论采用哪种方式,
及时释放FIFO槽位是防止溢出的唯一手段
。一个典型的错误实践是:在ISR中只读取数据,却忘记执行
RFOM0
操作。这会导致FIFO计数器永远停留在3,后续所有匹配的报文都将被丢弃,而
FULL
位也因FIFO未被释放而无法再次置位,从而使整个接收通道陷入“假死”状态。我在调试一款电机控制器时就曾遭遇此问题,现象是设备能发不能收,最终发现是ISR里遗漏了一行
CAN_RF0R |= CAN_RF0R_RFOM0;
。
3. 过滤器组配置详解:从寄存器映射到实战案例
过滤器组是CAN应用的灵魂,其配置的合理性直接决定了系统的健壮性与可维护性。本节将结合具体寄存器操作,解析如何配置一个实用的过滤器。
3.1 寄存器映射与配置流程
STM32F103的14个过滤器组,其寄存器位于
0x40006400
(CAN1)或
0x40006800
(CAN2)之后的特定偏移处。以配置过滤器组0为例,其核心步骤如下:
-
进入初始化模式
:向
CAN_MCR(主控制寄存器)的INRQ位置1,使控制器进入初始化模式。此时所有CAN操作暂停,寄存器可安全配置。 -
配置过滤器模式与尺度
:向
CAN_FM1R的FBM0位写入0(屏蔽位模式)或1(列表模式);向CAN_FS1R的FS0位写入0(32位)或1(16位)。 -
写入过滤器值
:根据模式,向
CAN_FiR0和CAN_FiR1写入相应的ID值或屏蔽码。 -
配置过滤器归属与激活
:向
CAN_FMR的FACT0位写入0(分配给CAN1)或1(分配给CAN2);向CAN_FA1R的FACT0位置1以激活该过滤器组。 -
退出初始化模式
:向
CAN_MCR的INRQ位写0,控制器恢复正常工作模式。
3.2 实战案例:构建一个标准帧ID范围过滤器
假设我们的应用需要接收ID在
0x200
到
0x20F
之间的所有标准数据帧(共16个ID),这是一个典型的“范围筛选”需求,最适合采用
32位屏蔽位模式
。
-
目标ID范围
:
0x200=0b0000001000000000,0x20F=0b0000001000001111 -
分析公共前缀
:两者前20位完全相同(
0b000000100000),后4位(0000到1111)是变化的。 -
构造屏蔽码
:将公共前缀对应的位置设为1(必须匹配),变化位对应的位置设为0(可忽略)。因此,屏蔽码为
0xFFFFF000。 -
构造基准ID
:取范围内的任意一个ID,如
0x200,其32位表示为0x00000200(标准帧ID左对齐于ID[28:18]位)。 -
寄存器写入
:
```c
// 进入初始化模式
CAN1->MCR |= CAN_MCR_INRQ;
while(!(CAN1->MSR & CAN_MSR_INAK)); // 等待初始化确认
// 配置为32位屏蔽位模式
CAN1->FM1R &= ~CAN_FM1R_FBM0; // FBM0 = 0
CAN1->FS1R |= CAN_FS1R_FS0; // FS0 = 1 (32-bit)
// 写入基准ID和屏蔽码
CAN1->sFilterRegister[0].FR1 = 0x00000200; // CAN_F0R1
CAN1->sFilterRegister[0].FR2 = 0xFFFFF000; // CAN_F0R2
// 分配给CAN1,激活
CAN1->FMR &= ~CAN_FMR_FACT0; // FACT0 = 0 (for CAN1)
CAN1->FA1R |= CAN_FA1R_FACT0; // ACTIVATE
// 退出初始化模式
CAN1->MCR &= ~CAN_MCR_INRQ;
```
此配置后,任何ID的高20位为
0x000002
的标准帧(即ID在
0x200
到
0x20F
之间)都将被接收,而
0x210
或
0x1FF
等ID则会被硬件自动丢弃,CPU对此毫无感知。
4. CAN位时序与波特率计算:精准同步的数学基础
CAN总线的可靠性建立在所有节点对每一位时间的精确共识之上。位时序(Bit Timing)是这一共识的数学表达,其配置失误是导致“偶发性通信失败”这一顽疾的最常见原因。
4.1 三段式位时序模型
与传统UART的简单波特率发生器不同,CAN的位时序被划分为三个逻辑段,每个段由若干个时间量子(Time Quantum, TQ)构成:
-
同步段(Sync Seg)
:固定为1个TQ,用于强制同步。当总线电平发生跳变(如从隐性到显性)时,控制器在此段内调整采样点,以补偿节点间的时钟漂移。
-
时间段1(BS1)
:可配置为1-16个TQ,包含传播时间段(Prop Seg)和相位缓冲段1(Phase Seg1)。它为信号在物理介质上的传播以及控制器内部逻辑的处理提供了裕量。
-
时间段2(BS2)
:可配置为1-8个TQ,即相位缓冲段2(Phase Seg2)。它是采样点之后的保护段,用于吸收时钟误差。
采样点(Sample Point)位于BS1的末尾,即在
Sync_Seg + BS1
个TQ之后。这是控制器读取总线电平以判定当前位是0还是1的关键时刻。一个经验法则是,采样点应设置在位时间的60%-80%处,以获得最佳的抗干扰能力。
4.2 波特率计算公式与工程推导
CAN控制器的位时间(
t_bit
)由以下公式决定:
1 / 波特率 = t_bit = (Sync_Seg + BS1 + BS2) * TQ
而
TQ
本身又由APB1总线时钟(
PCLK1
)和一个预分频器(
BRP
)决定:
TQ = (BRP + 1) / PCLK1
将两式合并,得到最终的波特率计算公式:
波特率 = PCLK1 / [(BRP + 1) * (Sync_Seg + BS1 + BS2)]
对于STM32F103,
PCLK1
通常为36MHz(由AHB时钟经APB1预分频器得到)。若目标波特率为500kbps,则:
500,000 = 36,000,000 / [(BRP + 1) * (1 + BS1 + BS2)]
这意味着
(BRP + 1) * (1 + BS1 + BS2) = 72
。我们需要在约束条件下(
BRP
为0-1023,
BS1
为1-16,
BS2
为1-8)寻找一组合理的整数解。
一个经典且鲁棒的解是:
BRP = 5
,
BS1 = 6
,
BS2 = 5
。验证:
(5 + 1) * (1 + 6 + 5) = 6 * 12 = 72
,完美匹配。
此时,采样点位置为
(1+6)/12 ≈ 58.3%
,符合推荐范围。
在代码中,这些参数通过
CAN_BTR
(位定时寄存器)一次性写入:
CAN1->BTR = (5 << CAN_BTR_BRP_Pos) | // BRP = 5
(6 << CAN_BTR_TS1_Pos) | // TS1 = BS1 = 6
(5 << CAN_BTR_TS2_Pos) | // TS2 = BS2 = 5
(1 << CAN_BTR_SJW_Pos); // SJW = 1 (同步跳转宽度)
SJW(Synchronization Jump Width) 是一个关键但常被忽略的参数,它定义了在重同步时,BS1或BS2最多可以延长或缩短的TQ数。将其设为1,为应对总线噪声提供了必要的弹性。
5. 跨型号兼容性要点与工程避坑指南
在实际项目中,工程师常需将F103的CAN代码移植到F407或F7系列。虽然核心逻辑一致,但细节差异足以引发致命故障。
5.1 关键差异点总结
| 项目 | STM32F103 | STM32F407/F7 | 工程影响 |
|---|---|---|---|
| CAN时钟源 | APB1总线时钟(通常36MHz) | APB1总线时钟(通常42MHz) |
BRP
值需重新计算,不可直接复用。
|
| 过滤器组数量 | 14个全局共享 | 28个(F407)或更多(F7) |
F103代码若硬编码
i<14
,在F407上可能访问非法地址。
|
| 中断向量 |
CAN_RX0_IRQn
,
CAN_TX_IRQn
|
CAN1_RX0_IRQn
,
CAN1_TX_IRQn
,
CAN2_RX0_IRQn
等
| 中断服务函数名与NVIC配置必须更新。 |
| 寄存器地址偏移 |
CAN1_BASE = 0x40006400
|
CAN1_BASE = 0x40006400
,
CAN2_BASE = 0x40006800
| 地址宏定义需适配,尤其在裸机编程中。 |
5.2 一个真实的移植陷阱
在将F103的CAN驱动移植到F407时,我曾遇到一个极其隐蔽的问题:CAN通信在低负载下完全正常,但在高负载(>100帧/秒)时,接收FIFO频繁溢出。排查数日,最终发现F407的
CAN_MCR
寄存器中有一个新增的
DBF
(Debug Freeze)位,默认为0。当芯片连接ST-Link进行调试时,若该位未被置1,控制器在CPU halted(暂停)状态下会停止接收新报文,但FIFO中的旧报文仍可被读取,这导致了一个“伪溢出”的假象——FIFO并未真正满,只是CPU暂停期间新报文被丢弃了。解决方案是在初始化时添加:
CAN1->MCR |= CAN_MCR_DBF;
。
这个案例深刻地说明,对芯片手册中每一个寄存器位的敬畏,是嵌入式工程师职业生涯的护身符。
6973

被折叠的 条评论
为什么被折叠?



