[TOC]
在这里着重记述低功耗蓝牙BLE相关内容,库函数部分翻译自乐鑫官网文档
低功耗蓝牙(BLE)协议栈
低功耗蓝牙协议是蓝牙通信协议的一种,BLE协议栈就是实现低功耗蓝牙协议的代码
层次协议
蓝牙协议规定了两个层次的协议,分别为蓝牙核心协议(Bluetooth Core)和蓝牙应用层协议(Bluetooth Application)
蓝牙核心协议就是对蓝牙技术本身的规范,不涉及其应用方式
蓝牙应用层协议是在蓝牙核心协议的基础上,根据具体的应用需求定义出的特定策略
蓝牙协议栈框图如下所示:
蓝牙核心协议(Bluetooth Core)
蓝牙核心协议包含BLE Controller和BLE Host两部分
Controller负责定义RF、Baseband等偏硬件的规范
Host负责在逻辑链路的基础上,进行更为友好的封装,这样就可以屏蔽掉蓝牙技术的细节,让Bluetooth Application更为方便的使用
Controller是工作在物理层、数据链路层、网络层、传输层的协议,Host则是工作在传输层 、会话层、表示层、应用层的协议,Host将Controller封装成可被配置为函数的形式供程序使用
包含的层次简介
- 物理层(PHY)
用于指定BLE所用的无线频段、调制解调方式和方法等
BLE工作在1Mbps自适应跳频的GFSK射频,免于许可证的2.4GHz ISM(工业、课学、医疗)频段
可以直观理解为规定了BLE的天线部分
- 链路层(LL——Link Layer)
BLE协议栈的核心
相当于TCP/IP协议中的数据链路层(负责选择哪个射频通道,管理当前链路)+网络层(负责识别和发送空中数据包)+传输层(负责保证数据包安全、完整的发送、接收、重传等)
- 主机控制接口层(HCI——Host Controller Interface)
这一层是可选的,HCI主要用于2颗IC实现BLE协议栈的场合,用于贵方两者的通信协议和通信命令等
蓝牙应用协议(Bluetooth Application)
包含的层次简介
- 通用访问配置文件层(GAP——Generic access profile)
实际配置中常接触到的一层
GAP是对LL层有效数据包(payload)进行解析的两种方式中最简单的一种,主要用于广播、扫描、发起连接这些具体行为
- 逻辑链路控制及自适应协议层(L2CAP——Logical Link Control and Adaptation Protocol)
这一层是对LL的简单封装,在L2CAP中区分出是使用加密通道还是普通通道,同时负责连接间隔的管理
- 安全管理层(SM——Security Manager)
负责管理BLE连接的加密、安全,需要兼顾安全性和用户使用的便利性
- 属性协议层(ATT——Attribute protocol)
实际配置中最常接触到的一层
负责定义用户命令和命令操作的数据(如读写数据等)
详细内容见后文【BLE的两种模式】和【ATT简述】
- 通用属性配置文件层(GATT——Generic Attribute profile)
实际配置中常接触到的一层
用于规范attribute中的数据内容(attribute见后文【BLE的两种模式】),并使用分组(group)来对attribute进行分类管理
一般地,BLE在没有GATT的情况下也能跑,只不过互联互通会出现问题
BLE需要在GATT和各种应用profile的支持下才能实现最便捷高效稳定的通信
BT与BLE的区别
当前的蓝牙协议分为基础率/增强数据率(BR/EDR)和低耗能(LE)两种技术类型
经典蓝牙统称BT,低功耗蓝牙称为BLE
经典蓝牙模块(BT)
泛指支持蓝牙协议在4.0以下的模块,一般用于数据量比较大的传输。
经典蓝牙模块可再细分为:传统蓝牙模块和高速蓝牙模块。
传统蓝牙模块在2004年推出,主要代表是支持蓝牙2.1协议的模块,在智能手机爆发的
时期得到广泛支持。
高速蓝牙模块在2009年推出,速率提高到约24Mbps,是传统蓝牙模块的八倍。
低功耗蓝牙模块(BLE)
指支持蓝牙协议4.0或更高的模块,也称为BLE模块(Bluetooth Low Energy Module),最大的特点是成功和功耗的降低。
蓝牙低功耗技术采用可变连接时间间隔,这个间隔根据具体应用可以设置为几毫秒到几秒不等。
另外,因为BLE技术采用非常快速的连接方式,因此可以处于“非连接”状态(节省能源),此时链路两端相互间仅能知晓对方,必要时可以才开启链路,然后在尽可能短的时间内关闭链路。
其他分类
按用途来分:蓝牙模块有数据蓝牙模块,语音蓝牙模块,串口蓝牙模块和车载蓝牙模块
按芯片设计来分:蓝牙模块有flash版本和ROM版本。前者一般是BGA封装(球栅阵列封装),外置flash;后者一般是LCC封装(表面贴装型封装),外接EEPROM。
BLE的两种模式
- 客户端 Client
请求数据服务
客户端可以主动搜索并连接附近的服务端
客户端类似蹭网的
- 服务端Server
提供数据服务
服务端不需要进行主动设置,只要开启广播就可以让附近的客户端搜索到,并提供连接
服务端类似被蹭网的wifi
如果想要让ESP处于别人随时可以搜索连接的情况要配置为服务端;如果想让ESP通过扫描连接周围可连接的蓝牙设备,需要把它设置成客户端,正好和WiFi模式的设定相反
Server通过characteristic对数据进行封装,多个characteristic组成一个Service——Server是一个基本的BLE应用,如果某个Service是一个蓝牙联盟定义的标准服务,也可以称其为profile
要具体了解这些内容需要先了解属性协议层ATT
ATT简述
属性协议层ATT(Attribute Protocol)是GATT和GAP的基础,它定义了BLE协议栈上层的数据结构和组织方式;在层内,它定义了属性(Attribute)的内容,规定了访问属性的方法和权限
属性是一个数据结构,它包括了数据类型和数据值,可以像C语言的结构体那样构造
属性包括三种类型:服务项、特征值和描述符,三者呈包含关系:服务项包含一个或多个特征值,特征值包含一个或多个描述符,多个服务项组织在一起,构成属性规范(Attribute Profile)
属性的种类和分组
属性大致可以分为三种类型:服务项Profile、特征值Characteristic和描述符Descriptor
最顶级为Profile, 下面是多个服务项(Service), 服务项下面是多个特征值(Characteristic), 特征值下面是多个描述符(Descriptor)
每个设备都包含以下必要的特征值和服务项:
PROFILE
- Generic Access Service(Primary Service)
- Device Name(Characteristic)
- Appearance(Characteristic)
- Generic Attribute Service(Primary Service)
- Service Changed(Characteristic)
- CCCD(Descriptor)
- Service Changed(Characteristic)
服务项Service
服务项这种类型本身并不包含数据,仅仅相当于是一个容器,用来容纳特征值
特征值characteristic
特征值用于保存用户数据,但它也有自己的UUID——可以把它看作一个变量,变量里存着数据(用户数据),也有自己的地址信息(UUID)
使用特征值时,也要遵循“先声明再赋值”的步骤——先声明特征值自身,再声明它的项
一个characteristic包含三种条目:
- characteristic声明:每个characteristic的分界符,解析时一旦遇到一个声明,就可以认为接下来又是一个新的characteristic;声明还包含了接下来characteristic值的读写属性等
- characteristic值:数据的值
- characteristic描述符:数据的额外信息
一般BLE的属性体系在系统中以GattDB表示,即属性数据库,gattDB是BLE协议栈在内存中开辟的一段专有区域,会在特定的时候写入Flash进行保存,并在启动时读取出来回写到内存中去,但并非所有的BLE数据通信是操作gattDB
characteristic用Attribute数据结构来实现
属性Attribute的数据结构
Attribute由四部分组成:
属性句柄Attribute handle:可以视为指向属性实体的指针,对端设备通过属性句柄来访问某个属性,大小2字节,起始于0x0001,系统初始化时,各属性的句柄逐步+1,但最大不超过0xFFFF
属性类型Attribute type:用以区分当前属性是服务项或是特征值等,用通用唯一识别码(UUID)标识的16字节十六进制字符串(形如f6257d37-34e5-41dd-8f40-e308210498b4,从网上抄来的示例,如有雷同那就是雷同)表示。一个合法的UUID,一定是随机的、全球唯一的,不应该出现两个相同的UUID。属性类型分类如下:
- 首要服务项Primary Service
- 次要服务项Secondary Service
- 包含服务项Include
- 特征值Characteristic
他们与UUID的映射关系如下:
- 0x1800 – 0x26FF :服务项类型
- 0x2700 – 0x27FF :单位
- 0x2800 – 0x28FF :属性类型
- 0x2900 – 0x29FF :描述符类型
- 0x2A00 – 0x7FFF :特征值类型
为了减少传输的数据量,BLE协议做了一个转换约定,给定一个固定的16字节模板,只设置2个字节为变化量,其他为常量,2字节的UUID在系统内部会被替换,进而转换成标准的16字节UUID;反之,如果一个特征值的UUID是16字节的,在系统内部它的属性类型也可能写成第3、4字节组成的双字节
示例如下:
UUID模板为
1
0000XXXX-0000-1000-8000-00805F9B34FB
其中从左数第3、4个字节“XXXX”就是变化位,其他为固定位。如:UUID=0x2A00在系统内部会转换成00002A00-0000-1000-8000-00805F9B34FB。
属性值Attribute value:真正的数据值,大小为0-512字节。如果该属性是服务项类型或者是特征值声明类型,那么它的属性值就是UUID等信息;如果是普通的特征值,则属性值是用户的数据,属性值需要预留空间以保存用户数据,可以将属性值的预留空间看做I2C的数据空间,操作特征值里的用户数据,就是对那块内存空间进行读写,==所以启用蓝牙后会占用额外的内存==
属性权限Attribute permissions:Attribute的权限属性,主要有四种:
- 访问权限(Access Permission):只读或只写或读写
- 加密权限(Encryption Permission):加密或不加密
- 认证权限(Authentication Permission):需要认证或无需认证。指相互确认对方身份,BLE中所说的“认证”过程就是设备配对
- 授权权限(Authorization Permission):需要授权或无需授权。指对授信设备开放权利
授权的管控等级要高于认证,认证的设备未必被授权,授权的设备一定是认证的——认证是授权的充分不必要条件。认证是设备配对,两边都符合协议规定就行,但是授权取决于Server设备对Client设备的主动许可。
一个没有经过认证的设备,被称为未知设备(Unknown Device);经过了认证,该设备会在绑定信息中被标记为Untrusted,被称为不可信设备(Untrusted Device);经过了认证,并且在绑定信息中被标记为Trusted的设备被称为可信设备(Trusted Device)
具体的权限示例如下所示:
- Open(随意读写)
- No Access(禁止读写)
- Authentication(需要配对才能读写,分成很多子类型用于适配配对的类型)
- Authorization(允许应用在回调函数中读写)
- Signed(签名后才能随意读写)
属性协议ATT PDU
拥有一组属性的设备称为服务端(Server);读写该属性值的设备称为客户端(Client)
Client和Server之间通过ATT PDU通信
属性协议ATT PDU共有6种,如下表所示:
ATT PDU种类 | 发送方向 | 触发响应 | 说明 |
---|---|---|---|
Command | Client -> Server | – | 客户端发送Command,服务器无需任何返回 |
Request | Client -> Server | Response | 客户端发送Request,服务器需要返回一个Response,表明服务器收到 |
Response | Server -> Client | – | |
Notification | Server -> Client | – | 服务器发送Notification,户端无需任何返回 |
Indication | Server -> Client | Confirmation | 服务器发送Indication,客户端需要返回一个Confirmation,表明客户端收到 |
Confirmation | Client -> Server | – |
BLE下,所有命令都是“必达”的,每个命令发送完毕后,发送者会等待ACK信息(类似I2C),如果收到了ACK包,发起方认为命令完成;否则发起方会一直重传该命令直到超时导致BLE连接断开(类似CAN的出错重发机制),可以说只要数据包放到了协议栈射频FIFO中,蓝牙协议栈就能保证该数据包“必达”对方,但是没有回复相对有回复就是“不太可靠”,这时候就需要特殊的“有回复属性”
Request后缀
特别地,如果一个命令需要response,那么可以在相应命令后面加上request后缀,这个response包在应用层有回调事件,可以用于触发特殊的功能,这是默认的协议ACK恢复不具有的,采用request/response方式,应用层可以按顺序地发送一些数据包;如果一个命令只需要ACK而不需要response,那么它的后面就不会带request
然而Request/response会大大降低通信的吞吐率,因为request/response必须在不同的连接间隔中出现,这就导致两个连接间隔最多只能发一个数据包,而不带request后缀的ATT命令就没有这个问题——一般情况下,在同一个连接间隔中可以同时发多个数据包,这样将大大提高数据的吞吐率
常用的带request命令:所有read命令,writerequest,indication等
常用的不带request命令:write command,notification等
通用属性协议GATT简述
GATT(Generic Attribute Profile),描述了一种使用ATT的服务框架。该框架定义了服务(Server)和服务属性(characteristic)的过程(Procedure)及格式,负责处理具体数据段通过蓝牙连接的发送和接收
现在的BLE大多建立在GATT协议之上,GATT建立在ATT和L2CAP之上,GATT需要使用通用访问协议GAP来确定设备的连接
通用访问协议GAP
GAP 使设备被其他设备可见,并决定了当前设备是否可以或者怎样与合同设备进行交互
GAP中,设备被分为外围设备Peripheral和中心设备Central
外围设备:性能相对较弱、功耗相对低的设备,他们通常被连接到更加强大的中心设备
中心设备:性能相对较强、功耗较高的设备
GAP广播
GAP 中外围设备不停向外广播以让中心设备知道它的存在。通过两种方式向外广播数据:
广播数据(Advertising Data Payload):必须的,外设需要以此来和中心设备取得连接
扫描回复(Scan Response Data Payload):可选的,中心设备可以向外围设备请求扫描回复,向其提供一些设备的额外信息
外围设备会设定一个广播建个,每个间隔中,它会重新发送自己的广播数据,广播间隔越长约省电,但同时更不容易被扫描到
基于GATT广播的BLE连接只能是一个外围设备连接一个中心设备,可以理解成一个蓝牙耳机只能连接一台手机,不能同时连接两台手机
GATT协议
GATT协议建立在ATT协议的基础上。将ATT协议中的Service、Characteristic 及对应数据都保存在一个查找表中,查找表使用16位的ID作为索引。建立GATT连接前必须先经过GAP协议
GATT连接是独占的,也就是说同一个BLE外设(外部设备)同时只能被一个中心设备连接,一旦外设被连接,它就会停止GAP广播,对其它设备不可见;当设备断开时它又开始广播。
如果中心设备和外设需要双向通信,唯一的方式就是建立GATT连接,GAP通信是单向的,只能让中心设备向外设发送信息
GATT通信双方是C/S关系,外设作为GATT的Server,维持ATT查找表、Service、Characteristic定义;中心设备作为GATT的Client,向Server发起请求,所有通信事件都由中心设备Client发起,从Server接收响应。一旦连接建立,外设将会给中心设备建议一个连接间隔,中心设备可以选择在每个连接间隔尝试重新连接,检查是否有新数据,不过这个间隔只是建议,中心设备可以不严格按照这个间隔执行请求。
GATT结构
GATT结构建立在ATT的属性Attribute数据结构之上(其实和ATT的那些东西一模一样)
Attribute结构体组成种类不同的Characteristic,多个Characteristic被封装在Servce容器中,Characteristic和Service容器都有着自己的UUID(有官方认证的16位UUID和自定义的128位UUID),各种常用的Service集合成Profile
BLE外设的通信主要通过Characteristic,通过在Characteristic中读写数据就实现了双向通信,也可以通过实现类似串口的Service来配置TxCharacteristic和RxCharacteristic,这些都是具体项目的选择了
BLE从初始化到建立连接的过程简述
- 外围设备开始广播,发送完一个广播包后T_IFS,开启射频Rx窗口接收来自中心设备的数据包
- 中心设备扫描到广播,在收取此广播T_IFS后如果开启了中心设备的扫描回复,中心设备将向外设发送回复
- 外设收取到中心设备的回复,做好接收准备,并返回ACK包
- 如果ACK包未被中心设备接收到,中心设备将一直发送回复直到超时,此期间内只要外设返回过一次ACK包就算连接成功
- 开始建立通信,后续中心设备将以收取到外设广播的时间为原点,以Connection Interval为周期向外设发送数据包,数据包将具有两个作用:同步两设备时钟和建立主从模式通信
外设每收到中心设备的一个包,就会把自己的时序原点重新设置,以和中心设备同步(Service向Client同步)
BLE通信在建立成功后变为主从模式,中心设备Central变为Master,外设Peripheral变为Slave,Slave只能在Master向它发送了一个包以后才能在规定的时间内把自己的数据回传给Master
- 连接建立成功
- 外设自动停止广播,其他设备无法再查找到该外设
- 按照以下时序进行通信,在中心设备发送包的间隔内,外设可以发送多个包
- 需要连接断开时只需要中心设备停止连接(停止发送包)即可
- 中心设备可以将外设的addr写入Flash或SRAM等存储器件,保持监听此addr,当再次收到外设广播时就可以建立通信。BLE Server设备为了省电,当一段时间内没有数据要发送时,可以不再发送包,双方就会因为连接超时(connection timeout)断开,这时需要中心设备启动监听,这样,当BLE Server设备需要发送数据时,就可以再次连接
ESP的蓝牙外设配置
蓝牙配置相关库函数
相关头文件及其作用
1 |
蓝牙控制器
使用esp_bt_controller_init()
1 | esp_bt_controller_init(esp_bt_controller_config_t *cfg);//esp_bt_controller_config_t是蓝牙控制器配置结构体 |
初始化蓝牙控制器,此函数只能被调用一次,且必须在其他蓝牙功能被调用之前调用
使用esp_bt_controller_deinit()来取消初始化,用于关闭蓝牙并清除其占用的内存,还会将蓝牙任务删除
下面是蓝牙控制器的常用API
1 | esp_bt_controller_enable(esp_bt_mode_t mode);//使能蓝牙控制器,mode是蓝牙模式,如果想要动态改变蓝牙模式不能直接调用该函数,应该先用下面的disable关闭蓝牙再使用该API来改变蓝牙模式 |
特别地,官方文档中给出了一套在线升级蓝牙设备软件时的关闭流程
1 | esp_bluedroid_disable(); |
经典蓝牙
用于蓝牙运行的API如下所示
1 | esp_bluedroid_get_status(void);//获取蓝牙当前状态 |
用于设备蓝牙配置的API如下所示
1 | esp_bt_dev_get_address(void);//获取当前设备蓝牙地址 |
这些函数都应该在蓝牙启用后被调用
BLE-GAP相关库函数
外围设备库函数
1 | esp_ble_gap_start_advertising(esp_ble_adv_params_t *adv_params);//开始广播 |
中心设备库函数
1 | esp_ble_gap_start_scanning(uint32_t duration);//使用该函数让设备扫描附近正在广播的外设,duration为扫描间隔 |
连接配置库函数
1 | esp_ble_gap_update_conn_params(esp_ble_conn_update_params_t *params);//在连接建立后更新连接参数 |
GATT Server的配置
Server-Master
基本设置
1 | esp_err_t ret;//用于debug |
有限状态机FSM(finite state machine),或者说状态机SM(state machine)是一种特殊的控制算法,能够根据控制信号按照预先设定的状态进行状态转移
若输出只和状态有关而与输入无关,则称为Moore状态机;若输出不仅和状态有关而且和输入有关系,则称为Mealy状态机
控制蓝牙的状态机一般为Moore状态机,随蓝牙所处的状态进行不同的操作(代码中通过switch语句进行控制)
Server的profile利用一个结构体来定义,结构体成员取决于在这个profile中执行的service和characteristic,如下所示
1 | struct gatts_profile_inst { |
可以将这个结构体进一步组合为结构体数组
1 | static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = { |
这样使用类似gl_profile_tab[i].gatts_if
的语句就可以访问结构体的成员,i用于指示第(i+1)个profile
使用上面的结构体数组来定义每个profile对应的GATT回调函数(gatts_profile_a_event_handler()、gatts_profile_b_event_handler()),就使得每个不同的profile使用不同的接口;初始化时,将gatts_if = ESP_GATT_IF_NONE,在之后通过各自的处理函数将profile连接到接口
最后使用esp_ble_gatts_app_register()这个API将应用的ID注册到GATT
1 | ret = esp_ble_gatts_app_register(GATT_APP_A_ID);//run GATT app A register |
GAP设置
使用esp_ble_adv_data_t结构体来配置GAP广播情况,并使用esp_ble_gap_config_adv_data()函数进行广播
1 | typedef struct { |
一个广播的有效数据是31字节,如果超过会导致超出部分被截掉
使用esp_ble_gap_config_adv_data_raw()和esp_ble_gap_config_scan_rsp_data_raw()函数可以广播自定义的空数据
广播数据设置完毕后,会自动进入ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT()或ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT状态,此时可以在gap_event_handler()中设置FSM控制程序
1 | static void BLE_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param) |
只要使用了esp_ble_gap_start_advertising()函数,GATT Server就会开始广播,在此之前还需要用esp_ble_adv_params_t结构体配置相关的参数
1 | //广播参数 |
设置完毕后,可以使用esp_ble_gap_start_advertising()进行广播
注意:esp_ble_gap_config_adv_data()使用esp_ble_adv_data_t结构体进行设置,配置的是广播出去的数据;而esp_ble_gap_start_advertising()使用esp_ble_adv_params_t结构体进行设置,配置的是该怎样广播
经典蓝牙的子集SPP
蓝牙串口协议Serial Port Profile简写为SPP,SPP就是一种能在蓝牙设备之间创建串口进行数据传输的协议,最终目的是在两个不同设备(通信的两端)上的应用之间保证一条完整的通信路径
SPP的协议栈示意图如下
连接流程
- 创建虚拟连接
- 接受虚拟串口连接
- 在本地SDP数据上注册服务
SPP协议与GATT协议的对比
经典蓝牙BT-SPP | 低功耗蓝牙BLE-GATT |
---|---|
速率高 | 灵活多变、集成很多profile |
兼容性好 | 高速率传输时兼容性难以保障 |
对IOS不友好 | 对IOS很友好 |
APP编程不方便 | 开发资源丰富、接口多 |