adcTask 事件驱动消费(P1-5 方案 a)+ 补 F4 ADC 上电自校准 + 修正 2 ms 忙等过期表述

P1-5 方案 a(事件驱动消费,Task #32):
- TIM6 1 kHz ISR 除触发 CONVST 外再调 adc_tick_irq_notify(),置位
  ADC_WAKE_FLAG;adcTask 循环尾改 osThreadFlagsWait(osWaitForever),
  删除 ADC_TASK_BEAT_MS 定时轮询
- adcTask 优先级 Normal(24) -> AboveNormal(32)(Src/freertos.c USER CODE 区)
- 补消费者就绪门(规划未预见):TIM6 在 app_main_init 就启动,比调度器
  早约 100 ms,未就绪时 ISR 不得 osThreadFlagsSet
- 实测 6 次上板:idle 由 1~649 降到 0/20000 拍,frames 1000.0 Hz,
  seq-frames=7 正是就绪窗口;UI 侧 uip/uiw/uis 无退化

补 STM32F4 ADC 上电自校准(此前项目从未做过):
- 核查证实裁剪版 HAL 无 HAL_ADCEx_Calibration_Start、裁剪版 CMSIS 头无
  ADC_CR2_CAL -> 按 RM0090 13.3.6 在 Src/adc.c 的 USER CODE 区手写
  adc1_calibrate(),MX_ADC1_Init() 末尾调用
- 新增 cubemx-survival 审计项 adc1-calibration-call / -impl(清单 21->23)
- 实测校准前后同一拍 STM32 侧 6 路同向偏移 +10~+63 LSB,TPAFE 侧不变

修正两处过期表述(实测反证):
- 原文 9 通道全超时最坏 = 18 ms 连占 有误:stm32_scan() 在首个超时处即
  Stop+return,单拍上界 = 一次超时约 2~3 ms
- adcm 约 2.1 ms 的峰值归因由 ADC 采集改判为每 10 s 一次的心跳日志;
  6 次对照 adcp = 122 us(无超时)/ 2209 us(超时 1 次)
- 同步修正 adc_task.c 两处过期注释;新发现超时拍 STM32 侧静默沿用
  上一拍值的正确性代价,硬化排序记入 4.4 / 10.4(未实施)

验证:@build 0 错 0 警(Code 256728 -> 256868);@test 6/6 PASS;
stack_report 7 任务全 PASS(adcTask 832 B / 余 59%);18 钩子全绿
This commit is contained in:
2026-10-10 22:41:19 +08:00
parent 1d1a9d4656
commit 9231a3c22c
16 changed files with 480 additions and 172 deletions
+67 -1
View File
@@ -141,7 +141,15 @@ void MX_ADC1_Init(void)
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* STM32F4 ADC 上电**自校准**:RM0090 §13.3.6 / AN4976 要求的必需步骤,CubeMX 不生成,
* 且本仓库的 HAL 是裁剪子集、**没有** HAL_ADCEx_Calibration_Start()(已核实
* stm32f4xx_hal_adc_ex.c/.h 均无该函数)→ 用本文件 USER CODE BEGIN 1 里的等价实现。
* 必须在首次转换之前完成,否则 ADC 存在失调误差(未校准的偏移可达数十 LSB)。
* 放 USER CODE 区 = L2,重生成不丢;由 cubemx-survival 审计项 adc1-calibration 兜底。 */
if (adc1_calibrate() != 0)
{
Error_Handler();
}
/* USER CODE END ADC1_Init 2 */
}
@@ -222,6 +230,64 @@ void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
}
/* USER CODE BEGIN 1 */
/* CR2.CAL(bit 3)。⚠ 本仓库的 CMSIS 设备头 stm32f407xx.h 是裁剪版,**未定义**
* ADC_CR2_CAL(已核实:CR2 只有 ADON/CONT/DMA/DDS/EOCS/ALIGN/JEXTSEL/JEXTEN/
* JSWSTART/EXTSEL/EXTEN/SWSTART,没有 CAL)
* → 按 RM0090 在此补齐,命名避开 CMSIS 前缀以免将来重定义冲突。 */
#define ADC1_CAL_BIT (0x1UL << 3U)
/* 断电/校准等待上限(ms)。CAL 实测在数十 µs 内清零,10 ms 是 100 倍以上余量 */
#define ADC1_CAL_TIMEOUT_MS (10U)
/* ADON 0→1 后的稳定等待(µs),同 HAL 的 ADC_STAB_DELAY_US 口径 */
#define ADC1_STAB_DELAY_US (20U)
/*
* 函数功能:ADC1 上电自校准(等价于 HAL_ADCEx_Calibration_Start,本仓库 HAL 无此函数)
* 入口参数:无(操作 ADC1 寄存器)
* 返 回 值:0 - 校准完成;-1 - 等待超时(ADON 未清零 / CAL 未清零)
* 限定条件:MX_ADC1_Init() 之后、**首次转换之前**;内核启动前可调用
* (只用 HAL_GetTick 与寄存器,不触碰任何 CMSIS/FreeRTOS 对象)
* 函数说明:RM0090 §13.3.6 时序——① ADC 必须处于关闭态(清 ADON 并等其生效);
* ② 置 ADON 上电,等 Tstab;③ 置 CAL 启动自校准;④ 完成后**硬件自动**清 CAL,
* 等其清零即完成。校准值存在 ADC 内部偏置寄存器,此后所有转换都带上校正。
* 缺这一步的后果不是"读不到"而是"读得有偏"——正是 §7.2 那类
* adc1_err/异常值的候选根因之一,见 docs/硬件平台/ADC通道映射说明.md。
*/
int adc1_calibrate(void)
{
uint32_t tickstart;
/* 1) 前置条件:ADC 必须关闭 */
CLEAR_BIT(ADC1->CR2, ADC_CR2_ADON);
tickstart = HAL_GetTick();
while ((ADC1->CR2 & ADC_CR2_ADON) != 0U)
{
if ((HAL_GetTick() - tickstart) > ADC1_CAL_TIMEOUT_MS)
{
return -1;
}
}
/* 2) 上电(ADON 0→1),等待稳定后再启动校准 */
SET_BIT(ADC1->CR2, ADC_CR2_ADON);
{
__IO uint32_t wait_loop_index = (ADC1_STAB_DELAY_US * (SystemCoreClock / 1000000U));
while (wait_loop_index != 0U)
{
wait_loop_index--;
}
}
/* 3) 启动自校准,等硬件把 CAL 清零 */
SET_BIT(ADC1->CR2, ADC1_CAL_BIT);
tickstart = HAL_GetTick();
while ((ADC1->CR2 & ADC1_CAL_BIT) != 0U)
{
if ((HAL_GetTick() - tickstart) > ADC1_CAL_TIMEOUT_MS)
{
return -1;
}
}
return 0;
}
/* USER CODE END 1 */
+8 -3
View File
@@ -125,13 +125,18 @@ const osThreadAttr_t timeSyncTask_attributes = {
.stack_size = 512 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* B1(2026-10-10):ADC 采集消费者(1ms 采样节拍 + 原子快照 + 帧统计)。
* 手写于此而非 .ioc(同 timeSyncTask:不在 Tasks01 里 → 重生成会丢,靠审计 WARN 提示)。 */
/* B1(2026-10-10):ADC 采集消费者(TIM6 1 kHz 采样节拍 + 原子快照 + 帧统计)。
* 手写于此而非 .ioc(同 timeSyncTask:不在 Tasks01 里 → 重生成会丢,靠审计 WARN 提示)。
* ⚠ 优先级 AboveNormal(Task #32 P1-5 方案 a,2026-10-10):消费改由 TIM6 ISR 事件驱动后
* 提优先级,使"被唤醒 → 立即抢占其余 Normal 任务",唤醒延迟固定为 µs 级。
* 代价:本任务单拍最坏实测 adcm≈2.1 ms 会在 AboveNormal 上挤占 net/ftp/default/timeSync,
* 其中 ADC 的 2 ms 忙等(App/algo/adc_input.c)尤其敏感 → 硬化方向见
* docs/子系统设计/ADC采样节拍设计说明.md §10.4。 */
osThreadId_t adcTaskHandle;
const osThreadAttr_t adcTask_attributes = {
.name = "adcTask",
.stack_size = 512 * 4,
.priority = (osPriority_t) osPriorityNormal,
.priority = (osPriority_t) osPriorityAboveNormal,
};
/* USER CODE END Variables */
/* Definitions for defaultTask */
+8 -2
View File
@@ -42,6 +42,7 @@
#include "app_main.h"
#include "sys_clock.h"
#include "task_watchdog.h" /* task_wd_tick_probe():round 8b 时间源存活性取证 */
#include "adc_task.h" /* adc_tick_irq_notify():TIM6 节拍 → 唤醒 adcTask(P1-5 方案 a) */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
@@ -246,13 +247,18 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
task_wd_tick_probe();
}
/* 1 kHz 采样节拍:TIM6 更新中断内脉冲 CONVST(仅两次 GPIO 写,亚 µs)。
/* 1 kHz 采样节拍:TIM6 更新中断内脉冲 CONVST(仅两次 GPIO 写,亚 µs),
* 再唤醒 adcTask 消费这一拍(Task #32 P1-5 方案 a:ISR 只发唤醒,计算全留在任务)。
* TIM6 与 BUSY EXTI(EXTI9_5_IRQn) 同为抢占优先级 6 → 互不抢占,
* 读 RD 时序不会被下一次 CONVST 脉冲打断。详见
* docs/子系统设计/ADC采样节拍设计说明.md。放 USER CODE 区(L2)。 */
* docs/子系统设计/ADC采样节拍设计说明.md §10。放 USER CODE 区(L2)。
* ⚠ 唤醒必须走 adc_tick_irq_notify(),**不要**在这里直接 osThreadFlagsSet:
* TIM6 在 app_main_init() 就启动,远早于建任务与 osKernelStart,
* "消费者就绪"门在 adc_task.c 里(同本文件 TIM7 分支 2026-10-02 的实测坑)。 */
if (htim->Instance == TIM6)
{
tpafe5160_start_conv_irq();
adc_tick_irq_notify();
}
/* USER CODE END Callback 1 */
}