STM32风速风向测量仪:输入捕获测频测周与ADC DMA滤波标定
2026/9/17 13:13:58 网站建设 项目流程

简介:这是一份面向电子信息、自动化及测控类专业学生的毕业设计参考资料,围绕基于STM32的风速风向测量仪展开,适合正在准备嵌入式课程设计或毕业设计、需要完整方案论证与软硬件实现思路的读者。资源为1个doc文档,压缩包约681KB,正文含摘要、目录、章节论述与附录等完整论文结构,可直接作为撰写与排版参考。内容从方案比较入手,先分析传统数字电路方案与单片机方案在实施难度、成本与开发周期上的差异,进而确定以STM32为核心控制器;硬件部分覆盖电源设计、STM32选型与引脚特性、显示模块以及风速仪、风向仪与光电编码器电路;软件部分给出风向检测、风速检测与主程序流程,最后以测试方法与结果分析收尾。读者可据此梳理系统框架论证逻辑、传感器数据采集与滤波处理思路,并参考其章节组织方式完善自己的论文框架。目前已有181人学习下载。

1. 从选题到落地:风速风向测量仪到底要解决什么

风杯在转,风向标在摆,这两路信号一路是频率一路是电压,本质上都不是"风速"和"风向",只是两个需要被标定过的电信号。基于 STM32 的风速风向测量仪要干的事,就是用一颗 MCU 同时把这两路信号变成能上报、能显示、能存进论文数据表的物理量,并且在 0.5 m/s 的微风和 30 m/s 的强风之间都保持可接受的精度。

这个题目之所以常年出现在毕业设计清单里,是因为它把 STM32 的定时器输入捕获、ADC 多通道 DMA、串口通信、滤波算法全串了一遍,硬件不复杂但每个环节都有真实的坑:低频段测频分辨率不够、风向标电位器存在死区、风杯启动有阈值、温漂会让整条标定曲线平移。搞清这些,才算是把测量仪做出来了,而不是把传感器接上跑通一个 demo。

2. 传感器与 STM32 资源分配:先定方案再画板

方案定型这一步决定了后面 80% 的工作量。风速和风向各选一种传感原理,再把它们对 MCU 的资源需求列清楚,板子画出来基本就不会返工。

2.1 风速测量的三条路线与取舍

风杯式(含三杯、四杯)是绝大多数教学项目和气象站的选择:风杯带动同轴码盘或磁钢,输出频率与风速近似线性。优点是便宜、皮实、接口简单,缺点是启动风速通常在 0.3~0.8 m/s,低于这个值它根本不转,而且转动惯量让它对突变风的响应有滞后。

超声波时差法是另一条路:一对换能器对射,顺风与逆风方向的传播时间差正比于风速分量。没有机械磨损、无启动阈值、能测到 0 m/s,但换能器驱动需要高压脉冲、回波检测对阈值和温度补偿敏感,成本高出几倍。热线式响应最快,适合湍流测量,但探头娇贵且需要恒温控制电路。

方案输出形式量程启动阈值接口复杂度适合场景
风杯 + 霍尔/干簧管脉冲频率0.5~45 m/s0.3~0.8 m/s低(1 个定时器通道)教学、气象站
风杯 + 光电码盘脉冲频率0.3~60 m/s0.3 m/s低(1 个定时器通道)高精度计数
超声波时差时间差/串口0~60 m/s高(高压驱动 + 高速采样)无机械磨损场合
热线恒温桥路电压0~30 m/s中高(模拟闭环)湍流、快速响应

我的建议是:如果目标是做出一台可复现、可标定、论文数据能自圆其说的仪器,选风杯脉冲方案,把精力花在测频算法和标定上,这两处的技术含量足够撑起整篇论文。超声波方案留给有模拟前端调试经验的人。

2.2 风向测量的两类器件:电位器与磁编码器

风向标的位置检测,主流有两类。第一类是环形电位器(常见型号是 0~360° 输出、5 kΩ 或 10 kΩ),风向标轴带动滑臂,输出电压与角度近似线性,STM32 用一路 ADC 就能读。它的死区在 0° 附近,滑臂跨过缺口时电压会跳变,这是必须处理的。

