跳到主要内容

ADC 采样与转换

ADC(Analog-to-Digital Converter,模数转换器)是嵌入式系统中将模拟信号转换为数字信号的关键外设。STM32F1 集成了 12 位逐次逼近型 ADC,具有高速、高精度、多通道采样等特点。本文档详细介绍 ADC 的工作原理、配置方法和应用实践。

ADC 原理

逐次逼近型 ADC 工作原理

STM32F1 使用逐次逼近型(SAR - Successive Approximation Register)ADC,其工作过程如下:

          ┌─────────────────────────────────────────┐
│ 逐次逼近寄存器 (SAR) │
│ ┌─────┬─────┬─────┬─────┬─────┬─────┐ │
│ │ Bit11│ Bit10│ ... │ Bit1│ Bit0│ │
│ └──┬──┴──┬──┴─────┴──┬──┴──┬──┴─────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌────────────────────────────┐ │
│ │ DAC (数模转换器) │ │
│ └──────────┬─────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────┐ │
│ │ 比较器 (Comparator) │◄────────┼──── 输入电压 VIN
│ └──────────┬─────────────────┘ │
│ │ │
│ ▼ │
│ 比较结果输出 │
└─────────────┼───────────────────────────┘


逐位确定

工作步骤(以 12 位 ADC 为例):

  1. 初始化:SAR 寄存器所有位清零
  2. 最高位测试:将 Bit11 置 1,DAC 输出 Vref/2
  3. 比较:如果 VIN > VDAC,保持 Bit11=1;否则 Bit11=0
  4. 次高位测试:将 Bit10 置 1,重复比较过程
  5. 循环:直到 Bit0 测试完成
  6. 完成:SAR 寄存器的值即为 ADC 转换结果

12 位 ADC 的分辨率

STM32F1 的 ADC 是 12 位,分辨率为:

LSB = Vref+ / 4096

对于 3.3V 参考电压:

  • LSB = 3.3V / 4096 ≈ 0.806mV
  • 量化误差 = ±0.5 LSB ≈ ±0.4mV
模拟输入ADC 值(二进制)ADC 值(十进制)
0V0000 0000 00000
0.806mV0000 0000 00011
1.612mV0000 0000 00102
1.65V0110 0110 01101638
3.3V1111 1111 11114095

ADC 转换时间

ADC 完成一次转换需要的时间包括:

  1. 采样时间(Sample Time):电容采样输入电压的时间
  2. 转换时间(Conversion Time):12 位转换需要 12 个时钟周期

总转换时间公式

Tconv = 采样时间 + 12 个时钟周期

STM32F1 ADC 时钟(ADCCLK)来自 APB2 时钟,最大 14MHz:

ADCCLK 分频ADCCLK 频率采样时间(1.5周期)总转换时间
236MHz / 2 = 18MHz83ns0.75μs
436MHz / 4 = 9MHz167ns1.5μs
636MHz / 6 = 6MHz250ns2.25μs
836MHz / 8 = 4.5MHz333ns3μs

采样时间可配置选项:1.5、7.5、13.5、28.5、41.5、55.5、71.5、239.5 周期

采样时间与输入阻抗

采样时间需要足够长,以对输入信号进行充电:

采样时间 > (RA + RS) × Csh × ln(4096)

其中:

  • RA:输入阻抗
  • RS:信号源阻抗
  • Csh:采样电容(约 8pF)

建议:信号源阻抗不应超过 10kΩ,或者增加采样时间

ADC 通道与序列

ADC 通道映射

STM32F103 有多个 ADC 通道,分为:

外部通道(连接到 GPIO)

通道ADC1ADC2ADC3
通道 0PA0PA0PA0
通道 1PA1PA1PA1
通道 2PA2PA2PA2
通道 3PA3PA3PA3
通道 4PA4PA4PF6
通道 5PA5PA5PF7
通道 6PA6PA6PF8
通道 7PA7PA7PF9
通道 8PB0PB0PF10
通道 9PB1PB1PF3
通道 10PC0PC0PC0
通道 11PC1PC1PC1
通道 12PC2PC2PC2
通道 13PC3PC3PC3
通道 14PC4PC4-
通道 15PC5PC5-

内部通道

通道功能地址
通道 16温度传感器-
通道 17内部参考电压 (Vrefint)1.2V
通道 18VBAT 电压VBAT/4

规则组与注入组

ADC 有两种转换组:

规则组(Regular Group)

  • 最常用的转换组
  • 最多 16 个通道
  • 转换结果存储在一个数据寄存器中
  • 适用于连续多通道采样

注入组(Injected Group)

  • 优先级高于规则组
  • 最多 4 个通道
  • 转换结果存储在 4 个独立数据寄存器中
  • 适用于中断触发的转换

ADC 转换模式

单次转换模式(Single Conversion)

ADC 完成一个通道的转换后停止:

