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)。
This commit is contained in:
2026-10-10 16:06:31 +08:00
parent b80f59cc5d
commit d59c2198eb
16 changed files with 550 additions and 38 deletions
+1
View File
@@ -16,6 +16,7 @@ FreeRTOS/CMSIS-RTOS API**(见 `AGENTS.md`「平台约束」)。
| `app/` | **应用入口与全局**:`app_main.c/h`(初始化入口)、`app_cfg.h`(编译开关)、`sys_events.h`(就绪事件) | [`app/README.md`](app/README.md) |
| `cfg/` | **配置引擎**:`/config.ini` 解析 + 原子持久化(旧二进制档已废弃,2026-10-10) | [`cfg/README.md`](cfg/README.md) |
| `sync/` | **时间同步任务**:`time_sync.c/h` | [`sync/README.md`](sync/README.md) |
| `algo/` | **采集与算法层(L1/L2)**:`adc_input.c/h`(语义通道映射 + STM32 ADC1 扫描) | [`algo/README.md`](algo/README.md) |
| `log/` | **日志子系统**:syslog(NAND)+ 控制台单写者日志 | [`log/README.md`](log/README.md) |
| `task/` | **FreeRTOS 任务实现**(6 个任务) | [`task/README.md`](task/README.md) |
| `util/` | **通用工具**:CRC / 环形缓冲 / 墙钟时间 | [`util/README.md`](util/README.md) |
+27
View File
@@ -0,0 +1,27 @@
# App/algo/ — 采集与算法层(L1 / L2)
> **自研代码**,属 `docs/算法/继电保护功能复现规划.md` §2.1 分层中的 **L1(采集与预处理)**
> 与 **L2(相量计算,拟)**。原则:**算法与 I/O 分离**——本层不碰寄存器、不碰任务调度,
> 只做"数"(码值 → 工程量)与"相量"(工程量 → 复相量),从而可成为主机侧单元测试
> (L0.5)的天然适格对象(落实登记簿 **A-4**)。
## 现有模块
| 文件 | 层 | 职责 |
|---|---|---|
| `adc_input.c/h` | L1 | **ADC 输入层**:① **语义通道映射**(L1-4)——`UA/UB/UC/UN/IA/IB/IC/IN/温度` → 物理通道,消费侧**按语义取数、不写死下标**;② **STM32 ADC1 9 通道软件触发顺序扫描**(L1-3);③ TPAFE5160 原子快照消费 |
## 关键约定
- **语义映射的唯一权威口径**是 `docs/硬件平台/ADC通道映射说明.md`(V0.1 有 13/17 路被置换,
网名编号 ≠ 通道号)。V0.1 上 8 路电气量分属两片转换器(硬件缺陷 **N-18**):`UA/UN/IA`
在 TPAFE5160、`UB/UC/IB/IC/IN` 在 STM32 ADC1。**V0.2 改版只改映射表内容**,本层 API
与上层语义不动。
- **取数一律经 `adc_input_raw()`**(按语义),禁止在消费侧直接索引驱动缓冲或写死下标。
- 本层**不做**跨转换器的相位 / 功率 / 功率因数 / 矢量类运算(V0.1 硬件缺陷 N-18 限制)。
## 相关文档
- 通道映射权威口径:`docs/硬件平台/ADC通道映射说明.md`
- 分层与路线图:`docs/算法/继电保护功能复现规划.md`(§2.1 分层、§3.1 L1 清单)
- 采样节拍:`docs/子系统设计/ADC采样节拍设计说明.md`
+237
View File
@@ -0,0 +1,237 @@
/*
* 模块名称:ADC 输入层(L1 采集与预处理)
* 模块功能:语义通道映射(L1-4)+ STM32 ADC1 顺序扫描(L1-3)+ TPAFE 快照消费。
* 消费侧经 adc_input_raw() 按语义取数,不写死下标。
* 适用平台:STM32F407ZGTx + TPAFE5160
* 作者:王建锋
* 创建日期:2026-10-10
* 修改记录:
* v1.0 2026-10-10 王建锋 创建:L1-3(STM32 ADC1 扫描)+ L1-4(语义映射)
*/
#include "adc_input.h"
#include <stddef.h>
#include "adc.h" /* hadc1:MX_ADC1_Init() 已配 9 通道 / 144 cycles / 软件触发 */
#include "tpafe5160.h" /* tpafe5160_snapshot():临界区原子快照(AGENTS 铁律) */
/* STM32 ADC1 单次转换轮询超时(ms)。单通道 ≈ 7.4 µs(144+12 cycles @ 21 MHz),
* 2 ms 余量充裕;超时即判本次扫描失败,绝不长时间阻塞消费节拍 */
#define ADC_INPUT_POLL_TIMEOUT_MS 2U
/* STM32 ADC1 通道号上限(F4 上 ADC_CHANNEL_0..15 的宏值即通道号 0..15) */
#define ADC_INPUT_STM32_CH_MAX 16U
/* ===== 语义映射表(L1-4)=====
* 权威口径:docs/硬件平台/ADC通道映射说明.md(2026-10-09 硬件确认的语义分配)。
* index 含义:TPAFE 源 = 驱动 buf 下标 0..7;STM32 源 = ADC1 通道号 0..15。
* ⚠ 消费侧一律经本表按语义取数,**不得写死下标**(V0.1 有 13/17 路被置换,
* 网名编号与通道号不等价);V0.2 改版(N-18)只改本表内容。 */
static const adc_channel_desc_t k_adc_map[ADC_SEM_COUNT] = {
[ADC_SEM_UA] = { ADC_SRC_TPAFE, 6U }, /* TPAFE buf[6](ch7) */
[ADC_SEM_UB] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_14 }, /* PC4 */
[ADC_SEM_UC] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_3 }, /* PA3 */
[ADC_SEM_UN] = { ADC_SRC_TPAFE, 5U }, /* TPAFE buf[5](ch6) */
[ADC_SEM_IA] = { ADC_SRC_TPAFE, 2U }, /* TPAFE buf[2](ch3) */
[ADC_SEM_IB] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_6 }, /* PA6 */
[ADC_SEM_IC] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_1 }, /* PA1 */
[ADC_SEM_IN] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_2 }, /* PA2 */
[ADC_SEM_TEMP] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_5 }, /* PA5(V_ZL2) */
};
/* STM32 ADC1 顺序扫描顺序(Rank 1..9),须与 MX_ADC1_Init() 的 Rank 配置严格一致:
* Rank1..8 = CH0..CH7(PA0..PA7),Rank9 = CH14(PC4)。 */
static const uint8_t k_stm32_rank_ch[ADC_INPUT_STM32_CH_NUM] = {
(uint8_t)ADC_CHANNEL_0, (uint8_t)ADC_CHANNEL_1, (uint8_t)ADC_CHANNEL_2,
(uint8_t)ADC_CHANNEL_3, (uint8_t)ADC_CHANNEL_4, (uint8_t)ADC_CHANNEL_5,
(uint8_t)ADC_CHANNEL_6, (uint8_t)ADC_CHANNEL_7, (uint8_t)ADC_CHANNEL_14,
};
/* STM32 ADC1 最新一帧原始码值,按 ADC1 通道号索引(0..15) */
static uint16_t s_stm32_raw[ADC_INPUT_STM32_CH_MAX];
/* TPAFE5160 最新一帧原始码值(16 位二进制补码),按驱动 buf 下标索引 */
static int16_t s_tpafe_raw[ADC_INPUT_TPAFE_CH_NUM];
static uint8_t s_tpafe_valid; /* TPAFE 侧是否已有有效帧 */
static uint8_t s_stm32_valid; /* STM32 侧是否已有有效扫描 */
static int s_stm32_rc; /* STM32 最近一次扫描状态码 */
static uint32_t s_stm32_errs; /* STM32 扫描累计失败次数 */
/*
* 函数功能:STM32 ADC1 顺序扫描一次(L1-3)
* 入口参数:无
* 返 回 值:0 - 成功;-1 - 启动失败;-2 - 转换轮询超时
* 限定条件:仅任务上下文调用;MX_ADC1_Init() 已完成
* 函数说明:软件触发一次 9 通道扫描(ScanConvMode=ENABLE、ContinuousConvMode=
* DISABLE),按 Rank 顺序逐通道轮询 EOC 并读 DR,结果按 ADC1 通道号
* 存入 s_stm32_raw[]。EOCSelection=SINGLE_CONV 且未用 DMA → 每个通道
* 产生一次 EOC,可逐通道轮询(见 HAL_ADC_PollForConversion 说明)。
*/
static int stm32_scan(void) {
uint8_t i;
if (HAL_ADC_Start(&hadc1) != HAL_OK) {
s_stm32_rc = ADC_INPUT_RC_START;
s_stm32_errs++;
return ADC_INPUT_RC_START;
}
for (i = 0u; i < ADC_INPUT_STM32_CH_NUM; i++) {
if (HAL_ADC_PollForConversion(&hadc1, ADC_INPUT_POLL_TIMEOUT_MS) != HAL_OK) {
(void)HAL_ADC_Stop(&hadc1);
s_stm32_rc = ADC_INPUT_RC_TIMEOUT;
s_stm32_errs++;
return ADC_INPUT_RC_TIMEOUT;
}
s_stm32_raw[k_stm32_rank_ch[i]] = (uint16_t)HAL_ADC_GetValue(&hadc1);
}
(void)HAL_ADC_Stop(&hadc1);
s_stm32_valid = 1u;
s_stm32_rc = ADC_INPUT_RC_OK;
return ADC_INPUT_RC_OK;
}
/*
* 函数功能:语义映射表自洽性自检
* 入口参数:无
* 返 回 值:0 - 自洽;-1 - 存在不自洽条目
* 限定条件:无
* 函数说明:逐条校验映射表与扫描配置是否一致——① TPAFE 源下标须落在
* buf[0..7];② STM32 源的通道号必须出现在 k_stm32_rank_ch[] 中,
* 否则该通道永远不会被扫描(读到的恒为陈旧值)。这是"通道宏写错 →
* 读到另一只脚/读不到"这一 §5 警告的代码级兜底(无信号源时无法做
* 引脚级注入验证,见 docs/硬件平台/ADC通道映射说明.md §8)。
*/
static int map_check(void) {
uint8_t s;
int rc = 0;
for (s = 0u; s < (uint8_t)ADC_SEM_COUNT; s++) {
if (k_adc_map[s].source == ADC_SRC_TPAFE) {
if (k_adc_map[s].index >= ADC_INPUT_TPAFE_CH_NUM) {
rc = -1;
}
} else {
uint8_t i;
uint8_t found = 0u;
for (i = 0u; i < ADC_INPUT_STM32_CH_NUM; i++) {
if (k_stm32_rank_ch[i] == k_adc_map[s].index) {
found = 1u;
}
}
if (found == 0u) {
rc = -1;
}
}
}
return rc;
}
/*
* 函数功能:ADC 输入层初始化
* 入口参数:无
* 返 回 值:0 - 成功;-1 - 语义映射表自检失败
* 限定条件:内核启动前调用(无 RTOS 依赖);STM32 ADC1 外设已由 MX_ADC1_Init 配好
* 函数说明:清空原始码值缓冲与统计,并做一次映射表自洽性自检;本层不重复配置
* 外设,也不启动任何转换(STM32 侧为软件触发,按拍扫描)。
*/
int adc_input_init(void) {
uint8_t i;
for (i = 0u; i < ADC_INPUT_STM32_CH_MAX; i++) {
s_stm32_raw[i] = 0u;
}
for (i = 0u; i < ADC_INPUT_TPAFE_CH_NUM; i++) {
s_tpafe_raw[i] = 0;
}
s_tpafe_valid = 0u;
s_stm32_valid = 0u;
s_stm32_rc = ADC_INPUT_RC_NOT_RUN;
s_stm32_errs = 0u;
return map_check();
}
/*
* 函数功能:采集一拍(TPAFE 原子快照 + STM32 ADC1 顺序扫描)
* 入口参数:无
* 返 回 值:TPAFE 快照结果(1=有新帧 0=无新帧 -1=参数错)
* 限定条件:仅**任务上下文**调用(TPAFE 快照走临界区)
* 函数说明:先取 TPAFE 一帧,再做 STM32 ADC1 扫描;两者相互独立,STM32 扫描
* 失败不影响 TPAFE 快照与返回值语义(STM32 侧状态另由
* adc_input_stm32_rc() 暴露)。
*/
int adc_input_poll(void) {
int r = tpafe5160_snapshot(s_tpafe_raw, (uint8_t)ADC_INPUT_TPAFE_CH_NUM);
if (r == 1) {
s_tpafe_valid = 1u;
}
(void)stm32_scan();
return r;
}
/*
* 函数功能:取语义通道的物理描述
* 入口参数:ch - 语义通道
* 返 回 值:指向静态描述表的指针;ch 越界返回 NULL
* 限定条件:无
* 函数说明:返回的是静态存储地址,调用方不得修改。
*/
const adc_channel_desc_t *adc_input_desc(adc_semantic_t ch) {
if ((unsigned int)ch >= (unsigned int)ADC_SEM_COUNT) {
return NULL;
}
return &k_adc_map[ch];
}
/*
* 函数功能:读取语义通道的原始码值(未标定)
* 入口参数:ch - 语义通道
* out - 输出原始码值(TPAFE 侧为 16 位补码;STM32 侧为 0..4095)
* 返 回 值:0 - 成功;-1 - 参数错;-2 - 该来源当前无有效数据
* 限定条件:无
* 函数说明:消费侧取数的唯一入口(按语义,不写死下标)。
*/
int adc_input_raw(adc_semantic_t ch, int32_t *out) {
if ((out == NULL) || ((unsigned int)ch >= (unsigned int)ADC_SEM_COUNT)) {
return -1;
}
if (k_adc_map[ch].source == ADC_SRC_TPAFE) {
if (s_tpafe_valid == 0u) {
return -2;
}
*out = (int32_t)s_tpafe_raw[k_adc_map[ch].index];
} else {
if (s_stm32_valid == 0u) {
return -2;
}
*out = (int32_t)s_stm32_raw[k_adc_map[ch].index];
}
return 0;
}
/*
* 函数功能:读取 STM32 ADC1 最近一次扫描状态码
* 入口参数:无
* 返 回 值:ADC_INPUT_RC_xxx
* 限定条件:无
* 函数说明:供心跳日志 / 自检观测 STM32 侧健康度。
*/
int adc_input_stm32_rc(void) {
return s_stm32_rc;
}
/*
* 函数功能:读取 STM32 ADC1 扫描累计失败次数
* 入口参数:无
* 返 回 值:累计失败次数(启动失败或转换超时均计入)
* 限定条件:无
* 函数说明:单调递增,供心跳日志观测。
*/
uint32_t adc_input_stm32_errs(void) {
return s_stm32_errs;
}
+127
View File
@@ -0,0 +1,127 @@
/*
* 模块名称:ADC 输入层(L1 采集与预处理)
* 模块功能:把两片转换器的原始码值按**语义**(UA/UB/UC/UN/IA/IB/IC/IN/温度)统一
* 暴露,使上层(UI / 自检 / 保护算法)**按语义取数,不写死下标**:
* ① 语义通道映射表(L1-4)——权威口径 docs/硬件平台/ADC通道映射说明.md;
* ② STM32 ADC1 9 通道软件触发顺序扫描(L1-3)。
* V0.1 上 8 路电气量分属两片转换器(硬件缺陷 N-18):UA/UN/IA 走
* TPAFE5160(16 位 / ±5 V 双极性),UB/UC/IB/IC/IN 走 STM32 ADC1
* (12 位 / 0~3.3 V 单极性)。V0.2 改版(8 路电气量全部接 TPAFE)
* 只改映射表内容,本层 API 与上层语义不动。
* 适用平台:STM32F407ZGTx + TPAFE5160
* 作者:王建锋
* 创建日期:2026-10-10
* 修改记录:
* v1.0 2026-10-10 王建锋 创建:L1-3(STM32 ADC1 扫描)+ L1-4(语义映射)
*/
#ifndef ADC_INPUT_H
#define ADC_INPUT_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* 语义通道(V0.1:8 路电气量 + 温度)。枚举值即语义编号,顺序不得随意变更;
* 备用通道(RESERVED)不接入语义,故不在此枚举内。 */
typedef enum {
ADC_SEM_UA = 0, /* A 相电压(TPAFE) */
ADC_SEM_UB, /* B 相电压(STM32) */
ADC_SEM_UC, /* C 相电压(STM32) */
ADC_SEM_UN, /* 零线电压(TPAFE) */
ADC_SEM_IA, /* A 相电流(TPAFE) */
ADC_SEM_IB, /* B 相电流(STM32) */
ADC_SEM_IC, /* C 相电流(STM32) */
ADC_SEM_IN, /* 零线电流(STM32) */
ADC_SEM_TEMP, /* 温度(STM32,V_ZL2) */
ADC_SEM_COUNT /* 语义通道数(哨兵) */
} adc_semantic_t;
/* 数据来源:V0.1 的 8 路电气量分属两片转换器(N-18) */
typedef enum {
ADC_SRC_TPAFE = 0, /* TPAFE5160:16 位、±5 V 双极性、8 通道同步 */
ADC_SRC_STM32 /* STM32 ADC1:12 位、0~3.3 V 单极性、9 通道顺序扫描 */
} adc_source_t;
/* 语义通道 → 物理通道描述 */
typedef struct {
adc_source_t source; /* 数据来源 */
uint8_t index; /* TPAFE 源:驱动 buf 下标 0..7;STM32 源:ADC1 通道号 0..15 */
} adc_channel_desc_t;
/* TPAFE5160 一帧通道数(= 驱动 buf 长度) */
#define ADC_INPUT_TPAFE_CH_NUM 8U
/* STM32 ADC1 顺序扫描通道数(Rank 1..9) */
#define ADC_INPUT_STM32_CH_NUM 9U
/* STM32 ADC1 最近一次扫描状态码 */
#define ADC_INPUT_RC_OK (0) /* 扫描成功 */
#define ADC_INPUT_RC_START (-1) /* HAL_ADC_Start 失败 */
#define ADC_INPUT_RC_TIMEOUT (-2) /* 转换轮询超时 */
#define ADC_INPUT_RC_NOT_RUN (-3) /* 尚未扫描过 */
/*
* 函数功能:ADC 输入层初始化
* 入口参数:无
* 返 回 值:0 - 成功;-1 - 语义映射表自检失败(映射指向了不会被扫描的通道)
* 限定条件:内核启动前调用(无 RTOS 依赖);STM32 ADC1 外设已由 MX_ADC1_Init 配好
* 函数说明:清空原始码值缓冲与统计,并做一次映射表自洽性自检;本层不重复配置外设。
*/
int adc_input_init(void);
/*
* 函数功能:采集一拍(TPAFE 原子快照 + STM32 ADC1 顺序扫描)
* 入口参数:无
* 返 回 值:TPAFE 快照结果(1=有新帧 0=无新帧 -1=参数错);STM32 侧状态另见
* adc_input_stm32_rc()
* 限定条件:仅**任务上下文**调用(TPAFE 快照走临界区)
* 函数说明:本函数只搬运原始码值,不做标定/运算。STM32 侧扫描失败不阻断 TPAFE
* 快照,也不影响返回值语义。
*/
int adc_input_poll(void);
/*
* 函数功能:取语义通道的物理描述
* 入口参数:ch - 语义通道
* 返 回 值:指向静态描述表的指针;ch 越界返回 NULL
* 限定条件:无
* 函数说明:供自检注入 / UI 通道序等需要"语义 → 物理通道"映射的场景使用。
*/
const adc_channel_desc_t *adc_input_desc(adc_semantic_t ch);
/*
* 函数功能:读取语义通道的原始码值(未标定)
* 入口参数:ch - 语义通道
* out - 输出原始码值(TPAFE 侧为 16 位二进制补码;STM32 侧为 0..4095)
* 返 回 值:0 - 成功;-1 - 参数错;-2 - 该来源当前无有效数据
* 限定条件:无
* 函数说明:消费侧取数的唯一入口(按语义,不写死下标)。取数前须先成功
* adc_input_poll() 一次。
*/
int adc_input_raw(adc_semantic_t ch, int32_t *out);
/*
* 函数功能:读取 STM32 ADC1 最近一次扫描状态码
* 入口参数:无
* 返 回 值:ADC_INPUT_RC_xxx
* 限定条件:无
* 函数说明:供心跳日志 / 自检观测 STM32 侧健康度。
*/
int adc_input_stm32_rc(void);
/*
* 函数功能:读取 STM32 ADC1 扫描累计失败次数
* 入口参数:无
* 返 回 值:累计失败次数(启动失败或转换超时均计入)
* 限定条件:无
* 函数说明:单调递增,供心跳日志观测。
*/
uint32_t adc_input_stm32_errs(void);
#ifdef __cplusplus
}
#endif
#endif /* ADC_INPUT_H */
+15 -2
View File
@@ -23,6 +23,7 @@
#include "sys_clock.h"
#include "gd5f2gq5ue.h"
#include "tpafe5160.h"
#include "adc_input.h" /* L1 采集层:adc_input_init()(语义映射 + STM32 ADC1 扫描) */
#include "sys_log.h"
#include "task_watchdog.h" /* task_wd_seg_report():round 8 复位不丢"进段打点"上报 */
@@ -88,9 +89,9 @@ static void reset_cause_str(uint32_t csr, char *buf, unsigned int len) {
* 1) 时间子系统(SD2506 RTC → 墙钟基准)
* 2) RS-485
* 3) GD5F NAND
* 4) TPAFE5160 ADC
* 4) TPAFE5160 ADC → ADC 输入层(L1,语义映射 + STM32 ADC1)
* 5) SD2506 诊断信息
* 6) 触发首次 ADC 转换
* 6) 启动 TIM6 1 kHz 采样节拍(ISR 内触发 CONVST)
*/
int app_main_init(void) {
/* 0) 使能 MemManage / BusFault / UsageFault(复位后默认只有 HardFault 使能)。
@@ -181,6 +182,18 @@ int app_main_init(void) {
syslog_post(SYSLOG_SELFTEST, "ADC fault");
}
/* 4b) ADC 输入层(L1)初始化:清原始码值缓冲与统计 + **语义映射表自洽性自检**
* (映射指向了不会被扫描的通道 → 返回 -1,避免"读到恒为陈旧值"的静默错)。
* STM32 ADC1 外设已由 MX_ADC1_Init()(main.c)配好 9 通道 / 144 cycles /
* 软件触发,本层不重复配置、也不启动转换(软件触发按拍扫描)。
* 须在启动采样节拍(步骤 6)之前。 */
if (adc_input_init() != 0) {
DBG_ERROR("ADC input layer: map self-check FAILED");
syslog_post(SYSLOG_SELFTEST, "ADC map fault");
} else {
DBG_INFO("ADC input layer init: OK");
}
/* 5) SD2506 RTC 诊断信息 */
{
/* 读取 ID */
+1 -1
View File
@@ -10,7 +10,7 @@
| `default_task.c/h` | defaultTask | Normal | FTL + FatFS 挂载、配置引擎属主、syslog 唯一写者、LED 指示 |
| `net_task.c/h` | netTask | Normal | `net_stack_bring_up` → 置 NET_READY → 循环 `net_poll` + `net_process_messages` |
| `ui_task.c/h` | uiTask | BelowNormal | LVGL 界面(菜单 / 内容页 / 待机)+ 推流 |
| `adc_task.c/h` | (ADC 任务) | — | 高精度 ADC(TPAFE5160)数据采集 |
| `adc_task.c/h` | (ADC 任务) | — | ADC 采集消费:TPAFE5160 快照 + STM32 ADC1 顺序扫描,经 [`../algo/adc_input.c`](../algo/README.md) **按语义**取数(采样节拍由 TIM6 硬件触发) |
| `rs485_task.c/h` | (RS-485 任务) | — | 半双工 RS-485 协议通信 |
| `../sync/time_sync.c` | timeSyncTask | Normal | 墙钟时间同步(实现不在本目录,见 [`../sync/README.md`](../sync/README.md)) |
+44 -19
View File
@@ -1,6 +1,7 @@
/*
* 模块名称:ADC Sampling Task
* 模块功能:TPAFE5160 8 通道同步采样——帧消费 + 帧统计(节拍由 TIM6 硬件定时器提供)
* 模块功能:ADC 采集消费——TPAFE5160 快照 + STM32 ADC1 顺序扫描(经 L1 层
* App/algo/adc_input.c 按语义取数)+ 帧统计(节拍由 TIM6 硬件定时器提供)
* 状态说明:**B1 已落地(2026-10-10)**——本任务从空桩转为真实消费者:
* 用 tpafe5160_snapshot() 原子取数(AGENTS 铁律),统计帧数/无新帧次数,
* 并纳入看门狗 required_mask(TASK_WD_ADC)。
@@ -9,11 +10,14 @@
* 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)测量有效;本任务只取数,不做保护运算。
* 接入前必读:取数只用 tpafe5160_snapshot()(临界区原子快照,仅任务上下文;
* 旧的 get_buf/data_ready/clear_ready 已删除);接消费者前的强制门禁
* 见根 AGENTS.md「接 ADC / RS-485 消费者前的强制门禁」+ 建任务前后各跑
* scripts/stack_report.py 做栈预算。
* 接入前必读:取数只用 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
@@ -28,10 +32,14 @@
* 就绪前挂起上报,就绪后补报最近一帧。
* 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"
@@ -44,9 +52,8 @@
/* 心跳日志周期(ms):周期性上报帧统计,作为采集链路存活的观测面 */
#define ADC_TASK_REPORT_MS 10000U
/* 最新一帧原始码值(8 通道;语义映射见 docs/硬件平台/ADC通道映射说明.md) */
static int16_t s_frame[TPAFE5160_CH_NUM];
/* 统计:已消费帧数 / 无新帧次数(首拍必为无新帧,之后应稳定为 0 增量) */
/* 统计:已消费帧数 / 无新帧次数(首拍必为无新帧,之后应稳定为 0 增量)。
* 原始码值缓冲由 L1 层 App/algo/adc_input.c 持有(消费侧按语义取数,不写死下标) */
static uint32_t s_frames_consumed;
static uint32_t s_frames_idle;
@@ -69,10 +76,10 @@ int adc_task_init(void) {
* 入口参数:arg - FreeRTOS 传入参数(未使用)
* 返 回 值:无
* 限定条件:adc_task_init() 已调用;内核已启动
* 函数说明:每 1ms 原子快照取最新一帧并统计。采样节拍由 TIM6 硬件定时器提供
* (ISR 内触发 CONVST),本任务不再触发。稳态下 consumed 每拍 +1、
* idle 不再增长;idle 持续增长 = EXTI 未在读帧或 TIM6 未启动(异常),
* 由周期心跳日志暴露。
* 函数说明:每 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();
@@ -84,9 +91,10 @@ void adc_task_func(void *arg) {
for (;;) {
int r;
/* 1) 原子快照消费最新一帧(仅任务上下文;1=有新帧 0=无新帧 -1=参数错)。
/* 1) 采集一拍:L1 层 adc_input_poll() = TPAFE 原子快照 + STM32 ADC1 顺序扫描
* (仅任务上下文;TPAFE 侧 1=有新帧 0=无新帧 -1=参数错)。
* 采样节拍由 TIM6 更新中断提供(ISR 内触发 CONVST),本任务不触发。 */
r = tpafe5160_snapshot(s_frame, (uint8_t)TPAFE5160_CH_NUM);
r = adc_input_poll();
if (r == 1) {
s_frames_consumed++;
} else if (r == 0) {
@@ -98,12 +106,27 @@ void adc_task_func(void *arg) {
/* 2) 首帧码值上报:**写者就绪后**才落日志。写者未注册时 con_append 走
* 无锁直发路径,与 defaultTask 的存储初始化日志会字节级交错
* (2026-10-10 台架实测);此时挂起上报,就绪后补报最近一帧。
* 只报 TPAFE 三路(UA=buf[6] UN=buf[5] IA=buf[2]),验证取数契约 */
* 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 frame: UA=%d UN=%d IA=%d",
s_frame[6], s_frame[5], s_frame[2]);
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) */
@@ -116,11 +139,13 @@ void adc_task_func(void *arg) {
if ((int32_t)(now - report_at) >= 0) {
if (dbg_console_writer_ready() != 0u) {
DBG_INFO("frames=%lu idle=%lu seq=%lu frst_err=%u",
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());
(unsigned)tpafe5160_frstdata_err(),
adc_input_stm32_rc(),
(unsigned long)adc_input_stm32_errs());
}
report_at = now + ADC_TASK_REPORT_MS;
}