第二类是磁编码器,比如 AS5600 这类 12 位 I²C 器件,把磁铁装在转轴上,直接读出 0~4095 对应 0~360°。没有接触磨损、没有死区,代价是多一路 I²C 和一颗磁铁的对中安装。

器件接口分辨率死区长期可靠性备注
环形电位器ADC 单通道取决于 ADC,理论 12 位约 3~8°有磨损便宜,需软件补死区
AS5600 类磁编码I²C12 位(0.088°)无接触需磁铁对中,成本略高

如果只是做一台样机,电位器方案够用;如果论文里要谈"长期免维护",磁编码器是更好的论据。

2.3 STM32 引脚与定时器资源分配表

以最常见的 STM32F103C8T6 为例,下面这张分配表可以直接照着画原理图。

功能外设引脚说明
风速脉冲输入TIM2_CH1 输入捕获PA0上升沿捕获,接传感器脉冲输出
1 Hz 门闸定时TIM6 基本定时器内部用于 1 s 计数窗口
风向标电压ADC1_IN0PA1电位器滑动端,前端加分压与 RC
电池/电源监测ADC1_IN1PA2可选,用于低电压告警
OLED 显示I²C1PB6/PB70.96 寸屏,现场读数
上位机通信USART1PA9/PA10115200 8N1,上报 CSV 行
调试口SWDPA13/PA14保留,别复用

分配时有一个原则:风速脉冲一定要落在带输入捕获的定时器通道上,不要图省事用外部中断 EXTI 去数脉冲。EXTI 在高频(>1 kHz)下会被中断开销拖垮,而且没有硬件滤波和预分频,抖动会直接变成计数误差。

2.4 供电与前端调理电路

风杯传感器多为 5~24 V 供电的开集输出,STM32 是 3.3 V 系统,中间必须做电平处理。常见做法是 10 kΩ 上拉到 3.3 V,加一级 RC 低通(1 kΩ + 100 nF,截止约 1.6 kHz)滤掉长线引入的尖峰,再用施密特触发门整形。风向标电位器直接接 3.3 V 激励,滑动端经过 10 kΩ + 100 nF 进 ADC,这一级 RC 很关键——没有它,ADC 采样保持电容充放电会在读数里留下几十个 LSB 的噪声。

注意:风杯和风向标如果共地长距离走线,地线上的压降会把脉冲边沿抬高,出现"多计一个脉冲"的现象。传感器侧和 MCU 侧分别就近接地,脉冲线用地线绞合屏蔽。

3. 测频与采样:STM32 端的两路信号怎么变成数

硬件接好之后,软件要做两件事:把脉冲数成频率,把电压读成角度。这两件事各自有一套"什么时候该换算法"的判断。

3.1 输入捕获测频法:门时间 1 s 的配置与代码

风杯输出的是方波,中心频率大致在 0~200 Hz 区间(对应 0~30 m/s)。最直观的做法是"定门时间计脉冲数",也叫测频法:开一个 1 s 的定时窗口,数这个窗口里来了几个上升沿,计数就是频率。

CubeMX 里把 TIM2 配成输入捕获,预分频器给 72-1(72 MHz → 1 MHz 计数时钟),捕获边沿选上升沿,开启捕获中断;TIM6 配成 1 Hz 更新中断当作门闸。

/* TIM2_CH1 输入捕获:每个上升沿进一次中断,只做计数 */ volatile uint32_t g_pulse_cnt = 0; /* 门时间内的脉冲个数 */ volatile uint32_t g_freq_hz = 0; /* 换算后的频率(Hz) */ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { /* 读一次 CCR 清掉捕获标志,值本身在测频法里用不到 */ (void)HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); g_pulse_cnt++; /* 1 s 内累加,溢出前最多 200 次 */ } } /* TIM6 更新中断:1 Hz 门闸,关窗、结算、清零 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM6) { g_freq_hz = g_pulse_cnt; /* 门时间正好 1 s,计数即 Hz */ g_pulse_cnt = 0; /* 立刻清零,避免跨窗口串数 */ } }

