0f7b5a87fb
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号) 一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、 采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。 门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏 undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8 reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF; id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。 test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字, 是本批唯一改变构建产物的文件集合。 为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged 部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、 不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。 实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01% (基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 / 告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。 基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处 引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使 识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。 ⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二 根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有), 今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。 固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0; App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889 断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿; check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带 BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。 .cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法: Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。 同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%, RW_CCM 57,376 B / 87.9%,阈值 92%。
402 lines
18 KiB
C
402 lines
18 KiB
C
/*
|
||
* 模块名称:ADC 输入层(L1 采集与预处理)
|
||
* 模块功能:语义通道映射(L1-4)+ STM32 ADC1 采样消费(L1-3,TIM3 TRGO 触发 +
|
||
* DMA 双缓冲)+ TPAFE 快照消费。
|
||
* 消费侧经 adc_input_raw() 按语义取数,不写死下标。
|
||
* 适用平台:STM32F407ZGTx + TPAFE5160
|
||
* 作者:王建锋
|
||
* 创建日期:2026-10-10
|
||
* 修改记录:
|
||
* v1.0 2026-10-10 王建锋 创建:L1-3(STM32 ADC1 扫描)+ L1-4(语义映射)
|
||
* v2.0 2026-10-10 王建锋 STM32 侧由"软件触发 + 逐通道轮询"改为
|
||
* "TIM3 TRGO 硬件触发 + DMA 双缓冲消费":本层不再触发
|
||
* 也不等待转换(消除最坏 2~3 ms 忙等);新增三个 HAL
|
||
* 回调(ISR 内只计数)与 seq / hw_errs 观测量
|
||
* v2.1 2026-10-21 王建锋 丢帧计数口径由「次数」改为「帧点数」(累加每次前进
|
||
* 的超出部分),与判定层 TPAFE 侧
|
||
* adc_health_tpafe_gaps() 同单位。⚠ 换版前固件里心跳
|
||
* 字段 adc1_gap 是次数口径,不可跨版本直接对比
|
||
*/
|
||
|
||
#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 通道号上限(F4 上 ADC_CHANNEL_0..15 的宏值即通道号 0..15) */
|
||
#define ADC_INPUT_STM32_CH_MAX 16U
|
||
/* DMA 搬运缓冲长度 = 2 x 9 = 18:**双缓冲(乒乓)**。
|
||
* ADC1 配 ExternalTrigConv=TIM3_TRGO + DMAContinuousRequests=ENABLE + DMA CIRCULAR,
|
||
* 每拍(TIM3 TRGO,1 kHz)自动搬 9 个 → 每 2 拍绕一圈:半传输中断 = 前半 9 个就绪,
|
||
* 传输完成中断 = 后半 9 个就绪。消费侧永远读"刚完成的那一半",而 DMA 正在写另一半
|
||
* → 无撕裂、无需临界区。代价:STM32 侧数据固定滞后 1 拍(1 ms),9 路同一延迟,
|
||
* 对 20 点 DFT 窗是纯传输延迟、不引入通道间偏差。 */
|
||
#define ADC_INPUT_STM32_DMA_LEN (2U * ADC_INPUT_STM32_CH_NUM)
|
||
/* 连续"无新帧"达到该拍数才计一次停摆故障(单拍是相位伪影,见 stm32_take 注释) */
|
||
#define ADC_INPUT_STALL_RUN_MAX 2U
|
||
|
||
/* ===== 语义映射表(L1-4)=====
|
||
* 权威口径:docs/硬件平台/ADC通道映射说明.md(2026-10-09 硬件确认的语义分配)。
|
||
* index 含义:TPAFE 源 = 驱动 buf 下标 0..7;STM32 源 = ADC1 通道号 0..15。
|
||
* ⚠ 消费侧一律经本表按语义取数,**不得写死下标**(V0.1 有 13/17 路被置换,
|
||
* 网名编号与通道号不等价);V0.2 改版(消除双转换器无共同采样触发)只改本表内容。 */
|
||
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];
|
||
/* DMA 目的缓冲(乒乓两半区,按 Rank 序 0..8 / 9..17)。
|
||
* ⚠ **必须落在 SRAM(0x20000000 段)**:ADC1 挂的是 **DMA2**,而 F4 的 DMA2 不可达
|
||
* CCM(0x10000000)。实测 .map 里本文件 .bss 在 RW_IRAM1 → 普通 static 即可;
|
||
* 日后加大缓冲或改链接段时**必须用 .map 复核地址**——放错不报编译错、不报运行错,
|
||
* 只是数据永远搬不回来。 */
|
||
static uint16_t s_stm32_dma[ADC_INPUT_STM32_DMA_LEN];
|
||
static volatile uint32_t s_dma_seq; /* DMA 半帧/整帧完成次数(ISR 递增,只增不清) */
|
||
static uint32_t s_dma_seq_seen; /* 消费侧上次看到的完成计数 */
|
||
static volatile uint32_t s_stm32_hw_errs; /* HAL ADC/DMA 错误回调累计(ISR 写) */
|
||
static uint32_t s_stm32_gaps; /* 丢帧点数:Σ(前进量-1),与 tgap 同单位 */
|
||
static uint8_t s_stall_run; /* 连续"无新帧"拍数(相位伪影只会是 1 拍)*/
|
||
/* 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,DMA 双缓冲)
|
||
* 入口参数:无
|
||
* 返 回 值:0 - 取到新一帧;-2 - 本拍无新帧(TIM3 未启动 / ADC 停摆 / DMA 出错)
|
||
* 限定条件:仅任务上下文调用;adc_input_init() 已挂好 DMA
|
||
* 函数说明:本函数**既不触发转换也不等待转换**——转换由 TIM3 TRGO 硬件触发、由 DMA
|
||
* 自动搬运。每拍只做"看完成计数是否前进 + 拷 9 个半字",故**零忙等**:
|
||
* 单拍只剩"读一个计数 + 拷 9 个半字"→ µs 量级。
|
||
* 读的是"上一拍刚完成的那一半"乒乓缓冲(DMA 此刻正在写另一半)→ 无撕裂。
|
||
* ⚠ 计数未前进 = 本拍 STM32 侧**无有效数据**,必须清 s_stm32_valid,绝不
|
||
* 沿用上一拍旧值(半新半旧的混合帧会污染相量窗)→ adc_input_raw()
|
||
* 返 -2 → adc_task 走既有"整帧跳过"路径;下一拍自动恢复。
|
||
*/
|
||
static int stm32_take(void) {
|
||
uint32_t seq = s_dma_seq; /* 单次 32 位读:与 ISR 自增不产生撕裂 */
|
||
const uint16_t *src;
|
||
uint32_t d;
|
||
uint8_t half;
|
||
uint8_t i;
|
||
|
||
if (seq == s_dma_seq_seen) {
|
||
s_stm32_valid = 0u;
|
||
s_stm32_rc = ADC_INPUT_RC_STALL;
|
||
/* 单拍"无新帧"**多半是消费者相位伪影,不是故障**:本层在 TIM3 触发瞬间被唤醒,
|
||
* 而该拍的 DMA 完成落在 ~67 us 之后;若上一拍被日志/抢占拖过这 67 us,本拍就会
|
||
* 看到"计数没前进"(那个完成已被上一拍消费掉)。这种伪影**只会连续 1 拍**——
|
||
* DMA 若真停,第 2 拍仍不会前进。故只有连续 >= ADC_INPUT_STALL_RUN_MAX 拍
|
||
* 无新帧才计入故障计数(实测开机日志突发期约 21~26 次伪影、稳态每 10 s 心跳 1 次,
|
||
* 全部为单拍;见 docs/子系统设计/ADC采样节拍设计说明.md §8) */
|
||
if (s_stall_run < 255u) {
|
||
s_stall_run++;
|
||
}
|
||
if (s_stall_run >= ADC_INPUT_STALL_RUN_MAX) {
|
||
s_stm32_errs++;
|
||
}
|
||
return ADC_INPUT_RC_STALL;
|
||
}
|
||
|
||
d = seq - s_dma_seq_seen;
|
||
if (d > 1u) {
|
||
/* 计数一次前进 >1 = 中间有帧没被读走(乒乓缓冲已被覆盖)→ **真丢帧**,
|
||
* 与"无新帧"分开计数:前者是消费者超拍(本任务单拍耗时 > 1 ms),
|
||
* 后者多半是相位伪影。按**丢掉的帧数**累计(d-1),与判定层 TPAFE 侧的
|
||
* adc_health_tpafe_gaps() 同单位——否则心跳里 adc1_gap 与 tgap 一个是次
|
||
* 一个是点,跨字段对比会得出错误结论 */
|
||
s_stm32_gaps += (d - 1u);
|
||
}
|
||
s_stall_run = 0u;
|
||
s_dma_seq_seen = seq;
|
||
/* 半区下标由 seq 奇偶推出,**不再另读 ISR 变量**:DMA 从 NDTR=18 起、每拍搬 9 个,
|
||
* 故第 1/3/5... 次完成的是前半区(下标 0)、第 2/4/6... 次是后半区(下标 1)。
|
||
* 只读 seq 一个量 → 消费侧与 ISR 之间不存在"读到一半被换半区"的窗口。 */
|
||
half = (uint8_t)(1u - (seq & 1u));
|
||
src = &s_stm32_dma[((uint32_t)half * ADC_INPUT_STM32_CH_NUM)];
|
||
|
||
for (i = 0u; i < ADC_INPUT_STM32_CH_NUM; i++) {
|
||
s_stm32_raw[k_stm32_rank_ch[i]] = src[i];
|
||
}
|
||
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[] 中,
|
||
* 否则该通道永远不会被扫描(读到的恒为陈旧值)。这是"通道宏写错 →
|
||
* 读到另一只脚/读不到"这一警告的代码级兜底(警告口径与逐通道验证方法:
|
||
* docs/硬件平台/ADC通道映射说明.md §5、§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 - 语义映射表自检失败;-2 - DMA 挂载失败
|
||
* 限定条件:内核启动前调用(无 RTOS 依赖);STM32 ADC1 外设已由 MX_ADC1_Init 配好
|
||
* 函数说明:清空原始码值缓冲与统计 + 映射表自洽性自检 + **挂载一次 ADC1 DMA**
|
||
* (此后转换由 TIM3 TRGO 硬件触发、DMA 自动搬运,本层不再触发转换)。
|
||
* 须在启动采样节拍(app_main.c 步骤 6)之前调用。
|
||
*/
|
||
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_STM32_DMA_LEN; i++) {
|
||
s_stm32_dma[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;
|
||
s_dma_seq = 0u;
|
||
s_dma_seq_seen = 0u;
|
||
s_stm32_hw_errs = 0u;
|
||
s_stm32_gaps = 0u;
|
||
s_stall_run = 0u;
|
||
|
||
if (map_check() != 0) {
|
||
return -1;
|
||
}
|
||
|
||
/* 挂**一次** DMA,之后不再重启:ADC 由 TIM3 TRGO 每拍触发一轮 9 通道转换,
|
||
* DMA 在 CIRCULAR 模式下自动搬运、硬件自动重载 NDTR → 每 2 拍绕一圈(乒乓)。
|
||
* 内核启动前调用安全:只碰寄存器/HAL,不创建任何 CMSIS 对象(AGENTS 铁律)。
|
||
* 此刻 TIM3 尚未启动(app_main.c 步骤 6)→ 不会发生任何转换或搬运。 */
|
||
if (HAL_ADC_Start_DMA(&hadc1, (uint32_t *)s_stm32_dma,
|
||
ADC_INPUT_STM32_DMA_LEN) != HAL_OK) {
|
||
s_stm32_rc = ADC_INPUT_RC_START;
|
||
s_stm32_errs++;
|
||
return -2;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:采集一拍(TPAFE 原子快照 + 消费一帧 STM32 ADC1 DMA 采样)
|
||
* 入口参数:无
|
||
* 返 回 值:TPAFE 快照结果(1=有新帧 0=无新帧 -1=参数错)
|
||
* 限定条件:仅**任务上下文**调用(TPAFE 快照走临界区)
|
||
* 函数说明:先取 TPAFE 一帧,再消费 STM32 侧最近一帧 DMA 双缓冲;两者相互独立,
|
||
* STM32 侧无新帧不影响 TPAFE 快照与返回值语义(其状态另由
|
||
* adc_input_stm32_rc() / adc_input_stm32_seq() 暴露)。
|
||
* ⚠ STM32 侧数据比 TPAFE 侧**滞后 1 拍**(乒乓双缓冲的固有延迟,
|
||
* 9 路一致);因双转换器无共同采样触发,V0.1 本就禁止跨转换器做矢量运算。
|
||
*/
|
||
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_take();
|
||
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 停摆故障累计次数
|
||
* 入口参数:无
|
||
* 返 回 值:DMA 挂载失败次数 + "连续 >=2 拍无新帧"的次数
|
||
* 限定条件:无
|
||
* 函数说明:单调递增,供心跳日志观测。单拍无新帧(相位伪影)**不计入**,
|
||
* 丢帧由 adc_input_stm32_gaps() 单独计数。
|
||
*/
|
||
uint32_t adc_input_stm32_errs(void) {
|
||
return s_stm32_errs;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读取 STM32 ADC1 累计丢帧点数
|
||
* 入口参数:无
|
||
* 返 回 值:Σ(完成计数每次前进的超出部分),即被乒乓缓冲覆盖掉、没被消费的帧数
|
||
* 限定条件:无
|
||
* 函数说明:这是"消费者超拍"的直接证据(本任务单拍耗时 > 1 ms,如心跳日志突发期);
|
||
* 与 adc_input_stm32_errs()(节拍源停摆)是两类不同的故障,**勿混读**。
|
||
* 单位与判定层 TPAFE 侧 adc_health_tpafe_gaps() 一致(都是帧点数),心跳里
|
||
* 可直接对比;注意 2026-10-21 之前固件该字段是"次数"口径。
|
||
*/
|
||
uint32_t adc_input_stm32_gaps(void) {
|
||
return s_stm32_gaps;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读取 STM32 ADC1 的 DMA 整帧完成计数
|
||
* 入口参数:无
|
||
* 返 回 值:DMA 半区完成中断 + 传输完成中断的累计次数(每拍 +1)
|
||
* 限定条件:无
|
||
* 函数说明:这是 STM32 侧"节拍是否还在喂数据"的直接观测量:与 TPAFE 的
|
||
* tpafe5160_frame_seq() 对称。它若停增而 TIM3 仍在跑 → DMA/ADC 侧停摆。
|
||
*/
|
||
uint32_t adc_input_stm32_seq(void) {
|
||
return s_dma_seq;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读取 STM32 ADC1 的 HAL 层错误回调累计次数
|
||
* 入口参数:无
|
||
* 返 回 值:HAL_ADC_ErrorCallback 触发次数(DMA 传输错误等)
|
||
* 限定条件:无
|
||
* 函数说明:与 adc_input_stm32_errs()(消费侧"无新帧")分开计数,便于区分
|
||
* "节拍源停了"与"DMA 出错"两类故障。
|
||
*/
|
||
uint32_t adc_input_stm32_hw_errs(void) {
|
||
return s_stm32_hw_errs;
|
||
}
|
||
|
||
/* ===== HAL 回调(ISR 上下文)=====
|
||
* ⚠ 三个回调都在中断里跑(DMA2_Stream4_IRQn,抢占优先级 6)→ **只允许**写 volatile
|
||
* 计数/标志,不得调用任何 CMSIS-RTOS API、不得日志、不得拷贝大缓冲。
|
||
* 搬运与消费全在任务侧(stm32_take())。 */
|
||
|
||
/*
|
||
* 函数功能:DMA 传输完成回调 = 后半区(下标 1)9 个采样就绪
|
||
* 入口参数:hadc - 触发回调的 ADC 句柄
|
||
* 返 回 值:无
|
||
* 限定条件:仅 ISR 上下文;覆盖 HAL 的 __weak 实现(本板只有 ADC1)
|
||
* 函数说明:见 stm32_take() 的奇偶推半区口径。
|
||
*/
|
||
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
|
||
if (hadc == &hadc1) {
|
||
s_dma_seq++;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:DMA 半传输完成回调 = 前半区(下标 0)9 个采样就绪
|
||
* 入口参数:hadc - 触发回调的 ADC 句柄
|
||
* 返 回 值:无
|
||
* 限定条件:仅 ISR 上下文;覆盖 HAL 的 __weak 实现
|
||
* 函数说明:DMA 长度 18 = 2 x 9,故半传输即"一帧就绪",与传输完成交替出现。
|
||
*/
|
||
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) {
|
||
if (hadc == &hadc1) {
|
||
s_dma_seq++;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:ADC/DMA 错误回调
|
||
* 入口参数:hadc - 触发回调的 ADC 句柄
|
||
* 返 回 值:无
|
||
* 限定条件:仅 ISR 上下文;覆盖 HAL 的 __weak 实现
|
||
* 函数说明:只计数。错误后 DMA 可能已停 → 消费侧会看到 s_dma_seq 停增(rc=STALL、
|
||
* 每拍 skip),并由 IWDG 心跳日志暴露,不在 ISR 里尝试恢复。
|
||
*/
|
||
void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc) {
|
||
if (hadc == &hadc1) {
|
||
s_stm32_hw_errs++;
|
||
}
|
||
}
|