本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目基于高性能ARM Cortex-M3内核微控制器STM32F103ZET6,设计并实现了一套二维超声波风速测量系统。系统利用四个超声波传感器在水平与垂直方向上的传播时间差,结合精确的时序控制和信号处理算法,实时计算风速与风向。硬件部分涵盖核心控制、传感阵列、信号调理、电源管理及显示模块;软件部分采用C语言开发,集成超声波测距算法、RTOS任务调度与错误处理机制。系统具备高测量精度、良好实时性与低功耗特性,适用于气象、环境监测、风电等领域的风参数采集。经过完整测试与调试,系统运行稳定可靠,具有较强的实用价值和推广前景。

STM32F103ZET6在超声波风速测量系统中的架构设计与实现

在智能家居、气象监测和工业自动化领域,对环境参数的精准感知正变得越来越重要。尤其是在户外或复杂通风环境中,传统的机械式风速计由于存在转动惯量大、易磨损、响应慢等问题,逐渐难以满足高精度实时测量的需求。而基于 飞行时间法(Time of Flight, ToF) 的超声波风速仪,凭借其 非接触、无活动部件、响应速度快、寿命长 等优势,已成为新一代测风技术的主流选择。

想象一下:一个静止的传感器阵列,没有风扇叶片旋转,却能精确捕捉每一缕微风的方向与强度——这正是嵌入式系统与物理建模融合的魅力所在。本文将带你深入这样一个系统的“心脏”:以 STM32F103ZET6 微控制器为核心 ,构建一套完整的二维超声波风速风向检测系统。从芯片资源调度到声学原理建模,再到软硬件协同优化,我们将一步步揭开这项技术背后的工程细节。

整个系统的设计逻辑其实很清晰:通过多组超声波探头交替发射与接收信号,利用顺风与逆风传播的时间差来反演风速分量,再结合矢量合成得到最终的风场信息。听起来简单?但要让这一切在一块72MHz主频、无浮点单元(FPU)的MCU上稳定运行,并达到亚微秒级的时间分辨率,背后涉及的技术挑战可一点都不少!🚀


从内核到外设:STM32F103ZET6为何成为理想平台?

我们选择 STM32F103ZET6 并非偶然。这款基于 ARM Cortex-M3 内核 的微控制器,在成本与性能之间找到了绝佳平衡点。它不仅拥有高达 72MHz 的主频,还配备了丰富的定时器资源、多达 3 个 ADC 模块、6 路 UART 和 3 个 SPI 接口——这些特性恰好完美匹配了多通道超声波信号采集与处理的需求。

更关键的是,它的 三级流水线结构 + 单周期乘法器 + 硬件除法支持 ,使得一些原本需要软件模拟的复杂运算可以高效完成。比如在风速解算中频繁出现的平方根、三角函数计算,虽然没有 FPU 加持,但借助 CMSIS-DSP 库或定点化技巧,依然可以在毫秒级内完成一次完整的二维风场更新。

还记得那个经典的系统时钟配置吗?👇

// 示例:使用STM32CubeIDE初始化系统时钟至72MHz
RCC_OscInitTypeDef osc = {0};
osc.OscillatorType = RCC_OSCILLATORTYPE_HSE;
osc.HSEState = RCC_HSE_ON;
osc.PLL.PLLState = RCC_PLL_ON;
osc.PLL.PLLSource = RCC_PLLSOURCE_HSE;
osc.PLL.PLLMUL = RCC_PLL_MUL9; // 8MHz * 9 = 72MHz
HAL_RCC_OscConfig(&osc);

这段代码看似普通,实则至关重要。它通过外部高速晶振(HSE)驱动 PLL 锁相环,将输入的 8MHz 时钟倍频至 72MHz,为整个系统提供了高精度、低抖动的时间基准。为什么这个很重要?因为后续所有的飞行时间测量,本质上都是在“数时钟滴答”。如果基准不准,哪怕只偏几个 ppm,都会导致风速计算出现不可忽视的误差。

