☰
HC05蓝牙模块与Arduino串口回显:手机控制LED的完整实现
2026/9/30 6:09:47 网站建设 项目流程

前阵子折腾“手机通过HC05控制Uno板载LED亮灭”,第一篇做了基础的单向控制——手机发指令,Arduino执行,灯亮灯灭。但用着用着就发现一个问题:手机这边发完指令,压根不知道板子到底收到没有,灯是真亮了还是没亮,心里没底。所以这次做“回显”,就是让Uno在收到指令后,把状态消息原路发回手机,把“看不见的串口通信”变成“看得见的确认反馈”,顺便把HC05蓝牙模块的配置、主从模式、透传数据、手机端调试这些坑都捋一遍。

1. 回显是什么,为什么非做不可

1.1 从“单向控制”到“双向互动”

单向控制在很多入门例程里都够用:发送“1”开灯,“0”关灯,Arduino收到后执行,完事。但真实场景下一旦距离稍远、蓝牙信号受干扰、或者手机APP或者串口助手发送时多带了个换行符,指令可能就丢了,或者被解析成了错误命令。没有回显,这些问题全靠猜。

回显(Echo)的本意是把接收到的数据原样返回,但在Arduino的应用语境里,我更推荐“状态回显”——不是简单地把你收到的字符发回去,而是Arduino在成功解析指令并执行操作后,把当前状态(比如LED是ON还是OFF)封装成一条消息发回手机。这样手机端就能确认“这条指令确实被正确执行了”,而不是只确认“收到了”。

从控制系统的角度理解,这其实是一个闭环:手机发送指令是前向通道,Arduino回传状态是反馈通道。有了反馈,整个系统才算是真正的双向通信,也才能为后续做错误重发、状态同步、多设备联动打下基础。

1.2 回显机制的核心原理

回显的核心逻辑在Arduino的循环里就三件事:读串口、解析指令、回发状态。

HC05蓝牙模块通过串口和Uno通信,默认波特率常见为9600或38400(取决于固件和配置)。手机端通过蓝牙无线发送数据,HC05收到后从TXD引脚把数据通过串口发给Uno的RX(引脚0),Uno的Serial.read()读到字节后,按约定协议解析,比如判断是否等于'1'或'0',然后把控制引脚的电平拉高或拉低。

回显的关键差异在于:执行完LED操作后,Uno再通过Serial.println()把一条状态消息发回HC05的RXD引脚,HC05再通过蓝牙无线发给手机。手机端蓝牙串口APP的接收区就能看到这条消息。

有一点需要注意:HC05和Uno之间的串口是交叉连接,HC05的TXD接Uno的RX(0号脚),HC05的RXD接Uno的TX(1号脚),GND必须共地。很多人第一次接线会接反,导致收不到任何数据。

1.3 回显在调试和产品化中的价值

做回显不只是“多一行代码”的事,它在整个项目生命周期里都有实际作用。

调试阶段的价值最直接。你写了一个解析逻辑,怎么知道它解析对了?没有回显,你只能靠猜:灯亮了说明收到了,灯没亮说明没收到。但灯没亮分好几种原因:手机没发出数据、蓝牙断了、HC05没配对、串口接线错了、Uno没跑起来、指令格式不对。有了回显,你发一条指令,手机立刻能收到“LED: ON”或“LED: OFF”,就能把问题快速定位到哪一层。

产品化阶段,回显更是刚需。比如做一个蓝牙控制的智能灯,用户手机上点“开灯”,如果灯坏了或者通信异常,用户会以为指令没发出去,体验很差。有回显的话,设备端至少能告诉手机“我收到了,并且执行了”,手机据此更新界面状态。更进一步,还可以回传传感器数据、电池电量、错误码,这就是一个设备状态上报协议的雏形了。

所以说,回显不是锦上添花,而是从“玩具”走向“工具”的必经之路。

2. 硬件接线与HC05模块工作模式

2.1 材料清单和接线细节

