CAN网关的区间白名单怎样装进硬件过滤器
网关只关心八个 CAN ID,其余报文应该在哪里被丢掉?
在收到数据之后写一句 if,功能上很直接。但如果判断发生在 4G 模组里,不相关报文已经经过 CAN 接收 FIFO、中断、环形缓冲和板内串口。过滤结果相同,前面几层付出的代价却不同。
这次把区间白名单往前移一层,放到 AT32F425 的硬件过滤器里。重点是把任意起止 ID 精确翻译成掩码,并处理帧格式混淆、过滤组耗尽和旧配置残留。静态工具、启动补丁和验证记录已经完成,整机烧录及总线实测不在本次结论内。

图1 原路径先由 MCU 接收,再由模组检查区间;改动把固定接收边界放到 FIFO 之前,软件区间过滤仍可保留。
先找到真正负责接收的芯片
这块板上有 LTE 模组,也有一颗负责 CAN 转 UART 的 AT32F425F8P7。今天动的是后者的接收过滤配置,不需要改 PCB。

图2 从对应 V1 工程的嘉立创 EDA 编辑器重新导出的原理图局部。U5 的 PA2 接 CAN1_RX,PA3 接 CAN1_TX。
原驱动把 PA2、PA3 配成 MUX4,与图中的收发方向对应。PA4 则接 CAN_120_CTRL,控制终端电阻继电器。它不是过滤器的使能脚。

图3 实际器件是 U6 SIT65HVD230DR,供电为 3V3;CN1 的 1、2、3 脚分别为 CANL、CANH、GND。不要因型号后缀相似,就把它写成另一家的器件。

图4 V1 PCB 顶、底面由实际编辑器重新导出。图用于定位板内 CAN 接收链路;本次修改保留原硬件版本,未宣称完成整板电源、射频或车辆接入验证。
源码里的关键线索很短。drv_can_filter_init() 启用第0组、32位掩码模式、FIFO0,但标识字和掩码全部为0。
掩码位为0,意思是这一位不用比较。因此这组过滤器既不比较 ID,也不比较 IDT 和 RTR,没有建立接收白名单。随后 ML307R 侧的 can_filter_check(ff, id) 才按帧格式和闭区间筛选。这里不是软件区间比较算错了,而是可以把固定边界前移,让无关报文不再进入这条接收搬运路径。
区间为什么不能直接写进掩码
假设要接收标准帧 0x123~0x12A,首尾都包含,一共八个 ID。
掩码不是大小比较器。它表达的是“哪些位必须相同”,天然适合按二次幂对齐的块。用一个过粗的掩码去罩住这八个 ID,往往会连附近不需要的 ID 一起放进来。

图5 8个 ID 的数量虽然是2的幂,起点却不按8对齐,因此仍需拆成4块。
拆分时,每次从区间左端拿走一个“起点对齐、长度不过界”的最大二次幂块。以第一步为例,0x123 最低位是1,只能先拿走一个 ID;移动到 0x124 后,可以拿4个。
核心计算如下,完整的输入检查、区间合并和输出代码在附件中。
while lo <= hi:
remaining = hi - lo + 1
fit = 1 << (remaining.bit_length() - 1)
aligned = (lo & -lo) if lo else (1 << bits)
size = min(aligned, fit)
mask = ((1 << bits) - 1) ^ (size - 1)
# 当前块为 [lo, lo + size - 1]
lo += size
bits 对标准帧为11,对扩展帧为29。起点为0时要单独处理,否则 lo & -lo 也是0,循环就无法前进。
在拆分之前,同一种帧格式里的重叠和相邻区间先合并,避免重复占用过滤组。标准帧与扩展帧分开合并,即使数值相同也不混在一起。
别漏掉帧格式位
AT32F425 技术手册 V2.05 的32位掩码模式中,标准 ID 位于寄存器的 [31:21];扩展 ID 使用 [31:3]。bit2 是 IDT,即标准/扩展格式标志,bit1 是 RTR。

