AD采集中的10种经典软件滤波程序优缺点分析(附程序)

在AD采集中经常要用到数字滤波,而不同情况下又有不同的滤波需求,下面是10种经典的软件滤波方法的程序和优缺点分析:

  1、限幅滤波法(又称程序判断滤波法)

  2、中位值滤波法

  3、算术平均滤波法

  4、递推平均滤波法(又称滑动平均滤波法)

  5、中位值平均滤波法(又称防脉冲干扰平均滤波法)

  6、限幅平均滤波法

  7、一阶滞后滤波法

  8、加权递推平均滤波法

  9、消抖滤波法

  10、限幅消抖滤波法

  1、限副滤波

  A、方法:

  根据经验判断,确定两次采样允许的最大偏差值(设为A)

  每次检测到新值时判断:

  如果本次值与上次值之差《=A,则本次值有效

  如果本次值与上次值之差》A,则本次值无效,放弃本次值,用上次值代替本次值

  B、优点:

  能有效克服因偶然因素引起的脉冲干扰

  C、缺点

  无法抑制那种周期性的干扰

  平滑度差

  程序:

  /* A值可根据实际情况调整

  value为有效值,new_value为当前采样值

  滤波程序返回有效的实际值*/

  #define A 10

  char value;

  char filter()

  {

  char new_value;

  new_value = get_ad();

  if ( ( new_value - value 》 A ) || ( value - new_value 》 A ) )

  return value;

  else

  return new_value;

  }

  2、中位值滤波法

  A、方法:

  连续采样N次(N取奇数),把N次采样值按大小排列 ,取中间值为本次有效值

  B、优点:

  能有效克服因偶然因素引起的波动干扰,对温度、液位的变化缓慢的被测参数有良好的滤波效果

  C、缺点:

  对流量、速度等快速变化的参数不宜

  程序:

  /* N值可根据实际情况调整

  排序采用冒泡法*/

  #define N 11

  char filter()

  {

  charvalue_buf[N];

  char count,i,j,temp;

  for ( count=0;count《N;count++)

  {

  value_buf[count] = get_ad();

  delay();

  }

  for(j=0;j《N-1;j++)

  {

  for(i=0;i《N-j-1;i++)

  {

  if ( value_buf》value_buf[i+1] )

  {

  temp =value_buf;

  value_buf = value_buf[i+1];

  value_buf[i+1] = temp;

  }

  }

  }

  returnvalue_buf[(N-1)/2];

  }

  3、算术平均滤波法

  A、方法:

  连续取N个采样值进行算术平均运算

  N值较大时:信号平滑度较高,但灵敏度较低

  N值较小时:信号平滑度较低,但灵敏度较高

  N值的选取:一般流量,N=12;压力:N=4

  B、优点:

  适用于对一般具有随机干扰的信号进行滤波

  这样信号的特点是有一个平均值,信号在某一数值范围附近上下波动

  C、缺点:

  对于测量速度较慢或要求数据计算速度较快的实时控制不适用

  比较浪费RAM

  程序:

  #define N 12

  char filter()

  {

  int sum = 0;

  for ( count=0;count《N;count++)

  {

  sum + =get_ad();

  delay();

  }

  return (char)(sum/N);

  }

  4、递推平均滤波法(又称滑动平均滤波法) (FIR前身)

  A、方法:

  把连续取N个采样值看成一个队列

  队列的长度固定为N

  每次采样到一个新数据放入队尾,并扔掉原来队首的一次数据。(先进先出原则)

  把队列中的N个数据进行算术平均运算,就可获得新的滤波结果

  N值的选取:流量,N=12;压力:N=4;液面,N=4~12;温度,N=1~4

  B、优点:

  对周期性干扰有良好的抑制作用,平滑度高

  适用于高频振荡的系统

  C、缺点:

  灵敏度低

  对偶然出现的脉冲性干扰的抑制作用较差

  不易消除由于脉冲干扰所引起的采样值偏差

  不适用于脉冲干扰比较严重的场合

  比较浪费RAM

  程序:

  #define N 12

  char value_buf[N];

  char i=0;

  char filter()

  {

  char count;

  int sum=0;

  value_buf[i++] = get_ad();

  if ( i == N ) i = 0;

  for ( count=0;count《N,count++)

  sum+ = value_buf[count];

  return (char)(sum/N);

  }

