Files
wandering1 f754d59218 docs(测试说明): 按用途分六个子目录并同步全库引用
分层:流程与规范/ 单元测试/ 板级测试/ 专项验证/ 测试工具/ 测试数据/,
顶层只留新增的目录导航 README.md(归层判据 + 硬约束 + 本次处置记录)。

- 搬迁 28 个路径:测试数据三个目录 58 个文件为纯改名(R100,内容逐字节不变)
- 全库引用同步 124 个文件:AGENTS / docs 索引 / skills / 门禁注释 / scripts
- 门禁面:scripts/check_test_traceability.py 的 glob 改 rglob
  (分层后不递归扫描=追溯门禁直接瞎掉)
- scripts/rename_ref.py:收尾的 ℹ 提示在 GBK 代码页抛 UnicodeEncodeError,
  让进程以退出码 1 收场而改写其实已落盘(RC 与真实结果相反);改为把
  stdout/stderr reconfigure 成 UTF-8,与 check_doc_links.py 等同法
- docs/文档规范.md §1.1 补「子目录文件过多时再分一层」;§3.6 补 rename_ref
  搬整目录的两个实测坑:① 会改写目录内部文件的内容 ② 内部文件的 ../ 出链
  不补层级(本次 23 个被误改的证据文件已按 HEAD 原始字节逐字节还原)
- docs/README.md §6 改为与六个子目录一一对应,并补登原先漏登记的
  「启动日志原子性复验流程」一行
- .cubemx_survival_baseline.json:App/util/task_watchdog.c 的注释路径也随分层更新,
  该文件是存活审计的 watchdog-module 漂移锚点,按脚本自己的提示跑 --write 重建基线
  (实测重建后仅 watchdog-module 一条哈希变化)
- 铁律 4 例外(本次在任务树里改了单写者文件):AGENTS.md 与
  .pre-commit-config.yaml 只被 rename_ref 改写路径字面量(后者全在注释行),
  任务表块与钩子声明一字未动

验证:check_doc_links.py --sections-strict 覆盖全部 88 个改动 .md 全绿;
check_test_traceability.py 输出与主树逐行一致;196 个文本文件无 BOM、无新增
CR、无 U+FFFD、无行尾空白;陈旧引用面由主树 25 处降到 6 处(无新增断链)。
2026-10-30 07:16:07 +08:00