逻辑说明:捕获中断里刻意不做浮点运算、不打印、不换算,只做一次自增,保证高频下中断执行时间可控。真正的换算放到 1 Hz 的低优先级回调里做,两者解耦。

参数说明:门时间取 1 s 是精度与刷新率的折中——窗口越长频率分辨率越高(±1 Hz 相对误差越小),但输出更新越慢。风杯在 5 m/s 时大约输出 45 Hz 左右,1 s 窗口的相对误差约 2%,对气象级应用可以接受;如果要求更高,把门时间改成 5 s,代价是响应变慢。

3.2 低频段的测周法:什么时候必须换算法

测频法在低频段会露馅。风速 0.5 m/s 时风杯可能只输出 2~5 Hz,1 s 窗口数下 2 个脉冲,读数是 2 Hz,下一秒可能是 3 Hz,量化噪声达到 ±50%。这种情况下要换成测周法:测出相邻两个上升沿之间的时间间隔 T,风速正比于 1/T。

/* 测周法:记录相邻两次捕获的时间戳,求周期 */ volatile uint32_t g_ts_prev = 0, g_ts_now = 0, g_period_us = 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { uint32_t ccr = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); /* 计数器是 16 位、1 MHz,溢出需在 TIM2 更新中断里累加高位 */ g_ts_now = (g_overflow_hi << 16) | ccr; g_period_us = g_ts_now - g_ts_prev; /* 单位:微秒 */ g_ts_prev = g_ts_now; } } /* 频率 = 1e6 / g_period_us,>10 Hz 时切回测频法 */

逻辑说明:测周法把量化误差从"±1 个脉冲"转移到"±1 个计数时钟",1 MHz 时基下测 5 Hz 信号的周期为 200000 μs,误差 1/200000,几乎可以忽略。代价是需要处理定时器溢出,且高频时周期太短反而不划算。

工程上的常见做法是分段:频率低于 10 Hz 走测周法,高于 10 Hz 走测频法,两者在交界处用同样的标定曲线,避免切换时读数跳变。

3.3 ADC 多通道 DMA 采风向标与中值滤波

风向标一路用 ADC1 的规则组扫描模式,配合 DMA 循环搬运,CPU 完全不参与采样。CubeMX 里开 ADC1 的 IN0 和 IN1,扫描模式使能,连续转换使能,DMA 请求设为循环。

#define ADC_CH_NUM 2 uint16_t adc_buf[ADC_CH_NUM]; /* [0]=风向标, [1]=电源监测 */ /* 启动一次,DMA 会一直循环刷新 adc_buf */ HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, ADC_CH_NUM); /* 中值滤波:三点取中,专治电位器滑动端的孤立跳变 */ static uint16_t median3(uint16_t a, uint16_t b, uint16_t c) { if (a > b) { uint16_t t = a; a = b; b = t; } if (b > c) { uint16_t t = b; b = c; c = t; } if (a > b) { uint16_t t = a; a = b; b = t; } return b; /* 排序后中间那个 */ } /* 每 100 ms 取一次数,三次连续采样做中值,再送一级滑动平均 */ float read_wind_dir_raw(void) { uint16_t s1 = adc_buf[0]; HAL_Delay(1); uint16_t s2 = adc_buf[0]; HAL_Delay(1); uint16_t s3 = adc_buf[0]; uint16_t med = median3(s1, s2, s3); return (float)med; /* 原始码值,换算在下一节 */ }

逻辑说明:中值滤波放在滑动平均之前。原因是电位器滑臂在振动环境下会产生"单点跳变"(比如从 1200 突然跳到 3900 再跳回来),均值滤波会把这种野值摊到好几个输出里,中值滤波则能整点剔除。顺序颠倒效果差很多。

参数说明:采样间隔 1 ms 是为了让三次采样落在不同的 ADC 转换周期上,避免采到同一个值。滑动平均窗口 N 建议取 8~16,对应时间常数约 0.8~1.6 s,风速风向的物理变化远慢于这个尺度,不会引入可感知的滞后。

3.4 频率转风速、电压转方位角

两路原始值到手后,换算成物理量。风速用线性式,风向用分段查表。