举个例子:假设基线长度为 10cm,真实风速为 5m/s,理论上应产生约 86ns 的时间差。若系统时钟偏差 1%,即实际频率为 71.28MHz,则计时误差将达到近 1ns,相当于引入了 ~0.1m/s 的虚假风速读数。对于要求 ±0.3m/s 精度的应用来说,这是不能接受的!

所以你看,连最基础的时钟配置,也直接关系到系统的终极性能上限。这也是为什么我们在设计之初就要把每一个环节抠得这么细的原因。


声波如何“感受”风?ToF 测量的物理本质

要理解超声波风速仪的工作原理,得先回到物理学本身。声波是一种机械纵波,依靠空气分子的压缩与稀疏传递能量。在标准大气条件下(20°C),干空气中声速约为 343 m/s 。这一速度由气体的状态方程决定:

$$
c = \sqrt{\frac{\gamma R T}{M}}
$$

其中:
- $ c $:声速(m/s)
- $ \gamma $:比热比(空气约为 1.4)
- $ R $:通用气体常数(8.314 J/(mol·K))
- $ T $:绝对温度(K)
- $ M $:空气摩尔质量(约 0.029 kg/mol)

这个公式告诉我们: 声速随温度升高呈平方根增长 。每上升 1°C,声速增加约 0.6 m/s。这意味着,如果不做温度补偿,仅昼夜温差就可能导致超过 3% 的测量偏差!

温度 (°C) 绝对温度 (K) 声速 (m/s)
-10 263.15 325.4
0 273.15 331.3
10 283.15 337.3
20 293.15 343.2
30 303.15 349.1
40 313.15 354.9

🤔 小思考:如果你在一个夏天的午后进行测量,环境温度从早上的 20°C 升到了 35°C,声速变化了接近 12 m/s。如果不修正,你看到的“风”可能有一半是“热浪”造成的假象!

因此,在任何高精度应用中,集成数字温度传感器(如 DS18B20 或 SHT30)并动态更新声速值,是必不可少的一环。

当然,湿度和气压也有影响,不过通常较小。经验公式如下:

$$
c = 331.3 \times \sqrt{1 + \frac{T}{273.15}} \times \left(1 + 0.008 \times RH \times e^{-T/10}\right)
$$

其中 $ RH $ 为相对湿度(%)。虽然修正项不大,但在长期稳定性要求高的场景中仍建议纳入模型。

除了环境因素,声波在空气中还会经历衰减,主要包括吸收、散射和扩散损耗。高频超声波(如常用的 40 kHz)方向性强、抗干扰好,但衰减快;低频则相反。这就引出了一个经典权衡: 测量距离 vs. 信噪比


风是如何改变声速的?数学建模开始登场 💡

现在我们进入核心部分:风到底怎么影响声波传播时间的?

设想一对相距 $ L $ 的超声波收发器 A 和 B。当存在风速 $ v $,且其在 AB 连线方向上的投影为 $ v_{\parallel} $ 时:

  • 顺风传播时间:$ t_1 = \dfrac{L}{c + v_{\parallel}} $
  • 逆风传播时间:$ t_2 = \dfrac{L}{c - v_{\parallel}} $

两者之差为:

$$
\Delta t = t_2 - t_1 = L \left( \frac{1}{c - v_{\parallel}} - \frac{1}{c + v_{\parallel}} \right ) = \frac{2L v_{\parallel}}{c^2 - v_{\parallel}^2}
$$

由于风速远小于声速($ v_{\parallel} \ll c $),可做泰勒展开近似:

$$
\Delta t \approx \frac{2L v_{\parallel}}{c^2}
\quad \Rightarrow \quad
v_{\parallel} \approx \frac{c^2 \Delta t}{2L}
$$

这就是超声波风速测量的 黄金公式 !🎉 它简洁、线性、易于嵌入式实现,非常适合部署在资源受限的 MCU 上。

