STM32 ADC底层原理与寄存器配置详解

一、ADC硬件架构分析

STM32的ADC是逐次逼近型模数转换器(SAR ADC),通过电容充放电和比较器逐位确定数字量。以STM32F103为例,内部集成3个12位ADC,每个ADC有18个输入通道,转换速度可达1MHz。

1.1 ADC物理结构

ADC核心由以下几个部分组成:

输入通道选择器: 通过模拟多路开关实现18个通道的选择,包括16个外部通道和2个内部通道(温度传感器和内部参考电压VREFINT)。

采样保持电路: 内部采样电容通过开关接入输入信号,采样时间由ADC_SMPR寄存器控制。采样电容典型值为8pF,输入阻抗在采样阶段约为1MΩ,保持阶段接近无穷大。

逐次逼近寄存器(SAR): 12位转换需要12个时钟周期,每个周期确定1位。从MSB开始,通过二分查找算法逐位逼近输入电压。

比较器: 将采样电压与DAC输出电压比较,输出结果送入SAR逻辑。

参考电压: ADC转换范围由VREF+和VREF-决定,VREF+通常连接到VDDA(模拟电源),VREF-连接VSSA(模拟地)。转换精度=(VREF+ - VREF-)/4096。

1.2 时钟系统

ADC时钟由APB2时钟经分频得到:

  • ADC时钟频率 = PCLK2 / 分频系数(2/4/6/8)
  • 最高14MHz,建议不超过14MHz以保证12位精度
  • 转换时间 = 采样时间 + 12.5个ADC时钟周期

例如:PCLK2=72MHz,6分频得到12MHz,采样时间1.5周期,则单次转换时间=(1.5+12.5)/12MHz=1.17μs。

二、关键寄存器详解

2.1 ADC控制寄存器(ADC_CR1)

复制代码
Bit 23: AWDEN - 规则通道模拟看门狗使能
Bit 22: JAWDEN - 注入通道模拟看门狗使能
Bit 16: DUALMOD[3:0] - 双ADC模式选择
Bit 13: DISCEN - 规则通道间断模式
Bit 12: JDISCEN - 注入通道间断模式
Bit 11: DISCNUM[2:0] - 间断模式通道数
Bit 8: SCAN - 扫描模式使能
Bit 7: JEOCIE - 注入通道转换结束中断
Bit 6: AWDIE - 模拟看门狗中断
Bit 5: EOCIE - 规则通道转换结束中断

2.2 ADC控制寄存器2(ADC_CR2)

复制代码
Bit 23: TSVREFE - 温度传感器和内部参考电压使能
Bit 20: EXTTRIG - 外部触发使能
Bit 17-19: EXTSEL[2:0] - 外部触发源选择
Bit 15: JEXTTRIG - 注入通道外部触发
Bit 12-14: JEXTSEL[2:0] - 注入通道触发源
Bit 11: ALIGN - 数据对齐方式(0右对齐/1左对齐)
Bit 8: DMA - DMA使能
Bit 3: RSTCAL - 复位校准
Bit 2: CAL - 启动校准
Bit 1: CONT - 连续转换模式
Bit 0: ADON - ADC使能

重点说明: ADON位需要写两次,第一次上电,第二次启动转换。两次写入之间需要延时稳定时间(典型值2-3μs)。

2.3 采样时间寄存器(ADC_SMPR1/SMPR2)

每个通道可独立设置采样时间(单位:ADC时钟周期):

  • 000: 1.5周期
  • 001: 7.5周期
  • 010: 13.5周期
  • 011: 28.5周期
  • 100: 41.5周期
  • 101: 55.5周期
  • 110: 71.5周期
  • 111: 239.5周期

选择原则:

  1. 源阻抗越大,采样时间越长
  2. 最小采样时间需满足: Ts ≥ (Radc + Rsource) × Cadc × ln(2^(n+2))
  3. 其中Radc=1kΩ,Cadc=8pF,n=12位

