10种简单的数字滤波算法(C语言源程序)

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

 A、方法:

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

 每次检测到新值时判断:

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

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

 B、优点:

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

 C、缺点

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

 平滑度差

#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;
  return new_value;
}

2、中位值滤波法

 A、方法:

 连续采样N次(N取奇数)

 把N次采样值按大小排列

 取中间值为本次有效值

 B、优点:

 能有效克服因偶然因素引起的波动干扰

 对温度、液位的变化缓慢的被测参数有良好的滤波效果

 C、缺点:

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

#define N  11
char filter()
{
  char value_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;i++)
     {
        if ( value_buf[i]>value_buf[i+1] )
        {
           temp = value_buf[i];
           value_buf[i] = value_buf[i+1]; 
            value_buf[i+1] = temp;
        }
     }
  }
  return value_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、递推平均滤波法(又称滑动平均滤波法)

 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);
}

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;i++)
     {
        if ( value_buf[i]>value_buf[i+1] )
        {
           temp = value_buf[i];
           value_buf[i] = 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的干扰信号


#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、缺点:

 对于纯滞后时间常数较小,采样周期较长,变化缓慢的信号

 不能迅速反应系统当前所受干扰的严重程度,滤波效果差


#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

字符设备 register_chrdev_region()、alloc_chrdev_re

内核中所有已分配的字符设备编号都记录在一个名为 chrdevs 散列表里。该散列表中的每一个元素是一个 char_device_struct 结构,它的定义如下:

  static struct char_device_struct {
      struct char_device_struct *next;    // 指向散列冲突链表中的下一个元素的指针
      unsigned int major;                 // 主设备号
      unsigned int baseminor;             // 起始次设备号
      int minorct;                        // 设备编号的范围大小
      char name[64];                      // 处理该设备编号范围内的设备驱动的名称
      struct file_operations *fops;       // 没有使用
      struct cdev *cdev;                  // 指向字符设备驱动程序描述符的指针
  } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

注意,内核并不是为每一个字符设备编号定义一个 char_device_struct 结构,而是为一组对应同一个字符设备驱动的设备编号范围定义一个 char_device_struct 结构。chrdevs 散列表的大小是 255,散列算法是把每组字符设备编号范围的主设备号以 255 取模插入相应的散列桶中。同一个散列桶中的字符设备编号范围是按起始次设备号递增排序的。

注册
内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。这三个函数都会调用一个共用的 __register_chrdev_region() 函数来注册一组设备编号范围(即一个 char_device_struct 结构)。

register_chrdev_region(dev_t first,unsigned int count,char *name)
First :要分配的设备编号范围的初始值(次设备号常设为0);
Count:连续编号范围.
Name:编号相关联的设备名称. (/proc/devices);
动态分配:
Int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);
Firstminor : 通常为0;
*dev:存放返回的设备号;
释放:
Void unregist_chrdev_region(dev_t first,unsigned int count);
调用Documentation/devices.txt中能够找到已分配的设备号.

所以下面先来看一下 __register_chrdev_region() 函数的实现代码。

static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name)
{
  struct char_device_struct *cd, **cp;
  int ret = 0;
  int i;

  cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
  if (cd == NULL)
      return ERR_PTR(-ENOMEM);

  mutex_lock(&chrdevs_lock);

  if (major == 0) {
       for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i–)
          if (chrdevs[i] == NULL)
              break;

      if (i == 0) {
          ret = -EBUSY;
          goto out;
      }
      major = i;
      ret = major;
  }

  cd->major = major;
  cd->baseminor = baseminor;
  cd->minorct = minorct;
  strncpy(cd->name,name, 64);

  i = major_to_index(major);

  for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
      if ((*cp)->major > major ||
           ((*cp)->major == major && ( ((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor)) ))
          break;

 
  if (*cp && (*cp)->major == major) {
      int old_min = (*cp)->baseminor;
      int old_max = (*cp)->baseminor + (*cp)->minorct – 1;
      int new_min = baseminor;
      int new_max = baseminor + minorct – 1;

     
      if (new_max >= old_min && new_max <= old_max) {
          ret = -EBUSY;
          goto out;
      }

     
      if (new_min <= old_max && new_min >= old_min) {
          ret = -EBUSY;
          goto out;
      }
  }

  cd->next = *cp;
  *cp = cd;
  mutex_unlock(&chrdevs_lock);
  return cd;
out:
  mutex_unlock(&chrdevs_lock);
  kfree(cd);
  return ERR_PTR(ret);
}
函数 __register_chrdev_region() 主要执行以下步骤:
1. 分配一个新的 char_device_struct 结构,并用 0 填充。
2. 如果申请的设备编号范围的主设备号为 0,那么表示设备驱动程序请求动态分配一个主设备号。动态分配主设备号的原则是从散列表的最后一个桶向前寻找,那个桶是空的,主设备号就是相应散列桶的序号。所以动态分配的主设备号总是小于 256,如果每个桶都有字符设备编号了,那动态分配就会失败。
3. 根据参数设置 char_device_struct 结构中的初始设备号,范围大小及设备驱动名称。
4. 计算出主设备号所对应的散列桶,为新的 char_device_struct 结构寻找正确的位置。同时,如果设备编号范围有重复的话,则出错返回。
5. 将新的 char_device_struct 结构插入散列表中,并返回 char_device_struct 结构的地址。