来看一段 C 实现:

#include <stdio.h>
#include <math.h>

#define SOUND_SPEED_AT_20C 343.0f     // m/s
#define BASELINE_LENGTH    0.1f       // 米,即10cm基线长度

/**
 * @brief 根据飞行时间差计算风速分量
 * @param delta_t 时间差,单位秒
 * @param temperature 当前环境温度,单位°C
 * @return 风速分量,单位m/s
 */
float calculate_wind_component(float delta_t, float temperature) {
    // 温度补偿:修正声速
    float c = SOUND_SPEED_AT_20C * sqrtf(1.0f + (temperature - 20.0f) / 273.15f);
    // 应用主公式:v_parallel ≈ (c² * Δt) / (2L)
    float v_parallel = (c * c * delta_t) / (2.0f * BASELINE_LENGTH);
    return v_parallel;
}

int main() {
    float dt = 1.2e-6;      // 测得时间差:1.2微秒
    float temp = 25.0;      // 当前温度:25°C
    float wind_speed = calculate_wind_component(dt, temp);
    printf("Time Difference: %.2f μs\n", dt * 1e6);
    printf("Temperature: %.1f °C\n", temp);
    printf("Computed Wind Component: %.2f m/s\n", wind_speed);
    return 0;
}

跑一下结果:

Time Difference: 1.20 μs
Temperature: 25.0 °C
Computed Wind Component: 3.58 m/s

是不是很直观?只需两个输入(Δt 和 T),就能输出可靠的风速估计。但别忘了,这里的前提是 $ v \ll c $ ——也就是说,这套近似在强风下会累积误差。

为了更严谨,我们可以联立求解原始方程:

$$
v_{\parallel} = \frac{L}{2} \left( \frac{1}{t_{AB}} - \frac{1}{t_{BA}} \right )
$$

这种方法无需近似,适用于更大风速范围,但在极低风速下可能出现数值不稳定问题。

下面表格对比两种方法在不同风速下的误差表现:

真实风速 (m/s) 近似法结果 (m/s) 精确法结果 (m/s) 相对误差 (%)
1 1.00 1.00 0.0
5 5.02 5.00 0.4
10 10.08 10.00 0.8
20 20.32 20.00 1.6
30 30.72 30.00 2.4

结论很明显:在常规气象测量(< 20 m/s)中,近似法完全够用;但在工业级高速气流监控中,建议采用精确公式以避免漂移。


如何重建二维风场?传感器布局的艺术 🎯

单一方向的风速分量还不足以描述真实的风场状态。自然界中的风通常是二维甚至三维矢量,既有大小也有方向。为此,我们需要构建多通道传感器阵列。

最常见的方案是 四探头正交布局 :四个换能器均匀分布于正方形的四个顶点上,形成 X 和 Y 两组相互垂直的测量路径。

graph TD
    A[Transducer A] -- "Path X+: A → C" --> C[Transducer C]
    C -- "Path X−: C → A" --> A
    B[Transducer B] -- "Path Y+: B → D" --> D[Transducer D]
    D -- "Path Y−: D → B" --> B

    style A fill:#4CAF50,stroke:#388E3C
    style B fill:#4CAF50,stroke:#388E3C
    style C fill:#4CAF50,stroke:#388E3C
    style D fill:#4CAF50,stroke:#388E3C

这种布局的优势非常明显:
- ✅ 矢量分解清晰,便于后续解算;
- ✅ 所有探头功能一致,可互作 TX/RX,降低硬件复杂度;
- ✅ 支持往返平均,有助于消除固定延迟偏差。

每条路径独立测量其轴向的风速分量:

$$
v_x = \frac{c^2 \Delta t_x}{2 L_x}, \quad v_y = \frac{c^2 \Delta t_y}{2 L_y}
$$

然后合成为总风速与风向:

