0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

信号处理:指数移动平均 (EMA) 滤波器

海阔天空的专栏 来源: Mustahsin Zarif 作者: Mustahsin Zarif 2025-10-04 18:35 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

作者: Mustahsin Zarif

之前我们在《信号处理简介》一文中已经见过了两类滤波器:有限脉冲响应 (FIR) 滤波器和无限脉冲响应 (IIR) 滤波器。我们看到了移动平均滤波器如何同时以 FIR 和 IIR 形式表示,但如果它们相互比较又各具哪些优势呢?

回顾我之前博客中的例子,我们可以将 FIR 滤波器展开为如下形式:

y[5] = (x[5]+x[4]+x[3]+x[2]+x[1]+x[0])/5,

在这里,我们看到我们需要:

  1. 5 次乘法和
  2. 4 次求和运算。

乘法运算的计算成本特别高。因此,如果我们再次查看 IIR 形式,我们会发现它只需要:

  1. 3 次乘法和
  2. 2 次求和运算

y[6]=(x[6]+y[5]-x[1])/5

这大大降低了计算成本!这对于单片机嵌入式设备来说非常好,因为它们在每个离散时间步骤上执行计算所消耗的资源更少。

例如,当我对采用 FIR 和 IIR 形式的 11 点移动平均滤波器使用 Python 函数 ‘time.time’ 时,所有参数(窗口大小、采样率、样本大小等)相同,我分别得到以下运行时间结果:51 ms、27 ms。

离散时间IIR 滤波器示例

现在我们已经了解了为什么 IIR 滤波器在单片机上表现更好了,让我们看一个使用 Arduino UNO 和 DFRobot MPU6050 惯性测量单元 (IMU) 的示例项目(图 1)。我们将对 IMU 数据应用指数移动平均 (EMA) 滤波器,以查看原始数据和平滑数据之间的差异。

图 1:MPU6050 与 Arduino Uno 之间的连接框图。(图片来源:Mustahsin Zarif)

图 2:MPU6050 与 Arduino Uno 之间的连接。(图片来源:Mustahsin Zarif)

指数移动平均滤波器具有递归形式:

y[n] = α*x[n] + (1- α)*y[n-1]

它之所以是递归,是因为我们测量的任何当前输出也取决于先前的输出;即系统具有记忆。

常数 alpha () 决定了我们想要赋予当前输入相对于先前输出多大的权重。为了清楚起见,让我们展开方程得到:

y[n] = α x[n] + (1- α ) (α*x[n−1]+(1−α)*y[n−2])

y[n] = αx[n] + (1- α ) x[n−1]+α (1−α)2x[n−2])+ ...

y[n] = k=0nα*(1−α)k*x[n−k]

我们看到,alpha 越大,当前输入对当前输出的影响就越大。这是好事,因为如果系统在不断演进,过去的值不能代表当前的系统。另一方面,如果系统突然发生瞬间异常变化,情况就会变得很糟糕;在这种情况下,我们希望我们的输出能够遵循之前输出所遵循的趋势。

事不宜迟,现在让我们看看 EMA 滤波器的代码是如何用于 MPU6050 的。

EMA 滤波器代码:

副本#include < wire.h >
#include < mpu6050.h >

MPU6050 mpu;

#define BUFFER_SIZE 11  // Window size

float accelXBuffer[BUFFER_SIZE];
float accelYBuffer[BUFFER_SIZE];
float accelZBuffer[BUFFER_SIZE];
int bufferCount = 0;  

void setup() {
  Serial.begin(115200);
  Wire.begin();

  mpu.initialize();

  if (!mpu.testConnection()) {
    Serial.println("MPU6050 connection failed!");
    while (1);
  }

  int16_t ax, ay, az;
  for (int i = 0; i < BUFFER_SIZE; i++) {
    mpu.getMotion6(&ax, &ay, &az, NULL, NULL, NULL);
    accelXBuffer[i] = ax / 16384.0;
    accelYBuffer[i] = ay / 16384.0;
    accelZBuffer[i] = az / 16384.0;
  }
  bufferCount = BUFFER_SIZE;
}

