当前位置: 首页 > news >正文

APM32芯得 EP.31 | APM32F402 HC-SR04超声测距经典操作:波形输出与滤波

 如遇开发技术问题,欢迎前往开发者社区,极海技术团队将在线为您解答~
极海官方开发者社区​https://community.geehy.cn/

《APM32芯得》系列内容为用户使用APM32系列产品的经验总结,均转载自21ic论坛极海半导体专区,全文未作任何修改,未经原文作者授权禁止转载。

目录

1 背景:为什么要看“波形”而不是“数字”?

2 波形输出:你只需要一个 SerialPlot

2.1 为什么选 SerialPlot?

2.2 “三步走”操作流程

2.3 Main 函数示例

2.4 设置SerialPlot

3 看波形时发现了离奇“跳变”:那它们从何而来?

4 滤波的三“怪侠”:均值、指数平滑、卡尔曼

4.1 均值滤波:最朴实的“一锅大杂烩”

4.2 指数平滑:给新数据一点“特殊照顾”

4.3 卡尔曼滤波:让你的测距结果变得“华丽丽”

5 实战对比:四条波形,谁更平稳?

6 结语:滤波要巧,内核更要“给力”


1 背景:为什么要看“波形”而不是“数字”?

很多时候,搞单片机的小伙伴们会说:“我不就是想看个距离嘛?干嘛整那么复杂?”的确,HC-SR04 测距可以很轻松地用串口打印,随手用 printf("%f", dist) 就能搞定。然而,当我们认真追究测量稳定性时,光看数字列表经常会“眼花缭乱”,而且一旦出现个别极端测量值,我们也很难察觉到背后的规律。

因此,我们往往倾向于“把数据变成波形”。所谓“一图胜千言”,对连续采样的结果进行可视化,常常能“一眼”看出抖动程度、周期性趋势或离群点。

APM32F402 这颗微控制器具备丰富的外设资源和较高的主频,加上 HC-SR04 超声波模块,本身就能很好地完成中近距离测量任务。那接下来,就让我们先弄清楚如何把这些测距数据“画”出来吧。

2 波形输出:你只需要一个 SerialPlot

2.1 为什么选 SerialPlot?