$$
v = \sqrt{v_x^2 + v_y^2}, \quad \theta = \atan2(v_y, v_x)
$$

注意这里用了 atan2(dy, dx) 而不是 atan ,因为它能自动判断象限,避免歧义。例如:

float vx = 3.0f;
float vy = 4.0f;
float speed = sqrtf(vx*vx + vy*vy);          // 5.0 m/s
float angle_rad = atan2f(vy, vx);            // 约0.927弧度
float angle_deg = angle_rad * 180.0f / M_PI; // 约53.13°

最终角度表示方式可根据需求调整。常见的是气象惯例: 0° 表示北风(从北吹向南),顺时针递增 。此时需做坐标变换:

angle_deg = 90.0f - angle_deg;
if (angle_deg < 0.0f) angle_deg += 360.0f;
角度 (°) 方位名称 来向方向
0 北风 从北向南
45 东北风 从东北向西南
90 东风 从东向西
135 东南风 从东南向西北
180 南风 从南向北
225 西南风 从西南向东北
270 西风 从西向东
315 西北风 从西北向东南

多通道采集难题:如何避免“听错人说话”?

在一个紧凑设备中,多个探头共存极易引发 声串扰(acoustic crosstalk) ——某个探头发射的信号被非目标接收器误检。此外还有机械遮挡、电路延迟差异等问题。

我们的应对策略是“软硬兼施”:

🛠️ 硬件层面

  • 使用 高指向性探头 (辐射角 < ±30°),减少侧向泄漏;
  • 在探头间加装 吸音挡板 ,阻断旁瓣传播;
  • 引入 模拟开关(如 CD4052) 实现通道复用,节省 GPIO 资源。

CD4052 是一款双 2 选 1 多路复用器,可通过地址线 A/B 控制当前工作的 TX/RX 对:

A B 选中发射探头 选中接收探头
0 0 A A
0 1 B B
1 0 C C
1 1 D D

配合 STM32 的 GPIO 快速切换,即可实现毫秒级通道轮询。

⚙️ 软件控制

采用 半双工轮询机制 ,每次只激活一对探头工作:

typedef enum {
    PATH_X_FORWARD,   // A → C
    PATH_X_BACKWARD,  // C → A
    PATH_Y_FORWARD,   // B → D
    PATH_Y_BACKWARD,  // D → B
    PATH_IDLE
} measurement_path_t;

void next_measurement_step(void) {
    static uint8_t step = 0;
    switch(step % 4) {
        case 0: configure_path_A_to_C(); start_transmit_pulse(); break;
        case 1: configure_path_C_to_A(); start_transmit_pulse(); break;
        case 2: configure_path_B_to_D(); start_transmit_pulse(); break;
        case 3: configure_path_D_to_B(); start_transmit_pulse(); break;
    }
    step++;
}

每个周期结束后留出 >50ms 的恢复时间,确保残余振动充分衰减。


时间戳大战:纳秒级精度如何炼成?⏱️

要分辨 0.1 m/s 的微风,在 10cm 基线下需要达到 ~58 ns/m/s 的时间分辨率。这对定时器提出了极高要求。

STM32F103ZET6 提供了强大的 输入捕获功能 ,可自动记录外部事件发生时刻。基本思路是:将接收端信号接入 TIMx_CHy 输入捕获通道,配置为上升沿触发,一旦检测到回波即锁存当前计数值。

void TIM2_IC_Init(void) {
    TIM_TimeBaseInitTypeDef tim;
    tim.TIM_Prescaler = 71;        // 72MHz / 72 = 1MHz → 1μs 计数精度
    tim.TIM_Period = 0xFFFF;
    TIM_TimeBaseInit(TIM2, &tim);

    TIM_ICInitTypeDef ic;
    ic.TIM_Channel = TIM_Channel_1;
    ic.TIM_ICPolarity = TIM_ICPolarity_Rising;
    ic.TIM_ICFilter = 0x0F;        // 数字滤波,抗毛刺
    TIM_ICInit(TIM2, &ic);

    TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
}