#p#副标题#e#

  5、中位值平均滤波法(又称防脉冲干扰平均滤波法)

  A、方法:

  相当于“中位值滤波法”+“算术平均滤波法”

  连续采样N个数据,去掉一个最大值和一个最小值

  然后计算N-2个数据的算术平均值

  N值的选取:3~14

  B、优点:

  融合了两种滤波法的优点

  对于偶然出现的脉冲性干扰,可消除由于脉冲干扰所引起的采样值偏差

  C、缺点:

  测量速度较慢,和算术平均滤波法一样

  比较浪费RAM

  程序:

  #define N 12

  char filter()

  {

  char count,i,j;

  char value_buf[N];

  int sum=0;

  for (count=0;count《N;count++)

  {

  value_buf[count] = get_ad();

  delay();

  }

  for (j=0;j《N-1;j++)

  {

  for (i=0;i《N-j-1;i++)

  {

  if ( value_buf》value_buf[i+1] )

  {

  temp = value_buf;

  value_buf = value_buf[i+1];

  value_buf[i+1] = temp;

  }

  }

  }

  for(count=1;count《N-1;count++)

  sum +=value[count];

  return(char)(sum/(N-2));

  }

  6、限幅平均滤波法

  A、方法:

  相当于“限幅滤波法”+“递推平均滤波法”

  每次采样到的新数据先进行限幅处理,

  再送入队列进行递推平均滤波处理

  B、优点:

  融合了两种滤波法的优点 对于偶然出现的脉冲性干扰,可消除由于脉冲干扰所引起的采样值偏差

  C、缺点:

  比较浪费RAM

  程序略 参考子程序1、3

  7、一阶滞后滤波法

  A、方法:

  取a=0~1

  本次滤波结果=(1-a)*本次采样值+a*上次滤波结果

  B、优点:

  对周期性干扰具有良好的抑制作用 适用于波动频率较高的场合

  C、缺点:

  相位滞后,灵敏度低 滞后程度取决于a值大小 不能消除滤波频率高于采样频率的1/2的干扰信号

  程序:

  /* 为加快程序处理速度假定基数为100,a=0~100 */

  #define a 50

  char value;

  char filter()

  {

  char new_value;

  new_value = get_ad();

  return ((100-a)*value + a*new_value);

  }

  8、加权递推平均滤波法

  A、方法:

  是对递推平均滤波法的改进,即不同时刻的数据加以不同的权

  通常是,越接近现时刻的数据,权取得越大。

  给予新采样值的权系数越大,则灵敏度越高,但信号平滑度越低

  B、优点:

  适用于有较大纯滞后时间常数的对象和采样周期较短的系统

  C、缺点:

  对于纯滞后时间常数较小,采样周期较长,变化缓慢的信号 不能迅速反应系统当前所受干扰的严重程度,滤波效果差

  程序:

  /* coe数组为加权系数表,存在程序存储区。*/

  #define N 12

  char code coe[N] ={1,2,3,4,5,6,7,8,9,10,11,12};

  char code sum_coe = 1+2+3+4+5+6+7+8+9+10+11+12;

  char filter()

  {

  char count;

  char value_buf[N];

  int sum=0;

  for (count=0,count《N;count++)

  {

  value_buf[count] = get_ad();

  delay();

  }

  for (count=0,count《N;count++)

  sum += value_buf[count]*coe[count];

  return (char)(sum/sum_coe);

  }

  9、消抖滤波法

  A、方法:

  设置一个滤波计数器

  将每次采样值与当前有效值比较:

  如果采样值=当前有效值,则计数器清零

  如果采样值《》当前有效值,则计数器+1,并判断计数器是否》=上限N(溢出)

  如果计数器溢出,则将本次值替换当前有效值,并清计数器

  B、优点:

  对于变化缓慢的被测参数有较好的滤波效果,

  可避免在临界值附近控制器的反复开/关跳动或显示器上数值抖动

  C、缺点:

  对于快速变化的参数不宜

  如果在计数器溢出的那一次采样到的值恰好是干扰值,则会将干扰值当作有效值导入系统

  程序:

  #define N 12

  char filter()

  {

  char count=0;

  char new_value;

  new_value =get_ad();

  while (value!=new_value)

  {

  count++;

  if (count》=N)return new_value;

  delay();

  new_value =get_ad();

  }

  return value;

  }

  10、限幅消抖滤波法

  A、方法:

  相当于“限幅滤波法”+“消抖滤波法”

  先限幅,后消抖

  B、优点:

  继承了“限幅”和“消抖”的优点

  改进了“消抖滤波法”中的某些缺陷,避免将干扰值导入系统

  C、缺点:

  对于快速变化的参数不宜

  程序略 参考子程序1、9

首先俺不得不说,下面俺给出的方法可以帮楼主剔除AD采样的异常子数据,但是慎用!数值稳定性是一个很复杂的东西,这个方法不能100%保证情况改善,原因有下:
楼主已经在程序里做了10次弃极值平均,这个算法非常厉害,但是仍出现了大幅波动,因此俺觉得,楼主的采样数据中应该存在多个异常值。就这一点而言,楼主是否更应该关注一下这些异常值的产生?异常值的出现是多方面的,比如ADC本身?程序问题?硬件设计问题?接触不良?电源自身的问题?建议楼主用电池、线性电源之类的采集几次试试,仔细检查检查程序有没有啥异常。硬件上ADC这玩意出问题也比较多,但是楼主用的这个ADC他不是闪速ADC(Flash ADC),出现真正意义上的闪码(Flash Code)几乎是不可能的,而硬件上出问题的概率似乎也不是很大。
因此,随便来个滤波,剃坏值貌似是不严谨,而且不负责任做法。
抛开上述问题不谈,对于某一两个坏值,这个方法还是挺好用的。
剔除坏值的方法有很多,肖维勒准则,格拉布斯准则都是可以的,当然还有其他方法。肖维勒准则对于n<10的情况处理还是不错的,而格拉布斯准则处理n>20效果较好,对于楼主的程序,不妨采用前者。
肖维勒准则说:|Vi|>|Xn-X平均|σZc,就是残差Vi的绝对值如果 大于 单次采样值与采样平均值之差绝对值的方差乘以Zc倍,那么该值为坏值。Zc可以通过查表得到,n为数据个数,大概如下
n=3 Zc=1.38
4 1.54
5 1.65
6 1.73
7 1.80
8 1.86
9 1.92
10 1.96
11 2.00
12 2.03
13 2.07
14 2.10
15 2.13