2.4 规则序列寄存器(ADC_SQR1/2/3)

ADC_SQR1[23:20]存储转换序列长度(L[3:0],实际长度=L+1)

ADC_SQR1/2/3存储最多16个转换序列,每个序列5位表示通道号(0-17)

复制代码
SQR1: [SQ16][SQ15][SQ14][SQ13]
SQR2: [SQ12][SQ11][SQ10][SQ9][SQ8][SQ7]
SQR3: [SQ6][SQ5][SQ4][SQ3][SQ2][SQ1]

2.5 数据寄存器(ADC_DR)

12位转换结果存储:

  • 右对齐: DR[11:0]存储数据,DR[31:12]为0
  • 左对齐: DR[15:4]存储数据,DR[3:0]为0

左对齐适用于不需要完整12位精度的场景,可直接读取高8位。

三、ADC工作模式

3.1 单次转换模式

配置CONT=0,启动一次转换后自动停止。每次转换需重新触发。

硬件流程:

  1. 写ADON=1(第二次)或外部触发启动
  2. 模拟开关连接选中通道到采样电容
  3. 采样结束后断开开关,开始逐次逼近
  4. 12.5个时钟后转换完成,EOC置1
  5. 读取DR寄存器,EOC自动清零

3.2 连续转换模式

配置CONT=1,转换完成后自动启动下一次。

3.3 扫描模式

配置SCAN=1,按SQR寄存器设定的序列依次转换多个通道。通常与DMA配合,将多通道数据存储到内存。

扫描时序:

复制代码
[CH0采样]-[CH0转换]-[CH1采样]-[CH1转换]-...-[CHn转换]-[EOC=1]

3.4 间断模式

规则组可分为多个子组,每次触发只转换一个子组。通过DISCNUM设置子组通道数(1-8)。

3.5 注入转换模式

注入通道可打断规则通道转换,转换完成后继续规则通道。适用于优先级高的信号采集。

四、DMA传输原理

ADC与DMA配合实现零CPU占用的连续采集:

  1. DMA配置: 外设地址=ADC_DR寄存器地址,内存地址=数据缓冲区,传输宽度=16位
  2. 触发机制: 每次ADC转换完成,硬件自动触发DMA请求
  3. 数据流向: DMA控制器自动将DR寄存器内容搬移到内存
  4. 循环模式: DMA循环模式+ADC连续转换实现无限采集

关键点:

  • ADC_CR2的DMA位必须置1
  • DMA传输完成后不会自动关闭ADC,需手动控制
  • 扫描模式下,DMA传输次数=通道数

五、ADC校准原理

STM32内置校准功能消除内部电容的寄生电容误差。

校准流程:

  1. 写CAL=1启动校准
  2. 硬件自动执行校准序列(约83个ADC时钟)
  3. CAL自动清零表示完成
  4. 校准结果存储在内部寄存器,上电或复位后需重新校准

校准必须在ADC使能(ADON=1)且未转换时进行。

六、完整代码实现

6.1 单通道单次转换(寄存器版)

c 复制代码
#include "stm32f10x.h"