void loop() {
  int16_t accelX, accelY, accelZ;

  mpu.getMotion6(&accelX, &accelY, &accelZ, NULL, NULL, NULL);

  float accelX_float = accelX / 16384.0;
  float accelY_float = accelY / 16384.0;
  float accelZ_float = accelZ / 16384.0;

  if (bufferCount < BUFFER_SIZE) {
    accelXBuffer[bufferCount] = accelX_float;
    accelYBuffer[bufferCount] = accelY_float;
    accelZBuffer[bufferCount] = accelZ_float;
    bufferCount++;
  } else {
    for (int i = 1; i < BUFFER_SIZE; i++) {
      accelXBuffer[i - 1] = accelXBuffer[i];
      accelYBuffer[i - 1] = accelYBuffer[i];
      accelZBuffer[i - 1] = accelZBuffer[i];
    }
    accelXBuffer[BUFFER_SIZE - 1] = accelX_float;
    accelYBuffer[BUFFER_SIZE - 1] = accelY_float;
    accelZBuffer[BUFFER_SIZE - 1] = accelZ_float;
  }

//calculate EMA using acceleration values stored in buffer
  float emaAccelX = accelXBuffer[0];
  float emaAccelY = accelYBuffer[0];
  float emaAccelZ = accelZBuffer[0];
  float alpha = 0.2;

  for (int i = 1; i < bufferCount; i++) {
    emaAccelX = alpha * accelXBuffer[i] + (1 - alpha) * emaAccelX;
    emaAccelY = alpha * accelYBuffer[i] + (1 - alpha) * emaAccelY;
    emaAccelZ = alpha * accelZBuffer[i] + (1 - alpha) * emaAccelZ;
  }

  Serial.print(accelX_float); Serial.print(",");
  Serial.print(accelY_float); Serial.print(",");
  Serial.print(accelZ_float); Serial.print(",");
  Serial.print(emaAccelX); Serial.print(",");
  Serial.print(emaAccelY); Serial.print(",");
  Serial.println(emaAccelZ);

  delay(100);
}
< /mpu6050.h >< /wire.h >

当我们运行此代码并检查串口绘图仪时,我们可以看到 x、y 和 z 轴方向加速度的成对粗糙和平滑线条,其中窗口大小为 11 和 alpha 值为 0.2(图 3 至 5)。

图 3:x 方向的原始加速度值和滤波后的加速度值。(图片来源:Mustahsin Zarif)

图 4:y 方向的原始加速度值和滤波后的加速度值。(图片来源:Mustahsin Zarif)

图 5:z 方向的原始加速度值和滤波后的加速度值。(图片来源:Mustahsin Zarif)

让代码的智能化更进一步

我们现在知道,与 FIR 滤波器相比,IIR 滤波器更适合用作控制器,因为所需的求和和乘法计算明显较少。然而,当我们实现这段代码时,执行的计算并不只有求和和乘法:每当有新的时间样本进入时,我们都必须移动样本,而这个过程在后台需要计算能力。因此,我们可以借助循环缓冲区,而不是在每个采样时间间隔移动所有样本。

我们的做法是:用一个指针来记住传入的数据样本的索引。然后,每次指针指向缓冲区中的最后一个元素时,它接下来都会指向缓冲区的第一个元素,新数据将替换之前存储在这里的数据,因为这是现在我们不再需要的最旧数据(图 6)。因此,这种方法允许我们跟踪缓冲区中最旧的样本并替换该样本,而不必每次都移动样本以将新数据放入数组的最后一个元素中。

图 6:循环缓冲区示例图。(图片来源:Mustasin Zafir)

这是使用循环缓冲区的 EMA 滤波器实现的代码。您能尝试对陀螺仪而不是对加速计运行这段代码吗?也可以尝试使用不同的系数!

使用循环缓冲区代码的 EMA 滤波器:

副本#include < wire.h >

#include < mpu6050.h >

MPU6050 mpu;

#define BUFFER_SIZE 11  // Window size

float accelXBuffer[BUFFER_SIZE];

float accelYBuffer[BUFFER_SIZE];

float accelZBuffer[BUFFER_SIZE];

int bufferIndex = 0;  

void setup() {

  Serial.begin(115200);

  Wire.begin();
 

  mpu.initialize();


  if (!mpu.testConnection()) {

    Serial.println("MPU6050 connection failed!");

    while (1);

  }

  int16_t ax, ay, az;

  for (int i = 0; i < BUFFER_SIZE; i++) {

    mpu.getMotion6(&ax, &ay, &az, NULL, NULL, NULL);

    accelXBuffer[i] = ax / 16384.0;

    accelYBuffer[i] = ay / 16384.0;

    accelZBuffer[i] = az / 16384.0;

  }

}

