1. 项目概述:为什么Arduino 101的蓝牙值得你花时间?
如果你手头有一块Arduino/Genuino 101开发板,并且已经点亮了LED、读过了传感器,那么恭喜你,是时候解锁它最酷的特性之一了:板载蓝牙低功耗(BLE)。很多新手拿到这块板子,可能只把它当作一个更快的Arduino Uno来用,这实在是有点“暴殄天物”。它的核心价值,就在于那颗集成的Intel Curie模块,原生支持BLE 4.1。这意味着你不需要额外购买HC-05、HM-10这类串口蓝牙模块,也不用处理复杂的接线和AT指令,就能直接让设备无线“开口说话”。
这个教程要解决的,就是把101板子采集到的数据(比如温度、光线强度、按钮状态)通过蓝牙,稳定、可靠地发送到你的手机或电脑上。这不仅仅是点对点传输,更是物联网(IoT)的敲门砖。想想看,一个自制的温湿度计,数据能实时显示在手机App上;或者一个遥控小车,用手机就能直接控制。这些项目的核心通信链路,就是今天要搭建的。整个过程会涉及Arduino编程、BLE协议的基本概念,以及手机端(或电脑端)的调试工具使用。无论你是想做个智能家居原型,还是单纯想玩转无线通信,这篇手把手的指南都会帮你绕过我当初踩过的那些坑,直达目标。
2. 硬件与软件环境准备:别在第一步就卡住
开始写代码之前,确保你的“战场”已经打扫干净。硬件上很简单,你只需要一块Arduino/Genuino 101开发板和一根Micro-USB数据线。软件方面则有几个关键点需要注意,特别是Arduino IDE的版本和库的安装。
2.1 Arduino IDE的版本与板卡支持包安装
首先,我强烈建议你使用Arduino IDE 1.8.x 版本,而不是最新的2.0版本。虽然IDE 2.0界面更现代化,但在对Arduino 101的支持上,1.8.x系列经过多年迭代,更加稳定可靠,社区资源也大多基于此版本。很多在2.0上遇到的诡异问题,换回1.8.x就迎刃而解了。
安装好IDE后,你需要安装Arduino 101的板卡支持。打开IDE,依次点击“工具” -> “开发板” -> “开发板管理器...”。在弹出窗口的搜索框中输入“Intel Curie”。你应该会看到一个名为“Intel Curie Boards by Intel”的条目,点击并安装它。这个过程会下载必要的编译工具链和核心库,包括我们今天要用的蓝牙库。安装时间取决于你的网络速度,请耐心等待。
安装完成后,再次点击“工具” -> “开发板”,现在你应该能在列表里找到“Arduino/Genuino 101”并选中它。同时,检查一下“端口”选项,确保你的101板子通过USB连接后,对应的COM口(Windows)或/dev/tty.usbmodemxxx(Mac/Linux)被正确选中。
2.2 理解101的板载蓝牙与外接模块的本质区别
这里有一个非常重要的概念需要厘清,它能帮你避免后续很多困惑。Arduino 101的蓝牙是板载集成、原生支持BLE的。它与我们常见的、需要通过TX/RX引脚连接的HC-05蓝牙模块有本质区别:
- 协议不同:101使用BLE(蓝牙低功耗),主打低功耗、间歇性数据传输,适合传感器上报;HC-05多是经典蓝牙(Bluetooth Classic),常用于持续音频流或大量数据传输。
- 编程接口不同:对于101,你使用专门的BLE库(如
CurieBLE)进行编程,操作的是“服务”、“特征值”这些BLE概念。对于HC-05,你通常是用SoftwareSerial库把它当成一个无线串口来读写,发送AT指令配置。 - 功耗与成本:101的BLE功耗更低;而外接模块需要额外成本和接线。
所以,当你搜索“Arduino 蓝牙发送数据”时,看到的很多教程是关于HC-05的,那些代码不能直接用在101上。你必须寻找明确标注“Arduino 101 BLE”或“CurieBLE”的教程。我们今天用的正是CurieBLE库,它已经随着板卡支持包一起安装好了。
3. BLE核心概念快速入门:服务、特征值与你
在写代码前,花五分钟理解BLE的基本模型,会让后面的所有操作都变得清晰。你可以把BLE设备(这里就是你的101板子)想象成一个提供信息的“服务器”(Peripheral),而手机或电脑则是“客户端”(Central)来读取信息。
这个“服务器”提供多种“服务”(Service),每个“服务”代表一个独立的功能领域,比如“电池信息服务”、“心率测量服务”。每个“服务”下包含若干个“特征值”(Characteristic),这才是实际存放数据的地方。比如,在“环境传感服务”下,可能有一个“温度特征值”和一个“湿度特征值”。
“特征值”有几个关键属性:
- 值(Value):就是你要发送的数据本身,比如一个整数、一个字符串。
- 属性(Properties):定义了客户端能对这个值做什么。最常用的有:
BLEWrite:客户端可以写入数据到设备(控制设备)。BLERead:客户端可以读取设备的数据(获取传感器值)。BLENotify:设备可以在数据变化时主动“通知”客户端,这是实现实时推送的关键。
- UUID:一个128位的唯一标识符,用来区分不同的服务和特征值。为了简化,蓝牙技术联盟(SIG)定义了一些标准的16位UUID,比如
0x180A代表设备信息服务。我们也可以自定义UUID。
在我们的项目里,101板子将创建一个自定义服务,其中包含一个具有BLERead和BLENotify属性的特征值。这样,手机端既可以随时读取当前数据,也能在数据更新时自动收到通知。
4. 从零编写BLE数据发送程序
理论铺垫完毕,现在打开Arduino IDE,我们开始写一个完整的示例程序。这个程序会让101板子创建一个BLE设备,广播一个服务,并定期(比如每2秒)更新一个特征值,模拟发送传感器数据。
4.1 程序骨架与库引入
首先,在代码开头引入必要的库,并定义一些常量。
#include <CurieBLE.h> // 定义自定义服务的UUID。你可以用在线UUID生成器生成,这里为了演示使用一个简单的。 // 注意:通常自定义UUID格式为 `XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX`,但CurieBLE库也接受简短的格式。 #define SERVICE_UUID "19B10000-E8F2-537E-4F6C-D104768A1214" #define CHARACTERISTIC_UUID "19B10001-E8F2-537E-4F6C-D104768A1214" // 实例化BLE对象 BLEPeripheral blePeripheral; // 创建一个服务 BLEService customService(SERVICE_UUID); // 创建一个特征值,属性为可读(BLERead)和可通知(BLENotify) BLECharacteristic dataCharacteristic(CHARACTERISTIC_UUID, BLERead | BLENotify, 4); // 最后一个参数4表示这个特征值最多容纳4个字节 // 模拟的传感器数据 int sensorValue = 0;关键点解析:
#include <CurieBLE.h>:这是使用101蓝牙功能的基石。BLEPeripheral:代表101板子本身作为一个BLE外设。BLEService和BLECharacteristic:分别创建我们自定义的服务和特征值。BLECharacteristic的构造函数中,第三个参数4很重要。它设定了这个特征值“值”的最大字节长度。这里设为4,是因为我们打算发送一个int类型的数据(在Arduino上通常是2字节,但为了兼容性留足空间)。如果你要发送更长的字符串,需要调整这个大小。
4.2 Setup函数:初始化与广播配置
在setup()函数中,我们需要完成BLE的初始化和广播设置。
void setup() { Serial.begin(9600); // 初始化串口,用于调试输出 while (!Serial); // 等待串口连接(对于某些板子需要) Serial.println("Starting BLE Peripheral..."); // 1. 设置BLE设备的本地名称,这个名称会被手机扫描到 blePeripheral.setLocalName("MyArduino101"); // 2. 设置设备的“广播名称”(Advertised Name),通常与本地名一致 blePeripheral.setAdvertisedServiceUuid(customService.uuid()); // 3. 为设备添加我们创建的服务 blePeripheral.addAttribute(customService); // 4. 为服务添加特征值 customService.addAttribute(dataCharacteristic); // 5. 初始化特征值的初始值 dataCharacteristic.setValue(sensorValue); // 6. 开始广播,等待手机等中心设备连接 blePeripheral.begin(); Serial.println("BLE Peripheral started, waiting for connections..."); }操作意图与避坑点:
setLocalName和setAdvertisedServiceUuid:这两个调用决定了你的设备在手机蓝牙扫描列表里显示为什么,以及携带了什么服务信息。名字起得直观一些,方便识别。addAttribute:这一步非常关键,必须按照“设备->添加服务->服务添加特征值”的层级关系来操作。忘记添加特征值到服务,是导致手机端看不到数据或无法读写的常见原因。begin():调用这个方法后,板子才开始真正广播蓝牙信号。在这之前,手机是扫描不到它的。
4.3 Loop函数:维持连接与更新数据
loop()函数是程序的主循环,在这里我们需要维持BLE连接,并模拟数据更新和发送。
void loop() { // 1. 监听BLE中心设备的连接事件 BLECentral central = blePeripheral.central(); // 如果有设备连接上了 if (central) { Serial.print("Connected to central: "); Serial.println(central.address()); // 当连接保持时 while (central.connected()) { // 模拟传感器数据变化(例如,读取A0引脚) // sensorValue = analogRead(A0); // 为了演示,我们用一个递增的计数器 sensorValue++; if (sensorValue > 1000) sensorValue = 0; // 防止溢出 // 2. 更新特征值的数值 dataCharacteristic.setValue(sensorValue); // 3. 重要:对于 Notify 属性,更新值后,客户端会自动收到通知。 // 对于只有 Read 属性的特征,则需要客户端主动来读。 Serial.print("Sensor value updated to: "); Serial.println(sensorValue); // 通过串口打印出特征值当前的原始字节,用于高级调试 // const unsigned char* rawData = dataCharacteristic.value(); // for (int i = 0; i < dataCharacteristic.valueLength(); i++) { // Serial.print(rawData[i], HEX); // Serial.print(" "); // } // Serial.println(); delay(2000); // 每2秒更新一次数据 } // 当设备断开连接时 Serial.print("Disconnected from central: "); Serial.println(central.address()); } }核心逻辑与经验技巧:
blePeripheral.central():这个调用是非阻塞的,它会立即返回。如果有设备连接或断开,它会返回对应的BLECentral对象,否则返回一个未连接的状态。所以它必须放在loop中持续检查。central.connected():这是一个非常实用的方法,用于在连接保持期间执行你的主业务逻辑(如读取传感器、更新数据)。setValue():这是发送数据的核心。它接受多种类型的参数,如int,float,char*等,库函数会帮你处理字节转换。但你需要确保创建特征值时指定的最大字节数足够容纳你的数据。- Notify机制的优势:我们为特征值设置了
BLENotify属性。这意味着,一旦我们调用setValue更新了数据,所有订阅了此特征值通知的客户端,都会立刻自动收到新数据。这比让客户端不断轮询(Read)要高效和实时得多,是传感器数据上报的首选方式。 - 调试串口:代码中的
Serial.print语句至关重要。它们是你了解程序运行状态的“眼睛”。通过串口监视器,你可以确认设备是否启动、是否被连接、数据是否在更新。如果手机端收不到数据,首先检查串口输出是否正常。
5. 手机端测试与数据接收实战
代码上传到101板子后,它就开始广播名为“MyArduino101”的蓝牙设备了。接下来,我们需要一个“客户端”来连接它并读取数据。最方便的工具就是手机App。
5.1 通用BLE调试App的选择与使用
在手机应用商店搜索“BLE Scanner”或“蓝牙调试器”,会有很多选择。iOS上如“LightBlue Explorer”,Android上如“nRF Connect”或“BLE调试助手”,都是非常强大且免费的工具。它们的界面可能不同,但核心操作流程一致:
- 扫描:打开App,点击扫描(Scan)。你应该能在设备列表里找到“MyArduino101”。
- 连接:点击你的设备名称进行连接。
- 发现服务:连接成功后,App会列出设备提供的所有服务。找到我们自定义的UUID对应的服务(
19B10000-E8F2-537E-4F6C-D104768A1214),或者有时App会直接显示“Unknown Service”(因为UUID不是标准化的)。 - 查看特征值:点击进入该服务,你会看到其下的特征值列表。找到我们定义的
CHARACTERISTIC_UUID(19B10001-...)。 - 读取与订阅通知:
- 读取(Read):点击“Read”按钮,App会立即读取一次特征值的当前数据,并以十六进制(Hex)或十进制(Decimal)等形式显示。你应该能看到一个不断变化的值(对应我们程序里的
sensorValue)。 - 订阅通知(Notify/Indicate):找到“Subscribe”或“Enable Notification”之类的开关,打开它。这是关键一步!打开后,你不需要再手动点击“Read”,App界面上的数据就会自动、实时地更新,与我们
loop函数中每2秒的setValue同步。
- 读取(Read):点击“Read”按钮,App会立即读取一次特征值的当前数据,并以十六进制(Hex)或十进制(Decimal)等形式显示。你应该能看到一个不断变化的值(对应我们程序里的
注意:手机App显示的数据通常是十六进制字节流。比如我们的
sensorValue是整数500,在内存中可能是两个字节0x01 0xF4(小端序)。一些高级的App可以让你选择解析格式(如“16-bit Integer Little Endian”),选择正确后就能直接显示“500”。如果显示的是乱码或奇怪的数字,先确认你解析的格式是否正确。
5.2 数据格式解析与常见问题排查
当你成功收到数据,但显示“不对”时,问题通常出在数据格式的解析上。Arduino端和手机端必须对数据的“解释方式”达成一致。
Arduino端发送了什么?在我们的例子里,
dataCharacteristic.setValue(sensorValue)发送的是一个int型变量。在Arduino 101上,int占2个字节(16位)。它是以“小端序”(Little-Endian)存储在内存中的,即低字节在前,高字节在后。例如,十进制500(十六进制0x01F4),发送的字节序列是F4 01。手机端如何解析?
- 原始十六进制查看:这是最可靠的方式。在App里查看原始的Hex值,如果看到类似
F4 01这样每2秒变化一次的数据流,说明通信本身完全正确。 - 选择解析器:在App中找到数据展示区域附近的设置,选择正确的数据类型。对于我们的2字节整数,应选择“16-bit Integer”或“Short Integer”,并注意字节序。尝试“Little Endian”(小端序),如果显示的值是500、501、502...,那就对了。如果选择“Big Endian”(大端序),可能会显示一个很大的错误数字(如
0xF401= 62465)。
- 原始十六进制查看:这是最可靠的方式。在App里查看原始的Hex值,如果看到类似
如果收不到任何数据?
- 检查串口输出:首先确认Arduino程序运行正常,串口监视器显示“BLE Peripheral started”以及后续的连接和数据更新信息。
- 检查手机蓝牙权限:确保手机App已获得定位或蓝牙权限(在Android上,蓝牙扫描需要定位权限)。
- 重启蓝牙:有时关闭手机蓝牙再重新打开,或重启App能解决临时性问题。
- 检查UUID:确认手机App里连接的服务和特征值UUID,与代码中定义的完全一致,一个字符都不能差。
- 检查特征值属性:确保代码中创建特征值时包含了
BLENotify属性,并且手机端成功开启了通知订阅。
6. 进阶应用:发送更复杂的数据与稳定化技巧
掌握了基础的数据发送后,我们可以尝试更实用的场景:发送浮点数、多个数据打包,以及让连接更稳定。
6.1 发送浮点数与结构化数据
传感器数据常常是浮点数,比如float temperature = 25.6;。你不能直接setValue(temperature),因为setValue对float的重载可能会因库版本不同而有问题。最稳妥的方式是,将数据转换为字节数组再发送。
float temperature = 25.6; float humidity = 60.5; // 创建一个足够大的字节数组 byte dataBuffer[8]; // 两个float,每个4字节,共8字节 // 使用memcpy将浮点数的内存拷贝到缓冲区 memcpy(dataBuffer, &temperature, 4); memcpy(dataBuffer + 4, &humidity, 4); // 更新特征值,注意创建时最大长度要设为8或更大 dataCharacteristic.setValue(dataBuffer, 8);在手机端,你需要知道这个8字节的数组前4个字节是一个小端序的float,后4个字节是另一个float,并使用相应的工具或代码来解析。对于简单的调试,你可以选择在Arduino端先将浮点数转换为字符串发送,手机端直接显示文本。
char strBuffer[20]; sprintf(strBuffer, "%.1f,%.1f", temperature, humidity); dataCharacteristic.setValue(strBuffer); // 发送字符串,如 "25.6,60.5"6.2 连接稳定性优化与功耗浅析
在实际项目中,无线连接可能不稳定。以下是一些提升鲁棒性的技巧:
- 连接事件处理:我们的示例代码在
loop中检测连接。更健壮的做法是,在连接建立和断开时,执行一些初始化和清理工作,比如连接后打开传感器,断开后关闭传感器以省电。 - 广播参数调整(高级):
CurieBLE库允许你调整广播间隔等参数,这会影响设备被发现的快慢和功耗。但这部分API可能不直接暴露,通常默认值已足够。 - 心跳与超时:在
while (central.connected())循环内,除了发送数据,可以添加一个简单的“心跳”机制,比如每10秒通过串口打印一条信息,确认循环还在运行。如果长时间没有数据更新,可以检查传感器是否异常。 - 关于功耗:BLE本身是低功耗的,但Arduino 101的Curie模块在运行时的整体功耗相比专门的低功耗MCU(如nRF52840)还是要高一些。对于电池供电的长期监测项目,需要考虑让MCU在数据发送间隙进入睡眠模式。这涉及到更底层的Curie芯片电源管理,超出了本入门教程范围,但它是产品化路上必须考虑的一环。
7. 从调试到应用:下一步可以做什么?
当你成功在手机App上看到来自101板子的实时数据流时,这个入门项目就完成了。但这仅仅是起点。你可以尝试:
- 连接真实传感器:将代码中的
sensorValue++替换为analogRead(A0)或digitalRead(),连接一个光敏电阻、温湿度传感器(如DHT11,需用数字引脚和库)或按钮,发送真实的世界数据。 - 开发专属手机App:使用MIT App Inventor(图形化,简单)、React Native或Flutter(跨平台)甚至原生Android/iOS开发,编写一个定制化的App,用更友好的界面(图表、仪表盘)展示数据,并增加控制功能(如发送指令控制101板上的LED)。
- 连接物联网平台:让101板子将数据通过WiFi网关(或直接使用ESP32这类带WiFi的板子更简单)上报到阿里云、腾讯云或自建的MQTT服务器,实现真正的远程监控。
- 实现双向通信:为特征值添加
BLEWrite属性。这样,手机App就可以向特征值写入数据(例如发送一个命令字符串“LED_ON”),101板子在代码中监听特征值的写入事件,并解析命令来控制硬件。
我个人的体会是,玩转Arduino 101的蓝牙,最难的不是写代码,而是建立对BLE通信模型的正确理解,以及掌握有效的调试方法。一旦打通了第一个“Hello World”级别的数据流,后面各种想法的实现就只是时间和耐心的问题了。多利用串口打印日志,耐心分析手机端收到的原始字节数据,这两个习惯能帮你解决90%的通信问题。