☰
HC-05蓝牙模块与Arduino Uno控制LED完整教程
2026/9/30 23:58:44 网站建设 项目流程

最近在帮社团做一个蓝牙小装置,需求其实特别朴素:手机按一下,桌上的Arduino Uno板载LED就亮;再按一下,它就灭。硬件方案我选了HC-05蓝牙模块,因为它是SPP串口透传模块,和单片机打交道几乎是零门槛,手机端只需要当普通串口用就行,不需要处理BLE协议栈,也没有UUID、特征值这些麻烦概念。这篇文章是系列第一篇,我把从硬件接线、AT指令配置、Arduino端代码到手机调试的完整过程都记录下来,适合手里正好有Uno和HC-05、但还不清楚怎么把它们串起来的朋友。跟HC-05打交道这几年,踩过的坑我都放在后面专门一节里,照着步骤走,基本一下午能跑通。

1. 项目目标与选型:为什么是HC-05 + Arduino Uno

1.1 先把要做的功能拆干净

很多人一上来就找代码,代码其实是最后一步。先想清楚这个项目到底要解决什么问题。

我要的是“手机无线控制LED亮灭”,拆开看就三件事:

  • 手机端能发出控制指令;
  • 指令能通过无线链路送到Uno;
  • Uno解析指令后控制对应引脚的电平。

这三点对应到硬件上,就是蓝牙模块负责无线传输,Uno负责串口接收和GPIO输出,板载LED作为被控对象。整个链路是单向的,但为了调试方便,我在程序里加了回传功能,让Uno把收到的指令原样发回手机,这样能确认数据真的走到了。

1.2 HC-05的串口透传原理

HC-05是蓝牙2.0时代的经典模块,核心功能简单粗暴:它内置了一个蓝牙收发端和一个UART串口端,外部单片机只要接上它的TX和RX,就能把串口数据“透明”地打包成蓝牙数据发给手机,反过来也一样。

这里的关键词是“透传”。对Uno来说,HC-05就像一个无线延长线,你往串口写数据,手机那边就能收到;手机发来的数据,Uno从串口也能读到。中间不需要TCP/IP协议、不需要组网、不需要知道蓝牙底层怎么分包。这也是我选它而不是ESP32+Cellular之类方案的主要原因:逻辑链路最简单,出问题好排查。

当然,这种简单也有代价。HC-05支持的蓝牙版本较老,传输速率不高,穿墙能力一般,但控制LED这种几个字节的小指令完全够用。如果你要传音频、图片,就别用HC-05了。

1.3 为什么不先上BLE或ESP32

前几年做同类项目,大家经常纠结选HC-05还是BLE模块,或者是直接上ESP32。

我的看法是,如果你是第一次做手机控制硬件的项目,HC-05的学习曲线最友好。BLE模块虽然新,但手机端App要处理BLE的扫描、连接、服务发现、特征值读写,概念一下子翻好几倍,很容易卡在“连上了但收不到数据”。ESP32功耗和性能更好,但它内置Wi-Fi和蓝牙双栈,初学者很容易在烧录、配网这些环节上分心。

HC-05不是最先进的选择,却是最能让你把“串口控制”这件事一次跑通的选择。先跑通,再升级,这个顺序值得遵循。

2. 硬件接线:模块针脚、电平匹配与上电顺序

2.1 HC-05的六个针脚到底怎么接

市面上的HC-05模块常见有两种外壳:一种是带底板的,引出六针排针;一种是纯裸模块,引出排针引脚。无论哪种,关键引脚都是下面这几个:

引脚名方向说明
VCC电源输入通常接5V,部分裸模块兼容3.3V
GND电源地必须和Uno共地
TXD数据输出接Uno的RX,注意电平问题
RXD数据输入接Uno的TX,注意电平问题
STATE状态输出未连接时低电平,连接后高电平
EN/KEY模式切换接高电平进入AT指令模式

很多教程把STATE忽略掉,但我建议有条件就接一个Uno的数字引脚,比如D4,用digitalRead判断蓝牙是否连接成功。后面调试会让你省很多事。

2.2 我推荐的Uno接线方案:软串口

