基于AT指令与CC2540的BLE开发:从原理到Arduino环境监测站实战
1. 项目概述:Seeed BLE Shield v1,一个被低估的蓝牙开发“老伙计”
如果你在几年前开始接触Arduino和物联网,并且想给项目加上无线连接能力,那么“Seeed BLE Shield v1”这个名字你大概率不会陌生。它是一款基于TI CC2540蓝牙4.0芯片的Arduino扩展板,主打低功耗蓝牙连接。在BLE 5.0甚至5.3已经普及的今天,回过头来看这块“老”板子,它更像是一个特定时代的产物,承载了许多创客和开发者的入门记忆。今天,我们不谈那些参数爆炸的新品,就专门来聊聊这块经典的Shield,拆解它的设计思路、核心玩法,以及在当下它还能在哪些场景里发挥余热。对于刚入门的开发者,理解这样一款结构清晰、文档丰富的经典模块,远比直接上手复杂的新模块更能打好基础。
这块板子的核心价值在于,它将一个完整的BLE从机(Peripheral)解决方案,以最“Arduino”的方式呈现出来——直接插在UNO、Leonardo等主流开发板上,通过UART串口与主控通信。你不需要关心复杂的射频电路设计,也无需从零开始啃蓝牙协议栈,只需要像操作串口一样发送AT指令,就能让设备具备广播、连接、数据传输的能力。这种设计极大地降低了蓝牙开发的门槛,让开发者可以专注于上层应用逻辑。虽然它的性能在今天看来有些“复古”,但正是这种简洁和直接,让它成为了学习BLE通信原理、进行快速原型验证的绝佳工具。
2. 核心硬件与设计思路拆解
2.1 核心芯片:TI CC2540的功与过
Seeed BLE Shield v1的核心是德州仪器(TI)的CC2540。这是一颗集成了8051内核和2.4GHz射频前端的单芯片蓝牙低功耗解决方案。在它诞生的年代,这是一款非常成功的产品,被广泛应用于各类蓝牙手环、智能门锁、遥控器等设备中。
它的“功”在于高度集成和低功耗。一颗芯片就包含了微控制器、射频收发器、协议栈和丰富的外设(ADC、定时器、UART等),开发者可以将其作为主控使用,也可以像本Shield一样,仅用作通信协处理器。其低功耗特性使得由电池供电的物联网设备能够运行数月至数年。Shield板的设计正是利用了其作为协处理器的模式:CC2540独立运行TI的BLE协议栈,并通过UART与Arduino对话,Arduino则负责处理传感器数据、逻辑控制等应用层任务,两者分工明确。
它的“过”,或者说局限性,从今天的视角看也很明显。首先,它仅支持蓝牙4.0(BLE),不支持后续版本的高速、远距离、高广播数据量等特性。其次,其内置的8051内核主频和资源有限,处理复杂任务的能力较弱。最后,作为一款较老的芯片,其开发工具和社区活跃度已不如当前主流芯片(如Nordic的nRF52系列、TI自家的CC2640系列)。但对于学习而言,这些“过”恰恰成了优点——协议简单、资源有限,迫使你必须写出更高效的代码,更能理解BLE通信的本质。
2.2 板载资源与接口设计解析
拆开看这块Shield的板载资源,其设计思路非常“教科书”:
- UART通信接口:这是Arduino与CC2540对话的唯一通道。板载了一个电平转换芯片(通常是TXB0104或类似),确保Arduino的5V逻辑电平能与CC2540的3.3V I/O口安全通信。通信波特率默认为9600或115200,可通过AT指令修改。
- 状态指示灯:通常包含一个电源指示灯(PWR)和一个连接状态指示灯(STA)。STA灯的闪烁模式能直观反映设备状态(如快闪表示广播中,常亮表示已连接),这是调试时最直观的辅助工具。
- 按键与模式选择:板上一般会有一个复位按键(用于重启CC2540)和一个模式按键。长按模式按键可以使模块进入AT指令模式(此时UART用于接收AT指令)或数据透传模式(此时UART数据直接打包为BLE数据发送)。这个物理按键的设计避免了软件配置的麻烦。
- 天线设计:采用PCB板载倒F天线(IFA),这种天线成本低、体积小,足以满足一般室内环境下的通信需求(约10-30米)。但需要注意,天线区域下方应尽量避免铺铜或放置金属物体,以免影响信号。
- 引脚兼容性:作为标准Arduino Shield,它完美兼容UNO的引脚布局。需要注意的是,它占用了Arduino的硬件串口引脚(D0-RX, D1-TX)。这意味着当你使用Shield进行蓝牙通信时,将无法通过USB串口打印调试信息(因为共享了同一个硬件UART)。这是新手最容易踩的坑之一。
注意:由于占用了硬件串口,强烈建议在调试阶段使用
SoftwareSerial库将蓝牙模块连接到其他数字引脚(如D2, D3)进行通信,这样主串口(D0, D1)仍可用于连接电脑进行Serial.print输出,极大方便调试。
3. 固件、AT指令与通信模式详解
3.1 固件类型与选择
CC2540需要运行特定的固件才能工作。Seeed通常为这块Shield预烧录了两种主流固件之一:TI Sensortag固件或基于TI BLE Stack的通用AT指令固件。
- TI Sensortag固件:这是一个演示性固件,它将CC2540模拟成一个包含温度、湿度、气压等多种虚拟传感器的设备。手机APP(如TI的“SensorTag”)连接后可以直接读取这些“传感器”数据。这个固件适合用于快速演示和体验,但定制化能力弱。
- 通用AT指令固件:这是更常用、也更强大的选择。模块上电后,可以通过串口发送特定的AT指令集来配置模块的所有参数,如设备名称、广播间隔、连接间隔、串口波特率、配对密码等,并实现数据透传。我们后续的实操都将基于此固件。
如果你的模块行为异常或想更换固件,可能需要使用TI的CC Debugger编程器配合Flash Programmer软件重新烧录.hex文件。这是一个稍进阶的操作,但对于深入理解模块很有必要。
3.2 核心AT指令集实战
AT指令是控制模块的“语言”。以下是一些最核心、最常用的指令及其详解。假设我们使用Arduino的SoftwareSerial库,在波特率9600下与模块通信。
// Arduino端示例:设置SoftwareSerial,使用引脚2(RX)和3(TX)连接BLE Shield #include <SoftwareSerial.h> SoftwareSerial bleSerial(2, 3); // RX, TX void setup() { Serial.begin(9600); // 用于调试输出到电脑 bleSerial.begin(9600); // 与BLE模块通信 delay(1000); // 等待模块启动 // 发送测试指令 bleSerial.println("AT"); delay(100); if(bleSerial.available()) { String response = bleSerial.readString(); Serial.print("Response: "); Serial.println(response); // 应返回 "OK" } } void loop() { // 主循环 }常用AT指令列表:
| 指令 | 功能描述 | 示例(发送) | 预期响应 | 说明与注意事项 |
|---|---|---|---|---|
AT | 测试连接 | AT | OK | 最基本的指令,用于检查串口通信是否正常。 |
AT+NAME? | 查询设备名称 | AT+NAME? | +NAME=<name> | 查询当前蓝牙广播时的名称。 |
AT+NAME=<name> | 设置设备名称 | AT+NAME=MyBLE | OK | 设置名称,立即生效,断电保存。 |
AT+ROLE? | 查询角色 | AT+ROLE? | +ROLE=0 | 通常返回0(从机角色)。该Shield一般固定为从机。 |
AT+BAUD? | 查询波特率 | AT+BAUD? | +BAUD=4 | 返回一个数字代码,如4代表9600。 |
AT+BAUD=<n> | 设置波特率 | AT+BAUD=4 | OK | 设置UART通信波特率。危险操作:设置后需立即将Arduino串口波特率改为对应值,否则后续通信中断。常用代码:4(9600), 5(19200), 6(38400), 7(57600), 8(115200)。 |
AT+PASS? | 查询配对码 | AT+PASS? | +PASS=123456 | 查询六位数字配对密码。 |
AT+PASS=<nnnnnn> | 设置配对码 | AT+PASS=654321 | OK | 设置六位数字配对密码。手机等主设备连接时需要输入。 |
AT+ADDR? | 查询MAC地址 | AT+ADDR? | +ADDR=xx:xx:xx:xx:xx:xx | 查询模块唯一的蓝牙MAC地址。 |
AT+VERSION? | 查询固件版本 | AT+VERSION? | +VERSION=... | 了解当前固件信息。 |
AT+RESTART | 重启模块 | AT+RESTART | OK | 软重启模块,使部分设置生效。 |
| (进入透传模式) | 开始数据透传 | 无特定指令 | (进入无响应状态) | 通常通过特定操作进入:1. 发送AT+NOTI1开启通知。2. 发送AT+START。3. 或直接短接模块上某个测试点再上电。具体需参考对应固件手册。进入后,串口收发的所有数据都将通过BLE通道与连接的主设备交换。 |
实操心得:在编写AT指令控制代码时,务必在每个指令发送后添加足够的延时(如100-200ms),并读取和解析返回的数据。模块处理指令需要时间,连续快速发送指令会导致模块无法响应或响应错乱。一个好的做法是为每个AT指令编写一个带超时判断的响应等待函数。
3.3 通信模式:AT指令模式 vs. 透传模式
这是理解该模块工作的关键。模块有两种基本工作模式:
- AT指令模式(配置模式):上电默认状态(或通过按键触发)。在此模式下,通过串口发送的以“AT”开头的字符串会被识别为指令并执行,返回结果。此模式用于所有配置工作。
- 透传模式(数据传输模式):通过发送特定AT指令(如
AT+START)或硬件触发进入。在此模式下,模块不再识别AT指令,串口收到的任何数据都会被直接打包成BLE数据包发送给已连接的中心设备(如手机);反之,从中心设备收到的数据也会原样从串口输出。此时,蓝牙通道就变成了一个透明的无线串口。
模式切换的典型流程:
- 上电 → AT指令模式(配置设备名称、波特率、密码等)。
- 发送
AT+START(或触发硬件)→ 进入透传模式,开始广播。 - 手机APP搜索并连接“MyBLE”,输入密码“654321”。
- 连接成功后,Arduino通过
bleSerial.print("Hello")发送的数据,手机APP就能收到;手机APP发送的数据,Arduino可以通过bleSerial.read()获取。 - 透传模式下,如何返回AT模式?通常有两种方法:① 使用模块上的硬件模式按键;② 在固件支持的情况下,发送一个特殊的“退出序列”,如连续发送“+++”(注意不带回车换行,且前后需要至少1秒的静默时间),但这取决于固件具体实现,最可靠的方式还是硬件按键。
4. 完整项目实战:构建一个蓝牙环境监测站
现在,我们将理论付诸实践,用一个完整的项目来串联所有知识点。我们的目标是制作一个基于Arduino、BLE Shield v1以及温湿度传感器(如DHT11)的环境监测站,将数据通过蓝牙发送到手机APP上显示。
4.1 硬件连接与准备工作
所需材料清单:
- Arduino Uno 开发板 x1
- Seeed BLE Shield v1 x1
- DHT11 温湿度传感器模块 x1
- 面包板、杜邦线若干
- 手机一部(安装好串口调试APP,如“LightBlue Explorer”、“nRF Connect”或“Serial Bluetooth Terminal”)
接线步骤:
- 安装BLE Shield:直接将Seeed BLE Shield v1插在Arduino Uno的引脚排母上。注意:这会占用硬件串口(D0, D1)。
- 连接DHT11:
- DHT11的VCC引脚接Arduino的5V。
- DHT11的GND引脚接Arduino的GND。
- DHT11的DATA引脚接Arduino的数字引脚4(我们选择这个引脚,以避开可能冲突的0,1,2,3引脚)。
- 为调试预留串口:由于主串口被占用,我们使用
SoftwareSerial与BLE模块通信。将BLE Shield的TX引脚(发送端)连接到Arduino的D2(设置为RX),将BLE Shield的RX引脚(接收端)连接到Arduino的D3(设置为TX)。这里极易接反!记住一个原则:设备的TX应接MCU的RX,设备的RX应接MCU的TX。BLE Shield的引脚标号通常在板子边缘。
4.2 Arduino端代码编写与解析
我们将代码分为几个部分:库引入、引脚定义、全局变量、初始化设置、主循环。
#include <SoftwareSerial.h> #include <DHT.h> // 1. 定义软串口引脚,用于连接BLE模块 #define BLE_RX_PIN 2 // Arduino的RX,接BLE的TX #define BLE_TX_PIN 3 // Arduino的TX,接BLE的RX SoftwareSerial bleSerial(BLE_RX_PIN, BLE_TX_PIN); // 2. 定义DHT11引脚和类型 #define DHTPIN 4 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); // 3. 全局变量 unsigned long previousMillis = 0; const long interval = 5000; // 数据发送间隔,5秒 void setup() { // 初始化调试串口(连接电脑) Serial.begin(9600); Serial.println("Env Monitor BLE Start..."); // 初始化与BLE模块的软串口 bleSerial.begin(9600); // 确保与模块当前波特率一致 delay(1000); // 等待BLE模块启动 // 初始化DHT传感器 dht.begin(); // 可选:在此处发送AT指令配置BLE模块 // configureBLE(); } void loop() { unsigned long currentMillis = millis(); // 每间隔一段时间读取并发送传感器数据 if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; // 读取温湿度 float humidity = dht.readHumidity(); float temperature = dht.readTemperature(); // 默认为摄氏度 // 检查读取是否成功 if (isnan(humidity) || isnan(temperature)) { Serial.println("Failed to read from DHT sensor!"); return; } // 在电脑串口监视器打印 Serial.print("Humidity: "); Serial.print(humidity); Serial.print(" %\t"); Serial.print("Temperature: "); Serial.print(temperature); Serial.println(" *C"); // 构建要发送给手机的数据字符串 // 格式可以自定义,例如: "T:25.6,H:60.2" String dataToSend = "T:" + String(temperature, 1) + ",H:" + String(humidity, 1); // 通过软串口发送给BLE模块(此时模块应处于透传模式) bleSerial.println(dataToSend); Serial.println("Data sent via BLE: " + dataToSend); } // 监听来自手机的数据(如果手机发送了指令,例如请求实时数据) if (bleSerial.available()) { String receivedCommand = bleSerial.readStringUntil('\n'); // 假设以换行符结束 receivedCommand.trim(); // 去除首尾空白字符 Serial.print("Received from BLE: "); Serial.println(receivedCommand); // 可以在这里解析指令,例如收到“GET_DATA”就立即发送一次数据 if (receivedCommand == "GET_DATA") { // 立即触发一次数据读取和发送 previousMillis = currentMillis - interval; // 让下一次循环立刻执行 } } } // 可选的BLE配置函数 void configureBLE() { Serial.println("Configuring BLE..."); sendATCommand("AT+NAME=EnvMonitor"); // 设置设备名 delay(200); sendATCommand("AT+PASS=123456"); // 设置配对码 delay(200); sendATCommand("AT+BAUD=4"); // 设置波特率为9600 (代码4) delay(200); // 注意:更改波特率后,需要同步修改 bleSerial.begin(9600) 并重启模块 sendATCommand("AT+RESTART"); Serial.println("BLE Configured. Please restart module or change baud rate."); } // 发送AT指令并等待响应的辅助函数 String sendATCommand(String command) { bleSerial.println(command); delay(150); // 等待响应 String response = ""; while (bleSerial.available()) { char c = bleSerial.read(); response += c; } response.trim(); Serial.print("CMD: "); Serial.print(command); Serial.print(" -> RSP: "); Serial.println(response); return response; }代码关键点解析:
- 双串口管理:
Serial对象用于连接电脑调试,bleSerial对象用于与蓝牙模块通信,两者完全独立。 - 定时发送:使用
millis()函数进行非阻塞延时,每5秒读取一次传感器数据并通过bleSerial.println()发送。println会自动在字符串末尾添加换行符\r\n,这有助于手机APP区分数据帧。 - 数据格式:我们定义了简单的“T:25.6,H:60.2”格式。手机APP端需要按照此格式进行解析。定义清晰、简洁的数据协议是物联网项目成功的关键。
- 指令响应:
loop()中持续监听bleSerial,如果手机发送了指令(如“GET_DATA”),可以立即响应。这实现了简单的双向交互。 - 配置函数:
configureBLE()函数展示了如何用AT指令初始化模块。重要:一旦更改了波特率(如AT+BAUD=8设为115200),你必须将代码中的bleSerial.begin(9600)改为bleSerial.begin(115200)并重新上传程序,否则通信会失败。
4.3 手机端连接与数据接收
- 上传代码:将上述代码编译并上传到Arduino。确保在
setup()中暂时注释掉configureBLE()的调用,除非你确定需要修改模块配置。 - 进入透传模式:根据你的模块固件手册,使其进入透传模式。对于很多预烧录AT固件的模块,上电后默认就在透传广播状态。观察模块上的STA指示灯是否在快速闪烁(广播状态)。
- 手机操作:
- 打开手机蓝牙设置,搜索附近设备。你应该能看到名为“EnvMonitor”(或你设置的名字)的设备。
- 打开“Serial Bluetooth Terminal”这类APP(在应用商店搜索即可找到)。
- 在APP内扫描并连接“EnvMonitor”,输入配对码“123456”。
- 连接成功后,APP通常会打开一个终端界面。如果一切正常,你将每隔5秒看到一行“T:xx.x,H:xx.x”格式的数据刷新。
- 你可以在APP的发送框输入“GET_DATA”并发送,观察电脑的串口监视器是否会打印出接收到的指令,并且设备是否会立即回复一条数据。
5. 常见问题、调试技巧与进阶思考
5.1 典型问题排查速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 电脑串口能收到AT返回的“OK”,但手机搜不到设备 | 1. 模块未进入透传/广播模式。 2. 模块天线附近有强烈干扰或金属屏蔽。 3. 手机蓝牙兼容性问题或之前配对缓存。 | 1. 确认已发送AT+START或通过硬件触发进入透传模式(STA灯快闪)。2. 将设备移至开阔处,远离路由器、微波炉等。 3. 重启手机蓝牙,或忘记已保存的该设备后重新搜索。 |
| 手机能连接,但收不到数据 | 1. Arduino代码中bleSerial发送部分未执行。2. 手机APP未正确订阅接收数据的特征值(对于通用APP)。 3. 波特率不匹配。 | 1. 检查电脑串口监视器,看是否打印了“Data sent via BLE:”。 2. 使用“LightBlue Explorer”连接后,找到“FFE1”之类的特征值,点击“Subscribe”或“Listen for notifications”。 3. 确认Arduino代码中 bleSerial.begin()的波特率与模块当前波特率一致。 |
| 发送AT指令无任何回应 | 1. 接线错误(TX/RX接反)。 2. 波特率设置错误。 3. 模块处于透传模式(不响应AT指令)。 4. 模块损坏或供电不足。 | 1. 检查TX-RX交叉接线。 2. 尝试常见的波特率:9600, 115200等。 3. 按模块上的模式键或复位键,使其返回AT指令模式。 4. 用万用表检查VCC和GND电压是否稳定在3.3V/5V。 |
| 连接非常不稳定,容易断开 | 1. 信号强度弱。 2. BLE连接参数(间隔、延迟)设置不佳。 3. Arduino loop执行过慢,阻塞了通信。 | 1. 改善设备摆放位置。 2. 通过AT指令(如 AT+INTERVAL,具体指令需查手册)适当增加连接间隔。3. 优化代码,避免在 loop中使用长延时delay(),改用状态机或millis()定时。 |
| 数据在手机端显示乱码 | 1. 手机APP与设备字符编码不一致。 2. 数据中包含非ASCII字符或格式错误。 | 1. 确保APP设置为接收文本(Text)模式,而非十六进制(Hex)。 2. 在Arduino端发送纯ASCII字符串,并确保以 \r\n结尾。 |
5.2 调试技巧与实操心得
- 分而治之:永远不要试图一次性调通所有环节。先确保Arduino能通过
Serial打印出正确的传感器数据。再确保bleSerial能正常收发AT指令(收到“OK”)。最后再用手机连接测试数据流。 - 善用指示灯:模块上的STA灯是状态机。常灭(可能未上电)、慢闪(可能AT模式)、快闪(广播中)、常亮(已连接)。通过灯的状态可以快速定位问题阶段。
- 电源是关键:蓝牙射频发射时瞬时电流较大。务必使用稳定可靠的电源为Arduino供电,避免使用老旧或功率不足的USB线。供电不足会导致模块反复复位或无法连接。
- 软件串口的局限性:
SoftwareSerial在较高波特率(如115200)下长时间通信可能不稳定,尤其是在执行其他中断服务时。如果项目对可靠性要求高,可以考虑使用Arduino Mega(多硬件串口)或Leonardo(用Serial1)来连接BLE模块。 - 数据协议设计:简单的“键值对”字符串(如
T:25.6)对于调试很方便,但不够高效。如果数据量大或需要发送多种信息,可以考虑设计更紧凑的二进制协议,或者使用标准的JSON格式,这样手机APP(尤其是自己开发的APP)解析起来会更规范。
5.3 项目进阶与扩展思路
虽然BLE Shield v1性能有限,但依然可以玩出很多花样:
- 多传感器融合:除了DHT11,可以连接光照传感器、空气质量传感器(如MQ-135)、声音传感器等,构建一个多功能环境监测站。注意规划好Arduino的引脚和电源负载。
- 低功耗优化:CC2540本身支持低功耗,但Arduino Uno的MCU(ATmega328P)功耗较高。要实现真正的低功耗设备,可以将Arduino替换为同样使用ATmega328P但设计为低功耗的Pro Mini板,并在代码中深度睡眠,仅定时唤醒读取数据并通过BLE发送。这需要仔细设计BLE连接间隔与睡眠周期的匹配。
- 充当蓝牙网关:让Arduino + BLE Shield作为一个集中器,连接多个其他的BLE传感器(如心率带、防丢器),将数据汇总后,通过其本身的串口发送给树莓派或ESP32等更强大的主机,再由主机上传到云端。这时,BLE Shield的角色就从终端设备变成了一个简单的BLE主机扫描器(需要支持主机模式的固件,但v1标准固件通常只支持从机模式,这是一个限制)。
- 与手机APP深度交互:不仅仅是数据上传,可以让手机APP发送控制指令。例如,在环境中监测到温度过高时,手机APP可以发送指令,让Arduino控制一个继电器打开风扇。实现完整的双向智能控制。
回过头看,Seeed BLE Shield v1就像一位朴实无华的老朋友,它没有炫酷的参数,但提供了最直接、最稳定的BLE接入方式。通过这个项目,你真正掌握的不是某一个模块的使用,而是**“微控制器 + 专用通信协处理器”** 这种经典物联网架构的实战经验,以及AT指令控制、串口通信、数据协议设计、无线调试这一整套方法论。这些技能,在你未来使用ESP32的蓝牙、LoRa模块、NB-IoT模组时,都将完全适用。技术迭代,但解决问题的底层逻辑是相通的。