TA的每日心情 | 开心 2017-5-15 14:59 |
---|
签到天数: 8 天 连续签到: 1 天 [LV.3]偶尔看看II
|
概述:
要进行BLE开发,GATT协议是必须了解的,因为思路与传统蓝牙不同,导致刚接触BLE的人产生很大困惑。
本文章教你如何在 https://www.bluetooth.com/specifications/gatt 里查询你需要的Service并编写相关的应用。
目的:
教大家了解GATT协议、如何查询GATT服务并用CurieNano编写GATT服务
BLE中的连接关系和服务关系:
连接关系:主动连接其他设备的设备称为Central(中心设备),接受其他设备连接的设备称为Peripheral(外围设备)。Central与Peripheral的关系是连接关系。
服务关系:提供服务的一方是Server(服务端),被提供服务的一方是Client(客户端)。Server与Client的关系是服务关系。
两种关系是共同存在的,比如本帖要讲解的例子中,CurieNano是Peripheral同时也是Server,手机是Central同时也是Client。
GATT协议描述:
GATT协议规定了BLE提供的标准服务,编写应用最好遵循这些标准。比如心率服务就负责发送心率数据,血压服务就负责发送血压数据,最好不要乱用。
上图是GATT Profile的逻辑结构。解释一下上图的几个名词:
- Profile,翻译为规范或配置文件,个人认为这个翻译容易引起误导。实际上,Profile就是在BLE通信中,由若干服务构成,实现某个特定功能的实体。
- Service,翻译为服务,是由数据及其关联的行为构成的集合,由一个或多个特征组成
- Characteristic,翻译为特征,通常由若干字段组成。
- UUID:统一唯一标识符,用于标识唯一的服务或特征,比如心率服务的UUID是0x180D,温度测量值特征的UUID是0x2A1C。UUID的作用是:通信双方互相告知UUID,就能知道对方提供的是哪些服务。
这种解释不够直观。那么我们举一个例子:
- 心率服务的UUID是0x180D,它只包含一个特征,叫心率测量值特征,UUID是0x2A37。
- 该特征的第一个字节是控制字节,它的各个比特位规定了心率值是1字节还是2字节,传感器是否接触人体,是否有补充字段等。
- 第二个字节开始就是心率值数据了,CurieNano要做的就是不断更新这个值,在手机上就能看到心率值的变化了。
这些字节的格式都是GATT规定好的,不能乱改,也不要混用!比如不要把心率服务用来传输电池服务数据。因为不同服务直接是不能互相连接的!一个提供心率服务的CurieNano不能与接受电池服务的手机连接。
这种强制的GATT规定看似多余,实际上非常有利于物联网的分工开发。比如开发蓝牙体重秤,负责手机APP和负责下位机的两个小组不需要商定数据格式,只需要共同遵守GATT的体重服务规定即可。
在哪查询GATT定义?
那么,在哪查看GATT规定了哪些Service和Characteristic,以及他们对应的UUID呢?当然是蓝牙官网:https://www.bluetooth.com/specifications/gatt 点击GATT Services,可以查看各个Service的UUID,以及他们包含哪些Characteristic。下图给出了部分GATT Services
点击GATT Characteristic,可以查看各个Characteristic,以及他们包含哪些字段。
图:部分GATTSerivce条目
实战一:通过查询GATT定义,编写心率服务:
CurieNano心率服务是Arduino.cc提供的官方BLE例程之一(链接点此),我在这里把它的编程思路为大家梳理一下。注:我们并不是编写真正心率测量的应用,为了突出蓝牙编程的细节,本示例不会真正地去使用心率传感器。
首先,查询GATT定义:- 进入GATT Servies查询页面:https://www.bluetooth.com/specifications/gatt/services
- 找到Heart Rate(心率服务),它的UUID是0x180D。
- 在 Service Characteristics 表格里,看到该服务包括好几个特征,但只有一个特征 Heart Rate Measurement 标记为Mandatory(强制的),其它特征都标记为Optional(可选的)。为了简化编程,我们只考虑这一个特征。
- 注意到Heart Rate Measurement的Properties栏里,只有Notify标记为Mandatory(强制的),也就是说该特征必须有Notify属性,其他属性都是可选的。所谓Notify,就是当CurieNano(Server方)改变特征的值时,手机(Client方)能立即检测到。
- 点进Heart Rate Measurement(心率测量值特征),看到它的UUID是0x2A37,它包括字段如下表。我们可以令Flags恒为0,改变Heart Rate Measurement Value即可
字段 | 大小 | 意义 | 备注 | Flags | 1Byte | 控制字节,指出其他字段的大小,以及可选字段是否存在 | 必须存在 | Heart Rate Measurement Value | 1~2Byte | 心率值,当Flags的第一bit为0时该字段1Byte,为1时该字段2Byte | 必须存在 | 其他字段(本程序不考虑) | -- | 当Flags相应的bit都为0时,这些字段都不存在 | 可选 |
好,让我们理一下思路:我们的程序首先要建立一个UUID为0x180D的心率服务。然后建立一个UUID为0x2A37的心率测量值特征,该特征包括2字节:前1字节是Flags字段,我们让它恒为0;后1字节是心率值,不断改变该值,手机上就能收到不断变化的心率值了。
下面我们按此思路编写代码,注意注释中每个语句与我们的思路如何联系。
- #include <CurieBLE.h>
-
- BLEPeripheral blePeripheral;
- // 创建UUID为0x180D的服务(心率服务)
- BLEService Service("180D");
- // 创建UUID为0x2A37的特征(心率测量值特征),它的属性为BLENotify,字段总长度2Byte
- BLECharacteristic c("2A37", BLENotify, 2);
-
- void setup() {
- blePeripheral.setLocalName("Arduino 101");
- blePeripheral.setAdvertisedServiceUuid(Service.uuid());
- blePeripheral.addAttribute(Service);
- blePeripheral.addAttribute(c);
- blePeripheral.begin();
- }
-
- // 待发送的2Byte心率特征的值。其中即data[0]是控制字节,令它恒为0。data[1]是心率值
- uint8_t data[] = {0,0};
-
- void loop() {
- // 将心率值+1
- data[1] ++;
- // 发送心率值
- c.setValue(data,2);
- delay(500);
- }
复制代码 程序测试:
将以上代码上传到CurieNano。我们使用NRFToolBox和NRFConnect两款手机APP进行测试。NRFToolBox可以用于一些简单的蓝牙应用,NRFConnect可以用于调试蓝牙。建议学BLE的同学都安装上。
下载这些工具请前往奈何大大的帖子:http://www.arduino.cn/thread-22901-1-1.html
测试方法一:打开NRFToolBox,点击HRM(HeartRateMonitor),连接CurieNano后可以看到心率值一直在线性增加。
测试方法二:打开NRFConnect,点SCAN搜索设备,搜到CurieNano后点击Heart Rate,再点击Heart Rate Measurement右边的三个向下箭头,可以看到Value不断地变化,每次+1
图:心率服务测试结果
通过以上例子,你应该掌握了如何“如何查询GATT条目,编写自己的BLE物联网应用”。下面我们按照这个套路再举一个例子。
实战二:通过查询GATT定义,编写骑行计程服务:
首先,查询GATT定义:- 进入GATT Servies查询页面:https://www.bluetooth.com/specifications/gatt/services
- 找到Cycling Speed and Cadence(自行车速度和节奏),它的UUID是0x1816。
- 在 Service Characteristics 表格里,看到该服务包括好几个特征,只有两个特征CSC Measurement和CSC Feature是Mandatory(强制的)。我们只考虑这两个特征。
- 注意到CSC Feature特征的Properties栏里,只有Read是Mandatory(强制的)。所谓Read,指的是手机(Client方)有权读取该特征的值。
- 注意到CSC Measurement特征的Properties栏里,只有Notify是Mandatory(强制的)。所谓Notify,就是当CurieNano(Server方)改变特征的值时,手机(Client方)能立即检测到。
- 点开CSC Feature特征,看到它的UUID是0x2A5C,它仅仅包含一个2Byte的字段。看起来这个字段没什么重要的,我们令它为0即可
- 点开CSC Measurement特征,看到它的UUID是0x2A5B,它包含的字段如下表。为了让Cumulative Wheel Revolutions和Last Wheel Event Time字段存在,我们令Flags的第一bit为1,其余bit为0,即Flags=1。Cumulative Wheel Revolutions是用来指示骑行里程的,Last Wheel Event Time是用来指示骑行速度的。
字段 | 大小 | 意义 | 备注 | Flags | 1Byte | 控制字节,指出其他字段是否存在 | 必须存在 | Cumulative Wheel Revolutions | 4Byte | 车轮总转数计数,当Flags的第一bit为1时,该字段存在 | 可选 | Last Wheel Event Time | 2Byte | 上一次车轮转满一圈时的时间值,当Flags的第一bit为1时,该字段存在 | 可选 | 其他字段 | -- | 当Flags相应的bit都为0时,这些字段都不存在 | 可选 |
好,让我们理一下思路:我们的程序要建立一个UUID为0x1816的Cycling Speed and Cadence服务、一个UUID为0x2A5B的CSC Measurement特征、一个UUID为0x2A5C的CSC Feature特征。其中CSC Feature特征包括2字节,我们让它恒为0。CSC Measurement特征包括7字节:第一字节为控制字符Flags,我们让它恒为1;中间4字节是车轮总转数计数,我们让它不断+1;后2字节是上一次车轮转满一圈的时间值,我们让它不断增加。
代码如下:- #include <CurieBLE.h>
-
- BLEPeripheral blePeripheral;
- BLEService CSC("1816");
- BLEShortCharacteristic CSCFeature("2A5C", BLERead);
- BLECharacteristic CSCMeasure("2A5B", BLENotify, 7);
-
- void setup() {
- blePeripheral.setLocalName("Arduino 101");
- blePeripheral.setAdvertisedServiceUuid(CSC.uuid());
- blePeripheral.addAttribute(CSC);
- blePeripheral.addAttribute(CSCFeature);
- blePeripheral.addAttribute(CSCMeasure);
- blePeripheral.begin();
- CSCFeature.setValue(0);
- }
-
- uint8_t data[7] = {1,0,0,0,0,0,0};
-
- void loop() {
- data[1]++;
- data[6]++;
- CSCMeasure.setValue(data,7);
- delay(1000);
- }
复制代码 程序测试:
将以上代码上传到CurieNano。
测试方法一:打开NRFToolBox,点击CSC(Cycling Speed and Cadence),连接CurieNano后可以看到左下图的效果。
测试方法二:打开NRFConnect,点SCAN搜索设备,搜到CurieNano后点击Cycling Speed and Cadence,再点击右边的三个向下箭头,可以看到Value不断地变化
图:骑行服务测试结果
|
|