赞
踩
目录
最近在做有关低功耗蓝牙 BLE 的项目,还是有必要整理一下 BLE 的资料。在树莓派连接低功耗蓝牙 BLE 用 C 语言实现已经实现,前期的准备这个折磨了我好久,网上关于这个的资料很少,大部分都是用 Python 来实现的,但是作为一名嵌入式工程师,我们还是得必须使用 C 语言来实现,所以我就开启了阅读源码的道路。本人第一次从源码中提取自己想要的 API 刚开始确实有点难度,踩过很多的坑,但是天不负有心人,只要坚持最终还是有所成就的!请相信自己。
蓝牙(Bluetooth)是一种短距离的无线通讯技术,运行在 2.4GHz 免费频段,可实现固定设备、移动设备之间的数据交换。一般蓝牙 3.0 之前的 BR/EDR 蓝牙称为经典蓝牙,而将蓝牙 4.0 规范下的 LE 蓝牙称为低功耗蓝牙(BLE, Bluetooth Low Energy)。BLE 主要用于医疗保健、运动健身、信标、安防、家庭娱乐等邻域的新兴应用。相较经典蓝牙,低功耗蓝牙旨在保持同等通信范围的同时显著降低低功耗和成本。
Bluetooth LE 协议栈从下至上分为几个层级:Physical Layer(PHY)、Link Layer(LL)、Host Controller Interface(HCI)、Logical Link Control and Adaption Protocol Layer(L2CAP)、Attribute Protocol(ATT)、Security Manager Protocol(SMP)、Generic Attribute Protocol(GATT)、Generic Access Protocol(GAP)。
在 Bluetooth LE 协议栈中不同的层级有不同的角色划分。这些角色划分互不影响。
当主机和从机建立连接之后才能相互互发数据
另外还有观察者(Observer)和广播者(Broadcaster),这两种角色不常使用,但也十分有用,例如 iBeacon,就可以使用广播者角色来做,只需要广播特定内容即可。
GATT 其实是一种属性传输协议,简单的讲可以认为是一种属性传输的应用协议。这个属性 的结构非常简单。它由服务组成,每个服务由不同数量的特征组成,每个特征又由很多其他的元素组成。
GATT 服务端 和 GATT 客户端这两种角色存在于 Bluetooth LE 连接建立之后。GATT 服务器存储通过属性协议传输的数据,并接受来自 GATT 客户端的属性协议请求、命令和确认。简而言之,通提供数据的一端称为 GATT 服务端,访问数据的一端称为 GATT 客户端。
上面整个数据包有以下问题:
有了 PHY,LL 和 GAP ,就可以发送广播包,但广播包携带的信息极其有限,而且还有如下几大限制:
而连接则可以很好解决上述问题。
到底什么叫连接(connect)?像有线UART,很容易理解,就是用线(Rx和Tx等)把设备A和设备B相连,即为理解。用“线”把两个设备相连,实际是让2个设备有共同的通信媒介,并让两者时钟同步起来。
一个完整的BLE 广播报文由四部分组成,分别是前导码、接入地址、协议数据单元(PDU)和CRC校验码。
PDU 数据报头(2个字节)由下面几个字段组成:
公有地址和私有地址的区别(为什么说这个呢?因为接下的项目中我们需要连接 BLE ,会涉及到这个地址的连接类型 dst_type 的选择,还是有必要了解一下的)
为了连接到 BLE 设备,我们首先需要扫描发现设备。其中一种方法是使用 hcitool 实用程序,它基本上是所有 HCI 命令(经典蓝牙和 BLE)的“瑞士刀”。经典蓝牙的扫描使用 scan,而 BLE 设备的扫描则使用 lescan。
- pi@raspberrypi:~ $ sudo hcitool -i hci0 lescan
- LE Scan ...
- A0:76:4E:59:12 ESP32
- ... ....
-
- pi@raspberrypi:~ $ sudo hcitool leinfo A0:76:4E:59:12
- Requesting information ...
- Handle: 64 (0x0040)
- Features: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
在上面的扫描过程中,我们会发现 ESP32 BLE 设备,这是因为我们在 ESP32 BLE 中设定的广播报文里含有设备名。
BLE 如何开启广播模式请参考官方文档,这里就不多说了。
hcitool 命令不是操作 GATT 的工具,通过它我们扫描发现 BLE 设备的地址后,就可以使用gatttool 命令来连接并操纵 BLE 设备的 GATT。该命令支持交互式和非交互式两种工作模式,在交互模式下,控制台提供了一个界面,使您可以发出命令并与设备进行交互,您将需要时再退出返回到终端。
使用下面命令可以用来连接、断开蓝牙 BLE。
- pi@raspberrypi:~ $ gatttool -I -i hci0 -b A0:76:4E:59:12
-
-
- -I 进入交互模式
- -i 指定蓝牙的接口设备,如果不指定默认使用 hci0
- -b 指定连接的蓝牙设备的MAC地址
-
- [A0:76:4E:59:12][LE]> help //查看使用帮助信息
- ... ...
-
-
- [A0:76:4E:59:12][LE]> connect //有时第1此可能connect 不成功,多connect几次就ok
- Attempting to connect to A0:76:4E:59:12
- Error: connect to A0:76:4E:59:12: Function not implemented (38)
- [A0:76:4E:59:12][LE]> connect
- Attempting to connect to A0:76:4E:59:12
- Connection successful
- [A0:76:4E:59:12][LE]> disconnect //断开BLE蓝牙设备的连接
- (gatttool:2362): GLib-WARNING **: 10:07:24.803: Invalid file descriptor.
- [A0:76:4E:59:12][LE]> exit //退出交互模式
- pi@raspberrypi:~ $
使用 primary 命令可以查看当前蓝牙设备提供的所有的服务。
- [A0:76:4E:59:12][LE]> connect
- Attempting to connect to A0:76:4E:59:12
- Connection successful
-
- [A0:76:4E:59:12][LE]> primary //发现BLE的GATT服务,这里有3个服务,最后1个为自定义服务
- attr handle: 0x0001, end grp handle: 0x0005 uuid: 00001801-0000-1000-8000-00805f9b34fb
- attr handle: 0x0014, end grp handle: 0x001c uuid: 00001800-0000-1000-8000-00805f9b34fb
- attr handle: 0x0028, end grp handle: 0xffff uuid: 0000abf0-0000-1000-8000-00805f9b34fb
其中:
不同的设备可能会不太一样。
使用 characteristics 命令可以查看当前蓝牙设备提供的所有的characteristics。每个特征值都有一个独一无二的 UUID 值,同时也有一个相应的 handle 值,它是在使用命令或编程访问特征值时的句柄。每个特征值也有它的相应属性,如可读、可写等。
- [A0:76:4E:59:12][LE]> characteristics
- handle: 0x0002, char properties: 0x20, char value handle: 0x0003, uuid:00002a05-0000-1000-8000-00805f9b34fb
- handle: 0x0015, char properties: 0x02, char value handle: 0x0016, uuid:00002a00-0000-1000-8000-00805f9b34fb
- handle: 0x0017, char properties: 0x02, char value handle: 0x0018, uuid:00002a01-0000-1000-8000-00805f9b34fb
- handle: 0x0019, char properties: 0x02, char value handle: 0x001a, uuid:00002aa6-0000-1000-8000-00805f9b34fb
- handle: 0x0029, char properties: 0x06, char value handle: 0x002a, uuid:0000abf1-0000-1000-8000-00805f9b34fb
- handle: 0x002b, char properties: 0x12, char value handle: 0x002c, uuid:0000abf2-0000-1000-8000-00805f9b34fb
- handle: 0x002e, char properties: 0x06, char value handle: 0x002f, uuid:0000abf3-0000-1000-8000-00805f9b34fb
- handle: 0x0030, char properties: 0x12, char value handle: 0x0031, uuid:0000abf4-0000-1000-8000-00805f9b34fb
使用 char-read-uuid 命令可以读取某个 characteristics 值。
- [A0:76:4E:59:12][LE]> characteristics 0x0001 0x001c //查找标准服务里的所有特征值
- handle: 0x0002, char properties: 0x20, char value handle: 0x0003, uuid:00002a05-0000-1000-8000-00805f9b34fb
- handle: 0x0015, char properties: 0x02, char value handle: 0x0016, uuid:00002a00-0000-1000-8000-00805f9b34fb
-
- [A0:76:4E:59:12][LE]> char-read-uuid 00002a05-0000-1000-8000-00805f9b34fb //该特征值不具备可读属性,所以读取失败
- Error: Read characteristics by UUID failed: Attribute can't be read
-
-
- [A0:76:4E:59:12][LE]> char-read-uuid 00002a00-0000-1000-8000-00805f9b34fb //该特征值可读
- handle: 0x0016 value: 45 53 50 33 32 //("ESP32")
-
- [A0:76:4E:59:12][LE]> char-read-hnd 0x0016 //使用 handle 来读取该特征值的 handle(注意是0x0016,而不是0x0015)
- Characteristic value/descriptor: 45 53 50 33 32
我们可以使用 char-write-req 命令来写相应的数据。
- [A0:76:4E:59:12][LE]> char-read-hnd 0x002f
- Characteristic value/descriptor: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
-
- [A0:76:4E:59:12][LE]> char-write-req 0x002f 11223344
- Characteristic value was written successfully
-
- [A0:76:4E:59:12][LE]> char-read-hnd 0x002f
- Characteristic value/descriptor: 11 22 33 44
从上面的操作中我们可以了解到 BLE通信就是对相应的特征值进行读、写操作。但通常数据的读写都是由主机(如手机)发起。那如果蓝牙从机(如ESP32)有数据更新(如传感器重新采样了),那如何通知主机来读取呢?对于这种情形的话,蓝牙BLE支持 Notification 机制,如果主机选择使能该机制之后,从机的数据更新将会主动发送给主机。
gatttool 的交互模式下并不能读到这些数据。如果想实时获取 Notification 的数据 话,则需要使用 gatttool 非交互模式来监听。
- pi@raspberrypi:~ $ gatttool -I -i hci0 -b A0:76:4E:59:12
- ... ...
-
- [A0:76:4E:59:12][LE]> exit
- (gatttool:2467): GLib-WARNING **: 14:52:01.698: Invalid file descriptor.
-
- pi@raspberrypi:~ $ sudo gatttool -i hci0 -b A0:76:4E:59:12 --char-write-req -a 0x002d -n 0100
-
- //此时在 BLE 控制终端随便输入一些数据,树莓派的 gatttool 命令下将会显示收到的数据。
下面这个代码 BLE 的 MAC 地址和 uuid 都要改成自己设备的,使用到了 BlueZ 库 和 Gattlib 库,具体的后续在说(这阵子实在没有充足的时间来对这个项目进行总结,如果你有什么疑惑的地方可以私聊我,或者直接留言)。
这个 demo 可以自己再改进一下,锻炼一下自己的编程能力。
- #include <stdio.h>
- #include <sys/types.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <glib.h>
- #include <gattlib.h>
- #include <bluetooth/bluetooth.h>
- #include <bluetooth/hci.h>
- #include <bluetooth/hci_lib.h>
-
- #define BLE_SCAN_TIMEOUT 5
- #define bleaddr "A0:76:4E:55:ED:B2" //target BLE device BT-MAC addr !!uppercase!! characters
-
-
- const char * write_uuid = "c303";//any esp32 writable characteristic uuid
- static uuid_t uuid;//be used to change type :wrtite_uuid(char) to uuid(uuid_t)
-
-
- /*扫描回调函数*/
- static void BLE_discovered_callback(void *adapter,const char* addr, const char* name, void* user_data) //regular scanning
- {
- if(name)
- {
- printf("BLE Discovered %s - '%s'\n", addr, name);
- }
- else
- {
- printf("BLE Discovered %s - 'unknown'\n", addr);
- }
-
- return ;
- }
-
-
-
- int main(int argc, char** argv)
- {
-
- int rv,i;
-
-
- void * adapter ;
- const char * adapter_name = NULL; //if NULL ,hci0 be choiced default in my situation
-
- char uuid_str[MAX_LEN_UUID_STR + 1]; //MAX_LEN_UUID_STR =37
-
- gatt_connection_t * conn = NULL;
-
- rv = gattlib_adapter_open(adapter_name,&adapter); //open the Pi bt-device based on the dapter_name,if adapter_name is NULL,default Pi bt-device will be chose(hci0 in this situation)
- if(rv)
- {
- printf("adapter open failed\n");
- return 1;
- }
-
-
- rv = gattlib_adapter_scan_enable(adapter,BLE_discovered_callback,BLE_SCAN_TIMEOUT,NULL);
-
- //gattlib_adapter_scan_enable_with_filter()
- if (rv)
- {
- printf("Failed to scan.\n");
- return -1;
- }
-
-
- if(gattlib_adapter_scan_disable(adapter) != GATTLIB_SUCCESS)
- {
- printf("Error: end scan failed,ready to find AD info\n\n");
- return -1;
- }
- puts("Scan completed\n");
-
-
- printf("start to connnect ...\n");
-
- conn = gattlib_connect(NULL,bleaddr, GATTLIB_CONNECTION_OPTIONS_LEGACY_DEFAULT);
- if (conn == NULL) {
- printf("Fail to connect to the bluetooth device : %s\n",bleaddr);
- return 1;
- }
- printf("coonect to BLE devices : %s sucess \n",bleaddr);
-
-
- memset(&uuid_str,0,sizeof(uuid_str));
- strncpy(uuid_str,write_uuid,sizeof(uuid_str));
-
- if (gattlib_string_to_uuid(uuid_str, strlen(uuid_str) + 1, &uuid) < 0) {
- printf("string to uuid_t failed \n");
- return -1;
- }
-
-
- for(i=49;i<54;i++) //十进制的49 对应ascii码的31,即字符'1'
- {
-
- rv = gattlib_write_without_response_char_by_uuid(conn,&uuid,(uint8_t*)&i,1);
- if (rv != GATTLIB_SUCCESS)
- {
- if(rv == GATTLIB_NOT_FOUND)
- {
- printf("Could not find GATT Characteristic with UUID %s");
- return -1;
- }
- else
- {
- printf("Error while writing GATT Characteristic with UUID %s (ret:%d)",uuid_str, rv);
- return -2;
- }
- }
- sleep(1);
-
- }
-
-
-
-
-
- gattlib_disconnect(conn);
- if(gattlib_adapter_close(adapter) != GATTLIB_SUCCESS)
- {
- printf("adapter close failed\n");
- }
-
-
- printf("\nnormally exit\n");
- return 0;
-
-
-
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。