void loop() {

  int16_t accelX, accelY, accelZ;

  mpu.getMotion6(&accelX, &accelY, &accelZ, NULL, NULL, NULL);

  float accelX_float = accelX / 16384.0;

  float accelY_float = accelY / 16384.0;

  float accelZ_float = accelZ / 16384.0;

  accelXBuffer[bufferIndex] = accelX_float;

  accelYBuffer[bufferIndex] = accelY_float;

  accelZBuffer[bufferIndex] = accelZ_float;

  bufferIndex = (bufferIndex + 1) % BUFFER_SIZE; //circular buffer implementation 

  float emaAccelX = accelXBuffer[bufferIndex];

  float emaAccelY = accelYBuffer[bufferIndex];

  float emaAccelZ = accelZBuffer[bufferIndex];

  float alpha = 0.2;

  for (int i = 1; i < BUFFER_SIZE; i++) {

    int index = (bufferIndex + i) % BUFFER_SIZE;

    emaAccelX = alpha  accelXBuffer[index] + (1 - alpha)  emaAccelX;

    emaAccelY = alpha  accelYBuffer[index] + (1 - alpha)  emaAccelY;

    emaAccelZ = alpha  accelZBuffer[index] + (1 - alpha)  emaAccelZ;

  }

  Serial.print(accelX_float); Serial.print(",");

  Serial.print(emaAccelX); Serial.print(",");

  Serial.print(accelY_float); Serial.print(",");

  Serial.print(emaAccelY); Serial.print(",");

  Serial.print(accelZ_float); Serial.print(",");

  Serial.println(emaAccelZ);

  delay(100);

}
< /mpu6050.h >< /wire.h >

结语

在这篇博客中,我们讨论了 IIR 和 FIR 滤波器之间的区别,重点讨论了它们的计算效率。通过从 FIR 到 IIR 所需运算次数减少这一小例子,我们可以想象当应用规模化时 IIR 滤波器的效率会有多高,这对于硬件能力有限的实时应用非常重要。

我们还研究了一个使用 Arduino Uno 和 MPU6050 IMU 的示例项目,其中我们部署了一个指数移动平均滤波器来降低传感器数据中的噪声,同时仍然捕捉底层信号行为。最后,为了提高效率,我们提供了一个更智能的示例代码,即采用循环缓冲区而不是在每个时间间隔移动数据。

