在嵌入式开发中,ADC采集的稳定性直接影响整个系统的可靠性。最近在做一个工业传感器项目时,发现STM32F103C8T6采集的原始数据存在明显波动。经过反复测试,最终通过组合滤波算法将采集精度提升了3倍。今天就把这套经过实战验证的方案完整分享出来,包含硬件连接、软件配置和五种滤波算法的对比实测数据。
使用STM32F103C8T6的ADC1通道0(PA0)作为采集口时,硬件上要注意:
实测发现,不接去耦电容时采集值会有约5%的跳变,这是很多新手容易忽略的细节。
c复制void ADC1_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道模式
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);
// 设置采样时间239.5周期(提高精度)
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
关键提示:采样周期设置为239.5时,ADC转换时间约为17.8μs(72MHz主频下),这个参数需要根据信号频率调整。我曾遇到过采样周期过短导致数据失真的情况。
c复制#define FILTER_LEN 10
uint16_t MovingAverageFilter(uint16_t newVal) {
static uint16_t buf[FILTER_LEN] = {0};
static uint8_t index = 0;
static uint32_t sum = 0;
sum -= buf[index];
buf[index] = newVal;
sum += newVal;
index = (index + 1) % FILTER_LEN;
return sum / FILTER_LEN;
}
实测数据:
适合变化缓慢的温度类信号,但对突变的压力信号会产生滞后。
c复制uint16_t MedianAverageFilter(uint16_t newVal) {
static uint16_t buf[FILTER_LEN];
static uint8_t index = 0;
uint16_t temp[FILTER_LEN];
buf[index++] = newVal;
if(index >= FILTER_LEN) index = 0;
// 拷贝并排序
memcpy(temp, buf, sizeof(buf));
BubbleSort(temp, FILTER_LEN);
// 去掉最大最小值后求平均
uint32_t sum = 0;
for(uint8_t i=1; i<FILTER_LEN-1; i++) {
sum += temp[i];
}
return sum/(FILTER_LEN-2);
}
在电机干扰环境下测试:
c复制#define ALPHA 0.3f // 滤波系数
uint16_t FirstOrderFilter(uint16_t newVal) {
static uint16_t lastVal = 0;
lastVal = (uint16_t)(ALPHA * newVal + (1-ALPHA) * lastVal);
return lastVal;
}
参数调节经验:
c复制typedef struct {
float Q; // 过程噪声协方差
float R; // 观测噪声协方差
float x; // 估计值
float P; // 估计误差协方差
float K; // 卡尔曼增益
} KalmanFilter;
uint16_t KalmanUpdate(KalmanFilter* kf, uint16_t z) {
// 预测
kf->P = kf->P + kf->Q;
// 更新
kf->K = kf->P / (kf->P + kf->R);
kf->x = kf->x + kf->K * (z - kf->x);
kf->P = (1 - kf->K) * kf->P;
return (uint16_t)kf->x;
}
初始化参数建议:
c复制uint16_t WeightedFilter(uint16_t newVal) {
static uint16_t buf[FILTER_LEN];
const uint8_t weight[FILTER_LEN] = {1,2,3,5,8,5,3,2,1}; // 高斯分布权重
static uint8_t index = 0;
uint32_t sum = 0;
uint16_t total_weight = 0;
buf[index] = newVal;
for(uint8_t i=0; i<FILTER_LEN; i++) {
uint8_t pos = (index + i) % FILTER_LEN;
sum += buf[pos] * weight[i];
total_weight += weight[i];
}
index = (index + 1) % FILTER_LEN;
return sum / total_weight;
}
这种方案在图像传感器信号处理中表现优异,能兼顾响应速度和稳定性。
在工业级应用中,我推荐采用三级处理:
c复制uint16_t GetFilteredValue(void) {
uint16_t raw = ADC_GetValue();
uint16_t mid_val = MedianAverageFilter(raw);
return FirstOrderFilter(mid_val);
}
| 滤波方式 | 波动范围 | 响应延迟 | CPU占用率 | 适用场景 |
|---|---|---|---|---|
| 无滤波 | ±25 | 0 | 0% | 实验室测试 |
| 移动平均 | ±8 | 10周期 | 2% | 低速变化信号 |
| 中位值平均 | ±5 | 10周期 | 5% | 存在脉冲干扰 |
| 一阶滞后 | ±6 | 3周期 | 1% | 动态跟踪 |
| 卡尔曼 | ±3 | 2周期 | 8% | 高精度场合 |
| 三级融合 | ±2 | 5周期 | 6% | 工业级应用 |
对于资源紧张的C8T6(仅20K RAM),可以采用以下优化:
c复制// 优化后的移动平均滤波
uint16_t OptimizedFilter(uint16_t newVal) {
static uint16_t buf[16]; // 16=2^4
static uint8_t index = 0;
static uint32_t sum = 0;
sum -= buf[index & 0x0F]; // 替代%16
buf[index & 0x0F] = newVal;
sum += newVal;
index++;
return sum >> 4; // 替代/16
}
通过串口实时输出原始值和滤波值,建议用Python可视化:
python复制import matplotlib.pyplot as plt
plt.plot(raw_data, label='Raw')
plt.plot(filtered_data, label='Filtered')
plt.legend()
plt.show()
调试中发现,当信号频率>1/5采样频率时,移动平均滤波会产生明显相位延迟。
对于高频噪声,建议在信号输入端增加二阶有源滤波:
code复制信号源 → 10kΩ → 0.1μF → 10kΩ → 0.1μF → ADC
| |
GND GND
这个电路在变频器干扰测试中,将噪声幅度从300mV降至30mV。