分析完 __register_chrdev_region() 后,我们来一个个看那三个注册函数。首先是 register_chrdev_region()。

register_chrdev_region() 函数用于分配指定的设备编号范围。如果申请的设备编号范围跨越了主设备号,它会把分配范围内的编号按主设备号分割成较小的子范围,并在每个子范围上调用 __register_chrdev_region() 。如果其中有一次分配失败的话,那会把之前成功分配的都全部退回。

alloc_chrdev_region() 函数用于动态申请设备编号范围,这个函数好像并没有检查范围过大的情况,不过动态分配总是找个空的散列桶,所以问题也不大。通过指针参数返回实际获得的起始设备编号。

最后一个 register_chrdev() 是一个老式分配设备编号范围的函数。它分配一个单独主设备号和 0 ~ 255 的次设备号范围。如果申请的主设备号为 0 则动态分配一个。该函数还需传入一个 file_operations 结构的指针,函数内部自动分配了一个新的 cdev 结构。关于这些,在后续讲字符设备驱动的注册时会说明。

注销
和注册分配字符设备编号范围类似,内核提供了两个注销字符设备编号范围的函数,分别是 unregister_chrdev_region() 和 unregister_chrdev() 。它们都调用了 __unregister_chrdev_region() 函数。

struct-timeval 用法

gettimeofday() — 获取当前时间(保存在结构体timeval中)

例子:

#include <stdio.h>
#include <sys/time.h>
#include <time.h>

int main(int argc, char * argv[]){
   struct timeval tv;                //(1)
   while(1){
       gettimeofday(&tv, NULL);      //(2)
       printf(“time %u:%u\n”, tv.tv_sec, tv.tv_usec);
       sleep(2);
   }
   return 0;

}

(1) struct–timeval

struct timeval {
   time_t      tv_sec;    
   suseconds_t tv_usec;   
};
millisecond        毫秒
microsecond        微秒
timeval表示一个时间点,比如:
timeval.tv_sec = 1   (s)
timevat.tv_usec = 500 000 (
μs)
1:500 = 1s500000
μs = 1.5s
(2) gettimeofday()
int gettimeofday(struct timeval *tv, struct timezone *tz); 

 
gettimeofday()会把目前的时间有tv所指的结构返回,当地时区的信息则放到tz所指的结构中。

timezone 结构定义为:
struct timezone{
int tz_minuteswest;
int tz_dsttime;
};

vc++编译error LNK2001| unresolved external symbol _WinMain@16|symbol __beginthreadex

在编译VC++程序时,出现Linking… /subsystem:windows
LIBCD.lib(wincrt0.obj) : error LNK2001: unresolved external symbol _WinMain@16
Debug/TestWin.exe : fatal error LNK1120: 1 unresolved externals
Error executing link.exe.

1.原因

解决这个错误首先在新建工程时要分清Win32 Application和Win32 Console Application,因为Win32 Application的入口函数为WinMain,Win32 Console Application的入口函数是main。就是说,如果你编写传统的C程序,必须建立Win32 Console程序,但VC里面默认的是Win32 Application,于是上面提及的链接错误就就经常出现了。

2.解决办法

将[project]-[settings]-[link]的project options里的 /subsystem:windows
改成 /subsystem:console

