Arduino UNO 代码下载
带 0.96 寸 OLED 显示,适合 UNO / Nano 教学演示,已烧录验证。
下载 Arduino UNO ZIP接线与代码参考手册 —— 测距原理、ESP32 / STM32 / 树莓派三种主控的接线图,以及可直接使用的示例代码。
Download
三个平台的代码已经按文件夹打包,客户下载 ZIP 后解压,打开对应工程即可。ESP32 2.8 黄屏请用带 _tft 的版本。
带 0.96 寸 OLED 显示,适合 UNO / Nano 教学演示,已烧录验证。
下载 Arduino UNO ZIP真正驱动 2.8 黄屏 TFT 显示的版本。不要使用过渡的纯串口版,否则屏幕不会亮。
下载 ESP32 黄屏 ZIP蓝丸 + 0.96 寸 OLED 版本,包含 platformio.ini 和 src/main.cpp,适合 PlatformIO 打开。
不接 OLED 时用这个精简版本,方便先验证 Trig/Echo 测距逻辑。
下载 STM32 串口版包含 Arduino UNO、ESP32 2.8 黄屏、STM32 OLED、STM32 串口版,适合客服一次发给客户。
下载全部代码 ZIPHC-SR04 是最常见的超声波测距模块,原理和蝙蝠回声定位一样:主控给它一个"喊话"信号,它发射一串 40kHz 的超声波,碰到前方物体反射回来,模块把"从喊话到听到回声"这段时间转换成一个电平脉冲告诉主控,主控再用声速换算成距离。整个模块只有 4 根针,不需要额外的库或者复杂配置,是最适合入门的测距传感器。
全系列还有 AJ-SR04M / JSN-SR04T(防水一体化外壳,适合车外或潮湿环境)等变种,接口协议和本文一致,选型时按外壳/功耗需求换一下即可,代码不用改。
同一款外观(双探头蓝板)实际有三种芯片方案,淘宝同一个链接的"颜色分类"里经常混着卖,选购/接线前建议对照实物丝印确认到底是哪种:
| 方案 | 工作电压 | 尺寸 | 说明 |
|---|---|---|---|
| 双芯片(标准版) | 5V | 45.2×20×14.5mm | 最常见的版本,本文默认按这个写 |
| 双芯片(宽电压版) | 3.3–5V | 45.1×20×14.5mm | 背面丝印通常还带 Echo/Tx/SDA、Trig/Rx/SCL 字样,除了普通 Trig/Echo 模式,部分还支持 I2C/UART 等替代协议(本文只用最基础的 Trig/Echo 模式,其它模式不在本文范围);第 5 节 ESP32-2432S028 专项说明里推荐买的就是这个方案 |
| 单芯片(CS100A 方案) | 3–5.5V | 45.2×20×14.5mm | 用 CS100A 单颗芯片实现,同样是宽电压,背面丝印会印 "CS100A Inside" |
三种方案的 Trig/Echo 基础用法和引脚定义完全一致,本文的接线图/代码对三种都适用;区别只在供电电压范围,3.3V 主控(ESP32/树莓派/STM32)优先选后两种宽电压方案,可以省掉第 4 节的分压电阻(前提仍是第 4 节反复强调的:接上后自己用万用表确认 Echo 电压)。
| 参数 | 数值 |
|---|---|
| 工作电压 | DC 5V |
| 工作电流 | 约 15mA |
| 工作频率 | 40kHz |
| 测距范围 | 2cm – 400cm(近距离 <2cm 属于盲区,测不到) |
| 测量精度 | 约 ±0.3cm |
| 探测角度 | 不大于 15°(锥形范围,超出角度容易漏检) |
| 触发信号 | Trig 引脚给 ≥10µs 的高电平脉冲 |
| 输出信号 | Echo 引脚输出与距离成正比的高电平脉冲(5V TTL) |
| 引脚 | 类别 | 说明 |
|---|---|---|
| VCC | 电源 | 5V 供电,模块内部没有稳压电路,必须是 5V,不能用 3.3V 供电(测不出来或测量严重不准)。 |
| Trig | 输入 | 触发引脚,主控给一个 ≥10µs 高电平脉冲,模块就发一串超声波。 |
| Echo | 输出 | 回声引脚,模块用它输出一个高电平脉冲,脉冲宽度(时间)代表测到的距离。这个引脚输出的是 5V 电平,接 3.3V 逻辑的主控前务必看第 4 节。 |
| GND | 地 | 公共地。 |
整个测距过程只有三步:主控拉高 Trig 至少 10µs → 模块自动发出 8 个 40kHz 方波 → 模块把 Echo 拉高,一直等到收到反射回波才拉低。Echo 保持高电平的时间,就是超声波"跑一个来回"的时间。
距离和 Echo 高电平时间的换算,最常用的是这个近似公式(声速取 340 m/s,除以 2 是因为时间算的是"去 + 回"两段路程):
距离(cm) = Echo高电平时间(µs) ÷ 58
等价写法:距离(cm) = 时间(µs) × 0.017。如果追求更高精度,可以把 340 m/s 换成按温度修正的声速 331.4 + 0.606 × 温度(℃),一般教学/避障场景用不到这一步。
HC-SR04 是 5V 模块,Echo 引脚输出的高电平是 5V。ESP32、树莓派、以及绝大多数 STM32 型号的 GPIO 只能承受 3.3V 左右,5V 直接怼进去超出芯片规格,短期内可能看着正常,长期用容易把这个引脚"作旧"甚至直接损坏。网上不少教程图省事直接硬接,能跑不代表安全,本文档统一按规范接法来。
做法:Echo 出来后先过一个分压电阻,再进主控的 GPIO;Trig 是主控发向模块,3.3V 电平模块也能正常识别为高电平,不需要处理。
后面第 5–7 节的接线图里,Echo 这一路统一按"过分压电阻"画。分压电阻是最省钱的方案,如果手头有现成的双向电平转换模块(logic level converter),效果一样,接法更简单,二选一即可。
ESP32 这块比 STM32 省事很多——板子自带 USB 转串口,一根 USB 线就能烧录+供电+看串口,不需要额外的编程器或 USB转TTL 模块。分两种情况:普通 DevKit(下面直接给接线)和 ESP32-2432S028 2.8寸黄屏/CYD(专用接口,见下方 ✅已验证 的详细说明)。
USB 线接电脑,Arduino IDE 里开发板选对应的 ESP32 型号,端口选对应 COM 口,点上传。烧录完打开串口监视器(波特率115200),应该能看到持续打印的 Distance: xx cm。代码见第9节。
这块板子不是普通 DevKit,板上除了 ESP32 本体,屏幕/触摸已经占用了一大票 GPIO,只留了一个 CN1 4 针外接口:GND - IO22 - IO27 - 3V3(之前接 BNO085/VL53L5CX 这些 I2C 传感器时,IO22/IO27 是当 SCL/SDA 用的)。HC-SR04 不走 I2C,但这两个引脚本身就是普通 GPIO,代码里改成 pinMode() + digitalWrite()/digitalRead(),不要调用 Wire.begin(),接口照样能用:
2026-07-13 实物验证通过:Trig=IO22、Echo=IO27、VCC=CN1 3V3、GND=CN1 GND,串口实测能连续读出正确变化的距离值(约50–99cm 随物体移动实时变化)。厂家原厂原理图里 CN1 那张图纸只标了 IO27,没有 IO22,和这次实测、以及 01535 项目里三套已验证程序的接法对不上——这次以实测为准,原理图那份大概率是旧版本或者对不上这批板子的丝印,不代表 IO22 这条线接错了。
| HC-SR04 | CN1 | 说明 |
|---|---|---|
| GND | GND | 公共地 |
| VCC | 3V3 | ⚠️ CN1 只有 3.3V,没有 5V,见下方说明 |
| Trig | IO22 | 当普通输出脚用,不初始化 I2C |
| Echo | IO27 | 当普通输入脚用,见下方分压说明 |
代码不用换成 I2C 库,第 9 节 ESP32 代码原样能用,只要把 TRIG_PIN 改成 22、ECHO_PIN 改成 27 就行,其它逻辑不变。
CN1 不出 5V,标准 5V-only 版 HC-SR04 没法只靠这个口供电——这次实测用的是第 1 节表格里的"双芯片宽电压版"(3.3–5V),VCC 接 CN1 的 3V3、Echo 不加分压直接接 IO27,功能完全正常(能连续测出正确变化的距离)。仍然没有拿万用表实测过 Echo 高电平的具体电压值——"能正常测距"不等于"电压绝对安全",追求稳妥的话还是建议实测一下 Echo 电压,确认在 3.3V 附近再长期使用。如果你买的是标准 5V-only 版(第1节表格第一行),不要照抄这一节的接法,CN1 供电不够,需要另找 5V 电源。
STM32 是本文四个平台里步骤最多的一个——板子本身不带 USB 口,需要额外的编程器和串口模块才能烧录/调试。这一节把"准备什么东西"到"烧录成功+验证"的每一步都拆开写,照着顺序做,中途遇到的坑本节也都记录了原因和解法。以 STM32F103C8T6 最小系统板(俗称"蓝丸",实际板型很多,有些还带独立的 4 针 SWD 调试口,跟经典两排排针的蓝丸不完全一样,接线前对照自己板子的丝印确认)为例。
板上一般有 BOOT0(有些板子还有 BOOT1)跳线帽。只要是用 ST-Link 走 SWD 烧录(本节的接法),BOOT0 拨到 0(靠 GND 那一侧)就行,不需要去动它——SWD 烧录不经过芯片自带的系统引导程序,跟 BOOT0 无关。BOOT0 拨到 1 只在"不用 ST-Link,改用 USB 转串口走芯片内置串口 Bootloader 烧录"这种方案时才需要,本节不涉及,全程保持 BOOT0=0(有 BOOT1 的话也一起拨 0)即可。
把第10节的 platformio.ini 和 src/main.cpp 内容贴进步骤1建好的项目里,确认 ST-Link 接好(步骤3)、BOOT 跳线正常(步骤4),点 VS Code 底部状态栏的 →(Upload)图标,或者在终端跑 pio run -t upload。看到 SUCCESS 和 Verified OK 字样就是烧录成功了。
ST-Link 和 CP2102 各自的 USB 头只负责给它们自己供电,不会自动给 STM32 板子供电——板子要通电,必须有一根独立的 3.3V 线接到板子的 3V3 脚。这根线只能接一次:本节接法是让 ST-Link 的 3.3V 供电,CP2102 的 VCC 就必须悬空不接;如果两边都接了 3.3V,两个电源会在同一条轨上"打架",轻则供电不稳,重则发烫损坏元件。两个 USB 头可以同时插着电脑(ST-Link 管供电+烧录,CP2102 只管收发数据),但供电的线只能有一条。
按上面步骤1~7走完,烧录成功、串口能连续读出正确变化的距离值、OLED 也能正常显示,有实拍视频为证。调试过程中实际遇到的坑,按出现频率排列:
树莓派 40 针排针上物理引脚 2/4 是 5V,物理引脚 6 是 GND,本文用 BCM 编号对应 GPIO23 / GPIO24。树莓派的 GPIO 完全不能超过 3.3V,Echo 分压这一步在树莓派上是必须做的,不是"建议"。⚠ 这一节目前只是接线原理整理,手头没有树莓派实物,没有真正烧录测试过——跟 ESP32/STM32/Arduino 三节不一样,接线前建议自己先小范围验证一下。
Arduino Uno 本身就是 5V 逻辑,和 HC-SR04 的 5V TTL 电平天然匹配——这是四个平台里唯一不用给 Echo 加分压电阻的,接线最省事,也不需要额外的编程器/USB转TTL模块(Uno 自带USB口),很适合当"第一次接线"的入门组合。屏幕用最常见的 0.96 寸 SSD1306 OLED(I2C 接口,4 根线),用来把测到的距离数字直接显示出来,不用接电脑看串口。
USB 线接电脑,Arduino IDE 里开发板选 Arduino Uno,端口选对应 COM 口,点上传。代码见第12节。接了 OLED 的话,屏幕应显示"HC-SR04 Distance"和实时距离数字;没接 OLED 就打开串口监视器(波特率115200)看数字。
用 Arduino IDE 烧录,开发板选对应的 ESP32 型号即可,不需要额外安装库,pulseIn() 是 Arduino 核心自带函数。
// HC-SR04 · ESP32 · Trig=GPIO5, Echo=GPIO18(经分压) #define TRIG_PIN 5 #define ECHO_PIN 18 void setup() { Serial.begin(115200); pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); digitalWrite(TRIG_PIN, LOW); } float getDistanceCM() { digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); // ≥10us 触发脉冲 digitalWrite(TRIG_PIN, LOW); // 30ms 超时:约等于5m的往返时间上限,避免物体太远时死等 unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000); if (duration == 0) return -1; // 没测到(超出量程/无反射) return duration / 58.0; } void loop() { float d = getDistanceCM(); if (d < 0) { Serial.println("超出量程或无回波"); } else { Serial.print("距离: "); Serial.print(d, 1); Serial.println(" cm"); } delay(300); }
这一节改用 PlatformIO + STM32duino(STM32 官方维护的 Arduino 兼容核心),代码风格和前面 ESP32/Arduino 完全一样(pinMode/digitalWrite/pulseIn),不用再手搭 CubeMX 定时器、也不用切换到 C 语言的 HAL 写法。选这条路是因为它是本文档里唯一实际编译烧录、在真实硬件上测过的方案——CubeMX/HAL 那套理论上可行,但没有实测硬件可以验证,为了保证文档"代码要和实物一致",这里只保留验证过的版本。开发环境怎么装、接线怎么接、烧录完怎么验证,第 6 节按步骤1~8完整写了一遍,这里只放代码本身。
Windows 下这套工具链(GCC ARM 编译器的链接器)遇到中文路径会在最后链接这一步报错(类似 Illegal byte sequence/打不开 .map 文件),前面每个源文件单独编译都不会报错,只有链接会炸。项目文件夹本身、以及它所在的上级目录,都不能含有中文字符,用纯英文路径(比如 D:\projects\hcsr04_stm32\)建项目就没有这个问题。
[env:bluepill_f103c8]
platform = ststm32
board = bluepill_f103c8
framework = arduino
upload_protocol = stlink
monitor_speed = 115200
lib_deps =
adafruit/Adafruit SSD1306@^2.5.13
adafruit/Adafruit GFX Library@^1.11.11
最后两行 lib_deps 是给第 6 节那块可选的 0.96 寸 OLED 用的,PlatformIO 会自动下载;如果不接 OLED,删掉这两行也能正常编译烧录。
// HC-SR04 · STM32F103C8T6 · PlatformIO/STM32duino · Trig=PA0, Echo=PA1(经分压) // 串口通过CP2102接PA9(TX)/PA10(RX) · OLED(SSD1306,I2C,0x3C)可选: SCL=PB6, SDA=PB7 #include <Arduino.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define TRIG_PIN PA0 #define ECHO_PIN PA1 #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 HardwareSerial DebugSerial(PA10, PA9); // RX, TX —— 给 CP2102 看的调试串口 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); void setup() { DebugSerial.begin(115200); pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); digitalWrite(TRIG_PIN, LOW); Wire.begin(); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); display.setTextColor(SSD1306_WHITE); } float getDistanceCM() { digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIG_PIN, LOW); unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000); if (duration == 0) return -1; return duration / 58.0; } void loop() { float d = getDistanceCM(); if (d < 0) { DebugSerial.println("No echo / out of range"); } else { DebugSerial.print("Distance: "); DebugSerial.print(d, 1); DebugSerial.println(" cm"); } display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.println("HC-SR04 Distance"); display.setTextSize(3); display.setCursor(10, 24); if (d < 0) { display.print("--.-"); } else { display.print(d, 1); } display.setTextSize(1); display.setCursor(100, 40); display.println("cm"); display.display(); delay(300); }
串口打印统一用英文(Distance: xx cm),不是不支持中文——而是不同串口调试工具的默认解码编码不一样(有的按 UTF-8,有的按 GBK),中文很容易在某些工具里显示成乱码,改英文就没有这个烦恼,跟接线、跟数据本身对不对没关系。
不需要 OLED 的话,把 Wire.h/Adafruit_* 相关的三行 #include、display 变量定义、Wire.begin()/display.begin() 这几行、以及 loop() 里画屏幕的那一段全部删掉,只留 DebugSerial 相关的部分,就是最精简的串口版。
树莓派 OS 通常自带 RPi.GPIO 库,没有的话 pip install RPi.GPIO 装一下即可,然后 python3 直接运行。
import RPi.GPIO as GPIO import time TRIG = 23 # 物理引脚16 ECHO = 24 # 物理引脚18,经分压电阻接入 GPIO.setmode(GPIO.BCM) GPIO.setup(TRIG, GPIO.OUT) GPIO.setup(ECHO, GPIO.IN) GPIO.output(TRIG, False) time.sleep(0.5) # 模块上电稳定 def get_distance_cm(): GPIO.output(TRIG, True) time.sleep(0.00001) # 10us 触发脉冲 GPIO.output(TRIG, False) timeout = time.time() + 0.03 while GPIO.input(ECHO) == 0: start = time.time() if start > timeout: return None timeout = time.time() + 0.03 while GPIO.input(ECHO) == 1: end = time.time() if end > timeout: return None duration = end - start return round((duration * 34300) / 2, 1) # 声速34300cm/s,除2是往返路程 try: while True: d = get_distance_cm() print(f"距离: {d} cm" if d is not None else "超出量程或无回波") time.sleep(0.3) except KeyboardInterrupt: GPIO.cleanup()
需要先在库管理器里装 Adafruit SSD1306 和它依赖的 Adafruit GFX Library。大多数 0.96 寸 OLED 的 I2C 地址是 0x3C,少数是 0x3D,屏幕不亮先试试换这个地址。本页代码已在实物上烧录验证通过(Arduino Uno + 4 针 I2C OLED,实测效果见第 15 节实拍图)。
市面上外观几乎一样的 I2C OLED,驱动芯片常见的有两种:SSD1306 和 SH1106(丝印经常是 GND VDD SCK SDA 四针,光看外观和针脚名分辨不出是哪颗芯片)。两者命令集不完全一样,装错库常见表现是屏幕完全不亮、或者画面错位/重影。本页代码用的是 Adafruit_SSD1306 库——如果你的模块实际是 SH1106,先按这页代码试一下(部分 SH1106 模块对 SSD1306 库也有较好的兼容性,实测能正常显示),不行的话换成 Adafruit_SH110X 库(同样是 Adafruit 出的,装法一样),把头文件换成 #include <Adafruit_SH110X.h>,对象声明换成 Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);,display.begin(SSD1306_SWITCHCAPVCC, 0x3C) 换成 display.begin(0x3C, true),其余代码不用改(SSD1306_WHITE 换成 SH110X_WHITE)。
// HC-SR04 · Arduino Uno · Trig=D9,Echo=D8 · OLED(SSD1306, I2C, 0x3C) #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define TRIG_PIN 9 #define ECHO_PIN 8 #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); void setup() { pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); digitalWrite(TRIG_PIN, LOW); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); display.setTextColor(SSD1306_WHITE); } float getDistanceCM() { digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIG_PIN, LOW); unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000); if (duration == 0) return -1; return duration / 58.0; } void loop() { float d = getDistanceCM(); display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.println("HC-SR04 Distance"); display.setTextSize(3); display.setCursor(10, 24); if (d < 0) { display.print("--.-"); } else { display.print(d, 1); } display.setTextSize(1); display.setCursor(100, 40); display.println("cm"); display.display(); delay(300); }
多次测量取平均值或中位数再显示,能滤掉大部分噪声;如果现场有两个以上超声波模块同时工作,会互相收到对方的回波造成串扰,简单的办法是把各个模块的触发时间错开(比如轮流触发,而不是同时触发)。
检查 Echo 有没有漏接第 4 节说的分压电阻——5V 长期直接打到 3.3V 的 GPIO 上,即使当下测着正常,也在透支芯片寿命。
这是模块物理盲区,属于正常现象,不是接线或代码问题,2cm 以内本来就测不了。
90% 是 I2C 地址不对:先试 0x3C,不行换 0x3D;剩下的大概率是 SCL/SDA 接反,或者 OLED 和 HC-SR04 同时占用面包板同一路电源导致压降太大,分开供电试试。
检查驱动芯片是不是搞反了——见第 12 节的驱动芯片提醒,SSD1306 和 SH1106 长得几乎一样但库不通用。装错库常见表现就是"完全不亮"或者"画面错位/花屏",两个库都装上分别试一下,哪个能正常显示就用哪个。
面向完全没接触过传感器、没接过线、不会写代码的观众,讲解顺序按"是什么 → 干什么用 → 怎么接 → 怎么跑起来 → 效果展示"来,专业名词尽量换成生活类比,具体电阻阻值、电压这些数字可以在画面里标出来,但口播不用逐字念参数。
下面是淘宝卖家实拍图,用来对照实物长相和常见配件,以及两段接线原理动图(本节是文档里少数允许用图片/动图的地方,其余章节按要求保持纯文本);Arduino + OLED 组合已经实物验证并拍了真实效果图,下方还留了两个位置,等 ESP32/STM32/树莓派也实物验证后陆续补上。