// ADC初始化 - PA0/ADC1_IN0
void ADC1_Init(void)
{
    // 1. 使能时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;    // GPIOA时钟
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;    // ADC1时钟
    
    // 2. 配置GPIO为模拟输入
    GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);  // PA0模拟输入
    
    // 3. 配置ADC时钟分频 PCLK2/6=12MHz
    RCC->CFGR &= ~RCC_CFGR_ADCPRE;
    RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6;
    
    // 4. 复位ADC配置
    RCC->APB2RSTR |= RCC_APB2RSTR_ADC1RST;
    RCC->APB2RSTR &= ~RCC_APB2RSTR_ADC1RST;
    
    // 5. ADC基本配置
    ADC1->CR1 = 0;  // 独立模式,单次转换,不扫描
    
    // 6. 右对齐,单次转换模式
    ADC1->CR2 = 0;
    ADC1->CR2 &= ~ADC_CR2_ALIGN;  // 右对齐
    ADC1->CR2 |= ADC_CR2_EXTTRIG;  // 使能外部触发(软件触发也需要)
    ADC1->CR2 |= ADC_CR2_EXTSEL;   // 软件触发(SWSTART)
    
    // 7. 配置采样时间 通道0采样239.5周期
    ADC1->SMPR2 &= ~ADC_SMPR2_SMP0;
    ADC1->SMPR2 |= ADC_SMPR2_SMP0_2 | ADC_SMPR2_SMP0_1 | ADC_SMPR2_SMP0_0;
    
    // 8. 配置规则序列
    ADC1->SQR1 = 0;  // 转换1个通道
    ADC1->SQR3 = 0;  // 第1个转换通道为CH0
    
    // 9. 使能ADC
    ADC1->CR2 |= ADC_CR2_ADON;
    
    // 延时稳定
    for(uint32_t i = 0; i < 10000; i++);
    
    // 10. 校准ADC
    ADC1->CR2 |= ADC_CR2_RSTCAL;  // 复位校准
    while(ADC1->CR2 & ADC_CR2_RSTCAL);  // 等待复位完成
    
    ADC1->CR2 |= ADC_CR2_CAL;  // 启动校准
    while(ADC1->CR2 & ADC_CR2_CAL);  // 等待校准完成
}

// 读取ADC值
uint16_t ADC1_Read(void)
{
    // 启动转换(第二次写ADON或写SWSTART)
    ADC1->CR2 |= ADC_CR2_SWSTART;
    
    // 等待转换完成
    while(!(ADC1->SR & ADC_SR_EOC));
    
    // 读取数据(读DR会自动清除EOC标志)
    return (uint16_t)ADC1->DR;
}

// 转换为电压(mV)
uint16_t ADC_ToVoltage(uint16_t adc_value)
{
    // Vref=3.3V, 12位ADC
    return (uint16_t)((adc_value * 3300) / 4096);
}

6.2 多通道扫描+DMA(寄存器版)

c 复制代码
#include "stm32f10x.h"

#define ADC_CH_NUM  4  // 采集4个通道
uint16_t ADC_Value[ADC_CH_NUM];  // DMA目标缓冲区

// 多通道ADC+DMA初始化
// PA0-ADC1_IN0, PA1-ADC1_IN1, PA2-ADC1_IN2, PA3-ADC1_IN3
void ADC1_DMA_Init(void)
{
    // 1. 使能时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_ADC1EN;
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    
    // 2. 配置GPIO
    GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0 |
                     GPIO_CRL_MODE1 | GPIO_CRL_CNF1 |
                     GPIO_CRL_MODE2 | GPIO_CRL_CNF2 |
                     GPIO_CRL_MODE3 | GPIO_CRL_CNF3);
    
    // 3. ADC时钟12MHz
    RCC->CFGR &= ~RCC_CFGR_ADCPRE;
    RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6;
    
    // 4. 配置DMA
    DMA1_Channel1->CCR = 0;
    DMA1_Channel1->CPAR = (uint32_t)&(ADC1->DR);  // 外设地址
    DMA1_Channel1->CMAR = (uint32_t)ADC_Value;    // 内存地址
    DMA1_Channel1->CNDTR = ADC_CH_NUM;            // 传输数量
    
    // DMA配置: 内存递增,循环模式,半字传输,外设到内存,传输完成中断
    DMA1_Channel1->CCR |= DMA_CCR1_MINC |   // 内存地址递增
                          DMA_CCR1_CIRC |   // 循环模式
                          DMA_CCR1_PSIZE_0 | // 外设16位
                          DMA_CCR1_MSIZE_0 | // 内存16位
                          DMA_CCR1_TCIE;     // 传输完成中断
    
    DMA1_Channel1->CCR |= DMA_CCR1_EN;  // 使能DMA通道
    
    // 5. 配置ADC
    ADC1->CR1 = 0;
    ADC1->CR1 |= ADC_CR1_SCAN;  // 扫描模式
    
    ADC1->CR2 = 0;
    ADC1->CR2 |= ADC_CR2_DMA;      // DMA使能
    ADC1->CR2 |= ADC_CR2_CONT;     // 连续转换
    ADC1->CR2 |= ADC_CR2_EXTTRIG;
    ADC1->CR2 |= ADC_CR2_EXTSEL;
    
    // 6. 采样时间配置
    ADC1->SMPR2 = 0;
    ADC1->SMPR2 |= (7 << 0) | (7 << 3) | (7 << 6) | (7 << 9); // 239.5周期
    
    // 7. 配置转换序列 CH0-CH3
    ADC1->SQR1 = (ADC_CH_NUM - 1) << 20;  // 转换4个通道
    ADC1->SQR3 = (0 << 0) | (1 << 5) | (2 << 10) | (3 << 15);
    
    // 8. 使能ADC
    ADC1->CR2 |= ADC_CR2_ADON;
    for(volatile uint32_t i = 0; i < 10000; i++);
    
    // 9. 校准
    ADC1->CR2 |= ADC_CR2_RSTCAL;
    while(ADC1->CR2 & ADC_CR2_RSTCAL);
    ADC1->CR2 |= ADC_CR2_CAL;
    while(ADC1->CR2 & ADC_CR2_CAL);
    
    // 10. 使能DMA中断
    NVIC_EnableIRQ(DMA1_Channel1_IRQn);
    NVIC_SetPriority(DMA1_Channel1_IRQn, 0);
}

