adc: 节拍源 TIM6->TIM3 + ADC1 走 DMA 乒乓双缓冲,消除 2 ms 忙等(P1-5 单拍硬上界办结)
用户选定最彻底方案(B2):DMA + 定时器 TRGO 硬件触发 + 消费上一拍 已完成的半区。对应 P1-5 拆出的"单拍硬上界",现已办结。 硬件事实更正(查 CubeMX 器件库 + 本仓 HAL 头证实):F4 上 ADC1 的 外部触发清单里没有 TIM6/TIM7 TRGO(只接 DAC)。故 1 kHz 节拍源由 TIM6 改为 TIM3(PSC 83 / ARR 999,TRGO=UPDATE),同一个更新事件 同时 ① ISR 脉冲 TPAFE CONVST ② 经 TRGO 触发 ADC1 一轮 9 通道 ③ 置位 ADC_WAKE_FLAG 唤醒 adcTask。TIM6 从 .ioc 整体移除。 代码: - Src/tim.c/h:新增 MX_TIM3_Init(含 MasterSlaveMode),删 htim6 - Src/adc.c:ExternalTrigConv=T3_TRGO + EDGE_RISING + DMAContinuousRequests=ENABLE;MspInit 挂 hdma_adc1(DMA2_Stream4 循环模式)。DDS 位关掉 = 一轮只发一次请求,静默丢 8 路 - Src/dma.c:DMA2_Stream4 NVIC 优先级 6(CubeMX 换流会把它重置为 0, 优先级 0 会抢占 FreeRTOS 临界区,违反"外设中断 ≥5") - Src/main.c:周期回调加 TIM3 分支;stm32f4xx_it.c 向量同步 - App/algo/adc_input.c:stm32_scan() → stm32_take(),逐通道 PollForConversion 全删;乒乓半区由完成计数奇偶推出(只读一个 32 位量 → 与 ISR 无竞态);STM32 侧数据固定滞后 1 拍 - App/task/adc_task.c:心跳日志拆出四类计数 观测性:adc1_seq(完成计数)/ adc1_gap(seq 前进 >1 = 丢帧)/ adc1_err(连续 ≥2 拍无新帧 = 停摆)/ adc1_hw(HAL 错误回调)。 单拍无新帧是消费者相位伪影(DMA 完成落在唤醒后 ≈67 µs),不计故障。 验证: - 主机单测 79 → 101 全绿;反向验证四连均咬(半区奇偶取反 11 条、 停摆拍置 valid 4 条、STALL_RUN_MAX 改 1 为 6 条、gap 阈值 1 条) - @build 0 错 0 警,Code 256872 → 257660 - @test PASS:adcp 122 µs → 8 µs、adc1_err=0、adc1_hw=0、 adc1_gap=27/20002 拍、adc1_seq 与 seq 恒定偏移 2 - stack_report 7/7 PASS,adcTask 832 B / 余 59%(不变) - cubemx-survival 新增 5 项审计(TIM3 分支 / T3_TRGO / DDS / DMA 挂载 / 流优先级),28 通过 0 失败 文档:ADC 采样节拍设计说明新增 §11;看门狗停摆分析 §4.4 与 §8 收口; 算法规划 L1-1/L1-3、通道映射、测试说明、各 README 同步。
This commit is contained in:
+16
-10
@@ -11,7 +11,7 @@
|
||||
|
||||
| 文件 | 层 | 作用(一句话) | 主要对外入口 | 覆盖规划项 |
|
||||
|---|---|---|---|---|
|
||||
| `adc_input.h` / `adc_input.c` | L1 | **统一语义取数层**:把两片转换器的原始码值按语义(`UA…IN`/温度)暴露,并驱动 STM32 ADC1 9 通道顺序扫描 | `adc_input_init()` `adc_input_poll()` `adc_input_raw()` | L1-2 / L1-3 / L1-4 |
|
||||
| `adc_input.h` / `adc_input.c` | L1 | **统一语义取数层**:把两片转换器的原始码值按语义(`UA…IN`/温度)暴露,并消费 STM32 ADC1 的 DMA 乒乓双缓冲 | `adc_input_init()` `adc_input_poll()` `adc_input_raw()` | L1-2 / L1-3 / L1-4 |
|
||||
| `sample_ring.h` / `sample_ring.c` | L2 输入 | **采样环缓冲**:按语义通道保存最近 2 个周波(40 点)采样值,供相量层"以当前时刻为终止"取窗 | `sample_ring_push_frame()` `sample_ring_window()` | L2 输入(为 L2-6 滑动窗供数) |
|
||||
| `phasor.h` / `phasor.c` | L2 | **全周傅氏相量计算**:一周波 20 点单频 DFT → 基波/谐波相量(有效值口径),双区原子切换供无锁读 | `phasor_run()` `phasor_get()` `phasor_calc()` | L2-1 / L2-2 / L2-3 / L2-6 / L2-7 |
|
||||
|
||||
@@ -19,14 +19,17 @@
|
||||
`sample_ring.c` / `phasor.c` **零平台依赖**(只用 `math.h` / `stddef.h` 与本目录头);
|
||||
`adc_input.c` 另依赖 `adc.h`(`hadc1`)与 `tpafe5160.h`(快照),主机侧以桩头替身。
|
||||
|
||||
## 2. 数据流(一拍 = TIM6 1 kHz 节拍)
|
||||
## 2. 数据流(一拍 = TIM3 1 kHz 节拍)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
TIM6["TIM6 更新中断 1 kHz<br/>Src/main.c → tpafe5160_start_conv_irq()<br/>+ adc_tick_irq_notify()"] --> TPAFE["TPAFE5160 转换<br/>BUSY EXTI 收帧"]
|
||||
TIM6 -. "ADC_WAKE_FLAG<br/>osThreadFlagsWait" .-> TASK
|
||||
TIM3["TIM3 更新事件 1 kHz(PSC 83 / ARR 999)<br/>ISR: tpafe5160_start_conv_irq() + adc_tick_irq_notify()<br/>同一事件经 TRGO 触发 ADC1"] --> TPAFE["TPAFE5160 转换<br/>BUSY EXTI 收帧"]
|
||||
TIM3 --> ADC["STM32 ADC1 一轮 9 通道(≈67 us)<br/>DMA2_Stream4 循环模式自动搬运"]
|
||||
ADC --> PP["乒乓双缓冲 s_stm32_dma[2×9]<br/>半传输 / 传输完成回调各推进 seq"]
|
||||
TIM3 -. "ADC_WAKE_FLAG<br/>osThreadFlagsWait" .-> TASK
|
||||
TPAFE --> POLL
|
||||
TASK["adcTask(事件驱动唤醒,非定时轮询)<br/>App/task/adc_task.c"] --> POLL["adc_input_poll()<br/>TPAFE 快照 + STM32 ADC1 9 通道扫描"]
|
||||
PP --> POLL
|
||||
TASK["adcTask(事件驱动唤醒,非定时轮询)<br/>App/task/adc_task.c"] --> POLL["adc_input_poll()<br/>TPAFE 快照 + 消费刚完成的那半区(零忙等)"]
|
||||
POLL --> RAW["adc_input_raw(ch) × 9<br/>按语义取原始码值"]
|
||||
RAW --> RING["sample_ring_push_frame()<br/>整帧入环(40 点窗)"]
|
||||
RING --> RUN["phasor_run()<br/>逐实例 20 点重算 + 双区切换"]
|
||||
@@ -48,11 +51,12 @@ flowchart TD
|
||||
|
||||
| API | 干什么 | 注意 |
|
||||
|---|---|---|
|
||||
| `adc_input_init()` | 清缓冲与统计 + **映射表自检**(每个 STM32 语义通道必须落在扫描 Rank 序列内) | 内核启动前调用;返回 `-1` = 映射指向了从不扫描的通道(会恒读陈旧值) |
|
||||
| `adc_input_poll()` | 采一拍:`tpafe5160_snapshot()` 原子快照(8 通道)+ STM32 ADC1 9 通道软件触发顺序扫描 | **仅任务上下文**(快照走临界区);只搬码值,不做标定/运算 |
|
||||
| `adc_input_init()` | 清缓冲与统计 + **映射表自检**(每个 STM32 语义通道必须落在扫描 Rank 序列内)+ `HAL_ADC_Start_DMA()` 挂载乒乓缓冲(**只挂一次**) | 内核启动前调用;`-1` = 映射指向了从不扫描的通道(会恒读陈旧值);`-2` = DMA 挂载失败 |
|
||||
| `adc_input_poll()` | 采一拍:`tpafe5160_snapshot()` 原子快照(8 通道)+ **消费刚完成的那半区**(`stm32_take()`,按 `seq` 奇偶选半区,零忙等) | **仅任务上下文**(快照走临界区);只搬码值,不做标定/运算;STM32 侧固定滞后 1 拍 |
|
||||
| `adc_input_raw(ch, &out)` | 按语义读原始码值(TPAFE 16 位补码 / STM32 0..4095) | 消费侧取数**唯一入口**;须先成功 `poll()`;`-2` = 该来源尚无有效数据 |
|
||||
| `adc_input_desc(ch)` | 语义 → 物理通道描述(来源 + 下标/通道号) | 供自检注入、UI 通道序;返回静态地址,勿改 |
|
||||
| `adc_input_stm32_rc()` / `adc_input_stm32_errs()` | STM32 侧最近扫描状态码 / 累计失败次数 | 心跳日志观测面(`adc1_rc` / `adc1_err`) |
|
||||
| `adc_input_stm32_rc()` | STM32 侧最近一拍状态码(0 OK / -2 本拍无新帧) | 心跳日志 `adc1_rc` |
|
||||
| `adc_input_stm32_seq()` / `_errs()` / `_gaps()` / `_hw_errs()` | DMA 整帧完成计数 / 停摆(连续 ≥2 拍无新帧)/ 丢帧(seq 一次前进 >1)/ HAL 错误回调 | 心跳日志 `adc1_seq` / `adc1_err` / `adc1_gap` / `adc1_hw`——四类成因不同,**勿混读** |
|
||||
|
||||
**要点**
|
||||
|
||||
@@ -60,7 +64,9 @@ flowchart TD
|
||||
`UA/UN/IA` → TPAFE `buf[6]/[5]/[2]`;`UB/UC/IB/IC/IN/TEMP` → ADC1 `CH14/3/6/1/2/5`。
|
||||
**V0.2 改版只改这张表的内容**,本层 API 与上层语义不动。
|
||||
- STM32 侧是**顺序扫描**(Rank1..8 = CH0..CH7、Rank9 = CH14,与 `MX_ADC1_Init()` 严格一致),
|
||||
9 个通道**不是同一时刻**;单通道轮询超时 2 ms,超时判失败、绝不拖垮消费节拍。
|
||||
9 个通道**不是同一时刻**(一轮 ≈67 µs)。v2.0 起这一轮由 **TIM3 TRGO 硬件触发 + DMA 自动搬运**,
|
||||
消费侧**零忙等**(旧实现逐通道轮询、单通道超时 2 ms,已于 2026-10-10 移除)。
|
||||
代价:STM32 侧数据**固定滞后 1 拍(1 ms)**——与 TPAFE 侧本来就没有同刻性(N-18),无新增误差。
|
||||
- 本文件是本层**唯一**允许出现 HAL 调用的地方(它必须驱动 ADC1);新增模块若不需要外设,
|
||||
请保持 `sample_ring` / `phasor` 那种零依赖形态,才能免桩单测。
|
||||
|
||||
@@ -165,5 +171,5 @@ flowchart TD
|
||||
- 通道映射权威口径:`docs/硬件平台/ADC通道映射说明.md`
|
||||
- 分层与路线图(L1/L2 清单与状态):`docs/算法/继电保护功能复现规划.md`
|
||||
- 傅氏算法与有效值口径:`docs/算法/傅氏算法详解.md`
|
||||
- 采样节拍(TIM6 1 kHz):`docs/子系统设计/ADC采样节拍设计说明.md`
|
||||
- 采样节拍(TIM3 1 kHz + TRGO 触发 ADC1/DMA):`docs/子系统设计/ADC采样节拍设计说明.md`
|
||||
- 相量层单测细则:`docs/测试说明/相量层单元测试说明.md`
|
||||
|
||||
+191
-51
@@ -1,12 +1,17 @@
|
||||
/*
|
||||
* 模块名称:ADC 输入层(L1 采集与预处理)
|
||||
* 模块功能:语义通道映射(L1-4)+ STM32 ADC1 顺序扫描(L1-3)+ TPAFE 快照消费。
|
||||
* 模块功能:语义通道映射(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 观测量
|
||||
*/
|
||||
|
||||
#include "adc_input.h"
|
||||
@@ -16,11 +21,17 @@
|
||||
#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
|
||||
/* 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 硬件确认的语义分配)。
|
||||
@@ -49,6 +60,17 @@ static const uint8_t k_stm32_rank_ch[ADC_INPUT_STM32_CH_NUM] = {
|
||||
|
||||
/* 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; /* 丢帧:seq 一次前进 >1(消费者超拍漏读)*/
|
||||
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 侧是否已有有效帧 */
|
||||
@@ -57,49 +79,59 @@ static int s_stm32_rc; /* STM32 最近一次扫描状态码 */
|
||||
static uint32_t s_stm32_errs; /* STM32 扫描累计失败次数 */
|
||||
|
||||
/*
|
||||
* 函数功能:STM32 ADC1 顺序扫描一次(L1-3)
|
||||
* 函数功能:消费一帧 STM32 ADC1 采样(L1-3,v2.0 DMA 双缓冲版)
|
||||
* 入口参数:无
|
||||
* 返 回 值: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 次。
|
||||
* 返 回 值:0 - 取到新一帧;-2 - 本拍无新帧(TIM3 未启动 / ADC 停摆 / DMA 出错)
|
||||
* 限定条件:仅任务上下文调用;adc_input_init() 已挂好 DMA
|
||||
* 函数说明:本函数**既不触发转换也不等待转换**——转换由 TIM3 TRGO 硬件触发、由 DMA
|
||||
* 自动搬运。每拍只做"看完成计数是否前进 + 拷 9 个半字",故**零忙等**:
|
||||
* 原轮询版常态 adcp=122 µs、超时拍最坏 2~3 ms,现在两档都降到 ~1 µs 量级。
|
||||
* 读的是"上一拍刚完成的那一半"乒乓缓冲(DMA 此刻正在写另一半)→ 无撕裂。
|
||||
* ⚠ 计数未前进 = 本拍 STM32 侧**无有效数据**,必须清 s_stm32_valid,绝不
|
||||
* 沿用上一拍旧值(同 2026-10-10 修的陈旧帧缺陷口径)→ adc_input_raw()
|
||||
* 返 -2 → adc_task 走既有"整帧跳过"路径;下一拍自动恢复。
|
||||
*/
|
||||
static int stm32_scan(void) {
|
||||
uint8_t i;
|
||||
static int stm32_take(void) {
|
||||
uint32_t seq = s_dma_seq; /* 单次 32 位读:与 ISR 自增不产生撕裂 */
|
||||
const uint16_t *src;
|
||||
uint8_t half;
|
||||
uint8_t i;
|
||||
|
||||
if (HAL_ADC_Start(&hadc1) != HAL_OK) {
|
||||
/* 失败拍必须清有效标志,见下方超时分支说明(同一语义) */
|
||||
if (seq == s_dma_seq_seen) {
|
||||
s_stm32_valid = 0u;
|
||||
s_stm32_rc = ADC_INPUT_RC_START;
|
||||
s_stm32_errs++;
|
||||
return ADC_INPUT_RC_START;
|
||||
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 §10.5) */
|
||||
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;
|
||||
}
|
||||
|
||||
if ((uint32_t)(seq - s_dma_seq_seen) > 1u) {
|
||||
/* 计数一次前进 >1 = 中间有帧没被读走(乒乓缓冲已被覆盖)→ **真丢帧**,
|
||||
* 与"无新帧"分开计数:前者是消费者超拍(本任务单拍耗时 > 1 ms),
|
||||
* 后者多半是相位伪影 */
|
||||
s_stm32_gaps++;
|
||||
}
|
||||
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++) {
|
||||
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);
|
||||
s_stm32_raw[k_stm32_rank_ch[i]] = src[i];
|
||||
}
|
||||
|
||||
(void)HAL_ADC_Stop(&hadc1);
|
||||
s_stm32_valid = 1u;
|
||||
s_stm32_rc = ADC_INPUT_RC_OK;
|
||||
return ADC_INPUT_RC_OK;
|
||||
@@ -145,10 +177,11 @@ static int map_check(void) {
|
||||
/*
|
||||
* 函数功能:ADC 输入层初始化
|
||||
* 入口参数:无
|
||||
* 返 回 值:0 - 成功;-1 - 语义映射表自检失败
|
||||
* 返 回 值:0 - 成功;-1 - 语义映射表自检失败;-2 - DMA 挂载失败
|
||||
* 限定条件:内核启动前调用(无 RTOS 依赖);STM32 ADC1 外设已由 MX_ADC1_Init 配好
|
||||
* 函数说明:清空原始码值缓冲与统计,并做一次映射表自洽性自检;本层不重复配置
|
||||
* 外设,也不启动任何转换(STM32 侧为软件触发,按拍扫描)。
|
||||
* 函数说明:清空原始码值缓冲与统计 + 映射表自洽性自检 + **挂载一次 ADC1 DMA**
|
||||
* (此后转换由 TIM3 TRGO 硬件触发、DMA 自动搬运,本层不再触发转换)。
|
||||
* 须在启动采样节拍(app_main.c 步骤 6)之前调用。
|
||||
*/
|
||||
int adc_input_init(void) {
|
||||
uint8_t i;
|
||||
@@ -156,6 +189,9 @@ int adc_input_init(void) {
|
||||
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;
|
||||
}
|
||||
@@ -164,17 +200,39 @@ int adc_input_init(void) {
|
||||
s_stm32_valid = 0u;
|
||||
s_stm32_rc = ADC_INPUT_RC_NOT_RUN;
|
||||
s_stm32_errs = 0u;
|
||||
return map_check();
|
||||
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 顺序扫描)
|
||||
* 函数功能:采集一拍(TPAFE 原子快照 + 消费一帧 STM32 ADC1 DMA 采样)
|
||||
* 入口参数:无
|
||||
* 返 回 值:TPAFE 快照结果(1=有新帧 0=无新帧 -1=参数错)
|
||||
* 限定条件:仅**任务上下文**调用(TPAFE 快照走临界区)
|
||||
* 函数说明:先取 TPAFE 一帧,再做 STM32 ADC1 扫描;两者相互独立,STM32 扫描
|
||||
* 失败不影响 TPAFE 快照与返回值语义(STM32 侧状态另由
|
||||
* adc_input_stm32_rc() 暴露)。
|
||||
* 函数说明:先取 TPAFE 一帧,再消费 STM32 侧最近一帧 DMA 双缓冲;两者相互独立,
|
||||
* STM32 侧无新帧不影响 TPAFE 快照与返回值语义(其状态另由
|
||||
* adc_input_stm32_rc() / adc_input_stm32_seq() 暴露)。
|
||||
* ⚠ STM32 侧数据比 TPAFE 侧**滞后 1 拍**(乒乓双缓冲的固有延迟,
|
||||
* 9 路一致);按 N-18,V0.1 本就禁止跨转换器做矢量运算。
|
||||
*/
|
||||
int adc_input_poll(void) {
|
||||
int r = tpafe5160_snapshot(s_tpafe_raw, (uint8_t)ADC_INPUT_TPAFE_CH_NUM);
|
||||
@@ -183,7 +241,7 @@ int adc_input_poll(void) {
|
||||
s_tpafe_valid = 1u;
|
||||
}
|
||||
|
||||
(void)stm32_scan();
|
||||
(void)stm32_take();
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -240,12 +298,94 @@ int adc_input_stm32_rc(void) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读取 STM32 ADC1 扫描累计失败次数
|
||||
* 函数功能:读取 STM32 ADC1 停摆故障累计次数
|
||||
* 入口参数:无
|
||||
* 返 回 值:累计失败次数(启动失败或转换超时均计入)
|
||||
* 返 回 值:DMA 挂载失败次数 + "连续 >=2 拍无新帧"的次数
|
||||
* 限定条件:无
|
||||
* 函数说明:单调递增,供心跳日志观测。
|
||||
* 函数说明:单调递增,供心跳日志观测。单拍无新帧(相位伪影)**不计入**,
|
||||
* 丢帧由 adc_input_stm32_gaps() 单独计数。
|
||||
*/
|
||||
uint32_t adc_input_stm32_errs(void) {
|
||||
return s_stm32_errs;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读取 STM32 ADC1 的丢帧次数
|
||||
* 入口参数:无
|
||||
* 返 回 值:DMA 完成计数一次前进 >1 的次数(中间那帧已被乒乓缓冲覆盖)
|
||||
* 限定条件:无
|
||||
* 函数说明:这是"消费者超拍"的直接证据(本任务单拍耗时 > 1 ms,如突发日志突发期);
|
||||
* 与 adc_input_stm32_errs()(节拍源停摆)是两类不同的故障。
|
||||
*/
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
+52
-16
@@ -3,7 +3,8 @@
|
||||
* 模块功能:把两片转换器的原始码值按**语义**(UA/UB/UC/UN/IA/IB/IC/IN/温度)统一
|
||||
* 暴露,使上层(UI / 自检 / 保护算法)**按语义取数,不写死下标**:
|
||||
* ① 语义通道映射表(L1-4)——权威口径 docs/硬件平台/ADC通道映射说明.md;
|
||||
* ② STM32 ADC1 9 通道软件触发顺序扫描(L1-3)。
|
||||
* ② STM32 ADC1 9 通道采样(L1-3)——由 **TIM3 TRGO 硬件触发 + DMA 双缓冲
|
||||
* 搬运**(v2.0),本层只消费、不触发、不等待。
|
||||
* 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)
|
||||
@@ -13,6 +14,10 @@
|
||||
* 创建日期:2026-10-10
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-10 王建锋 创建:L1-3(STM32 ADC1 扫描)+ L1-4(语义映射)
|
||||
* v2.0 2026-10-10 王建锋 STM32 侧改 TIM3 TRGO 硬件触发 + DMA 双缓冲消费:
|
||||
* 消除逐通道轮询的 2 ms 忙等;节拍源 TIM6 -> TIM3
|
||||
* (F4 上 ADC1 只认 TIM2/TIM3/TIM8 的 TRGO,TIM6 TRGO
|
||||
* 仅接 DAC);新增 adc_input_stm32_seq()/_hw_errs()
|
||||
*/
|
||||
|
||||
#ifndef ADC_INPUT_H
|
||||
@@ -56,29 +61,32 @@ typedef struct {
|
||||
/* 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) /* 尚未扫描过 */
|
||||
/* STM32 ADC1 最近一次取帧状态码(v2.0:DMA 消费,已无"轮询超时"这一档) */
|
||||
#define ADC_INPUT_RC_OK (0) /* 取到新一帧 */
|
||||
#define ADC_INPUT_RC_START (-1) /* HAL_ADC_Start_DMA 挂载失败 */
|
||||
#define ADC_INPUT_RC_STALL (-2) /* 本拍无新帧(TIM3 未启动 / ADC 停摆 / DMA 出错) */
|
||||
#define ADC_INPUT_RC_NOT_RUN (-3) /* 尚未取过帧 */
|
||||
|
||||
/*
|
||||
* 函数功能:ADC 输入层初始化
|
||||
* 入口参数:无
|
||||
* 返 回 值:0 - 成功;-1 - 语义映射表自检失败(映射指向了不会被扫描的通道)
|
||||
* 返 回 值:0 - 成功;-1 - 语义映射表自检失败(映射指向了不会被扫描的通道);
|
||||
* -2 - STM32 ADC1 的 DMA 挂载失败
|
||||
* 限定条件:内核启动前调用(无 RTOS 依赖);STM32 ADC1 外设已由 MX_ADC1_Init 配好
|
||||
* 函数说明:清空原始码值缓冲与统计,并做一次映射表自洽性自检;本层不重复配置外设。
|
||||
* 函数说明:清缓冲与统计 + 映射表自洽性自检 + 挂载一次 ADC1 DMA(此后转换由
|
||||
* TIM3 TRGO 硬件触发、DMA 自动搬运)。须在启动采样节拍之前调用。
|
||||
*/
|
||||
int adc_input_init(void);
|
||||
|
||||
/*
|
||||
* 函数功能:采集一拍(TPAFE 原子快照 + STM32 ADC1 顺序扫描)
|
||||
* 函数功能:采集一拍(TPAFE 原子快照 + 消费一帧 STM32 ADC1 DMA 采样)
|
||||
* 入口参数:无
|
||||
* 返 回 值:TPAFE 快照结果(1=有新帧 0=无新帧 -1=参数错);STM32 侧状态另见
|
||||
* adc_input_stm32_rc()
|
||||
* adc_input_stm32_rc() / adc_input_stm32_seq()
|
||||
* 限定条件:仅**任务上下文**调用(TPAFE 快照走临界区)
|
||||
* 函数说明:本函数只搬运原始码值,不做标定/运算。STM32 侧扫描失败不阻断 TPAFE
|
||||
* 快照,也不影响返回值语义。
|
||||
* 函数说明:本函数只搬运原始码值,不做标定/运算;STM32 侧**零忙等**(不触发、
|
||||
* 不等待转换)。STM32 侧无新帧不阻断 TPAFE 快照,也不影响返回值语义。
|
||||
* ⚠ STM32 侧数据滞后 1 拍(乒乓双缓冲固有延迟,9 路一致)。
|
||||
*/
|
||||
int adc_input_poll(void);
|
||||
|
||||
@@ -103,7 +111,7 @@ const adc_channel_desc_t *adc_input_desc(adc_semantic_t ch);
|
||||
int adc_input_raw(adc_semantic_t ch, int32_t *out);
|
||||
|
||||
/*
|
||||
* 函数功能:读取 STM32 ADC1 最近一次扫描状态码
|
||||
* 函数功能:读取 STM32 ADC1 最近一次取帧状态码
|
||||
* 入口参数:无
|
||||
* 返 回 值:ADC_INPUT_RC_xxx
|
||||
* 限定条件:无
|
||||
@@ -112,14 +120,42 @@ int adc_input_raw(adc_semantic_t ch, int32_t *out);
|
||||
int adc_input_stm32_rc(void);
|
||||
|
||||
/*
|
||||
* 函数功能:读取 STM32 ADC1 扫描累计失败次数
|
||||
* 函数功能:读取 STM32 ADC1 停摆故障累计次数
|
||||
* 入口参数:无
|
||||
* 返 回 值:累计失败次数(启动失败或转换超时均计入)
|
||||
* 返 回 值:DMA 挂载失败次数 + "连续 >=2 拍无新帧"的次数
|
||||
* 限定条件:无
|
||||
* 函数说明:单调递增,供心跳日志观测。
|
||||
* 函数说明:单调递增,供心跳日志观测。单拍无新帧(消费者相位伪影)不计入。
|
||||
*/
|
||||
uint32_t adc_input_stm32_errs(void);
|
||||
|
||||
/*
|
||||
* 函数功能:读取 STM32 ADC1 的丢帧次数
|
||||
* 入口参数:无
|
||||
* 返 回 值:DMA 完成计数一次前进 >1 的次数(中间那帧已被覆盖,未及消费)
|
||||
* 限定条件:无
|
||||
* 函数说明:与 errs 分开——gap = 消费者超拍(单拍耗时 > 1 ms),
|
||||
* err = 节拍源/ DMA 停摆。两类故障处置不同,混在一起就查不了。
|
||||
*/
|
||||
uint32_t adc_input_stm32_gaps(void);
|
||||
|
||||
/*
|
||||
* 函数功能:读取 STM32 ADC1 的 DMA 整帧完成计数
|
||||
* 入口参数:无
|
||||
* 返 回 值:DMA 半区/整区完成中断的累计次数(正常时每拍 +1)
|
||||
* 限定条件:无
|
||||
* 函数说明:与 tpafe5160_frame_seq() 对称的 STM32 侧"节拍是否还在喂数据"观测量。
|
||||
*/
|
||||
uint32_t adc_input_stm32_seq(void);
|
||||
|
||||
/*
|
||||
* 函数功能:读取 STM32 ADC1 的 HAL 层错误回调累计次数
|
||||
* 入口参数:无
|
||||
* 返 回 值:HAL_ADC_ErrorCallback 触发次数(DMA 传输错误等)
|
||||
* 限定条件:无
|
||||
* 函数说明:与 adc_input_stm32_errs() 分开计数,用于区分"节拍源停了"与"DMA 出错"。
|
||||
*/
|
||||
uint32_t adc_input_stm32_hw_errs(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user