手机蓝牙控制台灯:HC-05 开关、无级调光、定时关灯(附 .aia 源码与单片机程序)

蓝牙台灯:手机通过 HC-05 / HC-06 经典蓝牙控制一盏台灯——开关、拖动滑块无级调光、三档预设、定时关灯,单片机每次执行后回报当前亮度。

下载 BtLamp.aia 返回搜索需求专题

这一篇回答的搜索:蓝牙控制升压台灯、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;
    }
  }
}

这段程序没有在实物板卡上验证过,接线前请核对你手上模块的引脚定义和电压。

怎么用

  1. 配对:先在手机「设置 → 蓝牙」里和 HC-05 配对(配对码 1234 或 0000)。App 里的列表只显示已配对的设备。
  2. 连接:点「选择已配对设备」,选 HC-05。
    中途验证:标题栏变成「已连接」,HC-05 的指示灯从快闪变成慢闪(每 2 秒闪两下)。
  3. 开关与调光:点圆形大按钮开关灯;拖动滑块实时调光,松手后记住亮度;「夜灯 / 阅读 / 全亮」一键切换。
    中途验证:底部「设备回报」显示 OK:60,说明单片机收到并执行了。
  4. 定时关灯:下拉选 15/30/60 分钟,倒计时到点自动发 L:0。
  5. 下次打开 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 程序未上板验证。详见验证记录。

文档反馈