// 启动连续转换
void ADC1_Start(void)
{
    ADC1->CR2 |= ADC_CR2_SWSTART;
}

// DMA中断处理
void DMA1_Channel1_IRQHandler(void)
{
    if(DMA1->ISR & DMA_ISR_TCIF1)  // 传输完成
    {
        DMA1->IFCR |= DMA_IFCR_CTCIF1;  // 清除标志
        
        // 此时ADC_Value[]已更新,可以处理数据
        // 由于循环模式,DMA会自动继续传输
    }
}

6.3 注入通道+模拟看门狗

c 复制代码
// 注入通道配置(高优先级采集)
void ADC1_Injected_Init(void)
{
    // GPIO和基本配置同上
    ADC1_Init();  // 复用单通道初始化
    
    // 配置注入通道
    ADC1->CR1 |= ADC_CR1_JAUTO;  // 规则通道结束后自动转换注入通道
    
    // 注入序列长度和通道
    ADC1->JSQR = 0;
    ADC1->JSQR |= (0 << 20);     // 转换1个注入通道(JL=0)
    ADC1->JSQR |= (1 << 15);     // 注入通道1: CH1
    
    // 使能注入通道转换结束中断
    ADC1->CR1 |= ADC_CR1_JEOCIE;
    NVIC_EnableIRQ(ADC1_2_IRQn);
}

// 配置模拟看门狗(监测电压范围)
void ADC1_Watchdog_Init(uint16_t low, uint16_t high)
{
    // 设置阈值(12位数据)
    ADC1->LTR = low & 0x0FFF;
    ADC1->HTR = high & 0x0FFF;
    
    // 使能看门狗和中断
    ADC1->CR1 |= ADC_CR1_AWDEN;   // 规则通道看门狗
    ADC1->CR1 |= ADC_CR1_AWDIE;   // 看门狗中断
    ADC1->CR1 |= ADC_CR1_AWDSGL;  // 单通道模式
    ADC1->CR1 |= (0 << 0);        // 监测CH0
    
    NVIC_EnableIRQ(ADC1_2_IRQn);
}

