BLE 发现过程介绍

BLE 主从设备建立连接以后,先执行发现服务和特征的过程(Discovery Procedure),然后对着指定特征执行数据收发。

本文以一个Nordic LED Button服务为例(如下),介绍服务和特征发现过程的协议细节。

1. 发现服务

对于主机而言,首先找到目标服务的句柄范围,然后再在句柄范围内找下面的特征。

发现服务有两个方案:

  • 发现全部服务
  • 发现指定UUID的服务

1.1 发现全部服务

发现一个服务的基本思路是搜索Primary Service的UUID(0x2800),如果找到,即可获得它的句柄范围。

第一次我们从0x0001开始搜索,找到一个服务后,从该服务的句柄末尾开始继续搜索,可以找到第二个服务的句柄范围。

反复执行发现服务操作,就可以依次找到全部服务的句柄范围。

执行发现服务时,GATT协议层主机会产生一个请求(Read By Group Type Request),从机收到后返回一个响应(Read By Group Type Response)。

(为了说明一些问题,这个图片不对应上面的Nordic LED Button服务)

图中,请求包中包含了起始和末尾的句柄,第一次发现默认是0x0001~0xFFFF。

响应帧中包含了两个服务的UUID,代表它发现的两个服务。(为啥一次性发现两个?原因未知,它仅限于Generic Access和Generic Attribute)。

第二次的响应帧就比较正常,返回了发现的一个服务UUID。如果检查该数据帧的细节,能够看到该服务的起始和结束UUID。

SDK 15.3中执行发现全部服务的函数是: sd_ble_gattc_primary_services_discover(uuid),注意要将uuid参数设置为NULL。 在该函数的回调事件中即可获得发现的服务的UUID和句柄范围。

1.2 发现指定UUID服务

发现指定UUID的服务,顾名思义,除了查找0x2800,还查找指定的UUID。

通过它可以快速找到目标服务,对于不关心的服务项可以快速略过,加快整体发现过程的速度。

当执行发现指定UUID服务时,主机发出一个请求(Find By Type Value Request),从机返回一个响应(Find By Type Value Response)。

观察请求包,它包含了一个UUID的输入参数。响应包中,返回数据中的11就是该服务的句柄。

SDK 15.3中执行发现全部服务的函数是: sd_ble_gattc_primary_services_discover(uuid),注意要将uuid参数设置为目标UUID。 在该函数的回调事件中即可获得发现的服务的UUID和句柄范围。

值得一提的是, 如果你做一个nRF Connect APP这种通用性主机,肯定要使用发现全部服务。对于一个特定的主机,比如针对这个Blinky从机做的主机,我们知道Blinky服务的UUID,则更适合选择第二种方案。

2. 发现特征

发现特征一定是发现某个指定服务下面的特征,所以一定要先发现服务,再发现特征。

现在我们有了一个服务的句柄范围,比如0x000B ~ 0x0010。

与发现服务类似,发现特征也有两个方案:

  • 发现全部特征
  • 发现指定UUID的特征

发现特征时,主机在服务的句柄范围内搜索特征声明的UUID(0x2803),一旦找到就返回响应,响应中包含了特征的关键信息,比如:特征的句柄范围,特征值的UUID,特征的属性、权限等。

根据特征的句柄范围,我们能够判断服务下面的特征是否都被发现,如果没有,则需要重复执行发现特征,直到所有的特征都被发现。

发现特征时,主机发出一个请求(Read By Type Request ),从机返回一个响应(Read By Type Response)。

图中,从响应包中可以看到该特征的属性(read, Notify),句柄(13)和UUID。

在SDK 15.3中,可以利用sd_ble_gattc_characteristics_discover()来执行发现全部特征的操作。

但是很遗憾,没有一个API能够发现指定UUID的特征,只能一个一个的发现。

3. 发现描述符

如果特征具有Notify或Indicate属性,它一定携带一个CCCD描述符。

我们在发现特征的时候检测其属性,如果属性包含Notify或Indicate,则执行发现描述符的过程。

发现描述符相对简单,搜索描述符的UUID(0x2902或其他),并在响应包中给出其句柄和UUID信息。

发现描述符时,主机发出一个请求(Find Information Request),从机返回一个响应(Find Information Response)。

通过这几个步骤,就可以完整的发现目标服务和特征。

如果服务下面有多个特征,一个一个的发现会非常繁琐,代码上需要进行大量的判断,代码逻辑必然不简单,所以SDK 提供了一个库来完成这些繁冗的操作,它就是ble_db_discovery库。

如果理解了上面这套发现过程,使用ble_db_discovery必然能驾轻就熟,反过来则很可能搞不懂它的设计思路并在使用中产生疑惑,这也是本文产生的原因。

(完)