多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

蓝牙无线遥控器实现指南:SPP透传、AT指令与帧协议设计

蓝牙无线遥控器实现指南:SPP透传、AT指令与帧协议设计 简介一套基于蓝牙技术的多路无线遥控设计资料面向电子爱好者、嵌入式开发者以及智能家居、工业自动化领域初学者围绕蓝牙模块、51单片机与继电器控制展开帮助读者搭建由手机或专用遥控器远程操控设备的完整系统。压缩包共24个文件总大小约210KB内容涵盖Altium Designer绘制的原理图与PCB工程文件、51单片机C语言源程序、编译生成的hex固件、项目配置与打印文档既可用于电路设计参考也便于直接烧录验证和二次开发。目前已有343人学习下载。借助这套资料读者能够掌握蓝牙模块与51单片机的软硬件联调方法、多路继电器独立控制逻辑理解从原理图到PCB布局的完整设计流程并可根据实际需求修改代码与电路自制多路无线遥控器。1. 蓝牙无线遥控的起点把按键变成串口数据冬天窝在被子里用遥控器红外必须对准机顶盒窗口角度偏一点就失灵客厅里同时控制投影、风扇和氛围灯2.4G 接收器又占满了 USB 口。想用一个遥控器覆盖手机、PC 和嵌入式设备蓝牙无线遥控是改动最小、成本最低的方案——终端侧不需要插任何接收器只要是带蓝牙的设备就能直接收。brotheryz9 这类遥控器拆开看结构并不神秘按键矩阵接一颗 MCUMCU 的 UART 接一块 SPP 串口透传蓝牙模块。按下按键MCU 把键值按固定帧格式发给模块模块通过经典蓝牙协议把数据送到主机。适合给现有设备快速增加无线遥控能力也适合自己复刻一套遥控系统。整条链路的核心就三个字透传、组帧、对参数。2. 蓝牙遥控器的协议选型与 brotheryz9 最小接线方案2.1 经典蓝牙 SPP 还是 BLE 低功耗先回答三个问题蓝牙协议栈分 BR/EDR 和 BLE 两条技术路线。很多人不区分直接买 HC-05 或 JDY-31理由是「能透传、好调试」。这个选择对遥控器通常没错前提是你只做点对点、数据量小、且接收端不用 iOS。做选型时先回答三个问题要不要纽扣电池级低功耗接收端是不是只有 Android 或单片机需不需要一套代码同时兼容手机双端对比项经典蓝牙 SPPBLE 低功耗协议栈BR/EDRRFCOMM 串口仿真GATT蓝牙 4.0典型模块HC-05/06、JDY-31、CSR BC04nRF52832、ESP32、泰凌微手机兼容Android 需支持 SPPiOS 不开放Android/iOS 都支持数据通路串口透传使用门槛最低自定义 Service/Characteristic待机功耗毫安级不适合长期电池供电广播/睡眠交替微安级上手难度AT 指令配置后直接收发要写 GATT 服务和应用层brotheryz9 这种以无线遥控为目标的基础方案我一般用经典蓝牙 SPP 模块。理由很直接接收端多半是手机 App、PC 串口工具或另一块单片机SPP 对三者都天然友好按键事件每秒最多几十字节BLE 的高吞吐优势完全用不上。反过来如果遥控器要塞进纽扣电池、要求用几个月不换那 BLE 才是唯一合理选择。拿 STM32 做红外无线遥控时还要处理 38K 载波调制和发射管驱动换成蓝牙方案之后MCU 只需要管串口发送整个工程复杂度降了一截。2.2 brotheryz9 遥控器的典型接线与电平注意点常见做法是类似 brotheryz9 的底板按键矩阵占用 MCU 的 GPIO蓝牙模块的 UART 接 MCU 的另一组串口模块天线区域悬空不铺铜。以完全兼容 HC-05/06 的 JDY-31 蓝牙 3.0 底板为例它自带稳压和底板端子最少四根线就能跑起来。接线要特别注意 TX、RX 交叉模块 TX 接 MCU RX模块 RX 接 MCU TX。接反是「第一台机器完全没反应」的第一原因。模块引脚单片机侧说明VCC3.6-6V 电源JDY-31 底板带稳压可直接接 5VGND共地不共地时串口电平没有参考点TXUART_RX模块发送到 MCURXUART_TXMCU 发送到模块STATEGPIO 输入高电平表示蓝牙已连接可驱动指示灯EN/KEYGPIO 输出拉低进入 AT 指令模式HC-05 需按键上电电平是另一个高频坑。HC-05 原版模块是 3.3V 逻辑MCU 如果是 5V 的 Arduino 或 STCRX 引脚建议串一个 1kΩ 限流电阻长期直连容易损坏模块 IOMCU 本身是 3.3V 则没有这个问题。JDY-31 底板大多兼容 5V 供电但 IO 引脚按 3.3V 处理更稳妥。2.3 用 AT 指令把蓝牙模块配置成从机模式模块在 AT 模式下配置一次参数掉电保存。HC-05 要按住底板按键再上电才能进入 AT 模式否则发 AT 会收到乱码或完全无响应JDY-31 不需要按键上电后直接发 AT。下面这组命令是 brotheryz9 这类遥控方案的典型配置ATROLE0 # 0从机1主机遥控器是发射端但蓝牙链路里做从机 ATCMODE1 # 1表示任意地址可连接生产环境建议改成0并绑定主机地址 ATUART9600,0,0 # 波特率9600停止位1无校验必须与MCU串口参数一致 ATNAMEbrotheryz9 # 广播名称手机和PC列表里就显示这个名字 ATPSWD1234 # 配对码Android和Windows配对时按这个输入 ATRESET # 复位让以上参数生效ROLE 决定模块是被搜索方还是搜索方。遥控器虽然负责上报数据但按键数据是上行的蓝牙链路里它做从机、等待手机或电脑连接这是最常见也最省电的接法。CMODE 为 1 时任何主机都能连调试方便但没有安全性如果固定配一台主机改成 0 并配合绑定地址能避免邻居的手机误连。UART 参数要和 MCU 完全一致模块、MCU、上位机三处只要有一处对不上就会表现成「连得上但收不到任何数据」。最后提醒HC-05 的 AT 波特率默认 38400而数据通道波特率是另一个参数很多人在这一步把模块误判成坏的。3. 遥控器端固件按键扫描、数据帧与发送时序3.1 数据帧设计长度、键值和序列号缺一不可蓝牙串口透传本质是无线串口它不保证上层数据包的边界。MCU 连续发多个帧主机端可能一次读到好几帧粘包也可能一帧被拆成两次读完断包。所以固件里不能只发裸键值要按帧组织。我常用的帧格式是六个字节字节012345含义帧头长度命令键值序列号校验和示例0xAA0x040x010x030x210xDB帧头 0xAA 用于接收端找起点长度指从命令字到帧尾的字节数这里固定 4命令字区分按下、释放和长按键值是按键编号序列号每次发送加一接收端靠它判断有没有丢帧校验和对前五个字节做 XOR能拦截最常见的单字节位翻转错误。量产项目中我会把 XOR 换成 CRC8多项式用 0x31但协议结构不变后续升级只动校验算法。3.2 STM32 上的最小发送实现扫描、消抖、发帧以 STM32F103 为例串口初始化和 GPIO 配置省略核心是发送函数和按键扫描。发送函数负责组帧扫描函数负责判断按键边沿。static uint8_t g_seq 0; static uint8_t calc_xor(const uint8_t *p, uint8_t len) { uint8_t x 0; for (uint8_t i 0; i len; i) { x ^ p[i]; } return x; } void remote_send_key(uint8_t cmd, uint8_t key) { uint8_t frame[6]; frame[0] 0xAA; // 帧头 frame[1] 0x04; // 长度命令到校验共4字节 frame[2] cmd; // 命令按下/释放/长按 frame[3] key; // 键值 frame[4] g_seq; // 序列号自增接收端判断丢帧 frame[5] calc_xor(frame, 5); for (uint8_t i 0; i 6; i) { while (!(USART1-SR USART_SR_TXE)); // 等待发送寄存器空 USART1-DR frame[i]; } }发送前先组帧、再统一发送避免边组边发导致帧间出现不确定间隔。while 等待 TXE 标志在波特率 9600 下几乎不阻塞每字节约 1ms六字节一帧 6ms对按键事件完全够用。这里故意不用中断发送遥控器是低频率小数据量设备块发送代码最清晰。3.3 长按、释放与组合键命令字里的状态位只发「按下」会让接收端无法区分长按和连击因此命令字我固定这样定义0x01 按下、0x02 释放、0x03 长按连发、0x04 组合键。按下沿发 0x01松开沿发 0x02接收端可以据此实现「单击回首页、长按关机」这类逻辑需要连发时固件在按键持续按住且超过 800ms 后每 120ms 发一帧 0x03。组合键要扩展协议把长度改为 5在多一个字节里放第二个键值void remote_send_combo(uint8_t key1, uint8_t key2) { uint8_t frame[7]; frame[0] 0xAA; frame[1] 0x05; // 长度变为5 frame[2] 0x04; // 组合键命令 frame[3] key1; frame[4] key2; frame[5] g_seq; frame[6] calc_xor(frame, 6); // 发送逻辑同 remote_send_key }扫描部分我一般放在 1ms 定时器中断里做标记主循环只做边沿判断和发帧。按键用内部上拉、按下接地检测到低电平时先消抖 20ms再读一次确认有效然后判断是刚按下还是持续按住。消抖时间短了会误触发长了长按首帧延迟明显20ms 是按键手感比较折中的值。4. 接收端联调手机配对、PC 解析与单片机状态机4.1 HC-05 频繁连接不上的三个排查点模块配置完先别急着写软件用手机或 PC 配对验证链路。HC-05 模块连接不上时我按固定顺序查搜索不到、配对失败、连上无数据。搜索不到模块先看 STATE 引脚。模块已经和另一台设备保持连接时它会从可搜索列表里消失这是最容易误判的情况——把已连接设备断开或者给模块重新上电再搜。其次是确认模块没停在 AT 模式HC-05 按住按键上电后只响应 AT 指令不进入配对状态重新断电上电一次即可。配对失败基本是 PSWD 输错HC-05 默认配对码是 1234Android 配对弹窗直接输入这个值Windows 则会弹「输入设备的配对代码」同样输入 1234。连上但收不到数据九成是波特率不一致模块设 9600串口助手却开着 38400或者数据位/停止位不匹配。手机端可以用 Serial Bluetooth Terminal 这类串口 App 验证能收到键值再进下一步。4.2 PC 端按帧解析Python 串口读回遥控数据PC 端联调我用 Python 的 pyserial 库写个最短解析脚本。注意不能读一个字节处理一次要把串口数据先放进缓冲区再按帧头、长度、校验三个条件切割。import serial ser serial.Serial(COM3, 9600, timeout0.1) buf bytearray() key_map {0x01: POWER, 0x02: OK, 0x03: UP, 0x04: DOWN} while True: data ser.read(64) if not data: continue buf.extend(data) i 0 while i len(buf): if buf[i] ! 0xAA: i 1 continue if len(buf) - i 2: break length buf[i 1] # 长度字段从命令字到校验和 total length 2 # 加上帧头和长度本身 if len(buf) - i total: break frame buf[i:i total] check 0 for b in frame[:-1]: check ^ b if check frame[-1]: cmd, key, seq frame[2], frame[3], frame[4] name key_map.get(key, hex(key)) print(fkey{name} seq{seq} f{DOWN if cmd 0x01 else UP if cmd 0x02 else HOLD}) i total del buf[:i]这段逻辑的关键是把读取和解析分开。read(64) 一次可能拿到半帧、一帧或多帧所以 buf 负责聚合内层循环先找 0xAA 定位帧头再判断缓冲区有没有凑够总长度凑够才取帧。校验通过才打印按键事件校验失败直接丢弃这一帧、继续找下一帧头。seq 字段建议打出来如果发现序列号跳变说明射频层有丢包需要减小数据速率或拉近通信距离。4.3 单片机接收端串口中断加解析状态机接收端如果是一块单片机不要在主循环里用阻塞方式读串口更不要每收一个字节就进一次解析。我一般维护一个四状态状态机状态条件动作IDLE收到 0xAA进入 LEN 状态记录帧头LEN收到长度字节校验范围进入 DATA 状态DATA按长度收完剩余字节进入 CHECK 状态CHECK校验通过调用回调返回 IDLEvoid uart_rx_isr(uint8_t byte) { static uint8_t state 0, idx 0, len 0, buf[8]; switch (state) { case 0: if (byte 0xAA) { idx 0; state 1; } break; case 1: len byte; buf[idx] byte; state 2; break; case 2: buf[idx] byte; if (idx len 1) state 3; // 长度字段后的数据都收齐 break; case 3: uint8_t check 0; for (uint8_t i 0; i len; i) check ^ buf[i]; if (check byte) { // buf[0]是命令buf[1]是键值buf[2]是序列号 remote_on_key(buf[0], buf[1], buf[2]); } state 0; break; } }状态机的好处是中断服务函数里只做赋值和少量比较不调用耗时的发送函数不会阻塞更低优先级的任务。遥控数据本身是事件型主机端拿到回调后做响应比轮询按键状态更省 CPU。5. 进阶验证蓝牙测距、SPP 链路与驱动的排错技巧5.1 用 RSSI 判断遥控器是否进入有效范围蓝牙模块普遍提供 RSSI接收信号强度可以据此粗略估算遥控器与主机之间的距离用在「靠近自动亮屏」「离开自动锁屏」这类场景。工程上常用对数路径损耗模型import math A -59 # 1米处的RSSI参考值实测标定 n 2.0 # 环境系数开阔空间2.0室内有遮挡2.5~3.0 rssi -72 # 从模块或手机读到的实时RSSI d 10 ** ((A - rssi) / (10 * n)) print(festimated distance: {d:.2f} m)A 和 n 如果照抄网上默认值误差能到一倍以上。正确标定方法是把遥控器放在主机正前方 1 米处连续读 30 个 RSSI 取平均得到 A再放到 5 米处读值反推 n。做距离判断时不要用单次 RSSI取最近 5 次的滑动平均设两个阈值形成滞回比如 2 米判定「近」、4 米判定「远」避免人在边界来回走动导致频繁切换。5.2 数据卡顿的隐藏原因A2DP/SCO 链路与蓝牙驱动如果配对正常、波特率正确但按键数据每隔几秒卡一下先关掉主机连接的蓝牙音箱或耳机再试。蓝牙同时传输音频和数据时A2DP/SCO 链路优先级通常高于 SPP射频时隙被音频抢占遥控器数据就会出现周期性延迟。手机和电脑传文件失败、SPP 数据断流很多时候是同一个原因。Windows 主机上还有一个典型现象配对记录删不掉、设备管理器里 Bluetooth 设备显示异常或 generic adapter 驱动报错。常见做法是先把所有蓝牙设备取消配对停掉蓝牙服务再重启让适配器重新枚举一次驱动层面确认用的是系统自带的 Microsoft Bluetooth Enumerator而不是第三方全家桶驱动。有条件的话用蓝牙抓包工具看一下实际发射间隔和重传次数能直接区分是射频干扰还是协议栈调度问题。遥控距离突然变短则先检查模块天线周围是不是新增了金属外壳或地铜皮天线净空区被遮挡会让灵敏度下降几个 dBm反应到距离上就是腰斩。本文还有配套的精品资源点击获取
返回列表