// ADC中断处理
void ADC1_2_IRQHandler(void)
{
    // 注入通道转换完成
    if(ADC1->SR & ADC_SR_JEOC)
    {
        ADC1->SR &= ~ADC_SR_JEOC;
        uint16_t inj_value = ADC1->JDR1;  // 读取注入通道1数据
        // 处理高优先级数据
    }
    
    // 模拟看门狗
    if(ADC1->SR & ADC_SR_AWD)
    {
        ADC1->SR &= ~ADC_SR_AWD;
        // 电压超出阈值,报警处理
    }
}

6.4 温度传感器读取

c 复制代码
// 读取芯片内部温度
float ADC_ReadTemperature(void)
{
    // 使能温度传感器(CH16)
    ADC1->CR2 |= ADC_CR2_TSVREFE;
    
    // 配置转换CH16
    ADC1->SQR3 = 16;  // 通道16
    ADC1->SMPR1 |= ADC_SMPR1_SMP16;  // 最长采样时间
    
    // 启动转换
    ADC1->CR2 |= ADC_CR2_SWSTART;
    while(!(ADC1->SR & ADC_SR_EOC));
    
    uint16_t adc_val = ADC1->DR;
    
    // 温度计算公式(参考手册典型值)
    // V25 = 1.43V(25°C时电压)
    // Avg_Slope = 4.3mV/°C
    float voltage = (adc_val * 3.3f) / 4096.0f;
    float temperature = (1.43f - voltage) / 0.0043f + 25.0f;
    
    return temperature;
}

6.5 HAL库版本对比

c 复制代码
// HAL库简化版本
void HAL_ADC_Init_Example(void)
{
    ADC_HandleTypeDef hadc1;
    
    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = DISABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    
    HAL_ADC_Init(&hadc1);
    
    ADC_ChannelConfTypeDef sConfig;
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
    
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
    HAL_ADC_Start(&hadc1);
}

七、实战应用案例

7.1 电池电压监测

c 复制代码
#define VBAT_CHANNEL  0  // PA0连接分压电阻
#define VBAT_R1       10000  // 上拉10K
#define VBAT_R2       10000  // 下拉10K

typedef struct {
    uint16_t voltage_mv;
    uint8_t  percent;
    uint8_t  status;  // 0正常 1低电压 2过压
} Battery_t;

Battery_t Battery_Monitor(void)
{
    Battery_t bat;
    uint16_t adc = ADC1_Read();
    
    // 计算实际电池电压(考虑分压)
    uint16_t vdiv = (adc * 3300) / 4096;
    bat.voltage_mv = vdiv * (VBAT_R1 + VBAT_R2) / VBAT_R2;
    
    // 锂电池电量估算(3.0V-4.2V)
    if(bat.voltage_mv < 3000) {
        bat.percent = 0;
        bat.status = 1;
    } else if(bat.voltage_mv > 4200) {
        bat.percent = 100;
        bat.status = 2;
    } else {
        bat.percent = ((bat.voltage_mv - 3000) * 100) / 1200;
        bat.status = 0;
    }
    
    return bat;
}

7.2 快速数据采集系统

c 复制代码
#define SAMPLE_RATE  10000  // 10KHz采样率
#define BUFFER_SIZE  1024

uint16_t ADC_Buffer[BUFFER_SIZE];
volatile uint8_t buffer_full = 0;

void ADC_HighSpeed_Init(void)
{
    // 使用定时器触发实现精确采样率
    // TIM2配置为10KHz
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    
    TIM2->PSC = 7199;  // 72MHz/7200 = 10KHz
    TIM2->ARR = 0;     // 不分频
    TIM2->CR2 |= TIM_CR2_MMS_1;  // 更新事件作为TRGO
    TIM2->CR1 |= TIM_CR1_CEN;
    
    // ADC配置
    ADC1->CR1 = 0;
    ADC1->CR2 = 0;
    ADC1->CR2 |= ADC_CR2_EXTTRIG;
    ADC1->CR2 |= (6 << 17);  // 外部触发源: TIM2_TRGO
    ADC1->CR2 |= ADC_CR2_DMA;
    
    // DMA配置
    DMA1_Channel1->CPAR = (uint32_t)&(ADC1->DR);
    DMA1_Channel1->CMAR = (uint32_t)ADC_Buffer;
    DMA1_Channel1->CNDTR = BUFFER_SIZE;
    DMA1_Channel1->CCR = DMA_CCR1_MINC | DMA_CCR1_PSIZE_0 | 
                         DMA_CCR1_MSIZE_0 | DMA_CCR1_TCIE | DMA_CCR1_EN;
    
    ADC1->CR2 |= ADC_CR2_ADON;
    // 校准...
}