// 配置为单次转换模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 关闭扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 关闭连续转换

// 启动转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);

// 等待转换完成
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
uint16_t value = ADC_GetConversionValue(ADC1);

连续转换模式(Continuous Conversion)

ADC 完成一个通道转换后立即开始下一个转换:

// 配置为连续转换模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 开启连续转换

// 启动转换(只需一次)
ADC_SoftwareStartConvCmd(ADC1, ENABLE);

// 后续直接读取数据寄存器即可
uint16_t value = ADC_GetConversionValue(ADC1);

扫描模式(Scan Mode)

启用扫描模式后,ADC 按顺序转换规则组中的所有通道:

// 配置为扫描模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 开启扫描
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;

// 设置规则组通道序列
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_SoftwareStartConvCmd(ADC1, ENABLE);

// 等待所有通道转换完成(使用 DMA 时不需要)
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
uint16_t values[3];
for (int i = 0; i < 3; i++)
{
values[i] = ADC_GetConversionValue(ADC1);
}

间断模式(Discontinuous Mode)

将规则组分成多个子组,每次转换一个子组:

// 配置间断模式,每 2 个通道为一组
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_DiscontinuousConvMode = ENABLE; // 开启间断模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_ConfigExternalTrigConv(ADC1, ADC_ExternalTrigConv_None);

// 配置要转换的通道数(每组 2 个)
ADC_SetExternalTrigConv(ADC1, 2);

DMA 模式

ADC 与 DMA 配合使用可以实现高速连续采样,无需 CPU 干预:

DMA 配置

#define ADC_BUFFER_SIZE  100
uint16_t adc_buffer[ADC_BUFFER_SIZE];

void ADC1_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

// DMA1 Channel 1 用于 ADC1
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 外设作为数据源
DMA_InitStructure.DMA_BufferSize = ADC_BUFFER_SIZE;
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 DMA 模式初始化

void ADC1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;

// 使能 GPIO 和 ADC 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

// 配置 ADC 通道对应的 GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入
GPIO_Init(GPIOA, &GPIO_InitStructure);

// ADC 配置
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 = 3; // 3 个通道
ADC_Init(ADC1, &ADC_InitStructure);

// 配置规则组通道序列
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 DMA
ADC_DMACmd(ADC1, ENABLE);

// 使能 ADC
ADC_Cmd(ADC1, ENABLE);

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

// 启动转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

读取 DMA 数据

// 在主循环或任务中计算平均值
uint16_t ADC_GetAverage(uint8_t channel)
{
uint32_t sum = 0;
for (int i = 0; i < ADC_BUFFER_SIZE; i++)
{
// 假设 DMA 缓冲区按顺序存储每个通道的值
// 即:ch0,ch1,ch2,ch0,ch1,ch2,...
sum += adc_buffer[i * 3 + channel];
}
return sum / (ADC_BUFFER_SIZE / 3);
}

温度传感器与内部参考电压

温度传感器

STM32 内部集成了温度传感器,可以测量芯片温度:

void ADC_TempSensor_Init(void)
{
// 使能温度传感器
ADC_TempSensorVrefintCmd(ENABLE);

// 配置为通道 16
ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_239Cycles5);

// 启动转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);

uint16_t temp_raw = ADC_GetConversionValue(ADC1);

// 计算温度(公式因芯片型号而异,请参考数据手册)
// 对于 STM32F103:
// T(°C) = (1.43 - Vtemp) / 0.00425 + 25
}

内部参考电压

内部参考电压 Vrefint = 1.2V(典型值),用于校准 ADC:

void ADC_Vrefint_Init(void)
{
// 使能内部参考电压
ADC_VrefintCmd(ENABLE);

// 配置为通道 17
ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 1, ADC_SampleTime_239Cycles5);

ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);

uint16_t vref_raw = ADC_GetConversionValue(ADC1);

// 计算实际 VCC
// VCC = 3.3V × 4096 / vref_raw
}

ADC 寄存器详解

ADC_CR1 - 控制寄存器 1

名称说明
23AWDEN模拟看门狗使能(规则组)
22JAWDEN模拟看门狗使能(注入组)
19:16DUALMOD[3:0]双模式选择
13DISCNUM[2:0]间断模式通道数
12JDISCEN注入间断模式使能
11DISCEN规则间断模式使能
8JAUTO注入组自动转换
5AWDSGL看门狗单通道模式
4SCAN扫描模式使能
3JEOCIE注入转换完成中断使能
2AWDIE模拟看门狗中断使能
1EOCIE转换结束中断使能
0AWDCH[4:0]模拟看门狗通道选择

ADC_CR2 - 控制寄存器 2