至于格拉布斯准则,楼主可以自己了解一下。
在剔除坏值以后再求平均,效果较好,楼主可以写个程序试试。
俺还是坚持上面的看法,不检查出坏值产生的原因,啥滤波剃坏值都是不太好的,虽然最后出来的东西可能比较好看。俺看楼主的单片机有4K的程序空间,不如用打log的方式,写个串口通讯吧每一次采样值都发到上位机上,看看数据情况,以便分析。
最后,祝楼主早日解决问题,做个高高高高高高精度的电压电流表!楼主的视频做的也非常好!

1.ADC配置
采样配置ADC1通道1并以循环模式工作 打开DMA1并设置中断

  ADC_InitTypeDef ADC_InitStructure;                                       //ADC初始化宏定义  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;                       //独立ADC模式                                                  
  ADC_InitStructure.ADC_ScanConvMode = ENABLE ;                            //禁止扫描模式,扫描模式用于多通道采集                               ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;                       //开启连续转换模式,即不停地进行ADC转换                       ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;      //不使用外部触发转换                                                   ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;                   //采集数据右对齐                                                       ADC_InitStructure.ADC_NbrOfChannel = 3;                                  //要转换的通道数目1                                      
  ADC_Init(ADC1, &ADC_InitStructure);                                      //ADC初始化    ADC_TempSensorVrefintCmd(ENABLE);                                           //打开温度传感器以及基准电源
  ADC_RegularChannelConfig(ADC1,ADC_Channel_1 ,1,ADC_SampleTime_239Cycles5); //配置ADC1,通道1,规则顺序1,采样时间239.5个周期      ADC_DMACmd(ADC1, ENABLE);                                                //ADC1的DMA1使能                        
  ADC_Cmd(ADC1, ENABLE);      

2.软件滤波
软件部分采样四倍过采样 并使用中位值平均滤波法 进行软件滤波,输出结果

void ADC_Operation(void)                                                   //ADC结果运算
{
    u8 u8_1,u8_2=0;                                        
    u32 ADC_Complete=0;                                      //ADC运算结果
    u16 Compare_min=0xffff,Compare_max=0;                    //ADC最大值和最小值
    for(u8_1=0;u8_1<18;u8_1++)                               //过采样运算4bit 16次+最大最小
    {
        u8_2=u8_1*3;
        if(ADC_TempValue[u8_2]<Compare_max)                    //比较最大值
        {
            if(ADC_TempValue[u8_2]>Compare_min)                  //比较最小值
                 ADC_Complete+=ADC_TempValue[u8_2];                //加数据到总数据
                else                                               //数据比现有记录最大值更大
                {
                    Compare_min=ADC_TempValue[u8_2];                 //则更新最低值记录
                    ADC_Complete+=ADC_TempValue[u8_2];               //加数据到总数据
                }
        }
        else
        {
            Compare_max=ADC_TempValue[u8_2];                     //则更新最高值记录          
            ADC_Complete+=ADC_TempValue[u8_2];                   //加数据到总数据      
        }                
    }
    ADC_Temper=ADC_TempValue[1];                             //读温度值
    ADC_Vrefint=ADC_TempValue[2];                            //读基准值
    ADC_Complete=ADC_Complete-Compare_max-Compare_min;       //总数据减去最高最低值
    ADC_max=Compare_max;
    ADC_min=Compare_min;
    ADC_ConvertedValue=ADC_Complete>>4;                      //计算16次采样值总数/4
}


3.电压计算
先放大100倍在计算,以便减少浮点数计算    加快运算速度
ADC_ConvertedValueLocal =ADC_ConvertedValue*3300/4096;  


四.ADC采样片内温度以及基准
1. 配置ADC1的16通道温度传感器和17通道的内部基准,通过DMA1传输到内存  
  在ADC1的配置中加入一下代码:
  ADC_RegularChannelConfig(ADC1,ADC_Channel_16,2,ADC_SampleTime_239Cycles5); //配置ADC1,温度传感器,规则顺序2,采样时间239.5个周期     ADC_RegularChannelConfig(ADC1,ADC_Channel_17,3,ADC_SampleTime_239Cycles5); //配置ADC1,基准,规则顺序3,采样时间239.5个周期
2.温度以及基准电压计算5
ADC_ConvertedValueLocal =ADC_Vrefint*3300/4096;  //基准电压计算
Current_Temp=(V25-ADC_Temper)/Avg_Slope+25;  //温度计算

发布了24 篇原创文章 · 获赞 46 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/m0_37777700/article/details/99733540