蓝牙台灯:手机通过 HC-05 / HC-06 经典蓝牙控制一盏台灯——开关、拖动滑块无级调光、三档预设、定时关灯,单片机每次执行后回报当前亮度。
这一篇回答的搜索:蓝牙控制升压台灯、AppInventor 蓝牙、hc05 蓝牙模块 app 设计、hc05 app 开发、App Inventor 蓝牙与单片机通信源码、蓝牙小车控制界面、怎么用蓝牙组件、蓝牙客户端、读取蓝牙传过来的数据、ble 和 spp 的区别。
界面

图为由最终 .aia 生成的布局预览,不是真机截图。
通信协议
手机和单片机之间只约定一条指令,简单到可以用任何串口助手手动测试:
| 方向 | 内容 | 含义 |
|---|---|---|
| 手机 → 台灯 | L:60\n |
亮度设为 60%(0 = 关灯,范围 0~100) |
| 台灯 → 手机 | OK:60\r\n |
已执行,当前亮度 60% |
开关灯不用单独指令:关灯发 L:0,开灯发上次的亮度。
硬件与单片机程序
| 器材 | 说明 |
|---|---|
| Arduino Uno/Nano | 其它能跑 Arduino 的板子也行,改引脚即可 |
| HC-05 或 HC-06 | 经典蓝牙(SPP)模块,默认波特率 9600、配对码 1234 或 0000 |
| MOSFET 驱动模块(如 IRF520) | 用 PWM 控制灯的电流;台灯若带升压/恒流驱动板,接它的 PWM 调光输入 |
| 灯与电源 | 12V LED 灯带/灯板与对应电源,和 Arduino 共地 |
接线:HC-05 TXD → D10,RXD ← D11(经 1k/2k 电阻分压到 3.3V),VCC 5V,GND;MOSFET 模块 SIG ← D9。
#include <SoftwareSerial.h>
SoftwareSerial bt(10, 11); // RX, TX
const int LAMP = 9; // PWM 引脚,接 MOSFET 模块 SIG
String line;
void setup() {
bt.begin(9600); // HC-05 出厂波特率
pinMode(LAMP, OUTPUT);
analogWrite(LAMP, 0);
}
void loop() {
while (bt.available()) {
char c = bt.read();
if (c == '\n') {
line.trim();
if (line.startsWith("L:")) {
int level = constrain(line.substring(2).toInt(), 0, 100);
analogWrite(LAMP, map(level, 0, 100, 0, 255));
bt.print("OK:");
bt.println(level);
}
line = "";
} else if (line.length() < 32) {
line += c;
}
}
}
这段程序没有在实物板卡上验证过,接线前请核对你手上模块的引脚定义和电压。
怎么用
- 配对:先在手机「设置 → 蓝牙」里和 HC-05 配对(配对码 1234 或 0000)。App 里的列表只显示已配对的设备。
- 连接:点「选择已配对设备」,选 HC-05。
中途验证:标题栏变成「已连接」,HC-05 的指示灯从快闪变成慢闪(每 2 秒闪两下)。 - 开关与调光:点圆形大按钮开关灯;拖动滑块实时调光,松手后记住亮度;「夜灯 / 阅读 / 全亮」一键切换。
中途验证:底部「设备回报」显示OK:60,说明单片机收到并执行了。 - 定时关灯:下拉选 15/30/60 分钟,倒计时到点自动发
L:0。 - 下次打开 App,点「连接上次的台灯」即可,不用再选。
核心代码
连接已配对设备
AddressesAndNames 是「地址 名称」形式的列表,选中项可以原样交给 Connect,组件会自己截取前面的地址。
when 列表选择器_设备.BeforePicking() {
列表选择器_设备.Elements = 蓝牙客户端1.AddressesAndNames
}
when 列表选择器_设备.AfterPicking() {
if 蓝牙客户端1.Connect(列表选择器_设备.Selection) {
微数据库1.StoreValue("lastDevice", 列表选择器_设备.Selection)
标签_连接状态.Text = "已连接"
} else {
标签_连接状态.Text = "连接失败:确认台灯已上电并且已经配对"
}
}
拖动滑块调光:节流发送
拖动时 PositionChanged 一秒能触发几十次,每次都发蓝牙会把 HC-05 的缓冲塞满、灯反而一卡一卡。做法是事件里只记下最新值,由一个 150 毫秒的计时器发送,而且只在值变化时发。
global 待发亮度 = -1
global 已发亮度 = -1
when 滑动条_亮度.PositionChanged(thumbPosition) {
待发亮度 = round(thumbPosition)
标签_亮度.Text = join("亮度 ", 待发亮度, "%")
}
when 计时器_节流.Timer() {
if 蓝牙客户端1.IsConnected and 待发亮度 >= 0 and 待发亮度 != 已发亮度 {
蓝牙客户端1.SendText(join("L:", 待发亮度, "\n"))
已发亮度 = 待发亮度
}
}
注意:在代码里修改滑块的 ThumbPosition(比如点「夜灯」按钮)同样会触发 PositionChanged。成品里用一个「程序调节」标志把这种情况区分开,否则启动时恢复亮度会误把灯打开。
按行接收单片机回报
蓝牙收到的是字节流,一次可能只到半行。先攒进缓冲,遇到换行再取出一整行:
global 接收缓冲 = ""
when 计时器_接收.Timer() {
if 蓝牙客户端1.IsConnected {
var 可读 = 蓝牙客户端1.BytesAvailableToReceive()
if 可读 > 0 {
接收缓冲 = join(接收缓冲, 蓝牙客户端1.ReceiveText(可读))
while textContains(接收缓冲, "\n") {
var 位置 = startsat(接收缓冲, "\n")
标签_设备回报.Text = join("设备回报:", trim(segment(接收缓冲, 1, 位置 - 1)))
接收缓冲 = segment(接收缓冲, 位置 + 1, textLength(接收缓冲) - 位置)
}
}
}
}
常见问题
| 现象 | 原因与处理 |
|---|---|
| 列表里没有 HC-05 | 列表只显示已配对设备,先去系统蓝牙设置里配对;Android 12 以上还要允许 App 的「附近设备」权限。 |
| 选了设备但连接失败 | HC-05 已被别的手机连着;离得太远;模块处于 AT 模式(指示灯慢闪 1 秒一次,按键上电会进 AT 模式),重新上电即可。 |
| 连上了但灯不亮 | 先用串口助手直接给单片机发 L:100 测试;检查灯电源与 Arduino 共地;MOSFET 模块要接 PWM 引脚(Uno 是 3/5/6/9/10/11)。 |
| 调光时灯闪烁或一顿一顿 | 发送太密,HC-05 缓冲溢出——用上面的节流写法;灯本身的驱动板不支持 PWM 调光也会闪,换成支持调光的驱动。 |
| 「设备回报」一直是 — | 单片机没有回发,或 HC-05 的 TXD 没接到 Arduino 的 RX 引脚(D10)。回报不影响控制,只是看不到确认。 |
| 用 BLE 模块(JDY、HM-10)能用吗 | 不能直接用:那是低功耗蓝牙,要用低功耗蓝牙组件。经典蓝牙(SPP)和 BLE 是两套协议,模块型号说明里写着「4.0/BLE」的就是后者。 |
| 一段时间后自动断开 | 手机息屏省电会断开蓝牙连接;回到 App 点「连接上次的台灯」。需要常亮可参考 防止休眠。 |
用到的组件
相关教程:蓝牙遥控教程 · JDY-34 蓝牙模块 · 经典蓝牙与 BLE 的区别
验证范围
最终 .aia 已通过:正式编辑器载入与保存重开 0 错误、后台导入、Android APK 编译,并用替身蓝牙连接执行了真实积木的 12 项业务检查(连接与状态同步、开灯发送上次亮度、滑块节流只发最新值、预设档位、按行解析回报、定时关灯倒计时、断线提示、重启记住设备与亮度、连接失败提示)。手机端未与实物台灯联调,Arduino 程序未上板验证。详见验证记录。
扫码添加客服咨询