void DMA1_Channel1_IRQHandler(void)
{
    if(DMA1->ISR & DMA_ISR_TCIF1)
    {
        DMA1->IFCR = DMA_IFCR_CTCIF1;
        buffer_full = 1;
        
        // 数据处理(FFT/滤波等)
        Process_ADC_Data(ADC_Buffer, BUFFER_SIZE);
        
        // 重启DMA
        DMA1_Channel1->CCR &= ~DMA_CCR1_EN;
        DMA1_Channel1->CNDTR = BUFFER_SIZE;
        DMA1_Channel1->CCR |= DMA_CCR1_EN;
    }
}

7.3 多路传感器采集

c 复制代码
typedef struct {
    uint16_t temp_adc;       // 温度传感器
    uint16_t light_adc;      // 光敏电阻
    uint16_t pressure_adc;   // 压力传感器
    uint16_t humidity_adc;   // 湿度传感器
} Sensor_Data_t;

Sensor_Data_t sensors;

void Multi_Sensor_Init(void)
{
    // 4通道扫描+DMA
    ADC1_DMA_Init();  // 复用前面的DMA配置
    ADC1_Start();
}

void Process_Sensor_Data(void)
{
    sensors.temp_adc = ADC_Value[0];
    sensors.light_adc = ADC_Value[1];
    sensors.pressure_adc = ADC_Value[2];
    sensors.humidity_adc = ADC_Value[3];
    
    // 转换为实际物理量
    float temp = (sensors.temp_adc * 3.3f / 4096.0f - 0.5f) / 0.01f; // TMP36传感器
    uint8_t light_percent = sensors.light_adc * 100 / 4096;
    float pressure_kpa = sensors.pressure_adc * 500.0f / 4096.0f;    // 0-500kPa
    uint8_t humidity_rh = sensors.humidity_adc * 100 / 4096;
}

八、优化技巧与注意事项

8.1 精度优化

  1. 硬件电路:

    • 参考电压使用低温漂LDO(如REF3030)
    • 输入信号增加RC低通滤波(截止频率 < ADC时钟/2)
    • PCB布线远离数字信号,独立模拟地平面
    • VDDA单独供电,串联磁珠滤波
  2. 软件校准:

c 复制代码
// 多次采样取平均
uint16_t ADC_ReadAverage(uint8_t times)
{
    uint32_t sum = 0;
    for(uint8_t i = 0; i < times; i++)
    {
        sum += ADC1_Read();
    }
    return (uint16_t)(sum / times);
}

// 中位值滤波
uint16_t ADC_Median_Filter(void)
{
    uint16_t buf[5];
    for(uint8_t i = 0; i < 5; i++)
        buf[i] = ADC1_Read();
    
    // 冒泡排序
    for(uint8_t i = 0; i < 4; i++)
        for(uint8_t j = 0; j < 4-i; j++)
            if(buf[j] > buf[j+1]) {
                uint16_t temp = buf[j];
                buf[j] = buf[j+1];
                buf[j+1] = temp;
            }
    
    return buf[2];  // 返回中位值
}

8.2 速度优化

  1. 缩短采样时间(源阻抗允许的情况下)
  2. 使用DMA减少CPU开销
  3. 多ADC交替使用,提高吞吐量
  4. 降低ADC分辨率(10位模式更快)

8.3 功耗优化

