多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

ESP32蓝牙控制舵机零基础教程:手机无线操控PWM角度

ESP32蓝牙控制舵机零基础教程:手机无线操控PWM角度 “手机一点舵机就转”——这个画面感很强也是我推荐新手入门ESP32的原因。ESP32本身就带蓝牙舵机又是学习PWM控制最典型的执行器两者一组合你不仅能理解蓝牙串口到底怎么工作还能亲手完成第一个无线控制项目。准备材料很便宜逻辑也不复杂没有电子基础也能做。说实话我见过很多朋友买回ESP32和舵机之后不是卡在接线就是卡在蓝牙配对最后只能看着示例代码发呆。其实这个项目拆开看只有三部分ESP32负责接收蓝牙指令并输出PWM信号舵机根据PWM信号转到指定角度手机上的蓝牙串口助手负责发送角度值。整个过程中没有任何一步需要高深的编程基础只要你会复制代码、会接三根线、会装Arduino IDE就能跑通。下面我从硬件选型讲到代码调试把每个环节里最容易被新手忽略的坑都提出来。1. 这个项目到底在做什么零基础也能有个清晰的概念1.1 最终效果和原理拆解先别急着开干花两分钟搞清楚这件事的本质。做出来的效果是手机打开蓝牙串口助手搜到ESP32点连接然后发送一个数字比如90舵机立刻转到90度发送180舵机转到180度发送0舵机回到零位。整个过程响应很快基本没有延迟感所以看起来就像“手机一点舵机就转”。原理其实是一根“无线串口线”。ESP32的经典蓝牙里有个SPP协议全称Serial Port Profile翻译成人话就是把蓝牙通信虚拟成一条串口线。手机通过App连接之后它发的字符就会出现在ESP32的蓝牙缓冲区里。你的代码做的事情很简单读取缓冲区里的字符拼出一个数字解析成角度然后调用舵机库输出对应的PWM信号。很多人听到“蓝牙协议”就觉得难但这里你完全不用关心协议栈的细节。ESP32官方库已经把底层封装好了你只需要把它当成一个叫BluetoothSerial的串口对象来用跟用Serial.print在电脑串口上打印日志一样简单。1.2 零基础需要准备的硬件清单这个项目的物料成本很低我把清单列一下ESP32开发板一块建议用经典的DevKit V1或者NodeMCU-32S38Pin版本最常见资料也最多。SG90舵机一个这是入门最常用的9g舵机几块钱到十几块钱都有。杜邦线3根或者直接焊线也行用来连接舵机和ESP32。面包板一个非必需但我强烈建议准备接线会舒服很多。手机一台Android优先原因后面会讲。USB数据线一根用来连接电脑烧录程序注意选能传数据的线很多买电子模块送的线只能充电。这里再强调一下选型理由。SG90扭矩小、功耗低、价格便宜对于入门项目完全够用。如果你手里已经有MG996R这种大舵机接线方式一样但供电要求更高后面会专门说电源的坑。开发板选择DevKit V1是因为它引脚间距是标准的2.54mm插面包板方便而且很多开发板都带自动下载电路新手烧录成功率更高。1.3 为什么不用Arduino UNO加蓝牙模块我知道很多新手会搜到Arduino UNO加HC-05蓝牙模块的方案但我的建议是既然现在买的是ESP32就完全没必要再外挂蓝牙模块了。原因有三点。第一ESP32芯片本身同时支持经典蓝牙和BLE也就是蓝牙低功耗CPU性能也远超UNO价格却没有贵多少。第二Arduino UNO没有蓝牙你需要额外接线去调HC-05模块。HC-05的AT指令配置对新手非常不友好凡是搜过“hc05蓝牙模块连接不上”的人大概都有体会波特率、主从模式、配对密码任何一个设置不对都可能连不上。而ESP32的BluetoothSerial库开箱即用不需要AT指令不需要复杂的模块配置。第三ESP32通过USB线就能下载程序UNO加HC-05的话还需要操心模块和USB转TTL之间的交叉接线多了一堆出错点。所以这个项目用ESP32自带蓝牙属于把硬件复杂度降到最低的做法。你省下的时间可以用来理解舵机控制和指令协议这才是真正有用的东西。2. 接线前要搞懂的舵机逻辑和电源问题2.1 舵机是靠PWM脉宽“持续跟踪”角度的接线之前必须先知道舵机在等什么电信号。SG90这种模拟舵机内部结构是控制电路加直流电机加电位器。它工作的核心是PWM信号一个周期为20毫秒的脉冲其中高电平的持续时间叫做脉宽。0.5毫秒的脉宽对应0度2.4毫秒的脉宽对应180度中间按比例映射。也就是说你想让舵机转到90度就要持续输出一个周期20毫秒、脉宽1.45毫秒左右的方波。这里有个很多人容易误解的地方舵机不是收到一次脉冲就转到位然后停在那边了。它是“持续跟踪”的每隔一段时间就要收到一个刷新脉冲告诉它“你还应该保持在这个角度”。如果信号丢失舵机可能失去舵力用手可以轻轻掰动。所以你的代码在初始化之后Servo库会负责不断地发送PWM信号你不需要自己在loop里反复写。这个概念和普通直流电机完全不同。普通电机是给高电平就转不给就停。舵机是一个闭环系统内部控制电路会不断读取电位器的角度和当前脉宽对应的目标角度比较然后驱动电机修正误差。你用生活经验来类比的话舵机就像一个有刻度盘的方向盘PWM脉冲是在告诉它“把这个刻度对准中线”它自己负责执行。2.2 哪些引脚能接舵机哪些不能乱接ESP32的引脚比较多但不是每个引脚都能随便用来输出PWM。很多新手随便挑一个GPIO接舵机结果发现编译失败、烧录失败或者舵机不动最后排查一圈才发现是引脚选错了。第一个禁忌是GPIO6到GPIO11这六个引脚它们默认连接到板载flash芯片是不能当普通IO使用的。你如果非要用它输出PWM程序能编译但实际不会有任何反应甚至可能干扰flash通信。第二个禁忌是GPIO34到GPIO39这六个引脚它们只能作为输入不能输出PWM。也就是说接舵机信号线必然没反应。第三个要注意的是GPIO1和GPIO3默认是串口引脚虽然很多开发板能复用但不建议新手在这里折腾。我的建议是直接用GPIO18或者GPIO19这两个引脚既能安全输出PWM又不涉及串口和flash是舵机控制最稳的选择。如果你以后想控制多个舵机GPIO21、GPIO22也都可以用。我见过一个比较典型的故障有人把舵机信号线接到GPIO16程序烧了好几次都失败最后发现那块板子的GPIO16接到了PSRAM虽然理论上可以用但会自动占用。所以新手阶段就按照推荐引脚来不要为了省一根线去碰这些特殊引脚。2.3 SG90接线方式与“共地”原则接线非常简单但越简单越容易在供电上翻车。SG90舵机引线一般有三种颜色棕色线是GND红色线是电源正极橙色线是信号线。接线方式如下棕色线接到GND可以是ESP32的GND也可以是外部电源的GND最好两者都接。红色线接到5V电源正极。橙色线接到GPIO18。这里最关键的是“共地”。如果你的舵机电源来自外部5V电源比如充电宝、锂电池、稳压模块那么外部电源的GND必须和ESP32的GND连在一起。因为舵机信号是一个相对于GND的电压差如果两个设备的地电位不统一信号线送到舵机控制板的电压就不准舵机要么不转要么乱抖。那能不能直接从ESP32板子的5V引脚给舵机供电可以但前提是你只带一个SG90而且USB供电的电流要足够。SG90正常转动时的电流大概在几十到一百多毫安堵转瞬间可能冲到几百毫安。ESP32本身跑WiFi或蓝牙时的电流也不小板载稳压芯片如果压不住电压一跌轻则舵机抽搐重则ESP32直接重启。所以我的做法是调试阶段直接用ESP32的5V引脚给SG90供电跑通之后如果发现舵机抖动就老老实实外接一个5V/2A的电源把GND和ESP32连在一起。如果你用的是MG996R这类大舵机强烈建议直接外接电源不能用开发板供电。大舵机堵转电流动不动就上安培板载LDO根本扛不住。关于电源这个问题我在后面避坑章节还会再详细说因为它真的能制造出各种“像代码BUG但其实不是代码BUG”的现象。3. Arduino IDE里的ESP32开发环境第一次顺利烧录的关键3.1 安装ESP32开发板包并配置镜像源ESP32不是Arduino官方支持的主控芯片所以需要在Arduino IDE里额外安装开发板包。打开Arduino IDE后先进入“文件”菜单里的“首选项”找到“附加开发板管理器网址”这一栏填入Espressif官方维护的JSON地址https://espressif.github.io/arduino-esp32/package_esp32_index.json如果你下载速度很慢可以把这个JSON地址替换成国内镜像源。具体的镜像地址网络上很好找我这里不单独推荐某一家。填好之后点击“工具”菜单选择“开发板”下的“开发板管理器”搜索“esp32”应该能看到“esp32 by Espressif Systems”。点击安装推荐安装正式版而不是预览版。安装过程会下载几百兆文件耐心等几分钟。这一步看起来简单但新手最容易卡在“JSON地址填了但下载失败”上。如果失败先检查Arduino IDE是不是2.x版本管理器和1.x版本有区别。另外安装过程可能需要访问外网网络不稳定时就换镜像源多试几次就好。3.2 板卡选择与端口识别安装完成之后在“工具”菜单里找到“开发板”选择“ESP32 Arduino”下的“ESP32 Dev Module”。注意不要选成“ESP32S2 Dev Module”或者“ESP32C3 Dev Module”哪怕你的芯片型号看起来像这里建议统一选ESP32 Dev Module大部分通用开发板都能正常烧录。然后选择端口。把ESP32开发板用USB线连上电脑工具菜单的“端口”里会出现一个串口设备。如果是CP2102芯片的板子端口名通常是COM3这样的数字如果是CH340芯片也会显示COM口。如果端口列表为空多半是USB驱动没装好需要手动安装CH340或CP2102驱动。Windows系统插上板子如果提示USB设备未识别十有八九就是驱动问题。选好板子和端口之后烧录速度建议保持默认的921600。如果烧录老是失败可以把波特率降到115200成功率会高很多。3.3 先烧一个舵机测试程序确认硬件没问题搞完环境先不要急着上蓝牙。我强烈建议先烧一个最简单的舵机测试程序把硬件链路确认好这样后面做蓝牙对接时出问题更容易定位。在Arduino IDE里新建一个项目复制下面的代码#include ESP32Servo.h #define SERVO_PIN 18 Servo myservo; void setup() { myservo.attach(SERVO_PIN, 500, 2400); myservo.write(90); } void loop() { }这段代码的作用是让舵机转到90度并保持。需要先在“库管理器”里搜“ESP32Servo”并安装这个库在ESP32上的兼容性比Arduino自带的Servo库更好。attach函数里的500和2400表示舵机在0度和180度时对应的脉宽微秒数SG90默认在这个范围直接照抄就行。烧录好之后如果舵机“咔”一声转到90度说明供电、信号线、引脚全都没问题。如果舵机不动先确认舵机信号线是不是插在GPIO18再确认红色线是不是接了5V最后检查共地。这个过程需要单独测试别把蓝牙问题混在一起。4. 蓝牙部分为什么用经典蓝牙SPP而不是BLE4.1 ESP32双模蓝牙和SPP方案的选择ESP32的蓝牙是双模的既支持经典蓝牙也支持BLE低功耗蓝牙。很多评测文章都喜欢强调BLE但我要说的是零基础做“手机控制舵机”这个项目经典蓝牙SPP比BLE友好太多。原因很直白BLE不是像串口那样直接发字节你需要理解GATT服务、服务UUID、特征值UUID还要在手机端写一个配套App或者用支持BLE调试的工具去读写特征值。这些概念对新手来说太抽象了。而经典蓝牙SPP把通信通道仿真成一个串口手机端随便下载一个蓝牙串口助手App连接之后就能直接收发字符。ESP32这边用BluetoothSerial库基本就是把Serial换成SerialBTAPI几乎一样。所以这个项目选SPP本质是把精力集中在舵机控制和字符解析上而不是去啃蓝牙协议栈。等你把这个项目跑通理解了“接受字符、解析指令、执行动作”这套思路之后再去玩BLE也不迟。不过这里有一个手机端的限制需要提前告诉你们SPP在Android平台的支持很成熟App也很多但在iPhone上比较麻烦。iOS不开放SPP通道给第三方App访问除非用特定的BLE方案否则非常折腾。所以如果你手里是iPhone建议换个Android手机来玩这个项目或者直接跳过SPP转向BLE方案。4.2 手机配对与蓝牙串口助手的真实连接流程蓝牙配对这里藏着一个很多人都会踩的坑在手机系统蓝牙设置里搜索到“ESP32_Servo”并配对成功但打开蓝牙串口助手后却发现连不上。原因是这样的经典蓝牙SPP需要在App端建立一个实际的数据连接而不只是系统层面的配对。手机系统的配对只是把两个设备“认识”了但具体使用数据通道还需要App去主动connect。所以正确流程是先打开手机系统蓝牙搜索设备找到“ESP32_Servo”后点击配对配对码一般是0000或1234。配对成功之后再打开蓝牙串口助手在软件里的设备列表中选择“ESP32_Servo”这时App才真正建立SPP连接。如果App里显示连接失败不要反复去系统设置里找先把之前配对的设备“取消配对”然后重新搜索和配对再回App连接。我遇到过很多次就是因为系统记住了旧的配对状态导致后面App连接不上。这个习惯很重要遇到蓝牙连接异常第一反应不是研究App而是先把配对记录清掉重来。4.3 BluetoothSerial库的核心用法先介绍一下库的基本函数心里有数之后再看代码会更顺畅。使用BluetoothSerial库时你需要先定义一个全局对象BluetoothSerial SerialBT;然后在setup里启动SerialBT.begin(ESP32_Servo);括号里的字符串就是蓝牙搜索时看到的设备名称你可以随便改。之后的操作跟串口完全一样用SerialBT.available()判断有没有收到字符用SerialBT.read()读一个字符用SerialBT.print()和SerialBT.println()回消息给手机。这段代码之所以简单就是因为ESP32官方已经把所有SPP底层细节封装好了。你在手机上发送的字符流会一个个出现在SerialBT这个对象里就像你在电脑上往ESP32的Serial串口发数据一样。理解了这一点后面写指令解析就水到渠成。5. 完整示例代码手机指令如何变成舵机角度5.1 需要提前安装的库正式写代码之前确认两个库已经就位一个是前面说过的ESP32Servo需要在库管理器里手动搜索安装另一个是BluetoothSerial这个库包含在ESP32开发板包里不需要单独安装。如果你在编译时提示找不到BluetoothSerial.h说明ESP32开发板包没有装好先回到第3节检查。5.2 指令解析为什么不能只用一个toInt再说一个新手特别容易踩的坑用SerialBT.readString()配合toInt()去解析角度看起来简单实际用起来很痛苦。因为手机端蓝牙串口助手发送数据并不是一次一个字符它可能一次性发来一串字符也可能只发一半而toInt()在缓冲区里挑数字跳着读容易把奇怪的内容当成0舵机毫无征兆归零。更通用的做法是自己维护一个字符串缓冲区把收到的字符一个一个拼起来直到收到换行符再判断这串字符是不是合法角度。这样做的逻辑更清晰也方便调试。手机App端发送数字时如果勾选“发送新行”程序就能按行切分即使没勾选我还会加一个超时机制字符停顿一段时间后也视为一条命令结束容错性更好。5.3 完整示例程序与逐段解释直接给出完整代码#include BluetoothSerial.h #include ESP32Servo.h #define SERVO_PIN 18 BluetoothSerial SerialBT; Servo myservo; String buffer ; unsigned long lastReceiveTime 0; int parseAngle(String s) { // 只允许纯数字命令避免非数字内容误转成0 if (s.length() 0) return -1; int sum 0; for (unsigned int i 0; i s.length(); i) { char c s.charAt(i); if (c 0 || c 9) return -1; sum sum * 10 (c - 0); if (sum 180) return -1; } return sum; } void processCommand(String cmd) { cmd.trim(); if (cmd.length() 0) return; // 兼容 90 这种带尖括号的写法方便某些串口助手发送 if (cmd.startsWith() cmd.endsWith() cmd.length() 3) { cmd cmd.substring(1, cmd.length() - 1); } int angle parseAngle(cmd); if (angle 0 angle 180) { myservo.write(angle); Serial.println(Angle: String(angle)); // 电脑端调试观察 SerialBT.println(OK: String(angle)); // 手机端回显 } else { SerialBT.println(ERR:0-180); } } void setup() { Serial.begin(115200); // 电脑串口调试 myservo.attach(SERVO_PIN, 500, 2400); SerialBT.begin(ESP32_Servo); // 蓝牙设备名 Serial.println(Bluetooth Servo ready); } void loop() { while (SerialBT.available()) { char c SerialBT.read(); if (c \n || c \r) { processCommand(buffer); buffer ; } else { buffer c; lastReceiveTime millis(); } } // 部分手机App不发送换行符字符停顿超过50ms也视作命令结束 if (buffer.length() 0 millis() - lastReceiveTime 50) { processCommand(buffer); buffer ; } }代码看起来不短但逻辑很清楚。setup里做了三件事打开电脑串口把舵机信号绑定到GPIO18启动蓝牙服务并命名为ESP32_Servo。loop里每一轮都检查蓝牙缓冲区有字符就读进来遇到换行符就调用processCommand处理。parseAngle函数的作用是校验收到的内容是不是0到180之间的数字避免“abc”这种乱码被当成0而把舵机打到零位。processCommand先处理带尖括号的格式再解析角度合法就转动并回显不合法就回复错误信息。这里的回显很重要你可以在手机App上看到一个“OK:90”如果发送的指令有误也能立刻看到“ERR:0-180”方便判断究竟是手机App的问题还是代码的问题。5.4 编译和烧录注意事项复制这段代码到Arduino IDE时记得先确认开发板是“ESP32 Dev Module”端口选对。编译过程中如果报错说找不到ESP32Servo.h回到库管理器搜索安装ESP32Servo。有些版本还需要在工具菜单里把“Flash Size”设置成4MB如果默认已经识别就不用动。烧录过程中如果出现“Failed to connect to ESP32: Timed out”不用慌按住开发板上的BOOT按钮不放点击烧录看到“Connecting... ...”提示时再松开BOOT按钮绝大多数情况下都能成功。这个问题在USB转串口芯片不稳定的板子上尤其常见多试几次完全正常。6. 手机侧实操与调试一整套完整的验证方法6.1 蓝牙串口助手App怎么选打开应用商店搜“蓝牙串口助手”或者“Serial Bluetooth Terminal”能找到一大堆原理基本一样。我的建议是找一个界面里有输入框、发送按钮、和“发送新行”复选框的App因为这三个功能对我们调试最有用。大多数蓝牙串口助手在连接成功后会有一个提示你可以先在手机系统蓝牙里完成配对再在App里选择设备连接。如果你手头的App连接不上不妨换个App试试。不同App对不同品牌手机蓝牙栈的兼容性有差异多换几个不丢人毕竟目标是让项目跑通。6.2 发送指令的准确姿势和调试顺序连接成功后先在手机App的输入框里输入“90”点击发送。如果舵机没反应但App界面里收到了“OK:90”的回复说明蓝牙通信是通的问题在舵机供电或接线。如果连回复都没有说明蓝牙数据通道没建立起来回去检查配对状态。我建议第一次测试按这个顺序来慢慢输入“90”发送看舵机是否转。发送“0”看舵机是否回零位。发送“180”看是否能转到另一侧。发送“abc”看是否收到“ERR:0-180”。最后一步非常重要能验证你的指令解析是否可靠。如果你发现发送“abc”舵机反而转到0度说明代码里用的是简单toInt()需要换成我前面提供的parseAngle版本。另外要记得手机App发送时最好勾选“发送新行”这样发送的字符串后面会自动带上换行符代码一收到换行符就会立刻处理。如果你用的App没有这个选项代码里的50毫秒超时机制也能兜底但响应速度会稍慢一点点不影响使用。6.3 跑完之后我发现的那些奇葩现象调试过程中会遇到一些看起来特别像模块坏了的情况这里列一个对照表方便排查现象发送“90”后舵机完全不动但App显示连接正常。对策首先确认GPIO18是否正确然后检查红色线是否接了5V最后看信号线是不是插在面包板上接触不良。现象舵机抖动像抽搐一样。对策八成是电源问题换外部5V电源共地不要只靠USB供电。现象发送“180”能转到大概位置但角度误差很大。对策检查attach里的微秒参数不同批次SG90的脉宽范围有差异可以手动微调500和2400这两个数字。现象发送几次之后蓝牙自动断开。对策某些手机蓝牙省电策略会在设备空闲时关闭SPP连接去系统设置里关闭该蓝牙设备的省电优化或者换一台手机测试。现象发送“10”之后舵机有轻微转动但是找不到90度的位置。对策这种一般是脉宽范围不对SG90的0度和180度不一定完全对应0.5ms和2.4ms可以根据实测把attach的第二、第三个参数改成550和2450这类接近值。这些都是实际项目里很常见的问题我以前调试时被供电不足折磨过整整一下午。现在回想起来如果一开始就遵循“先测舵机测试程序再上蓝牙”的流程至少能省出一个小时。7. 蓝牙控舵机跑通之后扩展思路与我的调试习惯当你做完这一步其实已经掌握了两类重要技能一类是ESP32的PWM控制能力另一类是蓝牙串口的数据交互能力。这两个能力组合起来可以玩出很多花样。先说说多路舵机。ESP32Servo库支持同时控制多个舵机你只需要再定义几个Servo对象分别绑定到不同引脚然后手机端发送类似“1:90,2:45”这样的复合指令解析时按第一个数字选舵机通道后面跟角度值。这也是未来做机械臂、四足机器人、云台的基础。要注意的是多路舵机并联时供电压力更大每一路舵机都需要充足的电流否则会互相干扰。再说说总线舵机。普通模拟舵机只能接收角度指令无法把当前角度回传给主控。如果你想做机械臂闭环控制比如让机械臂知道自己现在在什么位置那就要用带反馈的总线舵机。这类舵机走UART或者RS485协议ESP32有多个串口配合总线舵机的库也能控制但指令协议就比PWM复杂得多。我建议先把今天的普通舵机玩熟再决定要不要跳进这个坑。还有一个方向是BLE。如果你需要iPhone控制或者未来想接小程序、AppBLE是主流方案。ESP32的BLE开发可以用BLE UART这样的现成库其实也是封装好的只是手机端需要一个真正的BLE调试工具比如LightBlue这类而且是读写特征值而不是直接收发字符。等你在SPP模式下理解了什么叫做“收到一个字节”再去看BLE的service和characteristic就没有那么懵了。最后分享一个我个人的调试习惯。每次拿到新板子或者新模块我一定会先写一个最小测试程序把硬件部分单独验证一遍就像前面说的舵机归零测试。然后再叠加功能。如果一次项目里所有功能都混在一起出了问题就很难排查你会分不清到底是蓝牙断了、还是代码崩了、还是舵机电源不够。把问题拆小一步步验证看起来慢实际是最快的。这个项目的价值在于每一步你都能看到实实在在的东西信号线接对了舵机会动蓝牙连上了手机能回显代码写对了指令能控制角度。这三样东西组合在一起就是未来无数智能硬件的雏形。后面如果你想给ESP32加个网页控制界面或者接上温度传感器做自动调节无非是在这个框架里多加一条输入或输出的链路而已。
返回列表