自制 Arduino 测距仪与电子水平仪——分步详解
概述
本教程将学习如何制作一个 Arduino 测距仪和电子水平仪。你可以观看下方视频或阅读以下图文教程。
设备包含超声波测距模块、加速度计测倾角、LCD 显示屏和定制 PCB。

单按钮操作:上电后先选择单位制;短按切换单位,长按进入第一个程序(测距,可存储最近两次测量)。

再次长按进入第二程序(测面积:两次垂直测距相乘)。

第三程序为电子水平仪,测量相对地面的角度,短按切换俯仰/横滚轴。

Arduino 测距仪电路原理图

相关模块教程:超声波传感器教程、LCD 教程、MEMS 加速度计教程。
所需组件:
- 超声波传感器 HC-SR04
- MPU6050 三轴陀螺仪加速度计
- 16×2 字符 LCD
- Arduino Nano
定制 PCB 设计
按原理图用 EasyEDA 设计 PCB,导出 Gerber 文件,JLCPCB 制板(首单 10 片 $2)。



设备组装
PCB 到货质量优秀。焊排针、三个电阻(两个分压调 LCD 对比度:R1 1k、R2 220Ω;一个按钮上拉)。


外壳用透明亚克力(5mm 顶盖、其余 4mm),圆锯切割,钻孔开槽装 LCD、开关、按钮、超声波模块。环氧胶组装,顶盖用螺栓+螺母固定。




LCD、开关、按钮、电池扣焊排针/跳线,装入超声波模块、Arduino、MPU6050、电池扣,盖顶盖拧螺母。9V 电池供电,硬件完成。



