Files
STM32F4-Base/Src/adc.c
T
wandering1 f5902bcc21 docs(design): 运行/自检记录与 ADC 节拍两篇改为现状设计并去掉档名"说明"
两篇规划期文档改成对当前系统的描述,档名统一去掉"设计说明"的"说明"。

运行与自检记录:
- 旧档重写为现状版(10 节:概览 / 记录布局 / 写入路径 / 读取与呈现 /
  事件分类 / 控制台日志 / 双端差异 / 内存 / 异常恢复 / 自测要点),
  删掉需求表、调研摘要、模块清单动作列、实施步骤与开放问题。
- 按代码纠正:七类记录齐备(原文只写了两类)、自检描述串换成实际
  落盘的 28 条、待机阈值以 ui_tokens.h 为准、记录页已走 ui_w_records
  共享组件、容量满只删该文件而非整卷清空。
- 待机屏移出本篇:它不属于日志,归 UI 侧;内容页架构补"待机屏
  (覆盖层)"一节,产品需求 FR-LOG-3 拆成单纯的上电/重启分类,
  待机显示归口已有的 FR-UI-3。

ADC 采样节拍:
- §4.5 从 45 行压到 24 行,只留规则 / 违反的代价 / 为什么 / 兜底与
  档案指针;九路置换实测表、A/B 对照、探针计数等取证归
  ADC_Trap_Records.md 的 TRAP-ADC-01(那里已有完整档案)。
- adc1_gap 字段去掉"2026-10-21 起、此前固件为次数"的修订记录式括号。
- app_main.c 的注释改为指向 TRAP-ADC-01 取证、§4.5 设计口径。

引用同步:两个旧档名全仓替换(含代码注释、角色档、归档 id 表),
旧名残留 0;对 ADC 篇的 § 引用逐条复核落在真实章节上。

验证:48 个提交期钩子全通过,check_doc_links --sections-strict 通过。
2026-11-15 21:30:07 +08:00

351 lines
12 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)
/* 关闭等待上限(ms)。ADON 由硬件清零,实测在数十 ns 内完成;10 ms 是十万倍余量 */
#define ADC1_OFF_TIMEOUT_MS (10U)
/*
* 函数功能: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;
}
/*
* 函数功能:关闭 ADC1(清 CR2.ADON 并等硬件清零生效)
* 入口参数:无
* 返 回 值:0 - 已关闭;-1 - 等待超时(ADON 未清零)
* 限定条件:任何上下文均可(只碰 ADC1 寄存器与 HAL_GetTick);须在挂 DMA 之前调用
* 函数说明:**本函数的存在只为把"使能 ADC"推迟到挂 DMA 的同一刻。**
* MX_ADC1_Init() 在 main() 里调用,而挂 DMA 在 app_main_init 步骤 4b,
* 中间隔着 GD5F 全片 BBT 扫描(数秒)。台架实测(同一次开机内用无 DMA
* 单通道探针取真值对照,9/9 完美置换):ADC 使能后闲置数秒才挂 DMA,
* DMA 乒乓缓冲会整体 +2 格旋转 —— 槽 i 装的是 rank ((i+1) mod 9)+1,
* 即 9 路通道按 rank 序整体错开 2 位,表现为"接地 AIN2、UB 却读到别的
* 通道的值"这种看似映射表写错的现象。
* 调用本函数后 ADC 处于关闭态,HAL_ADC_Start_DMA 内部会自己
* __HAL_ADC_ENABLE + 等 Tstab(~20 µs),然后紧接着开 CR2.DMA 并启流,
* 使能与搬运之间不再有可观测的间隔 → 旋转消失(台架 4/4 次,差值 ≤4 码)。
* 证据、复现方法与机制分析见 docs/子系统设计/ADC采样节拍设计.md §4.5。
*/
int adc1_disable(void)
{
uint32_t tickstart;
CLEAR_BIT(ADC1->CR2, ADC_CR2_ADON);
tickstart = HAL_GetTick();
while ((ADC1->CR2 & ADC_CR2_ADON) != 0U)
{
if ((HAL_GetTick() - tickstart) > ADC1_OFF_TIMEOUT_MS)
{
return -1;
}
}
return 0;
}
/* USER CODE END 1 */