ADC单通道非连续采样

Tutorial: 电子电路 Category: 嵌入式 Published: 2026-08-29 08:09:36 Views: 0 Likes: 0 Comments: 0

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.cdma.cmain.c 配置为准。

项目视频演示/原始笔记工程源码快照(以此为准)
规则组通道数2(Rank1、Rank2)3(Rank1~Rank3)
通道讲解中称 Channel 1、Channel 2ADC1_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 的“间断模式”混为一谈。

二、数据对应关系与验证方法

  1. 先用万用表测量实际 VDDA,并给 PA0、PA1、PA2 接入处于 0~VDDA 范围内的稳定电压;ADC 输入不能超过 VDDA,也不能输入负电压。
  2. 观察 ADC_DMA_Buff[0][1][2] 是否分别随 PA0、PA1、PA2 改变。若通道顺序错位,首先检查 CubeMX 的 Rank,而不是修改数组下标。
  3. Length 暂时改为 2 只能在同时把规则组数量和数组容量改为 2 时进行;否则 DMA 会少搬一个通道,或继续写越界区域。
  4. 为验证 DMA 是否真正工作,可先只打印十进制原始值,排除 %f 链接和浮点计算的影响;确认原始值变化后,再恢复电压换算。
  5. 观察 DMA 的全传输回调或使用示波器/逻辑分析仪测量采样触发,能比“串口有没有字”更准确地判断 ADC-DMA 链路是否正常。

三、可迁移的实现要点

  • DMA 会异步修改缓冲区,优化级别较高的正式工程通常把共享缓冲区声明为 volatile,或只在回调中复制到普通数组后再处理;具体写法要结合 HAL 接口和编译器告警处理。
  • 连续 ADC 的采样频率由 ADC 时钟、采样周期、通道数共同决定。采样时间越长,输入源阻抗适应性越好,但每轮转换越慢;不能只为“看到串口输出”盲目使用最大采样时间。
  • 本课使用软件启动、规则组扫描、DMA 循环,适合建立数据通路概念。要获得稳定固定频率的采样,应进一步使用定时器触发 ADC,并在 DMA 批次完成后处理数据,而不是让 printf 直接占据主循环。