3、ADC驱动开发:初始化流程、通道配置、采样时序设置、DMA传输配置

ADC驱动开发,说白了就是让芯片的“耳朵”能听准外界的模拟信号。我刚开始做这个的时候,总觉得不就是配几个寄存器嘛,结果采样出来的数据全是乱的。后来才明白,初始化流程、通道选择、采样时间、DMA搬运,每一步都有讲究。

这一节,咱们就把ADC驱动开发的四个核心环节拆开揉碎。你跟着走一遍,基本就能搞定大部分MCU的ADC外设了。

3.1 ADC初始化流程——先让外设“活”起来

初始化ADC,不是上来就配采样通道。你得先给它供电、选时钟、定分辨率。我习惯按这个顺序来:

  1. 使能ADC时钟——大部分MCU的ADC外设默认是关着的,得先打开时钟门控。
  2. 配置ADC时钟分频——ADC有最高工作频率限制,比如STM32的ADC最高14MHz,超了采样值就飘。
  3. 设置分辨率与数据对齐——12位、10位、8位?左对齐还是右对齐?这决定了你读到的寄存器值怎么换算。
  4. 选择转换模式——单次转换、连续转换、扫描模式?看应用场景选。
  5. 使能ADC——最后一步,调用校准函数,然后正式使能。

关键点:ADC使能后,通常需要等待一个稳定时间(比如几十微秒)再开始转换。我见过有人刚使能就立刻采样,前几个数据全是错的。

// 以STM32F4为例,ADC初始化核心代码
void ADC_Init(void)
{
    // 1. 使能ADC1时钟
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    
    // 2. 配置时钟分频,ADC时钟 = APB2时钟 / 4
    RCC->CFGR |= RCC_CFGR_ADCPRE_DIV4;
    
    // 3. 设置12位分辨率,右对齐
    ADC1->CR1 |= ADC_CR1_RES_12BIT;
    ADC1->CR2 |= ADC_CR2_ALIGN_RIGHT;
    
    // 4. 单次转换模式,不使能扫描
    ADC1->CR1 &= ~ADC_CR1_SCAN;
    ADC1->CR2 &= ~ADC_CR2_CONT;
    
    // 5. 使能ADC,等待稳定
    ADC1->CR2 |= ADC_CR2_ADON;
    delay_us(100);  // 等待稳定
    
    // 6. 校准
    ADC1->CR2 |= ADC_CR2_CAL;
    while(ADC1->CR2 & ADC_CR2_CAL);
}

3.2 通道配置——选对“耳朵”听哪个信号

ADC初始化完了,接下来就是告诉它:你要听哪个引脚上的信号。每个ADC模块通常有多个输入通道,比如ADC1的通道0对应PA0,通道1对应PA1。

配置通道时,我一般关注两个东西:

  • 采样通道选择——在规则序列寄存器里写入你要用的通道号。
  • 采样时间设置——每个通道可以单独配置采样周期数,这个直接影响采样精度。

我的习惯:如果多个通道的输入阻抗不同,我会给高阻抗信号源配更长的采样时间。比如传感器输出阻抗10kΩ,采样周期至少设到15个ADC时钟周期以上。

// 配置ADC1通道0(PA0),采样时间144周期
ADC1->SQR3 |= 0 << 0;          // 第1个转换:通道0
ADC1->SMPR2 |= 7 << 0;         // 采样时间:144周期

3.3 采样时序设置——时间不对,数据全废

采样时序是ADC驱动里最容易踩坑的地方。为什么?因为采样时间太短,电容没充满,数据偏小;采样时间太长,转换速度跟不上。

采样时间由两部分组成:

参数 说明 典型值
采样周期数 ADC采样开关闭合的时间,以ADC时钟周期为单位 3~480周期
转换周期数 逐次逼近所需的时间,固定为12周期(12位) 12周期
总转换时间 采样周期 + 转换周期 15~492周期

