资讯中心

Arduino 101蓝牙BLE数据发送实战:从原理到传感器应用

📅 2026/7/29 13:35:59
Arduino 101蓝牙BLE数据发送实战:从原理到传感器应用
1. 项目概述为什么Arduino 101的蓝牙值得你花时间如果你手头有一块Arduino/Genuino 101开发板却只把它当普通的Arduino Uno来用点灯、读传感器那真是有点“暴殄天物”了。这块板子最核心的亮点就是它内置了低功耗蓝牙BLE和六轴运动传感器。今天我们就来彻底盘一盘它的蓝牙功能特别是如何用它来发送数据。这不仅仅是让一个LED灯通过手机控制亮灭那么简单而是为你打开一扇门如何让你制作的智能设备比如环境监测站、遥控小车、可穿戴设备摆脱线缆的束缚与手机、平板甚至另一块开发板进行无线“对话”。很多人一听到蓝牙编程就头疼觉得要配置复杂的协议栈、处理晦涩的UUID。但Arduino 101通过一个名为CurieBLE的库极大地简化了这个过程。它把蓝牙设备抽象成“服务”Service和“特征值”Characteristic你只需要关心“我有什么数据要发”和“发给谁”底层的广播、连接、数据封装它都帮你处理好了。本教程的目标就是带你绕过那些繁琐的理论直接上手写一个能通过手机蓝牙APP接收数据的实用程序。你会发现原来让硬件“开口说话”可以这么简单。2. 核心概念与硬件准备BLE不是传统蓝牙在动手写代码之前我们必须先搞清楚一个关键区别Arduino 101使用的是蓝牙低功耗Bluetooth Low Energy BLE而不是你连接音箱、耳机用的经典蓝牙Bluetooth Classic。这两者虽然都叫蓝牙但设计目标和实现方式天差地别。经典蓝牙就像一条持续不断的水管适合传输音频、文件等大数据流但耗电高。BLE则像是一个个精心设计的小邮筒设备大部分时间在睡觉超低功耗只有需要收发数据时才短暂唤醒投递或收取一个数据“包裹”。这种设计使其非常适合传感器数据上报、遥控指令等间歇性、小数据量的场景一颗纽扣电池能工作数月甚至数年。对于Arduino 101我们不需要任何额外的蓝牙模块它的核心处理器Intel Curie已经集成了BLE功能。你需要准备的只是一块Arduino/Genuino 101开发板。一条USB数据线用于供电和上传程序。一台安装好Arduino IDE的电脑。请确保IDE版本在1.6.7或以上并已通过“工具”-“开发板”-“开发板管理器”安装了“Intel Curie Boards”支持包。一部支持BLE的智能手机iOS 8 / Android 4.3。我们将在手机上使用一个名为nRF Connect由Nordic Semiconductor开发的免费APP来进行测试。这款APP是调试BLE设备的“瑞士军刀”强烈推荐。注意由于Intel已停止对Curie模块的维护在较新版本的Arduino IDE2.x中可能需要手动添加开发板支持网址或寻找社区维护的版本。如果遇到问题使用Arduino IDE 1.8.x版本通常是更稳妥的选择。3. 软件架构解析理解BLE的“服务”与“特征”CurieBLE库采用了BLE通用的GATT通用属性协议架构。你可以把它想象成一个提供多种服务的“公司”。外设Peripheral 提供服务的设备也就是我们的Arduino 101。它像一家商店对外广播自己提供的服务。中央设备Central 寻找并使用服务的设备比如我们的手机。它像顾客扫描并选择要光顾的商店。服务Service 商店提供的一项具体业务比如“温度报告服务”或“LED控制服务”。每个服务都有一个唯一的UUID来标识。特征Characteristic 一项业务下的具体数据或操作。它是客户端手机与服务器Arduino进行数据交换的实际接口。一个服务可以包含多个特征。特征有三大关键属性值Value 实际存储的数据比如当前的温度值“25.3”。属性Properties 定义了这个特征能干什么比如READ可读、WRITE可写、NOTIFY可通知。NOTIFY是我们实现数据发送的关键它允许外设在数据变化时主动“通知”中央设备中央设备随后再来读取新值这是一种高效的推送机制。描述符Descriptor 对特征的额外描述最常用的是“客户端特征配置描述符”CCCD用于启用或禁用NOTIFY功能。我们的项目目标就是让Arduino 101作为一个外设创建一个带NOTIFY属性的特征。当这个特征的值被更新比如读取了传感器数据它就会自动通知已连接的手机手机便可以读取到最新的数据。这就是“发送数据”的核心流程。4. 从零开始编写蓝牙数据发送程序理论说再多不如一行代码。我们从一个最简单的例子开始让Arduino 101周期性地向手机发送一个不断递增的计数器值。4.1 初始化与BLE设备设置首先我们需要在代码中引入CurieBLE.h库并定义一些全局对象。#include CurieBLE.h // 定义BLE外设对象 BLEPeripheral blePeripheral; // 定义服务Service和特征Characteristic // 这里使用一个标准的“电池服务”UUID作为示例你可以自定义 BLEService myService(180F); // 电池服务UUID // 创建一个特征其UUID为“2A19”电池电量属性为可读READ和可通知NOTIFY BLEUnsignedCharCharacteristic myCharacteristic(2A19, BLERead | BLENotify); // 定义一个我们要发送的计数器变量 unsigned char sendCounter 0;代码解读BLEPeripheral blePeripheral; 创建了一个BLE外设对象它是我们整个蓝牙功能的管家。BLEService myService(180F); 创建了一个服务。180F是蓝牙技术联盟SIG定义的“电池服务”的标准UUID。使用标准UUID的好处是手机APP能自动识别并友好地显示服务名称。你也可以使用自己生成的UUID如“12345678-1234-1234-1234-123456789ABC”但那样在APP里就只显示一串数字。BLEUnsignedCharCharacteristic myCharacteristic(2A19, BLERead | BLENotify); 这是关键。我们创建了一个无符号字符型0-255的特征。2A19是“电池电量”特征的标准UUID。属性BLERead | BLENotify表示这个特征既可被手机读取也支持通知功能。我们发送数据就依赖BLENotify。选择BLEUnsignedCharCharacteristic是因为计数器值在0-255之间用1字节传输最省资源。CurieBLE库还提供了BLECharCharacteristic、BLEIntCharacteristic、BLEFloatCharacteristic等多种数据类型你可以根据要发送的数据类型选择。4.2 在setup()中完成蓝牙配置蓝牙外设的初始化工作都在setup()函数中完成。void setup() { Serial.begin(9600); // 初始化串口用于调试输出 // 等待串口连接仅在需要调试时打开。正式使用时可以注释掉。 // while (!Serial); // 1. 设置BLE设备的本地名称和广播的服务UUID blePeripheral.setLocalName(MyArduino101); // 手机扫描时看到的设备名 blePeripheral.setAdvertisedServiceUuid(myService.uuid()); // 广播我们创建的服务 // 2. 将特征添加到服务中再将服务添加到外设中 blePeripheral.addAttribute(myService); blePeripheral.addAttribute(myCharacteristic); // 3. 初始化特征的值 myCharacteristic.setValue(sendCounter); // 4. 开始广播等待手机连接 blePeripheral.begin(); Serial.println(BLE Peripheral started, waiting for connections...); }操作意图与避坑点setLocalName 给你的设备起个名字手机扫描时会看到它。名字要简洁易识别。setAdvertisedServiceUuid 告诉外界我主要提供哪个服务。这能帮助手机APP快速过滤设备。addAttribute 这是构建GATT层级结构的关键步骤。必须按照“外设 - 服务 - 特征”的顺序添加属性。忘记添加特征是最常见的错误之一会导致手机连接后看不到数据接口。setValue 在广播开始前给特征赋一个初始值。虽然我们后续会更新它但一个有效的初始值能避免一些连接问题。begin() 一切就绪启动蓝牙广播。此时你的手机用nRF ConnectAPP就能扫描到名为“MyArduino101”的设备了。实操心得在开发阶段务必保持Serial.begin(9600)和后面的Serial.println调试语句。蓝牙调试不像串口那样直观通过串口打印日志如“连接成功”、“开始发送数据”是判断程序运行到哪一步、是否出错的最重要手段。4.3 在loop()中实现数据发送与连接管理主循环loop()负责两件事维护蓝牙连接和周期性地更新数据并通知客户端。void loop() { // 1. 监听蓝牙连接事件 BLECentral central blePeripheral.central(); // 2. 检查是否有手机连接 if (central) { Serial.print(Connected to central: ); Serial.println(central.address()); // 打印连接设备的MAC地址 // 3. 当手机保持连接时循环执行数据发送 while (central.connected()) { // 模拟数据产生计数器递增 sendCounter; if (sendCounter 255) { // 防止无符号字符型溢出 sendCounter 0; } // 4. 更新特征值并通知已连接的客户端 myCharacteristic.setValue(sendCounter); // 注意setValue()本身不会发送通知需要下面这行代码 // 实际上对于CurieBLE库setValue后如果特征支持NOTIFY且客户端已启用通知数据会被推送。 // 更准确的做法是在确保通知已启用后更新值即可。 // 这里我们简化操作直接更新值。 Serial.print(Counter value updated and notified: ); Serial.println(sendCounter); delay(1000); // 每隔1秒发送一次数据 } // 5. 手机断开连接后 Serial.print(Disconnected from central: ); Serial.println(central.address()); // 这里可以添加一些断开连接后的清理或待机代码 } // 如果没有连接程序会快速循环等待下一次连接 }核心机制详解blePeripheral.central() 这个函数会检查是否有中央设备手机连接或断开并返回一个BLECentral对象。它是连接管理的入口。while (central.connected()) 这是一个内层循环。只要手机还连着我们就一直待在里面执行数据发送逻辑。这是确保连接期间持续通信的关键结构。myCharacteristic.setValue(sendCounter) 这行代码做了两件事首先它把sendCounter的值写入特征其次如果连接的手机已经启用了这个特征的通知功能CCCD被设置为1那么库会自动将这次数值更新通过BLE链路“通知”给手机。这就是数据发送的实质。延迟delay(1000) 控制数据发送的频率。对于传感器数据1秒一次是常见间隔。太快可能耗电且无必要太慢则实时性差。请根据实际应用调整。关键注意事项很多初学者在这里会困惑“我调用了setValue为什么手机收不到” 原因就在于通知Notify需要客户端显式启用。在nRF ConnectAPP中连接到设备后你需要找到我们的特征2A19点击它旁边的三个点或向下箭头选择“启用通知”Enable notifications。只有启用后setValue的更新才会被自动推送到手机。如果只启用“读取”Read那么你只能手动点击“读取”按钮来获取当前值无法实现自动推送。5. 项目进阶发送真实的传感器数据只会发计数器太“玩具”了。让我们把Arduino 101内置的六轴传感器加速度计陀螺仪用上发送真实的运动数据。5.1 添加传感器库与变量首先需要引入CurieIMU.h库。#include CurieBLE.h #include CurieIMU.h // 新增引入IMU库 BLEPeripheral blePeripheral; BLEService imuService(181A); // 使用“环境传感服务”的UUID比较贴合 // 创建三个特征分别发送X, Y, Z轴的加速度值。使用16位有符号整数类型。 BLEIntCharacteristic accelXChar(2A6E, BLERead | BLENotify); // X轴加速度 BLEIntCharacteristic accelYChar(2A6F, BLERead | BLENotify); // Y轴加速度 BLEIntCharacteristic accelZChar(2A70, BLERead | BLENotify); // Z轴加速度 // 用于存储传感器读数的变量 int ax, ay, az;5.2 修改setup()初始化传感器void setup() { Serial.begin(9600); // 初始化IMU传感器 CurieIMU.begin(); CurieIMU.setAccelerometerRange(2); // 设置加速度计量程为±2g。可选2, 4, 8, 16g。 Serial.println(IMU initialized.); // BLE设置 blePeripheral.setLocalName(101_IMU_Sensor); blePeripheral.setAdvertisedServiceUuid(imuService.uuid()); // 将三个特征添加到服务中 blePeripheral.addAttribute(imuService); blePeripheral.addAttribute(accelXChar); blePeripheral.addAttribute(accelYChar); blePeripheral.addAttribute(accelZChar); // 初始化特征值 accelXChar.setValue(0); accelYChar.setValue(0); accelZChar.setValue(0); blePeripheral.begin(); Serial.println(BLE IMU Peripheral started.); }参数选择解析CurieIMU.setAccelerometerRange(2)设置了传感器的量程。量程越小在低加速度下的分辨率越高但容易超出量程。对于手部运动等场景±2g通常足够。如果你要做剧烈运动监测如摔跤检测可能需要设置为±8g或±16g。5.3 修改loop()读取并发送传感器数据void loop() { BLECentral central blePeripheral.central(); if (central) { Serial.print(Connected to: ); Serial.println(central.address()); while (central.connected()) { // 读取加速度计数据 CurieIMU.readAccelerometer(ax, ay, az); // 更新BLE特征值 accelXChar.setValue(ax); accelYChar.setValue(ay); accelZChar.setValue(az); // 通过串口监视器查看数据调试用 Serial.print(Accel X: ); Serial.print(ax); Serial.print( Y: ); Serial.print(ay); Serial.print( Z: ); Serial.println(az); delay(50); // 20Hz的更新率适合运动捕捉 } Serial.print(Disconnected from: ); Serial.println(central.address()); } }实操要点CurieIMU.readAccelerometer(ax, ay, az)读取的是原始数据。这个值是一个有符号整数其单位取决于量程。例如在±2g量程下数值范围大约是-16000到16000对应-2g到2g。你需要根据数据手册进行换算才能得到以g为单位的加速度值。但很多手机APP如一些传感器数据可视化工具可以直接接收并解释这些原始值。延迟delay(50)对应20Hz的采样率。对于大多数人体运动识别10-50Hz已经足够。更高的频率如100Hz意味着更快的响应但也会增加功耗和数据吞吐量需要根据电池供电情况和手机APP的处理能力权衡。6. 手机端测试与数据可视化代码写好了怎么验证数据真的发出去了呢这就需要用到前面提到的nRF ConnectAPP。上传程序将完整的程序编译并上传到Arduino 101。打开手机蓝牙和APP确保手机蓝牙已开启并打开nRF ConnectAPP。扫描设备点击APP顶部的“SCAN”按钮在设备列表中寻找名为“101_IMU_Sensor”或你在代码中设置的名称的设备。连接设备点击设备名称旁边的“CONNECT”按钮。探索服务连接成功后APP会显示一个列表展开后可以看到我们创建的“环境传感服务”UUID: 181A。启用通知点击服务下的“Unknown Characteristic”通常对应我们的特征UUID 2A6E等。在新页面中你会看到“Value”字段可能显示为0。点击右上角的三个竖点菜单图标选择“Enable notifications”启用通知。对三个特征都执行此操作。观察数据启用通知后Value字段的数字会开始动态变化这正是Arduino 101发送过来的加速度计原始数据。同时在APP的“LOG”标签页你可以看到详细的数据流日志。进阶测试你可以在手机端编写一个简单的App使用Android的BluetoothGATTAPI或iOS的CoreBluetooth框架来解析这些数据并实时绘制出三轴加速度的波形图这样就实现了一个简易的无线运动捕捉系统。7. 常见问题排查与性能优化技巧在实际操作中你几乎一定会遇到下面这些问题。这里是我的踩坑实录和解决方案。7.1 手机扫描不到设备检查供电确保Arduino 101通过USB线稳定供电。USB口接触不良或电源功率不足可能导致蓝牙模块工作不正常。检查程序是否运行观察板载的“L”灯是否在缓慢闪烁这是程序正常运行的标志。如果灯不亮或常亮可能是程序未成功上传或板子有问题。检查设备名称确认代码中setLocalName设置的名字是唯一的没有和其他BLE设备重名。重启与距离重启Arduino 101和手机的蓝牙功能。确保设备之间距离在几米内无严重遮挡。确认广播已开始在setup()函数的blePeripheral.begin()后面加一句Serial.println(“Broadcasting...”);通过串口监视器确认程序执行到了这里。7.2 手机连接后立即断开特征属性错误最常见的原因是特征Characteristic的属性设置不正确。如果你希望手机能自动接收数据特征必须包含BLENotify属性。同时确保在setup()中正确调用了addAttribute添加了该特征。服务UUID冲突如果你使用了自定义的随机UUID请确保它在整个项目中是唯一的并且手机APP能正确处理它。对于测试优先使用SIG定义的标准UUID。内存或资源不足过于复杂的程序或过高的数据发送频率可能导致Curie芯片处理不过来引发不稳定。尝试降低发送频率增加delay或简化代码逻辑。7.3 手机收到数据全是0或不变传感器未初始化对于传感器项目检查CurieIMU.begin()是否执行成功。可以在后面加一个判断if (!CurieIMU.begin()) { Serial.println(“IMU init failed!”); while(1); }。数据未更新检查loop()中while (central.connected())循环内的代码是否确实被执行。通过串口打印调试信息是最佳方法。通知未启用这是最高频的问题务必在手机APP如nRF Connect中对每个你要接收数据的特征手动点击“启用通知”Enable notifications。仅仅连接和读取是不够的。7.4 数据传输不稳定或延迟大降低发送频率BLE连接有开销。对于101板稳定的数据发送间隔建议在50ms20Hz以上。过快的delay(10)可能导致系统繁忙。减少单次数据量优先发送必要的数据。例如加速度计三个轴的数据如果每个轴用int2字节加上协议开销一包数据也不小。考虑是否需要对数据进行压缩、取平均值或降低精度。优化手机端手机APP处理数据的速度也会影响感知。确保手机端应用没有在UI线程进行复杂的数据处理。7.5 功耗优化建议虽然101板通常USB供电但了解BLE低功耗特性对做电池项目很有帮助。广播间隔CurieBLE库默认的广播间隔是相对平衡的。如果需要极低功耗可以深入研究库文件尝试调整广播参数但这对初学者较复杂。连接间隔在BLE连接中中央设备手机和外设101会协商一个“连接间隔”。间隔越长越省电但数据实时性越差。这个参数通常由中央设备主导外设可以提出建议值。在CurieBLE库中可以在begin()之前通过blePeripheral.setConnectionInterval(min, max)来设置单位是1.25ms例如setConnectionInterval(80, 160)对应100ms到200ms。睡眠模式当没有连接时可以让Arduino 101进入深度睡眠。但这需要更复杂的程序来管理蓝牙唤醒事件超出了基础教程范围。我个人在多次项目实践中发现稳定的核心是简化与调试。先从最简单的“计数器发送”例程开始确保连接、通知、接收整个链路走通。然后再逐步添加传感器逻辑、复杂数据处理。每添加一步都用串口打印日志来验证。蓝牙无线调试就像隔山打牛清晰的日志是你唯一的“眼睛”。最后别忘了nRF Connect这个神器它不仅能测试其日志功能更是分析通信协议、排查数据包问题的终极武器。当你看到手机APP上那个数值随着你晃动开发板而欢快地跳动时那一刻的成就感就是硬件编程最大的乐趣。