d59c2198eb
新增 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)。
238 lines
9.2 KiB
C
238 lines
9.2 KiB
C
/*
|
||
* 模块名称: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;
|
||
}
|