10-ADC模数转换.md 10 KB


title: ADC模数转换 tags: [STM32, ADC, 模拟量采集]

created: 2026-07-06

ADC — 模数转换

原理

逐次逼近型 (SAR) ADC

Vin ─→ 采样保持 ─→ 比较器 ─→ 逐次逼近寄存器(SAR) ─→ N位数字输出
                    ↑               ↑
                DAC输出      SAR控制逻辑

工作原理:
1. MSB 先置 1,DAC 输出与 Vin 比较
2. Vin > DAC? → 保留该位为 1;否则置 0
3. 依次比较下一个位,直到 LSB

STM32F103C8T6 ADC 特性

参数
ADC 数量 2 (ADC1, ADC2)
分辨率 12 位 (0–4095)
外部通道数 10 个 (PA0–PA7, PB0, PB1)
内部通道 温度传感器(ADC1_CH16), Vrefint(ADC1_CH17)
转换时间 最短 1µs (14 周期 @ 14MHz)
ADC_CLK 最大 14MHz

规则组 vs 注入组

规则组(Regular Group):
  最多16个通道,按顺序转换
  一次配置可以转换多个通道(扫描模式)
  结果存入唯一的 ADC_DR 寄存器

注入组(Injected Group):
  最多4个通道,可打断规则组转换
  有独立的数据寄存器 ADC_JDRx
  优先级高于规则组

转换模式

单次转换(Single):
  触发一次 → 转换一个通道 → 停止
  适合单通道电压读取

连续转换(Continuous):
  触发一次 → 持续转换 → 数据不断更新
  适合持续采集一个通道

扫描模式(Scan):
  配合规则组,依次转换多个通道
  可单次扫描或连续扫描

触发方式

软件触发:
  ADC_SoftwareStartConvCmd(ADC1, ENABLE)
  写一次触发一次

外部触发(定时器/EXTI):
  TIMx_CCx 事件触发
  EXTI 线触发
  适合固定频率采样

数据对齐

右对齐:
  ┌────────────────────────────┐
  │  0 │  0 │  0 │  0 │ D11···D0 │
  └────────────────────────────┘
  直接读取 value = ADC_GetConversionValue()

左对齐(8位精度):
  ┌────────────────────────────┐
  │ D11···D0 │  0 │  0 │  0 │  0 │
  └────────────────────────────┘
  高位对齐,适合 8 位读取

API 表格

ADC_InitTypeDef

typedef struct {
    uint32_t ADC_Mode;             // ADC_Mode_Independent / Dual模式
    FunctionalState ADC_ScanConvMode;    // ENABLE/DISABLE 扫描模式
    FunctionalState ADC_ContinuousConvMode; // ENABLE/DISABLE 连续转换
    uint32_t ADC_ExternalTrigConv; // ADC_ExternalTrigConv_T1_CC1 / None_Software
    uint32_t ADC_DataAlign;        // ADC_DataAlign_Right / Left
    uint8_t  ADC_NbrOfChannel;     // 规则组通道数 (1–16)
} ADC_InitTypeDef;

核心函数

函数 描述
ADC_Init(ADCx, &ADC_InitStructure) 初始化 ADC
ADC_RegularChannelConfig(ADCx, ADC_Channel_x, Rank, SampleTime) 配置规则组通道
ADC_SoftwareStartConvCmd(ADCx, ENABLE) 软件触发转换
ADC_GetConversionValue(ADCx) 读取转换结果
ADC_Cmd(ADCx, ENABLE) 使能 ADC
ADC_ResetCalibration(ADCx) 复位校准
ADC_StartCalibration(ADCx) 开始校准
ADC_GetCalibrationStatus(ADCx) 获取校准状态
ADC_DMACmd(ADCx, ENABLE) 使能 DMA 请求(配合 ADC+DMA)
ADC_ITConfig(ADCx, ADC_IT_EOC, ENABLE) 使能转换结束中断

ADC_ExternalTrigConv 枚举

说明
ADC_ExternalTrigConv_T1_CC1 TIM1 CC1 触发
ADC_ExternalTrigConv_T2_CC2 TIM2 CC2 触发
ADC_ExternalTrigConv_T3_TRGO TIM3 TRGO 触发
ADC_ExternalTrigConv_None 软件触发

ADC_SampleTime 枚举

周期数 转换时间(@14MHz)
ADC_SampleTime_1Cycles5 1.5 ≈ 1.07µs
ADC_SampleTime_7Cycles5 7.5 ≈ 1.54µs
ADC_SampleTime_13Cycles5 13.5 ≈ 1.93µs
ADC_SampleTime_28Cycles5 28.5 ≈ 2.79µs
ADC_SampleTime_41Cycles5 41.5 ≈ 3.57µs
ADC_SampleTime_55Cycles5 55.5 ≈ 4.43µs
ADC_SampleTime_71Cycles5 71.5 ≈ 5.36µs
ADC_SampleTime_239Cycles5 239.5 ≈ 17.1µs