Arduino 测距仪与电子水平仪源代码
代码分段讲解,文末附完整代码。
库与变量定义
#include <Wire.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
const int MPU = 0x68;
#define trigPin 8
#define echoPin 9
#define selectButton 10
int16_t AcX, AcY, AcZ;
long duration;
float distance;
int program = 0;
float d = 0, d1 = 0, d2 = 0, area = 0;
int axis = 0, angle = 0;
int unitSelect = 0;
String unit = "cm";
setup 初始化
void setup() {
Wire.begin();
Wire.beginTransmission(MPU);
Wire.write(0x6B); Wire.write(0); Wire.endTransmission(true);
lcd.begin(16, 2);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
pinMode(selectButton, INPUT_PULLUP);
}
主循环:状态机切换程序
case 0:选择单位(cm/in/m/ft),短按切换,长按 >0.5s 进入程序 1。
case 0:
lcd.setCursor(0,0); lcd.print("Select Unit: "); lcd.setCursor(13,0); lcd.print(unit); lcd.print(" "); delay(10);
if (digitalRead(selectButton)==0) {
if (unitSelect==0) { unit="in"; unitSelect=1; }
else if (unitSelect==1) { unit="m"; unitSelect=2; }
else if (unitSelect==2) { unit="ft"; unitSelect=3; }
else if (unitSelect==3) { unit="cm"; unitSelect=0; }
delay(500);
if (digitalRead(selectButton)==0) { program=1; lcd.clear(); delay(500); }
}
break;
单位换算在 getDistance() 中根据 unitSelect 将基础 cm 转换。
if (unitSelect==1) { distance=distance; unit="cm"; }
else if (unitSelect==2) { distance*=0.393701; unit="in"; }
else if (unitSelect==3) { distance*=0.01; unit="m"; }
else if (unitSelect==0) { distance*=0.0328; unit="ft"; }
case 1:测距程序。调用 getDistance() 获取距离,显示并可短按存储最近两次测量(d 变量切换 0/1),长按进入程序 2。
case 1:
distance = getDistance();
lcd.setCursor(0,0); lcd.print("Dist: "); lcd.print(distance); lcd.print(" "); lcd.setCursor(14,0); lcd.print(unit); delay(10);
lcd.setCursor(0,1); lcd.print("d:"); lcd.setCursor(8,1); lcd.print("d:"); delay(200);
if (digitalRead(selectButton)==0) {
if (d==0) { lcd.setCursor(0,1); lcd.print("d: "); lcd.setCursor(2,1); lcd.print(distance); d=1; delay(100); }
else if (d==1) { lcd.setCursor(8,1); lcd.print("d: "); lcd.setCursor(10,1); lcd.print(distance); d=0; delay(100); }
delay(500);
if (digitalRead(selectButton)==0) { program=2; d=0; lcd.clear(); delay(500); }
}
break;
getDistance() 自定义函数:触发超声波,pulseIn 读取回波时间,按声速计算距离(cm),再按 unitSelect 换算单位。
float getDistance() {
digitalWrite(trigPin, LOW);
digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = duration * 0.034 / 2;
// 单位换算同上
return distance;
}
case 2:测面积程序。两次垂直测距相乘,d 变量 0/1/2 分别对应等待第一次、第二次、显示结果。短按存储 d1/d2 并计算面积,第三次短按清零重测,长按进入程序 3。
case 2:
distance = getDistance();
lcd.setCursor(0,0); lcd.print("Area: "); lcd.print(area); lcd.setCursor(12,0); lcd.print(unit); lcd.print("^2"); delay(200);
if (d==0) { lcd.setCursor(0,1); lcd.print("d1: "); lcd.setCursor(3,1); lcd.print(distance); delay(200); }
else if (d==1) { lcd.setCursor(9,1); lcd.print("d2: "); lcd.setCursor(12,1); lcd.print(distance); delay(200); }
else if (d==2) { lcd.setCursor(6,0); lcd.print(area); delay(200); }
if (digitalRead(selectButton)==0) {
if (d==0) { lcd.setCursor(0,1); lcd.print("d1: "); lcd.setCursor(3,1); lcd.print(distance); d=1; d1=distance; delay(100); }
else if (d==1) { lcd.setCursor(9,1); lcd.print("d2: "); lcd.setCursor(12,1); lcd.print(distance); d=2; d2=distance; area=d1*d2; delay(100); }
else if (d==2) { lcd.clear(); d=0; area=0; delay(100); }
delay(500);
if (digitalRead(selectButton)==0) { program=3; d=0; lcd.clear(); delay(500); }
}
break;
case 3:测角程序。读取 MPU6050 六个寄存器合成 X/Y/Z 轴加速度值,根据公式计算俯仰或横滚角(参考 Freescale 应用笔记《使用三轴加速度计的倾斜感测》)。axis 0 显示 Pitch,1 显示 Roll,短按切换,长按回到程序 0。
case 3:
Wire.beginTransmission(MPU); Wire.write(0x3B); Wire.endTransmission(false);
Wire.requestFrom(MPU, 6, true);
AcX = Wire.read()<<8 | Wire.read();
AcY = Wire.read()<<8 | Wire.read();
AcZ = Wire.read()<<8 | Wire.read();
if (axis==0) { angle = atan(-1*AcX/sqrt(pow(AcY,2)+pow(AcZ,2)))*180/PI; lcd.setCursor(0,0); lcd.print("Pitch"); }
else if (axis==1) { angle = atan(-1*AcY/sqrt(pow(AcX,2)+pow(AcZ,2)))*180/PI; lcd.setCursor(0,0); lcd.print("Roll "); }
lcd.setCursor(0,1); lcd.print("Angle: "); lcd.print(abs(angle)); lcd.print(" "); lcd.setCursor(10,1); lcd.print("deg"); delay(200);
if (digitalRead(selectButton)==0) {
if (axis==0) { axis=1; delay(100); }
else if (axis==1) { axis=0; delay(100); }
delay(500);
if (digitalRead(selectButton)==0) { program=0; lcd.clear(); delay(500); }
}
break;
完整源代码
/* DIY Digital Range Measurer and Spirit Level by Dejan Nedelkovski, www.HowToMechatronics.com */
#include <Wire.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
const int MPU = 0x68;
#define trigPin 8
#define echoPin 9
#define selectButton 10
int16_t AcX, AcY, AcZ;
long duration;
float distance;
int program = 0;
float d = 0, d1 = 0, d2 = 0, area = 0;
int axis = 0, angle = 0;
int unitSelect = 0;
String unit = "cm";
void setup() {
Wire.begin(); Wire.beginTransmission(MPU); Wire.write(0x6B); Wire.write(0); Wire.endTransmission(true);
lcd.begin(16, 2);
pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); pinMode(selectButton, INPUT_PULLUP);
}
void loop() {
switch (program) {
case 0:
lcd.setCursor(0,0); lcd.print("Select Unit: "); lcd.setCursor(13,0); lcd.print(unit); lcd.print(" "); delay(10);
if (digitalRead(selectButton)==0) {
if (unitSelect==0) { unit="in"; unitSelect=1; }
else if (unitSelect==1) { unit="m"; unitSelect=2; }
else if (unitSelect==2) { unit="ft"; unitSelect=3; }
else if (unitSelect==3) { unit="cm"; unitSelect=0; }
delay(500);
if (digitalRead(selectButton)==0) { program=1; lcd.clear(); delay(500); }
}
break;
case 1:
distance = getDistance();
lcd.setCursor(0,0); lcd.print("Dist: "); lcd.print(distance); lcd.print(" "); lcd.setCursor(14,0); lcd.print(unit); delay(10);
lcd.setCursor(0,1); lcd.print("d:"); lcd.setCursor(8,1); lcd.print("d:"); delay(200);
if (digitalRead(selectButton)==0) {
if (d==0) { lcd.setCursor(0,1); lcd.print("d: "); lcd.setCursor(2,1); lcd.print(distance); d=1; delay(100); }
else if (d==1) { lcd.setCursor(8,1); lcd.print("d: "); lcd.setCursor(10,1); lcd.print(distance); d=0; delay(100); }
delay(500);
if (digitalRead(selectButton)==0) { program=2; d=0; lcd.clear(); delay(500); }
}
break;
case 2:
distance = getDistance();
lcd.setCursor(0,0); lcd.print("Area: "); lcd.print(area); lcd.setCursor(12,0); lcd.print(unit); lcd.print("^2"); delay(200);
if (d==0) { lcd.setCursor(0,1); lcd.print("d1: "); lcd.setCursor(3,1); lcd.print(distance); delay(200); }
else if (d==1) { lcd.setCursor(9,1); lcd.print("d2: "); lcd.setCursor(12,1); lcd.print(distance); delay(200); }
else if (d==2) { lcd.setCursor(6,0); lcd.print(area); delay(200); }
if (digitalRead(selectButton)==0) {
if (d==0) { lcd.setCursor(0,1); lcd.print("d1: "); lcd.setCursor(3,1); lcd.print(distance); d=1; d1=distance; delay(100); }
else if (d==1) { lcd.setCursor(9,1); lcd.print("d2: "); lcd.setCursor(12,1); lcd.print(distance); d=2; d2=distance; area=d1*d2; delay(100); }
else if (d==2) { lcd.clear(); d=0; area=0; delay(100); }
delay(500);
if (digitalRead(selectButton)==0) { program=3; d=0; lcd.clear(); delay(500); }
}
break;
case 3:
Wire.beginTransmission(MPU); Wire.write(0x3B); Wire.endTransmission(false);
Wire.requestFrom(MPU, 6, true);
AcX = Wire.read()<<8 | Wire.read();
AcY = Wire.read()<<8 | Wire.read();
AcZ = Wire.read()<<8 | Wire.read();
if (axis==0) { angle = atan(-1*AcX/sqrt(pow(AcY,2)+pow(AcZ,2)))*180/PI; lcd.setCursor(0,0); lcd.print("Pitch"); }
else if (axis==1) { angle = atan(-1*AcY/sqrt(pow(AcX,2)+pow(AcZ,2)))*180/PI; lcd.setCursor(0,0); lcd.print("Roll "); }
lcd.setCursor(0,1); lcd.print("Angle: "); lcd.print(abs(angle)); lcd.print(" "); lcd.setCursor(10,1); lcd.print("deg"); delay(200);
if (digitalRead(selectButton)==0) {
if (axis==0) { axis=1; delay(100); }
else if (axis==1) { axis=0; delay(100); }
delay(500);
if (digitalRead(selectButton)==0) { program=0; lcd.clear(); delay(500); }
}
break;
}
}
float getDistance() {
digitalWrite(trigPin, LOW);
digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = duration * 0.034 / 2;
if (unitSelect==1) { distance=distance; unit="cm"; }
else if (unitSelect==2) { distance*=0.393701; unit="in"; }
else if (unitSelect==3) { distance*=0.01; unit="m"; }
else if (unitSelect==0) { distance*=0.0328; unit="ft"; }
return distance;
}
希望你喜欢这个 Arduino 项目并学到了新知识。欢迎在评论区提问。
制造工艺
- Arduino 自制 DCC 命令站:蓝牙控制模型铁路,免费 App 支持 CV 读写
- Arduino 数字骰子:LCD 显示随机点数的电子骰子
- Arduino UNO 驱动 16x2 LCD 实现动画与小游戏
- Arduino 音频频率检测:基于 FFT 测量最响亮声音峰值
- 自制 Arduino 电子宠物「Tamaguino」:复刻经典 Tamagotchi 全攻略
- Arduino 井字棋游戏:MAX7219 点阵屏 + 纸板外壳 DIY
- Arduino DIY 电压表:Nokia 5110 屏幕显示,手把手教学
- 使用 SparkFun 7 段数码管构建可靠的 Arduino 倒计时器
- Python 3 控制 Arduino UNO - 串口通信点亮内置 LED 入门教程
- 使用 Arduino Nano 和 RDA8057M 制作紧凑型 FM 收音机
- DIY 集水坑液位监测仪 - Arduino Nano、MPXV5010DP 与 OLED 显示
- 自制 Arduino 遥控保时捷赛车:从改装底盘到手机 App 控制全攻略