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

11 KiB
Raw Blame History

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(已占用);TIM1TIM6、TIM8TIM14 当前全部空闲。

步骤 位置 值 说明
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 分支:
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(); 替换为启动周期触发:
    /* 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 验收待做