名称说明
23TSHUT温度传感器关闭
22VREFEN内部参考电压使能
15SWSTART起始转换(规则组)
14EXTTRIG外部触发转换使能
13:12EXTSEL[1:0]外部触发选择
11JSWSTART起始转换(注入组)
10JEXTTRIG注入组外部触发使能
9:8JEXTSEL[1:0]注入组外部触发选择
1CAL校准启动
0ADONADC 使能

ADC_SR - 状态寄存器

名称说明
4STRT规则组转换开始
3JSTRT注入组转换开始
2JEOC注入转换完成
1EOC转换完成
0AWD模拟看门狗事件

标准库配置模板

基础配置

#include "stm32f10x_adc.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"

void ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

// 使能 GPIO 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// 配置为模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void ADC_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;

// 使能 ADC1 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

// 配置 ADC 时钟(APB2 时钟 72MHz,分频 6 = 12MHz)
RCC_ADCCLKConfig(RCC_PCLK2_Div6);

// ADC 配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
ADC_InitStructure.ADC_NbrOfChannel = 3; // 3 个通道
ADC_Init(ADC1, &ADC_InitStructure);

// 配置通道序列和采样时间
// 通道 0,序列 1,采样时间 55.5 周期
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 通道 1,序列 2,采样时间 55.5 周期
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
// 通道 2,序列 3,采样时间 55.5 周期
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);

// 使能 ADC
ADC_Cmd(ADC1, ENABLE);

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

void ADC_Init(void)
{
ADC_GPIO_Config();
ADC_Mode_Config();
}

读取 ADC 值

uint16_t ADC_ReadChannel(uint8_t channel)
{
// 配置单个通道
ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_55Cycles5);

// 启动转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);

// 等待转换完成
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);

// 返回结果
return ADC_GetConversionValue(ADC1);
}

// 多次采样取平均
uint16_t ADC_ReadChannelAverage(uint8_t channel, uint8_t times)
{
uint32_t sum = 0;
for (uint8_t i = 0; i < times; i++)
{
sum += ADC_ReadChannel(channel);
}
return sum / times;
}

应用实例

电位器分压检测

// 假设 PA0 连接电位器中间抽头
// 电位器两端分别接 3.3V 和 GND

uint16_t ReadPotentiometer(void)
{
uint16_t adc_value = ADC_ReadChannel(ADC_Channel_0);
// 将 ADC 值转换为电压(mV)
uint32_t voltage_mv = adc_value * 3300 / 4096;
return voltage_mv;
}

光敏电阻检测

// 假设光敏电阻连接在 PA1
// 使用上拉或下拉电阻构成分压电路

uint16_t ReadLightSensor(void)
{
uint16_t adc_value = ADC_ReadChannel(ADC_Channel_1);
return adc_value; // 值越小表示光线越强(具体取决于电路连接)
}

多通道轮询采集

#define NUM_CHANNELS  3
uint16_t adc_values[NUM_CHANNELS];

void ADC_ScanChannels(void)
{
// 配置为扫描模式,单次转换
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = NUM_CHANNELS;
ADC_Init(ADC1, &ADC_InitStructure);

// 启动转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);

// 等待所有通道转换完成
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);

// 读取结果
for (int i = 0; i < NUM_CHANNELS; i++)
{
adc_values[i] = ADC_GetConversionValue(ADC1);
}
}

常见问题

1. ADC 值跳动大

原因

  • 电源噪声
  • 输入阻抗过高
  • 采样时间不足
  • 参考电压不稳定

解决方案

  • 增加采样时间(如 239.5 周期)
  • 在 ADC 输入引脚添加滤波电容(100nF)
  • 使用多次采样取平均
  • 确保参考电压稳定

2. ADC 值始终为 0 或 4095

原因

  • GPIO 模式配置错误(不是 AIN)
  • 通道号配置错误
  • 硬件连接问题

排查

  • 检查 GPIO 配置
  • 确认通道映射正确

3. 双 ADC 同步采样配置

// ADC1 和 ADC2 同步采样
void Dual_ADC_Config(void)
{
// ADC1 配置(为主)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimultaneous; // 规则组同步模式
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 = 1;
ADC_Init(ADC1, &ADC_InitStructure);

// ADC2 配置(为从)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimultaneous;
ADC_Init(ADC2, &ADC_InitStructure);

// ADC2 配置触发源
ADC_ExternalTrigConvCmd(ADC2, ENABLE);

// 使能 DMA(ADC1 的 DMA)
ADC_DMACmd(ADC1, ENABLE);

// 启动校准和转换
// ...
}

总结

ADC 是嵌入式系统中非常重要的外设:

  • 逐次逼近原理:12 位分辨率,4096 个量化级别
  • 转换时间:采样时间 + 12 周期
  • DMA 模式:解放 CPU,实现高速连续采样
  • 温度传感器和 Vrefint:内部校准资源
  • 双 ADC 模式:同步采样提高采样率

掌握 ADC 的配置和应用对于传感器数据采集至关重要。