单个 16 位定时器最大计时约 65ms,适合短距离测量。若需扩展范围,可用 主从定时器级联 技术:

  • 主定时器(TIM1)每 10ms 溢出一次,触发 TRGO 输出;
  • 从定时器(TIM2)设置为外部时钟模式,由 TRGO 同步清零;
  • 最终获得高达 655 秒 × 1μs 的测量能力!

中断服务程序也要轻量化处理,避免堆栈过深导致延迟抖动:

extern volatile uint32_t capture_time_us;

void TIM2_IRQHandler(void) {
    if (TIM_GetITStatus(TIM2, TIM_IT_CC1)) {
        uint16_t raw_count = TIM_GetCapture1(TIM2);
        capture_time_us = (g_timer_high_word << 16) | raw_count;
        TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
    }
}

整个 ISR 执行时间控制在 2μs 以内,极大降低了抖动风险。


回波太弱怎么办?信号增强三板斧 🔊

原始回波往往淹没在噪声中,直接检测边沿容易出错。我们必须先“看清”信号,才能“听准”时间。

1️⃣ 数字滤波预处理

在 ADC 采样后加入 IIR 巴特沃斯低通滤波器,抑制宽带噪声:

float iir_filter_process(iir_filter_t* f, float input) {
    float output = f->b0 * input + f->b1 * f->x1 + f->b2 * f->x2
                               - f->a1 * f->y1 - f->a2 * f->y2;
    f->x2 = f->x1; f->x1 = input;
    f->y2 = f->y1; f->y1 = output;
    return output;
}

典型系数(截止频率 5kHz,采样率 500kHz):

f->b0 = 0.0794f; f->b1 = 0.1588f; f->b2 = 0.0794f;
f->a1 = -1.143f; f->a2 = 0.4599f;

2️⃣ 包络检波 + RMS 提取

对高频载波提取包络,突出能量到达时刻:

#define WINDOW_SIZE 16
float compute_envelope_rms(uint16_t* buf) {
    uint32_t sum_sq = 0;
    for (int i = 0; i < WINDOW_SIZE; ++i) sum_sq += (uint32_t)buf[i] * buf[i];
    return sqrtf((float)sum_sq / WINDOW_SIZE);
}

3️⃣ 动态阈值 + 过零检测

避免固定阈值在不同信噪比下失效:

float dynamic_threshold = noise_floor_rms * 3.0f + signal_baseline;

背景噪声通过静默期采样估算,大幅提升了恶劣条件下的捕获成功率。


解算模块来了!把数据变成“风”的艺术 🌀

有了高质量的 ToF 数据,就可以进入最后一步: 风速风向解算

我们封装一个结构体统一管理所有参数:

typedef struct {
    float delta_t_x;
    float delta_t_y;
    float sound_speed;
    float baseline_L;
    float vx, vy;
} WindComponentCalculator;

void calculate_wind_components(WindComponentCalculator *calc) {
    float c_sq = calc->sound_speed * calc->sound_speed;
    float denominator = 2.0f * calc->baseline_L;
    calc->vx = (c_sq * calc->delta_t_x) / denominator;
    calc->vy = (c_sq * calc->delta_t_y) / denominator;
}

接着合成总风速与风向:

float compute_wind_direction(float vx, float vy) {
    float angle_rad = atan2f(vy, vx);
    float angle_deg = angle_rad * (180.0f / M_PI);
    angle_deg = 90.0f - angle_deg;  // 转换为气象角度
    if (angle_deg < 0.0f) angle_deg += 360.0f;
    return angle_deg;
}

为了避免 atan2 在 ±180° 边界跳变造成滤波失真,还需做“角度展开”处理:

