简介:这份英飞凌官方应用笔记(AP32221)中文版面向汽车电子与嵌入式底层开发工程师,聚焦AURIX™家族TriCore™ 32位微处理器中的安全看门狗机制,帮助读者理解如何借助WDT保障系统运行的安全性与可靠性。压缩包内仅有1个PDF文件,约419KB,篇幅精炼,便于随查随用。正文围绕看门狗定时器依次展开:先概述WDT的整体结构与主要特性,再逐项说明相关寄存器的定义;随后深入ENDINIT保护功能,讲清对WDTxCON0寄存器的静态密码、自动密码序列、与时间无关的密码及时间检查密码等访问方式,并交代检查访问与修改访问的具体流程;文末附缩略词与参考文献,便于延伸查阅。目前已有1066人学习,适合正在配置AURIX平台安全机制、需要核对寄存器操作时序与密码解锁流程的开发者作为案头参考。
1. 一块板子十分钟复位一次,让我重新翻开了这份 AURIX 安全看门狗笔记
某次调试一块多核板子,现象很玄学:上电跑得好好的,七八分钟到十几分钟之间必复位,CPU 没跑飞,栈也没溢出,最后是从 SMU 报警状态位里才顺藤摸瓜找到原因——清 ENDINIT 之后,重装载入窗口超时了。问题定位完,我把压在硬盘角落里的那份 AURIX 应用笔记又翻了出来:AP32221《CPU 和安全看门狗》,围绕 TC27x 器件,把安全看门狗、三个 CPU 看门狗、ENDINIT 保护、密码访问机制到示例代码全串了一遍,一共二十几页,薄,但每一页都是能直接落到寄存器上的东西。它不讲“看门狗很重要”这种废话,直接告诉你 WDTxCON0 的 [1:0] 位该写 01B 还是 11B、密码读回来为什么是反的、清完 ENDINIT 为什么必须先读一次再写。适合三类人:刚接手 AURIX 项目被 SMU 报警折磨的软件工程师、要做分层看门狗设计的、以及想把“喂狗”从玄学变成可验证流程的从业者。
2. 四种看门狗怎么分工:安全看门狗与 CPUx 看门狗的职责边界
2.1 复位后谁醒着:四种 WDT 的默认状态与权限归属
TC27x 上不是“一个看门狗”,是四个:一个功能安全看门狗(WDTs),加 CPU0、CPU1、CPU2 各一个(WDTCPU0/1/2)。这个数量差异直接决定了复位之后的行为差异——CPU0 复位后处于 RUN 模式,它的 CPU 看门狗自动就启动了;CPU1 和 CPU2 初始在 HALT 状态,对应的 WDT 是停用的,必须等软件把它们唤醒之后自己去配置和使能。安全看门狗则是复位后就默认有效的那一个。这一点在实际项目里非常容易翻车:单核时代养成的习惯是“上电就有人在喂狗”,搬到多核上,CPU1 的核间任务跑起来了,但它的看门狗压根没开,等于这段代码完全没有运行时保护。
两者的定位也不一样。安全看门狗是中心资源,盯着与安全相关的系统级功能,它独立于任何 CPU 看门狗存在,即使某个 CPU 的看门狗被停用,安全看门狗照样能兜底;CPU 看门狗是局部资源,监控单个 CPU 的执行线程,不需要多个 CPU 去协调一个公共看门狗。权限上,安全看门狗的密码寄存器受 SCU 的通用写保护机制(ACCEN0)约束,典型配置下只有被指定为“安全核”的那个 CPU 才能配置和喂它;CPU 看门狗则各自受独立保护,只能被与它对应的那个 CPU 访问,跨核去碰别人的 WDTCPUyCON0 是碰不到的。
我在做分层设计时一般这么分:安全看门狗交给主核,周期放宽,检查的是“整个系统还活着”;每个 CPU 看门狗盯自己的任务序列,周期收紧,检查的是“我这条线程没卡死”。两层的时间常数拉开三到五倍,避免同时超时导致没有任何缓冲。这份笔记里还提到一个容易被忽略的细节:复位之后用于功能安全和 CPU0 的看门狗默认有效,如果应用根本不用看门狗,停用之后 ENDINIT 特性依然有效——也就是说,你不能靠“把狗关了”来绕过受保护寄存器的访问流程。
2.2 WDTxCON0 / WDTxCON1 / WDTxSR:三个寄存器各自管什么
这份笔记把寄存器拆得很干净,落地时按这三个来记就够了。WDTxCON0 是“操作入口”,高 16 位是用户可定义的重载值 REL,中间 14 位([15:2])是密码字段 PW,最低两位决定这次访问的类型,另外还挂着 ENDINIT 位和由硬件控制的锁定位 LCK。WDTxCON1 是“配置入口”,管输入频率选择、停用请求、解锁限制、密码自动序列使能、时间戳检查配置这些运行参数,它本身也受对应 WDTxCON0.ENDINIT 位的保护。WDTxSR 是“状态出口”,出问题时你基本就靠它活着。
| 寄存器 | 关键字段 | 落地时的用途 |
|---|---|---|
| WDTxCON0 | REL[31:16]、PW[15:2]、LCK、ENDINIT | 解锁、喂狗、改重载值、开关 ENDINIT |
| WDTxCON1 | 分频选择、停用请求、UR 解锁限制、PAS 自动密码、TCS 时间戳检查 | 上电时定一次,运行期少动 |
| WDTxSR | AE 访问故障、溢出故障、DS 停用状态、US 解锁限制状态、PAS/TCS 状态、TIM 计数值、TCT 公差 | 报警后第一现场,排查必读 |
WDTxSR 里我最常看的是 AE 和 TIM 两个。AE 置位说明发生过访问错误——要么密码写错,要么修改访问的保护位不对,这时候不用怀疑时钟、不用怀疑电源,一定是软件序列写错了。TIM 是当前计数器内容,配合 TCT 公差,是做执行时间监控的基础。另外 WDTxSR 还带着看门狗输入时钟状态、使能/停用状态、超时模式标志、SMU 解锁限制状态等位,排查时一次性全部打印出来,比一条条猜快得多。
安全看门狗的 WDTSCON1 比 CPU 看门狗多一个 CLRIRF 位,用来清除检测双 SMU 复位(比如看门狗触发的复位)的内部复位状态。这个位在“复位后想知道上次到底是不是看门狗干的”这种场景里很有用,我一般在上电初始化的最前面就读一次相关状态再清,否则等初始化跑完,现场已经被覆盖了。
2.3 时钟分频与 16 位重载值:溢出周期怎么手算
每个看门狗都是 16 位计数器,输入时钟三选一:fSPB/64、fSPB/256、fSPB/16384。重载值也是 16 位,正常模式下由用户定义,超时模式下被硬件固定成 0xFFFC。溢出周期的估算就一个式子:
T ≈ (REL + 1) × 分频系数 / fSPB拿常见配置举例:fSPB 按 100 MHz 算,选 64 分频、REL 取 0xFFFF,周期约 65536 × 64 / 100e6 ≈ 41.9 ms;换成 16384 分频、REL 取 0x7FFF,周期约 32768 × 16384 / 100e6 ≈ 5.37 s。前者适合做任务级心跳,后者适合做系统级兜底。中间那档 256 分频适合几百毫秒级的场景,比如周期性通信任务。实际取值一定要回查用户手册确认 fSPB 的真实频率,别拿主频直接套,总线时钟和内核时钟不是一回事。
这里有个反直觉的地方:超时模式下的重载值是固定的 0xFFFC,不是你在 REL 里配的那个值。而看门狗一旦被解锁(LCK=0),会自动切进超时模式;只有在对 WDTxCON0 的一次有效修改访问里把 ENDINIT 置位之后,超时模式才终止。换句话说,你清 ENDINIT 打开的访问窗口,长度是由 0xFFFC 和当前分频决定的,按 64 分频、100 MHz 算也就 42 ms 左右。这个窗口里要塞进所有受保护寄存器的写操作,窗口一过没恢复 ENDINIT,SMU 就报警。我第一次踩这个坑就是在这个窗口里调了一堆外设初始化,时间根本不够。
选型上的经验:分频系数和重载值要一起定,不要先定重载值再凑分频。可行的做法是先按任务周期算出期望的超时时间,然后在三档分频里挑一档让 REL 落在 0x1000 到 0xEFFF 之间——太低会让计数抖动的影响放大,太高则失去了调整余量,改周期时无值可调。
3. ENDINIT 与密码访问:受保护寄存器的一次开锁全过程
3.1 ENDINIT 位到底锁住了什么,为什么清完还要先读一次
ENDINIT 是这整套机制的核心。TC27x 用一系列 ENDINIT 特性保护系统关键寄存器,写这些寄存器必须同时满足两个条件:对应 WDTx 控制寄存器 0 里的 ENDINIT 位被清 0,且当前处于超级管理员模式。少一个,写操作直接被忽略,寄存器内容不变,而且不会给你任何提示——这是最难查的一类问题,代码看起来执行了,值就是没写进去。
更狠的是,清 ENDINIT 这个动作本身也受保护。你必须先做一次有效的密码访问把 WDTxCON0 解锁,再做一次有效的修改访问把 ENDINIT 清 0。解锁的瞬间看门狗自动进超时模式,开始倒计时;如果在倒计时结束前 ENDINIT 没被重新置 1,系统就被判定为软件流异常,报警请求发往 SMU。
这份笔记里有一句注释,我建议每个接触 AURIX 的人都抄在笔记本第一页:清除 ENDINIT 位需要花费一定时间,为了确保真正使能了对 ENDINIT 保护寄存器的访问,在 ENDINIT 位被清 0 之后、首次访问受保护寄存器之前,应该先读取一次 ENDINIT 位。也就是说,“写寄存器清 ENDINIT”和“ENDINIT 真的变成 0”之间有时序差,不读一次确认就往下写,前面几个寄存器大概率被吞掉。我后来的习惯是:写完清除操作立刻回读,确认是 0 再继续,多花的那几个周期远比事后定位便宜。
顺带记一个调试相关的东西:Cerberus 模块的 CBS_OSTATE.ENIDIS 位可以覆盖所有 WDT 的 ENDINIT 控制。该位置位时,无论当前 WDT 配置如何,所有 ENDINIT 保护都被停用;清 0 则控制权交回看门狗。调试阶段这确实省事,但也意味着你在调试器下验证通过的代码,脱机跑可能完全是另一回事。
3.2 静态密码与 LFSR 自动序列:两种密码模式的取舍
密码字段是 14 位(PW[15:2]),应用复位后的默认值是 00000000111100B,也就是 0x3C。这里有个坑必须先讲清楚:从 WDTxCON0.PW 读回来的值,低 6 位(PW[7:2])是被取反的,高 8 位(PW[15:8])才是原值。
这个反码设计不是刁难人,它防的是“读出来原样写回去”这种偷懒操作。如果读和写能构成一个合法的密码访问序列,那么任何一段跑飞的代码只要误打误撞读到这个寄存器再写回去,就能把看门狗喂了、把 ENDINIT 开了,保护形同虚设。取反之后,简单的读-写序列必然产生无效密码。
两种密码模式各有适用面。静态密码模式(WDTxSR.PAS = 0)下,只有有效的修改访问才能改密码,密码值在软件里保存一份、每次访问直接用,实现简单、时序可预测,绝大多数项目用这个就够了。自动密码序列模式(WDTxSR.PAS = 1)下,每次密码检查之后密码自动更新,下一个期望值遵循基于 14 位 Fibonacci LFSR 的伪随机序列,特征多项式是 x¹⁴ + x¹³ + x¹² + x² + 1;也可以通过修改访问提供初始密码,之后手动更新。这个模式对“代码执行序列检查”有意义——密码对不上,说明访问顺序或时机不对。
我的取舍标准是:如果项目只需要看门狗做超时监控,用静态密码,代码好读好维护;如果要额外做代码流完整性检查(比如防范跳过某段初始化直接进主循环这类问题),再上自动序列。上自动序列就必须把 LFSR 的推进逻辑封装成一个函数,任何一次密码访问后都同步更新软件侧的期望值,漏一次后面全错。
3.3 密码访问、检查访问、修改访问:三种访问的位模式对照
这是整份笔记里最“抄作业”的部分,三种访问的区别全在最低两位和 REL 字段的填法上。我把它整理成表,落地时照着填就不会错。
| 访问类型 | [1:0] | REL[31:16] 填什么 | 效果 |
|---|---|---|---|
| 密码访问 | 01B | TCS=0 时填当前重载值;TCS=1 时填 TIM 的反码(须在 ±TCT 内) | 解锁 WDTxCON0,LCK 变 0 |
| 检查访问 | 11B | 同上 | 做中间点校验,不清 LCK |
| 修改访问 | bit1 固定 1,bit0 为期望的 ENDINIT 值 | 用户期望的新重载值 | 仅在已解锁时可写,完成后自动重新上锁 |
密码访问是解锁动作,前提是访问前 LCK 位为 1;密码有效且 SMU 状态满足 WDTxSR.US 位要求时,访问一完成 WDTxCON0 立即解锁,LCK 变 0。检查访问和密码访问几乎一样,唯一区别是它不清 LCK,不能接着做修改访问,专门用来在任务序列中间打点。修改访问则要求先解锁,位 1 必须写 1,位 0 放你希望 ENDINIT 变成的值,写完自动重新上锁。
三种访问只要有一处不满足要求,结果都是同一个:WDTxSR.AE 置位,报警请求发给 SMU。所以看到 AE,先回头核这张表,别去查别的地方。
还有一个和 SMU 联动的位值得单独说:WDTxCON1.UR。把它置 1 之后,如果 SMU 处于报警状态,看门狗会被阻止解锁。这是为了防止 CPU 已经出问题、却还在机械地喂狗把故障掩盖过去。开了这个位,SMU 报警必须被真正处理掉,喂狗才能继续,否则就等着溢出复位。做功能安全相关设计时我一般会打开它。
3.4 从清 ENDINIT 到重新置位:一条带时间预算的时序
把上面这些串起来,一次完整的受保护寄存器写操作是这样的:
第一步,密码访问解锁 WDTxCON0,LCK 变 0,看门狗自动进入超时模式,倒计时开始。第二步,紧接着做修改访问,bit1 写 1、bit0 写 0,把 ENDINIT 清 0。第三步,回读确认 ENDINIT 确实为 0。第四步,在窗口内完成所有受保护寄存器的写操作。第五步,再做一次密码访问解锁,然后做修改访问,bit0 写 1 把 ENDINIT 置回去。置位完成后超时模式终止,一切恢复正常。
这条链上最脆弱的是第四步的时间预算。前面算过,超时窗口按 0xFFFC 和分频决定,几十毫秒量级。如果受保护寄存器有一大堆要配(外设初始化常常如此),要么把分频调大拉长窗口,要么把配置拆成多批、每批走一遍完整的开锁上锁流程。我倾向后者,因为单次窗口开得越长,保护的意义就越弱。拆批的代价是每批都要多做两次访问,但这部分开销是可以算出来、也可以测出来的。
4. 落地代码:初始化、喂狗、改重载值与停用看门狗
4.1 上电初始化:先定分频,再定密码与重载值
初始化的顺序不能乱:WDTxCON1 里的分频、使能这些配置本身受 ENDINIT 保护,所以必须先把 ENDINIT 打开,改完 CON1,再把 CON0 的密码和重载值落实,最后恢复 ENDINIT。下面这段是骨架,寄存器名按 CPU0 的看门狗写,换成 WDTSCON0 或 WDTCPU1CON0 逻辑一样。
/* 说明:以下为寄存器级示意,实际工程建议封装到平台层, 并优先复用厂商底层驱动库里的宏定义,避免位域写错 */ #define WDT_PW_ACCESS 0x1u /* [1:0] = 01B:密码访问 */ #define WDT_MODIFY_BIT 0x2u /* bit1 固定写 1:修改访问 */ #define WDT_PW_DEFAULT 0x003Cu /* 复位后默认密码 00000000111100B */ static void wdt_cpu0_init(uint16_t new_rel, uint16_t new_pw) { uint32_t v; /* 1) 解锁:密码访问,REL 填当前重载值(TCS=0 的模式) */ WDTCPU0CON0 = ((uint32_t)new_rel << 16) | ((uint32_t)(new_pw & 0x3FFFu) << 2) | WDT_PW_ACCESS; /* 2) 修改访问:清 ENDINIT,进入受保护寄存器写窗口 */ WDTCPU0CON0 = ((uint32_t)new_rel << 16) | ((uint32_t)(new_pw & 0x3FFFu) << 2) | WDT_MODIFY_BIT | 0x0u; /* 3) 回读确认 ENDINIT 真的被清掉,再动受保护寄存器 */ while ((WDTCPU0CON0 & 0x1u) != 0u) { } /* 4) 此时可写 WDTCPU0CON1:选分频、清停用位、按需开 UR/PAS/TCS */ v = WDTCPU0CON1; v &= ~(0x3u << 0); /* 清分频选择位,具体位号以手册为准 */ v |= (0x1u << 0); /* 选 fSPB/64,按需换成 256 或 16384 */ v &= ~(0x1u << 3); /* 清停用请求,确保看门狗处于使能状态 */ WDTCPU0CON1 = v; /* 5) 重新置 ENDINIT:再走一遍解锁 + 修改访问 */ WDTCPU0CON0 = ((uint32_t)new_rel << 16) | ((uint32_t)(new_pw & 0x3FFFu) << 2) | WDT_PW_ACCESS; WDTCPU0CON0 = ((uint32_t)new_rel << 16) | ((uint32_t)(new_pw & 0x3FFFu) << 2) | WDT_MODIFY_BIT | 0x1u; }代码里第 1、2 步的 REL 字段在 TCS=0 时必须填当前重载值,第一次初始化时还没有“当前值”,用你准备设进去的新值即可,因为修改访问会把它正式写进去。第 3 步那个空转等待看着多余,但它是整段代码里最值钱的一行——没有它,第 4 步的写操作可能被静默丢弃。第 4 步里 CON1 的位号我按典型布局标注,实际位号务必回查用户手册,不同器件子型号的位定义可能有差异。
参数上有两个建议:new_pw 不要直接用默认的 0x3C,换个固定值并在软件里保存好;new_rel 按第 2.3 节的公式反推,别拍脑袋。至于用默认密码行不行——行,但等于把“任何知道默认值的人都能解锁”这件事写进了产品里。
4.2 运行期喂狗:静态密码模式与时间戳检查模式
静态密码模式下喂狗,本质是“解锁 + 修改访问,但 ENDINIT 目标值保持 1”。有个细节:读回来的密码低 6 位是反的,所以要写回去必须先翻回来。有两种写法,一种是软件保存明文密码,一种是从寄存器读回再翻转,后者更“自洽”,我一般用后者。
static void wdt_cpu0_service(void) { uint32_t v = WDTCPU0CON0; uint16_t pw = (uint16_t)((v >> 2) & 0x3FFFu); uint16_t rel = (uint16_t)(v >> 16); /* PW[7:2] 读回来是反码,写回前必须翻转低 6 位 */ pw ^= 0x003Fu; /* 密码访问:解锁 */ WDTCPU0CON0 = ((uint32_t)rel << 16) | ((uint32_t)pw << 2) | WDT_PW_ACCESS; /* 修改访问:bit1=1,bit0=1 保持 ENDINIT 保护 */ WDTCPU0CON0 = ((uint32_t)rel << 16) | ((uint32_t)pw << 2) | WDT_MODIFY_BIT | 0x1u; }如果开了时间戳检查(TCS=1),REL 字段就不能填重载值了,必须填当前计数值的反码,且必须落在 TIM ± TCT 的范围内。这个机制的价值在于:它不只检查“你在喂狗”,还检查“你在什么时候喂狗”。执行时间超限、跳过了某段代码直接来喂狗,都会因为估值落在公差带外面而触发报警。
static void wdt_cpu0_service_ts(void) { uint32_t sr = WDTCPU0SR; uint16_t tim = (uint16_t)(sr & 0xFFFFu); /* 当前计数器内容 */ uint32_t v = WDTCPU0CON0; uint16_t pw = (uint16_t)((v >> 2) & 0x3FFFu); uint16_t est = (uint16_t)(~tim); /* 必须写 TIM 的反码 */ pw ^= 0x003Fu; WDTCPU0CON0 = ((uint32_t)est << 16) | ((uint32_t)pw << 2) | WDT_PW_ACCESS; WDTCPU0CON0 = ((uint32_t)est << 16) | ((uint32_t)pw << 2) | WDT_MODIFY_BIT | 0x1u; }注意读 TIM 和写 CON0 之间有指令延迟,计数值一直在变,所以 TCT 公差别设得太紧,官方默认值通常够用,除非你对执行时间有很严的约束。另外要提醒一点:即使在超时模式下,时间检查比较仍然需要一次密码或检查访问,别以为“反正已经解锁了就不用再走流程”。
喂狗的位置比喂狗的频率更重要。我的做法是把喂狗点放在主循环末尾或者高优先级任务的最外层,让“能喂到狗”这件事等价于“所有关键任务都跑完了一轮”。把手动喂狗散落在各个中断里,是给自己埋雷。
4.3 改重载值与切换时钟源:运行期重配置的正确姿势
运行期改周期(比如从性能模式切到低功耗模式要放宽超时时间),必须走完整的解锁流程,因为 WDTxCON1 受 ENDINIT 保护,而重载值在 WDTxCON0.REL。顺序是:解锁 → 修改访问时把新的 REL 写进高 16 位并清 ENDINIT → 写 CON1 改分频 → 再解锁 → 修改访问把 ENDINIT 置回并再次确认 REL 已经是新值。
改重载值时最容易出错的是“只改了一半”:CON1 的分频改了,CON0 的 REL 还是按旧分频算出来的值,结果实际超时时间和你以为的差好几倍。我一般的做法是把分频系数和 REL 打包成一个结构体常量表,切模式时整组切换,杜绝手工凑值。
还有一点:切换时钟源会改变计数速率,如果在计数器运行中途切,当前这一次计数周期的实际时长是混合的。对精度要求高的场景,干脆先停用看门狗,改完配置再使能,代价是中间那一小段没有保护,要确认这段时间里系统处于可控状态。
4.4 停用看门狗:DS 置 1 之后 ENDINIT 依然有效
停用看门狗不是“一关了之”。操作本身要先解锁、进修改访问、清 ENDINIT,然后在 CON1 里把停用请求位置起来,最后恢复 ENDINIT。关掉之后 WDTxSR.DS 会指示停用状态,外部心跳引脚也就没有脉冲了。
关键认知是:即使看门狗被停用,ENDINIT 特性依然有效。这意味着你仍然需要按第 3.4 节的完整流程去访问受保护寄存器,只是不再需要周期喂狗了。有些项目为了省事在调试阶段把狗关了,结果后面发现某些寄存器还是写不进去,又回头查了半天——原因就在这里。
还有一种半停用状态值得注意:WDTxCON1.UR 置 1 之后,SMU 报警状态下看门狗会被阻止重载。这不是停用,而是“故意让它溢出”。它保证未处理的安全报警一定会引起 SMU 的硬件响应,而不是被软件悄悄喂掉。设计上我把它当成最后一道闸门,只有在确认整个报警处理链完整之后才打开。
5. 避坑排查:五条从 SMU 报警里挖出来的血泪经验
5.1 现象:清 ENDINIT 之后写受保护寄存器没反应
现象很典型:代码单步走完全正常,全速跑起来某些外设配置就是不对,读回来全是复位值,也没有任何异常上报。
原因是清 ENDINIT 的写操作和 ENDINIT 位真正变 0 之间存在时序差。清完之后立刻去写受保护寄存器,前几个写操作会在 ENDINIT 还是 1 的时候到达,被硬件直接忽略,不报错、不置标志。单步调试时因为指令间隔被拉长,恰好避开了这个问题,所以“调试通过、脱机失败”。
解决办法就是笔记里那句注释:清 ENDINIT 之后,在首次访问受保护寄存器之前,先读一次 ENDINIT 位,确认已经是 0 再往下走。我现在把这条写成宏,任何清 ENDINIT 的地方后面强制跟一次回读,谁也别想跳过。
5.2 现象:喂狗喂得越勤,复位来得反而越快
有一版代码把喂狗放进了 1 ms 的定时中断里,想着喂得勤总不会超时,结果复位频率明显上升。
原因是喂狗不是“写个值”那么简单,它是密码访问加修改访问两次寄存器写,而每次解锁都会让看门狗切进超时模式,靠随后的修改访问把 ENDINIT 置回去才退出。喂得太频繁,等于让看门狗长期在高频的解锁/上锁状态里切换;更要命的是如果喂狗中断本身抢占了正在执行受保护寄存器写的任务,两次解锁序列互相打断,密码序列或时间戳校验必然对不上,AE 置位,SMU 报警。
解决办法是把喂狗周期定在超时时间的 1/3 到 1/2,别贴着极限喂;喂狗动作本身要保证原子性,别让高优先级中断插在密码访问和修改访问中间。真要在中断里喂,就把这段临界区保护起来。
5.3 现象:接了调试器就不报警,脱机必炸
调试器连着的时候怎么跑都正常,拔掉跑十几分钟就复位,日志里什么都没有。
原因有两层。一是 Cerberus 模块的 CBS_OSTATE.ENIDIS 位,调试环境下它可能被置位,所有 ENDINIT 保护被整体停用,你的访问序列写成什么样都不会报错。二是调试器的断点会让 CPU 停下来,而看门狗的计数时钟未必跟着停,一旦超出范围就是溢出。这两层叠加,导致调试环境下的时序和真实运行完全不同。
解决办法是:涉及看门狗、ENDINIT、SMU 的验证,一律脱机跑,至少也要在调试器不设断点、不暂停的条件下跑足够长的时间;调试阶段确实需要停保护,就把 ENIDIS 的开关做成显式的、可编译期裁剪的宏,别让它悄悄生效。
5.4 现象:CPU1 跑起来了,它的看门狗却没生效
多核项目里很常见:CPU1 的核间任务正常执行,但故意让它死循环,系统居然不复位。
原因是复位后只有 CPU0 处于 RUN 状态、其 WDT 自动启动,CPU1 和 CPU2 初始在 HALT 状态,对应的 WDT 是停用的。你把 CPU1 唤醒了,不等于它的看门狗也被使能了,还得由 CPU1 自己去配置并使能——而且只能由它自己来做,跨核写别人的 WDTCPUyCON0 是访问不到的。
解决办法是在每个从核的启动流程里加一段显式的看门狗初始化,并在启动完成时用一个标志位确认“本核看门狗已使能”。验证方法是主动在从核里插一段死循环,看系统会不会在规定时间内复位;不复位就说明配置没生效。
5.5 现象:读回来的密码和写进去的永远不一样
写进去一个密码,读回来发现低几位全反了,以为是硬件坏了或者地址算错了。
原因就是那个反码设计:从 WDTxCON0.PW 读回时,PW[7:2] 是取反的,PW[15:8] 是原值。直接拿读回值当密码用,下一次访问必然失败,AE 置位。
解决办法是软件侧保存一份明文密码作为唯一真值,需要从寄存器读的时候,把读回值的低 6 位翻回来再用。另外还有个坑要注意:笔记里给出的外部心跳引脚映射中,CPU0 和 CPU1 的 LCK 状态标注落在了同一个端口位上,这种疑似笔误的细节不要照抄,落地前回查用户手册的端口复用表确认。
6. 用 LCK 心跳引脚做验证:把看门狗从“信它”变成“看得见”
软件层面验证看门狗最难的地方在于:它不工作时你没有任何反馈,等到它工作时系统已经复位了。这份笔记里有个很少被提起但极好用的设计——每个 WDTxCON0.LCK 位的当前状态可以选择性地输出到外部引脚。因为 LCK 只在看门狗服务期间(密码访问和修改访问之间)为低,所以这个引脚上会出现周期性的窄脉冲,等于给外部世界一个“我还活着”的心跳。典型映射是安全看门狗走 P20.9,CPU2 走 P20.6,CPU0 和 CPU1 的标注有重名,按手册核对后再用。
我现在的做法是:样机阶段至少把这一个心跳引脚引到测试点上,用逻辑分析仪按 10 秒窗口抓一次。判据有三条——脉冲周期是否稳定等于喂狗周期、脉宽是否稳定等于解锁到上锁的那几微秒、脉冲是否在你预期的时刻消失。
判据一:脉冲周期 ≈ 软件喂狗周期,抖动应在毫秒级以内 判据二:脉宽稳定,说明密码访问与修改访问之间没有被打断 判据三:故意在某任务里插入长延时,脉冲应先变稀再完全消失, 且消失时刻与 SMU 报警、复位时刻能对上第三条是关键。只观察“有脉冲”只能证明喂狗代码在跑,证明不了看门狗真的能兜住故障。必须主动注入故障:在某段任务里插一个可控的忙等,让喂狗点被跳过,然后看三个时间点是否自洽——最后一次心跳、SMU 报警置位、系统复位。这三个点对不上,说明中间某一层的配置和你的认知有偏差,比如超时时间算错了,或者 UR 位导致重载被阻止而你没意识到。
再往上走一层,是把时间戳检查(TCS=1)和代码序列检查用起来,做执行时间监控。做法是在关键任务的入口做一次检查访问、出口再做一次,配合 TCT 公差,能抓住“任务跑超时”和“任务被跳过”这两类问题。这比单纯喂狗强的地方在于:喂狗只能发现“没喂”,检查访问能发现“喂的时机不对”。代价是每次检查都要走一遍寄存器访问序列,开销要实测,别在微秒级的中断里塞这个。
最后是一个习惯问题,也是我从那次十分钟复位里学到的最实在的一条:从那以后,我每次碰 AURIX 的看门狗配置,都会强制走一遍“算周期 → 打表 → 脱机跑两倍超时时间 → 注入故障验证”这四步,一步都不省。省掉哪一步,后面就得用几倍的调试时间去还。这份笔记值钱的地方不在于它讲了多少寄存器,而在于它把 ENDINIT 窗口、密码反码、超时模式这些反直觉的细节都写在了明面上,照着走能少踩很多坑。希望帮到你。
本文还有配套的精品资源,点击获取