c 复制代码
// ADC省电模式
void ADC_PowerSave_Mode(void)
{
    // 采样完成后关闭ADC
    ADC1->CR2 &= ~ADC_CR2_ADON;
    
    // 关闭ADC时钟
    RCC->APB2ENR &= ~RCC_APB2ENR_ADC1EN;
}

// 需要时重新初始化
void ADC_Wakeup(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    ADC1->CR2 |= ADC_CR2_ADON;
    for(volatile int i = 0; i < 1000; i++);  // 稳定时间
}

8.4 常见问题

问题1: 读数不稳定跳动

  • 原因: 采样时间不足,输入阻抗过大
  • 解决: 增加采样周期或降低源阻抗

问题2: DMA只传输一次

  • 原因: 未配置循环模式
  • 解决: DMA_CCR |= DMA_CCR1_CIRC

问题3: 转换结果始终为0或4095

  • 原因: GPIO未配置为模拟输入或VREF异常
  • 解决: 检查CRL/CRH寄存器,测量VDDA电压

问题4: 多通道数据错位

  • 原因: DMA传输速度 < ADC转换速度
  • 解决: 降低ADC时钟或使用双缓冲

九、调试方法

c 复制代码
// ADC状态诊断函数
void ADC_Diagnose(void)
{
    printf("ADC1 Status:\n");
    printf("ADON: %d\n", (ADC1->CR2 & ADC_CR2_ADON) ? 1 : 0);
    printf("EOC: %d\n", (ADC1->SR & ADC_SR_EOC) ? 1 : 0);
    printf("STRT: %d\n", (ADC1->SR & ADC_SR_STRT) ? 1 : 0);
    printf("Current Channel: %d\n", ADC1->SQR3 & 0x1F);
    printf("DR Value: %d\n", ADC1->DR);
    
    // 检查时钟配置
    uint32_t pclk2 = SystemCoreClock / 2;  // 假设PCLK2为HCLK/2
    uint32_t adcpre = (RCC->CFGR & RCC_CFGR_ADCPRE) >> 14;
    uint32_t adc_clk = pclk2 / (2 * (adcpre + 1));
    printf("ADC Clock: %d Hz\n", adc_clk);
}

十、总结

STM32的ADC从硬件层面理解需要掌握:

  1. SAR转换原理和时序关系
  2. 寄存器位域的具体作用
  3. 时钟树对ADC性能的影响
  4. DMA与ADC的握手机制

实际应用中,根据需求选择合适的工作模式,通过硬件优化和软件滤波提升精度。掌握寄存器级编程能更灵活地控制ADC行为,理解底层机制对调试和优化至关重要。

以上代码均基于STM32F103系列,其他系列需参考对应的参考手册调整寄存器定义。建议结合示波器观察ADC_IN引脚波形和DR寄存器更新时序,加深对ADC工作流程的理解。

相关推荐
d111111111d2 小时前
STM32外设学习--DMA直接存储器读取--学习笔记。
笔记·stm32·单片机·嵌入式硬件·学习
一支闲人3 小时前
OLED代码演示-使用缓存区
stm32·单片机·嵌入式硬件·oled模块
点灯小铭3 小时前
基于单片机的噪声波形检测与分贝测量仪设计
单片机·嵌入式硬件·mongodb·毕业设计·课程设计·期末大作业
奋斗的牛马4 小时前
硬件基础知识-电容(一)
单片机·嵌入式硬件·学习·fpga开发·信息与通信
Nuyoah11klay5 小时前
华清远见25072班单片机基础学习day1
单片机·嵌入式硬件·学习
icy、泡芙7 小时前
移远 5G RG255AA-CN 调试
linux·单片机·5g
时空自由民.7 小时前
MCU 内存栈介绍和内存段
单片机·嵌入式硬件
YeGop7 小时前
51单片机定时器函数分享(8051汇编)
汇编·嵌入式硬件·51单片机
许嵩668 小时前
virtual_clock
单片机·嵌入式硬件