ADC单通道非连续采样
146.6.3 ADC单通道非连续采样
/* main.c 用户代码区的采样片段;MX_* 初始化函数由 CubeMX 生成。 */
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
uint16_t ADC_DMA_Buff[3]; // 当前工程快照配置了 Rank 1~3 三路规则组转换
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
HAL_ADCEx_Calibration_Start(&hadc1); // F1 ADC 初始化后先校准
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ADC_DMA_Buff, 3); // 长度是 3 个半字
while (1)
{
for (int i = 0; i < 3; i++)
{
float ADC_Value = 3.3f * ADC_DMA_Buff[i] / 4096.0f;
printf("PA%d DMA_ADC_Value:%1.2fv ADC Num: %u\n",
i, ADC_Value, ADC_DMA_Buff[i]);
}
}
}
一、ADC单通道非连续采样
本课标题按原课程保留,但必须把标题和工程实际配置分开理解。视频演示过程中讲的是两路规则组通道;仓库中 视频配套/3、STM32单片机裸机部分/code/CubeIDE/12ADC_DMA 的当前源码快照则是三路扫描,并且启用了连续转换和 DMA 循环,因而严格说并不是“单通道、非连续转换”。学习时应以实际生成的 adc.c、dma.c 和 main.c 配置为准。
| 项目 | 视频演示/原始笔记 | 工程源码快照(以此为准) |
|---|---|---|
| 规则组通道数 | 2(Rank1、Rank2) | 3(Rank1~Rank3) |
| 通道 | 讲解中称 Channel 1、Channel 2 | ADC1_IN0、IN1、IN2,即 PA0、PA1、PA2 |
| ADC 转换 | 连续转换 | ContinuousConvMode = ENABLE |
| ADC 非连续模式 | 未展开 | DiscontinuousConvMode = DISABLE |
| DMA | 外设到内存、循环 | DMA1_Channel1,DMA_CIRCULAR |
1.1 配置时钟
- 系统时钟目标为 72 MHz:外部 8 MHz 高速时钟(HSE)经 PLL 9 倍频得到 72 MHz。
- APB2 仍为 72 MHz,ADC1 时钟选择 APB2 的 6 分频:
72 MHz / 6 = 12 MHz。STM32F1 的 ADC 时钟上限通常为 14 MHz,12 MHz 在允许范围内;APB1 的 36 MHz 不参与 ADC 时钟计算。 - ADC 采样时间和 ADC 时钟是两个概念。工程使用
ADC_SAMPLETIME_239CYCLES_5,每个通道的总转换时间可按Tconv = (Tsample + 12.5) / fADC估算;12 MHz 下约为(239.5 + 12.5) / 12 MHz = 21 us,三路完成一个规则组约 63 us(未计入其他软件开销)。 - 视频中先用较短采样时间,后来改为最大采样时间后出现串口输出。这个现象只能说明修改后问题消失,不能据此断定串口故障由采样时间单独造成。对于传感器输出阻抗较高的信号,1.5 周期可能不足以给 ADC 内部采样保持电容充电,延长采样时间会改善电压准确性;若是“完全没有串口输出”,还应同时检查 USART 引脚、波特率、
printf重定向和浮点格式支持。 - CubeMX 工程模板可以提前准备,但时钟、ADC 分频和通道数量仍需逐项核对,不能把模板中的旧配置当成当前实验配置。
1.2 配置DMA
- 在 ADC1 的 DMA 设置中添加
DMA1_Channel1,方向为DMA_PERIPH_TO_MEMORY。 - 外设地址是 ADC 数据寄存器(DR),每次读取后 DR 地址不变,因此
PeriphInc = DMA_PINC_DISABLE。 - 内存地址指向
ADC_DMA_Buff,数组元素依次存放各 Rank 的结果,因此MemInc = DMA_MINC_ENABLE。 - 外设和内存数据宽度都设为
Half Word(16 位)。ADC 是 12 位结果,放入 16 位容器;一次 DMA 传输的“数据项”就是一个半字,而不是一个字节。 Mode = DMA_CIRCULAR:传输完设定长度后,DMA 自动把内存地址和剩余计数重新循环,继续覆盖写入缓冲区。这里的“循环”与 ADC 的连续转换是两个独立开关,二者配合才形成无人值守的连续采集。- 工程的
MX_DMA_Init()使能 DMA1 时钟并打开 DMA1_Channel1 中断;stm32f1xx_it.c中的中断入口调用HAL_DMA_IRQHandler(&hdma_adc1)。本课主循环没有使用回调,后续若要在一批数据完成时处理,可在此基础上添加 HAL 的半传输/全传输回调。 - 外设地址固定、内存地址递增、半字对齐、循环模式,是这类“ADC 一个 DR 搬到数组”的核心参数。任何一项不匹配,都可能表现为数据不变、错位或越界。
1.3 配置ADC
- 工程启用 ADC1 扫描模式:
ScanConvMode = ADC_SCAN_ENABLE,规则组数量为 3,Rank 顺序为 ADC1_IN0、ADC1_IN1、ADC1_IN2;PA0、PA1、PA2 被配置为模拟输入。 ExternalTrigConv = ADC_SOFTWARE_START表示由软件启动规则组;启动后因为ContinuousConvMode = ENABLE,ADC 会自动开始下一轮规则组转换。DiscontinuousConvMode = DISABLE表示没有启用 ADC 的“间断模式”。因此不能把 DMA 的 Circular 或“每次搬运一个数据项”称为 ADC 非连续转换;它们是不同层次的概念。- ADC 的规则组只有一个 DR 寄存器。多通道扫描时,ADC 按 Rank 顺序把每个结果写入 DR,DMA 每收到一次请求就把当前 DR 半字搬到数组下一个元素:
ADC_DMA_Buff[0]对应 Rank1/PA0,[1]对应 Rank2/PA1,[2]对应 Rank3/PA2。 - 初始化完成后先调用
HAL_ADCEx_Calibration_Start(&hadc1)校准,再调用HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ADC_DMA_Buff, 3)启动 ADC-DMA。生产代码应检查两个 HAL 函数的返回值,出错时进入错误处理;示例代码为突出流程省略了判断。 HAL_ADC_Start_DMA的第三个参数是传输数据项数量。当前配置是 3 个半字,所以必须与 Rank 数量和数组容量一致;视频中两路示例使用长度 2,不能直接套用到当前三路工程。
1.4 配置USART
- 当前工程使用 USART1,常见配置为 115200、8 数据位、无校验、1 停止位;发送引脚为 PA9,接收引脚为 PA10。
printf需要在工程已有的重定向函数中转发到huart1,否则即使 ADC 正常也不会在串口终端看到文本。 - 缓冲区类型应为
uint16_t,因为 ADC/DMA 使用半字对齐。若只采两路可定义uint16_t ADC_DMA_Buff[2];当前源码是三路,定义为[3],循环边界和 DMA 长度也都为 3。 printf中%1.2f表示至少 1 个字符、保留 2 位小数;%u用于无符号原始 ADC 数值。STM32CubeIDE 若链接的是 newlib-nano,通常还要在 linker 选项加入-u _printf_float(或启用等效的浮点 printf 选项),否则%f可能不输出小数、输出异常,或者增加较多 Flash 占用。- 源码中的
3.3f假定 ADC 的 VDDA 为 3.3 V。实际电压换算应使用实测 VDDA;12 位 ADC 原始值范围为 0~4095,代码采用理想 LSB 换算V = VDDA * Raw / 4096。若希望把满量程码 4095 映射到 VDDA,可使用/ 4095.0f,但应在项目中统一口径,不能混用后再比较结果。
1.5 问题及解决
- 小数不显示:优先检查浮点 printf 链接选项、格式串、串口重定向和终端设置。仅修改 ADC 采样时间并不能证明浮点格式已经正确。
- 完全没有串口输出:按“硬件链路→串口配置→代码执行→ADC 状态”的顺序排查:确认 PA9/PA10 与 USB-UART 接线及共地,终端为 115200 8N1,确认
printf确实指向 USART1;再检查HAL_ADC_Start_DMA返回值和 DMA/ADC 时钟。把采样时间从 1.5 周期改成 239.5 周期可以作为实验对比,但不要把它当成唯一结论。 - CNDTR 位宽校正:STM32F1 DMA 通道的传输数量有效字段为 16 位,数量范围是 0~65535;
HAL_ADC_Start_DMA(..., Length)写入的是“数据项数量”,当前为 3。资料或注释中把 CNDTR 直接说成“32 位传输数量寄存器”是不严谨的;即使 CMSIS 结构体按 32 位类型访问,硬件有效计数仍是低 16 位。 - DMA 数据流:每完成一次 ADC 规则转换,ADC 将结果放入 DR;DMA 从固定 DR 读取一个半字,写入
ADC_DMA_Buff的当前地址并递增。三次传输后完成一轮规则组,Circular 模式随后从数组首地址重新写入。 - 主循环无需延时:DMA 正在后台搬运,主循环只负责读取并打印,因此示例没有
HAL_Delay也能运行。但“没有延时”不等于串口能跟上 ADC:115200 波特率每秒只能发送约 11.5 kbit 的原始串行位,浮点文本输出很容易成为瓶颈,数组会在打印期间被 DMA 多次覆盖。 - 读取的一致性:主循环逐项读取数组时,DMA 可能恰好改写其中某一项,导致同一轮打印的三个值不一定来自同一个采样时刻。演示用途通常可以接受;需要一致快照时,应在 DMA 全传输回调中置位,再复制缓冲区,或使用双缓冲/更大的批量缓冲后再处理。
- 查询、DMA 和中断的选择:查询模式由 CPU 启动并等待每次转换,代码直观但会阻塞;DMA 适合连续、高速或数据量较大的搬运;中断模式适合数据量不大但希望在事件发生时执行处理的场景。三者是 ADC 结果处理方式的选择,不要把 DMA 的“循环模式”与 ADC 的“间断模式”混为一谈。
二、数据对应关系与验证方法
- 先用万用表测量实际 VDDA,并给 PA0、PA1、PA2 接入处于 0~VDDA 范围内的稳定电压;ADC 输入不能超过 VDDA,也不能输入负电压。
- 观察
ADC_DMA_Buff[0]、[1]、[2]是否分别随 PA0、PA1、PA2 改变。若通道顺序错位,首先检查 CubeMX 的 Rank,而不是修改数组下标。 - 将
Length暂时改为 2 只能在同时把规则组数量和数组容量改为 2 时进行;否则 DMA 会少搬一个通道,或继续写越界区域。 - 为验证 DMA 是否真正工作,可先只打印十进制原始值,排除
%f链接和浮点计算的影响;确认原始值变化后,再恢复电压换算。 - 观察 DMA 的全传输回调或使用示波器/逻辑分析仪测量采样触发,能比“串口有没有字”更准确地判断 ADC-DMA 链路是否正常。
三、可迁移的实现要点
- DMA 会异步修改缓冲区,优化级别较高的正式工程通常把共享缓冲区声明为
volatile,或只在回调中复制到普通数组后再处理;具体写法要结合 HAL 接口和编译器告警处理。 - 连续 ADC 的采样频率由 ADC 时钟、采样周期、通道数共同决定。采样时间越长,输入源阻抗适应性越好,但每轮转换越慢;不能只为“看到串口输出”盲目使用最大采样时间。
- 本课使用软件启动、规则组扫描、DMA 循环,适合建立数据通路概念。要获得稳定固定频率的采样,应进一步使用定时器触发 ADC,并在 DMA 批次完成后处理数据,而不是让
printf直接占据主循环。