AVR DA/DB系列安全就绪特性助力IEC 60730 B类功能安全设计

发布时间:2026/8/28 12:37:37
AVR DA/DB系列安全就绪特性助力IEC 60730 B类功能安全设计 做家电和工控的同行应该都有体会这两年客户对功能安全的要求越来越具体了。前阵子接了一个厨电控制板项目规格书里直接写了需要满足 IEC 60730 B 类要求这在以前只有出口欧洲的大客户才会提现在内销产品也开始跟进。过认证最麻烦的不是硬件而是软件自检RAM 测试、Flash 校验、时钟检测、端口回读每一项都得自己写工作量不小还有一个更头疼的问题——写出来的自检代码本身可靠性如何没人敢打包票。后来方案选型时注意到 AVR 的 DA/DB 系列芯片资料里明确写了 Safety-Ready Features把 CRC 闪存扫描、电压监视器、窗口看门狗这些安全外设直接集成到硬件里这篇文章就聊聊这些特性的实际用法和踩坑记录。1. 为什么一款 8 位 MCU 要强调安全就绪1.1 安全就绪到底在回应什么问题功能安全标准对嵌入式软件有几类常见要求程序存储区需要周期性做完整性检查RAM 需要做固定测试CPU 寄存器与程序计数器需要检查时钟源需要验证数字 I/O 需要回读。这些要求本质上是同一个逻辑单靠 MCU 自己跑应用无法证明 MCU 自己是健康的。以前做这类产品常规套路是外挂一个独立看门狗芯片主控在启动阶段用软件做一轮检查运行期间周期性执行。这套方案可行但痛点非常明显。第一软件检查占用 CPU 时间尤其是 Flash CRC 这种重活在 8 位平台上逐字节算 CRC32 可能要几十毫秒甚至更久期间实时响应全被耽误。第二自检代码本身也是代码它有没有 bug、覆盖是否完整需要额外的验证成本。第三外围器件一多BOM 成本就上去了小家电的利润率根本经不起这么加料。AVR 的回应思路是把这类安全机制做成片内外设。CRCSCAN 用硬件扫描 FlashVLM 做电压监视WDT 支持窗口模式这些都不需要 CPU 持续参与。开发者要做的是在启动阶段编排自检顺序、配置好外设、定义好异常处理策略。硬件负责干活软件负责决策这是嵌入式安全设计里更合理的一种分工。1.2 这一代 AVR 的安全外设全景具体到 AVR DA/DB/DD 系列安全相关的周边资源大致是这些外设功能定位典型用途CRCSCAN硬件 CRC 扫描引擎可扫描 Flash 主区或 Boot 段程序区完整性自检支持 CRC-16/CRC-32VLM可编程电压阈值监视器掉电预警、电池低压告警、欠压保护WDT普通模式 窗口模式看门狗程序跑飞复位、识别提前喂狗异常CLKCTRL 时钟校验内部高频振荡器配合 RTC 低功耗振荡器测频率偏差时钟源偏移检测EVSYS 事件系统把安全事件直接送达 CPU 或其他外设VLM 触发后联动关闭 PWM 输出这个列表看起来简单但真正把它们用对需要理解每个外设背后的设计意图。比如 VLM 和传统 BOD 的差异、CRCSCAN 的期望值怎么烧进去、窗口看门狗的窗口时间怎么留裕量。下面几节逐个展开讲清楚原理也讲实操中容易踩的坑。2. CRCSCAN闪存完整性自检不再靠软件硬算2.1 硬件 CRC 引擎的扫描过程CRCSCAN 的工作方式和软件算 CRC 完全不同。软件计算要求 CPU 从 Flash 读出每个字节然后按多项式算法逐步处理一个 32KB 的应用跑完一轮 CRC32在 8 位 MCU 上可能要花几十毫秒期间 CPU 无法处理其他任务。对实时性要求高的电机控制、通信采集场景这几十毫秒根本挤不出来。CRCSCAN 是独立硬件模块它利用 CPU 空闲的存储总线周期一个字节一个字节地读取 Flash 内容算出 CRC 后和预先存放在 Flash 里的期望值比对。注意利用空闲周期这个设计很关键也就是说即使应用正常运行CRCSCAN 也能在后台持续扫描不影响实时性能。这也是它和上电做一次 CRC 校验的最大区别运行期间也是持续覆盖的而不是只在启动时检查一次。配置的时候要选三类参数扫描源全部程序 Flash还是只扫 Boot 段CRC 多项式CRC-16 或 CRC-32工作模式Class1整区扫描或 Class2只扫 Boot 段。如果 CRC 不匹配可以配置成产生复位让系统重新来一遍完整自检也可以配置成触发中断让主程序把关键负载关掉再进入安全状态。我偏向于触发中断因为复位不是目的安全停机才是。直接复位可能让系统陷入启动-检错-复位的循环对周边负载反而是二次冲击。2.2 把 CRC 校验值烧进 Flash 的正确姿势这是最容易搞错的一步。CRCSCAN 不会自动算出期望值然后自己存起来它需要一个参考值来比对。参考值放在哪、怎么算出来需要你自己安排在 Flash 的某个地址。我的做法是在工程里预留一个 4 字节的 CRC 存储区构建流程里加一个小工具编译完成后读取生成的 hex 文件计算应用程序区实际内容的 CRC32再把结果写回预留地址。这里有个坑计算范围要排除 CRC 值本身所在的那 4 个字节否则你把 CRC 写进去之后整个区域的内容变了算出的 CRC 又对不上。对应到代码里可以在链接脚本里预留一个段// 链接脚本中的预留段示例实际名称以工程为准 __attribute__((section(.crc_storage))) const uint32_t flash_crc 0x00000000; // 构建工具运行时会替换这个值构建脚本里加一个很小的 Python 处理从生成的 hex 里读出代码区字节计算 CRC32填充到预留地址。核心逻辑就三步import zlib def fill_crc(hex_path, code_start, code_end, crc_addr): # 1. 解析 hex得到 code_start 到 code_end 的字节流 code_bytes extract_hex_range(hex_path, code_start, code_end) # 2. 计算 CRC32注意 zlib.crc32 的结果需要按大端或小端写入看 CRCSCAN 的字节序要求 crc_value zlib.crc32(code_bytes) 0xFFFFFFFF # 3. 把 crc_value 写回 crc_addr 地址 write_word_to_hex(hex_path, crc_addr, crc_value)第一次烧录后在调试器里读取预留地址确认值已经被替换再观察 CRCSCAN 状态寄存器是否输出匹配标志。这一步做通了后面量产就省心。另外强烈建议产品量产之前做一次 CRC 校验的负向测试。手动改掉 Flash 里一个字节比如故意烧写错误 hex看 CRCSCAN 是否能在预期时间内拉高错误标志或触发复位。这一步能验证整个监控链路的有效性比跑一万次正常测试都有意义。3. VLM 电压监视器与窗口 WDT供电和程序流程的最后一层防线3.1 VLM 和 BOD 有什么区别为什么要两个BODBrown-out Detection是大部分 AVR 都有的掉电检测电压跌落到阈值以下就产生复位。它解决的是电压过低导致芯片乱跑的问题。但 BOD 的阈值是固定几个档位触发行为通常只有复位不够灵活。VLM 可以理解为增强版的可编程电压检测。它能选多个阈值能指定触发沿上升沿、下降沿、双向触发。触发了可以产生中断让应用有机会保存关键参数、关闭外部负载或者标记一次欠压事件也可以输出到事件系统直接驱动其他外设动作。这个灵活性在实际产品里价值很大。举个我经手的例子。一个电池供电的传感节点主控需要监测电池电压。常规做法是 ADC 一直采样但系统大部分时间在休眠ADC 常开就失去了省电意义。VLM 是纯模拟比较器电路功耗极低休眠模式下照样工作电压跌到阈值就唤醒 MCU 做处理。这比定时醒来开 ADC 量一次电压再睡的方案省电得多。配置 VLM 时要注意别和 BOD 打架。如果 BOD 门槛比 VLM 高电压快速跌落时 BOD 先复位VLM 中断根本没机会跑如果 BOD 门槛过低VLM 报警后程序还没来得及做保护就被 BOD 复位移除。实际产品里的做法是VLM 阈值选在系统可以正常工作、但电压已经不是健康范围的那个点BOD 阈值选在 MCU 数据手册标注的最小工作电压附近两级之间留出足够的保护区。// VLM 配置示例以 AVR DB 系列为例寄存器定义以具体型号手册为准 VLM.CTRLA VLM_TRIGGER_FALLING_gc; // 只监视下降沿避免上电瞬间反复触发 VLM.CTRLB VLM_INTLVL_MED_gc; // 中等优先级中断VLM 触发之后中断服务程序里做的事情要尽量短置一个标志位、关闭危险负载、记录事件然后尽快退出。不要在中断里做 Flash 写入、长时间延时这类操作。3.2 窗口模式 WDT 的配置细节普通看门狗只有超时没喂就复位这一个判断维度。窗口模式加了一个限制喂狗动作必须在时间窗口内执行喂早了也会复位。这在某些异常场景下很有价值。比如程序主循环卡死在某个中断里普通看门狗可能被中断里的喂狗代码喂掉系统一直带病运行窗口狗能识别出程序没有按主循环节律运行从而触发复位。窗口模式的配置有两个关键参数窗口期和超时周期。窗口期是允许喂狗的时间范围超时周期是 WDT 的完整计时长度。工程上有个常见误区把窗口调得很窄觉得越窄越安全结果主循环因为一个耗时的通信任务抖动喂狗时机滑出窗口系统被误复位。窗口宽度一定要按主循环最长路径估算留足裕量。我习惯把窗口设置为超时周期的 25%~50%既保留了提前喂狗的检测能力又不会过于敏感。调试窗口狗时有一个极其容易踩的坑使用调试器单步执行时窗口狗照样计时程序停在断点上喂不了狗过一会儿就复位了。第一次遇到这个现象十个人有九个以为是代码问题。解决办法很简单调试阶段把窗口狗功能加一个条件编译开关只在产品模式启用或者先把超时周期调长先用串口日志验证业务逻辑最后再把时间收严。4. 实操搭一个 IEC 60730 B 类自检工程4.1 上电自检流程的编排我自己搭安全自检工程时的启动顺序是这样的设置时钟源等待内部振荡器稳定配置外设总线时钟RAM 测试在启用全局变量和进入 main 主流程之前做因为测试会破坏内存内容Flash CRC 启动扫描把 CRCSCAN 初始化好触发一次快速扫描数字 I/O 回读测试确认关键的输出引脚状态可控ADC 自检采样内部参考通道得到预期范围内的读数窗口 WDT 启动自检完成后开启进入正常运行运行期周期自检主循环里定时做 Flash 后台扫描和 RAM 快速复查。为什么 RAM 测试放在最前面因为 C 语言的全局变量是启动代码在调用 main 之前初始化的。如果进入 main 之后再做 RAM 测试测试模式和初始化值会打架反过来如果在启动代码里先做 RAM 测试再做全局变量初始化测试又会把栈区破坏掉。所以普遍的做法是在复位向量后、静态变量初始化之前用启动文件里的钩子函数执行 RAM 测试。工程上最简单的方案是修改启动文件在__libc_init_array之前调用一个safety_ram_test()。时钟自检也是一个容易漏掉的环节。AVR 内部 RC 振荡器的频率会随温度和电压漂移如果应用的通信波特率依赖主时钟时钟偏移会导致通信异常。常见的检测手段是用 32.768kHz 的低功耗振荡器作为参考测量主时钟在固定周期内的计数判断是否超出容差范围。这个测试在启动时做一次运行期每隔一段时间做一次就能覆盖时钟漂移的风险。4.2 RAM 自检、端口回读、ADC 自检的代码要点RAM 测试我平时用 March C 算法的一个简化版。大致逻辑是把每个 RAM 字节依次写成 0x00、0xFF、0xAA、0x55再依次读回比对两个方向各遍历一次。代码量不大但有一个关键点测试函数本身不能在测试区里运行否则它的返回地址会被清掉测完直接跑飞。// RAM 自检的核心逻辑伪代码 int safety_ram_test(void) { volatile uint8_t *p; uint8_t patterns[4] {0x00, 0xFF, 0xAA, 0x55}; for (int round 0; round 4; round) { // 升序写 for (p RAM_START; p RAM_END; p) { *p patterns[round]; } // 升序读 for (p RAM_START; p RAM_END; p) { if (*p ! patterns[round]) return 1; } // 降序读 for (p RAM_END; p RAM_START; p--) { if (*p ! patterns[round]) return 1; } } return 0; }这段代码只能当作示意实际工程里要做更细致的内存区划分把栈区、全局变量区、DMA 缓冲区分开处理。栈区一般不做破坏性测试而是用填充特定字节、检查水位的方式验证栈是否有溢出风险。端口回读测试首先要明确哪些引脚可以测。驱动继电器、MOS 管的输出引脚不能贸然翻转否则负载动作会导致事故。安全的做法是在产品上电初期、负载还没有使能时把引脚配置成普通 IO输出一组测试序列比如先低后高同时把对应的输入方向打开回读确认逻辑一致再恢复为外设模式。如果某个引脚外接了强上拉或强下拉的器件测试时要预判回读电平否则会把正常状态误判为故障。ADC 自检常用手法是采样一个已知的片内信号。AVR 多数型号内部有多路 ADC 输入通道可以选到内部参考电压或温度传感器。配置 ADC 通道为内部参考输入采样结果应该落在数据手册给定的范围内。如果超出了说明 ADC 的参考源或者采样链路有问题。注意这个测试只覆盖 ADC 自身链路不覆盖外部传感器但有总比没有强。另外一个细节ADC 自检完成后要把通道切回实际使用的外部通道避免影响后续采样。5. 调试安全功能时踩过的坑5.1 CRC 不匹配、VLM 误触发这类老问题先说 CRC 不匹配。最常见的原因有两个一个是 CRC 值写入了但计算范围没排除 CRC 自身区域导致 CRCSCAN 算出来的值和存放的期望值永远对不上另一个是改了代码之后没有重新生成 CRC 值烧进去的还是旧值。建议在修改任何代码之后、对比升级之前都把 CRC 值重新生成一遍。量产烧录时也要确保工具流程里包含 CRC 替换这一步否则每片芯片的 CRC 全是空值CRCSCAN 会一直报错。VLM 误触发也是个高频问题。配置了下降沿触发后如果产品使用开关电源上电瞬间的电压跌落曲线可能不是单调的会在阈值附近来回穿越导致 VLM 中断反复触发。解决办法有两个方向软件上加去抖在中断里做 10ms 或 100ms 的延时确认硬件上在 VDD 引脚加 0.1uF 陶瓷去耦电容也能抑制毛刺。如果 VLM 的输出同时接到事件系统触发 PWM 关断误触发会造成负载意外停机这个问题更要认真处理。运行期周期性自检还有一个隐蔽问题如果主循环里调用 Flash CRC 扫描启动同时又跑窗口狗两者不要抢时间窗口。扫描期间存储总线被占用一部分如果此时又有高速通信任务在收发数据延时预算会受影响。我实测中遇到过窗口狗和 CRC 扫描同时触发导致喂狗超时的情况最后把 CRC 扫描的启动时机放到系统空闲时执行才稳定下来。我把几个高频问题整理成了速查表现象可能原因排查方向CRCSCAN 一直报不匹配期望值未填充或计算范围包含 CRC 区检查 hex 文件预留地址重新生成 CRCVLM 反复触发中断上电电压跌落曲线非单调软件加去抖VDD 脚加去耦电容窗口狗周期性复位主循环最长路径超出窗口期加大窗口裕量关闭调试模式观察单步调试时系统总是复位窗口狗/WDT 在断点期间照常计时调试阶段用条件编译关闭 WDTRAM 测试后程序跑飞测试代码本身位于被测 RAM 区调整测试位置排除栈区和代码区ADC 自检数值不在预期范围参考源异常或通道未恢复检查参考电压寄存器恢复外部通道5.2 如何验证你的自检真的有效验证自检最好的方式是故障注入这是我做过几轮认证项目后觉得效果远好于代码 review 的方法。简单说就是故意制造故障看系统能不能按预期进入安全状态。故障注入可以这样操作Flash 测试用调试器修改程序区某个字节观察 CRCSCAN 是否触发复位或中断。验证完记得恢复原内容并重新生成 CRC 值。RAM 测试运行中硬改某个变量地址看 RAM 复检是否能发现。注意如果这个操作触发了后续控制逻辑要保证测试台架安全。时钟测试在代码里把时钟源配置故意改错观察时钟监测逻辑是否能报错。端口回读短接一个输出引脚到电源或地看回读测试是否能检测到异常。每次故障注入要记录注入时间、预期表现、实际表现形成一张测试记录表。这张表既是产品验证的证据也是过认证评审的有力素材。另外故障注入测试要在产品最终版本的软硬件上进行因为在开发阶段临时改的配置很有可能掩盖或放大某些问题。我习惯在项目原理图阶段就把安全策略画出来标出每个监控点用哪个外设、事件路径是什么再动手写代码。芯片提供的安全外设再多也只是一个一个孤立的功能模块怎么把它们组合成一条完整的安全链仍然需要软件工程师从系统层面设计。AVR 的 Safety-Ready Features 最大的价值在于把原来要自己写、自己证明可靠性的代码变成了芯片自带的硬件行为。但这不意味着可以不做系统设计而是把精力从反复验证自检代码是否正确转移到设计安全状态机、定义异常响应流程上。希望这篇内容能帮你在做安全功能时少踩几个坑。

相关新闻