Uno本身只有一组硬件串口,也就是D0(RX)和D1(TX)。这个串口同时被USB转串口芯片占用了,如果HC-05也接在这两个引脚上,上传程序时两者会互相干扰。新手最常见的现象就是:HC-05接好后,Arduino IDE上传一直失败。

所以我的推荐接法是:用SoftwareSerial库把D2和D3变成软串口,专门给HC-05用,硬串口留给电脑。

接线如下:

  • HC-05 VCC → Uno 5V
  • HC-05 GND → Uno GND
  • HC-05 TXD → Uno D2
  • HC-05 RXD → Uno D3
  • HC-05 STATE(可选)→ Uno D4

注意,Uno的D2是软串口的RX,D3是软串口的TX。HC-05的TXD要接Uno的RX,也就是D2;HC-05的RXD接Uno的TX,也就是D3。交叉接,别接成直连。

2.3 电平匹配和供电细节

HC-05的逻辑电平是3.3V,Uno的I/O电平是5V。绝大多数带底板的HC-05模块已经在板子上做了电平转换,直接接5V问题不大。但如果用的是那种超小裸模块,最好给Uno TX到HC-05 RXD之间加一个分压电阻。

一个稳妥做法:串一个1kΩ电阻在信号线上,再在模块RXD端并联一个2kΩ电阻到GND。这样5V信号能被分到大约3.3V,不至于烧模块引脚。

供电这里有个小坑:有些同学直接用Arduino的5V引脚给HC-05供电,插上USB线后模块的红色LED会亮,但等手机搜索时却发现找不到设备。原因可能是USB口供电能力不足,或者线材太差导致压降太大。建议使用独立5V电源或者换一根短而粗的USB线,实测能解决一多半“搜不到设备”的怪问题。

3. 让HC-05进入AT模式并配置参数

3.1 进入AT模式的硬条件

HC-05出厂时是“从机模式”,默认名称通常叫“HC-05”,默认配对密码是1234或0000。默认波特率分两类:带底板的常见是9600,裸模块常见是38400。但真正决定后续能不能用,是把模块切到AT指令模式,把参数改成自己需要的值。

进入AT模式有两种办法:

  1. 按住模块上的黑色小按键再上电,这是最常用的方法;
  2. 如果模块没有按键,就把EN/KEY引脚接高电平(3.3V或5V),再上电。

判断是否进入AT模式,看模块上的LED闪烁速度。正常工作模式下,LED一般是每秒闪一次左右;AT模式下,LED通常变成约2秒慢闪一次。这个状态很关键,很多人对着一个没进入AT模式的模块敲AT指令,永远只会收到乱码或没反应。

3.2 用USB转TTL下发AT指令

AT配置阶段不要用Uno的软串口,最省事的是USB转TTL模块直接连HC-05。接线方式:

  • USB转TTL TXD → HC-05 RXD
  • USB转TTL RXD → HC-05 TXD
  • 5V → VCC
  • GND → GND
  • EN/KEY接3.3V(如果模块没有按键)

然后打开电脑上的串口调试工具,波特率选38400,发送“AT”,收到“OK”就说明模块正常。如果没反应,把波特率改成9600再试。这个“试两个波特率”的经验可以少走很多弯路。

3.3 我建议配置的三个参数

进入AT模式后,至少做三件事:

AT // 测试,返回OK AT+NAME=HC05-LED // 改蓝牙名称,方便识别 AT+ROLE=0 // 设置为从机角色 AT+UART=9600,0,0 // 设置串口波特率9600,无校验,1个停止位

AT+ROLE这个参数要重点解释一下。HC-05支持主机、从机、回环三种角色,我们手机连接模块的场景,模块必须是从机。如果你设置了主机角色,手机就搜不到它了。之前有个同学把模块设成了主机模式,折腾了一上午,最后发现是ROLE=1,改成0立刻恢复正常。

AT+UART这条指令会把模块通信波特率改成9600,后面Uno代码里的串口波特率也必须是9600,两边不一致就会出现一堆乱码。这里千万不要嫌麻烦跳过,等代码里用默认9600、模块里却是38400时,你会看到串口监视器里全是乱码,指令根本进不去。