举个例子:ADC时钟12MHz,采样周期设144,那么采样时间就是144/12MHz = 12μs。加上转换的1μs,一次转换总共13μs。采样率大约77kHz。

我曾经踩过的坑:用ADC采集音频信号,采样周期设了3个时钟周期,结果高频成分全衰减了。后来查手册才发现,信号源内阻大,采样时间不够。换成144周期后,波形才正常。所以,采样时间不是越小越好,得看信号源阻抗。

3.4 DMA传输配置——让数据自己“跑”起来

如果每次转换完都靠CPU去读数据,那CPU就别干别的了。DMA就是干这个的——转换完成自动把数据搬到内存里。

配置DMA传输ADC数据,我总结了三步:

  1. 使能DMA时钟,配置DMA通道。
  2. 设置源地址为ADC数据寄存器,目标地址为用户定义的数组。
  3. 设置传输长度(要转换的次数),使能循环模式(如果需要连续采样)。
// DMA配置示例:ADC1转换结果自动搬运到buffer
void ADC_DMA_Config(void)
{
    // 1. 使能DMA2时钟
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
    
    // 2. 配置DMA2_Stream0,通道0对应ADC1
    DMA2_Stream0->PAR = (uint32_t)&(ADC1->DR);   // 源:ADC数据寄存器
    DMA2_Stream0->M0AR = (uint32_t)adc_buffer;    // 目标:内存数组
    DMA2_Stream0->NDTR = 100;                     // 传输100次
    
    // 3. 配置传输方向、大小、循环模式
    DMA2_Stream0->CR |= DMA_SxCR_DIR_PS;          // 外设到内存
    DMA2_Stream0->CR |= DMA_SxCR_MSIZE_16BIT;     // 内存16位
    DMA2_Stream0->CR |= DMA_SxCR_PSIZE_16BIT;     // 外设16位
    DMA2_Stream0->CR |= DMA_SxCR_CIRC;            // 循环模式
    
    // 4. 使能DMA流
    DMA2_Stream0->CR |= DMA_SxCR_EN;
    
    // 5. 使能ADC的DMA请求
    ADC1->CR2 |= ADC_CR2_DMA;
}

小技巧:DMA传输完成后,通常会触发中断。你可以在中断里设置一个标志位,主循环检测到标志位后处理数据。这样CPU既不忙等,又能及时拿到最新数据。

3.5 整体流程框架

下面这张图,把ADC驱动开发的四个环节串起来了。你照着这个流程走,基本不会漏步骤。

ADC驱动开发流程框架图 ADC驱动开发核心流程 1. 时钟与初始化 2. 通道与采样时间 3. 转换模式选择 CPU轮询/中断读数据 DMA自动搬运数据 5. 数据处理与校准 ⚠ 常见问题 • 采样时间不足导致数据偏小 • DMA地址未对齐导致数据错位 • 时钟频率超限导致转换异常

3.6 避坑清单

最后,我把这些年调试ADC驱动遇到的典型问题列出来。你调试的时候,可以对照着查:

现象 可能原因 解决办法
采样值始终为0 ADC未使能、通道未配置、GPIO未设为模拟模式 检查时钟、GPIO模式、ADC使能位
采样值偏小或波动大 采样时间太短、信号源阻抗高 增大采样周期数,降低ADC时钟频率
DMA传输数据错位 DMA地址未对齐、传输宽度不匹配 确保源/目标地址对齐,外设和内存宽度一致
转换速度达不到预期 ADC时钟分频太大、采样周期过长 优化时钟分频,在精度允许下减小采样周期
多通道数据混乱 扫描序列未正确配置、DMA缓冲区未对应 检查SQR寄存器,确保DMA缓冲区大小匹配通道数

最后提醒一句:ADC驱动调试,最忌讳“凭感觉配参数”。每次改完寄存器,都去量一下实际引脚上的波形,看看采样时间够不够。我见过太多人代码写得飞起,结果示波器一挂,发现采样窗口根本没对准信号。