嵌入式C语言实现12位ADC高精度采样:软件滤波把采集误差压到0.1%以内
12位ADC理论分辨率是1/4096≈0.024%,压到0.1%看起来宽松,但工程里真做不到——不是算法不行,是硬件没兜底。先说结论:0.1%精度=稳定参考源+足够采样时间+两点校准+“中值削尖峰→过采样提有效位→滑动平均压噪声”三段滤波。只写“采16次求平均”永远到不了。
一、先堵硬件漏点
软件洗不掉系统误差:
VREF用VDDA→电源纹波直接进读数,必须外置基准(REF3033/REF5030类)
高阻信号采样时间太短→采样电容没充满,读数偏低,按源阻抗拉长Sampling Time
输入前加RC(1k~10k+10nF~1μF),运放缓冲高阻源
上电先做ADC自校准,消偏移和增益误差
HAL_ADCEx_Calibration_Start(&hadc1); // 校准在使能后、主循环前
二、过采样提有效位数
每4倍过采样多约1位有效分辨率:16倍≈多2位,把12位洗到接近14位有效。
#define OS_N 16
uint32_t adc_oversample(void){
uint32_t sum=0;
for(int i=0;i<OS_N;i++){
sum += adc_single(); // 硬件触发,固定间隔
}
return sum>>4; // 16倍右移4位
}
前提:信号带自然噪声做dither,纯直流无噪时过采样不增值。
三、中值滤波削单点尖峰
静电、PWM耦合、接触不良会产生离群点,平均会被带偏,先中值:
#define MED_N 5
uint16_t median5(uint16_t*b){
uint16_t t;
for(int i=0;i<MED_N-1;i++)
for(int j=i+1;j<MED_N;j++)
if(b[i]>b[j]){t=b[i];b[i]=b[j];b[j]=t;}
return b[2];
}
N=5或7够用,太大响应迟滞。
四、滑动平均收尾
中值输出再进环形缓冲做滑动平均,压随机白噪声:
#define AVG_N 8
static uint16_t ring[AVG_N];
static uint32_t idx=0, sum=0;
uint16_t adc_chain(uint16_t raw){
uint16_t m = median5(window_push(raw));
sum -= ring[idx];
ring[idx]=m; sum += m;
idx=(idx+1)%AVG_N;
return sum/AVG_N;
}
工程组合顺序固定:过采样→中值→滑动平均。反过来先平均再中值,尖峰已经污染累加值。
五、两点校准补系统误差
滤波只压噪声,不补偏移/增益。上电用标准源做两点:
float adc_cal(uint16_t raw){
// offset: 零点码值, gain: 满量程斜率
return (raw - cal_offset)*cal_gain;
}
void calib_do(uint16_t raw0v, uint16_t raw_full, float vref){
cal_offset = raw0v;
cal_gain = vref/(raw_full-raw0v);
}
零点接GND测offset,满量程接基准电压测gain,存Flash,每次上电加载。
六、完整采集节拍
float sample_voltage(void){
uint16_t raw = adc_chain(adc_oversample());
float v = adc_cal(raw) * VREF / 4095.0f;
return v;
}
慢信号(温度、电池电压)窗口可以长;控电流环里平均窗口别太长,相位延迟会带偏闭环。
七、落坑清单
VREF接VDDA→电源抖全部进读数
采样时间按默认最短→高阻源读偏低
只平均不过中值→单点毛刺拉偏
不过校准→整体偏移0.1V查不出
过采样但信号纯直流无噪→白采
滑动窗口32点→阶跃响应慢半拍
短接输入还对称抖才叫噪声,单向偏是硬件错
滤波后不校准→精度卡在1%上不去
中断里跑排序中值→ISR超时
参考源没去耦电容→高频纹波残留
八、落地顺序
外置基准+RC前端+长采样时间
上电自校准+两点标定存Flash
过采样16倍提有效位
中值滤波削脉冲
滑动平均压随机噪声
校准参数回灌电压换算
短接输入看噪声底,再接标准源看系统误差
实测0.1%内才进业务,否则回查硬件
ADC高精度不是“多采几次”,是硬件把系统误差控住、软件把噪声洗掉:基准和采样时间是地基,过采样换有效位,中值砍尖峰,滑动平均收尾,校准补偏移增益。只靠平均永远压不到0.1%——先短接输入端看一眼,对称抖动是能救的噪声,单向偏移是救不回来的硬件病。
你想继续深入校准实现、噪声分析,还是代码优化?





