Files
STM32F4-Base/docs/子系统设计/ADC采样节拍设计说明.md
T
wandering1 970e574433 feat(adc): 1 kHz 采样节拍改由 TIM6 硬件定时器触发
- CubeMX 加 TIM6(PSC=83 / ARR=999 / ARR 预装载 / NVIC 抢占优先级 6)
- Src/main.c 回调加 TIM6 分支触发 CONVST(USER CODE 区)
- app_main.c 用 HAL_TIM_Base_Start_IT(&htim6) 替换单次触发
- adc_task.c 退化为纯消费者(1 ms 轮询仅作消费节奏)
- @build 0 错 0 警;台架 @test 验收待做
- 同步 ADC采样节拍设计说明 / 继电保护复现规划 / AGENTS / TPAFE README
2026-10-10 15:24:02 +08:00

204 lines
11 KiB
Markdown
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 采样节拍设计说明(1 kHz 硬件定时器触发)
> **状态**:**已实现(2026-10-10)**——TIM6 更新中断(1 kHz)触发已落地(`Src/main.c` 回调 +
> `app_main.c` 启动 + `adc_task.c` 退化为纯消费者),`@build` 0 错 0 警;**台架 `@test` 验收(§7)待做**。
> **上位需求**:`docs/算法/继电保护功能复现规划.md` §3.2 L1-1(周期 CONVST 重触发 1000 Hz,
> 与保护节拍同源)。**驱动契约**:`Drivers/BSP/TPAFE5160/README.md`。
> **背景**:B1(2026-10-10)已把 `adc_task` 由空桩改为真实消费者,但采样节拍是
> **任务周期软触发**(`osDelayUntil`)——CONVST 边沿 = 任务被调度到的时刻,抖动大、
> 且与 FreeRTOS tick 同源。本项把节拍源换成**硬件定时器**。
---
## 1. 目标与现状
| 项 | 改造前(B1 软触发) | 改造后(TIM6 硬件定时器,2026-10-10 已落地) |
|---|---|---|
| CONVST 边沿时刻 | 任务被调度时刻(抖动 = 调度延迟,可达数十 µs~ms) | TIM 更新事件 + ISR 入口延迟(亚 µs~数 µs) |
| 周期精度 | 依赖 FreeRTOS tick(TIM7) | 定时器硬件分频,**晶振级** |
| 丢拍 | 任务被长时抢占会漏拍 | 硬件自动重载,不漏拍 |
| 等周波调频 | 需改 tick 周期(不可行) | 运行期写 ARR 即可(§8) |
**判据(L1-1)**:周期 CONVST 重触发 1000 Hz;示波器见 U3 `BUSY` 连续脉冲;
`tpafe5160_frame_seq()` 持续递增(每秒 +1000)。
---
## 2. 方案选型
### 2.1 方案 A(**推荐**):TIM6 更新中断 → ISR 内脉冲 CONVST
- TIM6 配成 1 kHz 更新中断;ISR 内调 `tpafe5160_start_conv_irq()`(现成的 GPIO 脉冲)。
- **零引脚改动**:PC9 保持 `GPIO_Output`,驱动与 `tpafe5160_init()` 首帧自检**完全不动**。
- BUSY(PC7)EXTI 读帧路径**完全不动**。
- 周期精度 = 定时器硬件(晶振级);CONVST 边沿抖动 = TIM6 ISR 入口延迟(见 §5)。
- 运行期改 ARR 即改变采样率 → 天然支持等周波调频。
### 2.2 方案 B(备选,暂不采用):TIM3_CH4/TIM8_CH4 硬件 PWM 直驱 PC9
- PC9 复用为定时器通道,硬件直接产生 CONVST 方波,**零软件抖动**。
- **代价**:① 需把 PC9 由 `GPIO_Output` 改为 AF(`.ioc` 引脚改动);
② 驱动的 `TPAFE5160_CONVST_*` 宏(GPIO 写)全部失效 → `tpafe5160_init()` 的
"确保 CONVST 低"与首帧自检(E-44)都要重写;③ 上电初值/空闲电平语义变化。
- **结论**:收益(把抖动从"数 µs"降到"0")不足以抵消改动面与回归风险。
**仅当 §5 实测抖动超过算法容限时再考虑**。
> 下文只展开**方案 A**。
---
## 3. CubeMX 配置规格(照此配置)
**前提**:SYSCLK = 168 MHz / HCLK = 168 MHz / APB1 = 42 MHz(÷4)→ **APB1 定时器时钟 = 84 MHz**;
HAL 时基 = TIM7(已占用);**TIM1~TIM6、TIM8~TIM14 当前全部空闲**。
| 步骤 | 位置 | 值 | 说明 |
|---|---|---|---|
| 1 | Timers → **TIM6** → *Activated* | ✅ 勾选 | 基础定时器,**无输出引脚**(不占用任何 IO) |
| 2 | Prescaler (PSC) | **83** | 84 MHz ÷ (83+1) = **1 MHz** 计数时钟(1 µs 分辨率) |
| 3 | Counter Period (ARR) | **999** | 1 MHz ÷ (999+1) = **1.000 kHz**(= 1000 µs 周期) |
| 4 | Counter Mode | Up | 向上计数 |
| 5 | **auto-reload preload** | **Enable** | 运行期写 ARR 在下一个更新事件生效,不产生毛刺(§8 等周波调频要用) |
| 6 | Clock Division | No Division | 与采样无关 |
| 7 | Trigger Event Selection | Reset(默认) | 不用 TRGO |
| 8 | NVIC Settings → **TIM6 global interrupt** | ✅ 勾选;**Preemption Priority = 6** | 与 BUSY EXTI(`EXTI9_5_IRQn` = 6)**同级**:两者互不抢占,避免"读 RD 时序被 CONVST 脉冲打断"(§5);且 6 ≥ `configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY`(=5),将来 ISR 里发任务通知也安全 |
**生成后**:CubeMX 会新建 `Src/tim.c` + `Inc/tim.h`(含 `htim6` 与 `MX_TIM6_Init()`)、
在 `Src/stm32f4xx_it.c` 生成 `TIM6_IRQHandler()`、在 `Src/main.c` 的 `main()` 里插入 `MX_TIM6_Init()`。
⚠ **`MX_TIM6_Init()` 只做配置,不启动计数**——启动由用户代码负责(§4 第 4 条)。
> **为何不用 TIM7 那种"1 MHz 计数 + ARR=999"以外的心跳**:本配置与 HAL 时基 TIM7
> 的构造完全一致(同为 84 MHz / 1 MHz / 1000 计数),便于对照排查;PSC 取 83 使 ARR 与
> "微秒"一一对应,等周波调频时可直接 `ARR = 周期µs - 1`。
---
## 4. 代码改造清单
> 约定:**生成区不改**(CubeMX 重生成会覆盖);所有手工代码落 `USER CODE` 保护区(L2)。
### 4.1 `Src/main.c`(CubeMX 生成;改 `USER CODE` 区)
- `USER CODE BEGIN Includes` 增加 `#include "tpafe5160.h"`。
- `HAL_TIM_PeriodElapsedCallback()`(**注意:该回调被 TIM7 时基共用**)增加 TIM6 分支:
```c
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM7) { HAL_IncTick(); } /* 既有:HAL 时基 */
/* USER CODE BEGIN Callback 0 / 1 —— 加在既有 TIM7 分支之后 */
if (htim->Instance == TIM6)
{
tpafe5160_start_conv_irq(); /* 1 kHz 采样节拍:ISR 内脉冲 CONVST(仅 GPIO 写,亚 µs) */
}
}
```
⚠ **不得**破坏既有 `HAL_IncTick()` / `task_wd_tick_probe()` 两条 TIM7 分支。
### 4.2 `Src/tim.c` / `Inc/tim.h` / `Src/stm32f4xx_it.c`
- **CubeMX 自动生成,勿手改**(`TIM6_IRQHandler` → `HAL_TIM_IRQHandler(&htim6)`)。
### 4.3 `App/app/app_main.c`
- 增加 `#include "tim.h"`。
- 第 213 行的**单次** `tpafe5160_start_conv_irq();` 替换为**启动周期触发**:
```c
/* 6) 启动 1 kHz 采样节拍(TIM6 更新中断 → ISR 内触发 CONVST) */
(void)HAL_TIM_Base_Start_IT(&htim6);
```
- ⚠ **必须在 `tpafe5160_init()` 成功之后**再启动 TIM6,否则 ISR 会在驱动自检
(首帧有效性探针,E-44)期间乱发 CONVST。
### 4.4 `App/task/adc_task.c`
- **删除**主循环里的 `tpafe5160_start_conv_irq();`(触发已交给 TIM6)。
- 本任务退化为**纯消费者**:`tpafe5160_snapshot()` + 帧统计 + 10 s 心跳 + 看门狗心跳。
1 ms 节拍(`osDelayUntil`)保留,仅用于"消费节奏"。
- 更新文件头与函数注释(说明节拍由 TIM6 提供,本任务不再触发)。
- **看门狗不变**:`TASK_WD_ADC` 心跳仍在循环内每拍调用。
### 4.5 文档同步(2026-10-10 已执行)
- `docs/算法/继电保护功能复现规划.md`:§3.2 L1-1 由 🔶 改为 ✅(TIM6 硬件触发)、§1.2 采样软件行 + 变更记录同步。
- `docs/待处理内容.md` §4:TPAFE 自动扫描模式待确认项标注"已不影响"(TIM6 方案不依赖它)。
- `AGENTS.md` 架构要点 `adcTask` 条 + `app_main_init` 序列:改为"TIM6 硬件触发 + 纯消费者"。
- `Drivers/BSP/TPAFE5160/README.md`:周期重触发改为"由 TIM6 更新中断提供"。
---
## 5. 时序与优先级分析
**单周期时序(1 kHz,周期 1000 µs)**:
```
t=0 TIM6 更新事件 → ISR → CONVST 脉冲(LOW→HIGH 上升沿)
t≈0+ε TPAFE5160 开始转换(BUSY 拉高)
t≈23 µs BUSY 拉低 → PC7 EXTI(优先级 6) → 读 8 通道(≈0.5 µs)→ seq++
t=1000 µs 下一个 TIM6 更新事件
```
- **读窗口与下一次 CONVST 相隔 ~977 µs**(转换仅 ~23 µs),互不重叠。
- **TIM6 与 BUSY EXTI 同级(6)→ 互不抢占**:读 RD 时序(8 次 RD 翻转)不会被打断,
避免"读到一半被新转换刷新数据总线"。这是选 6 而非 5 的原因。
- **触发抖动**:TIM6 是最高优先级之一(5 为 USART,7 为 SPI/DMA);被更高优先级 ISR
(USART,~数 µs)挡住的概率与时长都有限 → 最坏抖动约数 µs,占 1000 µs 的 <0.5%。
- **ISR 时长**:`tpafe5160_start_conv_irq()` 只做两次 `HAL_GPIO_WritePin`(寄存器写)
→ 亚 µs,不拖累其它中断。
---
## 6. 风险与注意事项
1. **共用回调**:`HAL_TIM_PeriodElapsedCallback` 同时服务 TIM7(HAL 时基)与 TIM6 →
分支必须并列、不得互相干扰;改动后必须确认开机日志时间戳正常(时基未坏)。
2. **启动顺序**:TIM6 必须在 `tpafe5160_init()` 之后启动(§4.3)。
3. **不新增任务/栈**:本项**零新任务、零新栈、零动态分配** → 不动 `stack_report.py` 预算。
4. **CubeMX 生存**:新增生成文件 `Src/tim.c` / `Inc/tim.h`;改后跑一次
`python scripts/check_cubemx_survival.py`,若有新增生成文件被登记,按提示重建基线。
5. **Keil 工程**:从 CubeMX 重生成工程(含 MDK-ARM)以把 `tim.c` 纳入编译;随后
`@build` 必须 0 错 0 警。
6. **`mem-numbers`**:本项改 Flash/RAM 极小但非零 → 若 `check_mem_numbers.py` FAIL,
跑 `python scripts/check_mem_numbers.py --write` 刷新快照。
7. **消费端与生产端同频**:TIM6(84 MHz 派生)与 FreeRTOS tick TIM7(同源)都恰好 1 kHz,
相位固定 → 消费端每拍恰好取到 1 帧。**一旦进入等周波调频(采样率≠1 kHz),
1 ms 轮询会与生产端失配 → 消费端须改为"帧就绪事件驱动"**(见 §8)。
---
## 7. 验收判据
| # | 判据 | 手段 |
|---|---|---|
| 1 | `@build` 0 错 0 警,且镜像无源文件新于 hex | `MDK-ARM/build.bat` + `find -newermt` |
| 2 | PC9 `CONVST` 周期 = 1.000 kHz(±0.1%) | 示波器 / 逻辑分析仪 |
| 3 | PC7 `BUSY` 每周期一个脉冲,与 CONVST 相位固定 | 示波器双通道 |
| 4 | `frame_seq()` 每秒 +1000(每 10 s +10000),严格递增 | `@test` 串口 `[ADC_TASK] frames=... seq=...` |
| 5 | `frst_err=0`、`idle` 基本为 0 | 同上 |
| 6 | 开机日志时间戳正常(TIM7 时基未坏) | 串口日志 |
| 7 | 无 HardFault / `[ERR]` / 看门狗复位 | `@test` Forbidden 判定 |
---
## 8. 后续:等周波调频(不在本项范围)
参考系统按 **20 点/周波**采样:电网频率漂移时需让采样率跟随(50.0 Hz → 1000 Hz;
49.5 Hz → 990 Hz),以保持傅氏全波窗对齐一个工频周期。
- 本配置已为此预留:**1 µs 分辨率** + **ARR 预装载使能** → 运行期
`__HAL_TIM_SET_AUTORELOAD(&htim6, 周期µs - 1)` 即可无毛刺改频。
- 需要配套:① 测频源(过零/DFT 相位);② 把当前采样率同步给保护算法(相量/定值);
③ **消费端改事件驱动**(§6.7)——否则 1 ms 轮询在非 1 kHz 采样率下会丢帧。
---
## 9. 变更记录
| 日期 | 变更 | 说明 |
|---|---|---|
| 2026-10-10 | 建立本文档 | B1(ADC 采集链路)遗留项"精确 1 kHz 硬件定时器触发"的规划:方案选型(A/B)、CubeMX 配置规格、代码改造清单、时序与优先级分析、风险与验收判据、等周波调频预留 |
| 2026-10-10 | **实现(方案 A)** | CubeMX 加 TIM6(PSC=83 / ARR=999 / ARR 预装载 / NVIC 抢占优先级 6);`Src/main.c` 回调加 TIM6 分支触发 CONVST;`app_main.c` 用 `HAL_TIM_Base_Start_IT(&htim6)` 启动节拍;`adc_task.c` 退化为纯消费者。`@build` 0 错 0 警;台架 `@test` 验收待做 |