配置完成后给模块断电再重新上电,让参数生效。注意AT模式下设置的参数虽然会保存到模块EEPROM里,但有些版本要断电重启才完全生效,别省这一步。

4. 编写Uno端串口控制LED代码

4.1 用SoftwareSerial还是硬串口

前面说了,Uno的硬件串口D0/D1被USB占用了。如果你把HC-05接在D0/D1上,首次上传程序会很不稳定。所以我用了SoftwareSerial库,把串口放在D2和D3上。

SoftwareSerial是Arduino官方库,模拟串口时序,性能瓶颈是处理器要不停翻转引脚,不适合高波特率和高吞吐量。但9600波特率下控制LED完全没问题,占用资源也不严重。

如果你用的是Arduino Mega,板子有多个硬件串口,直接接Serial1更稳。Uno用户请老老实实用软串口。

4.2 完整代码和逐段说明

下面是我实际烧录测试过的代码:

#include <SoftwareSerial.h> // 软串口:RX接D2,TX接D3,对应HC-05的TXD和RXD SoftwareSerial BT(2, 3); // 如果接了STATE引脚,可以拿D4来读连接状态 const int btStatePin = 4; void setup() { // 板载LED引脚,Uno上就是D13 pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); // 调试串口,接电脑 Serial.begin(9600); // 蓝牙串口,跟HC-05的波特率保持一致 BT.begin(9600); pinMode(btStatePin, INPUT); } void loop() { // 检查HC-05是否在AT使用过程中意外进入异常状态 if (digitalRead(btStatePin) == HIGH) { // 已连接,这个分支可以留着以后做逻辑 } if (BT.available()) { // 读一个字节 char cmd = BT.read(); // 把收到的数据回传,确认链路通 BT.print("echo:"); BT.println(cmd); // 顺便在电脑串口也打一份,方便调试 Serial.print("cmd="); Serial.println(cmd); if (cmd == '1') { digitalWrite(LED_BUILTIN, HIGH); BT.println("led:on"); } else if (cmd == '0') { digitalWrite(LED_BUILTIN, LOW); BT.println("led:off"); } else { // 其他字符,提示一下 BT.println("cmd:unknown"); } } }

这份代码的逻辑简单到不能再简单,但有几个细节值得讲。

第一,我特意把收到的字符回传。手机端如果能看到echo的内容,就证明蓝牙链路和Uno串口都正常,出问题能一步定位到是模块还是引脚还是代码。

第二,我没有用String类型去解析字符串。用char一个字节一个字节判断,对于单个字符指令是最高效的写法。如果有人想用“on”“off”这种字符串指令,也不是不行,但要注意结束符处理,稍微复杂一点。

第三,为什么不用delay(500)放在循环里做LED闪烁?因为delay会阻塞整个loop,一旦手机发来连续指令,Uno要等delay结束才能响应。后面如果你要同时处理多个传感器,delay会直接毁掉实时性。留给后续扩展再说。

4.3 上传代码前先断开硬件串口吗

用软串口的好处就是不需要断开HC-05也能上传程序。但如果你还是用了硬串口方案,上传前务必拔掉HC-05连接到D0/D1的两根线,否则Arduino IDE会卡在“上传中”,报avrdude的错误。

另外,如果你手里的Uno是替换过主控芯片的,比如自己把ATmega328P焊上去的,那得先烧好引导程序bootloader,再通过USB下载程序。如果Uno上电后电源灯亮但电脑识别不到串口,优先怀疑引导程序没烧好。给uno板烧录引导本身是个独立话题,这里只提醒一句:买Uno尽量选原厂或带引导的全新主控芯片,省得入门就受挫。

4.4 串口波特率这一关怎么过

代码里Serial和BT都用了9600。Serial连电脑的串口监视器,必须和Arduino IDE右下角的波特率一致,选9600。BT和HC-05的波特率也必须一致,前面AT指令里已经设成9600。

出现乱码时,优先检查这两个9600有没有对不上。我之前遇到过一次“模块能连、手机发指令就是乱码”,折腾半天发现是手机上的蓝牙串口App默认是38400,改回9600立刻好了。这个坑放在下面排错章节细说。