先说硬件,默认你手里已经有:

  • Arduino Uno板子一块(板载LED在数字13脚)
  • HC05蓝牙模块一个(主从一体版本,常见的是带底板的那种)
  • 手机一部(安卓苹果都行,苹果需要依赖Lightning转串口的硬件,实测体验一般,建议优先用安卓机调串口)
  • 4根杜邦线(母对母最好,HC05底板引脚间距是2.54mm标准排针,Uno的排母直接插)
  • 可选:面包板、USB转TTL模块(用于单独配置HC05参数)

接线之前强烈建议先给Uno断电,带电插拔串口线容易把模块烧掉。接线方式是:

HC05引脚Uno引脚说明
VCC5V供电,HC05板载稳压可以吃5V
GNDGND必须共地,否则通信电平参考不一样
TXDRX(0号)HC05发送给Uno
RXDTX(1号)Uno发送给HC05
STATE(可选)不接状态指示,用于检测连接状态
EN/KEY(可选)不接或接高电平用于强制进入AT指令模式

这里有个大坑:Uno的0号和1号引脚同时连接着板载USB转串口芯片,也就是说,当你用USB线把Uno连到电脑烧录程序时,HC05也接在同样的串口上,两边会争抢数据。所以每次烧录程序时,最稳妥的做法是拔掉HC05的TXD和RXD两根线,只保留供电和地线,否则烧录可能会失败。我一开始不知道这个,经常烧录时报错“avrdude: stk500_getsync() attempt 1 of 10: not in sync”,排查半天才发现是HC05干扰了串口。

2.2 让HC05进入AT指令模式

HC05有两种工作模式:AT指令模式和透传模式。AT模式用于配置参数,透传模式用于正常收发数据。

进入AT模式的方法,最常见的是:按住HC05底板上的黑色小按键,再给模块上电(或者先上电再按住不放,等2秒),此时模块指示灯会慢闪(大概2秒闪一次),就表示已经进入AT模式。有的模块有EN引脚,把EN接高电平再上电也能进入。

如果你用的是没有底板、裸版的HC05,那就需要把KEY/EN引脚接高电平(接3.3V或5V,看模块耐压,多数可以接5V)再上电。建议新手直接买带底板带按键的版本,省去飞线的麻烦。

进入AT模式后,模块默认波特率是38400(也有老版本是9600),需要用一个USB转TTL模块把它连接到电脑,然后打开串口助手,选择对应的COM口和波特率。注意:AT模式下发送指令必须带回车换行(\r\n),否则模块不响应。你可以用串口助手发送区勾选“发送新行”功能。

常用的AT指令有这些:

指令功能示例
AT测试通信是否正常返回OK
AT+NAME修改蓝牙名称AT+NAME=HC05-LED
AT+PSWD修改配对密码AT+PSWD=1234
AT+ROLE设置主从角色AT+ROLE=0(从机)
AT+UART设置串口波特率AT+UART=9600,0,0
AT+CMODE设置连接模式AT+CMODE=1(任意地址连接)

我实测下来,返回“OK”是AT模式正常工作的最直接体现。有不少人卡在这一步,就是因为没有勾选“发送新行”,或者波特率搞错了。如果发送AT没有反应,试着切换38400和9600两个波特率,再看模块指示灯是不是慢闪。

2.3 主从模式设置与串口参数配置

手机控制Uno的场景,HC05一般工作在从机模式(ROLE=0),手机作为主机主动搜索并连接它。如果要做两块HC05之间通信,才需要设置一主一从,那就是另一个话题了。

设置从机模式,先进入AT模式,发送:

AT+ROLE=0

正常会返回OK。确认一下也可以:

AT+ROLE?

返回值里能看角色编号。

然后设置串口波特率。这里有个关键决策:Uno的Serial默认跑什么速度,HC05就要配合什么速度。我习惯用9600,稳定且够用。指令如下:

AT+UART=9600,0,0

