一、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周期
选择原则:
- 源阻抗越大,采样时间越长
- 最小采样时间需满足: Ts ≥ (Radc + Rsource) × Cadc × ln(2^(n+2))
- 其中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,启动一次转换后自动停止。每次转换需重新触发。
硬件流程:
- 写ADON=1(第二次)或外部触发启动
- 模拟开关连接选中通道到采样电容
- 采样结束后断开开关,开始逐次逼近
- 12.5个时钟后转换完成,EOC置1
- 读取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占用的连续采集:
- DMA配置: 外设地址=ADC_DR寄存器地址,内存地址=数据缓冲区,传输宽度=16位
- 触发机制: 每次ADC转换完成,硬件自动触发DMA请求
- 数据流向: DMA控制器自动将DR寄存器内容搬移到内存
- 循环模式: DMA循环模式+ADC连续转换实现无限采集
关键点:
- ADC_CR2的DMA位必须置1
- DMA传输完成后不会自动关闭ADC,需手动控制
- 扫描模式下,DMA传输次数=通道数
五、ADC校准原理
STM32内置校准功能消除内部电容的寄生电容误差。
校准流程:
- 写CAL=1启动校准
- 硬件自动执行校准序列(约83个ADC时钟)
- CAL自动清零表示完成
- 校准结果存储在内部寄存器,上电或复位后需重新校准
校准必须在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 精度优化
-
硬件电路:
- 参考电压使用低温漂LDO(如REF3030)
- 输入信号增加RC低通滤波(截止频率 < ADC时钟/2)
- PCB布线远离数字信号,独立模拟地平面
- VDDA单独供电,串联磁珠滤波
-
软件校准:
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 速度优化
- 缩短采样时间(源阻抗允许的情况下)
- 使用DMA减少CPU开销
- 多ADC交替使用,提高吞吐量
- 降低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从硬件层面理解需要掌握:
- SAR转换原理和时序关系
- 寄存器位域的具体作用
- 时钟树对ADC性能的影响
- DMA与ADC的握手机制
实际应用中,根据需求选择合适的工作模式,通过硬件优化和软件滤波提升精度。掌握寄存器级编程能更灵活地控制ADC行为,理解底层机制对调试和优化至关重要。
以上代码均基于STM32F103系列,其他系列需参考对应的参考手册调整寄存器定义。建议结合示波器观察ADC_IN引脚波形和DR寄存器更新时序,加深对ADC工作流程的理解。