代码示例

1. 单通道电压测量 (PA0=ADC1_CH0, 软件触发)

#include "stm32f10x.h"

void Delay_ms(uint32_t ms)
{
    SysTick->LOAD = 72000 - 1;
    SysTick->VAL  = 0;
    SysTick->CTRL = 0x05;
    for (uint32_t i = 0; i < ms; i++) {
        while (!(SysTick->CTRL & (1 << 16)));
    }
    SysTick->CTRL = 0;
}

void ADC1_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);  // 72MHz / 6 = 12MHz (< 14MHz)

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AIN;  // 模拟输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_Mode               = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode       = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv   = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign          = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel       = 1;
    ADC_Init(ADC1, &ADC_InitStructure);

    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);

    ADC_Cmd(ADC1, ENABLE);

    // 校准
    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1));
}

uint16_t ADC1_GetValue(void)
{
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
    return ADC_GetConversionValue(ADC1);
}

// 转换结果为电压值(mV)
uint16_t ADC1_GetVoltage(void)
{
    uint32_t adc_val = ADC1_GetValue();
    // 3.3V / 4096 * adc_val * 1000
    return (uint16_t)(adc_val * 3300 / 4096);
}

int main(void)
{
    ADC1_Init();
    while (1) {
        uint16_t voltage_mV = ADC1_GetVoltage();
        // voltage_mV: 0~3300 对应 0~3.3V
        Delay_ms(100);
    }
}

2. 多通道扫描 + DMA

#include "stm32f10x.h"

#define ADC_CHANNELS 4

uint16_t ADC_Values[ADC_CHANNELS]; // PA0(0), PA1(1), PA2(2), PA3(3)

void ADC1_DMA_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DMA1, ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_Mode               = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode       = ENABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    ADC_InitStructure.ADC_ExternalTrigConv   = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign          = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel       = ADC_CHANNELS;
    ADC_Init(ADC1, &ADC_InitStructure);

    // 配置各通道(采样顺序:0→1→2→3)
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);

    // DMA 配置
    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr     = (uint32_t)ADC_Values;
    DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize         = ADC_CHANNELS;
    DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode               = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority           = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M                = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);

    DMA_Cmd(DMA1_Channel1, ENABLE);

    ADC_DMACmd(ADC1, ENABLE);
    ADC_Cmd(ADC1, ENABLE);

    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1));

    ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 启动连续扫描
}

// ADC_Values[0] = PA0,  ADC_Values[1] = PA1, ...
// DMA 自动更新,无需手动干预

常见坑点

  1. ADC 输入阻抗:STM32F103 ADC 采样电容约 4pF,建议外部信号源阻抗 < 10kΩ。信号源阻抗过大时,加缓冲器(运放跟随)或增大采样时间

  2. 转换结果公式电压 = 3.3 / 4096 × ADC值不是 3.3 / 4095。12位ADC的LSB对应 3.3V/4096 ≈ 0.8mV

  3. 校准必须做:每次上电后必须做 ADC_ResetCalibration + ADC_StartCalibration,否则结果可能有 ±10LSB 误差

  4. RCC_ADCCLKConfig 限制:ADC 时钟最大 14MHz。72MHz 主频可选分频系数:RCC_PCLK2_Div2(36MHz 超限)、Div4(18MHz 超限)、Div6(12MHz 可用)、Div8(9MHz 可用)。必须用 Div6 或 Div8

  5. GPIO 必须为 AIN 模式GPIO_Mode_AIN 将 GPIO 配置为模拟输入,关闭施密特触发器降低功耗;如果用浮空输入,测量值会偏小

  6. 单次转换需重新触发:每次 ADC_GetValue() 都要调用 ADC_SoftwareStartConvCmd(ADC1, ENABLE),再等待 EOC 标志。连续模式下则只需触发一次

  7. 连续转换 + 扫描模式:结合使用可实现后台自动采集所有通道,配合 DMA 读走即可。注意 ADC_NbrOfChannel 必须与 DMA BufferSize 一致

  8. DMA 传输完成:多通道扫描模式下,EOC 在每个通道转换完成后置位,需要等所有通道转换完(或用 DMA 传输完成中断)再处理数据

  9. 双 ADC 模式ADC_ModeADC_Mode_Independent 为独立模式;双 ADC 可配置为规则同步/注入同步/交替触发等,芯片内部自动同步

  10. VBAT 测量:VBAT 通道(ADC1_CH18)测量电池电压时注意分压系数(1/2),且 VBAT 和 VREFINT 共用通道选择