首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >CAN网关的区间白名单怎样装进硬件过滤器

CAN网关的区间白名单怎样装进硬件过滤器

原创
作者头像
不脱发的程序猿
发布于 2026-10-14 18:23:09
发布于 2026-10-14 18:23:09
70
举报

网关只关心八个 CAN ID,其余报文应该在哪里被丢掉?

在收到数据之后写一句 if,功能上很直接。但如果判断发生在 4G 模组里,不相关报文已经经过 CAN 接收 FIFO、中断、环形缓冲和板内串口。过滤结果相同,前面几层付出的代价却不同。

这次把区间白名单往前移一层,放到 AT32F425 的硬件过滤器里。重点是把任意起止 ID 精确翻译成掩码,并处理帧格式混淆、过滤组耗尽和旧配置残留。静态工具、启动补丁和验证记录已经完成,整机烧录及总线实测不在本次结论内。

原链路与改进后的静态过滤路径
原链路与改进后的静态过滤路径

图1 原路径先由 MCU 接收,再由模组检查区间;改动把固定接收边界放到 FIFO 之前,软件区间过滤仍可保留。

先找到真正负责接收的芯片

这块板上有 LTE 模组,也有一颗负责 CAN 转 UART 的 AT32F425F8P7。今天动的是后者的接收过滤配置,不需要改 PCB。

AT32F425的实际EDA引脚局部
AT32F425的实际EDA引脚局部

图2 从对应 V1 工程的嘉立创 EDA 编辑器重新导出的原理图局部。U5 的 PA2 接 CAN1_RX,PA3 接 CAN1_TX。

原驱动把 PA2、PA3 配成 MUX4,与图中的收发方向对应。PA4 则接 CAN_120_CTRL,控制终端电阻继电器。它不是过滤器的使能脚。

CAN收发器和接口电路
CAN收发器和接口电路

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

同版PCB顶面与底面
同版PCB顶面与底面

图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个。

核心计算如下,完整的输入检查、区间合并和输出代码在附件中。

代码语言:python
复制
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,不能省略。

生成标识字与掩码字时,使用以下变换。

代码语言:python
复制
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。

代码语言:txt
复制
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组,再逐组启用新表;空白名单就保持全部关闭,不自动退回“全收”。

实际写入使用工程自带的厂商函数,以下是启用新表的部分。

代码语言:c
复制
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 接收过滤也不等于总线隔离、认证或攻击防护,它不会降低总线上已经存在的报文负载。

复核本文可先在电脑运行以下命令,附件没有提供未经整机验证的烧录二进制。

代码语言:txt
复制
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保留各自声明,完整文本随源码包提供。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 先找到真正负责接收的芯片
  • 区间为什么不能直接写进掩码
  • 别漏掉帧格式位
  • 14组用完之后,必须承认放不下
  • 用集合比较去检验掩码,而不只看几个例子
  • 把静态边界和远程配置说清楚
  • 材料与验证说明
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档