图6 位段依据手册第341~342页重绘;代码中的 0x4 用于比较 IDT,不能省略。
生成标识字与掩码字时,使用以下变换。
shift = 3 if ff else 21
id_word = (lo << shift) | (ff << 2)
mask_word = (mask << shift) | 0x4
为什么掩码要加 0x4?如果只比较 ID 高位,扩展帧可能共享相同的标准 ID 前缀。显式比较 IDT 后,标准白名单只接收标准帧,扩展白名单只接收扩展帧。
RTR 这次保持不比较,因为原软件过滤器依据的是帧格式与 ID 区间,没有把数据帧和远程帧分成两套规则。如果应用只接收数据帧,应另外增加明确的 RTR 策略和测试,不能悄悄改变原有语义。
示例生成的前四组寄存器值如下,可以逐行对照图5。
ID word Mask word 标准帧范围
24600000 FFE00004 123~123
24800000 FF800004 124~127
25000000 FFC00004 128~129
25400000 FFE00004 12A~12A
附件还配置了扩展帧 0x18F00100~0x18F0010F。这一段按16对齐,只增加一组,标识字为 0xC7800804,掩码为 0xFFFFFF84;合计使用5组。
14组用完之后,必须承认放不下
这颗芯片提供14组过滤器,编号0~13。本实现统一使用32位掩码模式,每个拆分块占一组。它没有混排16位模式或列表模式,也不声称给出了所有硬件模式下的最少组数。
对于这套确定的表示方法,容量不足就应明确报错。不能截掉后面的块,也不能擅自放宽掩码“挤进去”。前者漏收,后者扩大接收集合,都会让白名单失去可解释性。
生成器先校验整数类型、ID宽度和区间方向,再统计拆分结果。超过14组时返回非零退出码,停止构建。JSON 中的 true、浮点数和字符串不会被当成合法 ID,重复键也会拒绝。
启动阶段还有一个容易留下的尾巴。配置从5组缩成2组时,只重写前2组并不够,原来的后3组可能仍在工作。因此适配器先关闭全部14组,再逐组启用新表;空白名单就保持全部关闭,不自动退回“全收”。
实际写入使用工程自带的厂商函数,以下是启用新表的部分。
for (unsigned bank = 0; bank < FILTER_COUNT; ++bank) {
f.filter_number = (uint8_t)bank;
f.filter_id_high = (uint16_t)(filter_table[bank].id_word >> 16);
f.filter_id_low = (uint16_t)filter_table[bank].id_word;
f.filter_mask_high = (uint16_t)(filter_table[bank].mask_word >> 16);
f.filter_mask_low = (uint16_t)filter_table[bank].mask_word;
f.filter_activate_enable = TRUE;
can_filter_init(can, &f);
}
完整函数已经在前面设置好32位、掩码模式和FIFO0,并完成旧组关闭。集成时在 CAN 时钟及基础参数配置之后、接收中断打开之前调用,由这个模块独占14组。不要直接把它搬到正在接收报文的运行期,假定切换期间也能无损收包。
用集合比较去检验掩码,而不只看几个例子
“边界上两帧能通过”不足以证明中间没有洞、旁边没有漏进来的 ID。测试以最朴素的区间判断作为独立参照,再与生成寄存器字的掩码匹配结果比较。

图7 来自本次运行日志的验证范围。RAM寄存器检查没有模拟真实 CAN 控制器,也没有连接实体总线。
120个标准区间分别穷举全部2048个 ID,同时检查数据/远程帧,以及共享 SID 前缀的另一种帧格式。扩展 ID 空间不适合在这篇专题里全量穷举,使用固定随机种子生成300个区间;其中287个容量合格实例执行随机与起止边界比较,其余13个因超过14组而被明确拒绝。
随后把工程中的真实 can_filter_init() 原样提取出来,对 RAM 中的厂商寄存器结构执行配置,检查示例5组、空表和满14组时的激活位、位宽、模式、FIFO关联和旧组清除。适配器、厂商函数及修改后的 drv_can.c 也完成了 Cortex-M4 目标对象编译。
这些证据支持“生成的集合与预期一致、参数能按厂商接口写入、改动文件可以针对目标编译”。它们不支持宣称整机已经烧录成功,更不能换算成实测 CPU 占用、丢帧率或通信稳定性。
把静态边界和远程配置说清楚
保留 ML307R 软件过滤时,最终通过的集合是 硬件白名单 H 与软件白名单 S 的交集。
这给部署带来一个明确约束。远程把软件白名单扩大到硬件范围之外,新 ID 依然进不来。附件会从同一输入生成硬件表和模组侧区间 JSON,便于核对配置;但没有实现自动下发,也没有构建 ML307R 的专有 SDK。
固定采集范围适合使用这次的静态方案。需要随时扩大接收范围,则应另行设计版本号、容量预检、确认应答及失效恢复协议,并先做台架验证。CAN 接收过滤也不等于总线隔离、认证或攻击防护,它不会降低总线上已经存在的报文负载。
复核本文可先在电脑运行以下命令,附件没有提供未经整机验证的烧录二进制。
python interval_filters.py whitelist.json generated
python test_filters.py
python build_and_verify.py --zig C:/path/to/zig.exe
改动的价值在于把“起止 ID”变成一份可以审查、可以拒绝、可以复算的硬件配置。区间边界、帧格式和硬件容量都写进同一条生成流程,白名单才不会在换一组参数之后悄悄变味。
材料与验证说明
下载源码与复核包,包含完整主机工具、生成配置、启动适配器、原工程集成补丁、所需厂商头文件、许可证和真实验证日志。环境为 Python 3.12.14、Zig 0.13.0,工程自带 AT32 外设库2.1.3;无需板卡即可复核主机工具,目标部分完成对象编译,未做整机链接与实测。
取材项目为《车载4G CANDTU》,原作者 eda_kwazdnpkc,项目 V1,页面更新于2025年3月24日。完整来源 https://oshwhub.com/eda_kwazdnpkc/ml3074gcandtu 。项目标注 GPL 3.0;原理图及 PCB 图片从对应 V1 的嘉立创 EDA 编辑器重新导出,仅裁剪、保真缩放和适度锐化;本文示意图独立绘制,未使用生成式图像补画电路。
技术依据为原项目的 F425_UART2CAN.7z、ML307R-SRC.7z,以及雅特力《AT32F425系列技术手册》V2.05第341、342、359页。本文独立实现了区间编译器与静态配置适配,未修改硬件。独立工具和适配器采用MIT许可,集成派生文件遵守GPL-3.0,第三方BSP、CMSIS和CherryRB保留各自声明,完整文本随源码包提供。
DF页面许可仅适用于本文独立文字,第三方工程与电路图仍按GPL-3.0,代码依附件分项许可。
技术交流,欢迎关注微信公众号:美男子玩编程。