5. 手机端连接与调试:配对、串口App、收发验证

5.1 配对和默认密码

确保HC-05处于正常工作模式且已上电,打开手机蓝牙扫描,会看到一个名字叫“HC05-LED”的设备(如果你按我上面的AT命令改过名的话)。

点击配对,系统会要求输入PIN码,一般是1234,也可能是0000。有一定概率你之前用其他设备配过,手机缓存了旧名称,这时候去蓝牙设置里删掉旧设备重新搜一次,很快解决问题。

有一点很多教程没提:HC-05虽然能同时保存几个已配对设备,但同一时刻只能和一个手机连接。如果你之前用电脑配过,手机再连就要先断开电脑那边。我见过最离奇的情况是模块被邻居手机连上了,自己手机一直显示“配对失败”。

5.2 手机端的串口调试App怎么选

Android上我常用“蓝牙串口助手”这类工具,界面里有一个连接按钮,选设备后就进入收发界面,类似串口调试助手。iOS上没有完美的免费方案,但也有一些支持蓝牙SPP的串口工具,原理一样。

选App的时候有个隐藏坑:很多BLE调试工具只支持BLE设备,不支持HC-05这种经典蓝牙SPP。你打开App发现列表里搜不到HC-05,不是模块坏了,而是App压根不扫经典蓝牙设备。换一个明确写着支持SPP/蓝牙2.0的串口助手就行。

5.3 实测整个控制链路

连接成功后会听到提示音,或者看到模块的STATE引脚变高电平,同时模块LED闪烁频率会变化。然后打开手机App的串口界面,确认波特率选9600,发一个字符“1”。正常情况下:

  • 模块LED变亮;
  • App收到一条“echo:1”;
  • App再收到一条“led:on”。

发“0”,LED灭,App收到“led:off”。

这里有个新手容易误会的地方:手机App发出去的是文本字符“1”,对应的ASCII码是49,Uno读到的就是char类型'1'。不是二进制的1,也不是数字1。如果有人说“我发1没反应”,记得检查App里有没有勾选“Hex发送”。如果勾了Hex,发“1”实际发的是0x01,不是ASCII字符“1”,Uno判断cmd=='1'自然就不成立。

5.4 收到乱码和频繁断开的处理

乱码大概率是波特率不匹配。手机App、HC-05、Uno代码三者必须统一在同一波特率。还有一个容易忽略的点:有些HC-05的AT指令默认波特率是38400,但工作模式波特率是9600,两者不一样。你AT配置完之后,重新上电退出AT模式,模块才会切回9600。如果你在AT模式下直接测试,当然乱码。

频繁断开先怀疑供电。HC-05工作时峰值电流不低,如果供电电压跌落,模块会自动断开。换独立5V供电,或者用移动电源给Uno供电,往往立竿见影。

6. 排错专题:HC-05连接不上的完整排查链路

6.1 从LED状态判断模块处于什么模式

很多同学一上来就说“HC-05蓝牙模块连接不上”,问半天也说不清模块灯是什么状态。我建议先把模块的LED当成诊断口:

  • 上电后LED常亮:模块未进入正常工作模式,可能是AT模式没退出;
  • 上电后LED每秒闪一次:正常待机,等待连接;
  • 上电后LED约2秒慢闪一次:AT模式,手机搜不到或者连不上是正常的;
  • 连接成功后LED可能变成双闪或慢闪。

如果你发现模块一直处于AT模式状态,重新上电前确保EN/KEY引脚没有接高电平,或者按键没有卡住。这一步能排除一多半“连接不上”。

6.2 AT指令无响应的三个排查方向

发AT没反应,按顺序查:

  1. TX和RX有没有接反。USB转TTL的TXD必须接HC-05的RXD,USB转TTL的RXD必须接HC-05的TXD。接成直连的,我见了不下十次。
  2. 波特率对不对。38400没反应就换9600,不要死磕一个。
  3. 是否真的进入AT模式。看LED是不是慢闪。

如果波特率和接线都对,AT还没反应,用万用表量一下模块VCC引脚是不是5V。有时候接触不良,模块其实没工作。

6.3 Uno上传失败和串口复用冲突