794 lines
42 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:Task Watchdog(IWDG + 任务心跳位图)
* 模块功能:把独立看门狗(IWDG)与"全部任务存活"绑定,使单任务死锁/饿死/
* 断言挂死可自动复位恢复,而非人工断电。
* 核心语义:喂狗 = "所有被监视任务都在抽样窗口内动过"。
* 适用平台:STM32F407ZGTx + FreeRTOS(CMSIS-RTOS V2)
* 作者:王建锋
* 创建日期:2026-09-29
* 修改记录:
* v1.0 2026-09-29 王建锋 创建:心跳位图 + 抽样判定 + IWDG 延后启动
* v1.1 2026-10-02 王建锋 第 7 轮纯取证埋点:近 8 次抽样位图环 + 最大喂狗
* 间隔统计 + 停喂现场落 /log/wdev.log(掉电不丢);
* 全部在 TASK_WATCHDOG_UNIT_TEST 守卫内,判定状态机
* 与主机侧单测逐字不变(见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)
* v1.2 2026-10-02 王建锋 第 8 轮取证:复位不丢的"进段打点"(.noinit 槽 +
* PREVRUN 开机上报 + STALL 行附 seg)——第 7 轮结论为
* "poller 自身停摆",需定位 defaultTask 主循环阻塞点
* v1.3 2026-10-02 王建锋 第 8b 轮取证:第二个 .noinit 槽"时间源存活性"
* (TIM7 ISR 每 1ms 写 uwTick/FreeRTOS tick/计数)+
* TICKRUN 开机上报(含相对进段时刻的增量 duw/dfr)——
* 第 8a 轮定位到 console 通道后,需钉死是 TIM7(HAL tick)
* 还是 SysTick(FreeRTOS tick) 停摆(见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)
*
* v1.5 2026-10-25 DTU 项目组 **停喂判定按位分账**(大窗判读 F2):stall/停喂的唯一
* 判据从"复合连续缺席窗数"改为"任一必需位的连续缺席窗数"
* (同一任务 800ms 没跑过才停喂申报复位);复合缺席窗仍照打
* "MISS start"(降级为诊断行,不再代表停喂)——根治"本窗缺 A、
* 下窗缺 B"式互不相干亚秒抖动被累计成复合 800ms 假停摆并停喂
* (2026-10-25 大窗 16:24/16:28 两起实证;真单任务停摆如
* 16:39 型 UI 单串 800ms+ 仍必报)。判据正文同步
* docs/子系统设计/看门狗设计说明.md §4;主机单测 TC-WATCHDOG-017/018
* v1.4 2026-10-02 王建锋 **P0 修复**:wd_platform_start() 补写 IWDG 的
* PR/RLR(= WD_IWDG_PRESCALER / WD_IWDG_RELOAD,即设计值
* /64 + 1249 → 1701~2498ms)。此前为"延后启动"删掉
* HAL_IWDG_Init() 时,一并丢掉了**唯一**写这两只寄存器的
* 路径 → 实机停在复位默认值 PR=0/RLR=4095(≈349~512ms),
* 比设计短约 5 倍 → 800ms 容忍分支成死代码、任何 >400ms
* 心跳缺口直接 IWDG 误复位(第 3~8 轮全部"空 STOR 必
* 复位"的共同根因)。SWD 现场改回设计值后同一用例 100% 通过
*
* 设计依据:docs/子系统设计/看门狗设计说明.md
* - §3.1 为何不用"每任务各自喂狗"(那只检测全系统同时挂死)
* - §3.3 为何用"单字位图 + 取走清零"(免临界区,天然原子)
* - §4.3 超时与窗口的取值推导(含 LSI 偏快 47kHz 的最坏情况校验)
* - §4.5 为何由 defaultTask 在 bring-up 完成后启动
* - §5.3 为何"停喂"而不直接 NVIC_SystemReset(保留 IWDGRSTF 权威归因)
*
* ⚠ 本模块不创建任何 FreeRTOS/CMSIS-RTOS 对象,可安全用于内核启动前后的上下文。
*
* === 可测性设计(2026-10-01)===
* 硬件访问(IWDG 寄存器读写 / HAL_GetTick)下沉到 task_watchdog.h 末段声明的
* 三个 __weak 钩子(wd_platform_feed / _start / _tick_ms);MCU 端默认实现见
* 文件末尾,行为与重构前逐字一致。判定状态机因此成为纯逻辑,主机侧单测
* (test/host/watchdog/)以强符号覆盖这三个钩子后即可离线穷举验证。
* 详见 docs/测试说明/单元测试/看门狗单元测试说明.md。
*/
#include "task_watchdog.h"
#include "main.h" /* HAL_GetTick()(HAL 时基 = TIM7,1ms) */
#include "iwdg.h" /* hiwdg 参数来源(CubeMX 生成,见 Src/iwdg.c) */
#ifndef TASK_WATCHDOG_UNIT_TEST
#include <string.h> /* strlen(取证行裸 UART 直发长度) */
#include "FreeRTOS.h" /* xTaskGetCurrentTaskHandle / TaskHandle_t(FTL 心跳路由) */
#include "task.h"
#include "lftpd.h" /* lftpd_task_wd_beat() 弱钩子声明(本文件提供强符号,见下方) */
#include "sys_log.h" /* SYSLOG_RUN(flush 诱导落盘)+ sys_log_evidence()(第 7 轮取证落盘) */
#include "console_log.h" /* dbg_console_tx_fault()(僵尸通道计数) */
/* 测试构建可见性(2026-10-25 台架实测缺陷):wd_required_mask() 里那条
* ENABLE_HW_LAYER_TESTS 分支要按"硬件层独占测试构建"剔除 netTask,而该宏定义在
* test/board/ch395f_test.h(由 test_config.h 拉入)。本文件此前没 include 它 →
* 宏在本 TU 永远不可见 → **那条分支自加入起就是死代码**,HW 层测试构建仍按产品
* 掩码 0x1F7 要求 netTask 供位,而 netTask 按设计自挂起 → 每 ~110ms 刷一条
* [ERR][WD] task stall(台架一轮刷出 468 条)。做法与 App/task/net_task.c 的
* #include "test_config.h" 一致;产品构建 TEST_SUITE_* 全注释、宏未定义,
* 走的仍是原分支(实测产品固件 Code/RO/RW/ZI 逐项不变)。
* 证据:boot log 20261025_050539(req=0x1F7 而 mask 最高只到 0x1F5)。 */
#include "test_config.h"
#endif
#define DBG_TAG "[WD]"
#include "dbg_log.h"
/* Private defines -----------------------------------------------------------*/
/* 抽样周期:defaultTask 主循环为 50ms 基准,100ms 是其整数倍(见 §4.3) */
#define WD_POLL_PERIOD_MS 100u
/* 连续未达标容忍次数:8 × 100ms = 800ms。
* 需容纳 NAND 擦写(最坏数百 ms)等长耗时操作,同时远小于 IWDG 最坏超时
* 1701ms,保证"停喂后仍能复上电"(§4.3 的包含关系) */
#define WD_MISS_TOLERANCE 8u
/* IWDG 超时参数:PR=4 → 64 分频;RLR=1249
* 标称 64 × 1249 / 32000 ≈ 2498 ms
* 最坏 64 × 1249 / 47000 ≈ 1701 ms(LSI 偏快时超时变短,按最坏校核)
* ⚠ 这两只值**必须写进寄存器**才生效——由 wd_platform_start() 在启动前写入
* (PR/RLR 的写访问需先写 KR=0x5555 解锁)。2026-10-02 之前无人写它们,
* 寄存器一直停在**复位默认值**(PR=0 → /4、RLR=4095 ≈ 349~512ms),
* 详见 wd_platform_start() 的函数说明。 */
#define WD_IWDG_PRESCALER 4u
#define WD_IWDG_RELOAD 1249u
/* IWDG 寄存器键值(RM0090 §21.4.4;KR 为只写寄存器,读回恒为 0) */
#define WD_IWDG_KEY_UNLOCK 0x00005555u /* 解锁 PR/RLR 写访问 */
#define WD_IWDG_KEY_FEED 0x0000AAAAu /* 重装计数器(喂狗) */
#define WD_IWDG_KEY_START 0x0000CCCCu /* 启动(此后不可停,只能靠复位) */
/* PVU/RVU 同步等待上界:PR/RLR 写入后需 ≤5 个 LSI 周期(≈0.15ms)同步到
* LSI 域,其间对应 PVU/RVU 位置 1 且**后续写会被丢弃**。此上界(168MHz 下
* 约 1ms)只为避免 LSI 异常时死等,正常路径远用不到。 */
#define WD_IWDG_SYNC_SPIN 0x10000u
/* Private types -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* 心跳位图:各任务只置位,校验者"取走并清零"。
* 单字读-改-写在 Cortex-M4 上天然原子,无需临界区(§3.3) */
static volatile uint32_t s_hb_bits;
static uint32_t s_miss_streak; /* 复合缺席连续窗数(饱和封顶)——自 v1.5 起仅作
* "MISS start" 诊断行的 episode 边沿检测,不再
* 参与停喂判定(停喂按 s_miss_bits 逐位分账) */
/* 按位分账(2026-10-25 大窗判读 F2):每个必需位"连续缺席抽样窗数"。位出现即清零,
* 缺席饱和到容忍值。停喂/stall 声明的唯一判据 = 任一必需位连击 >= WD_MISS_TOLERANCE,
* 即"同一个任务连续 8 个窗没跑过"。多任务各缺各的(本窗 A、下窗 B)各自清零,
* 不再串成复合 800ms 停喂——旧复合判据把互不相干的亚秒抖动累计成真停摆申报
* (2026-10-25 大窗 16:24/16:28 两起实证;判据正文 docs/子系统设计/看门狗设计说明.md §4)。 */
static uint8_t s_miss_bits[TASK_WD_ID_MAX];
static uint32_t s_miss_total; /* 累计未达标轮数(观测用) */
static uint32_t s_last_mask; /* 上次抽样到的位图(定位卡死任务用) */
static uint32_t s_started; /* 1 = IWDG 已启动 */
static uint32_t s_last_tick; /* 上次抽样时刻(100ms 节流基准) */
static uint32_t s_stall_evidenced; /* 1 = 本停摆 episode 已落盘证据(门闩;见 task_wd_poll) */
/* ==== 第 7 轮纯取证状态(仅 MCU 构建;判定状态机逐字不变)====
* 设计背景见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录:误复位后 RAM 态随复位消失,
* 故把"停喂现场"快照落盘(/log/wdev.log)供复位后回读定性。 */
#ifndef TASK_WATCHDOG_UNIT_TEST
#define WD_SNAP_HIST_DEPTH 8u
static uint32_t s_snap_hist[WD_SNAP_HIST_DEPTH]; /* 近 8 次抽样位图(环形,定位缺席位) */
static uint32_t s_snap_hist_idx; /* 环形写索引(写入后自增) */
static uint32_t s_last_feed_tick; /* 上次喂狗时刻(ms) */
static uint32_t s_max_feed_gap; /* 观测到的最大喂狗间隔(ms) */
static uint32_t s_feed_seen; /* 1 = 已记录首个喂狗时刻 */
/* ==== 第 8 轮取证:复位不丢的"进段打点"槽(.noinit → SRAM 保留)====
* IWDG/引脚复位不改变 SRAM 内容,故复位后仍能读出"上一轮 defaultTask 主循环
* 最后走到哪一段"。magic 用于区分"本模块写过"与上电时的随机值。
* 布局:[0]=magic [1]=段号 [2]=进段时刻(ms) [3]=循环序号
* ⚠ `zero_init` 必须与 `section` 同时给:只给 section 时 armcc 会把该段当
* "带初值 RW"(有 load 地址 → 启动 scatterload 会拷贝初值 = 抹掉保留内容);
* zero_init 使其成为 ZI,配合 scatter 的 UNINIT 执行区 → 启动**完全不碰**该段。 */
#define WD_SEG_MAGIC 0x57445347u /* 'WDSG' */
__attribute__((section(".noinit"), zero_init)) volatile uint32_t s_wd_seg_trail[4];
/* ==== 第 8b 轮取证:复位不丢的"时间源存活性"槽(.noinit → SRAM 保留)====
* 两个槽(均在 UNINIT_CCM,复位不清):
* s_wd_tick_cur [0]=magic [1]=uwTick(最新) [2]=本轮 TIM7 中断计数 [3]=保留
* —— TIM7 ISR 每 1ms 更新("本轮活动值")
* s_wd_tick_prev[0]=magic [1]=uwTick(上一轮最后) [2]=上一轮 TIM7 中断计数
* —— 由 task_wd_tick_snapshot() 在 main() 开头(**HAL_Init 之前**)拷一次
* ⚠ 为何必须在 HAL_Init 之前快照:TIM7 在 HAL_Init 里就 `HAL_TIM_Base_Start_IT`
* 开始中断,1ms 后就会把 cur 覆盖成本轮的值;而开机上报 task_wd_seg_report()
* 要等到 app_main_init(≈117ms)才跑——不先搬走,上一轮的终值就丢了
* (2026-10-02 台架实测:报告里的 uw 恒为 ~117 = 本轮开机时刻,而非上一轮终值)。
* 判读(duw = prev[1] - t,t = seg 槽的进段时刻):
* duw ≈ 0 → TIM7 与 defaultTask 同刻停摆(HAL 时基停摆)
* duw ≈ 2000+ → TIM7 活到复位(冻结不是 HAL 时基停摆,另有阻塞点)
* prev[2] → 上一轮 TIM7 总共跳了多少次(≈ 该轮存活毫秒数,可交叉核对)
* ⚠ 严禁在本 ISR 里调用任何 FreeRTOS API(含 xTaskGetTickCountFromISR):
* TIM7 远早于 xPortStartScheduler(),此时 port.c 的 ulMaxPRIGROUPValue 仍为 0
* → vPortValidateInterruptPriority() 的 configASSERT 必失败 → 断言打印在
* "USART1 尚未初始化"(huart1.Instance == NULL)的上下文里 → 卡死在 TIM7 ISR 内、
* 开机零输出(2026-10-02 台架实测:IPSR=0x47=TIM7、uwTick 冻结于 1)。
* 本探针只写 SRAM + 读 uwTick,无任何内核对象/API 依赖。 */
#define WD_TICK_MAGIC 0x5744544Bu /* 'WDTK' */
__attribute__((section(".noinit"), zero_init)) volatile uint32_t s_wd_tick_cur[4];
__attribute__((section(".noinit"), zero_init)) volatile uint32_t s_wd_tick_prev[4];
#endif
/* Private function prototypes -----------------------------------------------*/
static uint32_t wd_required_mask(void);
#ifndef TASK_WATCHDOG_UNIT_TEST
static void wd_feed_track(void);
static void wd_stall_evidence(uint32_t snapshot);
#endif
/* Private functions ---------------------------------------------------------*/
/*
* 函数功能:必需心跳位掩码(编译期按构建配置生成)
* 入口参数:无
* 返回值:本次构建要求"必须动过"的任务位集合
* 限定条件:无
* 函数说明:按 §4.1/§4.2 的设计,监视范围随构建配置变化:
* - testTask 仅在启用测试套件时创建(其不置心跳位,任何构建均不
* 入掩码;注意抽样为精确匹配,"会置位者"的位集合须恰等于掩码)
* - ENABLE_HW_LAYER_TESTS 下 netTask 入口自挂起(CH395F 由 testTask
* 独占)→ NET_READY 位永不置位 → ftpTask / uiTask / timeSyncTask
* 永久阻塞于屏障等待,但自 2026-10-02 起等待按 100ms 分片喂心跳
* → 该构建"会置位者"= default + ftp + ui + timesync(netTask 的
* 1s 挂起心跳无法持续供位,仍剔除;抽样为精确匹配,掩码必须恰等于
* 会置位的任务集合)(2026-10-02 台架:旧版"仅剔 NET 位 + 等待者
* 不置位"两种旧状态都会导致测试构建 IWDG 复位循环)
* - ftpTask 统一纳入(其主循环为 lftpd 事件循环):驱动侧节拍点
* 调用弱钩子 lftpd_task_wd_beat()(见 lftpd.h),本文件提供强
* 符号将其路由至 TASK_WD_FTP(2026-10-02 落地,台架 IWDG 复位
* 循环的根因修复:此前 FTP 位无埋点,产品构建必需掩码永不满足)
*/
static uint32_t wd_required_mask(void) {
uint32_t m = (1u << (uint32_t)TASK_WD_DEFAULT)
| (1u << (uint32_t)TASK_WD_NET)
| (1u << (uint32_t)TASK_WD_FTP)
| (1u << (uint32_t)TASK_WD_UI)
| (1u << (uint32_t)TASK_WD_TIMESYNC)
| (1u << (uint32_t)TASK_WD_ADC) /* adcTask 不依赖任何屏障,任何构建都置位 */
| (1u << (uint32_t)TASK_WD_IEC104) /* iec104Task 同 adcTask:任何构建都创建 */
| (1u << (uint32_t)TASK_WD_PROTECT);/* protectTask 同:判据节拍必须心跳 */
#if defined(ENABLE_HW_LAYER_TESTS)
/* 见函数说明:CH395F 硬件层测试构建 = default + 屏障等待者(等待分片喂位)
* + adcTask(其采集不依赖网络/存储屏障,任何构建都在置位) */
m = (1u << (uint32_t)TASK_WD_DEFAULT)
| (1u << (uint32_t)TASK_WD_FTP)
| (1u << (uint32_t)TASK_WD_UI)
| (1u << (uint32_t)TASK_WD_TIMESYNC)
| (1u << (uint32_t)TASK_WD_ADC)
| (1u << (uint32_t)TASK_WD_IEC104)
| (1u << (uint32_t)TASK_WD_PROTECT);
#endif
return m;
}
#ifndef TASK_WATCHDOG_UNIT_TEST
/*
* 函数功能:记录喂狗时刻并累计最大喂狗间隔(第 7 轮纯取证)
* 入口参数:无
* 返回值:无
* 限定条件:由 wd_platform_feed() 内调用(任意喂狗路径均经过)
* 函数说明:直接量化"停喂前喂狗节奏是否已变慢"——若 s_max_feed_gap 在停喂
* 前已 > 100ms,则属节奏性饥饿(喂狗周期被拉长)而非硬停摆。
* 只更新观测变量,不参与判定(判定状态机逐字不变)。
*/
static void wd_feed_track(void) {
uint32_t now = wd_platform_tick_ms();
if (s_feed_seen != 0u) {
uint32_t gap = now - s_last_feed_tick; /* 无符号差:回绕安全 */
if (gap > s_max_feed_gap) {
s_max_feed_gap = gap;
}
}
s_last_feed_tick = now;
s_feed_seen = 1u;
}
/*
* 函数功能:把停喂现场落盘到 /log/wdev.log(第 7 轮纯取证)
* 入口参数:snapshot - 本次抽样到的位图 uint32_t
* 返回值:无
* 限定条件:task_wd_poll 停喂分支(defaultTask 上下文)
* 函数说明:一行内含 snap/req(本次与必需掩码)、h(近 8 次抽样历史,定位
* 缺席位)、mg(最大喂狗间隔)、cf(console tx_fault 僵尸通道计数)。
* 经 sys_log_evidence 落盘(内部含 flush 诱导);只取证,不改判定。
* 串口 stall 行经僵尸通道可能丢失,故此处独立于串口留盘上证据。
*/
static void wd_stall_evidence(uint32_t snapshot) {
char hbuf[(WD_SNAP_HIST_DEPTH * 9u) + 1u];
char line[192];
unsigned i;
unsigned o = 0u;
int n;
/* 按时间顺序拼接历史(环形起点 = 最旧一条) */
for (i = 0u; i < WD_SNAP_HIST_DEPTH; i++) {
uint32_t v = s_snap_hist[(s_snap_hist_idx + i) % WD_SNAP_HIST_DEPTH];
o += (unsigned)snprintf(hbuf + o, sizeof(hbuf) - o,
(i == 0u) ? "%08X" : ",%08X", (unsigned)v);
}
n = snprintf(line, sizeof(line),
"STALL snap=0x%08X req=0x%08X h=%s mg=%lu cf=%lu seg=%lu",
(unsigned)snapshot, (unsigned)wd_required_mask(), hbuf,
(unsigned long)s_max_feed_gap,
(unsigned long)dbg_console_tx_fault(),
(unsigned long)s_wd_seg_trail[1]);
if (n > 0) {
/* 裸 UART 直发(不经环形缓冲/不依赖 defaultTask 排空):停喂窗口里
* 环形缓冲的排空本身可能停摆,裸发是本行证据的可靠通道(与盘上
* 证据互补——卷锁被阻塞方持有时盘上通道会失败,见 §0.5.4 判据缺口) */
/* 整行独占(道G task-26):本行两条裸发(正文 + CRLF),段间原本是缝;这行是
* 停喂判据的核心证据(盘上通道在卷锁被阻塞方持有时会失败,串口是唯一保底)。
* begin 非阻塞:拿不到也照发,停喂现场绝不能因为抢锁而丢。 */
(void)dbg_console_tx_begin();
dbg_console_write_raw(line, (unsigned)strlen(line));
dbg_console_write_raw("\r\n", 2u);
dbg_console_tx_end();
(void)sys_log_evidence(line);
}
}
/* ==================== 第 8 轮取证:复位不丢的"进段打点" ====================
* 见 task_watchdog.h 的说明(第 7 轮初判"poller 自身停摆"经第 8b 轮更正:
* 真因是 IWDG 超时被静默配成复位默认值,见 docs/子系统设计/看门狗设计说明.md
* 2026-10-02 变更记录)→ 需定位 defaultTask 主循环冻结在哪个调用点。
* 槽放 .noinit(scatter UNINIT_CCM):IWDG/引脚复位不改变 SRAM,复位后仍可读。 */
/*
* 函数功能:记录"即将进入的主循环段"到复位不丢的 .noinit 槽(第 8 轮取证)
* 入口参数:seg - 段号(task_wd_seg_t) uint32_t
* 返回值:无
* 限定条件:defaultTask 主循环调用(任意上下文亦可,纯 SRAM 写)
* 函数说明:写 [magic, 段号, 当前 ms, 循环序号];复位后由 task_wd_seg_report()
* 读出"上一轮最后进段"。纯 4 字写,无阻塞、无内核对象依赖。
*/
void task_wd_seg(uint32_t seg) {
s_wd_seg_trail[0] = WD_SEG_MAGIC;
s_wd_seg_trail[1] = seg;
s_wd_seg_trail[2] = wd_platform_tick_ms();
s_wd_seg_trail[3] = s_wd_seg_trail[3] + 1u;
}
/*
* 函数功能:开机快照上一轮的"时间源存活性"(第 8b 轮取证)
* 入口参数:无
* 返回值:无
* 限定条件:**必须在 HAL_Init() 之前调用**(main() 的 USER CODE BEGIN 1)
* 函数说明:把 s_wd_tick_cur(上一轮的终值,此刻尚未被本轮的 TIM7 中断覆盖)
* 搬到 s_wd_tick_prev,再把 cur 清零开始本轮累积。
* ⚠ 时序是本函数存在的唯一理由:TIM7 在 HAL_Init 里即开始 1ms 中断,
* 而开机上报要到 app_main_init(≈117ms)才跑——不先搬走,上一轮终值
* 会被本轮开机瞬间的 tick 覆盖(2026-10-02 台架实测踩坑)。
*/
void task_wd_tick_snapshot(void) {
if (s_wd_tick_cur[0] == WD_TICK_MAGIC) {
s_wd_tick_prev[0] = WD_TICK_MAGIC;
s_wd_tick_prev[1] = s_wd_tick_cur[1]; /* 上一轮最后一次 uwTick */
s_wd_tick_prev[2] = s_wd_tick_cur[2]; /* 上一轮 TIM7 中断总数 */
s_wd_tick_prev[3] = s_wd_tick_cur[3];
} else {
s_wd_tick_prev[0] = 0u; /* 上电首启:无上一轮数据 */
}
s_wd_tick_cur[0] = 0u; /* 本轮重新累积 */
s_wd_tick_cur[1] = 0u;
s_wd_tick_cur[2] = 0u;
s_wd_tick_cur[3] = 0u;
}
/*
* 函数功能:记录"时间源存活性"到复位不丢的 .noinit 槽(第 8b 轮取证)
* 入口参数:无
* 返回值:无
* 限定条件:TIM7 中断上下文调用(HAL_TIM_PeriodElapsedCallback 内,每 1ms)
* 函数说明:更新 s_wd_tick_cur = [magic, uwTick, 本轮中断计数, 0]。纯 3 字 SRAM 写
* + 读 uwTick,**不调用任何 FreeRTOS/CMSIS-RTOS API**——TIM7 在 HAL_Init
* 阶段即开始中断,远早于 xPortStartScheduler(),此时任何 ISR 版内核 API
* 都会触发 port.c 的优先级断言(ulMaxPRIGROUPValue 尚为 0)并把系统卡死
* 在"USART1 未初始化"的断言打印路径上(2026-10-02 台架实测根因)。
*/
void task_wd_tick_probe(void) {
s_wd_tick_cur[0] = WD_TICK_MAGIC;
s_wd_tick_cur[1] = wd_platform_tick_ms();
s_wd_tick_cur[2] = s_wd_tick_cur[2] + 1u;
}
/*
* 函数功能:开机上报上一轮最后进段 + 时间源存活性(第 8 轮取证)
* 入口参数:无
* 返回值:无
* 限定条件:启动早期调用(内核启动前亦可,裸 UART 直发)
* 函数说明:magic 命中才打一行 `PREVRUN seg=<段> seq=<序号> t=<ms>`(裸 UART,
* 不依赖环形缓冲写者),随后清 magic(一次复位只报一次)。
* 第 8b 轮:紧接着打 `TICKRUN uw= n= duw=`——uw 为 TIM7 最后一次记下的
* HAL tick、n 为 TIM7 中断计数、duw = uw - t(时间源是否活到复位)。
*/
void task_wd_seg_report(void) {
char m[128];
int n;
uint32_t t_seg;
if (s_wd_seg_trail[0] != WD_SEG_MAGIC) {
return; /* 上电首启或已被清:无上一轮轨迹 */
}
t_seg = s_wd_seg_trail[2];
/* 裸 UART 直发:启动早期环形缓冲写者尚未注册,不能走 DBG_* */
n = snprintf(m, sizeof(m), "PREVRUN seg=%lu seq=%lu t=%lu\r\n",
(unsigned long)s_wd_seg_trail[1],
(unsigned long)s_wd_seg_trail[3],
(unsigned long)t_seg);
if (n > 0) {
dbg_console_write_raw(m, (unsigned)strlen(m));
}
s_wd_seg_trail[0] = 0u; /* 一次复位只报一次 */
/* 第 8b 轮:时间源存活性(取自开机快照 prev,增量相对上一行的 t = 最后进段时刻)。
* duw≈0 → TIM7 与 defaultTask 同刻停摆(HAL 时基停摆);duw≈2000+ → TIM7 活到
* 复位(冻结另有阻塞点);n = 上一轮 TIM7 中断总数(≈该轮存活毫秒数)。 */
if (s_wd_tick_prev[0] == WD_TICK_MAGIC) {
uint32_t uw = s_wd_tick_prev[1];
n = snprintf(m, sizeof(m), "TICKRUN uw=%lu n=%lu duw=%lu\r\n",
(unsigned long)uw,
(unsigned long)s_wd_tick_prev[2],
(unsigned long)(uint32_t)(uw - t_seg));
if (n > 0) {
dbg_console_write_raw(m, (unsigned)strlen(m));
}
}
}
#endif /* !TASK_WATCHDOG_UNIT_TEST */
/* Public functions ----------------------------------------------------------*/
/*
* 函数功能:任务心跳置位
* 入口参数:id - 任务 ID(见 task_watchdog.h)
* 返回值:无
* 限定条件:可在任意上下文调用(单字写,无内核对象依赖)
* 函数说明:各任务在自己的主循环里调用。仅 1~2 条指令。
*/
void task_wd_beat(task_wd_id_t id) {
if ((uint32_t)id < (uint32_t)TASK_WD_ID_MAX) {
s_hb_bits |= (1u << (uint32_t)id);
}
}
/*
* 函数功能:启动 IWDG(延后启动,由 defaultTask 在 bring-up 完成后调用)
* 入口参数:无
* 返回值:无
* 限定条件:仅 defaultTask 调用;须在 osKernelStart 之后(本函数只用 HAL/寄存器)
* 函数说明:MX_IWDG_Init() 只把参数填进全局 hiwdg(Prescaler/Reload 由 CubeMX
* 在 Src/iwdg.c 生成区维护),**既不写寄存器也不启动**(见该文件
* IWDG-DEFER-START-ANCHOR 注释)。此处才真正启动——使"启动段不喂狗"
* 成立(§4.5)。IWDG 一旦启动不可停,只能靠复位终止。
* ⚠ PR/RLR 的写入由 wd_platform_start() 一并完成(v1.4 修复):
* 生成的 HAL_IWDG_Init() 被改名成空实现后它是唯一写这两只寄存器的路径,
* 否则寄存器停在复位默认值、超时只有 ~400ms(详见该函数说明)。
*/
void task_wd_start(void) {
if (s_started != 0u) {
return;
}
s_started = 1u;
/* 启动前先喂一次:让首个抽样窗口从"启动时刻"起算,
* 而非从 MX_IWDG_Init 的时刻起算(避免启动后立即超时) */
wd_platform_feed(); /* 重装计数器(喂狗) */
wd_platform_start(); /* 启动看门狗 */
DBG_INFO("[WD] armed: timeout ~2498ms (worst ~1701ms), miss_tol=800ms");
}
/*
* 函数功能:心跳抽样与喂狗判定(defaultTask 主循环调用)
* 入口参数:无
* 返回值:无
* 限定条件:仅 defaultTask 主循环调用(唯一喂狗者);内部节流到 100ms 一次
* 函数说明:取走并清零位图 → 与必需掩码比较(v1.5 起按位分账,判据推导见设计说明 §4):
* 全中 → 清零复合边沿与证据门闩并喂狗;
* 有缺但每位连击 < 容忍 → **继续喂狗**(单窗/亚秒级缺席是负载抖动,不是
* 停摆;只按位记账并在复合边沿打一条诊断行);
* 任一位连击 >= WD_MISS_TOLERANCE(同一任务 800ms 没跑过)= 真停摆 →
* 申报 [WD] task stall 并**停止喂狗**,让 IWDG 自然溢出复位
* (RCC_CSR 记权威 IWDGRSTF 而非 SFTRSTF —— §5.3)。
* ⚠ 判定故意不用"复合连续缺席窗数"当停喂门槛:那会把"本窗缺 A、下窗缺
* B"这类各自远低于容忍度的抖动串成假停摆(2026-10-25 大窗实证)。
*/
void task_wd_poll(void) {
uint32_t now;
uint32_t snapshot;
uint32_t mask_now;
uint32_t worst;
uint32_t i;
if (s_started == 0u) {
return; /* 未启动:不做任何判定 */
}
now = wd_platform_tick_ms();
if ((now - s_last_tick) < WD_POLL_PERIOD_MS) {
return; /* 节流:非 100ms 边界直接返回 */
}
s_last_tick = now;
snapshot = s_hb_bits; /* 读 */
s_hb_bits = 0u; /* 清:语义 = "自上次抽样以来谁动过" */
s_last_mask = snapshot;
#ifndef TASK_WATCHDOG_UNIT_TEST
/* 第 7 轮取证:留存近 8 次抽样位图(环形),停喂时随证据落盘 */
s_snap_hist[s_snap_hist_idx] = snapshot;
s_snap_hist_idx = (s_snap_hist_idx + 1u) % WD_SNAP_HIST_DEPTH;
#endif
mask_now = wd_required_mask();
/* 按位分账:位出现→该位清零;缺席→+1(饱和封顶)。掩码外的位不入账
* (监视范围随构建配置变化时,账本自动跟随,不会留旧账)。 */
worst = 0u;
for (i = 0u; i < (uint32_t)TASK_WD_ID_MAX; i++) {
if ((mask_now & (1u << i)) == 0u) {
s_miss_bits[i] = 0u;
continue;
}
if ((snapshot & (1u << i)) != 0u) {
s_miss_bits[i] = 0u;
} else if (s_miss_bits[i] < (uint8_t)WD_MISS_TOLERANCE) {
s_miss_bits[i]++;
}
if ((uint32_t)s_miss_bits[i] > worst) {
worst = (uint32_t)s_miss_bits[i];
}
}
if (snapshot == mask_now) {
s_miss_streak = 0u;
s_stall_evidenced = 0u; /* 恢复:允许下次停摆再落一次证据 */
wd_platform_feed(); /* 全员健康:喂狗 */
return;
}
/* 本窗有缺席位(复合缺席):记总数;**复合边沿**(上一窗全中)只打一条
* 诊断标记行——v1.5 起该行不再意味着停喂,停喂只看按位连击 */
s_miss_total++;
if (s_miss_streak == 0u) {
/* 复合缺席 episode 起点裸 UART 标记(每 episode 一条):证明 poller 仍在
* 抽样、并给出**首次**缺席位图——定位谁先停跑(第 7 轮出身)。⚠ v1.5 起
* 本行为**纯诊断**——复合边沿 ≠ 停喂,stall 判定权在下面的按位连击。 */
#ifndef TASK_WATCHDOG_UNIT_TEST
char m[96];
int mn = snprintf(m, sizeof(m),
"MISS start snap=0x%08X req=0x%08X t=%lu\r\n",
(unsigned)snapshot, (unsigned)mask_now,
(unsigned long)now);
if (mn > 0) {
dbg_console_write_raw(m, (unsigned)strlen(m));
}
#endif
}
if (s_miss_streak < WD_MISS_TOLERANCE) {
s_miss_streak++; /* 饱和封顶;仅供复合 episode 边沿 */
}
if (worst >= WD_MISS_TOLERANCE) {
/* 同一位连续缺席 8 窗(同一任务 800ms 没跑过)= 真停摆 → 停喂 → 约 2.5s
* 后 IWDG 复位。最后遗言走裸 UART(不依赖内核、不遍历指针、有界长度)。 */
DBG_ERROR("[WD] task stall: mask=0x%08X req=0x%08X miss=%lu",
(unsigned)snapshot,
(unsigned)wd_required_mask(),
(unsigned long)s_miss_total);
#ifndef TASK_WATCHDOG_UNIT_TEST
/* 第 7 轮取证:把现场落 /log/wdev.log(掉电不丢)。
* 串口 stall 行经僵尸通道可能丢失,故此处独立留盘上证据。
* ⚠ 本 episode 只写**一次**(s_stall_evidenced 门闩;注意 worst 连击达
* 上限后会**饱和**在容忍值,不能用它判"首次"):证据落盘经
* sys_log_evidence() 走 NAND 写 → FTL 路径调 nand_ftl_wd_feed()
* **直接喂狗**。若每轮 100ms 都写,等于在"停喂"窗口里持续喂狗 →
* IWDG 永不溢出 → 停喂语义被证据机制自身抵消(2026-10-03 台架 V2
* 实测:stall 已声明、miss 累至 1157≈115s 仍不复位,见
* docs/测试说明/板级测试/实机测试实施指南.md §4)。写一次即可留证,此后不再
* 触碰 NAND,恢复"停喂 → IWDG 溢出复位"(§5.3 的权威归因)。 */
if (s_stall_evidenced == 0u) {
s_stall_evidenced = 1u;
wd_stall_evidence(snapshot);
}
#endif
} else {
/* 有缺席位、但每一位连击都未到容忍值(含"本窗缺 A、下窗缺 B"的交替型):
* 照常喂狗——多任务各自亚秒抖动不再串成复合停喂(按位分账收口的正身,
* 2026-10-25 大窗 16:24/16:28 型事件的根治)。 */
wd_platform_feed();
}
}
/*
* 函数功能:读上次抽样到的位图(观测/自检用)
*/
uint32_t task_wd_last_mask(void) {
return s_last_mask;
}
/*
* 函数功能:读累计未达标轮数(观测/自检用)
*/
uint32_t task_wd_miss_count(void) {
return s_miss_total;
}
/*
* 函数功能:查询 IWDG 是否已启动(观测/自检用)
*/
uint32_t task_wd_is_armed(void) {
return (s_started != 0u) ? 1u : 0u;
}
/* Platform layer (MCU defaults, weak) ---------------------------------------*/
/*
* 以下三个 __weak 为 MCU 端默认实现,行为与重构前逐字一致。
* 主机侧单测提供同名强符号覆盖(链接器优先取强符号)以实现离线可观测。
* 声明见 task_watchdog.h 末段"平台后端(内部)";详见
* docs/测试说明/单元测试/看门狗单元测试说明.md。
*
* 主机侧单测(同编译单元 #include 本文件)需回避这组默认定义与自身桩重名,
* 故由 TASK_WATCHDOG_UNIT_TEST 守卫:该宏下不编入默认实现,仅留桩。
*/
#ifndef TASK_WATCHDOG_UNIT_TEST
/*
* 函数功能:重装 IWDG 计数器(喂狗)——MCU 端默认实现
* 入口参数:无
* 返回值:无
* 函数说明:写 IWDG->KR = WD_IWDG_KEY_FEED(0xAAAA)。100ms 级高频路径,
* 故不经 HAL 封装。
*/
__weak void wd_platform_feed(void) {
IWDG->KR = WD_IWDG_KEY_FEED; /* 重装计数器(喂狗) */
wd_feed_track(); /* 第 7 轮取证:记录喂狗时刻/最大间隔 */
}
/* ==== 为什么 IWDG 参数必须在这里写(2026-10-02 台架实测,P0 根因)====
* `Src/iwdg.c` 为实现"延后启动"把生成的 `HAL_IWDG_Init(&hiwdg)` 改名成空实现
* (见该文件 `USER CODE BEGIN 0` 里的 IWDG-DEFER-START-ANCHOR;2026-10-09 起
* 不再靠"每次重生成后删掉那一句"),而 `HAL_IWDG_Init()` 是 HAL 里**唯一**会把
* `hiwdg.Init.Prescaler/Reload` 写进寄存器的路径——改名后 `MX_IWDG_Init()`
* 只填结构体,PR/RLR 保持**复位默认值**(PR=0 → /4、RLR=4095)。
* 实测读数:`IWDG->PR = 0`、`IWDG->RLR = 4095` → 实际超时 4×4096/LSI
* ≈ 349~512ms,比设计的 1701~2498ms **短约 5 倍**。后果有二:
* ① `WD_MISS_TOLERANCE`(8×100ms=800ms)**永远等不到**就被 IWDG 复位
* → 停喂判定分支成为死代码(这正是第 7 轮观察到"STALL 从未执行"
* 的原因,当时误判为"poller 自身停摆");
* ② 任何 >400ms 的心跳缺口都直接 IWDG 误复位。空 STOR(0 字节截断)
* → dhara/FTL flush 使 NET/FTP/UI 心跳缺 ~400ms → 复位(第 3~8 轮
* 全部"空 STOR 必复位"的共同根因,P0-B 出口阻塞点)。
* 实测验证:SWD 现场把 PR/RLR 改成 4/1249 后,同一个空 STOR 由 5/5 复位
* 变为 100% 成功(`226 Closing data connection.`)。
* ==================================================================== */
/*
* 函数功能:按设计参数配置并启动 IWDG——MCU 端默认实现
* 入口参数:无
* 返回值:无
* 函数说明:解锁 PR/RLR → 写 WD_IWDG_PRESCALER / WD_IWDG_RELOAD(/64 + 1249
* → 1701~2498ms)→ 写 0xCCCC 启动(不可逆,只能靠复位停止)。
* 为何必须在此配置、否则超时短 5 倍,见上方长注释。
* ⚠ 顺序不可省:PR/RLR 有写保护,须先写 0x5555 解锁;每只寄存器写后要等
* `SR.PVU`/`SR.RVU` 清零(配置同步到 LSI 域,≤5 个 LSI 周期),否则**后写
* 的那只会被静默丢弃**(2026-10-02 SWD 实测:不等同步直接连写,RLR 未生效)。
*/
__weak void wd_platform_start(void) {
uint32_t spin;
IWDG->KR = WD_IWDG_KEY_UNLOCK; /* 解锁 PR/RLR 写访问 */
IWDG->PR = WD_IWDG_PRESCALER; /* 4 → /64 */
spin = WD_IWDG_SYNC_SPIN;
while (((IWDG->SR & IWDG_SR_PVU) != 0u) && (spin != 0u)) {
spin--; /* 等 PVU 清零:PR 已同步到 LSI 域 */
}
IWDG->RLR = WD_IWDG_RELOAD; /* 1249 */
spin = WD_IWDG_SYNC_SPIN;
while (((IWDG->SR & IWDG_SR_RVU) != 0u) && (spin != 0u)) {
spin--; /* 等 RVU 清零:RLR 已同步到 LSI 域 */
}
IWDG->KR = WD_IWDG_KEY_START; /* 启动看门狗(不可逆) */
}
/*
* 函数功能:读单调毫秒时基——MCU 端默认实现
* 入口参数:无
* 返回值:HAL 时基毫秒计数(32 位回绕安全,调用方用无符号差比较)
* 函数说明:HAL 时基 = TIM7(1ms),非 SysTick。
*/
__weak uint32_t wd_platform_tick_ms(void) {
return (uint32_t)HAL_GetTick();
}
/*
* 函数功能:lftpd 看门狗心跳钩子——MCU 端强符号实现
* 入口参数:无
* 返回值:无
* 限定条件:无(IWDG 启动前置位只写位图,不影响判定状态机)
* 函数说明:ftpTask 主循环 = lftpd 事件循环 / 传输等待点(埋点见 lftpd.c /
* lftpd_inet.c);驱动层只调用弱钩子 lftpd_task_wd_beat()
* (声明于 lftpd.h,弱空实现于 lftpd_inet.c),在此路由到任务
* 心跳位图的 FTP 位,使驱动层不向上依赖应用层(分层铁律)。
* 强符号覆盖同名弱实现(与 wd_platform_* 钩子同机制)。
* 背景:2026-10-02 台架实机——FTP 位无埋点 → 必需掩码永不满足
* → IWDG 启动后 800ms 停喂 → 约每秒一次 IWDG 复位、无限重启,
* 设备无法启动(P0-B 基线测试失败根因)。
*/
void lftpd_task_wd_beat(void) {
task_wd_beat(TASK_WD_FTP);
}
/* FTL 心跳路由表:任务句柄(0 = 未登记),下标 = 任务 ID */
static TaskHandle_t s_wd_bind[(uint32_t)TASK_WD_ID_MAX];
/*
* 函数功能:NAND FTL 看门狗心跳钩子 —— MCU 端强符号实现
* 入口参数:无
* 返回值:无
* 限定条件:任意上下文可调(内核启动前 / 未登记任务为空操作)
* 函数说明:gd5f_wait_busy 的长耗时轮询(块擦除等待最长 5 s、GC 页拷贝、
* 日志写)都运行在调用者任务上下文(default/ftp/ui/timesync 等),
* 此处按 task_wd_bind() 登记的句柄路由到调用者任务的心跳位,
* 防止长耗时窗口心跳缺席导致 IWDG 误复位。
* 背景:2026-10-02 台架——0 字节 STOR 截断 → dhara 日志块 GC
* (> 2 s,期间无埋点)→ FTP 位缺席 800 ms → 停喂 → IWDG 误复位。
* testTask 刻意不绑定:它不在必需掩码内(抽样为精确匹配),
* 其上下文置位会引入多余位反而误杀。
*/
void nand_ftl_wd_beat(void) {
TaskHandle_t h;
uint32_t i;
h = xTaskGetCurrentTaskHandle();
if (h == 0) {
return; /* 内核启动前:IWDG 未启动,无需心跳 */
}
for (i = 0u; i < (uint32_t)TASK_WD_ID_MAX; i++) {
if ((s_wd_bind[i] != 0) && (s_wd_bind[i] == h)) {
task_wd_beat((task_wd_id_t)i);
return;
}
}
/* 未登记任务(如 testTask):不置任何位(精确匹配抽样禁多余位) */
}
/*
* 函数功能:把当前任务句柄登记进 FTL 心跳路由表
* 入口参数:id - 任务 ID
* 返回值:无
* 限定条件:任务上下文调用(内核启动后)
* 函数说明:default/net/ftp/ui/timesync 五任务入口各调一次;
* 每任务 ID 一个句柄槽,重复登记覆盖(幂等)。
*/
void task_wd_bind(task_wd_id_t id) {
s_wd_bind[(uint32_t)id] = xTaskGetCurrentTaskHandle();
}
/*
* 函数功能:NAND FTL 看门狗直接喂狗钩子 —— MCU 端强符号实现
* 入口参数:无
* 返回值:无
* 限定条件:任意上下文可调(IWDG 未启动时为空操作)
* 函数说明:心跳钩子只维持心跳位,喂狗动作在 task_wd_poll(defaultTask 主
* 循环)。长耗时 FTL 操作(GC/整卷格式化)发生在 defaultTask 上下文
* 时,主循环阻塞在 FTL 调用内 → 喂狗点停摆 → 心跳位再新也无人喂 →
* IWDG 误复位(2026-10-02 台架:console tx fault 条目落盘触发 GC,
* poller 饥饿 ~2 s → IWDG 复位、无 stall 日志)。此处让 FTL 长路径
* 在操作进行中直接写 IWDG->KR,把"喂狗点自身被阻塞"的误复位形态
* 一并覆盖。语义边界(显式声明):NAND 侧真实卡死(wait_busy 自旋
* 超时后 dhara 恢复重试再卡死)时本路径持续喂狗 = 设备存活但功能
* 冻结(与 console ORE 自愈同类边界,V5 长跑观察)。
*/
void nand_ftl_wd_feed(void) {
if (s_started != 0u) {
wd_platform_feed();
}
}
#endif /* !TASK_WATCHDOG_UNIT_TEST */
/* Test-only hooks (compiled out unless TASK_WATCHDOG_UNIT_TEST) --------------*/
/*
* 函数功能:复位判定状态机到"刚上电"(仅主机侧单元测试使用)
* 入口参数:无
* 返回值:无
* 限定条件:仅当定义 TASK_WATCHDOG_UNIT_TEST 时存在
* 函数说明:把 s_started / 位图 / 计数 / 节流基线全部清零,使各用例互相独立。
* MCU 构建不定义该宏 → 零代码、零符号。
*/
#ifdef TASK_WATCHDOG_UNIT_TEST
void task_wd_test_reset(void) {
uint32_t i;
s_hb_bits = 0u;
s_miss_streak = 0u;
for (i = 0u; i < (uint32_t)TASK_WD_ID_MAX; i++) {
s_miss_bits[i] = 0u; /* 按位账本一并清零(v1.5 判定输入) */
}
s_miss_total = 0u;
s_last_mask = 0u;
s_started = 0u;
s_last_tick = 0u;
s_stall_evidenced = 0u;
}
#endif