• 免费又开源,安装便捷(https://bitbucket.org/hyOzd/serialplot/src/default/)。 • 能够实时读取串口并自带波形绘制功能,省去了对接 Matlab 或其他上位机软件的麻烦。 • 简单设置就能同时采集多通道数据,比如同时显示“原始测距”、“滤波后测距”等。

2.2 “三步走”操作流程

(1) 初始化串口: 在 APM32F402 上使用 USARTx (如 USART1),配置波特率、数据位、停止位等参数与 PC 端的 USB-UART 模块匹配。波特率常选用 115200。 (2) 发送数据: 主循环里,每隔 N 毫秒 (比如 20ms) 采集一次数据,用 printf() 。注意数据格式要简单统一,可用逗号或空格分隔多通道数值,然后以 \n 结尾; (3) SerialPlot 收数: 打开 SerialPlot,选定对应的串口号和波特率后,就能愉快地看到数据曲线随时间跳动了。

2.3 Main 函数示例

下方给出一个“多通道”输出的示例,只要做过类似串口输出的同学都能了解这块逻辑。对比之前,你会发现多了对各种滤波结果的采集输出。等下一节我们再把滤波代码补充进来,这里先看大致用法。

int main(void)
{USART_Config_T usartConfigStruct;float rawDist = 0.0f;  // 原始测距值// (1) NVIC vector table and basic initializationNVIC_ConfigVectorTable(NVIC_VECT_TAB_FLASH, 0x0000);BOARD_LED_Config(LED3);BOARD_Delay_Config();/* (2) Configure USART */USART_ConfigStructInit(&usartConfigStruct);usartConfigStruct.baudRate      = 115200;usartConfigStruct.mode          = USART_MODE_TX_RX;usartConfigStruct.parity        = USART_PARITY_NONE;usartConfigStruct.stopBits      = USART_STOP_BIT_1;usartConfigStruct.wordLength    = USART_WORD_LEN_8B;usartConfigStruct.hardwareFlow  = USART_HARDWARE_FLOW_NONE;BOARD_COM_Config(COM1, &usartConfigStruct);/* (3) Initialize HC-SR04 measurement module */TMR_HCSR04_Init();printf("APM32F402 & HC-SR04 Demo: Only rawDist output\r\n");while (1){// (4) Get raw distance from HC-SR04rawDist = sonar_mm_tmr();// (5) Print only rawDistprintf("%.2f\r\n", rawDist);// (6) Toggle LED3 to indicate activity and delayBOARD_LED_Toggle(LED3);BOARD_Delay_Ms(20);}
}

2.4 设置SerialPlot

在打开 SerialPlot 软件后,可参照以下步骤完成基础配置,确保软件能正确识别我们通过串口输出的数据,并绘制成波形:

  1. 选择正确的 COM 端口:在 SerialPlot 主界面,先下拉选择与板子连接的那个串口设备(比如 COM3 或 COM4 等)。
  2. 配置 Port 设置:将波特率(常见 115200)、数据位(8)、停止位(1)以及奇偶校验(None)等与我们在代码里配置的 USART 参数保持一致。
  3. 切换到“数据格式 (Data Format)”选项卡:

步骤 (4)~(6) 的配置需要和我们的代码输出相匹配。

  1. 数据格式设置:选择 ASCII(因为我们的代码通过 printf("%f...\n") 输出文本数据)。
  2. 通道数量 (Channels) 设置:目前配置为 1,其余的通道我们后续再加(因为我们现在只输出单一通道的数据)。
  3. 数据分隔符 (Delimiter) 选择:设为 “comma”,与我们代码里用逗号输出时保持一致(如果只是单通道输出,也可使用默认换行分隔,但为兼容后面多通道最好统一用逗号)。
  4. 切换到 “Plot” 选项卡:在这里可以对绘制参数进行个性化调整,让图像更易于读取。
  5. 通道名称配置:给当前单通道起个简明易懂的名字,例如 “rawDist”。调整线条颜色或其他可视化选项,不同颜色能让你在后续多通道时更容易区分。
  6. 最后,点击 “Open” 打开COM

PixPin_2025-06-28_10-32-32.png

一旦与板子连通,并且板子上已烧录代码,SerialPlot 界面就会开始刷新波形啦!

PixPin_2025-06-28_13-52-04.gif

3 看波形时发现了离奇“跳变”:那它们从何而来?

在你喜滋滋打开 SerialPlot 观看血(bu)脉(tong)喷(qiang)张(li)的距离曲线时,可能会发现原以为会像中学数学课本那样“顺滑”的数据却经常出现±几毫米的抖动,有时甚至莫名地瞬间高或瞬间低。

• 这是因为超声波在空气中的传播容易受到环境干扰,特别是气流变动、多重反射干扰 • 或者目标极近或极远,HC-SR04 本身难以准确捕捉回波 • 以及软件层面定时器捕获可能会有极端误差,总之挑战多多

4 滤波的三“怪侠”:均值、指数平滑、卡尔曼

当你的系统需要更精确、更稳定的测量结果,就必须引入“滤波”来对抗这些噪声和跳变。紧随其后我们就来谈谈几种经典滤波方式——从最简单的滑动平均,到稍微聪明一点的指数平滑,再到优雅的卡尔曼滤波——它们各自有什么优缺点?

4.1 均值滤波:最朴实的“一锅大杂烩”

• 算法原理,也就是滑动平均(Moving Average):将最近 N 次测量值加起来除以 N。

• 优点:实现简单,短时间内的随机噪声会被有效抵消。 • 缺点:突变信号时会出现延迟。窗口越大,延迟越明显;窗口过小又无法有效平滑。

简易示例代码(环形缓冲方式):

#define MA_WINDOW_SIZE 5typedef struct
{float buffer[MA_WINDOW_SIZE];uint32_t index;float sum;uint32_t count;
} MovingAverageFilter_t;void MAFilter_Init(MovingAverageFilter_t* filter)
{filter->sum = 0.0f;filter->index = 0;filter->count = 0;// Initialize the buffer to 0for(uint32_t i = 0; i < MA_WINDOW_SIZE; i++){filter->buffer[i] = 0.0f;}
}float MAFilter_Update(MovingAverageFilter_t* filter, float newSample)
{// Subtract the oldest sample from sum if buffer is fullif(filter->count >= MA_WINDOW_SIZE){filter->sum -= filter->buffer[filter->index];}else{// If the buffer isn't full, just increase countfilter->count++;}// Add new sample to sumfilter->sum += newSample;// Put new sample into bufferfilter->buffer[filter->index] = newSample;// Update index (circular)filter->index++;if(filter->index >= MA_WINDOW_SIZE){filter->index = 0;}// Calculate the averagefloat average = filter->sum / (float)(filter->count);return average;
}

4.2 指数平滑:给新数据一点“特殊照顾”

当我们想要占用更少的内存、并能灵活调节“新数据”和“旧数据”权重时,可以上“指数平滑 (Exponential Smoothing)”这把刀。它每次更新公式常写作:

filtered(k) = α × newSample + (1 - α) × filtered(k-1)

• α ∈ (0,1) 表示平滑系数。α 大——反应积极,α 小——稳重平滑。 • 同样也有滞后,但只需存上一次滤波值就行,代码很精简。

简易示例:

typedef struct
{float alpha;  float prevFiltered; uint8_t initFlag;   
} ExpSmoothFilter_t;void ExpSmoothFilter_Init(ExpSmoothFilter_t* filter, float alpha, float initialVal)
{filter->alpha = alpha;  filter->prevFiltered = initialVal;filter->initFlag = 1;
}float ExpSmoothFilter_Update(ExpSmoothFilter_t* filter, float newSample)
{if(!filter->initFlag){// If not initialized properly, we do it on the flyfilter->prevFiltered = newSample;filter->initFlag = 1;return newSample;}// filtered(k) = alpha * newSample + (1-alpha)*filtered(k-1)float currentFiltered = filter->alpha * newSample + (1.0f - filter->alpha) * filter->prevFiltered;// Store the result for next iterationfilter->prevFiltered = currentFiltered;// Return filtered outputreturn currentFiltered;
}

4.3 卡尔曼滤波:让你的测距结果变得“华丽丽”

有些场景,只靠均值或指数平滑,还无法很好地兼顾平滑度与实时性,而卡尔曼滤波 (Kalman Filter) 正在此时闪亮登场——它在四轴飞行器、机器人定位、VR/AR 追踪等高精度领域大放异彩。

它是利用最优状态估计理论,在已知系统模型、噪声统计特性的前提下,能同时降低随机噪声干扰,又能对真实值变化保持快速跟踪。

• 优点:自适应性更强,对离群值有一定抑制效果。

• 缺点:需要适当的噪声模型(比如 Q, R),是个“调参玄学”,一不留神数据就抖成筛子或者变得超级迟缓。

示例代码(简易一维场景):

void KalmanFilter_Init(KalmanFilter_t *kf, float initVal)
{/** x = initVal* p = 10000.0f        (初始较大的不确定度)* Q = 5.0f            (过程噪声: 可适度调大/调小)* R = 50.0f           (测量噪声: 数值越大说明观测值不可靠)*/kf->x = initVal;kf->p = 10000.0f;kf->Q = 5.0f;kf->R = 50.0f;
}float KalmanFilter_Update(KalmanFilter_t *kf, float measurement)
{/* 1) 预测阶段:x' = x, p' = p + Q */float x_prime = kf->x;float p_prime = kf->p + kf->Q;/* 2) 更新阶段:*    K = p' / (p' + R)*    x = x' + K*(z - x')*    p = (1 - K)*p'*/float K = p_prime / (p_prime + kf->R);kf->x   = x_prime + K * (measurement - x_prime);kf->p   = (1.0f - K) * p_prime;return kf->x;
}float getFilteredDistance(float measurement)
{/* 首次调用时初始化卡尔曼滤波器 */if (!s_kfInitFlag){KalmanFilter_Init(&s_filter, 0.0f);s_kfInitFlag = 1;}/* 使用传入的 measurement 完成卡尔曼滤波更新 */float filteredDist = KalmanFilter_Update(&s_filter, measurement);/* 返回滤波后的距离 */return filteredDist;
}

5 实战对比:四条波形,谁更平稳?

在前文的 main 函数循环里,只要我们分别调用均值滤波、指数平滑滤波与卡尔曼滤波,就能一口气输出四条通道数据到 SerialPlot 上:

  1. 原始距离 rawDist
  2. 卡尔曼滤波 kalmanDist
  3. 均值滤波 maDist
  4. 指数平滑 esDist

然后你会看到:

• rawDist 曲线上下跳动最欢快,偶尔还跑出离群值;

• maDist 明显平滑了一些,但在突然改变距离时会慢一拍;

• esDist 也能提供平滑效果,调 α 大小还可以自行微调它对新数据的敏感程;

• kalmanDist 在参数合适时,多数情况下兼具抑制噪声和快速响应的效果,但需要在 Q/R 之间把关系调和好,否则效果可能“翻车”。

波形输出:

PixPin_2025-06-28_13-56-00.gif

局部对比:

PixPin_2025-06-28_13-56-41.png

6 结语:滤波要巧,内核更要“给力”

APM32F402 采用了 Arm® Cortex®-M4F 内核,最高主频可达 120MHz,并且内置 FPU(Floating Point Unit)和 DSP 指令集,这让它在需要频繁浮点运算或数字信号处理(如滤波、傅里叶变换、控制算法)时比 Cortex®-M3、M0+ 更胜一筹。换句话说,在这颗“内芯”里跑各种滤波算法,可谓是事半功倍,既能提高实时性,也能减少软件浮点的额外开销。

• 如果只是小型电子制作,对精度和实时响应要求不算苛刻,均值滤波或指数平滑滤波就能应付绝大多数噪声场景;

• 如果项目场合复杂,既要实时跟踪又怕离群值捣乱,并且对系统运动或噪声分布有一定了解,那卡尔曼滤波当仁不让;

• 最终选择何种滤波,还是要结合具体需求、资源限制以及个人调参习惯。在嵌入式开发的世界里,“合用”往往胜过“一味追求顶配”。

以上便是本次分享的全部内容(这里是代码:​附件:APM32F402_403_SDK_V1.0.1_HC_SR04_Filter.zip)。希望能给你一点启发,让超声数据“乖乖听话”,也让你在面对跳变值时不再焦头烂额。你觉得那个滤波方式最好呢?欢迎在评论区留下你的观点。

http://www.dtcms.com/a/357168.html

相关文章:

  • 微算法科技(NASDAQ:MLGO)一种基于FPGA的Grover搜索优化算法技术引领量子计算
  • PCIe 6.0配置与地址空间架构:深入解析设备初始化的核心机制
  • C#实现OPC客户端
  • 《Password Guessing Using Random Forest》论文解读
  • system论文阅读--HPCA25
  • Excel Word Pdf 格式转换
  • ubuntu 安装 vllm
  • 电平移位器的原理
  • 群核科技--SpatialGen
  • pytest使用allure测试报告
  • Pytest 插件方法:pytest_runtest_makereport
  • 多方调研赋能AI+智慧消防 豪越科技人工智能创新获认可
  • 【网络安全领域】边界安全是什么?目前的发展及应用场景
  • java基本类型关键字
  • EasyExcel处理大数据量导出
  • 新手法务合同审查,有什么建议?
  • 单点登录(SSO)前端(Vue2.X)改造
  • 关于锁相放大器(LIA)的系统论文研究(重点于FPGA部分)
  • 设计模式:装饰模式(Decorator Pattern)
  • iOS开发之苹果系统包含的所有字体库
  • 最小生成树——Kruskal
  • 【机器学习入门】3.1 关联分析——从“购物篮”到推荐系统的核心逻辑
  • 响应式编程框架Reactor【2】
  • Windows C盘完全占满会如何?
  • 2024-06-13-debian12安装Mariadb-Galera-Cluster+Nginx+Keepalived高可用多主集群
  • 毕马威 —— 公众对人工智能的信任、态度及使用情况调查
  • C++基础(②VS2022创建项目)
  • docker compose设置命令别名的方法
  • Windows WizTree-v4.27.0.0-x64[磁盘空间分析软件]
  • C++中类,this指针,构造函数,析构函数。拷贝构造函数,初步理解运算符重载,初步理解赋值运算符重载