Files
STM32F4-Base/App/algo/adc_input.c
T
wandering1 dd8210867a adc_input: 失败拍清 s_stm32_valid,杜绝超时沿用上一拍旧值(单测 70->79)
缺陷(真机实测发现):本层只有一份 s_stm32_raw[],有效标志此前只在成功时
置 1、失败时从不清零 -> 超时拍消费侧照样取数、照样压入采样环(且超时前
几个 Rank 已写本拍新值、后面几个是上一拍旧值 = 半新半旧混合帧),直接污
染 20 点 DFT 相量窗;adc_input_poll() 返回值只由 TPAFE 决定,s_frames_skip
也抓不到它。

修复:stm32_scan() 两条失败支路(HAL_ADC_Start 失败 / 轮询超时)都清
s_stm32_valid -> 本拍 adc_input_raw() 对 STM32 语义通道返 -2 -> adc_task
走既有 skip 整帧跳过路径;下一拍扫描成功自动置回 1(自愈)。

测试:新增 TC-AI-070..078(70 -> 79 用例)。旧用例 TC-AI-034/039b 都是
init 后立刻失败(valid 本就是 0),照不出成功过之后再失败这条路径,故新
用例先成功一拍进入有效态再注入失败。**反向验证已做**:把支路改回旧行为 ->
TC-AI-039b/073/074 立即 FAIL(76/79),证明用例有效。

验证:主机单测 79/79 PASS;@build 0 错 0 警(Code 256868 -> 256872);
@test PASS(本轮无超时:adc1_err=0、skip=0、idle=0、frames=20001);
18 钩子全绿。文档同步 4 处(停摆分析 4.4 / ADC采样节拍 10.4 / ADC输入层
单测说明 / test-host README)。

遗留:2 ms 忙等本身未消(硬化 2 = DMA,方案 B2 待 CubeMX 配置)。
2026-10-10 23:08:01 +08:00

252 lines
10 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 输入层(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 说明)。
* 两条失败支路(启动失败 / 轮询超时)**都会清 s_stm32_valid**:本拍
* STM32 侧判为无有效数据,绝不沿用上一拍旧值(详见支路内注释)。
* 超时在**首个**失败通道处即 Stop+return,不轮询剩余通道 → 单拍耗时
* 上界 = 一次超时(≈2~3 ms,HAL_GetTick 1 ms 粒度),不是 9 次。
*/
static int stm32_scan(void) {
uint8_t i;
if (HAL_ADC_Start(&hadc1) != HAL_OK) {
/* 失败拍必须清有效标志,见下方超时分支说明(同一语义) */
s_stm32_valid = 0u;
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_raw[],
* 超时前几个 Rank 已写入**本拍新值**、后面几个还是**上一拍旧值** →
* 若保持 s_stm32_valid=1,消费侧会拿到"半新半旧"的混合帧并照样压入
* 采样环(adc_input_poll() 的返回值只由 TPAFE 决定,s_frames_skip
* 也抓不到它),直接污染 20 点 DFT 相量窗。清标志后 adc_input_raw()
* 对 STM32 语义通道返 -2 → adc_task 走既有的"整帧跳过"路径。
* 下一拍扫描成功会重新置 1(自愈),无需额外恢复动作。 */
s_stm32_valid = 0u;
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;
}