/* 风杯:v = b + k * f ,系数来自标定(示例值,必须自测) */ #define WIND_K 0.0877f #define WIND_B 0.1000f float freq_to_speed(float f_hz) { if (f_hz < 1.0f) return 0.0f; /* 低于启动阈值直接判 0 */ return WIND_B + WIND_K * f_hz; } /* 电位器:0..4095 码值映射到 0..360 度,含死区处理 */ float raw_to_dir_deg(uint16_t raw, float *last_valid) { float deg = (float)raw * 360.0f / 4095.0f; /* 电位器在 0 度附近有缺口,读数会从 358 跳到 2,落在死区就保持上次有效值 */ if (deg > 352.0f || deg < 8.0f) { if (last_valid != NULL) deg = *last_valid; } else { if (last_valid != NULL) *last_valid = deg; } return deg; }

逻辑说明:风速那行if (f_hz < 1.0f) return 0.0f是必要的。风杯静止时偶尔会因风抖动输出一两个脉冲,不设阈值会出现"无风报 0.2 m/s"的假数据。死区保持策略比线性插值更简单也更稳,因为死区里的电压本来就不代表角度。

参数说明:WIND_KWIND_B只能是示例,必须用自己的传感器实测。死区宽度 8° 是常见值,具体看器件手册,改这两个常量就能适配。

方位角到 16 方位的换算,用中心角加偏移量整除:

方位中心角判定区间方位中心角判定区间
N348.75~11.25S180°168.75~191.25
NNE22.5°11.25~33.75SSW202.5°191.25~213.75
NE45°33.75~56.25SW225°213.75~236.25
ENE67.5°56.25~78.75WSW247.5°236.25~258.75
E90°78.75~101.25W270°258.75~281.25
ESE112.5°101.25~123.75WNW292.5°281.25~303.75
SE135°123.75~146.25NW315°303.75~326.25
SSE157.5°146.25~168.75NNW337.5°326.25~348.75

index = (int)((deg + 11.25f) / 22.5f) % 16;一行就能落表,比十六个 if 干净,也方便论文里写成公式。

4. 标定、滤波与误差分析:论文数据章节怎么站得住

做完上面两步,示波器上能看到合理的读数,但读数不等于测量值。论文里真正决定分数的是标定过程和数据可信度,这一段必须有据可查。

4.1 风洞/台架标定与最小二乘拟合

没有风洞的情况下,可以用标准风速仪做比对:把自制的测量仪和一把经过校准的手持风速仪放在同一风源下(工业风扇、可变风道都行),改变风源档位,等读数稳定后同时记录两边的值。每组至少记 30 秒的均值,采 7~10 个档位覆盖量程。

拿到"频率—风速"样本对之后,做一次最小二乘线性拟合,把斜率截距写进固件、也写进论文。

import numpy as np # 风洞/比对实验采到的样本:f 为自测频率,v 为标准风速仪读数 f = np.array([0.0, 5.2, 10.4, 20.1, 30.3, 40.2, 50.5]) v = np.array([0.13, 0.55, 0.98, 1.86, 2.74, 3.62, 4.48]) A = np.vstack([f, np.ones_like(f)]).T # 模型 v = k*f + b k, b = np.linalg.lstsq(A, v, rcond=None)[0] resid = v - (k * f + b) # 残差 ss_res = float(np.sum(resid ** 2)) ss_tot = float(np.sum((v - v.mean()) ** 2)) print(f"k = {k:.5f} b = {b:.4f}") print(f"R^2 = {1 - ss_res / ss_tot:.4f}") print(f"最大残差 = {np.abs(resid).max():.4f} m/s")

逻辑说明:用lstsq而不是手算公式,是为了后面换二阶模型时只改A的构造。如果残差随风速呈明显弯曲(两端偏大、中间偏小),说明线性模型不够,改成v = a*f^2 + b*f + c,把A换成[f**2, f, ones]即可,np.linalg.lstsq本身不用动。

参数说明:R^2是论文里必须给的拟合优度,低于 0.995 就要考虑二阶模型或分段标定。最大残差比平均残差更能说明问题——论文评审更关心最坏情况。