在下一篇博客中,我们将利用 [Red Pitaya] 的 FPGA 功能来实现一个 4 抽头 FIR 滤波器数字电路

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 滤波器
    +关注

    关注

    162

    文章

    8502

    浏览量

    186703
  • 信号处理
    +关注

    关注

    49

    文章

    1181

    浏览量

    105348
  • ema
    ema
    +关注

    关注

    0

    文章

    4

    浏览量

    2508
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    屏蔽电源滤波器的分类

    滤波器 通用型屏蔽滤波器 高性能型屏蔽滤波器 低漏电型电源屏蔽滤波器 直流电源专用屏蔽滤波器 EMC暗室专用高性能
    的头像 发表于 04-16 10:53 277次阅读
    屏蔽电源<b class='flag-5'>滤波器</b>的分类

    滤波器:突破传统边界,高频时代之下的“挑战”

    在电子信息、通信工程、电力系统等多个技术领域,滤波器作为核心的信号处理器件,承担着“筛选信号、抑制干扰”的关键作用,是保障电子设备稳定运行、提升信号
    的头像 发表于 04-08 14:44 1097次阅读

    电子信号处理滤波器的关键要点与发展走向

    在电子信号处理领域,滤波器是不可或缺的核心器件,其核心功能是对输入信号进行频率筛选,让有用信号无衰减通过、抑制无用噪声及干扰
    的头像 发表于 03-28 13:59 1961次阅读

    科瑞杰横河WT5000功率分析仪指数平均移动平均功能的区别

    深圳市科瑞杰科技有限公司--横河WT5000功率分析仪都有一个平均功能,这个功能包含两种平均化方法,分别是指数平均移动
    的头像 发表于 03-26 17:42 1344次阅读
    科瑞杰横河WT5000功率分析仪<b class='flag-5'>指数</b><b class='flag-5'>平均</b>和<b class='flag-5'>移动</b><b class='flag-5'>平均</b>功能的区别

    滑动平均滤波器介绍

    滑动平均滤波器在工程中十分常见,这里用C语言设计一个灵活的滑动滤波器库函数,十分简单易用,并且滤波器不使用for循环,大大减少了代码运行时间。 并且
    发表于 01-23 08:18

    MAX262微处理器可编程通用有源滤波器:设计与应用指南

    MAX260/MAX261/MAX262 微处理器可编程通用有源滤波器:设计与应用指南 在电子设计领域,滤波器信号处理中不可或缺的组件。M
    的头像 发表于 01-20 11:05 617次阅读

    自适应滤波算法介绍之匹配滤波器的基本原理和应用示例

    自适应滤波理论在统计信号处理中占据非常重要的地位,在通信、控制、雷达等领域获得广泛应用。自适应滤波器的基本目标,是通过某种方式对参数θ(k)进行调整,使
    的头像 发表于 01-07 14:52 3913次阅读
    自适应<b class='flag-5'>滤波</b>算法介绍之匹配<b class='flag-5'>滤波器</b>的基本原理和应用示例

    如何选择一个合适的高阶低通滤波器

    高阶低通滤波器是一种通过组合多个二阶滤波器级来实现的滤波器,用于抑制高频信号并保留低频信号。从信号
    的头像 发表于 12-30 15:37 1483次阅读
    如何选择一个合适的高阶低通<b class='flag-5'>滤波器</b>

    中频信号处理的“精准筛子”:杰盈JY-SBP-10.7+带通滤波器技术解析

    在高频 rejection、镜像抑制与中频信号处理领域,9.5-11.5MHz 频段是信号提纯与干扰管控的关键区间,对滤波器的低插损、高选择性、宽温可靠性提出了严苛要求。杰盈通讯
    的头像 发表于 11-20 15:48 765次阅读
    中频<b class='flag-5'>信号</b><b class='flag-5'>处理</b>的“精准筛子”:杰盈JY-SBP-10.7+带通<b class='flag-5'>滤波器</b>技术解析

    信号处理简介:移动平均滤波器

    设计电路,以任何我们想要的方式控制信号。我们竟然能够使用电阻、电容器和运算放大器 (op-amps) 等电子元器件组合来模拟积分和微分等数学工具,这着实令人惊叹。 例如以下电路(图1): 图 1:简单的积分电路。(图片来源:Mustahsin Zarif) 进入拉普拉斯
    的头像 发表于 10-04 18:21 2161次阅读
    <b class='flag-5'>信号</b><b class='flag-5'>处理</b>简介:<b class='flag-5'>移动</b><b class='flag-5'>平均</b><b class='flag-5'>滤波器</b>

    高频滤波器精密加工——如何“筛”出纯净信号

    高频滤波器是无线通信、雷达、卫星导航等领域的核心元件,其作用是精准筛选特定频率信号,抑制干扰噪声。随着5G、物联网等技术的普及,通信设备对滤波器的性能要求愈发严苛——频率选择性更强、插入损耗更低
    的头像 发表于 09-24 15:02 838次阅读

    横河WT5000如何区别运用指数平均移动平均功能?

    横河功率分析仪都有平均功能,这个功能包含两种平均化方法,分别是指数平均移动平均(也叫线性
    的头像 发表于 08-19 19:16 1134次阅读
    横河WT5000如何区别运用<b class='flag-5'>指数</b><b class='flag-5'>平均</b>跟<b class='flag-5'>移动</b><b class='flag-5'>平均</b>功能?

    常用的数字滤波器算法及其特性

    常用的软件滤波器有限幅滤波法、中位值滤波法、算数平均滤波法、递推平均
    的头像 发表于 06-18 09:04 1480次阅读

    有源滤波器与无源滤波器的区别

    滤波器是根据电路参数对电路频带宽度的影响而设计出来的工程应用电路,滤波器种类很多,有源滤波器和无源滤波器的区别我们最简单的分别办法是看看是否需要电源,在作用上最大的区别在于有源
    的头像 发表于 06-18 09:03 2405次阅读