基于MicroPython与ESP32的超声波测距仪开发实战

发布时间:2026/7/28 12:14:51
基于MicroPython与ESP32的超声波测距仪开发实战 1. 项目概述从想法到实物的距离感知最近在整理工作室的物料发现手头积攒了不少ESP32开发板和HC-SR04超声波传感器。这些硬件单独看都是好东西但总感觉没发挥出全部价值。一个偶然的念头冒出来能不能用它们快速做一个即插即用、带显示功能的距离测量工具比如测量一下书架到墙面的精确距离或者给DIY的智能小车做个简易的避障探头。这个想法让我立刻想到了MicroPython——一种运行在微控制器上的Python实现。它语法简洁交互性强对于快速原型开发来说简直是神器。于是“基于MicroPython的距离测量仪”这个项目就正式启动了。这个项目的核心目标很明确利用ESP32作为主控驱动HC-SR04超声波传感器测量距离并将结果实时显示在一块OLED屏幕上。整个过程我们希望完全用MicroPython来实现从传感器数据读取、距离计算到最终的结果显示。它不仅仅是一个简单的代码拼接更涉及到MicroPython环境部署、硬件引脚驱动、定时器使用、数据显示优化等一系列嵌入式开发中的实际问题。无论你是想学习MicroPython硬件编程的初学者还是希望为某个创意项目快速添加距离感知功能的开发者这个从零到一的构建过程都能提供清晰的路径和可复现的细节。2. 核心硬件选型与电路设计思路2.1 主控芯片为什么是ESP32在众多微控制器中选择ESP32作为本项目的主控是经过多方面权衡的。首先ESP32内置Wi-Fi和蓝牙虽然本项目暂未用到无线功能但这为未来的功能扩展如将测量数据上传到服务器或通过手机APP查看预留了巨大的空间。其次ESP32拥有双核处理器和丰富的外设接口运行MicroPython解释器流畅能够轻松应对传感器数据采集和屏幕刷新这类任务。最重要的是ESP32在MicroPython社区的支持非常完善有大量现成的库和活跃的论坛遇到问题容易找到解决方案。相较于传统的Arduino开发方式使用MicroPython on ESP32的优势在于开发效率。我们无需复杂的IDE配置和编译下载过程通过串口工具如PuTTY、Thonny就能直接以交互式命令或运行脚本的方式操作硬件调试过程直观快捷。对于快速验证想法和迭代功能这种“所见即所得”的体验是无与伦比的。2.2 感知单元HC-SR04超声波传感器工作原理HC-SR04是目前最普及、性价比极高的超声波测距模块。它的原理并不复杂模块上有一个超声波发射器和一个接收器。工作时我们给Trig引脚一个至少10微秒的高电平脉冲模块便会自动发射一组8个40kHz的超声波。超声波在空气中传播遇到障碍物后反射回来被接收器捕获。模块内部的电路会监测这个回波并在Echo引脚输出一个高电平脉冲该脉冲的宽度与超声波从发射到返回所经历的时间成正比。因此测距的核心公式就是距离 (高电平时间 * 声速) / 2。声速在常温下约为340m/s。我们需要用微控制器精确测量Echo引脚高电平的持续时间。这里有一个关键点HC-SR04的有效测量范围官方标称是2cm到400cm但实际使用中在2cm以内由于回波与发射波重叠可能导致无法测量而超过4米后回波信号会变得非常微弱测量结果不稳定。对于精度要求不高的场景误差在几毫米到1厘米它完全够用。2.3 显示单元OLED屏幕的选择与优势为了能直观地看到测量结果我选择了一块0.96英寸的I2C接口OLED屏幕。选择它主要基于以下几点考虑一是自发光无需背光在暗环境下显示清晰且省电二是分辨率通常为128x64足以显示数字、字符和简单的图形三是I2C接口仅需两根数据线SDA, SCL即可通信极大节省了ESP32宝贵的GPIO资源。在MicroPython中通常使用ssd1306库来驱动这类OLED屏幕。我们需要将屏幕的SDA和SCL引脚分别连接到ESP32的任意I2C引脚上例如GPIO21和GPIO22并在代码中初始化I2C总线及屏幕对象。之后就可以像在电脑上打印信息一样调用方法将距离数值“画”到屏幕上了。2.4 电路连接与供电方案整个系统的连接非常简单清晰ESP32与HC-SR04将HC-SR04的Vcc和Gnd分别连接到ESP32的3.3V和GND。特别注意HC-SR04的工作电压是5V但其逻辑电平与3.3V系统兼容。经过实测直接使用ESP32的3.3V为其供电在短距离测量时工作正常。若追求最稳定的长距离性能可以考虑使用外部5V电源为传感器单独供电但Echo引脚信号仍需接入ESP32ESP32的GPIO可耐受5V输入。Trig引脚连接至ESP32的任意GPIO如GPIO5Echo引脚连接至另一GPIO如GPIO18。ESP32与OLEDOLED的VCC接3.3VGND接GND。SDA接GPIO21SCL接GPIO22。供电整个系统可通过ESP32开发板的Micro-USB口供电非常方便。如果希望做成独立设备可以连接一个容量合适的锂电池到ESP32的VIN引脚。注意在面包板上搭建电路时务必确保电源和地线连接牢固避免虚接导致传感器或屏幕工作不稳定。所有信号线不宜过长以减少干扰。3. MicroPython环境部署与基础驱动3.1 为ESP32刷入MicroPython固件这是所有工作的第一步。你需要从MicroPython官网下载针对ESP32的最新稳定版固件.bin文件。然后使用刷机工具如esptool.py通过USB转串口模块连接到ESP32的串口引脚。刷机命令通常如下在电脑命令行中执行具体端口号需根据实际情况修改esptool.py --chip esp32 --port COM3 erase_flash esptool.py --chip esp32 --port COM3 --baud 460800 write_flash -z 0x1000 esp32-xxx.bin第一条命令擦除原有固件第二条命令写入新的MicroPython固件。刷写成功后通过串口工具如Thonny IDE自带的终端或PuTTY以115200的波特率连接ESP32你应该能看到MicroPython的交互式解释器提示符。这意味着你的ESP32已经变成了一个Python运行时环境。3.2 安装必要的驱动库MicroPython固件本身并不包含针对特定硬件如HC-SR04, OLED的驱动库我们需要手动上传。以Thonny IDE为例这是一个极其方便的选择它集成了代码编辑、库管理和串口终端。安装HC-SR04驱动在PC上你可以找到一个名为hcsr04.py的MicroPython库文件可以从开源社区如GitHub获取。在Thonny中打开这个文件然后选择“文件” - “另存为”在弹出的对话框中选择“MicroPython设备”将其保存到ESP32的根目录下。这样在你的主程序中就可以通过import hcsr04来使用它了。安装OLED驱动同理找到ssd1306.py库文件用同样的方式上传到ESP32。3.3 编写传感器与屏幕的测试代码在深入核心逻辑前先分别测试两个外设是否工作正常。测试HC-SR04import machine, time from hcsr04 import HCSR04 # 初始化传感器Trig引脚5, Echo引脚18 sensor HCSR04(trigger_pin5, echo_pin18) while True: distance sensor.distance_cm() # 获取以厘米为单位的距离 print(“距离{} cm”.format(distance)) time.sleep(1) # 每秒测量一次运行这段代码在终端里你应该能看到不断输出的距离数据。用手在传感器前移动观察数值变化。测试OLED屏幕import machine from machine import Pin, SoftI2C import ssd1306 import time # 初始化I2C总线使用软件I2C引脚可自定义 i2c SoftI2C(sclPin(22), sdaPin(21)) # 初始化OLED对象参数为屏幕尺寸 oled ssd1306.SSD1306_I2C(128, 64, i2c) oled.fill(0) # 清屏0代表黑色1代表白色 oled.text(‘Hello World!’, 0, 0) # 在坐标(0,0)处显示文字 oled.text(‘OLED Test OK’, 0, 16) oled.show() # 将缓存内容显示到屏幕上 time.sleep(2)如果屏幕成功显示“Hello World!”则说明I2C通信和屏幕驱动都是正常的。4. 核心测量逻辑与代码实现4.1 超声波测距的MicroPython实现细节虽然我们可以使用现成的hcsr04库但理解其底层实现对于调试和优化至关重要。一个典型的底层测量函数如下def measure_distance(trig_pin, echo_pin): # 确保Trig引脚先拉低 trig Pin(trig_pin, Pin.OUT) echo Pin(echo_pin, Pin.IN) trig.value(0) time.sleep_us(2) # 发送10us的高脉冲触发信号 trig.value(1) time.sleep_us(10) trig.value(0) # 等待Echo引脚变高并记录时间 while echo.value() 0: pulse_start time.ticks_us() # 等待Echo引脚变低并记录时间 while echo.value() 1: pulse_end time.ticks_us() # 计算高电平持续时间微秒 pulse_duration time.ticks_diff(pulse_end, pulse_start) # 计算距离厘米。声速340m/s 0.034 cm/us除以2因为是往返距离 distance (pulse_duration * 0.034) / 2 return distance这段代码揭示了几个关键点时序要求time.sleep_us()函数用于微秒级延时这对产生精确的10us触发脉冲至关重要。时间测量time.ticks_us()获取当前微秒计数time.ticks_diff()计算时间差它能正确处理计数器溢出的情况。阻塞式等待代码中使用while循环等待Echo引脚电平变化这是一种阻塞方式。在等待期间CPU无法执行其他任务。对于简单的单任务测量仪这没问题但如果系统复杂可能需要用到中断或定时器来避免阻塞。4.2 数据滤波与稳定性处理在实际环境中超声波传感器很容易受到干扰偶尔会产生 outliers异常值比如突然一个非常大的数或0。直接显示这些数据会导致读数跳动剧烈体验很差。因此必须加入软件滤波。一个简单有效的方法是中值滤波。我们连续采样N次比如5次将这组数据排序然后取中间值作为最终结果。这能有效滤除偶然的突发干扰。def get_filtered_distance(sensor, sample_size5): readings [] for _ in range(sample_size): try: dist sensor.distance_cm() # 可选增加一个有效范围判断比如只接受2-400cm之间的值 if 2 dist 400: readings.append(dist) except Exception as e: # 捕获传感器超时等异常 print(“测量异常”, e) continue time.sleep_ms(50) # 每次采样间隔一小段时间 if readings: readings.sort() return readings[len(readings)//2] # 返回中值 else: return None # 所有采样都无效4.3 OLED屏幕显示优化直接将数字打印到屏幕上可能不够美观。我们可以进行一些优化清屏与局部更新每次更新数据时不需要用oled.fill(0)清空整个屏幕再重绘所有内容这样会导致闪烁。可以只清除数字显示的区域。例如如果我们固定在一个位置显示距离可以先用白色矩形块覆盖旧数字的区域再绘制新数字。大字体显示MicroPython的text函数默认字体较小。为了更醒目我们可以使用自定义的位图字体或者用画矩形、线条的方式“画”出大号数字。一个取巧的办法是如果距离是整数可以将数字转换成字符串然后用较大的字号通过多次调用text函数模拟显示。添加单位与标识在数字旁边固定显示“cm”单位甚至画一个简单的超声波图标都能让界面更专业。def display_distance(oled, distance): oled.fill_rect(40, 20, 60, 30, 0) # 局部清除数字显示区域 if distance: # 将浮点数格式化为一位小数 dist_str “{:.1f}”.format(distance) # 显示数字可以尝试用两行大字号显示 oled.text(dist_str, 40, 25) oled.text(“cm”, 85, 25) else: oled.text(“—–“, 40, 25) # 显示无效符号 oled.show()4.4 主程序循环与系统集成将以上所有部分整合起来就构成了主程序循环。这个循环需要平衡测量频率、显示刷新率和系统响应性。from machine import Pin, SoftI2C, Timer import ssd1306 from hcsr04 import HCSR04 import time # 初始化硬件 sensor HCSR04(trigger_pin5, echo_pin18) i2c SoftI2C(sclPin(22), sdaPin(21)) oled ssd1306.SSD1306_I2C(128, 64, i2c) # 初始化显示 oled.fill(0) oled.text(“Distance Meter”, 10, 0) oled.text(“—————”, 10, 10) oled.show() last_distance 0 update_interval 500 # 每500毫秒更新一次 def main_loop(timer): global last_distance filtered_dist get_filtered_distance(sensor, 5) # 只有当距离变化超过0.5cm时才更新屏幕减少不必要的刷新 if filtered_dist and abs(filtered_dist - last_distance) 0.5: display_distance(oled, filtered_dist) last_distance filtered_dist elif filtered_dist is None: display_distance(oled, None) # 使用定时器周期性地执行主循环避免使用time.sleep阻塞 timer Timer(-1) timer.init(periodupdate_interval, modeTimer.PERIODIC, callbackmain_loop) # 主程序保持运行 try: while True: time.sleep(1) except KeyboardInterrupt: timer.deinit() oled.fill(0) oled.text(“Bye!”, 50, 30) oled.show() print(“程序退出”)这个设计使用了硬件定时器来触发测量和显示逻辑使得主循环while True可以保持空闲或处理其他潜在任务提高了系统的可扩展性。5. 性能优化与功能扩展思考5.1 提升测量响应速度与精度基础的循环测量在响应速度上已经足够用于手动测距。但如果想用于高速移动的机器人避障就需要优化。首先可以尝试减少get_filtered_distance中的采样次数和间隔但这会牺牲稳定性。更高级的方法是使用中断。可以将Echo引脚配置为中断输入在上升沿和下降沿触发中断来精确记录时间戳。这样主程序完全不会被测量过程阻塞。不过在MicroPython中处理高频中断需要非常小心避免在中断服务程序中进行复杂操作。对于精度温度对声速影响很大。声速v ≈ 331.4 0.6 * T其中T为摄氏温度。如果环境温度变化大可以增加一个温度传感器如DS18B20根据实时温度修正声速值从而将精度从厘米级提升到毫米级。5.2 低功耗设计与电池供电如果希望做成便携设备低功耗是关键。ESP32具有深度睡眠模式。我们可以设计这样的工作流程设备大部分时间处于深度睡眠由一个外部唤醒源如定时器或按键唤醒。唤醒后迅速完成一次测量和显示然后再次进入睡眠。这样两节AA电池可能就能支撑数周甚至数月。在MicroPython中可以使用machine.deepsleep()函数并配合RTC内存保存必要状态。5.3 数据记录与无线传输功能扩展这是ESP32的强项。我们可以轻松添加SD卡模块将带有时间戳的距离数据记录到文件中用于长期监测如监测水位变化。更吸引人的是启用Wi-Fi功能。设备可以连接家庭路由器将实时数据通过MQTT协议发布到本地服务器或云平台如Home Assistant实现远程监控。或者开启一个简单的Web服务器在手机浏览器上输入ESP32的IP地址就能看到一个实时刷新的距离仪表盘。5.4 封装与产品化对于最终成品可以考虑用3D打印一个外壳将ESP32、传感器、屏幕和电池集成在一起。外壳需要为超声波传感器开好窗口并考虑散热和天线位置如果使用无线功能。甚至可以设计一个简单的按键用来切换测量模式单位切换、连续测量/单次测量等。6. 常见问题与调试心得实录在实际制作过程中你几乎一定会遇到下面这些问题。这里记录了我的排查过程和解决方案。6.1 传感器无响应或读数固定不变现象终端始终打印同一个值可能是0.0或者一个极大的数用手遮挡传感器也没有变化。排查步骤检查电源和地线这是最常见的问题。用万用表测量HC-SR04的Vcc和GND之间电压确保在3.3V左右或5V。接触不良是面包板项目的头号杀手。检查信号线连接确认Trig和Echo引脚是否与代码中定义的GPIO号对应且连接无误。检查代码引脚模式确保Trig引脚被设置为Pin.OUTEcho引脚被设置为Pin.IN。检查声波探头观察传感器表面的两个金属圆柱体收发探头是否干净有无异物遮挡。降低测量频率尝试在每次测量之间增加更长的延时如time.sleep(1)给传感器足够的恢复时间。更换传感器或ESP32引脚排除硬件损坏或特定引脚故障的可能性。6.2 测量结果跳动剧烈现象数值在真实值附近快速、无规律地跳动。原因与解决环境干扰超声波传感器对光滑的、角度倾斜的、吸音的如布料表面反射效果差异很大。确保被测物体表面平整且正对传感器。远离风扇、电机等产生空气扰动的设备。供电噪声如果使用开发板的3.3V同时为屏幕和传感器供电在大屏幕刷新时可能导致电压微小波动。尝试在HC-SR04的Vcc和GND之间并联一个10uF-100uF的电解电容以稳定电源。软件滤波不足增加中值滤波的采样次数例如从5次增加到7次或9次。虽然会降低响应速度但稳定性会大幅提升。6.3 OLED屏幕不显示或显示乱码现象屏幕一片漆黑、部分显示或显示奇怪的字符。排查步骤确认电源和I2C地址首先检查VCC和GND。然后运行一段I2C扫描程序确认OLED的地址是否正确通常是0x3C。import machine i2c machine.SoftI2C(sclmachine.Pin(22), sdamachine.Pin(21)) devices i2c.scan() print(“I2C设备地址”, devices)检查上拉电阻I2C总线需要上拉电阻通常4.7kΩ-10kΩ到3.3V。有些OLED模块已内置有些没有。如果模块没有内置需要在SDA和SCL线上各加一个上拉电阻。检查初始化代码确认SSD1306_I2C初始化时传入的宽度、高度参数与你的屏幕匹配常见的是128,64。检查show()函数调用oled.text()后必须执行oled.show()内容才会真正更新到屏幕。6.4 MicroPython程序运行内存不足现象在添加更多功能后程序运行出现MemoryError。优化策略使用.mpy库文件将.py库文件预编译成.mpy格式再上传可以节省RAM和闪存空间。及时释放大对象在函数内部创建的大列表、字符串在函数结束后应尽快删除或置为None。冻结模块将常用的、稳定的库直接编译进MicroPython固件这需要重新编译固件但能极大节省运行时内存。6.5 测量距离与实际距离存在固定偏差现象测量值总是比卷尺测量的实际值大或小一个固定值如2cm。原因与校准这可能是传感器本身的物理误差或者声速常数不精确。我们可以在代码中引入一个校准偏移量。找一个已知精确距离例如50.0cm的物体进行测量得到传感器读数例如52.0cm。那么偏移量就是实际值减去测量值50.0 - 52.0 -2.0cm。在最终返回距离前加上这个偏移量calibrated_distance raw_distance offset。对于更高要求可以在多个距离点进行测量拟合出一个线性校正公式。在整个项目搭建和调试过程中最深刻的体会是嵌入式开发是软件和硬件的紧密结合。一个看似是软件的问题根源可能在硬件连接上。养成系统性的排查习惯——从电源开始到信号连接再到代码逻辑——能节省大量时间。另外MicroPython的动态特性使得交互式调试非常方便多利用print()函数输出中间变量值是定位问题最快的方法。最后不要害怕修改和试验很多最优参数如滤波次数、测量间隔都是在具体的硬件环境和应用场景下试出来的。