9bbe117f97
用户选定最彻底方案(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 同步。
316 lines
10 KiB
C
316 lines
10 KiB
C
/* 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 */
|
||
|