/* * 模块名称:ADC Sampling Task * 模块功能:ADC 采集消费——TPAFE5160 快照 + STM32 ADC1 顺序扫描(经 L1 层 * App/algo/adc_input.c 按语义取数)+ 帧统计(节拍由 TIM6 硬件定时器提供) * 状态说明:**B1 已落地(2026-10-10)**——本任务从空桩转为真实消费者: * 用 tpafe5160_snapshot() 原子取数(AGENTS 铁律),统计帧数/无新帧次数, * 并纳入看门狗 required_mask(TASK_WD_ADC)。 * **采样节拍(v2.2,2026-10-10)**:由 **TIM6 更新中断(1 kHz)** 硬件触发—— * ISR 内调 tpafe5160_start_conv_irq()(Src/main.c 的 * HAL_TIM_PeriodElapsedCallback);本任务**不再自行触发**,退化为**纯消费者**, * 1ms 轮询(osDelayUntil)仅作消费节奏。详见 * docs/子系统设计/ADC采样节拍设计说明.md。 * **L1 采集层(v2.3,2026-10-10)**:取数改走 App/algo/adc_input.c—— * 本任务每拍调 adc_input_poll()(TPAFE 快照 + STM32 ADC1 9 通道扫描), * 经 adc_input_raw() **按语义**读通道(L1-3/L1-4),不再直接触碰驱动缓冲。 * V0.1 上只有 TPAFE 三路(UA/UN/IA)测量有效;本任务只取数,不做保护运算。 * 接入前必读:取数只用 adc_input_raw() / 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 * v2.1 2026-10-10 王建锋 日志门控:首帧/周期统计仅在 dbg_console_writer_ready() * 为真后落日志。写者未注册时控制台走无锁直发,与 * defaultTask 的存储初始化日志字节级交错(台架实测); * 就绪前挂起上报,就绪后补报最近一帧。 * v2.2 2026-10-10 王建锋 采样节拍改由 TIM6 更新中断(1 kHz)硬件触发;本任务 * 删除自行触发,退化为纯消费者(见文件头"采样节拍")。 * v2.3 2026-10-10 王建锋 L1-3/L1-4:取数改走 App/algo/adc_input.c(语义映射 + * STM32 ADC1 9 通道扫描);首帧日志补全 8 路电气量 + 温度, * 心跳日志增 adc1_rc / adc1_err 观测 STM32 侧健康度。 */ #include "adc_task.h" #include "cmsis_os.h" #include "adc_input.h" /* L1 采集层:adc_input_poll()/adc_input_raw()(按语义取数) */ #include "tpafe5160.h" #include "task_watchdog.h" #include "console_log.h" #define DBG_TAG "[ADC_TASK]" #include "dbg_log.h" /* 消费轮询周期(ms)。采样节拍由 TIM6(1 kHz)提供,本周期仅作消费节奏 */ #define ADC_TASK_BEAT_MS 1U /* 心跳日志周期(ms):周期性上报帧统计,作为采集链路存活的观测面 */ #define ADC_TASK_REPORT_MS 10000U /* 统计:已消费帧数 / 无新帧次数(首拍必为无新帧,之后应稳定为 0 增量)。 * 原始码值缓冲由 L1 层 App/algo/adc_input.c 持有(消费侧按语义取数,不写死下标) */ 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 采集一拍(adc_input_poll():TPAFE 原子快照 + STM32 ADC1 9 通道 * 顺序扫描)并统计。采样节拍由 TIM6 硬件定时器提供(ISR 内触发 CONVST), * 本任务不再触发。稳态下 consumed 每拍 +1、idle 不再增长;idle 持续增长 = * EXTI 未在读帧或 TIM6 未启动(异常),由周期心跳日志暴露。 */ 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) 采集一拍:L1 层 adc_input_poll() = TPAFE 原子快照 + STM32 ADC1 顺序扫描 * (仅任务上下文;TPAFE 侧 1=有新帧 0=无新帧 -1=参数错)。 * 采样节拍由 TIM6 更新中断提供(ISR 内触发 CONVST),本任务不触发。 */ r = adc_input_poll(); if (r == 1) { s_frames_consumed++; } else if (r == 0) { s_frames_idle++; } else { /* r == -1:参数错。调用点 count 为常量,不可达;不计入 idle 以区分异常 */ } /* 2) 首帧码值上报:**写者就绪后**才落日志。写者未注册时 con_append 走 * 无锁直发路径,与 defaultTask 的存储初始化日志会字节级交错 * (2026-10-10 台架实测);此时挂起上报,就绪后补报最近一帧。 * 8 路电气量 + 温度全部经 adc_input_raw() **按语义**取数(L1-4), * 用于验证"语义 → 物理通道"映射与两片转换器取数契约 */ if ((first_logged == 0u) && (s_frames_consumed != 0u) && (dbg_console_writer_ready() != 0u)) { int32_t ua = 0, ub = 0, uc = 0, un = 0; int32_t ia = 0, ib = 0, ic = 0, in = 0, temp = 0; (void)adc_input_raw(ADC_SEM_UA, &ua); (void)adc_input_raw(ADC_SEM_UB, &ub); (void)adc_input_raw(ADC_SEM_UC, &uc); (void)adc_input_raw(ADC_SEM_UN, &un); (void)adc_input_raw(ADC_SEM_IA, &ia); (void)adc_input_raw(ADC_SEM_IB, &ib); (void)adc_input_raw(ADC_SEM_IC, &ic); (void)adc_input_raw(ADC_SEM_IN, &in); (void)adc_input_raw(ADC_SEM_TEMP, &temp); first_logged = 1u; DBG_INFO("first: UA=%ld UB=%ld UC=%ld UN=%ld IA=%ld IB=%ld IC=%ld IN=%ld T=%ld", (long)ua, (long)ub, (long)uc, (long)un, (long)ia, (long)ib, (long)ic, (long)in, (long)temp); } /* 3) 看门狗心跳(本任务已纳入 required_mask) */ task_wd_beat(TASK_WD_ADC); /* 4) 周期心跳日志:帧统计 + 驱动侧 FRSTDATA 错误计数。 * 写者就绪前抑制(同 2) 的交错理由);到点无论是否落日志都推进下个上报点 */ { uint32_t now = osKernelGetTickCount(); if ((int32_t)(now - report_at) >= 0) { if (dbg_console_writer_ready() != 0u) { DBG_INFO("frames=%lu idle=%lu seq=%lu frst_err=%u adc1_rc=%d adc1_err=%lu", (unsigned long)s_frames_consumed, (unsigned long)s_frames_idle, (unsigned long)tpafe5160_frame_seq(), (unsigned)tpafe5160_frstdata_err(), adc_input_stm32_rc(), (unsigned long)adc_input_stm32_errs()); } report_at = now + ADC_TASK_REPORT_MS; } } /* 5) 等下一个节拍(固定周期;落后则立即补齐,不累积漂移) */ next += ADC_TASK_BEAT_MS; (void)osDelayUntil(next); } }