我发现与前几天出现的libcmtd.lib(crt0.obj) : error LNK2001: unresolved external symbol _main(解决办法见另一篇文章http://renrenstudy.com/php/vc-6-0-compile-error/)有几分相似,好多错误之前都会出现error LNK2001,于是我在网上浏览类似错误原因,还有解决办法,这里整理一下,方便亲们以后查询:

1.libcmtd.lib(crt0.obj) : error LNK2001: unresolved external symbol _main

解决方法:
见另一篇文章http://renrenstudy.com/php/vc-6-0-compile-error/

2.LIBCD.lib(wincrt0.obj) : error LNK2001: unresolved external symbol _WinMain@16

解决方法:见上文
3.msvcrtd.lib(crtexew.obj) : error LNK2001: unresolved external symbol _WinMain@16

解决方法:MFC项目的程序入口函数是WinMain, 如果编译项目的Unicode版本, 程序入口必须改为wWinMainCRTStartup, 所以需要重新设置程序入口:
[Project] –> [Settings] –> 选择”C/C++”属性页,
在Category中选择Output,
再在Entry-point symbol中填入wWinMainCRTStartup, 即可

4.nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __beginthreadex

nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __endthreadex

解决方法:

线程运行时库设置错误,这是因为MFC要使用多线程时库, 需要更改设置:
[Project] –> [Settings] –> 选择”C/C++”属性页,
在Category中选择Code Generation,
再在Use run-time library中选择Debug Multithreaded或者multithreaded
其中,
Single-Threaded                  单线程静态链接库(release版本)
Multithreaded                    多线程静态链接库(release版本)
multithreaded DLL                多线程动态链接库(release版本)
Debug Single-Threaded            单线程静态链接库(debug版本)
Debug Multithreaded              多线程静态链接库(debug版本)
Debug Multithreaded DLL          多线程动态链接库(debug版本)
单线程: 不需要多线程调用时, 多用在DOS环境下
多线程: 可以并发运行
静态库: 直接将库与程序Link, 可以脱离MFC库运行
动态库: 需要相应的DLL动态库, 程序才能运行
release版本: 正式发布时使用
debug版本: 调试阶段使用

vc 6.0 编译出错| error LNK2001: unresolved external symbol _main|fatal error LNK1120: 1 unresolved externals|解决方法

今天在学习VC++时,把网上下载好的例子,载入进行编译,可是出现error LNK2001: unresolved external symbol _main和fatal error LNK1120: 1 unresolved externals ,刚开始还挪动到安装目录工程下,没有任何效果,于是百度查找许久,把解决方法写下:

一、原因

1, 你用vc建了一个控制台程序,它的入口函数应该是main, 而你使用了WinMain.

2. 你用vc打开了一个.c/.cpp 文件,然后直接编译这个文件,这个文件中使用了WinMian而不是main作为入口函数。vc这时的默认设置是针对控制台程序的。

Windows子系统设置错误, 提示:libcmtd.lib(crt0.obj) : error LNK2001: unresolved external symbol _main,Windows项目要使用Windows子系统, 而不是Console。

二、 解决方法
1.进入【project】->【setting】->c/c++, 在category(类型目录)中选择preprocessor(预处理器),在processor definitions中删除_console, 添加_windows.

2.进入【project】->【setting】->Link(连接), 在Project options中将 /subsystem:console改为/subsystem:windows.

VC++6.0弹出“0x5003eaed”指令引用的“0x00000000”内存不能为“read”|“打开”文件按钮失效退出(附图)

今天在用VC++6.0时点打开文件时提示弹出“0x5003eaed”指令引用的“ox00000000”内存,该内存不能为“read”。我在网上也看了很多解决办法,总是有些模模糊糊,对初学者不太容易弄懂,特写此博客做一些总结:
一、原因
启动“打开文件”对话框,在 Visual C++ 中使用键盘快捷键或从文件菜单中将产生以下错误:
访问冲突 (0xC0000005) 在 0x5003eaed DEVSHL.DLL 中。
DevShl.Dll 引用在 “0x0000000” 内存。无法读取内存。
当您从 项目 菜单中选择 添加到项目 并单击 文件 时,将出现相同错误。
也有说是Miscrofoft office 2007与VC++不兼容,可是卸载后有时还会出错。
二、解决方法
FileTool.exe 是一个示例,用于替换“打开”和“添加”到项目菜单项在Visual C++中使用开发工具 Studio对象模型。
1.首先下载FileTool.exe,并运行。

下载地址:http://support.microsoft.com/kb/241396

自己选在安装路径,这里安装在D:\Program Files\Microsoft Visual Studio。

2.用VC++打开FileTool文件夹里的“FileTool.dsw”编译一下,就会在FileTool文件夹里的Debug文件夹里找到生成的“FileTool.dll”文件,然后将“FileTool.dll”文件复制到你安装VC++的D:\Program Files\Microsoft Visual Studio\Common\MSDev98\AddIns里,然后重启VC++。

3.打开VC++“工具”菜单下“定制”对话框中单击“附加项和宏文件”选项卡,勾选上“FileTool Developer Studio Add-in”,保存,这时会出现

,以后添加就点“A”,打开就点“O”,不要再使用菜单里的这两个按钮以及快捷键。