逐次逼近型ADC(Successive Approximation ADC)是嵌入式系统中最常见的模数转换器类型之一。这种ADC通过二分搜索的方式逐步逼近输入电压值,在转换速度和精度之间取得了很好的平衡。
在STM32系列微控制器中,内置的ADC模块大多采用这种架构。以STM32F103系列为例,其12位ADC的转换时间最短可达1μs(在56MHz ADC时钟下),这个性能对于大多数嵌入式应用已经足够。
注意:虽然SAR ADC的转换速度比不上流水线型ADC,但其功耗更低、电路更简单,非常适合资源受限的嵌入式场景。
SAR ADC的核心是一个数模转换器(DAC)、一个比较器和逐次逼近寄存器(SAR)。其工作流程如下:
对于12位ADC,这个过程需要12个时钟周期完成。这就是为什么STM32的ADC时钟配置会影响转换时间。
STM32的ADC时钟源自APB2总线,但最大不能超过14MHz(某些型号为36MHz)。配置时需要特别注意:
c复制// 以STM32F103为例,配置ADC时钟为12MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 假设APB2时钟为72MHz
实际经验:过高的ADC时钟会导致精度下降。对于需要高精度的应用,建议时钟不超过14MHz。
STM32的ADC支持多通道扫描模式,但需要注意:
c复制ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);
STM32 ADC需要定期校准以保证精度:
c复制ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
实测发现:上电后校准一次即可,频繁校准反而会引入噪声。
STM32的ADC精度高度依赖参考电压。对于没有独立VREF引脚的型号:
硬件滤波之外,软件算法也能显著提升ADC稳定性:
c复制#define SAMPLE_TIMES 32
uint16_t ADC_GetAverage(uint8_t ch) {
uint32_t sum = 0;
for(int i=0; i<SAMPLE_TIMES; i++) {
sum += ADC_GetValue(ch);
}
return sum/SAMPLE_TIMES;
}
采用电阻分压时需注意:
c复制float GetBatteryVoltage(void) {
uint16_t adc = ADC_GetAverage(ADC_Channel_0);
return adc * 3.3f / 4095 * (R1+R2)/R2; // 分压比补偿
}
STM32内置温度传感器典型用法:
c复制// 启用温度传感器通道
ADC_TempSensorVrefintCmd(ENABLE);
// 采样时需注意:
// 1. 采样时间建议≥17.1μs
// 2. 温度计算公式:
float GetMCUTemperature(void) {
uint16_t VSENSE = ADC_GetValue(ADC_Channel_16);
return ((1.43 - VSENSE*3.3/4095)/0.0043) + 25;
}
对于需要高速连续采样的场景,DMA是必备技术:
c复制// 初始化DMA
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
// ...其他DMA参数
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
// 配置ADC为连续转换模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_Init(ADC1, &ADC_InitStructure);
// 启动DMA和ADC
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
实测技巧:DMA缓冲区的长度最好设置为2的幂次方,便于后期数据处理。
可能原因及解决方案:
典型排查步骤:
解决方案:
对于电池供电设备:
c复制void ADC_LowPowerSampling(void) {
ADC_Cmd(ADC1, DISABLE);
PWR_EnterSleepMode(); // 进入低功耗模式
// 由外部中断唤醒后:
ADC_Cmd(ADC1, ENABLE);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
uint16_t value = ADC_GetConversionValue(ADC1);
}
在不需要ADC时彻底关闭其时钟:
c复制// 关闭ADC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, DISABLE);
// 重新启用时需要重新初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
ADC_Init(ADC1, &ADC_InitStructure);
通过过采样和抽取技术,可以提升有效分辨率:
c复制#define OVERSAMPLING 256 // 16位需要4^4=256倍过采样
int32_t ADC_HighResRead(uint8_t ch) {
int32_t sum = 0;
for(int i=0; i<OVERSAMPLING; i++) {
sum += ADC_GetValue(ch);
}
return sum >> 2; // 右移2位得到16位结果
}
实现要点:
利用STM32的ADC和DMA,配合LCD显示,可以制作简易示波器:
c复制#define BUF_SIZE 256
uint16_t adc_buf[BUF_SIZE];
void Scope_Init(void) {
// 配置ADC为扫描模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
// 配置DMA循环模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// 触发源设置为定时器
ADC_ExternalTrigConvCmd(ADC1, ENABLE);
}
void Scope_Refresh(void) {
// 在LCD上绘制波形
for(int i=0; i<BUF_SIZE; i++) {
LCD_DrawPoint(i, adc_buf[i]>>4); // 12bit转8bit显示
}
}
关键参数计算:
采样率 = 定时器触发频率
存储深度 = BUF_SIZE
带宽限制:根据奈奎斯特定理,有效带宽<采样率/2
注入通道可以中断当前转换,优先采样关键信号:
c复制// 配置注入通道
ADC_InjectedChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_28Cycles5);
ADC_InjectedSequencerLengthConfig(ADC1, 1);
// 触发注入转换
ADC_ExternalTrigInjectedConvCmd(ADC1, ENABLE);
// 读取结果
uint16_t inj = ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_1);
典型应用场景: