移动平均滤波器:从原理到DSP ADC采样实战(C语言实现)

做嵌入式开发的同学,大概率都遇到过这样的痛点:用ADC采集传感器数据时,读数总在小幅跳动——明明传感器静置不动,串口打印的数值却像“坐过山车”一样忽高忽低。这种高频噪声不仅会拉低数据精度,更可能导致后续控制逻辑误判:比如电机控制中误触发过流保护,或是温湿度监控系统出现无意义的报警弹窗。

应对这类问题,滤波器是核心解决方案。而移动平均滤波器(Moving Average Filter, MAF)凭借结构简单、计算量小、资源占用低的优势,成为嵌入式场景的“入门首选滤波方案”。今天这篇文章,就从原理推导、C语言实现,到DSP ADC采样实战,手把手带你吃透移动平均滤波器,彻底解决数据波动的烦恼。

一、简化推导:搞懂移动平均的核心逻辑

移动平均的核心逻辑特别好理解:用最近N个连续采样点的平均值,替代当前的原始采样值,以此抹平高频噪声。这里的N被称为“窗口大小”,是核心可调参数——窗口越大,滤波平滑效果越好,但对信号突变的响应速度越慢;窗口越小,响应速度越快,但滤波效果会减弱。实际开发中,需要根据场景需求平衡这两个指标。

我们用最直观的方式推导核心公式:假设已连续采集x₀, x₁, …, xₙ₋₁共n个数据,现在要计算第n个数据对应的滤波结果yₙ。按照移动平均的逻辑,yₙ就是最近N个采样点的平均值,公式如下:

yₙ = (xₙ + xₙ₋₁ + … + xₙ₋ₙ₊₁) / N (公式1)

这里要重点理解“移动”的含义:当采集到第n+1个新数据xₙ₊₁时,采样窗口会向前滑动一位——丢弃最旧的xₙ₋ₙ₊₁,纳入新数据xₙ₊₁,此时新的滤波结果yₙ₊₁为:

yₙ₊₁ = (xₙ₊₁ + xₙ + … + xₙ₋ₙ₊₂) / N (公式2)

对比两个公式能发现关键优化点:无需每次都重新计算N个数据的总和,只需用前一次的总和减去被丢弃的旧数据,再加上新数据即可,推导过程如下:

sumₙ₊₁ = sumₙ - xₙ₋ₙ₊₁ + xₙ₊₁

yₙ₊₁ = sumₙ₊₁ / N

这个优化至关重要——它将每次滤波的时间复杂度从O(N)降到了O(1),大幅降低CPU占用率,这也是移动平均滤波器能适配嵌入式、DSP等资源受限场景的核心原因。

二、分步实现:C语言代码拆解

接下来进入实操环节,我们严格按照“数据结构设计→核心函数编写→代码逐行解析”的流程,用C语言实现移动平均滤波器。全程聚焦窗口缓存数组、累加和等关键变量的设计思路,以及窗口未满边界处理、累加和溢出规避等实战高频问题。

2.1 数据结构设计:用结构体封装核心变量

嵌入式开发中,用结构体封装滤波器核心参数是标准做法,能让代码更模块化、可复用,后续移植或多滤波器并行使用时更高效。结合移动平均的工作原理,我们需要定义以下5个核心变量:

  • 窗口缓存数组:存储最近N个采样数据,用于滑动时快速定位并丢弃旧数据;

  • 窗口大小N:用户可配置的参数,根据场景调整;

  • 累加和sum:存储当前窗口内所有数据的和,避免重复计算;

  • 当前索引index:记录下一个要覆盖的旧数据位置,实现窗口“循环滑动”(避免数组移位);

  • 窗口满标志is_full:标记窗口是否已填满,解决初始化阶段(数据不足N个)的滤波计算问题。

结合上述变量,对应的C语言结构体定义如下(可直接复制到项目中使用):

// 移动平均滤波器结构体typedefstruct{int*window_buf;// 窗口缓存数组intwindow_size;// 窗口大小Nintsum;// 窗口内数据累加和intindex;// 当前索引(下一个要覆盖的位置)uint8_tis_full;// 窗口是否填满标志(0:未填满,1:填满)}MA_Filter_TypeDef;

2.2 核心函数编写:Init初始化函数 + Filter滤波函数

移动平均滤波器的核心功能由两个函数支撑:Init初始化函数(完成参数配置和状态复位)、Filter滤波函数(处理实时采样数据并输出结果),两者配合实现完整的滤波流程。

2.2.1 初始化函数MA_Filter_Init

初始化函数的核心作用:给结构体成员赋值、绑定缓存数组、初始化累加和/索引/满标志等状态变量。特别注意要加入参数合法性检查,避免空指针、无效窗口大小等低级错误。

// 移动平均滤波器初始化// 参数:filter:滤波器结构体指针;window_size:窗口大小(需>1);buf:用户提供的缓存数组(长度≥window_size)// 返回值:0-初始化成功,-1-参数错误intMA_Filter_Init(MA_Filter_TypeDef*filter,intwindow_size,int*buf){// 严格参数检查:避免空指针和无效窗口大小if(filter==NULL||window_size<=1||buf==NULL){return-1;// 初始化失败}filter->window_size=window_size;filter->window_buf=buf;// 绑定外部缓存(嵌入式推荐外部分配,避免动态内存碎片)filter->sum=0;// 累加和初始化为0filter->index=0;// 索引初始化为0(指向第一个待填充位置)filter->is_full=0;// 初始状态:窗口未填满// 缓存数组清零(可选,根据场景调整,避免残留旧数据影响初始滤波结果)for(inti=0;i<window_size;i++){filter->window_buf[i]=0;}return0;// 初始化成功}
2.2.2 滤波函数MA_Filter_Process

滤波函数是核心执行逻辑,负责完成“纳入新数据→更新累加和→计算滤波结果→窗口滑动”的全流程。开发时需重点解决两个实战问题:窗口未满时的边界处理累加和溢出的规避,这也是新手最容易踩坑的地方。

// 移动平均滤波器实时处理函数// 参数:filter:滤波器结构体指针;input:当前ADC采样原始值// 返回值:滤波后的输出值(无效输入返回0)intMA_Filter_Process(MA_Filter_TypeDef*filter,intinput){intoutput=0;// 异常检查:结构体指针为空直接返回if(filter==NULL){return0;}// 1. 累加和更新:窗口满则先减旧数据,再加新数据;未满直接加新数据if(filter->is_full){// 窗口已满,当前索引指向的是即将被覆盖的最旧数据,先从总和中减去filter->sum-=filter->window_buf[filter->index];}filter->sum+=input;// 纳入新数据// 2. 新数据存入缓存,索引更新filter->window_buf[filter->index]=input;filter->index++;// 3. 索引越界处理:循环覆盖旧数据,标记窗口满状态if(filter->index>=filter->window_size){filter->index=0;// 索引归零,实现循环滑动filter->is_full=1;// 首次越界说明窗口已填满,后续按满窗口计算}// 4. 计算输出值:窗口未满时除以实际数据个数,满窗口除以窗口大小if(filter->is_full){output=filter->sum/filter->window_size;}else{// 窗口未满时,index值等于已采集的数据个数(从1开始递增)output=filter->sum/filter->index;}returnoutput;}

2.3 代码逐行解析:关键细节与避坑指南

上面的代码看似简洁,但包含多个嵌入式开发的实战细节,新手很容易在这些地方出错。下面逐一对关键逻辑拆解,讲清“为什么这么写”以及“避免什么坑”。

2.3.1 窗口缓存数组的设计:循环覆盖 vs 移位

新手实现移动平均时,常采用“数组移位”的方式处理窗口滑动:比如要存入新数据时,把x₁~xₙ₋₁依次左移一位,再把新数据存到数组末尾。这种方式的问题很明显:每次移位都要操作N个数据,时间复杂度O(N),当N较大(如1024)或采样率较高(如10kHz)时,会严重占用CPU资源,甚至影响其他任务执行。

我们的实现采用“循环覆盖”思路,核心是通过index索引记录下一个要覆盖的旧数据位置:每次存入新数据时,直接覆盖index指向的旧数据,然后index自增;当index达到窗口大小N时,归零重新开始覆盖。这种方式无需移位,时间复杂度O(1),是嵌入式场景的最优实现方案,能最大限度节省CPU资源。

2.3.2 窗口未满时的边界处理

初始化后,窗口内没有任何数据,随着采样过程推进,数据逐步填充窗口,这个阶段就是“窗口未满”阶段。新手容易忽略这个阶段,直接按满窗口N计算平均值——比如N=5时,第一个采样值除以5,结果仅为实际值的1/5,明显错误,会导致初始化阶段数据严重失真。

我们的解决方案是通过is_full标志区分窗口状态:窗口未满时,除以当前实际采集的数据个数(此时index的值恰好等于已采集个数,因为每次采样index自增1);当index首次达到N并归零时,说明窗口已填满,is_full置1,后续统一除以N计算。这样能保证初始化阶段和稳定运行阶段的输出值都准确,避免边界错误。

2.3.3 避免累加和溢出的技巧

嵌入式系统中,int类型多为16位(范围-32768~32767),即使是32位int,当窗口大小N较大(如1024)且采样值较高(如16位ADC采样,最大值65535)时,累加和sum也可能超出变量范围,导致溢出(出现错误的负数结果),这是实战中必须规避的问题。

结合嵌入式开发场景,分享3个实用的溢出规避技巧(按优先级排序):

  1. 优先使用更大位数变量存储累加和:将sum定义为uint32_t或int32_t类型(32位),即使N=1024、采样值=65535,累加和最大值=1024×65535=67108864,远小于32位整数的最大值(2147483647),完全避免溢出;

  2. 采样值缩放预处理:若采样值范围较大(如16位ADC),可先将采样值除以2或4(根据精度需求调整)再存入缓存,减少累加和的增长速度,降低溢出风险;

  3. 合理选择窗口大小:无需盲目追求大窗口,先通过测试确定满足滤波效果的最小N值,在滤波效果和溢出风险之间找到平衡。

最推荐使用第一种方案,修改后的结构体定义如下(仅修改sum的类型):

// 优化后的移动平均滤波器结构体(32位累加和,避免溢出)typedefstruct{int*window_buf;// 窗口缓存数组intwindow_size;// 窗口大小Nint32_tsum;// 32位累加和,适配大窗口/高采样值场景intindex;// 当前索引(循环覆盖位置)uint8_tis_full;// 窗口满标志(0-未满,1-已满)}MA_Filter_TypeDef;

三、实战测试:DSP ADC采样场景应用

接下来我们将上述实现的移动平均滤波器,应用到DSP(以TI F28335为例)的ADC采样场景中,通过实战验证滤波效果。本次实战场景:采集温度传感器(如LM35)数据,ADC采样率1kHz,原始数据含±5左右的高频噪声,选用窗口大小N=10的移动平均滤波器进行平滑处理。

3.1 实战代码整合

#include"F28335_ADC.h"#include"stdint.h"// 移动平均滤波器结构体(32位累加和,避免溢出)typedefstruct{int*window_buf;intwindow_size;int32_tsum;intindex;uint8_tis_full;}MA_Filter_TypeDef;// 全局变量定义(根据实际场景调整)#defineWINDOW_SIZE10// 窗口大小N=10(经测试适配1kHz采样率的温度采集)intadc_buf[WINDOW_SIZE]={0};// 窗口缓存数组(静态分配,避免动态内存)MA_Filter_TypeDef temp_filter;// 温度采集专用滤波器实例intadc_raw;// ADC原始采样值(12位,范围0~4095)intadc_filtered;// 滤波后的数据// 系统初始化函数(整合ADC和滤波器初始化)voidInit_System(void){ADC_Init();// ADC初始化:1kHz采样率,单通道采集(连接LM35温度传感器)// 滤波器初始化:绑定结构体、窗口大小和缓存数组MA_Filter_Init(&temp_filter,WINDOW_SIZE,adc_buf);}// 主函数(核心业务逻辑)voidmain(void){Init_System();// 初始化ADC和滤波器while(1){adc_raw=ADC_Read();// 读取ADC原始采样值// 滤波处理:输入原始值,输出平滑后的值adc_filtered=MA_Filter_Process(&temp_filter,adc_raw);// 后续逻辑:将滤波后的值转换为温度(如LM35:1LSB≈0.125℃),或用于控制/显示// Temperature = (adc_filtered * 3.3 / 4096) * 100; // 示例:电压转温度}}

3.2 测试结果分析

为直观验证滤波效果,我们通过串口打印ADC原始数据和滤波后的数据,以下是部分实测结果(单位:ADC计数,12位ADC,参考电压3.3V):

采样序号原始数据(带噪声)滤波后数据(N=10)
120502050
220552052
320482051
1020522051
1120602052
1220452051

从实测结果能清晰看到:原始数据波动幅度约±5,经过N=10的移动平均滤波后,波动幅度缩小到±1,高频噪声被有效平滑,滤波效果显著;同时,由于滤波函数时间复杂度仅O(1),在TI F28335上运行时,CPU占用率不足1%,完全不会影响ADC采样、温度转换等其他任务的执行,适配性极佳。

四、优缺点总结与改进方向

4.1 移动平均滤波器的优缺点

优点:
  • 实现简单:代码逻辑清晰,核心函数仅几十行,新手容易理解和移植;

  • 资源占用低:仅需少量内存存储缓存数组和状态变量,计算仅含加减除法,CPU负担小,适配各类嵌入式/DSP芯片;

  • 高频滤波效果好:对ADC采样中常见的高频随机噪声,平滑效果显著,能快速提升数据稳定性。

缺点:
  • 响应速度与滤波效果矛盾:窗口大小N固定,无法同时兼顾——N越大滤波越平滑,但对信号突变的响应越慢(比如温度骤升时,滤波后的数据无法快速跟进);

  • 权重均等不合理:对窗口内所有采样点赋予相同权重,忽略了“新数据更能反映当前状态”的实际需求;

  • 存在相位滞后:滤波后的信号会比原始信号延迟,延迟时间约为(N-1)/2个采样周期,不适合对实时性要求极高的场景(如高速电机电流采样)。

4.2 改进方向:加权移动平均滤波器

针对传统移动平均“权重均等”的核心缺陷,最常用且易实现的改进方案是加权移动平均(Weighted Moving Average, WMA)。其核心思路是:给窗口内的新数据分配更大的权重,旧数据分配更小的权重,既保留滤波平滑效果,又提升对信号突变的响应速度。

加权移动平均的简化公式如下(仅修改权重分配逻辑):

yₙ = (w₀xₙ + w₁xₙ₋₁ + … + wₙ₋₁x₁) / (w₀ + w₁ + … + wₙ₋₁)

其中,w₀ > w₁ > … > wₙ₋₁ > 0,即数据越新,权重越大。举个实际例子:窗口N=3时,可设置权重w₀=3(最新数据)、w₁=2(次新数据)、w₂=1(最旧数据),此时具体公式为:

yₙ = (3xₙ + 2xₙ₋₁ + 1xₙ₋₂) / 6

加权移动平均的代码实现成本极低:在本文传统移动平均代码的基础上,只需新增一个权重数组,修改累加和的计算逻辑(每个数据乘以对应权重),窗口缓存、索引管理等核心逻辑完全复用。感兴趣的同学可以基于本文代码扩展,后续文章也会带来完整的实现教程。

五、总结与互动引导

本文从实战角度出发,分三个核心环节带大家掌握移动平均滤波器:先通过简化推导理解核心原理,再通过“数据结构-核心函数-逐行解析”的步骤掌握C语言实现(重点解决边界处理和溢出问题),最后在DSP ADC采样场景中完成实战验证,同时总结优缺点和改进方向。

移动平均滤波器是嵌入式开发的“基础必备工具”,掌握它能快速解决ADC采样数据波动的痛点。如果这篇文章对你的开发有帮助,欢迎点赞、收藏、关注!后续我会持续更新滤波算法系列内容,包括加权移动平均、指数移动平均(EMA)、卡尔曼滤波等进阶方案,以及在电机控制、传感器融合中的实战应用,带你从“入门”到“精通”嵌入式数据平滑技术。

如果在实践过程中遇到具体问题(比如代码移植报错、滤波效果不佳),或者有其他想了解的滤波相关知识点,欢迎在评论区留言讨论,我们一起交流进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1220636.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flutter for OpenHarmony: 从颜色模型到可访问性:一个 Flutter 高对比度 UI 的完整实践

Flutter for OpenHarmony&#xff1a; 从颜色模型到可访问性&#xff1a;一个 Flutter 高对比度 UI 的完整实践 在移动开发中&#xff0c;色彩不仅是视觉表达的核心&#xff0c;也是用户体验的关键。今天&#xff0c;我们将通过一个极简却极具教学价值的 Flutter 小项目——「…

Flutter for OpenHarmony:用 Flutter 构建一个数字猜谜游戏:从零开始的交互式应用开发

Flutter for OpenHarmony:用 Flutter 构建一个数字猜谜游戏&#xff1a;从零开始的交互式应用开发发布时间&#xff1a;2026年1月26日 技术栈&#xff1a;Flutter 3.22、Dart 3.4、Material Design 3&#xff08;Material You&#xff09; 适用读者&#xff1a;具备基础 Dart/F…

