资讯动态

Arduino超声波雷达DIY:用HC-SR04与WS2812B打造桌面环境感知装置

发布时间:2026/8/20 7:35:06 来源:尧图企业网站定制
1. 项目缘起一个极简雷达的诞生最近在整理工作室的杂物时翻出了几个闲置的HC-SR04超声波传感器和一堆五颜六色的LED灯珠。看着它们我脑子里突然冒出一个想法能不能用最简单、最廉价的材料做一个能“看见”物体移动的“雷达”不是那种复杂的毫米波雷达而是用超声波传感器扫描再用LED灯带实时显示物体距离和状态的桌面小装置。这个想法听起来有点“极客”但实现起来其实门槛很低核心就是Arduino、超声波传感器和WS2812B LED灯带也就是常说的NeoPixel。我给它起了个名字叫“极简短距雷达状态灯”。这个项目的核心逻辑非常直接让超声波传感器像雷达一样旋转扫描前方区域测量不同角度下的距离数据然后根据距离的远近控制一圈LED灯带显示出不同的颜色或亮度。比如物体越近对应的LED灯就显示为醒目的红色物体在中等距离显示为黄色物体较远或没有物体则显示为绿色或熄灭。这样一来你不需要看串口监视器里冰冷的数字扫一眼LED灯环就能直观地“看到”前方物体的轮廓和距离变化非常适合作为桌面上的一个环境感知小工具或者作为创客入门传感器融合的一个有趣案例。整个系统搭建起来硬件成本可能不到一百元软件上主要依赖Arduino IDE和Processing用于可选的上位机可视化。无论你是刚接触Arduino的新手想找一个综合性的练手项目还是老手想快速验证一个传感器可视化方案这个“极简雷达”都能给你带来不少乐趣和启发。接下来我就把从硬件连接到代码编写再到调试优化的完整过程以及我踩过的那些坑毫无保留地分享给你。2. 硬件选型与连接构建系统的骨架要实现一个旋转扫描的雷达我们需要让超声波传感器动起来。最经济实惠的方案就是使用一个微型舵机SG90来带动传感器进行左右摆动。整个系统的硬件清单如下主控板Arduino Uno R3。经典、稳定、资料多是新手和老手的共同选择。当然你也可以用Nano、ESP32如ESP32 Dev Module等但Uno的引脚布局最清晰便于理解。测距传感器HC-SR04超声波模块。这是创客领域的明星产品价格低廉使用简单测量范围2cm-400cm完全满足桌面雷达的需求。执行器SG90 9g舵机。用于带动传感器旋转。注意要买180度范围的我们不需要360度连续旋转。显示单元WS2812B LED灯环例如16位或24位。我强烈推荐使用灯环而不是灯条因为它天然形成一个圆形非常适合模拟雷达的扫描显示。WS2812B只需要一根数据线就能控制上百个灯极大地简化了布线。其他面包板、杜邦线公对公、公对母、一个5V/2A以上的外部电源单独给LED灯环供电很重要、以及固定传感器和舵机的结构件可以用乐高积木、3D打印件或者甚至硬纸板临时搭建。硬件连接示意图与原理连接是第一步理解为什么这么连接更能避免错误。下图展示了核心的连接逻辑Arduino Uno --- 外部世界 | |--- 5V, GND --- SG90舵机红线-5V 棕线-GND |--- Pin 9 (PWM) - SG90信号线橙线/黄线 | |--- 5V, GND --- HC-SR04 (VCC, GND) |--- Pin 10 (Trig) - HC-SR04 Trig |--- Pin 11 (Echo) - HC-SR04 Echo | |--- 5V, GND --- WS2812B灯环5V, GND |--- Pin 6 (Digital) - WS2812B Data In | |--- GND --- 外部电源GND共地关键连接细节与避坑指南舵机供电分离强烈建议SG90在工作时特别是堵转或启动瞬间电流可能超过500mA。如果和Arduino共用USB的5V极易导致Arduino复位或电脑USB端口保护。虽然最小系统可以直连但为了稳定最好通过面包板从外部5V电源取电。信号线PWM必须接Arduino的数字引脚如9号因为舵机角度是由来自Arduino的脉冲宽度信号控制的。HC-SR04的连接Trig触发和Echo回响引脚接任意数字IO口即可。我选择10和11号引脚。注意有些版本的HC-SR04是3.3V逻辑电平但大多数5V版本可以直接与Arduino 5V系统兼容。如果不确定可以在Echo脚和Arduino引脚之间加一个1kΩ的电阻起到限流保护作用。WS2812B灯环的供电是重中之重这是本项目最容易出问题的地方。一个LED全白亮时电流可达60mA。16个灯就是960mA瞬间电流轻松超过1A。Arduino Uno的5V引脚最大只能提供约500mA电流绝对不足以驱动灯环必须使用外部5V电源单独为灯环供电。正确接法外部电源的正极5V接灯环的5V负极GND接灯环的GND同时务必与Arduino的GND相连共地。数据线Din接Arduino的某个数字引脚如6号。共地确保了Arduino和灯环有相同的电压参考点数据信号才能被正确识别。错误接法只接外部电源和灯环不与Arduino共地。这会导致数据通信混乱灯环显示异常或完全不亮。结构搭建用热熔胶或螺丝将HC-SR04固定在舵机的舵盘上。确保传感器面朝前方并且舵机旋转时不会碰到任何线缆。你可以将整个结构竖立起来模拟雷达的扫描姿态。3. 核心代码解析让雷达“活”起来硬件连接好后就需要编写Arduino代码Sketch来协调舵机、传感器和灯环。我们将代码分成几个核心功能块来理解。3.1 库文件引入与全局变量定义首先我们需要引入控制WS2812B的库。Adafruit NeoPixel库是最佳选择可以通过Arduino IDE的库管理器直接搜索安装。#include Adafruit_NeoPixel.h // 注意舵机控制使用Arduino内置的Servo库无需额外引入但需在代码中声明。 // 定义引脚 const int trigPin 10; const int echoPin 11; const int servoPin 9; const int ledPin 6; const int numLeds 16; // 根据你的灯环LED数量修改 // 创建NeoPixel对象 Adafruit_NeoPixel strip Adafruit_NeoPixel(numLeds, ledPin, NEO_GRB NEO_KHZ800); // 创建舵机对象 Servo myServo; // 全局变量 int angle 0; // 当前舵机角度 int increment 2; // 每次扫描角度增量决定扫描速度和平滑度 bool scanningDirection true; // true为从左向右扫描为什么选择NEO_GRB NEO_KHZ800这是WS2812B灯珠最常见的颜色顺序和通信频率。如果你的灯环颜色显示不对比如红色命令显示成了绿色可能是颜色顺序不对可以尝试NEO_RGB、NEO_BRG等其他参数。3.2 初始化设置setup函数在setup()函数中我们需要初始化所有用到的硬件模块。void setup() { Serial.begin(9600); // 初始化串口用于调试输出距离数据 pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); myServo.attach(servoPin); // 将舵机对象绑定到控制引脚 myServo.write(90); // 初始位置设为中间90度 delay(1000); // 等待舵机就位 strip.begin(); // 初始化NeoPixel库 strip.show(); // 将所有LED初始化为熄灭状态 strip.setBrightness(50); // 设置亮度0-255建议开始时设低一点避免过亮刺眼 }注意strip.begin()和strip.show()必须调用。begin()初始化底层通信show()才会将颜色数据真正发送到灯带。你可以在setBrightness()里调整亮度特别是在室内亮度30-80通常就够了太亮反而看不清梯度变化。3.3 超声波测距函数这是一个标准且可靠的HC-SR04测距函数返回以厘米为单位的距离。long getDistance() { digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); // 发出10微秒的高电平脉冲触发测距 digitalWrite(trigPin, LOW); long duration pulseIn(echoPin, HIGH); // 读取高电平持续时间微秒 // 声音在空气中的速度约为340米/秒即0.034厘米/微秒。 // 距离 (持续时间 * 声速) / 2 因为声音是往返 long distance duration * 0.034 / 2; // 过滤明显错误的数据HC-SR04有效范围约2-400cm if (distance 2 || distance 400) { return -1; // 返回-1表示无效距离 } return distance; }关键点解析pulseIn(echoPin, HIGH)会等待echoPin变为高电平并开始计时直到它变回低电平。这个时间就是超声波从发射到返回的总时间。计算距离时除以2是因为我们只需要单程距离。过滤无效数据非常重要可以避免因传感器误触发导致的距离值剧烈跳变影响LED显示的稳定性。3.4 距离映射到LED颜色与位置这是本项目逻辑的核心。我们需要做两件事1. 根据距离决定颜色2. 根据当前雷达角度决定点亮灯环上的哪个LED。// 函数根据距离计算颜色 uint32_t getColorByDistance(long dist) { if (dist -1) { return strip.Color(0, 0, 0); // 无效数据熄灭 } // 定义距离阈值单位厘米可根据你的扫描范围调整 int closeThreshold 20; int farThreshold 100; if (dist closeThreshold) { // 近距离红色且越近越亮 int intensity map(dist, 2, closeThreshold, 255, 50); return strip.Color(intensity, 0, 0); } else if (dist farThreshold) { // 中距离黄色红绿越近黄色越偏红越远越偏绿 int red map(dist, closeThreshold, farThreshold, 255, 100); int green map(dist, closeThreshold, farThreshold, 100, 255); return strip.Color(red, green, 0); } else { // 远距离绿色且越远越暗 int intensity map(dist, farThreshold, 400, 255, 10); return strip.Color(0, intensity, 0); } } // 函数将舵机角度映射到LED索引 int getLedIndexByAngle(int servoAngle) { // 假设舵机扫描范围是30度到150度共120度对应灯环上所有LED0到15 // 使用map函数进行线性映射 int index map(servoAngle, 30, 150, 0, numLeds - 1); // 确保索引在有效范围内 index constrain(index, 0, numLeds - 1); return index; }颜色映射逻辑这里使用了map()函数它是一个非常实用的线性映射工具。例如map(dist, 2, closeThreshold, 255, 50)意味着当距离dist从2厘米变化到closeThreshold20厘米时输出的红色强度从255线性减少到50。这样就能实现“越近越红越远越绿”的平滑过渡效果比简单的分段阈值判断看起来更“雷达”。角度映射逻辑我们的舵机可能只在30度到150度之间扫描避免机械结构干涉。map(servoAngle, 30, 150, 0, 15)将角度区间映射到LED索引区间。constrain()函数确保计算出的索引不会超出灯环范围。3.5 主循环loop函数与扫描逻辑最后在loop()函数中将所有功能串联起来实现持续的扫描-测量-显示循环。void loop() { // 1. 更新舵机角度 if (scanningDirection) { angle increment; if (angle 150) { // 右边界 scanningDirection false; } } else { angle - increment; if (angle 30) { // 左边界 scanningDirection true; } } myServo.write(angle); delay(15); // 给舵机一点时间移动到指定位置这个延迟决定了扫描速度 // 2. 在当前角度下测量距离 long distance getDistance(); // 3. 根据距离和角度更新LED灯环 int ledIndex getLedIndexByAngle(angle); uint32_t color getColorByDistance(distance); // 为了实现“扫描线”效果可以先清除所有LED再点亮当前角度的LED // 但更酷的效果是让之前的点亮痕迹慢慢消失模拟雷达PPI显示 // 方法A清屏再画简单 // strip.clear(); // strip.setPixelColor(ledIndex, color); // strip.show(); // 方法B渐隐效果更接近雷达 fadeAllLeds(2); // 每个循环将所有LED亮度减弱2 strip.setPixelColor(ledIndex, color); strip.show(); // 4. 可选通过串口输出角度和距离用于调试或上位机 Serial.print(angle); Serial.print(,); Serial.println(distance); }扫描逻辑详解我们使用一个布尔变量scanningDirection来控制扫描方向。角度angle根据方向和增量increment不断变化到达边界后反向。delay(15)是关键它控制了舵机运动速度和测距采样率。太短如10ms可能导致舵机还没到位就测量数据不准太长如30ms则扫描显得卡顿。LED显示策略注释中提供了两种显示方法。方法A每次只亮一个灯简单直接。方法B是我推荐的它通过一个fadeAllLeds()函数需要自己实现见下文让所有LED的亮度每帧都衰减一点这样新的点亮处最亮之前点亮的地方会留下一条逐渐变暗的“尾巴”非常像传统雷达显示屏上的余辉视觉效果极佳。// 渐隐函数实现 void fadeAllLeds(int fadeValue) { for (int i 0; i numLeds; i) { uint32_t oldColor strip.getPixelColor(i); uint8_t r (oldColor 16) 0xFF; uint8_t g (oldColor 8) 0xFF; uint8_t b oldColor 0xFF; r (r fadeValue) ? r - fadeValue : 0; g (g fadeValue) ? g - fadeValue : 0; b (b fadeValue) ? b - fadeValue : 0; strip.setPixelColor(i, r, g, b); } }4. 上位机可视化用Processing绘制雷达图如果你觉得LED灯环的显示还不够直观或者想把数据记录下来那么用Processing编写一个简单的上位机程序是绝佳的选择。Processing与Arduino天生友好可以很方便地通过串口通信接收数据并绘制图形。Arduino端只需要保持上面loop()函数中最后的串口打印语句即可Serial.print(angle); Serial.print(,); Serial.println(distance);。它按“角度,距离”的格式发送数据。Processing端代码核心思路如下导入串口库import processing.serial.*;建立串口连接打开对应Arduino的串口如COM3或/dev/ttyUSB0波特率设置为9600。解析数据在serialEvent()回调函数中读取一行数据按逗号分割解析出角度和距离。坐标转换将极坐标角度距离转换为直角坐标x, y以便在屏幕上绘制。公式为x 中心点X cos(弧度角) * 距离缩放值y 中心点Y sin(弧度角) * 距离缩放值。注意Processing的cos()和sin()函数使用弧度制需要将角度转换为弧度弧度 角度 * PI / 180.0。绘制背景画一个半圆代表扫描范围。将历史点存储在一个数组中每帧绘制并使其逐渐淡出通过降低透明度实现形成余辉。将当前最新测量到的点用醒目的颜色如红色绘制出来。一个简化的Processing绘制核心片段如下// 在draw()函数中 background(0); // 黑色背景 translate(width/2, height/2); // 将坐标原点移到画面中心 // 绘制距离刻度圈 noFill(); stroke(100); for(int i1; i4; i) { ellipse(0, 0, i*50, i*50); // 假设每圈代表50像素的距离 } // 绘制历史轨迹点余辉 for (int i 0; i trailX.size(); i) { float px trailX.get(i); float py trailY.get(i); float alpha map(i, 0, trailX.size(), 50, 0); // 越旧的点越透明 stroke(0, 255, 0, alpha); // 绿色余辉 point(px, py); } // 绘制当前最新点 if (currentDist 0) { float rad radians(currentAngle - 90); // 调整角度偏移使0度朝上 float x cos(rad) * currentDist * scaleFactor; float y sin(rad) * currentDist * scaleFactor; stroke(255, 0, 0); // 红色当前点 strokeWeight(8); point(x, y); // 将当前点加入历史轨迹 trailX.add(x); trailY.add(y); // 限制轨迹长度避免数组过大 if (trailX.size() 200) { trailX.remove(0); trailY.remove(0); } }通过Processing你就能在电脑屏幕上看到一个动态更新的、带余辉效果的雷达扫描图比LED灯环显示的信息更丰富、更精确。5. 调试、优化与进阶玩法将代码上传到Arduino连接好硬件后你可能会遇到一些问题。这里分享一些我调试过程中的心得和进阶优化思路。5.1 常见问题排查灯环完全不亮或乱闪首要检查供电99%的问题源于供电不足或未共地。确保使用了独立的外部5V/2A以上电源给灯环供电并且电源的GND与Arduino的GND已经连接。检查数据线连接数据线是否接在了正确的Arduino引脚代码中定义的ledPin是否接触不良检查库和引脚定义确认安装了Adafruit NeoPixel库且ledPin定义的数字引脚没有与其他功能冲突。舵机抖动或不转动供电不足尝试用外部电源为舵机供电。机械阻力检查传感器是否安装过重或者线缆是否绊住了舵机转动。代码延迟loop()中delay(15)是否合适如果主循环太快舵机可能来不及响应。可以适当增加这个延迟。超声波测距不准或跳动大环境干扰超声波对光滑的硬表面玻璃、大理石测距准但对柔软、多孔的物体窗帘、泡沫效果差。确保传感器前方没有其他杂物干扰声波。多次测量取平均在getDistance()函数中进行3-5次测量然后取中值或平均值返回可以显著平滑数据。供电稳定确保HC-SR04的VCC电压稳定在5V。Processing上位机连接失败或数据错乱检查串口号在Processing代码中new Serial(this, “COM3”, 9600)里的“COM3”需要替换成你电脑设备管理器中看到的实际端口号。波特率一致确保Processing和Arduino代码中的Serial.begin()波特率相同都是9600。数据格式确保Arduino发送的数据格式与Processing解析代码匹配如“角度,距离\n”。5.2 性能与效果优化扫描平滑与数据滤波舵机平滑可以使用myservo.writeMicroseconds()进行更精细的角度控制或者使用第三方平滑舵机库如ServoEasing让舵机运动更顺滑减少抖动。数据滤波除了多次测量取平均还可以采用“一阶低通滤波”算法。简单实现filteredDistance alpha * newDistance (1 - alpha) * filteredDistance。其中alpha是一个介于0和1之间的系数值越小滤波效果越强数据越平滑但延迟也越大。LED显示效果增强颜色模式除了距离-颜色映射还可以尝试“高度-亮度”映射或者根据物体移动速度改变颜色需要记录历史距离计算速度。扫描线效果除了全局渐隐可以只让扫描线后方的一条“尾巴”渐隐这样显示更清晰。可以通过维护一个代表“能量”的数组每帧衰减并赋值给对应LED来实现。增加交互与功能添加按键/旋钮通过外接按键切换显示模式如颜色模式、报警阈值用旋钮实时调整扫描速度或探测范围。声音反馈连接一个无源蜂鸣器当检测到物体进入警戒距离时发出不同频率的提示音。数据记录使用SD卡模块将扫描的角度和距离数据以CSV格式保存下来便于后续在电脑上用Excel或Python进行分析。5.3 结构设计与扩展最初的纸板或积木结构可能不够稳固。你可以使用激光切割亚克力板或者3D打印来制作一个更专业的雷达外壳。设计时考虑将Arduino、电源模块和灯环底座整合在一起让作品看起来更完整。更进一步你可以尝试使用ESP32替代Arduino Uno。ESP32自带Wi-Fi和蓝牙你可以轻松地将雷达数据通过WebSocket发送到网页端实现一个网络化的雷达监控界面在任何连接同一局域网的设备上都能查看。或者利用ESP32的蓝牙将数据发送到手机APP上显示。这个“极简短距雷达”项目就像一颗种子从最基本的传感器执行器控制到数据处理、可视化再到网络通信它几乎触及了嵌入式物联网开发的各个基础环节。通过动手实现它你不仅能获得一个酷炫的桌面玩具更能深刻理解如何让硬件感知世界并用最直观的方式——光——将信息呈现出来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价