1. 从零认识HC-06:它是什么,以及为什么是它
如果你刚开始接触单片机或者物联网DIY,想给Arduino、STM32这些板子加上无线通信功能,第一个听到的名字很可能就是“HC-06”。这玩意儿在电子爱好者和学生群体里,几乎成了蓝牙模块的代名词。它不像那些复杂的Wi-Fi模块需要配置网络,也不像Zigbee那样有学习门槛,HC-06的核心就一个字:简单。
简单到什么程度呢?你可以把它理解成一个“蓝牙串口转换器”。它一端通过UART(串口)和你的单片机(比如Arduino)对话,另一端通过蓝牙和你的手机或者电脑连接。你的单片机程序完全不用管蓝牙协议栈那些复杂的东西,它只需要像往常一样,通过Serial.print()发送数据,HC-06就会自动把这些数据打包,通过蓝牙无线发送出去;反过来,手机App发来的数据,HC-06也会原封不动地通过串口送给单片机,单片机用Serial.read()就能收到。整个过程,对于开发者而言,透明得就像用了一根无形的串口线。
那为什么是HC-06,而不是其他型号?市面上常见的还有HC-05、JDY-31、ATK-HC05等等。HC-06最大的特点,也是它经久不衰的原因,在于它的从机(Slave)模式固定。它出厂就被设置为一个等待连接的从设备,不能主动去搜索和连接其他蓝牙设备。这个“缺陷”恰恰是它的优势:配置极其简单,没有主从模式切换的烦恼,价格也通常比功能更全的HC-05便宜一点。对于绝大多数“手机/电脑控制单片机”的应用场景(比如蓝牙小车、智能家居开关、数据上传显示),HC-06就是最直接、最经济的选择。你不需要一个模块去连接另一个模块,你只需要一个乖乖等待被连接的终端。
从硬件上看,一个典型的HC-06模块非常小巧,通常只有邮票大小。正面最显眼的是那颗蓝牙芯片(早期多是英国CSR的BC417系列,现在国产方案也很成熟),一个LED状态指示灯,以及关键的6个引脚:VCC(电源,通常是3.3V或5V,具体看模块)、GND(地)、TXD(模块发送端,接单片机RXD)、RXD(模块接收端,接单片机TXD)、STATE(连接状态引脚,可选)、EN(使能引脚,有些模块没有)。记住TXD和RXD的交叉连接,这是串口通信的基础,也是新手最容易接错的地方。
2. 硬件连接与供电:避开第一个大坑
拿到HC-06模块,第一步不是写代码,而是正确接线和供电。这一步看似基础,但至少一半的“模块不工作”问题都出在这里。我们分几个层面来说。
2.1 电压匹配:3.3V还是5V?
这是首要问题。早期的HC-06模块大多基于3.3V逻辑电平设计,其VCC供电和信号引脚(TXD/RXD)都只能耐受3.3V。如果你直接把它接到Arduino Uno的5V引脚和5V逻辑的串口上,轻则通信不稳定,重则瞬间烧毁模块。
如何判断?最稳妥的方法是查看模块的产品说明书或卖家提供的资料。如果找不到,可以观察模块上的稳压芯片:如果有一个叫AMS1117-3.3的芯片(一个三端稳压器),那么模块大概率是5V供电输入,内部降压到3.3V给蓝牙芯片使用。这种情况下,模块的VCC可以接5V,但信号引脚(RXD)仍然是3.3V逻辑,不能直接接5V单片机的TXD,需要电平转换。
更常见的简化版模块,为了降低成本,省去了这颗稳压芯片,直接由蓝牙芯片工作。这种模块必须使用3.3V供电,同时信号电平也是3.3V。对于Arduino Uno这种5V系统,你需要一个逻辑电平转换器(例如TXB0104、74HC125等),或者采用电阻分压等简易方法(不推荐用于稳定项目)。
注意:一个简单的测试方法是,如果你有万用表,在模块未连接时,测量VCC和GND引脚之间的电阻。如果阻值非常小(几欧姆),直接上5V很可能短路,这通常意味着模块是3.3V直接供电的。安全起见,对于不确定的模块,先用3.3V供电测试。
2.2 串口引脚交叉连接
原理很简单:发送端(TXD)要对接接收端(RXD)。但模块和单片机各有TXD和RXD,容易绕晕。
- HC-06的TXD: 要发送数据给单片机,所以应该连接到单片机的RXD(接收引脚)。
- HC-06的RXD: 要接收来自单片机的数据,所以应该连接到单片机的TXD(发送引脚)。
以Arduino Uno为例:
- HC-06 TXD -> Arduino Pin 0 (RX)
- HC-06 RXD -> Arduino Pin 1 (TX)
这里有个关键细节:Arduino的Pin 0和Pin 1也是其USB串口通信的引脚。当你通过USB线给Arduino上传程序或进行串口监视时,这两个引脚是被占用的。如果此时HC-06也连接在上面,可能会造成数据冲突,导致上传程序失败。因此,最佳实践是,在上传程序时,暂时断开HC-06与Arduino Pin 0和Pin 1的连接,或者使用SoftwareSerial库将蓝牙连接到其他数字引脚,实现硬件串口(USB)和软件串口(蓝牙)的分离。
2.3 供电与滤波
蓝牙模块在通信瞬间电流可能达到几十毫安,如果电源内阻大或纹波大,可能导致模块复位或通信错误。务必确保电源稳定。
- 如果使用3.3V供电,确保你的电源(如AMS1117-3.3稳压器)能提供至少150mA的电流。
- 在HC-06的VCC和GND引脚之间,就近并联一个10uF~100uF的电解电容和一个0.1uF的陶瓷电容。电解电容应对低频电流波动,陶瓷电容滤除高频噪声。这个简单的滤波电路能极大提高模块工作的稳定性,尤其是当电机、继电器等大电流设备在同一系统中时。
2.4 STATE引脚的使用
很多HC-06模块引出了一个STATE引脚。这个引脚的电平状态可以反映蓝牙的连接情况:未连接时为低电平(0V),连接成功后变为高电平(通常为3.3V)。你可以将这个引脚连接到单片机的某个数字输入引脚,通过读取其电平来判断蓝牙是否已连接,从而在程序中做出相应处理(比如连接后才允许控制电机),这是一个提升用户体验的好方法。
3. AT命令配置:让模块按你的想法工作
HC-06虽然简单,但也提供了一些基本的配置功能,通过AT命令实现。AT命令是一种古老的、基于文本的调制解调器控制协议,在这里用来设置蓝牙模块的参数。常见的设置包括:修改蓝牙名称(默认通常是HC-06)、修改配对密码(默认1234)、查询波特率等。
3.1 进入AT模式的关键:接线与上电时序
给HC-06发送AT命令,必须让它进入“AT模式”。而进入AT模式的方法,是第一个容易让人困惑的“坑”。不同版本、不同厂商的模块,方法可能略有差异,但最常见的有两种:
- 按键/使能引脚法:模块上可能有一个小按钮或一个标有“KEY”或“EN”的引脚。在给模块上电之前,将这个引脚通过导线连接到VCC(高电平),然后再接通电源。此时模块上的LED可能会慢闪(比如1秒亮1秒灭),这表明进入了AT模式。之后,你就可以通过串口发送AT命令了。发送完命令,断开KEY引脚的高电平连接,重新上电,模块就恢复正常的通信模式。
- 电压法:有些模块没有按键,而是要求在上电时,其RXD引脚(接收引脚)保持在一个特定的高电压(通常是3.3V)。你可以用杜邦线,在接通电源的瞬间,将模块的RXD引脚与VCC短暂触碰一下,然后再连接回单片机的TXD。这需要一点手速。
实操心得:最稳妥的方法是查阅你手上模块的具体资料。如果找不到,可以尝试第一种方法。如果模块上有按键,通常就是它。进入AT模式后,LED的闪烁模式会与平时不同(平时快闪搜索,连接后常亮),这是判断是否成功进入AT模式最直观的方法。
3.2 常用AT命令与通信设置
进入AT模式后,你需要通过一个串口工具与模块通信。你可以使用Arduino IDE的串口监视器,但更推荐功能更专业的工具,如Putty、CoolTerm或者SSCOM。关键点在于:必须将串口工具的波特率设置为HC-06 AT模式下的通信波特率。
这个波特率又是一个变量。常见出厂默认值有9600、38400或115200。如果你不知道,就需要逐个尝试。在串口工具中,发送纯文本“AT”(不带引号),如果模块回复“OK”,则说明波特率匹配,通信成功。
一些最常用的AT命令:
AT: 测试命令,回复OK表示模块正常且处于AT模式。AT+NAME?: 查询当前蓝牙名称。AT+NAME<name>: 设置蓝牙名称,例如AT+NAMEMyRobot。AT+PIN?: 查询当前配对密码。AT+PIN<password>: 设置新密码,例如AT+PIN8888。AT+BAUD?: 查询当前通信波特率。AT+BAUD<num>: 设置波特率。这里的<num>是代码,1代表1200,2代表2400,3代表4800,4代表9600(最常用),5代表19200,6代表38400,7代表57600,8代表115200。例如AT+BAUD4设置为9600。
3.3 配置波特率的一致性
这里有一个至关重要的细节:你通过AT命令设置的波特率,是模块在正常通信模式下与单片机串口通信的波特率。而你在Arduino程序中用Serial.begin(9600)初始化的波特率,必须与之一致。例如,你用AT命令将模块波特率设为9600(AT+BAUD4),那么你的Arduino程序就必须是Serial.begin(9600)。如果不一致,双方就无法理解对方发送的数据,表现为乱码或者根本收不到数据。
我的建议是,将HC-06的波特率设置为9600。这是最通用、最稳定的速率,对于传输控制指令、传感器数据等文本信息完全足够,且对单片机时钟精度要求不高。设置好后,记住这个值,并在你的所有相关设备(单片机程序、手机App调试终端)中都统一使用这个波特率。
4. 单片机端程序编写:以Arduino为例
硬件连好了,模块也配置好了,接下来就是让单片机程序与蓝牙模块对话。我们以最经典的Arduino Uno平台为例,展示两种主流的编程方法。
4.1 方法一:使用硬件串口(简单直接)
这是最直接的方法,利用Arduino自带的硬件串口(Serial)。
void setup() { // 初始化串口通信,波特率必须与HC-06设置的波特率一致 Serial.begin(9600); // 其他初始化代码... } void loop() { // 1. 监听来自蓝牙(手机)的数据 if (Serial.available() > 0) { char receivedChar = Serial.read(); // 读取一个字节 // 处理接收到的字符,例如控制LED if (receivedChar == '1') { digitalWrite(LED_BUILTIN, HIGH); Serial.println("LED ON"); // 通过蓝牙发送回执 } else if (receivedChar == '0') { digitalWrite(LED_BUILTIN, LOW); Serial.println("LED OFF"); } } // 2. 主动发送数据到蓝牙(手机) // 例如,读取一个传感器值并发送 int sensorValue = analogRead(A0); Serial.print("Sensor: "); Serial.println(sensorValue); delay(1000); // 避免发送太快 }这段代码实现了双向通信:手机发送‘1’或‘0’控制板载LED,同时Arduino每秒向手机发送一次模拟引脚A0的读数。
缺点:如之前所述,硬件串口(Pin 0, Pin 1)与USB编程口共享。上传程序时必须断开蓝牙模块的TXD/RXD,否则会冲突。
4.2 方法二:使用软件串口(推荐)
为了解决上述冲突,我们可以使用SoftwareSerial库,将蓝牙模块连接到任意两个数字引脚,创建一个独立的软件模拟串口。
#include <SoftwareSerial.h> // 定义软件串口:RX接模块的TXD,TX接模块的RXD SoftwareSerial myBluetooth(2, 3); // RX, TX void setup() { Serial.begin(9600); // 硬件串口用于USB调试输出 myBluetooth.begin(9600); // 软件串口用于蓝牙通信,波特率匹配HC-06 pinMode(LED_BUILTIN, OUTPUT); Serial.println("System Ready..."); } void loop() { // 监听蓝牙指令 if (myBluetooth.available() > 0) { char cmd = myBluetooth.read(); Serial.print("Received from BT: "); // 在USB串口监视器显示,便于调试 Serial.println(cmd); if (cmd == '1') { digitalWrite(LED_BUILTIN, HIGH); myBluetooth.println("Status: ON"); // 通过蓝牙回复 } else if (cmd == '0') { digitalWrite(LED_BUILTIN, LOW); myBluetooth.println("Status: OFF"); } } // 可以通过硬件串口(USB)发送指令测试蓝牙 if (Serial.available() > 0) { char usbCmd = Serial.read(); myBluetooth.write(usbCmd); // 将USB收到的命令转发给蓝牙 } }在这个例子中,蓝牙模块连接到数字引脚2和3。myBluetooth对象就是通往蓝牙的串口。我们保留了硬件Serial用于通过USB线在电脑上打印调试信息,这是一个非常实用的技巧。你可以同时在电脑上打开串口监视器,看到系统状态和从手机发来的指令,极大方便了调试。
4.3 数据协议与解析
上面的例子是单字符命令,实际应用中可能需要更复杂的指令,比如“MOTOR,150,200”(控制两个电机速度)。这就需要设计简单的通信协议并进行解析。
一种常见的方法是使用字符串分隔符。约定以换行符\n作为一条指令的结束。
String inputString = ""; // 用于存储接收到的字符串 bool stringComplete = false; // 标志是否收到完整指令 void loop() { // 持续读取蓝牙数据 while (myBluetooth.available()) { char inChar = (char)myBluetooth.read(); if (inChar == '\n') { // 如果收到换行符,说明指令结束 stringComplete = true; } else { inputString += inChar; // 否则将字符添加到字符串 } } // 如果收到完整指令 if (stringComplete) { Serial.print("Command: "); Serial.println(inputString); // 解析指令 if (inputString.startsWith("MOTOR")) { // 找到逗号分隔符 int firstComma = inputString.indexOf(','); int secondComma = inputString.indexOf(',', firstComma + 1); if (firstComma != -1 && secondComma != -1) { int speedA = inputString.substring(firstComma + 1, secondComma).toInt(); int speedB = inputString.substring(secondComma + 1).toInt(); // 调用控制电机的函数 setMotorSpeed(speedA, speedB); myBluetooth.println("MOTOR SET OK"); } } // 清空字符串,准备接收下一条指令 inputString = ""; stringComplete = false; } }这种方式的容错性比单字符好,可以传输更丰富的信息。在手机App端,发送指令时在末尾加上换行符即可。
5. 手机端连接与控制:App的选择与通信测试
单片机端准备就绪后,另一端就需要一个设备来连接和控制它。最常用的就是手机。
5.1 蓝牙串口调试App
对于开发和测试阶段,不需要自己开发App,直接在手机应用商店搜索“蓝牙串口”或“Serial Bluetooth Terminal”,会有大量免费好用的调试工具。我常用的是“Serial Bluetooth Terminal” (Android) 和 “LightBlue” 或 “Serial” (iOS)。这些App核心功能类似:搜索蓝牙设备、配对连接、打开一个终端界面进行收发数据。
连接步骤通常是:
- 打开手机蓝牙设置,搜索设备。你应该能看到你之前通过AT命令设置的蓝牙名称(如
MyRobot)。 - 点击配对,输入密码(默认1234或你修改后的密码)。
- 打开蓝牙串口App,在App内选择已配对的设备
MyRobot进行连接。 - 连接成功后,App的终端界面就可以发送和接收数据了。尝试发送一个‘1’,看看Arduino板上的LED是否点亮,同时接收区是否收到“LED ON”的回复。
5.2 使用App Inventor或MIT App Inventor快速制作控制界面
如果你不满足于简单的终端,想有一个带按钮、滑块的控制界面,但又不会Java/Kotlin或Swift开发,那么MIT App Inventor是绝佳选择。它是一个图形化的在线App开发工具,拖拽组件就能生成安卓App。
核心步骤:
- 在界面设计器中,添加
ListPicker(用于选择蓝牙设备)、Button(连接/断开、控制指令)、Label(显示状态)、Slider(调节速度)等组件。 - 在逻辑设计器(块编辑模式)中,使用
BluetoothClient组件。它的核心块包括:BluetoothClient.AddressesAndNames: 获取已配对设备列表。BluetoothClient.Connect: 连接指定地址的设备。BluetoothClient.SendText: 发送文本数据(就是我们单片机程序能解析的指令,如“MOTOR,150,200”)。BluetoothClient.ReceiveText: 接收文本数据(读取单片机发来的状态信息)。
- 将按钮点击、滑块拖动等事件与
SendText块连接,并填入要发送的指令字符串。
用App Inventor,你可以在半小时内做出一个带有方向控制按钮和速度滑块的蓝牙小车控制器,非常直观。它生成的App可以直接安装到安卓手机上使用。
5.3 通信调试与常见问题
在手机与模块通信测试时,可能会遇到以下问题:
- 搜不到设备: 确保HC-06已通电(LED快闪)。检查手机蓝牙是否已开启,并尝试刷新搜索列表。有些旧款手机或iOS设备对蓝牙2.0/2.1(HC-06是蓝牙2.0版本)支持不佳,可能无法搜索到,但通常配对后可以连接。
- 配对失败: 确认输入的PIN码正确。尝试默认的“1234”或“0000”。
- 连接后立即断开: 检查单片机端的程序是否在持续、快速地通过串口发送大量数据?有些低端模块或App处理不过来会导致断开。尝试降低单片机发送数据的频率。
- 发送数据无反应: 这是最常见的问题。请按以下顺序排查:
- 波特率: 确认手机App内设置的通信波特率与HC-06模块、单片机程序三处完全一致(通常是9600)。
- 接线: 再次检查TXD/RXD是否交叉连接正确。
- 供电: 用万用表测量HC-06的VCC电压是否稳定且在允许范围内。
- 共地: 确保手机充电宝(如果使用)供电的系统,其GND与单片机系统的GND是连接在一起的。所有通信设备必须共地。
- 程序逻辑: 在单片机程序中,通过硬件串口(USB)打印调试信息,确认程序是否运行到了数据接收和处理的部分。
6. 进阶应用与稳定性优化
当基础通信调通后,可以考虑一些进阶应用和优化措施,让你的项目更可靠、更专业。
6.1 多模块通信与主从配置
虽然HC-06是从机,但如果你需要两个单片机之间通过蓝牙通信,就需要至少一个主机模块。这时可以选用HC-05模块,它可以设置为主机模式。一个典型的设置是:A设备使用HC-05(设为主机),B设备使用HC-06(从机)。在HC-05的主机模式下,通过AT命令AT+LINK=<addr>(addr是HC-06的蓝牙MAC地址)可以主动连接HC-06,之后两者就可以像有线串口一样透明传输数据了。
6.2 功耗考虑
HC-06作为经典蓝牙(BR/EDR)模块,功耗相对后来的蓝牙低功耗(BLE)模块(如HM-10)要高不少,不适合电池供电且需要长期待机的场景。如果项目对功耗敏感,应考虑BLE模块。对于HC-06,如果可能,可以在单片机程序中控制其电源引脚,在不需通信时彻底断电以节省电量。
6.3 抗干扰与数据完整性
在电机、继电器等干扰源附近,无线通信容易受到干扰。除了之前提到的电源滤波,还可以在软件层面增加校验。
- 数据包结构: 定义简单的数据帧,例如
[起始符][数据长度][数据内容][校验和][结束符]。校验和可以是数据内容所有字节的简单累加和。 - 超时重发: 对于重要的控制指令,如果一段时间内没有收到对方的确认回复,则自动重发。
- 通信协议: 使用更健壮的协议,如SLIP(Serial Line Internet Protocol)或直接在应用层模仿Modbus RTU等格式。
对于Arduino,有现成的库如PacketSerial可以帮助你打包和解析数据包,处理校验和。
6.4 固件升级与版本差异
需要注意的是,市面上HC-06模块鱼龙混杂,不同批次、不同厂商的模块,其AT命令集、波特率甚至引脚定义可能有细微差别。如果你发现按照常见教程的AT命令不响应,最好向卖家索要该批次模块的详细AT指令集文档。有些新版本模块甚至支持更高级的查询命令,如AT+VERSION查询固件版本。
7. 项目实战:构建一个蓝牙遥控智能小车
让我们把以上所有知识点串联起来,完成一个经典的实战项目——蓝牙遥控智能小车。这不仅是对HC-06应用的整合,也涉及电机驱动、电源管理等综合知识。
7.1 系统架构与元件清单
- 主控: Arduino Uno R3
- 蓝牙模块: HC-06 (Slave)
- 电机驱动: L298N双H桥驱动模块(可驱动两个直流电机)
- 电源: 7.4V 2S锂电池组(为电机驱动供电),同时通过L298N的5V输出或独立的5V稳压模块为Arduino和HC-06供电。
- 车体: 双电机底盘套件(带轮子、电机)
- 其他: 杜邦线、开关、电容等。
7.2 电路连接详解
- 电源系统: 这是小车稳定运行的核心。锂电池正负极接L298N的电源输入端子。L298N上的+5V输出引脚(如果启用)可以给Arduino的VIN或5V引脚供电(注意电流是否足够)。更稳妥的做法是使用一个独立的5V/3.3V降压模块从电池取电,单独给Arduino和HC-06供电,避免电机启停对控制电路的干扰。
- 电机驱动: L298N的两个输出通道(OUT1/OUT2, OUT3/OUT4)分别连接两个电机的导线。使能端ENA和ENB通过跳帽使能(或接PWM引脚进行调速)。控制输入端IN1~IN4连接Arduino的数字引脚(如5,6,9,10)。
- 蓝牙连接: HC-06的VCC接Arduino的3.3V或5V(根据模块决定),GND接GND,TXD接Arduino的RX(Pin 0)或软件串口的RX引脚(如Pin 2),RXD接Arduino的TX(Pin 1)或软件串口的TX引脚(如Pin 3)。强烈建议使用软件串口,将HC-06连接到Pin 2和Pin 3。
7.3 核心控制程序逻辑
程序需要完成几件事:解析蓝牙指令、控制电机动作、实现调速。
#include <SoftwareSerial.h> SoftwareSerial BT(2, 3); // RX, TX // 电机控制引脚定义 const int IN1 = 5; const int IN2 = 6; const int IN3 = 9; const int IN4 = 10; const int ENA = 11; // PWM调速引脚 const int ENB = 12; // PWM调速引脚 int speed = 150; // 默认速度 (0-255) void setup() { Serial.begin(9600); BT.begin(9600); pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); // ... 初始化其他引脚 Serial.println("Bluetooth Car Ready!"); } void loop() { if (BT.available()) { char command = BT.read(); Serial.write(command); // 在USB串口回显 switch(command) { case 'F': // 前进 moveForward(speed); break; case 'B': // 后退 moveBackward(speed); break; case 'L': // 左转 turnLeft(speed); break; case 'R': // 右转 turnRight(speed); break; case 'S': // 停止 stopCar(); break; case '0'...'9': // 数字字符,调整速度 speed = map(command - '0', 0, 9, 0, 255); BT.print("Speed set to: "); BT.println(speed); break; } } } void moveForward(int spd) { digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); analogWrite(ENA, spd); analogWrite(ENB, spd); } // ... 实现其他moveBackward, turnLeft, turnRight, stopCar函数在手机App端,你可以设计五个按钮,分别发送‘F’, ‘B’, ‘L’, ‘R’, ‘S’字符,再添加一个滑块,将0-9的值发送给小车来调整速度。
7.4 调试与优化要点
- 电机干扰: 电机启动瞬间会产生很大的反向电动势,可能引起电源电压骤降,导致Arduino或HC-06复位。务必在电机的两个引脚之间并联一个104(0.1uF)的陶瓷电容,以及在电源输入端并联大容量电解电容(如470uF)。
- 控制延迟: 如果感觉遥控有延迟,检查蓝牙串口的波特率是否可提升(如改为115200),同时检查单片机循环中是否有不必要的
delay()。使用非阻塞的定时方式(如millis())来管理发送传感器数据的任务。 - 通信距离: HC-06的标称距离在空旷地带可达10米,但在室内有墙体阻隔时会急剧下降。确保天线部分(模块上的蛇形走线)没有被金属物体完全遮挡。
通过这个完整的项目,你不仅掌握了HC-06的使用,更串联起了嵌入式开发中硬件连接、电源管理、软件逻辑、无线通信和抗干扰设计等多个关键环节。HC-06就像一把钥匙,帮你打开了无线控制世界的大门,它的简单可靠让初学者能够快速获得正反馈,而深入其背后的细节,又能让你学到扎实的工程实践知识。