刚开始看到这个算法是只觉得很佩服,后来用了各种各样的算法后,才感到此算法简直到了鬼斧神工的地步(别以为看完后觉得太简单没啥大不了的,正是因为太简单才突出了它的了不起,最开始能想到将一个复杂的算法简化到无法再简的地步非一般功力所能做到的)。
在该基础上,我们再演变出一种带死区和限幅控制的队列平均值实用算法。采样值 C、累加器 S,平均值 A,采样次数 N;
传统的平均值滤波算法:
S = C(1) + C(2) + ... + C(N)
A = S / N
需要用循环来计算累加和,比较耗时,C(1~N)是缓存,随采样数N增大,所需内存量也增大
向队列平均值算法推进:
S = C(1) + C(2) + ... + C(N) (第一次)
C(x) = C(x + 1)(队列前移)
C(N) = C
S = S - C(1) + C(N)
A = S / N
运算量有所改进(用指针维护循环队列,不实际移动数据),占用内存问题不变
鬼斧神工算法:
初始化:A=初始值,S=A*N
S = S - A + C(i) i从第二个起,不算A
A = S / N
就这么简单,三个变量(N可以是常数),只要 S 的量程足够,N可以任意调整。
可以看出,此算法是从队列平均值算法演变而来,因没有了队列,每次计算时不知道该丢弃的最老的一个采样值是多少,这里用了个替代的办法,丢弃上次计算出的平均值。
去掉了缓存维护,节省内存空间,同时也将运算量压缩到了最小,执行效率非常高。调试时容易修改采样数。
优化算法:
此算法的核心思想还是平均值滤波,虽然改进了运算量和内存占用,但同样继承了平均值滤波法 N 值较大时平滑度好,反应迟钝的特点。
为此,对算法引入 S7-200 系统滤波程序中死区的概念:采样值偏差在死区范围内时,进行滤波计算,采样值偏差在死区范围以外时直接使用采样值,达到快速反应的效果。
再溶合限幅滤波法去掉偶然的干扰脉冲:采样值偏差在限幅范围内时,进行滤波计算,采样值偏差在限幅范围以外时直接丢弃,使用上次滤波输出值。显然,限幅值应该大于死区值。
将此算法写成两个子函数(也可以做成库)
① 主滤波程序 AveFilter
入口参数:
EN : 调用使能位
bType :采样值类型,'W'=整型、'F'=浮点型、'D'(或其它)=长整型,参数类型:字节
wHi : 采样值高位字(采样值为整型是,实参必须为0),参数类型,2字节
wLo : 采样值低位字,参数类型,2字节
rDie : 滤波死区,参数类型:浮点数
rMaxErr : 最大允许偏差,参数类型:浮点数
rLen :滤波队列长度,参数类型:浮点数
出/入口参数:
rSum :累加和,参数类型:浮点数
rAve :滤波输出平均值,参数类型:浮点数
命令行:CALL AveFilter, 'W', 0, SMW28, 640.0, 32000.0, 4.0, VD0, VD4
注意:本程序采样值是参数类型可适应的,用 wHi/wLo 的组合来适应整型、长整型、浮点型的参数类型输入,避免使用多个相同的子程序来适应不同类型的输入参数。由 bType 来指定输入的参数类型。
在许多的数据采集系统中,现场的强电设备较多,不可避免地会产生尖脉冲干扰,这种干扰一般持续时间短,峰值大,对这样的数据进行数字滤波处理时,仅仅采用算术平均或移动平均滤波时,尽管对脉冲干扰进行了1/n的处理,但,其剩余值仍然较大。
这种场合最好的策略是:将被认为是受干扰的信号数据去掉,这就是防脉冲干扰平均值滤波法的原理。防脉冲干扰平均值滤波法的算法是:对连续的n个数据进行排序,去掉其中最大和最小的2个数据,将剩余数据示平均值。
在一般8051单片机的应用中为了加快数据处理速度,n可以取值6。而对于具有较快速度的处理器,则n值可以适当取大一些。但最好是n=2^k+2, k为整数,因为这样在求平均值average=SUM/(n-2)=SUM/2^k时,可以写成average=SUM>>k,用移位的方法,可以加快处理速度。
上述算法显然还存在一个不足之处,就是每采集一个数据就要进行一次排序,这样会大量占用系统宝贵的时间。这可以通过存储当前数据中的最大值和最小值来改进。具体做法是:系统中用两个变量来存储当前n个数据的最大值和最小值在这个数组中的偏移量(也就是数组下标,存储数组下标而直接不存储数据本身是因为:在一般的系统中,n不会超无符号短整形的表示范围,因此用一个char形变量就可以存储了而如果直接存储数据本身,则许多情况下要用int形变量,甚至更长的类型)。这样只要在当前输入的数据将要覆盖的数据正好是当前的最大值或最小值时才在下个数组中查找最大值或最小值,而其他情况下则只要将输入的数据与最大值和最小值比较就可以修改下最大值和最小值了,而且不用进行数据排序。
这个算法很简单,下面是对应的C语言代码实现,可以很方便的应用的具体的51单片机或其他处理器上,只须做少量的修改。
#include"stdio.h"
#define dtypeunsigned int//采集数据的数据类型
#define uint8char
#define LEN6//移动算术平均的个数+2=SHIFT<<2+2
#define SHIFT2//2^SHIFT
uint8 pdata;//移动指针
uint8 pmax,pmin;//记录数据表中最大值和最小值的位置,在一般的数据采集系统中,数据的长度>=8,因此用指针记录而不是直接记录最大值和最小值
dtype datas[LEN];
dtype szlb(dtype _data)
{
/****************************/
/*在调用此子程序前必须对pdata,datas[]数组,pmax,pmin进行初始化*/
/****************************/
uint8 i;
dtype average=0;//清零,用来计算平均值
pdata=(pdata+1)%LEN;//指针下标在0到LEN-1上滑动
datas[pdata]=_data;//采样所得数据存入数据表中
for(i=0;i<LEN;i++)
average+=datas[i];//求所有数据总和
/*******去除被认为是脉冲的数据******/
if(_data>datas[pmax])
pmax=pdata;//得到最大值的指针
else if(_data<datas[pmin])
pmin=pdata;//得到最小值的指针
if(pdata==pmax)//如果当前输入值将存入当前最大值的位置时
{//由以上方法将不可行,必须从其他位置中查找极值
for(i=0;i<LEN;i++)
if(datas[i]>datas[pmax])
pmax=i;
}
else if(pdata==pmin)//如果当前输入值将存入当前最大值的位置时
{//由以上方法将不可行,必须从其他位置中查找极值
for(i=0;i<LEN;i++)
if(datas[i]<datas[pmin])
pmin=i;
}
average=average-datas[pmax]-datas[pmin];//减去脉冲
return (average>>SHIFT);//求算术平均值
}
/******以下是在VC++6.0环境下运行的测试程序**/
/***通过手动输入来模拟数据采集过程****/
void main()
{
uint8 i;
dtype _data;
pdata=0;
pmax=0;
pmin=0;
for(i=0;i<LEN;i++)
datas[i]=0;
printf("数据: 最大 最小/n");
while(1)
{
scanf("%u",&_data);
szlb(_data);
for(i=0;i<LEN;i++)
printf("%-3u ",datas[i]);
printf(" %-3u %-3u",datas[pmax],datas[pmin]);
printf("/n");
}
}