如果你用了硬串口接法,上传时HC-05也接着D0/D1,会造成冲突。Arduino IDE上传程序失败,或avrdude提示“programmer is not responding”时,先把HC-05接到D0/D1的线拔掉再上传。上传完再插回去,这是最省心的方式。

如果你换过主控芯片,上传时大概率会卡在“burning bootloader”这步。给Uno板烧录引导不是本项目的重点,但要注意:ATmega328P新芯片时内部没有引导程序,必须先通过ISP下载器烧写bootloader,否则USB串口无法下载程序。买芯片时问清楚是否带引导,能省很多麻烦。

6.4 灯不亮但串口有回显

还有一种少见但很有意思的情况:手机发了“1”,App也收到led:on的回显,但板载LED就是不亮。

先看代码里的LED_BUILTIN。在Arduino Uno上,LED_BUILTIN就是D13,板载LED焊在D13和GND之间。如果你用了别的开发板,LED_BUILTIN对应的引脚不同,甚至根本没有板载LED,代码也能编译通过,但不会有物理现象。

再看是不是引脚被复用了。Uno的D13同时连了SCK,如果外接SPI设备,可能会因为负载导致LED亮度异常,或者直接不亮。控制板载LED时最好把扩展屏、SD卡这类SPI外设先断开再测。

最后一个可能性是把引脚设成了INPUT。digitalWrite对INPUT引脚无效,代码里务必写pinMode(LED_BUILTIN, OUTPUT)。这属于低级错误,但确实发生过。

7. 从“点灯”出发:后续可以扩展的方向

7.1 双机主从通信和状态回传

这个项目跑通后,很多人会想着让两块Uno之间通过HC-05通信,这就是“HC-05双机通信主从信号”的玩法。把其中一块设成主角色,另一块设成从角色,上电后主模块自动搜索并连接从模块,然后两边就能通过串口互相发送字符串了。

我建议下一个项目就做这个。它跟手机控制的差异主要在角色配置和状态机设计,代码层面反而更简单。有了主从通信基础,就能做无线传感节点、遥控小车、多机协同这些进阶玩法。

7.2 定时器中断实现LED闪烁等非阻塞写法

控制单灯亮灭是第一步,第二步就是让LED按要求闪烁。初学者习惯用delay,但这会阻塞整个程序。更好的方式是使用millis()函数做非阻塞延时,或者直接上定时器中断。

Arduino Uno的ATmega328P有多个定时器,可以配置定时器中断来翻转引脚。实现LED闪烁只是最基础的演示,掌握了定时器中断后,你就能同时处理蓝牙指令、传感器采集和LED动画,程序结构会从“顺序执行”升级成“状态机驱动”。

7.3 把单字节指令升级为带校验的协议

现在用“1”和“0”控制开关,够用但不抗干扰。蓝牙环境里偶尔会有噪音字符,如果你把指令升级成类似“AT+LED=1\r\n”这种带帧头和结束符的协议,再在Uno端写解析函数,可靠性会高很多。

更严格一点还可以加CRC校验,做法就是在指令末尾附加一个校验字节,Uno收到后先算校验再执行。这套思路无论以后接传感器、控制舵机、还是做数据采集,都是通用的。本质上,你已经在写通信协议了,这对以后理解物联网项目帮助很大。

7.4 下一篇预告

手机通过HC-05控制Uno板载LED亮灭,到这里已经完成了从硬件到软件的完整闭环。系列下一篇我打算专门写手机端界面:不再用串口调试助手,而是用App Inventor做一个自己的控制界面,按钮亮灭、状态显示、滑块调亮度都能做。到时候你会看到,这套串口指令设计可以直接复用,不需要改Uno代码。

顺便说一句,类似“串口控制LED”的思路,在STM32、ESP32、甚至FPGA上都能见到。用Keil+STM32+Proteus点LED时,核心也是UART接收和GPIO输出,只是外设寄存器配置方式不同。把这篇里的通信逻辑吃透,换平台只是换API的事。所以别觉得点灯太简单,它的价值在于帮你打通“无线串口控制”这条主线,后面接什么功能,只是想象力的问题。

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

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

立即咨询