Files
STM32F4-Base/App/task/adc_task.c
T
wandering1 d59c2198eb feat(adc): L1 采集层落地——STM32 ADC1 扫描 + 语义通道映射
新增 App/algo/adc_input.c/h(L1 采集与预处理层,落实 L1-3/L1-4):
- L1-4 语义通道映射表 k_adc_map[]:UA/UN/IA → TPAFE buf[6]/[5]/[2];
  UB/UC/IB/IC/IN/TEMP → STM32 ADC1 CH14/3/6/1/2/5。消费侧一律经
  adc_input_raw() 按语义取数,不写死下标(V0.2 改版只改表内容)。
- L1-3 STM32 ADC1 9 通道软件触发顺序扫描:HAL_ADC_Start + 逐通道
  PollForConversion/GetValue(EOC_SINGLE_CONV + 无 DMA,可逐通道轮询)。
- adc_input_poll() 统一"TPAFE 原子快照 + STM32 扫描";
  adc_input_init() 做映射自检(STM32 映射通道必落在扫描序列内,否则
  返回 -1),挡住"映射到不扫描的通道 → 恒读陈旧值"的静默错。

集成:
- adc_task.c 取数改走 L1 层(首帧日志补全 8 路 + 温度;心跳增
  adc1_rc/adc1_err 观测 STM32 侧健康度)。
- app_main.c 在 tpafe5160_init() 之后调 adc_input_init(),失败记
  SYSLOG_SELFTEST("ADC map fault")。
- Keil 工程挂 adc_input.c 并加 ../App/algo 包含路径。
- 文档同步:继电保护规划 §1.2/§3.1(L1-3/L1-4 改 ✅)、ADC通道映射说明
  §7/§8/§9、产品需求 §5.9/§9、架构总览 §7、AGENTS.md、App/README.md、
  App/task/README.md、新增 App/algo/README.md、boot_log_data 清单。

验证:
- @build 0 错 0 警;镜像无源文件新于 hex。
- 台架 @test PASS:ADC input layer init OK、adc1_rc=0/adc1_err=0
  (30 s ≈27 万次转换零超时)、seq 仍 1000.0 Hz、adc 栈峰值
  528→584 B(余 71%)、回读一致、零 [ERR]/HardFault。
  证据 docs/测试说明/boot_log_data/20261010_prod_adc_l1_boot_log.txt
- 内存快照已刷新(Flash 269,404→270,712 B)。
2026-10-10 16:06:31 +08:00

159 lines
8.2 KiB
C
Raw 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.
/*
* 模块名称: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);
}
}