CAN过滤器代码初始化
182.1.5 CAN过滤器代码初始化
CAN_FilterTypeDef Can_FilterConfig; /* 定义一份 CAN 接收过滤器配置;本课随后会给这个结构体的全部成员逐项赋值。 */
Can_FilterConfig.FilterBank = 0; /* 选择过滤器组 0;STM32F103ZE 有 14 组,合法编号为 0~13。 */
Can_FilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; /* 使用“标识符 + 掩码”模式;掩码位决定对应标识符位是否参与比较。 */
Can_FilterConfig.FilterScale = CAN_FILTERSCALE_16BIT; /* 把本过滤器组配置为 16 位尺度,可形成两组 16 位的 ID/掩码比较项。 */
Can_FilterConfig.FilterIdHigh = 0x0; /* 把其中一组过滤标识符值写为 0;因对应掩码也是 0,该值实际不会限制报文。 */
Can_FilterConfig.FilterIdLow = 0x0; /* 把另一组过滤标识符值写为 0;它同样会因全 0 掩码而成为“全部放行”。 */
Can_FilterConfig.FilterMaskIdHigh = 0x0; /* 高半部分掩码全为 0,表示这些比较位全部“不关心”。 */
Can_FilterConfig.FilterMaskIdLow = 0x0; /* 低半部分掩码也全为 0;最终效果是接收所有 ID,而不是只接收 ID 0。 */
Can_FilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; /* 把通过过滤器组 0 的报文路由到硬件接收 FIFO0。 */
Can_FilterConfig.FilterActivation = CAN_FILTER_ENABLE; /* 激活过滤器组 0;这只启用该过滤器,并不等于已经启动整个 CAN 控制器。 */
Can_FilterConfig.SlaveStartFilterBank = 14; /* 双 CAN 器件中用于划分 CAN1/CAN2 的共享过滤器组;单 CAN 的 F103ZE 会忽略此项。 */
if (HAL_CAN_ConfigFilter(&hcan, &Can_FilterConfig) != HAL_OK) /* 把上述配置交给 hcan 对应的 CAN1;若 HAL 没有返回成功状态就进入错误处理。 */
{ /* 配置失败分支开始;正常配置成功时不会进入此代码块。 */
Error_Handler(); /* 调用 CubeMX 生成的错误处理函数;默认实现会关中断并停在死循环中。 */
} /* 过滤器配置返回值检查结束。 */
HAL_CAN_Start(&hcan); /* 让 CAN1 离开初始化状态并开始参与总线通信;原课程代码没有检查此函数的返回值。 */
上面只保留本节在
main.c中实际完成的业务代码,并在原语句上增加逐行注释。课程结尾只是从 HAL 头文件找到HAL_CAN_AddTxMessage()与HAL_CAN_GetRxMessage()的声明,尚未填写参数或形成收发逻辑,因此不把库函数声明和后续课程代码强行补进来。
一、代码书写
上一课的 MX_CAN_Init() 只完成 CAN 外设基础参数初始化,例如实例、模式、预分频、BS1、BS2、SJW 和自动重发选项。本课还要完成两件事:
- 配置至少一个接收过滤器,让符合条件的报文能够进入接收 FIFO;
- 调用
HAL_CAN_Start(),让控制器离开初始化状态并开始正常通信。
本节写入的代码位于 CubeMX 生成的 main() 中、MX_CAN_Init() 和 MX_USART1_UART_Init() 之后、while (1) 之前,也就是 /* USER CODE BEGIN 2 */ 区域。放在 USER CODE 区域内,后续重新生成代码时才能由 CubeMX 保留。
正确的启动顺序是:
HAL_Init / 时钟配置
↓
MX_CAN_Init():初始化 bxCAN 基础参数和 GPIO
↓
填写 CAN_FilterTypeDef
↓
HAL_CAN_ConfigFilter():写入并激活过滤器
↓
HAL_CAN_Start():启动 CAN
↓
while (1):后续课程再执行发送/接收
1.1 中文手册
CAN 是广播总线,一帧会到达所有节点。STM32 的 bxCAN 在报文交给软件之前使用硬件过滤器比较标识符等字段:
CANRX → 协议检查 → 过滤器组 → 匹配则进入 FIFO0/FIFO1 → 软件读取
└→ 不匹配则硬件丢弃
因此 CAN 与最简单的 UART 轮询收发不完全相同。使用 bxCAN 接收报文前,需要让至少一个过滤器组处于可匹配、已激活状态;否则总线上即使已有正确帧,也可能不会进入接收 FIFO。
课程打开 STM32 中文参考手册的双 CAN 框图,是为了观察“过滤器在 CAN 内核与两个接收 FIFO 之间”的位置。该图为互联型双 CAN 器件绘制;本课的 STM32F103ZET6 只有 CAN1 和 14 个过滤器组,只需要理解图中的主 CAN 路径,不要照图误认为芯片还有 CAN2。
MX_CAN_Init() 中的代码与上一课 CubeMX 配置相对应:Prescaler=4、BS1=9 TQ、BS2=8 TQ 等。CubeMX 没有在本次操作中替课程生成过滤规则,所以本课在用户代码区手动填写 CAN_FilterTypeDef,之后才会继续发送与接收。
1.2 查找函数
课程在 Keil 工程的 Drivers/STM32F1xx_HAL_Driver 下查找 HAL CAN 接口:
Inc/stm32f1xx_hal_can.h主要提供类型定义、宏、常量与函数声明,适合先查“有哪些接口、参数类型是什么”;Src/stm32f1xx_hal_can.c保存这些函数的实现,适合继续确认接口怎样操作寄存器、何时返回错误;- 头文件不应笼统理解成“保存变量的文件”。它通常保存声明和类型,真正具有存储空间的变量定义多放在
.c文件中,除非头文件中使用了特殊定义方式。
视频先在头文件约 649 行附近找到 HAL_CAN_Init(),再向下找到:
HAL_CAN_ConfigFilter() 配置接收过滤器
HAL_CAN_Start() 启动 CAN 控制器
HAL_CAN_AddTxMessage() 把一帧加入发送邮箱
HAL_CAN_GetRxMessage() 从接收 FIFO 取出一帧
不同 HAL 库版本的行号可能变化,所以不要死记“649 行”。更可靠的方法是使用 Keil 的 Go To Definition、工程内搜索或直接在 stm32f1xx_hal_can.h 搜索完整函数名。
1.3 初始化CAN
hcan 的类型是 CAN_HandleTypeDef,它保存 CAN 实例、初始化参数、状态和错误码等信息。CubeMX 已在 can.c 中定义并由 MX_CAN_Init() 初始化它,调用 HAL 接口时传入 &hcan,是把该结构体变量的地址交给函数。
过滤器参数则使用另一种类型 CAN_FilterTypeDef:
CAN_HandleTypeDef → 描述并管理整个 CAN 外设
CAN_FilterTypeDef → 描述某一个过滤器组怎样配置
HAL_CAN_ConfigFilter(&hcan, &Can_FilterConfig) 的两个参数分别指向 CAN 句柄和本次过滤器配置。使用地址而不是复制整个结构体,既让函数知道要操作哪个 CAN,也避免按值传递大型结构体。
本课把过滤器配置放在 while (1) 之前是正确的:过滤规则通常只在初始化阶段设置一次,无需在主循环中反复写寄存器。反复配置不仅浪费时间;HAL 在配置时还会让过滤器进入初始化状态,期间接收会暂停。
1.4 编译滤波器初始化代码
1.4.1 复制变量赋值
CAN_FilterTypeDef Can_FilterConfig; 只是在当前 main() 作用域内创建结构体变量,并不会自动产生有效过滤规则。本课随后为结构体所有成员赋值,再把地址传给 HAL。
FilterBank = 0 表示配置过滤器组 0。STM32F103ZE 共有 14 个过滤器组,编号为 0~13;“14 个”是数量,“13”是最后一个编号,原笔记的“13~14 个过滤器”说法不准确。
过滤器组不能简单等同于一个 ID 槽。每组有两个 32 位寄存器,根据 16/32 位尺度和列表/掩码模式,能够组织成不同数量的比较项。本节只使用 0 号组,其余组仍保持未激活。
在编辑器中输入 Can_FilterConfig. 后利用自动补全选择成员,是减少拼写错误的实用方法;但最终仍应对照 CAN_FilterTypeDef 定义逐项确认,不能只凭名字猜含义。
1.4.2 初始化别的参数
原笔记中的 model 应为 FilterMode,即过滤器模式。HAL 头文件为它提供两个宏:
| 宏 | 含义 |
|---|---|
CAN_FILTERMODE_IDMASK | 标识符屏蔽模式:过滤值配合掩码使用 |
CAN_FILTERMODE_IDLIST | 标识符列表模式:寄存器内放入需要精确匹配的标识符项 |
课程通过“选中类型或宏 → Go To Definition / Find in Files”的方法追踪定义。这比凭经验输入常量可靠,也能看见 HAL 对参数范围的注释。
1.4.3 查找滤波器模式
本课选择 CAN_FILTERMODE_IDMASK。屏蔽模式可用下面的关系理解:
[ (Received\ ID \land Mask)=(Filter\ ID \land Mask) ]
对每一个掩码位:
- 掩码为
1:收到报文的对应位必须与过滤 ID 相同; - 掩码为
0:对应位不参与比较,也就是“不关心”。
因此,屏蔽模式适合接收一组具有共同高位或共同模式的标识符;列表模式适合列出若干个必须精确相等的标识符。两种模式的具体位对齐会在后续过滤器课程继续展开,本节先建立“掩码决定哪些位要比较”的核心概念。
1.4.4 初始化别的变量
FilterScale = CAN_FILTERSCALE_16BIT 表示使用 16 位尺度。在当前的 ID Mask 模式下,一个过滤器组可以组织为两套 16 位“过滤 ID + 掩码”比较项;若改为 32 位尺度,则是一套 32 位过滤 ID 与掩码。
本课把以下四个字段都设为 0x0:
FilterIdHigh
FilterIdLow
FilterMaskIdHigh
FilterMaskIdLow
这里最容易误解:**它不是只允许 ID 为 0 的帧,而是允许所有帧通过。**原因是两个 Mask 字段全为 0,所有比较位均为“不关心”;无论接收标识符与两个 ID 字段是什么值,按位与掩码后的两侧都为 0。
这种“全接收”配置适合最初联调,便于先验证物理连接、位率和收发代码。但正式项目若总线报文很多,应根据消息规划设置精确过滤规则,否则所有帧都进入 FIFO0,会增加 CPU 负担并提高 FIFO 溢出风险。
1.4.5 初始化fifo赋值
FilterFIFOAssignment = CAN_FILTER_FIFO0 把过滤器组 0 的匹配结果路由到接收 FIFO0。这里选择的是“报文通过后放到哪个 FIFO”,不是启用一个新的软件数组。
HAL 中用于配置过滤器去向的宏是 CAN_FILTER_FIFO0;后续读取接收 FIFO 时使用的参数宏通常是 CAN_RX_FIFO0。二者数值可能相同,但语义位置不同,应按函数声明使用对应名称,不要混写。
两个 FIFO 都只有 3 帧深度。当前课程所有报文都通过过滤器组 0 进入 FIFO0,因此后续接收函数也必须从 FIFO0 读取;若读取 FIFO1,就看不到这些报文。
1.4.6 指定过滤器零的数据经过的fifo
本节的实际接收路径为:
所有正确接收的 CAN 帧
↓ Mask 全 0,所以均匹配
过滤器组 0
↓ FilterFIFOAssignment = CAN_FILTER_FIFO0
接收 FIFO0
↓ 后续由 HAL_CAN_GetRxMessage() 读取
应用程序
“数据直接通过 0 号过滤器”应理解成“本配置让所有帧都匹配过滤器组 0”,而不是 CAN 硬件永远无条件绕过过滤。将来只要把掩码改成非零,就会开始比较相应 ID、IDE 或 RTR 位。
1.4.7 初始化激活函数
原笔记所说的“激活函数”实际是结构体成员 FilterActivation。赋值 CAN_FILTER_ENABLE 表示激活过滤器组 0;它只控制该过滤器组,不等于启动 CAN 控制器,所以后面还必须调用 HAL_CAN_Start()。
SlaveStartFilterBank = 14 只对共享 28 个过滤器组的双 CAN 器件有实际划分意义:编号小于 14 的组分给主 CAN1,从 14 开始的组分给从 CAN2。当前 STM32F103ZE 只有单 CAN,HAL 源码在没有 CAN2 的条件编译分支中不会使用此成员;课程仍把它设为 14,使结构体字段有确定值,也便于代码迁移到双 CAN 项目时看见这个配置入口。
注意:这项参数不是“初始化从过滤器”,也不会凭空给 STM32F103ZE 增加 CAN2。该芯片仍是 14 个过滤器组,编号 0~13。
1.5 编译查看结果
初版代码直接调用 HAL_CAN_ConfigFilter() 可以通过语法编译,但仅仅“0 Error”不能证明运行时配置成功。课程随后参考 CubeMX 生成的 HAL_CAN_Init() 检查形式,把过滤器配置调用改为:
调用 HAL_CAN_ConfigFilter()
↓
返回 HAL_OK? ──是──→ 继续
│
否
↓
Error_Handler()
这样至少不会在过滤器配置失败后若无其事地继续运行。Error_Handler() 的 CubeMX 默认实现通常会关闭中断并停在无限循环;真正产品还应记录错误码、点灯或执行安全恢复,但本课没有修改该函数,不应提前补写。
课程原样代码对 HAL_CAN_Start(&hcan) 的返回值没有判断。该函数同样返回 HAL_StatusTypeDef,严谨项目通常也应检查,不过按照“源码原样保留”的要求,这里只在注释中提示,不擅自替课程改写。
1.6 滤波器初始化分析
原笔记中的“返回值类型别名为 h”是识别错误。HAL_CAN_ConfigFilter() 和 HAL_CAN_Init() 的返回类型都是 HAL_StatusTypeDef,这是 HAL 的通用状态枚举,包含:
HAL_OK 成功
HAL_ERROR 错误
HAL_BUSY 忙
HAL_TIMEOUT 超时
因此可以使用 != HAL_OK 判断“只要不是成功就进入错误处理”。这段写法相同,是因为两个函数采用相同的返回类型和错误处理约定,并不是因为两个函数完成相同工作:
HAL_CAN_Init()配置 CAN 基础寄存器、位时序与底层 GPIO/时钟;HAL_CAN_ConfigFilter()配置接收过滤器组;HAL_CAN_Start()请求 CAN 离开初始化状态,进入可收发状态。
三者缺一不可。过滤器配置成功也不代表总线硬件一定正常;若收发器未供电、CANH/CANL 接反、没有正确端接、另一节点位率不同或总线无法同步,后续通信仍会失败。
1.7 查找库函数接口
过滤器配置之后,课程从头文件找到 HAL_CAN_Start() 并调用:
HAL_CAN_ConfigFilter() → HAL_CAN_Start() → 以后再发送/接收
启动函数会请求 bxCAN 离开初始化状态,并把 HAL 句柄状态切换到监听/工作状态。它不负责自动构造业务报文,也不会自动把收到的数据打印到串口。
课程还在 HAL 头文件中找到了发送和接收接口,作为下一步的入口:
HAL_CAN_AddTxMessage():把报文头和 0~8 字节数据装入一个空发送邮箱,并返回所用邮箱;HAL_CAN_GetRxMessage():从指定 FIFO 读取队首报文头与数据,并释放该 FIFO 输出邮箱;- 在轮询接收模式下,通常还要先用
HAL_CAN_GetRxFifoFillLevel()判断 FIFO 是否非空,避免无报文时直接读取。
这里只说明接口用途,不抄入 HAL 库函数的内部实现。用户要求收录的是课堂业务代码,而不是把 2000 多行驱动源码复制进笔记。
1.8 回到前面指定的h文件
课程最后再次回到 stm32f1xx_hal_can.h,确认后续将使用:
| 接口 | 关键参数 | 本课进度 |
|---|---|---|
HAL_CAN_AddTxMessage() | hcan、发送帧头、数据数组、返回邮箱 | 只找到声明,尚未填写参数 |
HAL_CAN_GetRxMessage() | hcan、FIFO 编号、接收帧头、数据数组 | 只找到声明,尚未填写参数 |
因此本课的完成边界是:**过滤器组 0 全放行并路由到 FIFO0,随后启动 CAN1。**发送帧头、接收帧头、数据缓冲区和主循环收发逻辑均留到后续课程,不能为了让代码看起来完整而提前补上。
参考资料
- 课程配套 STM32中文参考手册:核对旧版第 22.7.4 节的过滤器组、16/32 位尺度、标识符列表/屏蔽模式、FIFO 分配和双 CAN 过滤器划分。
- 本课配套 main.c:只取其中与本节视频一致的过滤器初始化和 CAN 启动片段;文件内其余收发代码来自后续课程,没有提前收录。
- 配套 HAL stm32f1xx_hal_can.h 与 stm32f1xx_hal_can.c:核对
CAN_FilterTypeDef成员、宏定义、API 声明、单/双 CAN 条件分支以及寄存器写入过程。 - ST 官方 RM0008 Rev 21:核对当前版 bxCAN 过滤器、FIFO 与启动状态说明。