Files
STM32F4-Base/Src/adc.c
T
wandering1 9bbe117f97 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 同步。
2026-10-11 00:31:33 +08:00

316 lines
10 KiB
C
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.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file adc.c
* @brief This file provides code for the configuration
* of the ADC instances.
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "adc.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
/* ADC1 init function */
void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 9;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_144CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = 3;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_3;
sConfig.Rank = 4;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = 5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_5;
sConfig.Rank = 6;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_6;
sConfig.Rank = 7;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_7;
sConfig.Rank = 8;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_14;
sConfig.Rank = 9;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
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 */
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(adcHandle->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspInit 0 */
/* USER CODE END ADC1_MspInit 0 */
/* ADC1 clock enable */
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/**ADC1 GPIO Configuration
PA0-WKUP ------> ADC1_IN0
PA1 ------> ADC1_IN1
PA2 ------> ADC1_IN2
PA3 ------> ADC1_IN3
PA4 ------> ADC1_IN4
PA5 ------> ADC1_IN5
PA6 ------> ADC1_IN6
PA7 ------> ADC1_IN7
PC4 ------> ADC1_IN14
*/
GPIO_InitStruct.Pin = ADC9_RES_Pin|ADC10_IC_Pin|ADC11_IN_Pin|ADC12_UC_Pin
|ADC13_RES_Pin|ADC14_TEMP_Pin|ADC15_IB_Pin|ADC16_RES_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC17_UB_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ADC17_UB_GPIO_Port, &GPIO_InitStruct);
/* ADC1 DMA Init */
/* ADC1 Init */
hdma_adc1.Instance = DMA2_Stream4;
hdma_adc1.Init.Channel = DMA_CHANNEL_0;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);
/* USER CODE BEGIN ADC1_MspInit 1 */
/* USER CODE END ADC1_MspInit 1 */
}
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if(adcHandle->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspDeInit 0 */
/* USER CODE END ADC1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_ADC1_CLK_DISABLE();
/**ADC1 GPIO Configuration
PA0-WKUP ------> ADC1_IN0
PA1 ------> ADC1_IN1
PA2 ------> ADC1_IN2
PA3 ------> ADC1_IN3
PA4 ------> ADC1_IN4
PA5 ------> ADC1_IN5
PA6 ------> ADC1_IN6
PA7 ------> ADC1_IN7
PC4 ------> ADC1_IN14
*/
HAL_GPIO_DeInit(GPIOA, ADC9_RES_Pin|ADC10_IC_Pin|ADC11_IN_Pin|ADC12_UC_Pin
|ADC13_RES_Pin|ADC14_TEMP_Pin|ADC15_IB_Pin|ADC16_RES_Pin);
HAL_GPIO_DeInit(ADC17_UB_GPIO_Port, ADC17_UB_Pin);
/* ADC1 DMA DeInit */
HAL_DMA_DeInit(adcHandle->DMA_Handle);
/* USER CODE BEGIN ADC1_MspDeInit 1 */
/* USER CODE END ADC1_MspDeInit 1 */
}
}
/* 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 */