同样的流程搬到风向:给定标准角度(用角度盘或电子罗盘),记录每个角度下的 ADC 码值,得到"码值—角度"映射表。如果电位器线性度差,直接把这个表烧进固件做查表插值,比强行用线性公式准得多。

4.2 滤波参数怎么定

滤波参数不是随手填的,每个都要能说出"为什么是这个值"。

环节参数取值依据调大/调小的后果
风速门时间1 s5 m/s 时相对误差约 2%调大更准但更慢,调小抖动明显
风速滑动平均N = 5,1 Hz时间常数 5 s,远小于天气尺度变化调大更平滑,阵风被抹平
风向中值窗口3 点,100 ms消除滑臂单点跳变调大对连续抖动无效
风向滑动平均N = 16,100 ms时间常数 1.6 s调大滞后增加,调小噪声留存
死区保持宽度器件手册死区范围调大角度分辨率下降

一个常被忽略的点:滤波带来的滞后在论文里要量化写出来,比如"滑动平均 N = 16 引入的阶跃响应 90% 上升时间约 3.6 s"。评审很可能会问"你的实时性如何",答不上来就尴尬了。

4.3 误差来源清单与余弦误差补偿

论文的误差分析章节,把下面几项逐条列出来,配上实测数据,比空谈"存在一定误差"强得多。

第一是启动阈值误差。风杯低于启动风速不转,这段区间只能报 0,属于系统性的死区误差,写清具体数值(比如 0.4 m/s)。

第二是风向的余弦误差。风向标在不同风速下的指向会偏离真实风向,偏离量随风速变化、随风向角呈余弦规律,这是风向测量的主要误差源。改进办法是做一次"风洞角度扫描",把每个角度的偏差做成补偿表,固件里查表修正。

第三是温度漂移。电位器的阻值、ADC 的参考电压都随温度变化,整条曲线会平移。条件允许时用外部基准源(如 REF3033)替代 MCU 内部参考,或者把温度传感器读数和风向码值一起记录,做一阶温度补偿。

第四是安装误差。风向标的 0° 标记必须和仪器外壳的正北标记对齐,偏了几度,全量程就偏几度,这是最容易被忽略也最容易验证的一项。

5. 从能跑的 demo 到经得起追问的成品

读数稳定之后,剩下的工作往往决定这台仪器是"能演示"还是"能用"。

先说上报与掉电。串口上报别用阻塞式HAL_UART_Transmit,115200 下发 3 行 CSV 会占掉几毫秒,期间捕获中断还在来,容易丢脉冲。改成 DMA 加空闲中断的双缓冲发送,或者干脆在主循环里攒够一行再发。同时开独立看门狗,喂狗放在主循环,一旦 ADC 的 DMA 卡死或者 I²C 显示挂起,两秒内自动复位重启,现场无人值守时这一步能救命。

再说硬件上容易翻车的地方。风速脉冲线如果和 OLED 的 I²C 走同一个线束,边沿耦合会让脉冲数偶尔多一两个;把两路分开走、脉冲线加屏蔽并在 MCU 侧就近加 100 nF 到地,实测能压下去。ADC 的地线要单独回到 MCU 的 AGND 引脚,不要和传感器的功率地共用一段走线,否则读数会随风杯转动同步跳动,这种"跟着转的噪声"一眼就能看出来。

关于论文本身,有几个具体做法值得照搬。一是把每一次改参数前后的实测数据都存下来,答辩时被问"为什么用 N = 16 不用 N = 32",直接翻出两组曲线对比,比口头解释有说服力。二是系统框图和软件流程图里,把测频法和测周法的切换点画清楚,这是整个设计里最有技术含量的判断,不要埋在代码注释里。三是误差分析章节的每一项都配一张实测散点图,哪怕是手绘拍照贴上去的。

最后给一个验证整机是否可信的小技巧:把仪器静置在无风环境里连续跑 2 小时,记录风速和风向的输出序列,风速应该稳定在 0 且不出现偶发的 0.1~0.3 m/s 假读数,风向的波动范围应该在一两个方位以内。如果静置时风向就在满量程乱跳,问题几乎一定出在电位器采样前端——先查 ADC 参考电压稳不稳,再查 RC 的时间常数是否太小,最后才怀疑滤波参数。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询