ESP32蓝牙姿态控制迷宫:从传感器到执行器的物联网实践
1. 项目概述一个用手机姿态控制的实体迷宫几年前我在一个创客展上看到一个用摇杆控制的平衡球迷宫觉得很有意思但总觉得少了点“人机合一”的沉浸感。后来玩手机游戏时发现很多赛车游戏用重力感应控制方向手感非常直接。我就想能不能把这种体验搬到实体世界让手机直接变成操控杆通过倾斜手机来控制一个真实的迷宫平台让里面的小球滚动。这就是“BlueMaze”项目的起源。它本质上是一个嵌入式系统综合应用以ESP32微控制器为大脑通过蓝牙接收来自Android手机加速度计的实时数据再驱动两个伺服电机舵机分别控制一个木质迷宫平台在X轴和Y轴上的倾斜角度。你向左倾斜手机迷宫就向左倾斜小球随之滚动目标就是引导小球走出迷宫。这个项目麻雀虽小五脏俱全。它串联了传感器数据采集手机加速度计、短距离无线通信蓝牙、微控制器编程ESP32/Arduino、执行器控制伺服电机以及快速应用开发MIT App Inventor。无论你是想入门物联网、学习无线控制还是单纯想做一个有趣的互动玩具它都是一个绝佳的练手项目。下面我就把自己从电路连接、代码调试到迷宫制作、避坑优化的全过程经验拆解开来手把手带你复现这个项目。2. 核心硬件选型与电路设计解析硬件是整个系统的骨架选型和连接决定了项目的稳定性和扩展性。我的核心思路是主控要集成无线、执行器要独立供电、结构要稳固且易于加工。2.1 主控芯片为什么是ESP32市面上常见的单片机很多比如经典的Arduino Uno、功能强大的STM32但我最终选择了ESP32-DevKitC开发板。原因有三点集成双模蓝牙这是最关键的一点。ESP32集成了蓝牙4.2包括经典蓝牙和低功耗蓝牙我们本项目使用经典蓝牙SPP协议进行数据传输。这意味着你不需要额外购买和连接HC-05、HC-06这类蓝牙模块简化了电路也降低了成本和连接出错的概率。性能与资源充足ESP32是双核处理器主频高达240MHz内存也足够运行我们这种需要实时解析蓝牙数据并产生PWM信号控制舵机的程序绰绰有余。其丰富的GPIO口也为我们连接其他传感器如后续想增加声音反馈、灯光效果留下了余地。Arduino生态友好虽然ESP-IDF是乐鑫官方的开发框架功能更强大但对于大多数爱好者和快速原型开发来说Arduino IDE的库支持和社区资源更为丰富上手更快。本项目就完全基于Arduino框架开发。注意ESP32开发板型号众多建议选择引脚引出较多的型号如30 GPIO并确认其USB转串口芯片稳定如CP2102、CH340这关系到后续代码上传的可靠性。2.2 执行机构伺服电机的选择与控制迷宫平台需要两个自由度的倾斜因此需要两个舵机。我选择了最常见的SG90微型舵机。选择它是因为它价格低廉、扭矩适中1.8kg/cm、控制简单标准PWM信号。但这里有几个关键细节工作电压SG90标称工作电压为4.8V-6V。虽然ESP32的VIN引脚或某些板载的5V引脚可以输出5V但强烈不建议直接从ESP32取电给舵机供电。舵机在启动和堵转时会产生很大的瞬时电流可能超过1A这极易导致ESP32的电源不稳定引发复位或损坏。必须为舵机准备独立的电源。PWM信号舵机控制线通常是橙色或白色连接至ESP32的GPIO。ESP32的LEDCLED PWM控制器可以生成非常稳定的PWM信号。我们需要选择支持硬件PWM输出的引脚例如GPIO12、13、14、15、16、17、18、19、21、22、23、25、26、27等。机械安装舵机需要牢固固定。我使用了热熔胶但对于长期使用或频繁操作建议使用螺丝固定。舵机自带的塑料舵盘需要与迷宫结构进行可靠连接我通过3D打印了一个连接件也可以用轻木片加工。2.3 电源方案双路供电与共地如前所述电源分离是关键。我采用了一个带有双USB输出口的移动电源。一路USB5V/1A通过Micro-USB或Type-C线给ESP32开发板供电。另一路USB5V/1A或2.4A我剪断了一根旧的USB线引出红5V黑GND两根线专门用于给两个舵机供电。共地操作ESP32的GND和舵机电源的GND必须连接在一起这是确保PWM信号参考电位一致的基础否则舵机可能无法工作或乱转。即使两路电源来自同一个移动电源的不同接口也务必用导线将两者的GND引脚物理连接。2.4 电路连接图与实操要点下图清晰地展示了所有部件的连接关系[电源]移动电源 ├── USB口1 ── ESP32开发板 (VCC, GND) └── USB口2 ── 舵机电源线 (5V, GND) ├── 舵机1 (红线:5V, 棕线:GND, 橙线:信号) └── 舵机2 (红线:5V, 棕线:GND, 橙线:信号) [信号]ESP32 GPIO ── 舵机信号线 (例如GPIO13 - 舵机1信号线) (例如GPIO14 - 舵机2信号线) [地线]ESP32 GND引脚 ── 舵机电源GND线 (必须连接)实操连接步骤与心得准备杜邦线准备足够数量的公对公、公对母杜邦线。舵机线通常是母头所以连接ESP32时需要用公对公线连接电源排针时用公对母线。使用面包板或PCB对于初次测试强烈建议使用面包板。将ESP32、电源排针插在面包板上再用杜邦线连接非常便于修改和调试。确认一切工作正常后可以考虑焊接在洞洞板万用板上或者像我一样自己腐蚀一块简单的PCB以获得更稳定的连接。上电顺序建议先连接好所有线路检查无误后最后再插入移动电源。避免带电插拔信号线。信号线防干扰如果舵机线较长超过20cmPWM信号可能会受到干扰。如果发现舵机有抖动现象可以尝试在信号线靠近舵机端对地GND加一个0.1uF的电容进行滤波。3. ESP32端程序深度剖析与调试代码是项目的大脑负责解析蓝牙指令并精确控制舵机。我们使用Arduino IDE进行开发。3.1 开发环境搭建安装Arduino IDE从官网下载并安装最新版。添加ESP32板支持打开文件 - 首选项在“附加开发板管理器网址”中输入https://espressif.github.io/arduino-esp32/package_esp32_index.json。然后打开工具 - 开发板 - 开发板管理器搜索“esp32”安装“Espressif Systems”的包。安装蓝牙库ESP32的Arduino核心已包含蓝牙库BluetoothSerial无需额外安装。选择开发板与端口连接ESP32到电脑在工具菜单中选择正确的开发板型号如ESP32 Dev Module和对应的串口端口。3.2 核心代码解读以下是代码的核心逻辑我添加了详细注释#include BluetoothSerial.h #include ESP32Servo.h // 使用专门的ESP32舵机库兼容性更好 // 蓝牙串口对象 BluetoothSerial SerialBT; // 创建两个舵机对象 Servo servoX; Servo servoY; // 定义舵机信号引脚 const int servoXPin 13; const int servoYPin 14; // 存储从手机接收到的原始数据 int xValue 0; int yValue 0; // 舵机角度范围限制根据你的机械结构调整 const int minAngle 30; const int maxAngle 150; // 数据解析相关变量 String inputString ; // 存储接收到的字符串 bool stringComplete false; // 标志是否收到完整数据包 void setup() { Serial.begin(115200); // 启动硬件串口用于调试输出 SerialBT.begin(BlueMaze); // 启动蓝牙设备名为“BlueMaze” Serial.println(蓝牙设备已启动等待连接...); // 允许ESP32的GPIO用于舵机控制必须调用 ESP32PWM::allocateTimer(0); ESP32PWM::allocateTimer(1); ESP32PWM::allocateTimer(2); ESP32PWM::allocateTimer(3); // 关联舵机对象与引脚并设置PWM频率SG90通常为50Hz servoX.setPeriodHertz(50); servoX.attach(servoXPin, 500, 2400); // 最小500us最大2400us脉冲宽度可微调 servoY.setPeriodHertz(50); servoY.attach(servoYPin, 500, 2400); // 初始化舵机到中间位置 servoX.write(90); servoY.write(90); delay(1000); // 给舵机时间运动到初始位置 } void loop() { // 1. 检查蓝牙是否连接并读取数据 if (SerialBT.connected()) { while (SerialBT.available()) { char inChar (char)SerialBT.read(); // 读取一个字符 if (inChar \n) { // 如果收到换行符说明一个数据包结束 stringComplete true; } else { inputString inChar; // 否则将字符添加到字符串 } } } // 2. 如果收到完整数据包开始解析 if (stringComplete) { // 预期数据格式X123Y98\n即X和Y值以字母分隔 int xIndex inputString.indexOf(X); int yIndex inputString.indexOf(Y); if (xIndex ! -1 yIndex ! -1) { // 提取X值从X后一位开始到Y之前结束 String xStr inputString.substring(xIndex 1, yIndex); // 提取Y值从Y后一位开始到字符串末尾 String yStr inputString.substring(yIndex 1); // 转换为整数 xValue xStr.toInt(); yValue yStr.toInt(); // 调试输出可通过串口监视器查看 Serial.print(收到数据 - X: ); Serial.print(xValue); Serial.print(, Y: ); Serial.println(yValue); // 3. 数据映射与舵机控制 // 手机发来的数据范围假设为0-200App中加了100我们需要映射到舵机角度范围 // 注意手机坐标系与迷宫坐标系可能需要转换例如手机前后倾对应迷宫左右倾 int angleX map(yValue, 0, 200, minAngle, maxAngle); // 注意X/Y的对应关系 int angleY map(xValue, 0, 200, minAngle, maxAngle); // 限制角度在安全范围内 angleX constrain(angleX, minAngle, maxAngle); angleY constrain(angleY, minAngle, maxAngle); // 写入角度控制舵机 servoX.write(angleX); servoY.write(angleY); } // 处理完毕清空字符串和标志准备接收下一个数据包 inputString ; stringComplete false; } // 短暂延迟避免循环过快 delay(10); }代码关键点解析数据协议我定义了简单的字符串协议X[value]Y[value]\n。例如手机发送X145Y167\n。这种格式易于在Arduino端用indexOf和substring函数解析比传输二进制数据更直观、易调试。map()函数这是Arduino的核心函数用于将一个范围内的值线性映射到另一个范围。这里将手机发来的0-200映射到舵机的30-150度。务必根据你实际迷宫结构的机械限位来调整minAngle和maxAngle否则可能卡住或损坏结构。constrain()函数安全锁。确保计算出的角度值不会超出预设的安全范围是保护舵机和机械结构的重要一步。坐标系转换angleX map(yValue,...)和angleY map(xValue,...)这行代码非常关键因为手机平放时其X轴左右通常对应迷宫的Y轴前后倾斜Y轴前后对应迷宫的X轴左右倾斜。这个对应关系需要根据你安装手机的方向和舵机的安装方向来调整可能需要多次测试。3.3 上传代码与调试技巧上传在Arduino IDE中点击上传按钮。如果遇到“上传失败”或“串口打不开”请检查是否安装了正确的USB驱动CP210x或CH340。是否选择了正确的端口。尝试按住ESP32板上的BOOT按钮再点击上传待出现“上传中...”提示时松开。串口监视器上传成功后打开工具 - 串口监视器设置波特率为115200。重启ESP32你会看到“蓝牙设备已启动等待连接...”的提示。这是最重要的调试工具。舵机测试可以先注释掉蓝牙数据解析部分在setup()函数最后或loop()里写死几个角度值如servoX.write(30); delay(1000); servoX.write(150);测试舵机是否能正常运动到极限位置并确认机械结构运行顺畅无阻碍。4. Android应用开发MIT App Inventor 2 实战对于不熟悉Java/Kotlin的开发者MIT App Inventor 2AI2是快速构建原型App的神器。它采用图形化“积木块”编程直观易懂。4.1 界面设计Designer我们需要以下核心组件AccelerometerSensor加速度传感器非可视组件用于读取手机加速度数据。BluetoothClient蓝牙客户端非可视组件负责与ESP32通信。ListPicker列表选择器用户点击后弹出已配对的蓝牙设备列表。Clock时钟非可视组件用于定时发送数据这是实现流畅控制的关键。Label标签用于显示状态如“未连接”、“已连接”和当前的X、Y加速度值用于调试。Button按钮可选用于手动连接/断开。HorizontalArrangement水平布局用于排列组件使界面美观。布局要点将ListPicker的文本设为“点击连接蓝牙”并放置一个显示连接状态的Label。再放置两个Label分别显示“X: 0”和“Y: 0”。最后将Clock组件的TimerInterval属性设置为50毫秒即每秒发送20次数据这是一个在流畅度和功耗之间比较平衡的值。4.2 逻辑编程Blocks这才是核心。主要逻辑分为三块1. 初始化与蓝牙设备列表加载当 Screen1 初始化时 执行 设置 AccelerometerSensor1.Enabled 为 true 设置 ListPicker1.Elements 为 BluetoothClient1.AddressesAndNames解释屏幕一打开就启用加速度计并将已配对设备的列表赋值给ListPicker。2. 蓝牙连接当 ListPicker1.AfterPicking 时 执行 调用 BluetoothClient1.Connect 地址为 ListPicker1.Selection 如果 BluetoothClient1.IsConnected 成立 那么 设置 Label_Status.Text 为 “已连接” 设置 Label_Status.BackgroundColor 为 绿色 设置 Clock1.TimerEnabled 为 true //启动定时发送 否则 设置 Label_Status.Text 为 “连接失败” 设置 Label_Status.BackgroundColor 为 红色解释用户从列表中选择一个设备我们的ESP32“BlueMaze”后尝试连接。连接成功则更新状态并启动定时器失败则提示。3. 定时读取加速度计并发送数据当 Clock1.Timer 时 执行 如果 BluetoothClient1.IsConnected 成立 那么 // 获取加速度值单位m/s² 设 xRaw 为 AccelerometerSensor1.XAccel 设 yRaw 为 AccelerometerSensor1.YAccel // 数据缩放与偏移关键步骤 // 1. 放大乘以一个系数如10增加控制灵敏度 // 2. 取整转换为整数 // 3. 偏移加上一个固定值如100确保所有数据为正数方便ESP32解析 设 xValue 为 向下取整(xRaw * 10) 100 设 yValue 为 向下取整(yRaw * 10) 100 // 可选限制数值范围在安全区间例如0-200 设 xValue 为 限制数值范围(xValue, 0, 200) 设 yValue 为 限制数值范围(yValue, 0, 200) // 更新界面显示调试用 设置 Label_X.Text 为 连接字符串(“X: ”, xValue) 设置 Label_Y.Text 为 连接字符串(“Y: ”, yValue) // 按照协议格式发送数据”X[value]Y[value]\n” 调用 BluetoothClient1.SendText 文本为 连接字符串(“X”, xValue, “Y”, yValue, “\n”)这里是整个App的灵魂有几个经验点为什么用定时器发送而不是加速度变化时发送我最初尝试在AccelerometerSensor.AccelerationChanged事件中发送发现数据流不稳定有时会丢包或延迟导致迷宫控制卡顿。改为固定频率50ms一次的定时发送后控制变得非常平滑。这是因为事件触发频率可能过高且不均匀而定时器提供了稳定、可控的数据流。数据缩放与偏移原始加速度值通常在-10到10之间单位m/s²变化幅度小且包含负数。xRaw * 10将其放大使控制更灵敏。100是为了将所有值变为正数大致在0-200范围避免了在ESP32端解析负号字符串的麻烦。限制数值范围是另一道安全锁防止意外值导致舵机角度越界。协议格式必须与ESP32端代码的解析格式严格匹配。这里发送X123Y156\nESP32就等待这个格式。4.3 打包与安装在AI2中点击构建 - 安卓应用 (.apk)可以选择下载到电脑再传到手机安装或生成二维码直接扫码安装。首次安装非市场应用需要在手机设置中允许“安装未知来源应用”。5. 迷宫结构设计与制作详解稳定的机械结构是良好体验的保障。我的设计目标是轻质、稳固、低摩擦。5.1 材料与工具清单核心结构底板一块25cm x 30cm的MDF板中密度纤维板或亚克力板厚度约5mm。MDF易于加工亚克力更美观。支撑柱4根截面1cm x 1cm的轻木条Balsa长度分别为23cm2根和19cm2根用于制作底层框架。侧立板2根轻木条长度13cm和12cm用于支撑整个可动框架。可动平台迷宫托盘托盘底板16cm x 20cm的硬卡纸或薄木板如椴木板。迷宫墙壁使用1.5cm宽的硬卡纸条制作。转轴连接件可以用3D打印推荐也可以用厚塑料片或小木块加工。需要两个一个连接X轴舵机与迷宫托盘一个连接Y轴舵机与底层框架。从动轴两根竹签或直径2-3mm的钢棍作为迷宫托盘另一侧的支撑旋转点。连接与固定热熔胶枪与胶棒快速固定适合原型。小螺丝M2或M3用于加固关键连接点如舵机与木条的连接。手工刀、尺子、铅笔、手钻。5.2 制作步骤与技巧第一步制作迷宫托盘在16x20cm的卡纸上用铅笔画出迷宫路径设计。路径宽度建议在2cm以上确保小球我用铝箔揉成的球能顺利通过。用尺子和手工刀将1.5cm宽的卡纸条按设计图切割成相应长度的“墙壁”。关键技巧切割两条2cm宽的卡纸条作为“定位规”。在粘贴墙壁时用它们卡在两条平行墙壁之间可以确保所有路径宽度一致。使用少量热熔胶将墙壁垂直粘贴在底板对应位置上。动作要快胶量要少避免胶体凸起影响小球滚动。第二步组装可动框架将23cm和19cm的轻木条用热熔胶粘成一个外框23cm边为长边19cm边为宽边。确保四个角是直角可以借助直角尺或利用书本角。在这个外框的一条19cm边的中心位置粘贴或螺丝固定一个舵盘连接件连接Y轴舵机。在对边的中心位置粘贴一小段竹签或安装一个轴承作为从动旋转轴。在迷宫托盘的一条16cm边的中心粘贴另一个舵盘连接件连接X轴舵机。在迷宫托盘的对边中心同样粘贴一小段竹签作为从动轴。将迷宫托盘通过其舵盘连接件安装到Y轴舵机的舵盘上。同时托盘另一侧的竹签轴应能轻松搭在底层框架对应的支撑点上可以在支撑点粘一个带凹槽的小木块。第三步搭建整体支撑结构在MDF底板上确定两个侧立板的位置。它们应平行间距略大于可动框架的宽度19cm例如25cm。将13cm和12cm长的侧立板垂直固定在MDF底板上。可以用胶粘但更稳固的方法是在底板和木条上预钻孔然后用螺丝从底板下方拧紧。关键步骤将Y轴舵机固定在12cm高的侧立板顶端确保其转轴与可动框架上的连接件同心。同样在13cm高的侧立板顶端制作一个支撑凹槽用于承载可动框架另一侧的从动轴。将组装好的可动框架带着迷宫托盘架到两个侧立板上Y轴舵机与框架连接另一侧的从动轴放入支撑凹槽。此时用手拨动框架它应该在Y轴方向顺畅摆动。最后将X轴舵机固定在可动框架的侧面并将其舵盘与迷宫托盘上的连接件接好。现在迷宫托盘应能在X轴方向顺畅摆动。第四步总装与布线在MDF底板边缘钻一个直径6mm的孔用于穿过舵机和ESP32的电源线、信号线。将ESP32、移动电源固定在底板下方用扎带整理好线缆避免缠绕到运动部件。进行通电测试。先不装小球通过手机App控制观察两个舵机是否带动迷宫平台在两个方向上平滑、完整地运动且没有卡顿或异响。6. 系统联调、校准与玩法优化所有部分制作完成后进入最有趣的联调与优化阶段。6.1 首次连接与基础测试给整个系统上电。打开手机蓝牙在设置中搜索并配对名为“BlueMaze”的设备ESP32首次启动后可见。打开我们开发的App点击“点击连接蓝牙”选择“BlueMaze”。状态栏应变为绿色“已连接”。此时App已经开始发送数据。将手机平放在桌面上这应该是迷宫的“水平”位置。观察迷宫平台。它很可能没有水平或者倾斜方向与手机动作相反。这很正常需要校准。6.2 软件校准修改映射参数校准的核心是调整ESP32代码中的map()函数参数和手机App中的偏移量。确定中位值在手机水平放置时查看App界面显示的X和Y的数值例如“X: 102, Y: 98”。这个值就是“零位”对应的原始数据。假设是 (102, 98)。修改App代码在AI2的发送数据逻辑中将偏移计算修改为设 xValue 为 向下取整(xRaw * 10) (100 - 102) // 实际计算100 - 零位值 设 yValue 为 向下取整(yRaw * 10) (100 - 98)这样当手机水平时发送出的xValue和yValue理论上应接近100。重新打包安装App。调整ESP32映射在Arduino代码中map()函数将0-200映射到舵机角度。我们需要让“100”这个值映射到舵机的物理水平位置角度。这个角度不一定是90度需要实测。注释掉蓝牙控制部分手动编写测试代码让舵机慢慢从30度运动到150度观察迷宫平台何时水平。假设X轴舵机在115度时平台水平。则将map(yValue, 0, 200, minAngle, maxAngle)改为map(yValue, 0, 200, 115-(幅度), 115(幅度))。例如map(yValue, 0, 200, 75, 155)这样100就映射到115度即水平位置。Y轴同理。方向修正如果平台倾斜方向与手机相反只需在App端或ESP32端交换X/Y的映射关系或在某个数值前乘以-1即可。6.3 机械调校与润滑重心调整确保迷宫托盘尤其是装上小球后的重心大致在其几何中心。如果重心偏了舵机负载会不均匀可能产生抖动或无力。可以在托盘底部轻的位置粘贴配重如硬币来调整。减少摩擦所有旋转支点舵机轴、从动轴与支撑凹槽的摩擦要尽量小。可以在竹签轴上涂一点润滑油如凡士林或硅脂或使用微型轴承。限位保护在代码中设定的minAngle和maxAngle一定要小于机械结构的物理极限角度留出几度的余量防止堵转烧毁舵机。6.4 玩法扩展与优化建议增加难度与趣味性多球模式在迷宫中放置多个颜色不同的铝箔球挑战同时控制多个球。计时挑战在App端增加计时器功能记录走出迷宫的时间。迷宫地图切换设计多个可替换的迷宫底板用磁吸或卡扣方式固定。硬件升级金属舵机如果觉得SG90扭矩不足或有抖动可以升级为金属齿轮的MG90S舵机。电池集成使用18650电池盒和降压模块替代移动电源让作品更一体化。增加反馈在迷宫终点安装一个触碰开关或光电传感器当小球到达时让ESP32控制一个蜂鸣器播放胜利音效或通过蓝牙通知手机App。软件优化数据平滑滤波在ESP32端可以对接收到的X、Y值进行滑动平均滤波消除手机微小抖动带来的平台抖动。控制曲线将线性映射改为指数或自定义曲线使得手机在小角度倾斜时平台移动平缓大角度倾斜时移动迅速操控感更跟手。这个项目从构思到实现最深的体会是“软硬结合”的魅力。每一个环节——电路的一个虚焊、代码的一个参数、结构的一毫米偏差——都会直接影响最终体验。调试过程就是不断在手机屏幕、串口监视器、和晃动的迷宫平台之间来回切换、观察、思考、修改的过程。当最终小球随着手机倾斜而流畅滚动并成功走出迷宫时那种成就感远超单纯完成一个软件或硬件任务。它完整地呈现了一个物联网控制系统的闭环希望这个详细的分享能帮你顺利搭建出自己的“BlueMaze”并在此基础上玩出更多花样。