这次我们来看一个基于 STM32L432KC 微控制器的简易超声波测距装置。这个项目的核心不是复杂的算法,而是如何用一块低成本、低功耗的 MCU 和常见的 HC-SR04 超声波模块,快速搭建一个可用的测距系统。对于嵌入式初学者、电子爱好者或需要快速验证测距功能的开发者来说,这是一个非常直接的实践案例。
本文将带你从零开始,理解超声波测距原理,完成 STM32L432KC 的硬件连接、软件编程,并最终实现一个能够稳定测量距离并在串口显示结果的装置。整个过程重点关注代码的可读性、测量的稳定性以及如何在实际环境中进行校准和优化。如果你手头有 STM32 开发板和 HC-SR04 模块,跟着做一遍就能跑通。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 核心控制器 | STM32L432KC (ARM Cortex-M4, 低功耗) |
| 测距模块 | HC-SR04 超声波传感器 |
| 测距原理 | 测量超声波发射到接收回波的时间差,根据声速计算距离 |
| 有效测距范围 | 通常为 2cm ~ 400cm (实际效果受环境、模块精度影响) |
| 输出方式 | 通过 USART 串口输出距离数据到 PC 端串口助手 |
| 供电需求 | 3.3V 或 5V (STM32开发板供电,注意HC-SR04逻辑电平) |
| 开发环境 | STM32CubeIDE / Keil MDK |
| 关键外设 | GPIO (输出触发、输入捕获)、定时器 (高精度计时)、USART (通信) |
| 适合场景 | 嵌入式学习、课程设计、避障小车原型、简单物体检测 |
2. 适用场景与使用边界
这个简易超声波测距装置最适合以下几类人群和场景:
- 嵌入式初学者:通过一个完整的“传感器-控制器-输出”项目,学习 STM32 的 GPIO、定时器、中断和串口编程。
- 快速原型验证:在开发机器人、智能小车或安防设备初期,需要快速验证测距功能是否可行。
- 电子课程设计或毕业设计:作为一个功能明确、硬件成本低、代码量适中的选题。
它的能力边界也很清晰:
- 精度有限:HC-SR04 模块本身精度通常在毫米到厘米级,且声速受温度、湿度影响。不适合需要亚毫米级高精度测量的场合。
- 测量对象:对平坦、坚硬的表面反射效果最好。对棉花、泡沫等吸音材料,或角度过大的斜面,可能无法测距或误差极大。
- 环境干扰:多个超声波模块同时工作可能互相干扰。嘈杂的声学环境也可能影响测量。
- 非接触式:只能测量传感器正前方障碍物的距离,无法识别物体材质、形状。
重要提醒:本项目用于学习和功能验证。若用于实际产品,需考虑更专业的传感器、温度补偿算法以及严格的电磁兼容和安全设计。
3. 环境准备与前置条件
在开始编程和接线之前,请确保你已准备好以下软硬件环境。
硬件清单:
- STM32L432KC 开发板一块(如 NUCLEO-L432KC)。
- HC-SR04 超声波模块一个。
- 杜邦线若干(母对母、公对母,根据开发板接口而定)。
- USB 数据线一根(用于给开发板供电和程序下载/调试)。
- PC 电脑一台。
- (可选) 逻辑电平转换模块:如果开发板只有3.3V电平,而你的HC-SR04是5V逻辑版本,则需要一个5V转3.3V的电平转换器,用于Echo回波信号线。
软件清单:
- 集成开发环境 (IDE):STM32CubeIDE 或 Keil MDK (uVision)。本文以 STM32CubeIDE 为例,因为它集成了STM32CubeMX图形化配置工具,对新手更友好。
- 串口调试助手:如 Putty、SecureCRT、或者 IDE 自带的串口终端。用于查看测距结果。
- STM32CubeProgrammer:用于程序烧录(STM32CubeIDE 已集成烧录功能,可备用)。
4. 硬件连接与电路原理
HC-SR04 模块通常有 4 个引脚:VCC、Trig (触发)、Echo (回波)、GND。
连接示意图如下:
HC-SR04 STM32L432KC NUCLEO 板 ============================================= VCC -----------> 5V 或 3.3V 引脚 (注意模块电压) Trig -----------> 任意 GPIO 输出引脚 (如 PA5) Echo -----------> 任意 GPIO 输入引脚 (如 PA6) GND -----------> GND关键连接说明:
- VCC 与 GND:为模块供电。务必确认你的 HC-SR04 模块的工作电压。老版本多为5V,新版本有3.3V/5V兼容的。如果是5V模块,接到开发板的5V引脚;如果是3.3V模块,接到3.3V引脚。NUCLEO-L432KC 板上有标注的 5V (VIN) 和 3.3V (3V3) 输出。
- Trig (触发):连接到一个配置为推挽输出的 GPIO 引脚。STM32 通过向此引脚发送一个至少 10us 的高电平脉冲来触发模块发射超声波。
- Echo (回波):连接到一个配置为浮空输入或上拉输入的 GPIO 引脚。该引脚在模块接收到回波后会输出一个高电平脉冲,其宽度与距离成正比。这是最需要注意的地方:如果 HC-SR04 是 5V 逻辑输出,而 STM32L432KC 的 GPIO 引脚只能容忍 3.3V,则必须通过电平转换电路(如分压电阻或电平转换芯片)将 5V 降至 3.3V 后再接入 PA6,否则可能损坏 MCU!如果是 3.3V 逻辑的模块,则可以直接连接。
电平转换简易方案(针对5V Echo信号):使用两个电阻组成分压电路:Echo 信号先串联一个 1kΩ 电阻,然后连接到 STM32 的 GPIO,同时在 GPIO 引脚与 GND 之间并联一个 2kΩ 电阻。这样可以将 ~5V 电压分压到 ~3.3V。
5. STM32CubeMX 工程配置
我们使用 STM32CubeMX 进行图形化配置,生成初始化代码框架。
- 创建新工程:打开 STM32CubeIDE,选择 “Start new STM32 project”,搜索并选择 MCU 型号 “STM32L432KCUx”。
- 配置系统时钟 (SYS):
- 在 “Pinout & Configuration” 标签页,左侧找到 “System Core” -> “SYS”。
- 将 “Debug” 设置为 “Serial Wire” (这是 ST-LINK 调试接口所需)。
- 配置时钟树 (RCC):
- 找到 “RCC”。
- 将 “High Speed Clock (HSE)” 设置为 “Crystal/Ceramic Resonator”。(如果板载外部高速晶振)。
- 进入 “Clock Configuration” 标签页。输入 HSE 频率(如 8MHz),然后通过 PLL 将系统时钟 (SYSCLK) 配置到最高 80MHz。APB1 和 APB2 总线时钟也会自动配置。
- 配置 GPIO 引脚:
- 在芯片引脚图上,找到你计划使用的引脚(例如 PA5 和 PA6)。
- 单击 PA5,选择 “GPIO_Output”。在左侧的 GPIO 配置中,可以设置用户标签为 “TRIG”。
- 单击 PA6,选择 “GPIO_Input”。用户标签设置为 “ECHO”。根据是否需要内部上拉,在 GPIO 设置中选择 “Pull-up” 或 “No pull-up and no pull-down”。
- 配置定时器 (TIM) 用于高精度计时:
- 超声波测距需要微秒级的时间测量。我们将使用一个通用定时器(如 TIM2)的输入捕获功能,或者更简单的方式:在代码中利用定时器的计数器直接计算时间。
- 这里我们采用定时器中断+计数器读取的方式,更直观。找到 “TIM2”(或其他空闲的通用定时器)。
- 在 “Parameter Settings” 中:
Prescaler (PSC): 设置为 80-1。如果系统时钟是80MHz,分频后计数器时钟为 1MHz (1us计数一次)。Counter Period (ARR): 设置为 65535-1 (16位定时器的最大值) 或更大值(如果定时器是32位)。Counter Mode: Up。auto-reload preload: Disable。
- 在 “NVIC Settings” 中,不要使能定时器中断。我们只用它的计数器。
- 配置串口 (USART) 用于输出:
- 找到 “USART2”(NUCLEO板通常通过 ST-LINK 的虚拟串口连接到 USART2)。
- 将 “Mode” 设置为 “Asynchronous”。
- 在 “Parameter Settings” 中,波特率设置为 115200 Bits/s,其他默认(8位数据,无校验,1停止位)。
- 引脚 PA2 和 PA3 会自动配置为 USART_TX 和 USART_RX。
- 生成工程代码:
- 转到 “Project Manager” 标签页。
- 设置 “Project Name” 和 “Project Location”。
- “Toolchain / IDE” 选择 “STM32CubeIDE”。
- 点击 “Generate Code”。
6. 核心代码编写与解析
工程生成后,我们主要在main.c和stm32l4xx_it.c(如果用到中断)中添加业务逻辑。
6.1 宏定义与全局变量
在main.c的/* USER CODE BEGIN PV */区域,定义引脚和变量。
/* USER CODE BEGIN PV */ // 超声波模块引脚定义 (根据你的实际连接修改) #define TRIG_PIN GPIO_PIN_5 #define TRIG_PORT GPIOA #define ECHO_PIN GPIO_PIN_6 #define ECHO_PORT GPIOA // 声速定义 (单位:厘米/微秒),在20°C干燥空气中约为 0.0343 cm/us #define SOUND_SPEED_CM_US 0.0343 // 也可以定义为 34300 cm/s,然后在计算时转换时间单位 volatile uint32_t pulse_start = 0; volatile uint32_t pulse_end = 0; volatile uint8_t echo_received = 0; float distance_cm = 0; /* USER CODE END PV */6.2 超声波触发与回波捕获函数
在/* USER CODE BEGIN 0 */区域,编写驱动函数。
/* USER CODE BEGIN 0 */ #include <stdio.h> // 用于 printf // 微秒级延时函数(简易实现,精度受系统时钟和优化影响,用于触发脉冲) void Delay_us(uint16_t us) { uint32_t ticks = us * (SystemCoreClock / 1000000) / 5; // 粗略计算循环次数 while(ticks--) { __NOP(); } } // 发送触发脉冲 void Ultrasonic_Trigger(void) { HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET); // 拉高 Trig Delay_us(12); // 保持高电平至少10us,这里给12us HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET); // 拉低 Trig } // 获取高电平脉冲宽度(单位:微秒) // 方法:轮询等待 Echo 引脚上升沿和下降沿,利用定时器计数器计算时间差。 uint32_t Get_Pulse_Width_us(void) { uint32_t start_time = 0, end_time = 0; uint32_t timeout = 0; // 等待 Echo 变为高电平 (上升沿) timeout = 100000; // 超时计数,防止死等 while (HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) == GPIO_PIN_RESET) { if (timeout-- == 0) return 0; // 超时返回0 } start_time = __HAL_TIM_GET_COUNTER(&htim2); // 记录定时器当前值 // 等待 Echo 变为低电平 (下降沿) timeout = 100000; while (HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) == GPIO_PIN_SET) { if (timeout-- == 0) return 0; // 超时返回0 } end_time = __HAL_TIM_GET_COUNTER(&htim2); // 计算时间差 (us),因为定时器时钟是1MHz // 注意处理定时器溢出的情况(简单项目可暂不考虑,或确保测量距离内不会溢出) if (end_time >= start_time) { return (end_time - start_time); } else { // 计数器溢出,计算溢出后的值 return (0xFFFFFFFF - start_time + end_time); // 假设是32位定时器 } } // 重定向 printf 到串口 (方便打印) int _write(int file, char *ptr, int len) { HAL_UART_Transmit(&huart2, (uint8_t*)ptr, len, HAL_MAX_DELAY); return len; } /* USER CODE END 0 */6.3 主循环逻辑
在main()函数的while (1)循环中,添加测距和打印逻辑。
/* USER CODE BEGIN WHILE */ HAL_TIM_Base_Start(&htim2); // 启动定时器2计数器 printf("Ultrasonic Ranging System Started.\r\n"); while (1) { Ultrasonic_Trigger(); // 发送触发脉冲 HAL_Delay(60); // 等待至少60ms,防止发射信号对回波的干扰 uint32_t pulse_width = Get_Pulse_Width_us(); // 获取高电平时间(微秒) if (pulse_width > 0) { // 计算距离:距离 = (时间 * 声速) / 2 // 时间单位是 us,声速是 cm/us,除以2是因为声音走了来回两倍路程 distance_cm = (pulse_width * SOUND_SPEED_CM_US) / 2.0; printf("Pulse Width: %lu us, Distance: %.2f cm\r\n", pulse_width, distance_cm); } else { printf("Measurement timeout or error.\r\n"); } HAL_Delay(200); // 主循环延时,控制测量频率 } /* USER CODE END WHILE */7. 功能测试与效果验证
编译与下载:
- 在 STM32CubeIDE 中,点击 “Build” 按钮编译工程。
- 使用 USB 线连接开发板,点击 “Debug” 按钮将程序下载到 MCU 并进入调试模式,或直接点击 “Run” 运行。
串口监视:
- 退出调试模式(如果进入了)。在 IDE 的 “Window” -> “Show View” -> “Other…” 中搜索 “Serial Monitor” 并打开。
- 或者在电脑上打开独立的串口调试助手(如 Putty)。
- 选择正确的串口号(在设备管理器中查看 STM32 ST-LINK 虚拟串口的 COM 号)。
- 设置波特率为 115200,数据位 8,停止位 1,无校验,无流控。
- 连接串口。
观察结果:
- 将超声波传感器模块正面朝向空旷处或一个固定距离的物体(如墙壁)。
- 观察串口终端输出的信息。你应该能看到类似以下的输出:
Ultrasonic Ranging System Started. Pulse Width: 588 us, Distance: 10.09 cm Pulse Width: 588 us, Distance: 10.09 cm ... - 改变传感器与物体之间的距离,输出值应相应变化。
校准与优化:
- 基准测试:用一个卷尺测量实际距离,与串口输出的距离对比。计算误差。
- 调整声速:
SOUND_SPEED_CM_US常量是基于 20°C 空气的。如果环境温度差异大,可以根据公式V = 331.4 + 0.6 * T(m/s, T为摄氏温度) 重新计算并更新该常量。 - 处理异常值:在实际应用中,可以连续测量多次,然后取中值或平均值,以滤除偶然的干扰脉冲。
- 检查超时:如果总是测量超时,检查 Echo 引脚连接、电平转换是否正确,以及
Get_Pulse_Width_us函数中的超时值是否合理。
8. 资源占用与性能观察
- CPU 占用:主循环中主要的耗时操作是
HAL_Delay(200),即每 200ms 测量一次,CPU 大部分时间处于空闲或低功耗状态。Get_Pulse_Width_us函数中的轮询等待会短暂占用 CPU。对于 STM32L432KC 来说负载极低。 - 定时器资源:我们占用了一个通用定时器 (TIM2) 作为高精度时钟源。它被配置为自由运行模式,不产生中断,只提供计数器值。
- GPIO 资源:占用两个 GPIO 引脚。
- USART 资源:占用一个 USART 用于调试输出。
- 内存占用:代码量小,全局变量少,对 RAM 和 Flash 的占用可忽略不计。
- 功耗:STM32L432KC 本身是超低功耗 MCU,HC-SR04 模块在非触发状态下功耗也很低。整个系统非常适合电池供电的便携设备。
性能提升方向:
- 使用定时器的输入捕获中断来测量 Echo 高电平宽度,可以更精确且不阻塞 CPU。
- 使用 DMA 传输串口数据,进一步降低 CPU 干预。
- 实现温度传感器 (如 DS18B20) 实时补偿声速。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 串口无任何输出 | 1. 串口线未连接或端口错误。 2. 波特率设置错误。 3. 代码未成功运行。 | 1. 检查设备管理器中的串口号。 2. 确认串口助手波特率为115200。 3. 检查程序是否下载成功,MCU是否复位。 | 1. 选择正确的COM口。 2. 双方波特率保持一致。 3. 重新下载程序,按复位键。 |
| 串口一直输出 “Measurement timeout or error.” | 1. Echo 引脚始终为低电平。 2. 触发信号未发出。 3. 模块供电不足或损坏。 4. 物体超出测距范围或表面不反射。 | 1. 用逻辑分析仪或示波器检查 Trig 和 Echo 信号。 2. 用万用表测量 VCC 和 GND 电压。 3. 将模块对准近距离的平整墙壁测试。 | 1. 检查 Trig/Echo 连线,确认电平转换电路(如果需要)。 2. 确保供电电压符合模块要求(5V或3.3V)。 3. 更换模块测试。 |
| 测量距离固定不变或明显错误 | 1. 声速常量设置错误。 2. 定时器分频系数计算错误,导致时间单位不对。 3. 测量对象是软质或斜面。 | 1. 用尺子测量一个固定距离(如20.0cm),对比输出,反推实际声速或时间系数。 2. 检查 Get_Pulse_Width_us函数返回的时间值是否合理。 | 1. 根据实测结果校准SOUND_SPEED_CM_US或计算时的系数。2. 检查定时器时钟配置,确认计数器加1是否代表1us。 3. 对硬质平面进行测量。 |
| 测量值跳动大,不稳定 | 1. 环境噪声干扰。 2. 电源纹波大。 3. 软件中没有滤波算法。 | 观察多次测量的原始脉冲宽度值。 | 1. 在软件中实现多次测量取中值或平均值滤波。 2. 为模块电源增加滤波电容。 3. 避免在嘈杂的声学环境中使用。 |
| 程序下载失败 | 1. 调试器连接问题。 2. 芯片被写保护。 | 1. 检查 USB 线、驱动。 2. 查看 IDE 中的调试配置。 | 1. 重新插拔 USB 线,重启 IDE。 2. 使用 STM32CubeProgrammer 连接并解除保护。 |
10. 最佳实践与扩展建议
最佳实践:
- 首次上电:先不接 Echo 线,用示波器或逻辑分析仪观察 Trig 引脚是否有正确的 10us 以上脉冲。这是确认 MCU 控制逻辑正确的第一步。
- 电平安全:始终将“5V Echo 信号连接至 3.3V MCU”作为重点检查项,务必使用电平转换或分压电路。
- 电源去耦:在 HC-SR04 的 VCC 和 GND 引脚附近并联一个 10uF 电解电容和一个 0.1uF 陶瓷电容,以稳定电源,减少干扰。
- 软件滤波:在产品代码中,务必实现软件滤波。例如连续采样 5 次,去掉最大最小值后求平均,能极大提升读数稳定性。
- 错误处理:增加对超长回波(超出量程)、超短回波(低于最小测距)的判断和处理。
扩展建议:
- 增加显示:将距离值显示在 LCD 屏(如 OLED)或数码管上,做成一个独立的测距仪。
- 报警功能:设置一个距离阈值,当测量值低于该阈值时,让一个 LED 闪烁或蜂鸣器鸣叫,实现简易报警器。
- 多传感器:使用多个超声波模块,通过 GPIO 分时复用或不同的定时器通道,实现多方向测距,用于机器人环境感知。
- 无线传输:通过蓝牙模块 (如 HC-05) 或 WiFi 模块 (如 ESP8266) 将距离数据发送到手机或云端。
- 低功耗优化:利用 STM32L4 的低功耗模式,让 MCU 在两次测量之间进入 Sleep 或 Stop 模式,HC-SR04 模块也只在测量时上电,大幅延长电池寿命。
这个基于 STM32L432KC 的超声波测距项目,麻雀虽小五脏俱全。它串联了 GPIO 控制、定时器计时、串口通信等嵌入式核心知识点。通过动手实现它,你不仅能得到一个可工作的测距工具,更能透彻理解传感器与控制器的交互方式。建议在成功实现基础功能后,尝试上述一两个扩展功能,这对提升工程能力非常有帮助。