三个参数分别是波特率、停止位、校验位。9600,0,0表示波特率9600,停止位1位,无校验。返回OK后,模块会记住这个配置。下次重新上电,就默认以9600的波特率工作。

有人问为什么要单独设置波特率?因为HC05默认可能是38400(AT模式)或者9600(透传模式),如果你在Arduino代码里用的是9600,模块却是38400,两边比特率对不上,收到的就全是乱码。所以这步务必确认。

设置完成后,给模块断电,再重新上电,此时模块回到透传模式(如果你没有按住按键)。透传模式的指示灯通常是快闪(大概1秒闪几次),等待连接。

2.4 进入透传模式的判断方法

透传模式就是正常通信模式。判断方法主要看指示灯状态:

  • 慢闪(亮1秒灭2秒):AT指令模式
  • 快闪(亮0.1秒灭0.1秒左右):透传模式,等待连接
  • 常亮:已经有设备连接上了

我踩过的一个坑是:重新上电后忘记确认是否退出了AT模式,结果手机搜索蓝牙时能看到模块,但怎么都连不上,或者连上了发数据没反应。后来学会看指示灯:透传模式是快闪,AT模式是慢闪,一目了然。如果还处于AT模式,按一下模块上的复位按键(有些底板有RST键),或者断电重新上电就行。

还有一点:进入透传模式后,波特率要按照你AT指令里设置的9600来跑。手机连接模块后,在串口助手APP里也要把波特率选成9600,收发才能对上。

3. Arduino端代码设计与回显实现

3.1 代码框架:状态机还是直接解析

很多人一开始写蓝牙控制代码,就是最简单的:

if (Serial.available()) { char c = Serial.read(); if (c == '1') digitalWrite(13, HIGH); if (c == '0') digitalWrite(13, LOW); }

这个确实能跑,但有几个问题:

  1. 只能处理单个字符,如果你手机发送的是字符串“ON”、“OFF”,或者带换行,就不好处理。
  2. 没有回显,不知道执行结果。
  3. 没有做非法指令的处理。

所以这次我改成基于字符串解析的版本:从串口读取一行(以换行符\n为结尾),然后比对整个字符串。这样手机端发送消息时,格式更灵活,也方便扩展。

如果后续指令变多(比如要控制多个LED、读取温度、调整亮度),建议用状态机或者switch-case分支,但现阶段一个简单的字符串比对足够清晰。先把回显机制跑通,再考虑扩展。

3.2 完整代码示例(带回显)

下面是我调试通过的一个版本,配套手机蓝牙串口APP使用:

// 手机通过HC05控制Uno板载LED,带状态回显 // 波特率9600,8N1,指令以换行符\n结尾 const int ledPin = 13; // 板载LED,Uno上标着L的那个 String inputString = ""; // 用于缓存串口收到的字符 bool stringComplete = false; // 是否收到完整的一行 void setup() { Serial.begin(9600); pinMode(ledPin, OUTPUT); digitalWrite(ledPin, LOW); // 上电后主动回显一条欢迎消息,顺便验证链路是否通了 Serial.println("SYSTEM: HC05-LED READY"); } void loop() { // 有完整指令时处理 if (stringComplete) { // 去掉末尾的换行和回车 inputString.trim(); // 解析指令 if (inputString == "ON" || inputString == "on" || inputString == "1") { digitalWrite(ledPin, HIGH); Serial.println("LED: ON"); } else if (inputString == "OFF" || inputString == "off" || inputString == "0") { digitalWrite(ledPin, LOW); Serial.println("LED: OFF"); } else if (inputString == "STATE") { // 查询当前状态 if (digitalRead(ledPin) == HIGH) { Serial.println("LED: ON"); } else { Serial.println("LED: OFF"); } } else { // 未识别的指令,回显错误信息 Serial.println("ERR: UNKNOWN COMMAND"); } // 清空缓存,准备接收下一条 inputString = ""; stringComplete = false; } } // 串口中断接收,注意这是Uno默认的软串口机制?不是中断,是轮询。 // 实际上下面这个是标准做法,放在loop里读也可以,但更推荐用串口事件函数。

