ADC部分理论知识
148.6.5 ADC部分理论知识
/* 基于 KEIL/7ADC3_DMA 工程的关键代码。
* 注意:工程快照遗漏 ADC 时钟分频,下面 SystemClock_Config 中的 /6 是按参考手册补上的必要修正。 */
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "uart.h"
uint16_t AD_Buf[2]; // DMA 按 Rank1、Rank2 依次写入 PA1、PA2 的 12 位结果
void Init_DMA(void)
{
__HAL_RCC_DMA2_CLK_ENABLE();
HAL_NVIC_SetPriority(DMA2_Channel4_5_IRQn, 0, 0); // 大容量 F103 的 Ch4/5 共用向量
HAL_NVIC_EnableIRQ(DMA2_Channel4_5_IRQn);
}
void Init_ADC3(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc3.Instance = ADC3;
hadc3.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc3.Init.ContinuousConvMode = ENABLE;
hadc3.Init.DiscontinuousConvMode = DISABLE;
hadc3.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc3.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc3.Init.NbrOfConversion = 2;
HAL_ADC_Init(&hadc3);
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
HAL_ADC_ConfigChannel(&hadc3, &sConfig);
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = ADC_REGULAR_RANK_2;
HAL_ADC_ConfigChannel(&hadc3, &sConfig);
}
void SystemClock_Config(void)
{
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/* HSE x9、PCLK2=72 MHz 的其余系统时钟配置与工程快照一致。 */
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6; // 72 / 6 = 12 MHz,不超过 14 MHz
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
Init_GPIO();
Init_DMA();
Init_UART1();
Init_ADC3();
HAL_ADCEx_Calibration_Start(&hadc3);
HAL_ADC_Start_DMA(&hadc3, (uint32_t *)AD_Buf, 2);
while (1)
{
printf("ADC3_DMA PA1= V:%1.2f Value:%d\n\r", AD_Buf[0] * 3.3 / 4096.0, AD_Buf[0]);
printf("ADC3_DMA PA2= V:%1.2f Value:%d\n\r", AD_Buf[1] * 3.3 / 4096.0, AD_Buf[1]);
HAL_Delay(100);
}
}
一、dma模式的移植
本节使用 视频配套/3、STM32单片机裸机部分/code/KEIL/7ADC3_DMA,将此前 ADC-DMA 实验从 ADC1 改为 ADC3。移植的关键不是全局替换名字,而是重新核对 芯片型号是否有 ADC3、ADC3 的 DMA 固定映射、IRQ 名称和 ADC 时钟。
1.1 复制工程
- 视频从上一节工程复制出一个新工程,再命名为
7ADC3_DMA。复制工程能复用时钟、GPIO、串口和打印重定向等已验证部分,但也会继承旧外设配置,不能认为“能编译”就代表移植正确。 - 工程名只是目录和工程文件标识,不会选择 ADC 外设;真正选择 ADC3 的位置在
hadc3.Instance = ADC3、ADC3 时钟使能、DMA 通道和主函数启动参数中。 - 本实验必须使用具有 ADC3 和 DMA2 的 STM32F103 大容量型号,例如工程所用 STM32F103ZET6。较小容量的 F103 型号可能没有 ADC3/DMA2,不能照搬。
1.2 添加头文件
#include "dma.h"让使用Init_DMA()的源文件看见函数声明;头文件只声明接口,不会自动产生函数实现。dma.c存放Init_DMA()的定义,dma.h存放其声明。文件必须同时加入工程编译列表,否则即使包含了头文件,链接阶段仍会报“未定义的符号”。- 推荐的依赖关系为:
main.c包含adc.h、dma.h、uart.h;dma.c包含dma.h;adc.c包含adc.h。不要在头文件中互相无条件包含全部模块,以免形成循环依赖。
1.3 初始化dma
Init_DMA()至少应完成两件事:使能 DMA2 控制器时钟__HAL_RCC_DMA2_CLK_ENABLE(),并配置 ADC3 对应的 DMA2 Channel5。- DMA 配置本身位于
HAL_ADC_MspInit()的 ADC3 分支:方向为外设到内存、ADC 数据寄存器地址不递增、数组地址递增、两端都按半字(16 位)访问、循环模式。 - 仅仅“打开 DMA 时钟”不能传输数据;还需要
__HAL_LINKDMA(adcHandle, DMA_Handle, hdma_adc3)把 ADC3 句柄和 DMA 句柄关联,最后由HAL_ADC_Start_DMA()同时启动转换和传输请求。
1.4 添加c文件
- 新建
dma.c后,需要在其中定义void Init_DMA(void),并将文件加入 Keil 工程的源文件组。只在资源管理器中创建文件但未加入工程,Keil 不会编译它。 - 本工程的
dma.c只负责 DMA2 时钟和 NVIC;具体的DMA2_Channel5参数因 DMA 与 ADC3 的绑定关系在adc.c的HAL_ADC_MspInit()中初始化。两处职责不同,不应重复初始化同一个 DMA 句柄。
1.5 添加头文件
dma.h需要包含main.h,使void Init_DMA(void);所在的工程拥有 HAL 类型和基础定义;同时要使用包含保护:
#ifndef __DMA_H__
#define __DMA_H__
#include "main.h"
void Init_DMA(void);
#endif
- 包含保护防止同一个头文件被间接包含多次造成重复声明。宏名称应专用于当前文件,例如
__DMA_H__;复制adc.h模板后必须改名,不能保留__ADC_H__。
1.6 修改头文件
- 可以以
adc.h为模板创建dma.h,但需要保留#include "main.h"和包含保护,并删除与 ADC 句柄、Init_ADCx()无关的声明。 - 原始工程中 ADC3 句柄由
adc.c定义、由adc.h以extern ADC_HandleTypeDef hadc3;声明;DMA3 句柄同理应由adc.c定义、需要在中断文件中以extern DMA_HandleTypeDef hdma_adc3;引用。 - 复制后只通过搜索替换
adc为dma容易误伤标识符和注释。完成替换后必须逐项检查函数名、包含保护、句柄定义位置和被包含的文件。
1.7 声明函数
- 函数声明应在
dma.h中,调用它的main.c必须#include "dma.h"。否则编译器在编译main.c时无法知道Init_DMA()的原型。 - C 语言中“能看到声明”与“存在定义”是两回事:缺声明通常是编译警告或错误,缺定义通常是链接错误。二者都要通过头文件、源文件和工程文件列表一并检查。
- 保存、重新编译后错误消失,只表示声明/链接关系已建立;仍需确认初始化调用顺序是
Init_DMA()在HAL_ADC_Start_DMA()之前。
1.8 编译工程
- 从 ADC1-DMA 代码复制配置后,不能继续使用
DMA1_Channel1。STM32 的 DMA 请求映射由芯片硬件固定,软件只能选择手册允许的通道,不能任意把 ADC3 绑定到 DMA1 任一通道。 - STM32F103 大容量产品的 DMA1 有 7 个通道、DMA2 有 5 个通道。参考手册明确 ADC3 的 DMA 请求只存在于大容量产品;本工程应使用 DMA2 Channel5。
- 编译器不能检查“外设请求是否接到了正确 DMA 通道”。错误通道仍可能通过编译,却表现为 DMA 缓冲区始终为 0 或从不更新,因此必须查 DMA 请求映射表和实际芯片型号。
1.9 相关手册
- 查 DMA 映射时使用 STM32F103 对应的参考手册 DMA 章节,而不是只看 IDE 自动补全。参考手册说明 DMA 通道与外设请求的连线由硬件固定,并明确 ADC3 DMA 请求仅在大容量产品存在。
- 查中断处理函数名时看启动文件的向量表。例如本工程的
startup_stm32f103xe.s包含DMA2_Channel4_5_IRQHandler,这才是大容量 STM32F103 对 DMA2 Channel5 的正确入口名。 - 查引脚/封装时看数据手册或 CubeMX 引脚图;查寄存器、DMA 映射和时序时看参考手册。两类手册不能互相替代。
1.10 修改之前添加的dma头文件
- DMA2 Channel4 和 Channel5 是两个独立传输通道,配置时仍应使用具体的
DMA2_Channel5;不要因为中断名里有 “4_5” 就把 ADC3 配到 Channel4。 - 对 STM32F103 大容量产品,DMA2 Channel4 和 Channel5 共用同一个 NVIC 向量,因此中断函数名为
DMA2_Channel4_5_IRQHandler。该 ISR 内应把hdma_adc3传给HAL_DMA_IRQHandler(),由 HAL 根据 Channel5 状态位处理事件。 - ADC1 的 DMA 请求走 DMA1 Channel1;ADC3 的 DMA 请求走 DMA2 Channel5;ADC2 没有独立 DMA 请求,但可在双 ADC 模式中借助 ADC1 的 DMA 通路传输。原始笔记中“ADC2 无 DMA 支持”的说法应理解为“不能单独直接产生 DMA 请求”,不能误解为 ADC2 在所有 DMA 场景都不能使用。
1.11 修改adc头文件
- 将 ADC1/ADC2 迁移到 ADC3 时,应在
adc.h声明extern ADC_HandleTypeDef hadc3;和void Init_ADC3(void);,并在adc.c中定义同名句柄和函数。 - 选择 ADC3 的前提是目标芯片有 ADC3。不能为了 DMA 而在没有 ADC3 的 MCU 上把
ADC1字样直接改成ADC3;这种代码通常无法编译或无法在目标硬件上工作。 - 本实验使用 ADC3 的 Channel1、Channel2,分别连接 PA1、PA2。通道号与引脚的对应关系应以 STM32F103ZET6 的资料为准,而不是根据 ADC1 的命名猜测。
1.12 修改工程代码
1.12.1 代码初始化与函数修改
- 主函数中注释掉旧的
Init_ADC2(),调用Init_ADC3();随后应对hadc3校准并调用HAL_ADC_Start_DMA(&hadc3, ...)。旧 ADC2 初始化若保留且没有使用,可以删除以免混淆。 Init_ADC3()只负责 ADC3 参数和规则组通道;DMA 时钟需先由Init_DMA()打开,USART 初始化需在printf前完成。- 推荐顺序:系统时钟(含 ADC 分频)→ GPIO → DMA → USART → ADC3 → 校准 → 启动 ADC-DMA。这样可以在 ADC-DMA 关联和时钟都就绪后再发出转换请求。
1.12.2 函数声明与结构体定义
hadc3和hdma_adc3是两个不同的 HAL 句柄:前者描述 ADC3 外设,后者描述 DMA2 Channel5。__HAL_LINKDMA让hadc3.DMA_Handle指向后者。- 句柄只能在一个
.c文件中真正定义,其他文件用extern声明。多处定义会导致重复符号;没有外部声明则中断文件无法访问hdma_adc3。 - 函数名必须保持声明、定义、调用三处一致,例如都使用
Init_ADC3,不能混用init_adc3、adc_start等相近拼写。
1.12.3 ADC3初始化与配置
hadc3.Instance = ADC3指定外设基地址;其余初始化字段也要逐项复核,不能因为“模板看起来相同”就跳过。- 当前工程设置
ScanConvMode=ENABLE、NbrOfConversion=2,规则组 Rank1 为 Channel1/PA1,Rank2 为 Channel2/PA2;因此AD_Buf[0]对应 PA1,AD_Buf[1]对应 PA2。 ContinuousConvMode=ENABLE使一个两通道规则序列结束后自动开始下一轮;DiscontinuousConvMode=DISABLE,不能与连续规则转换混用。DMA 采用DMA_CIRCULAR,两次半字传输后从缓冲区开头覆盖写入。- 每路均使用 239.5 个 ADC 时钟周期的采样时间。长采样时间有利于高源阻抗传感器,但会降低每轮序列的最高频率。
1.12.4 ADC与DMA配置细节
HAL_ADC_MspInit()可通过if (adcHandle->Instance == ADC2)与if (adcHandle->Instance == ADC3)分支服务多个 ADC。ADC3 分支必须使能__HAL_RCC_ADC3_CLK_ENABLE(),并初始化hdma_adc3。- 源码中 ADC3 分支的注释仍写着 “ADC1 clock/GPIO/DMA Init”,这是复制遗留的注释错误;实际执行的是 ADC3 时钟和 DMA2 Channel5,阅读时应以代码标识符为准。
- DMA 参数含义:外设端为 ADC3 DR,地址固定;内存端为
AD_Buf,地址递增;两端半字对齐;方向外设到内存;循环模式持续覆盖两个数组元素;优先级 Low 只表示与其他 DMA 请求竞争时的仲裁级别。
1.12.5 GPIO与ADC时钟配置
- ADC3 的 Channel1/2 在本工程使用 PA1/PA2,GPIOA 时钟必须使能,两个引脚配置为
GPIO_MODE_ANALOG。模拟模式不是普通数字输入模式,能避免数字输入缓冲带来的额外干扰。 - PA1 应接光敏传感器 AO,PA2 接 3V3 作为已知电压参考演示。两者均要与开发板 GND 共地;ADC 输入只能落在 VSSA~VDDA 范围,传感器若由 5 V 供电且 AO 可能达到 5 V,必须先分压或做电平转换。
- 必须修正 ADC 时钟:工程
temp.ioc显示RCC.ADCFreqValue=36000000,而SystemClock_Config()也没有调用HAL_RCCEx_PeriphCLKConfig()设置 ADC 分频。在 PCLK2=72 MHz 时,默认/2会得到 36 MHz,超过 STM32F103 的 14 MHz 上限。应显式选择RCC_ADCPCLK2_DIV6得到 12 MHz;这不是可选优化,而是硬件规格要求。
1.12.6 主函数修改与ADC校准
HAL_ADCEx_Calibration_Start(&hadc3)应在 ADC3 初始化后、HAL_ADC_Start_DMA()前调用。参考手册建议每次上电后校准一次,HAL 会处理关停、使能、等待和校准寄存器复位等前置步骤。HAL_ADC_Start_DMA(&hadc3, (uint32_t *)AD_Buf, 2)的长度 2 是 DMA 数据项数量,必须同时匹配AD_Buf[2]和两个规则组 Rank。若长度、数组容量、Rank 数三者不一致,会发生漏数据、顺序错误或数组越界。- 主循环的
HAL_Delay(100)控制的是串口打印和 LED 翻转节奏,不会限制 ADC3-DMA 的采样频率;ADC 仍按连续转换和 DMA 循环高速覆盖AD_Buf。
1.12.7 变量定义与打印修改
AD_Buf应为uint16_t AD_Buf[2]。ADC 结果实际只有 12 位,但 DMA 配置为半字传输,16 位数组与数据宽度匹配。- 打印文本改为
ADC3_DMA PA1、ADC3_DMA PA2,用于和旧工程区分。电压换算Raw * 3.3 / 4096.0假定 VDDA=3.3 V,原始码范围是 0~4095;真实项目应使用实测 VDDA。 - 同一个 DMA 缓冲区在后台持续改写,先读
AD_Buf[0]后读[1]并不保证来自同一轮扫描。演示打印通常足够;需一致快照时应在 DMA 半传输/全传输回调中复制缓冲区或使用双缓冲。
1.13 编译错误
- 视频中的编译问题来自复制时漏掉分号或括号。这类错误应以编译器给出的文件、行号和首个语法错误为起点修复,后续连锁错误往往会随之消失。
- 编译通过不代表硬件通路正确。DMA 映射错误、ADC 时钟超限、GPIO 接线错误通常不会产生 C 编译错误,必须结合手册、寄存器配置和实际数据检查。
- DMA 中断如果被 HAL 的
HAL_ADC_Start_DMA()以中断方式启动,则 NVIC 和 ISR 要正确匹配;若不需要回调,也可以不启用 DMA 中断,但必须采用与 HAL 调用方式匹配的实现,不能留下一个启用却没有正确处理的 IRQ。
1.14 添加中断处理函数
- 大容量 STM32F103 的正确函数名是
DMA2_Channel4_5_IRQHandler,不是DMA1_Channel1_IRQHandler,也不是不存在的DMA2_Channel5_IRQHandler。 stm32f1xx_it.c中需要extern DMA_HandleTypeDef hdma_adc3;,中断服务函数中调用HAL_DMA_IRQHandler(&hdma_adc3);。该调用会清除/分发 DMA 事件,并进一步触发 HAL 的 ADC DMA 回调。- 中断函数名必须与启动文件向量表中的符号完全相同;拼写不同、通道不同或漏掉
extern都会导致中断落入默认处理函数或无法链接。
1.15 查看中断处理函数名称
- 最可靠的查法是打开目标芯片对应启动文件的向量表。本工程
startup_stm32f103xe.s在 DMA2 Channel3 后列出DMA2_Channel4_5_IRQHandler,说明 Channel4/5 共用此入口。 Ctrl+H/全局搜索可辅助定位,但搜索结果必须与当前芯片的启动文件核对。互联型 F1 与大容量 F1 的 DMA2 Channel4/5 向量安排不同,不能跨型号复制 IRQ 名称。- NVIC 枚举
DMA2_Channel4_5_IRQn、启动文件函数名和HAL_NVIC_EnableIRQ()的参数三者必须成对出现。
1.16 编译成功
1.16.1 编译过程与文件概览
- 无错误、仅有警告表示目标文件和链接产物已生成,但应消除与 DMA、格式串、未使用函数相关的重要警告。不能因程序“能烧写”而忽略警告。
HAL_DMA_IRQHandler()、HAL_ADC_Start_DMA()等 HAL 库函数提供通用状态处理;理解其职责有帮助,但应用层至少要确认句柄、通道、NVIC 和 DMA 请求映射正确。
1.16.2 DMA传输与中断管理
- 视频中上电后无输出、复位后才打印,随后数据全为 0。这种现象不能仅凭一次复位归因,应该分别确认串口是否已经启动、DMA 是否有传输完成标志、ADC 是否在转换,以及输入引脚是否有有效电压。
- DMA 传输本身由 ADC 请求驱动,不依赖 CPU 在主循环中手动搬运;中断用于在半传输/完成事件发生时通知 CPU 或清理 HAL 状态。若只打印循环缓冲区,仍需要保证 DMA 通路本身已正确配置。
1.16.3 代码追踪与问题排查
- 排查顺序应从时钟、外设使能、DMA 映射、GPIO/Ranks、启动函数、缓冲区长度、IRQ 依次向下:
| 检查项 | ADC3-DMA 工程的正确值 |
|---|---|
| ADC 时钟 | PCLK2/6 = 12 MHz |
| ADC 外设 | ADC3,时钟已使能 |
| 规则组 | Channel1/PA1 为 Rank1;Channel2/PA2 为 Rank2 |
| DMA 请求 | DMA2 Channel5 |
| 数据宽度 | 外设/内存均为 16 位半字 |
| 缓冲区和长度 | uint16_t AD_Buf[2] 与 Length=2 |
| IRQ | DMA2_Channel4_5_IRQHandler 调用 HAL_DMA_IRQHandler(&hdma_adc3) |
- 逐行检查时不要跳过“看起来没改”的字段。视频最终发现的正是 DMA 通道仍保留为 ADC1 的
DMA1_Channel1,这类遗漏会让数据通路失效。
1.16.4 ADC与DMA配置问题
- 不要把 USART1、ADC1、ADC2、ADC3 的初始化代码混在一起。串口仅负责输出文本,ADC3 决定采样,DMA2 Channel5 负责搬运;一个模块名字改对并不能修复另一个模块。
- 如果
AD_Buf始终为 0,先测 PA1/PA2 实际电压,再检查 ADC3 的 EOC/DMA 标志和 DMA2 Channel5 配置;不要先怀疑printf浮点显示。 - 源码中遗留的 ADC1 注释、
Init_ADC2()函数和已注释的调用都是复制痕迹。学习时只追踪当前执行路径:Init_ADC3、hadc3、hdma_adc3、DMA2_Channel5。
1.16.5 DMA通道配置错误排查
- ADC3 不能使用 DMA1 Channel1;那条通路是 ADC1 专用。把通道写错时,代码通常能通过编译,但 DMA 不会收到 ADC3 请求。
- 应在三处交叉检查:
hdma_adc3.Instance = DMA2_Channel5、DMA2 时钟使能、NVIC 的DMA2_Channel4_5_IRQn。三处任何一处残留 DMA1 都是不完整移植。 - ADC2 的旧配置即使仍存在于源码,若未调用
Init_ADC2()不会参与当前工作;但建议清理无用初始化,减少后续排错范围。
1.16.6 DMA通道定义与参数设置
- DMA2 Channel4 和 Channel5 的配置寄存器和通道实例彼此独立,ADC3 必须绑定
DMA2_Channel5。 - 两个通道共享的是中断向量,因此中断枚举和 ISR 名字包含
Channel4_5。不要从共享 IRQ 反推出共享同一个 DMA 通道。 - 修正为 Channel5 后,串口读数随光敏传感器遮挡/照射改变,PA2 接 3.3 V 的读数接近 3.3 V,才能说明 ADC3→DMA2 Channel5→
AD_Buf的数据链路基本正确。
1.17 复位查看效果
- 复位后观察 PA1(光敏 AO)和 PA2(3.3 V)是一个有用的端到端验证:遮挡或触摸传感器时 PA1 电压会随模块输出特性变化,PA2 应保持接近电源电压。
- “摸传感器”只适合作为定性演示。若要验证精度,应用万用表测量 AO 和 VDDA,并比较 ADC 原始码与公式
Raw ≈ Vin / VDDA × 4096的结果。 - 参考手册中 UART4 的映射示例为:RX 可用 DMA2 Channel3,TX 可用 DMA2 Channel5。这是另一条固定请求通路,与 ADC3 同用 DMA2 Channel5 时不能同时配置并发使用;DMA 通道不能同时服务两个外设请求。
二、内容复盘
- 模拟信号的幅值连续变化,例如传感器 AO 的电压;数字信号只在离散逻辑电平或离散数值间变化。ADC 的作用是以采样频率和量化位数把模拟电压转换成数字码。
- STM32F103ZET6 有 ADC1、ADC2、ADC3 三个 ADC 控制器,但并非所有封装/容量的 F103 都有 ADC3。多 ADC 不代表同一引脚可被随意同时采样,也不代表每个 ADC 都有独立 DMA 请求。
- 本课程使用规则组转换:通道根据 Rank 排队。扫描、连续、间断和 DMA 分别控制“序列怎么走”“是否自动重复”“一次触发走多少 Rank”“结果怎么搬运”,不要把它们混为同一个模式。
三、ADC控制器实验演示
- 规则转换:规则组中 Rank1、Rank2 等决定转换顺序。本工程使用两路:PA1 先于 PA2。
- 单次/间断查询:一次
HAL_ADC_Start()可按间断转换配置完成一个或多个 Rank,CPU 再轮询等待结果;此前多通道查询实验设置每次一个 Rank。 - 连续 DMA:本工程的 ADC3 设置
ContinuousConvMode=ENABLE,完成两路规则序列后自动重启;DMA 循环模式把每个结果写入数组,CPU 不必逐个等待。 - ADC 时钟:ADC 时钟来自 PCLK2 的 2、4、6 或 8 分频,STM32F103 的 ADC 输入时钟不得超过 14 MHz。72 MHz PCLK2 下常选 /6 得到 12 MHz;/4 的 18 MHz 和 /2 的 36 MHz 都超限。
- 转换时间:
Tconv = (Tsamp + 12.5) / fADC。采样时间可选 1.5、7.5、13.5、28.5、41.5、55.5、71.5 或 239.5 个 ADC 时钟周期;239.5 周期、12 MHz 时每路约 21 us,两路约 42 us。采样时间越长不等于量化位数更高,而是让采样保持电容有更充分的建立时间。 - 量化码:12 位 ADC 有 4096 个量化等级,输出整数码为 0~4095,不是“精度为 4096 V”。理想最小分辨电压约为
VDDA / 4096,VDDA=3.3 V 时约 0.806 mV,但实际有效精度还受参考电压、噪声、源阻抗和 PCB 布局影响。
四、ADC理论知识
- 12 位 ADC 的左/右对齐只是把 12 位结果放进 16 位数据寄存器的方式:右对齐时结果位于 bit[11:0],便于直接用 0~4095 参与计算;左对齐时结果左移 4 位,适合只读取高 8 位或高 12 位的场景。本课程使用右对齐。
- 更高分辨率、采样速率或更低噪声要求可以选用外部 ADC,通过 I2C、SPI 等总线读取。选型时不能只看“位数”:还要看采样率、输入范围、参考源、INL/DNL、噪声、接口速率和前端驱动要求。
- 初学应先掌握单通道/多通道查询的时钟、Rank、采样时间、换算和接线;中断与 DMA 是同一 ADC 数据通路的不同处理方式,理解后可逐步加入,不应以复制代码代替核对硬件映射。
- ADC 中断模式需要启用 ADC 的转换完成中断,在对应 ISR 或 HAL 回调中读取/处理数据;DMA 模式则由 DMA 搬运结果,通常在半传输/全传输回调中处理一批数据。两者都要求中断向量、优先级和共享数据访问正确。