Java毕设项目:基于springboot的个人健康管理系统(源码+文档,讲解、调试运行,定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

【毕业设计】基于springboot的个人健康管理系统(源码+文档+远程调试,全bao定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

解码内部集成电路(IIC)与OLED屏

内部集成电路(IIC/I2C)基本概念英文全称Inter Integrated Circuit,简称IIC或I2C,是半双工同步串行通信接口协议 1982年由飞利浦公司(现恩智浦半导体)设计推出 设计初衷:为微控制器(MCU)与外围芯片提供简洁、可…

深圳朋友圈广告代理:厚拓科技11年实战经验,精准触达12亿微信用户

在移动社交营销时代,微信朋友圈广告已成为品牌触达目标用户的核心阵地。作为腾讯生态中流量最密集、用户粘性最强的广告形式,朋友圈广告凭借其原生体验、精准定向和社交裂变能力,正为企业带来前所未有的营销转化机遇…

2026年干燥设备厂家推荐:常州市元泽干燥设备有限公司,多类型桨叶/流化床/喷雾干燥机供应

在工业干燥领域,设备的技术适配性与运行稳定性直接影响生产效率与产品质量。常州市元泽干燥设备有限公司作为国内专注于工业级干燥设备研发、定制、生产与服务的技术型企业,凭借多年技术积累与行业经验,已成为食品加…

2026年螺旋输送机厂家推荐:山东木子原环境工程,U型/双螺旋/食品级不锈钢输送机全系供应

在工业输送设备领域,螺旋输送机凭借其结构紧凑、密封性好、适应性强等特点,成为物料输送的核心设备之一。山东木子原环境工程有限公司作为行业技术驱动型企业,依托多年研发积累,形成了覆盖U型螺旋输送机、双螺旋输…

2026年气垫搬运设备推荐:陕西海创电子有限公司,全系气垫搬运装置车/工具供应

在工业搬运领域,气垫搬运设备因其高效、安全、灵活的特性,逐渐成为设备生产商、工程公司及国防工业等领域的核心工具。陕西海创电子有限公司作为该领域的深耕者,凭借其技术积累与产品创新,成为行业关注的焦点。公司…

Flutter for OpenHarmony 实战:碰撞检测算法与游戏结束处理

Flutter for OpenHarmony 实战&#xff1a;碰撞检测算法与游戏结束处理 文章目录Flutter for OpenHarmony 实战&#xff1a;碰撞检测算法与游戏结束处理一、前言二、碰撞检测概述2.1 墙壁碰撞2.2 自身碰撞2.3 检测时机三、墙壁碰撞检测3.1 边界判断算法3.2 坐标越界示例3.3 代码…

2026年旧变压器回收厂家推荐:铜陵市泰源物资回收有限公司,废旧/干式/厢式变压器全品类回收

在工业设备更新换代加速的背景下,变压器回收行业迎来发展机遇。据统计,2024年全国废旧变压器年产生量突破120万吨,其中干式变压器占比达35%,厢式变压器占比28%。铜陵市泰源物资回收有限公司凭借全品类回收能力,成…

基于Matlab的双边滤波去噪:图像的美颜魔法

基于Matlab的双边滤波去噪在图像处理领域&#xff0c;噪声就像是不速之客&#xff0c;破坏了图像原本的清晰与美感。双边滤波作为一种强大的去噪技术&#xff0c;如同图像的“美颜滤镜”&#xff0c;能在有效去除噪声的同时&#xff0c;最大程度保留图像的边缘细节。今天咱就来…

数据安全与合规:大数据治理的关键挑战与解决方案

数据安全与合规:大数据治理的关键挑战与解决方案 关键词:数据安全、合规性、大数据治理、隐私保护、数据泄露、监管法规、解决方案 摘要:在数字化时代,数据已成为企业的“数字石油”,但数据泄露、滥用等问题也频发。本文从“数据安全”与“合规”两大核心出发,结合生活案…

质量管理体系是什么,包括哪些内容?

谢邀。 质量管理体系是什么&#xff0c;包括哪些内容&#xff1f;很多老板一听质量管理体系&#xff0c;脑子里浮现的一般都是&#xff1a;“哦&#xff0c;就是那堆 ISO 文件和流程图吧&#xff1f;”实际上&#xff0c;企业真正头疼的并不是有没有文件&#xff0c;而是日常生…

Flutter for OpenHarmony 实战:贪吃蛇蛇的移动逻辑详解

Flutter for OpenHarmony 实战&#xff1a;贪吃蛇蛇的移动逻辑详解 文章目录 Flutter for OpenHarmony 实战&#xff1a;贪吃蛇蛇的移动逻辑详解一、前言二、坐标系统设计2.1 30x20网格坐标系2.2 坐标与像素映射2.3 Point类实现 三、Timer定时器实现自动移动3.1 Timer.periodic…

完整教程:Node.js 编程实战:自定义模块与包发布全流程解析

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

强烈安利!8个AI论文网站测评:本科生毕业论文全攻略

强烈安利&#xff01;8个AI论文网站测评&#xff1a;本科生毕业论文全攻略 2026年AI论文写作工具测评&#xff1a;为何你需要这份指南&#xff1f; 随着人工智能技术的不断发展&#xff0c;越来越多的本科生开始借助AI工具辅助完成毕业论文的撰写。然而&#xff0c;面对市场上…

微信小程序开发一般多少钱?10年程序员给你讲透

我是专注小程序生态解决方案的技术,过去10年服务过近200家企业。每天被问最多的问题就是:“开发一个小程序到底要花多少钱?” 这就像问“装修一套房子多少钱”一样,答案差距很大,今天我就用真实行业经验帮你彻底搞…

Flutter for OpenHarmony 实战:贪吃蛇游戏核心架构设计

Flutter for OpenHarmony 实战&#xff1a;贪吃蛇游戏核心架构设计 文章目录Flutter for OpenHarmony 实战&#xff1a;贪吃蛇游戏核心架构设计一、前言二、贪吃蛇游戏功能拆解2.1 核心游戏机制2.2 技术实现要点三、核心数据结构设计3.1 Direction方向枚举3.2 Point坐标类设计3…

P10137 [USACO24JAN] Walking in Manhattan G

大概想到了。 考虑行走过程,从某个点向右上方行走,将这个点先固定到遇到的第一个交点,那么我们现在的问题就仅针对于这 \(n^2\) 个交点了。 考察一个很关键的事情是,假设一条直线上有 \(k\) 个交点,这 \(k\) 个交…