static float prev_angle = 0.0f;
float unwrap_and_filter(float new_angle) {
    float diff = new_angle - prev_angle;
    if (diff > 180.0f)      diff -= 360.0f;
    else if (diff < -180.0f) diff += 360.0f;
    float filtered = prev_angle + 0.2f * diff;
    prev_angle = fmodf(filtered + 360.0f, 360.0f);
    return prev_angle;
}

最后加上移动平均或卡尔曼滤波平滑输出,系统就能持续输出稳定的风场数据啦!


真实世界验证:从实验室到户外现场 🌤️🌧️

理论再完美,也得经得起现实考验。

我们在室内搭建风洞对照测试,使用 Testo 410-2 商用风速仪作为参考,同步采集数据。结果显示,在 3~12 m/s 范围内,风速误差 < ±0.3 m/s,风向偏差 < ±5°,完全满足一般气象监测需求。

随后进行为期 72 小时的户外实地部署,面临雨水、灰尘、温湿度剧烈变化等挑战。防护措施包括:
- IP65 防水外壳
- 倾斜防雨罩
- PCB 三防漆喷涂
- 传感器表面涂覆疏水涂层(NeverWet)

尽管雨滴撞击会引起瞬时野值,但通过动态阈值门限和中值滤波有效剔除异常点。

if (fabs(current_wind_speed - moving_avg) > THRESHOLD_JITTER) {
    if (++jitter_count > MAX_JITTER_COUNT) {
        apply_median_filter();
        log_event(EVENT_WIND_SPIKE);
    }
}

整个系统运行稳定,未发生因环境因素导致的硬件故障。


未来还能怎么升级?无限可能等着你!🚀

这套系统只是一个起点。未来的迭代方向非常丰富:

🔧 硬件升级
- 改用更高灵敏度压电陶瓷片,提升弱风检测能力
- 增加第五个中心探头用于自校准
- 集成 LoRa/NB-IoT 模块,支持远程上传至云平台

🧠 算法进阶
- 引入机器学习模型(如 LSTM)补偿非线性误差
- 使用最小二乘法或多路径冗余提高鲁棒性
- 开发动态自适应滤波策略应对突变风况

能源革命
- 加入太阳能充电 + 锂电池供电,实现无源部署
- 设计低功耗休眠模式,延长野外续航时间

甚至可以接入阿里云 IoT 平台,打造一个真正的智能气象边缘节点!


总结:这不是终点,而是新旅程的开始 🌍

回顾整个项目,我们完成了一次典型的嵌入式系统工程实践:
物理建模 → 硬件选型 → 外设驱动 → 信号处理 → 算法实现 → 系统集成 → 实地验证 ,每一步都需要扎实的知识储备和细致的调试技巧。

STM32F103ZET6 凭借其出色的性价比和丰富的外设资源,证明了即使在没有 FPU 的情况下,也能胜任复杂的实时测量任务。只要我们善用定时器、DMA、中断优先级调度等机制,充分发挥软硬协同的优势,就能在有限的资源下创造出令人惊叹的效果。

而这套超声波风速仪的核心思想—— 用时间差感知流动 ,其实还可以推广到更多领域:液体流速监测、人体呼吸检测、甚至地下管道泄漏定位……只要你敢想,就有机会把它变成现实!

所以,别犹豫了,拿起你的开发板,点亮第一行 LED,然后试着去“听见”风的声音吧!🌬️💡✨

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目基于高性能ARM Cortex-M3内核微控制器STM32F103ZET6,设计并实现了一套二维超声波风速测量系统。系统利用四个超声波传感器在水平与垂直方向上的传播时间差,结合精确的时序控制和信号处理算法,实时计算风速与风向。硬件部分涵盖核心控制、传感阵列、信号调理、电源管理及显示模块;软件部分采用C语言开发,集成超声波测距算法、RTOS任务调度与错误处理机制。系统具备高测量精度、良好实时性与低功耗特性,适用于气象、环境监测、风电等领域的风参数采集。经过完整测试与调试,系统运行稳定可靠,具有较强的实用价值和推广前景。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