从竞赛真题到项目实战:手把手教你用STM32和超声波模块DIY一个智能测距仪(附完整代码)
在电子设计竞赛和嵌入式系统学习中,超声波测距是一个经典且实用的项目。它不仅考察了对定时器、中断、GPIO等基础外设的掌握程度,还能延伸出数据存储、人机交互等进阶技能。本文将带你从零开始,用STM32F103和HC-SR04超声波模块打造一个功能完善的智能测距仪,涵盖硬件选型、代码架构设计、调试技巧等全流程实战经验。
1. 项目规划与硬件选型
1.1 核心器件选择
主控芯片的选择直接影响项目复杂度:
- STM32F103C8T6(蓝色pill开发板):72MHz主频,64KB Flash,20KB RAM,性价比极高
- 替代方案:GD32F303(国产替代)、STM32G030(新一代产品)
测距模块对比:
| 型号 | 量程 | 精度 | 接口方式 | 价格 |
|---|---|---|---|---|
| HC-SR04 | 2cm-4m | ±3mm | 数字脉冲 | ¥8 |
| US-100 | 2cm-7m | ±1mm | UART/I2C | ¥25 |
| VL53L0X | 0-2m | ±1mm | I2C | ¥40 |
提示:HC-SR04虽然精度一般,但胜在价格低廉、资料丰富,特别适合初学者练手。
1.2 辅助模块配置
完整的系统还需要:
- 显示模块:0.96寸OLED(I2C接口)或LCD1602(并行接口)
- 输入设备:5向导航按键或旋转编码器
- 存储单元:AT24C02 EEPROM(记录历史数据)
- 报警装置:有源蜂鸣器+RGB LED
2. 硬件电路设计要点
2.1 超声波模块接口设计
HC-SR04的典型连接方式:
// STM32引脚定义 #define TRIG_PIN GPIO_Pin_9 #define TRIG_PORT GPIOB #define ECHO_PIN GPIO_Pin_8 #define ECHO_PORT GPIOF硬件连接注意事项:
- Trig引脚推挽输出,Echo引脚浮空输入
- 超声波模块VCC接5V时测距更稳定
- Echo信号最好串联200Ω电阻保护IO口
2.2 抗干扰设计实战技巧
遇到测量跳变的问题可以尝试:
- 在VCC和GND之间并联100μF+0.1μF电容
- 用锡箔纸包裹传感器侧面减少声波反射
- 测量间隔至少60ms(超过模块的自身周期)
3. 软件架构与核心代码实现
3.1 分层式代码设计
推荐采用模块化结构:
├── Drivers │ ├── hc_sr04.c // 超声波驱动 │ └── oled.c // 显示驱动 ├── Middlewares │ ├── filter.c // 数据滤波 │ └── storage.c // EEPROM存储 └── Application ├── ui.c // 用户界面 └── main.c // 主逻辑3.2 测距核心算法优化
原始竞赛代码的改进方案:
// 改进后的中断处理函数 void EXTI9_5_IRQHandler(void) { static uint32_t rise_time = 0; if(EXTI_GetITStatus(EXTI_Line8) != RESET) { if(GPIO_ReadInputDataBit(ECHO_PORT, ECHO_PIN)) { // 上升沿记录时间 rise_time = TIM_GetCounter(TIM3); } else { // 下降沿计算距离 uint32_t pulse_width = TIM_GetCounter(TIM3) - rise_time; current_distance = pulse_width * 0.017; // 340m/s ÷ 2 ÷ 10000 filter_update(&distance_filter, current_distance); } EXTI_ClearITPendingBit(EXTI_Line8); } }关键优化点:
- 采用移动平均滤波算法
- 使用硬件定时器捕获代替软件延时
- 增加超量程检测(>4m显示"Out of range")
3.3 实用功能扩展
历史记录功能实现:
void save_distance(float dist) { static uint8_t index = 0; eeprom_write_float(ADDR_BASE + index*4, dist); index = (index + 1) % MAX_RECORDS; } float get_max_distance() { float max = 0; for(int i=0; i<MAX_RECORDS; i++) { float val = eeprom_read_float(ADDR_BASE + i*4); if(val > max) max = val; } return max; }4. 调试技巧与性能优化
4.1 常见问题排查指南
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 固定返回0cm | Echo信号未连接正确 | 检查接线,确认中断配置 |
| 测量值波动大 | 电源噪声或环境反射 | 增加滤波算法,改善供电 |
| 超过2m后数据不准 | 声波衰减导致回波弱 | 调整触发间隔,增加发射功率 |
| 显示刷新卡顿 | 频繁清屏导致 | 采用局部刷新策略 |
4.2 校准方法与精度提升
现场校准步骤:
- 测量已知距离(如50cm、100cm)
- 记录原始测量值到数组:
# 校准数据示例 actual = [20, 50, 100, 150] # 实际距离(cm) measured = [18, 47, 95, 140] # 测量值 - 用最小二乘法计算补偿公式:
// 校准后的距离计算 float calibrated_distance(float raw) { return 1.052 * raw + 2.314; // 示例补偿系数 }
5. 项目进阶方向
5.1 多传感器融合方案
结合其他传感器提升可靠性:
- 红外测距(GP2Y0A21):补偿超声波盲区
- TOF激光传感器(VL53L1X):提高短距离精度
- 惯性测量单元(MPU6050):补偿姿态误差
5.2 物联网功能扩展
通过ESP-01S模块添加WiFi功能:
# 通过AT指令配置 AT+CWMODE=1 # Station模式 AT+CWJAP="SSID","password" # 连接WiFi AT+CIPSTART="TCP","api.thingspeak.com",80 # 连接云平台典型应用场景:
- 仓库货物距离监控
- 智能停车位检测
- 液位高度远程监测
完整工程代码结构
主控制循环示例:
while(1) { static uint32_t last_measure = 0; // 每100ms测量一次 if(HAL_GetTick() - last_measure > 100) { float dist = get_filtered_distance(); display_distance(dist); if(btn_pressed(BTN_SAVE)) { save_distance(dist); play_sound(SOUND_SAVE); } last_measure = HAL_GetTick(); } // 处理按键事件 handle_ui_events(); }这个项目最让我惊喜的是硬件滤波的效果——在传感器背面贴上一小块海绵后,测量稳定性提升了40%。后来发现这是因为吸收了电路板振动产生的噪声。