教程 · 入门 · 1 小时
用 HC-05 模块通过蓝牙控制机器人
把 HC-05 接到 Arduino 上并与手机配对,再设计一套串口指令协议,让机器人在蓝牙链路断开时依然能及时响应、保持安全。
引言
HC-05 是让机器人变成无线最便宜的办法。它是一个蓝牙串口桥:手机发来的任何东西,都会以普通字节的形式出现在 Arduino 的串口上;Arduino 打印的任何东西,也会回到手机。你的程序里完全不需要知道蓝牙的存在。
这种简单正是它的吸引力,也正是它的陷阱。无线电链路不是一根线——它会掉、会延迟,而且会在车还在跑的时候断掉。本文大部分内容讲的就是这个差别。
接线,以及几乎人人都会犯的那个错
HC-05 模块的逻辑供电是 3.3 V,尽管转接板上的 VCC 接受 5 V。RX 引脚不耐 5 V。
| HC-05 | Arduino Uno / Nano | 说明 |
|---|---|---|
VCC |
5V |
板载稳压器负责转换 |
GND |
GND |
与其他一切共地 |
TXD |
D10(软串口 RX) |
3.3 V 输出被 5 V 输入读作逻辑高没问题 |
RXD |
D11(软串口 TX)经分压器 |
直接灌 5 V 时间久了会损坏 |
EN / KEY |
D9(可选) |
上电时拉高,进入 AT 命令模式 |
用两只电阻的分压器把 Arduino 的 5 V 发送线降到 3.3 V——从 Arduino 引脚到 HC-05 的 RXD 串 2 kΩ,从这个节点到地再接 3.3 kΩ。成本是两个元件,却能避免一种诊断起来极其恼人的故障,因为这样损坏的模块往往还能半工作:它能配对、能发送,然后默默地无视或损坏你发给它的内容。
**用 SoftwareSerial,不要用 0、1 脚。**硬件串口是与 USB 共用的,模块接在那里会跟串口监视器抢线,还会阻塞上传。
#include <SoftwareSerial.h>
SoftwareSerial bt(10, 11); // RX, TX
void setup() {
Serial.begin(115200); // USB, for debugging
bt.begin(9600); // HC-05 default baud
}
配对
给机器人上电,模块的 LED 快速闪烁——那表示“正在等待配对”。在手机上,用配对码 1234 或 0000 与 HC-05 配对。
配对不等于连接。只有当手机上的某个应用真的打开了串口,链路才会建立。起步阶段随便一个蓝牙串口终端都行;连接建立后,模块的 LED 会放慢成双闪。
设计指令协议
最直白的第一个协议是每条指令一个字符,它大约能撑十分钟:
if (bt.available()) {
char c = bt.read();
if (c == 'F') forward();
else if (c == 'B') back();
else if (c == 'S') stop();
}
它有两个问题。
**它没有速度。**每条指令都是全油门,所以车不是停着就是猛冲。改成随指令一起发送数值——一个字母加一个数字,用换行结尾,解析起来很简单,手工输入调试时也很简单:
// "F180\n" — forward at 180 of 255
char buffer[16];
if (bt.available()) {
int n = bt.readBytesUntil('\n', buffer, sizeof(buffer) - 1);
buffer[n] = '\0';
char cmd = buffer[0];
int value = atoi(buffer + 1);
...
}
它假定消息完整到达。bt.available() 非零只意味着有字节到了,不是全部都到了。每轮循环读一个字符并立即动作,就会在半条指令上动作。一直读到换行符,才是让解析可靠的关键。
真正要紧的故障:链路丢失
有线的车,你一松手它就停。无线车会继续做你最后告诉它的事——如果那件事是“全速前进”,而你刚刚走出了覆盖范围,那么结果就是一台全速钻到沙发底下的车。
**每一台无线车都需要一个松手超时。**规则很简单:最近没有指令到达,就停车。
unsigned long lastCommandAt = 0;
const unsigned long TIMEOUT_MS = 500;
void loop() {
if (readCommand()) lastCommandAt = millis();
// Nothing heard recently? Assume the link is gone.
if (millis() - lastCommandAt > TIMEOUT_MS) stop();
}
要让它真的起作用,控制器必须持续说话。让手机重复发送当前指令——按住按键时大约每 100 ms 一次——而不是按下时一次、松开时一次。这样“按键松开”和“手机走出范围”就会得到同一个安全结果,而你也永远不必依赖那条可能永远不会到达的松开消息。
这里要用 millis(),永远不要用 delay()。循环里一个 delay(200) 就是 200 ms 期间不读任何指令、不检查超时,车是完全聋的。
AT 命令模式
要改模块名或改波特率,就在上电时把 EN/KEY 拉高。LED 会缓慢闪烁——大约每两秒一次——这就是你处在命令模式而不是数据模式的标志。
AT → OK
AT+NAME=MyRobot → OK
AT+PSWD="4821" → OK
AT+UART=38400,0,0
AT 模式下的指令必须以 38400 波特率发送,与数据模式的波特率无关,而且大多数固件要求回车和换行都发。如果 AT 什么都没返回,几乎总是这个不匹配造成的。
如果你要回传遥测数据,把数据波特率提上去是值得的;但在协议还没调通之前先留在 9600——链路更快主要只会让分帧错误更难看清。
开跑前先验证
三项检查,按这个顺序做:
- **回声测试。**让 Arduino 把收到的东西原样打印回来。如果手打的一行原封不动地回来,接线和波特率就是对的。
- **解析测试。**把解析出的指令字母和数值打印出来。确认
F180得到的是F和180,不是F和1。 - **超时测试。**发一条前进指令,然后关掉应用或走开。车必须在你设定的超时内停下。如果不停,在轮子上地面之前先把它修好。
链路可靠之后,同一套指令协议可以直接搬进一台带自主功能的机器人:无线层可以看手机遥控车项目,或者升级到 ESP32,它通过 Wi-Fi 提供一个控制页面,完全不需要手机应用。
探索关联图谱
本教程所属的搭建
包含本教程的项目和学习路线。
延伸阅读