/* * 模块名称:ADC Sampling Task * 模块功能:TPAFE5160 8 通道同步采样——1ms 采样节拍 + 原子快照消费 + 帧统计 * 状态说明:**B1 已落地(2026-10-10)**——本任务从空桩转为真实消费者: * 每 1ms 触发一次 CONVST,用 tpafe5160_snapshot() 原子取数(AGENTS 铁律), * 统计帧数/无新帧次数,并纳入看门狗 required_mask(TASK_WD_ADC)。 * ⚠ B1 阶段用**任务周期软触发**(osDelayUntil)产生采样节拍,满足 L1-1 * 判据(BUSY 连续脉冲 + frame_seq 持续递增);**精确 1 kHz / 等周波调频** * 的硬件定时器触发为后续(需 CubeMX 加 TIM)。 * V0.1 上只有 TPAFE 三路(UA/UN/IA)测量有效;本任务只取数,不做保护运算。 * 接入前必读:取数只用 tpafe5160_snapshot()(临界区原子快照,仅任务上下文; * 旧的 get_buf/data_ready/clear_ready 已删除);接消费者前的强制门禁 * 见根 AGENTS.md「接 ADC / RS-485 消费者前的强制门禁」+ 建任务前后各跑 * scripts/stack_report.py 做栈预算。 * 适用平台:STM32F407ZGTx + TPAFE5160 * 作者:王建锋 * 创建日期:2026-07-19 * 修改记录: * v1.0 2026-07-19 王建锋 创建初始版本 * v1.1 2026-10-05 王建锋 P3-11:注明"预留桩(未启用)",避免误认为已实现 * v2.0 2026-10-10 王建锋 B1:实现真实消费者(1ms 节拍 + 原子快照 + 帧统计 + * 看门狗心跳),由 Src/freertos.c 创建为 adcTask */ #include "adc_task.h" #include "cmsis_os.h" #include "tpafe5160.h" #include "task_watchdog.h" #define DBG_TAG "[ADC_TASK]" #include "dbg_log.h" /* 采样节拍周期(ms)。B1:任务周期软触发;硬件定时器方案见文件头说明 */ #define ADC_TASK_BEAT_MS 1U /* 心跳日志周期(ms):周期性上报帧统计,作为采集链路存活的观测面 */ #define ADC_TASK_REPORT_MS 10000U /* 最新一帧原始码值(8 通道;语义映射见 docs/硬件平台/ADC通道映射说明.md) */ static int16_t s_frame[TPAFE5160_CH_NUM]; /* 统计:已消费帧数 / 无新帧次数(首拍必为无新帧,之后应稳定为 0 增量) */ static uint32_t s_frames_consumed; static uint32_t s_frames_idle; /* * 函数功能:ADC 任务初始化 * 入口参数:无 * 返 回 值:0 - 成功,-1 - 失败 * 限定条件:tpafe5160_init() 已在 app_main_init()(内核启动前)完成 * 函数说明:驱动初始化不在本任务(见限定条件);此处仅清帧统计。 */ int adc_task_init(void) { s_frames_consumed = 0u; s_frames_idle = 0u; DBG_INFO("adc_task_init"); return 0; } /* * 函数功能:ADC 任务主函数 * 入口参数:arg - FreeRTOS 传入参数(未使用) * 返 回 值:无 * 限定条件:adc_task_init() 已调用;内核已启动 * 函数说明:每 1ms 触发一次 CONVST(采样节拍),随后原子快照取最新一帧并统计。 * 首拍无帧属正常(本次转换尚未完成);之后每拍消费上一拍触发的帧, * 故稳态下 consumed 每拍 +1、idle 不再增长。idle 持续增长 = EXTI 未在 * 读帧(异常),由周期心跳日志暴露。 */ void adc_task_func(void *arg) { uint32_t next = osKernelGetTickCount(); uint32_t report_at = next + ADC_TASK_REPORT_MS; uint8_t first_logged = 0u; (void)arg; for (;;) { int r; /* 1) 采样节拍:触发一次 CONVST(B1:任务周期软触发) */ tpafe5160_start_conv_irq(); /* 2) 原子快照消费最新一帧(仅任务上下文;1=有新帧 0=无新帧 -1=参数错) */ r = tpafe5160_snapshot(s_frame, (uint8_t)TPAFE5160_CH_NUM); if (r == 1) { s_frames_consumed++; if (first_logged == 0u) { first_logged = 1u; /* 只报 TPAFE 三路(UA=buf[6] UN=buf[5] IA=buf[2]),验证取数契约 */ DBG_INFO("first frame: UA=%d UN=%d IA=%d", s_frame[6], s_frame[5], s_frame[2]); } } else if (r == 0) { s_frames_idle++; } else { /* r == -1:参数错。调用点 count 为常量,不可达;不计入 idle 以区分异常 */ } /* 3) 看门狗心跳(本任务已纳入 required_mask) */ task_wd_beat(TASK_WD_ADC); /* 4) 周期心跳日志:帧统计 + 驱动侧 FRSTDATA 错误计数 */ { uint32_t now = osKernelGetTickCount(); if ((int32_t)(now - report_at) >= 0) { DBG_INFO("frames=%lu idle=%lu seq=%lu frst_err=%u", (unsigned long)s_frames_consumed, (unsigned long)s_frames_idle, (unsigned long)tpafe5160_frame_seq(), (unsigned)tpafe5160_frstdata_err()); report_at = now + ADC_TASK_REPORT_MS; } } /* 5) 等下一个节拍(固定周期;落后则立即补齐,不累积漂移) */ next += ADC_TASK_BEAT_MS; (void)osDelayUntil(next); } }