等等,上面注释里我写串口事件,其实Arduino默认的serialEvent()是在loop最后自动调用的,新版IDE和某些核心上已废弃。为了避免混淆,这里我改成直接在loop里非阻塞读取:

void loop() { // 非阻塞读串口 while (Serial.available()) { char inChar = (char)Serial.read(); inputString += inChar; if (inChar == '\n') { stringComplete = true; } } // 有完整指令时处理 if (stringComplete) { inputString.trim(); if (inputString == "ON" || inputString == "on" || inputString == "1") { digitalWrite(ledPin, HIGH); Serial.println("LED: ON"); } else if (inputString == "OFF" || inputString == "off" || inputString == "0") { digitalWrite(ledPin, LOW); Serial.println("LED: OFF"); } else if (inputString == "STATE") { if (digitalRead(ledPin) == HIGH) { Serial.println("LED: ON"); } else { Serial.println("LED: OFF"); } } else { Serial.println("ERR: UNKNOWN COMMAND"); } inputString = ""; stringComplete = false; } }

这样逻辑更清晰,而且不带任何平台特定的serialEvent依赖。你把上面setup和loop合在一起,就是完整代码。

3.3 关键代码逐段解读

先看这个while循环:

while (Serial.available()) { char inChar = (char)Serial.read(); inputString += inChar; if (inChar == '\n') { stringComplete = true; } }

Serial.available()返回接收缓冲区中的字节数,只要大于0,就说明有数据进来。每次读一个字节,追加到inputString里。遇到换行符\n,就认为一条指令结束。这里我特意用\n而不是\r\n,因为不同的手机APP发送换行方式不一样,有的发\r\n,有的只发\n。Arduino的Serial.read()会把\r和\n都读进来,所以后面用trim()把两端的空白字符(包括\r)去掉,确保比对时不会被干扰。

再来看指令比对:

if (inputString == "ON" || inputString == "on" || inputString == "1") { digitalWrite(ledPin, HIGH); Serial.println("LED: ON"); }

为什么要同时支持“ON”和“1”?因为有的手机APP有自定义按钮,可以把按钮值设为字符串或数字;有的用户习惯发“1”,有的习惯发“ON”。都兼容一下,省得到时候换APP还要改代码。

关键在于,执行完digitalWrite之后,紧跟一行Serial.println("LED: ON"),这就是回显。消息格式用了“LED: ON”这种语义化字符串,而不是只回一个字符。我后来扩展成查询指令STATE,手机发送“STATE”,Uno会返回当前LED状态,这在调试时特别有用——即使手机之前发了指令没收到回显,也能主动问一次。

还有错误处理分支:

else { Serial.println("ERR: UNKNOWN COMMAND"); }

不要小看这个else。如果你手机不小心发了“1\n”以外的垃圾字符,比如蓝牙连接时HC05会转发一些握手信息,或者串口助手默认带上了时间戳,Uno收到无法识别的指令时,会回显一个错误码。这不仅告诉你“确实收到了,但没听懂”,也方便你观察手机端发送的原始内容。很多奇怪问题都是这么定位出来的。

3.4 回显消息格式设计要点

回显消息虽然简单,但格式设计会影响后续扩展。我这里用了三组前缀:SYSTEM、LED、ERR。你们可以根据自己的项目设计一套简单的通信协议。

前缀含义示例
SYSTEM系统级消息,用于上电提示、连接确认SYSTEM: HC05-LED READY
LED控制结果状态LED: ON / LED: OFF
ERR错误信息ERR: UNKNOWN COMMAND

设计原则有三条:

  1. 可读性优先。人眼一看就知道发生了什么,不需要查协议表。像“LED: ON”这种比“0x01”直观得多。
  2. 前缀区分消息类型。这样手机APP收到消息后,可以用contains或者startswith判断是状态还是错误,便于界面更新。
  3. 每条消息以换行结尾。这样通讯双方都能用“按行读取”来切分数据,避免粘包。

如果你以后要做更复杂的功能,比如回传温度,可以定义成“TEMP: 25.6”;回传多个LED状态,可以定义成“LED: [ON, OFF, ON]”。这些都是从回显格式自然延伸出来的。

4. 手机端与HC05的连接和调试

4.1 手机配对HC05的注意事项

手机和HC05的配对,我实测遇到最多的问题就是“找不到模块”或“配对失败”。

首先要确保模块处于透传模式且未被占用。如果之前已经连过其他手机,有些模块不会自动断开老连接,导致新手机搜不到。解决办法:模块断电重新上电,让它重新进入待连接状态。

其次,手机蓝牙列表里可能有好几个设备,注意区分HC05默认名称一般是“HC-05”或你设置过的自定义名称。如果找不到,可以试一下在手机蓝牙设置里“扫描设备”,稍微等待几秒。

配对密码默认是1234或0000,视模块固件而定。输入正确密码后,手机显示“已配对”。但注意,配对成功不等于可以收发数据。与蓝牙音箱不同,HC05作为串口透传模块,配对后还需要手机端APP打开一个虚拟串口连接,才能收发数据。

安卓手机的话,很多自带蓝牙设置不支持“连接”这个服务,需要在APP里发起连接。所以建议直接装一个“蓝牙串口助手”之类的APP,在里面搜索并连接HC05,而不是在系统蓝牙里连。

4.2 使用串口调试APP实测回显

APP的选择,安卓端我常用“Serial Bluetooth Terminal”(中文叫串口蓝牙终端),也可以选“蓝牙调试助手”。IOS端因为蓝牙串口协议支持受限,得用特定的Lightning转串口硬件配合,体验比较折腾,所以这期主要讲安卓。

打开APP后,先搜索设备,找到HC05,点击连接。第一次连接可能会弹出配对请求,输入密码1234/0000。连接成功后,APP会提示已连接,同时HC05指示灯变为常亮。

这时在APP的发送框中输入“STATE”并发送(记得勾选发送新行,或者手动加\n),正常情况下,接收区会显示“LED: OFF”(如果上电后没开过灯)。这就是回显数据,说明整条链路打通了。

接着发送“ON”,接收区应该出现“LED: ON”。同时看一眼Uno板上的L灯,确实亮了。再发送“OFF”,灯灭,回显“LED: OFF”。到这里,一个完整的带回显的蓝牙控制就调通了。

我强烈建议把“发送新行”选项打开。因为Arduino代码里以\n作为指令结束符。如果不发换行,Uno的stringComplete永远不会置true,指令就一直缓存着不执行。这是新手最容易踩的坑——明明发送了“ON”,灯却不动,其实就是换行符的问题。

4.3 手机控制LED亮灭的回显流程演示

我实际的操作记录大致是这样:

  1. 打开APP,搜索到“HC05-LED”,点击连接,状态从“未连接”变“已连接”,模块指示灯常亮。
  2. 发送“STATE\n”,接收区刷出“LED: OFF”,说明链路正常。
  3. 发送“ON\n”,过了一两百毫秒,接收区刷出“LED: ON”,同时板载LED点亮。
  4. 发送“OFF\n”,接收区刷出“LED: OFF”,灯灭。
  5. 故意发送“ABC\n”,接收区刷出“ERR: UNKNOWN COMMAND”,证明错误处理也在工作。

这个流程看着简单,但每一步都在验证不同环节:第一次回显验证了串口链路和蓝牙透传,第二次回显验证了指令解析和执行,错误回显验证了异常处理。整套下来,系统就不再是一个“黑盒”了。

有人问回显延迟会不会明显?我实测在9600波特率下,一条“LED: ON”大概12个字符,每个字符大约1毫秒,加上处理时间,手机端几乎是秒回。如果出现明显延迟,多检查一下是不是手机APP本身卡顿,或者蓝牙信号差导致重传。

5. 典型问题与排查实录

5.1 HC05连接不上:先查硬件,再查配对

“HC05蓝牙模块连接不上”这个问题,几乎每天都有新人问。按以下顺序排查:

现象可能原因排查方法
手机搜不到HC05模块不在透传模式看指示灯:慢闪是AT模式,重新上电
手机搜不到HC05模块被其他设备占用断电重启模块
搜到了,但配对失败密码错误尝试1234或0000,也可以在AT模式下用AT+PSWD查看设定
配对成功,但APP连不上手机自带蓝牙管理限制直接在APP里重新搜索并连接
连接上了,但发指令没反应波特率不匹配检查Uno代码和APP的波特率是否都是9600
连接上了,但收不到回显接线错误检查TXD/RXD是否交叉连接,GND是否共地

我最常碰见的就是AT模式下忘记重新上电,手机当然搜不到。把模块断电再上电,指示灯从慢闪变快闪,就能搜到了。

5.2 能收不能发,或能发不能收

能收到回显但发不了指令,说明HC05到Uno的路径是通的,问题出在Uno到HC05。检查RXD(Uno TX)是否接了HC05的RXD,很多板子引脚标注容易看反。另外确认代码里是否用了Serial.print而不是Serial.println,如果你的协议按换行切分,缺了\n会导致数据一直堆积在缓冲区。

反过来,能发指令但收不到回显,重点检查HC05的TXD是否接到Uno的RX。如果Uno的RX没接好,串口中断收不到数据,当然也就无法解析和执行。还可以试试在setup里加一个Serial.println("TEST"),上电瞬间看手机能否收到,如果收到,说明蓝牙链路和串口方向都正常,问题只在执行逻辑里。

5.3 回显乱码或重复

乱码基本就是波特率不对。手机APP、HC05、Uno三者的波特率必须完全一致。HC05的波特率是在AT模式下用AT+UART设置的,Uno用Serial.begin设置,APP在连接后也要选同样的波特率。任何一处不匹配,收到的都是像“???”这样的乱码。

还有一种情况是回显重复,比如发一条“ON”,手机收到两条“LED: ON”。这通常是因为APP勾选了本地回显(本地回显把发送的字符也显示出来),再加上Uno的远程回显,看起来就像重复了。解决:关掉APP的本地回显选项。

5.4 状态指示灯的含义与异常判断

HC05底板上的LED状态是排查问题的第一参考。我把常见状态列个表:

LED状态含义应对
常亮已连接手机正常
快闪(亮灭间隔约0.5秒)透传模式,未连接等待连接
慢闪(亮约1秒,灭约2秒)AT指令模式断电重启进入透传
不亮没供电或模块损坏检查VCC/GND

如果你看到模块快闪但手机连不上,重点查配对密码和APP连接方式;看到常亮但发送没反应,重点查波特率和引脚接线。总之先看灯,再动手,能省很多时间。

最后说点实际体会

折腾完这一版带“回显”的HC05控制之后,我最大的感受是:多花十分钟写回显,能省下后面两三个小时的排查时间。以前发一条指令灯没亮,我得猜是蓝牙断了还是指令没解析对,现在看一眼手机上的回显消息,问题出在哪一层立刻清楚。而且这套“发送-解析-执行-回显-错误处理”的框架,基本可以直接套用到其他传感器读取、电机控制、甚至多设备通信上。

另外分享一个小技巧:如果你手头有两个HC05,可以试试双机通信,一个主机接UnoA,一个从机接UnoB,主机发指令,从机执行并回显。你会发现,主从模式下的回显机制和手机控制完全一样,只是数据源从手机换成了另一块板子。这个玩法搞通了,距离做一个真正的无线遥控小车或者分布式传感网络就不远了。

这篇的代码和接线都是实测过的基础版本,直接把Hex代码复制进Arduino IDE,按上面的接线方式连好,基本就能跑通。如果还有问题,欢迎在评论区把现象发出来,我尽量帮你定位。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询