深入NXP低功耗蓝牙SDK开发系列 - 广播,扫描与连接

接上一篇整体架构介绍后,相信大家对NXP低功耗协议的使用还是没有什么概念(我没写错你也没看错)。由于第一篇博文信息量过大,没有在NXP BLE SDK上做过一定开发的的同学看起来肯定是云里雾里。从本篇开始将BLE SDK中逐个功能进行剖析,并且尽可能按照由浅入深的顺序发布。

前置条件

由于网上有大量的文章介绍BLE技术,这儿就不从零讲起基本概念了,假定各位同学对BLE广播与扫描功能有基本的了解,至少知道得以下几个方面:

  1. 广播和扫描的意义(为什么双方要做这件事)
  2. 双方的角色扮演(简单说主机扫描,从机广播)
  3. 基本的广播参数:时间间隔,广播通道,扫描占空比等
  4. 广播中带有一些数据,如果知道他们的格式就更好了

虽然文章主要目的是要在介绍如何在NXP低功耗蓝牙SDK中进行广播和扫描,但如果一上来就讲API又过于干涩。在涉及到笔者认为有必要展开的协议内容的时候我还是会将一下原理的。

BLE 5对广播和扫描这部分有一些比较大的变化,目前市面上相关的应用还比较少,因此本篇仍以BLE 4.2规范中定义的功能作为出发点,后续如有必要再单独介绍BLE 5带来的广播扩展功能。

什么时候可以开始广播 & 扫描?

在BLE协议规范中,对主机(Host)和控制器(Controller)初始化的流程有一个清晰定义,用户要走完这个流程才能向协议栈提交请求,同时BLE芯片也需要对射频部分寄存器进行初始化来保证RF电路的正常工作。NXP BLE SDK中的系统入口main_task()任务体实现里,在正式进入事件loop之前将会调用Ble_Initialize()来准备以上两项工作。注意!是准备,而非完成。也就是说该函数返回后,协议栈可能仍然没有准备好。记得千万不要直接在这个函数后面开启广播和扫描!

那何时才可以呢?初始化过程在调用Ble_Initialize()时SDK会让安装一个默认的回调函数App_GenericCallback(),回调触发后再由她转而触发用户层的BleApp_GenericCallback()。当协议栈完成所有初始化工作后,用户将在这个回调函数里收到gInitializationComplete_c事件,这才标志着用户可以正常使用BLE协议栈提供的服务了。

广播(advertising)

在NXP BLE SDK中涉及到广播主要是4个API:

// 设置广播数据(和扫描回复数据)
bleResult_t Gap_SetAdvertisingData(gapAdvertisingData_t*   pAdvertisingData, 
                                   gapScanResponseData_t*  pScanResponseData);

// 设置广播参数
bleResult_t Gap_SetAdvertisingParameters(gapAdvertisingParameters_t* 
                                         pAdvertisingParameters);

// 开启广播
bleResult_t Gap_StartAdvertising(gapAdvertisingCallback_t advertisingCallback, 
                                 gapConnectionCallback_t  connectionCallback);

// 停止广播
bleResult_t Gap_StopAdvertising(void);

以上的4个API都是异步的,就意味着调用后会立刻得到返回值,该返回值仅表示函数调用(如参数传递是否正确,内存是否充足)的结果,实际功能的执行完成,用户应等到各个的回调事件到来作为判断。

下表列出了相关的事件:

事件Tag 触发函数&事件 用户回调函数
gAdvertisingDataSetupComplete_c Gap_SetAdvertisingData 通用回调函数BleApp_GenericCallback
gAdvertisingParametersSetupComplete_c Gap_SetAdvertisingParameters 通用回调函数BleApp_GenericCallback
gAdvertisingStateChanged_c * Gap_StartAdvertising Gap_StopAdvertising 广播回调函数BleApp_AdvertisingCallback

注意:用户需要自己记录当该事件产生时广播是被打开了还是被关闭了。

通常用户需要先设置好广播数据和广播参数再开启广播,需要按照一定顺序调用这几个API(利用回调作为衔接)。在使用NXP BLE SDK的时,ble conn manager和每一份例程代码都已经帮用户把广播流程规划好了,用户只需要在合适的时候调用例程代码中的 BleApp_Advertise()即可。如果例程带的广播策略符合用户的需求,则用户只需要关心app_config.c中所填充的广播数据和广播参数即可。

广播数据

根据BLE协议规定,广播包可以发送最多31个字节的数据,如果设备支持扫描请求(Scan Request),还可以在扫描回复(Scan Response)里在回复31个字节,这样最长也就是62字节的信息量。这些信息不是随便填的,必须按照BLE协议规范定义的格式,主机才能正确的解析其中的内容。一个广播包(或者Scan Response包)由如若干AD Structure结构组成,在代码层面,NXP协议栈提供了一个由gapAdStructure_t组成的结构体数组,用户可以将AD数据段依次填入。下面是一个有3个AD Structure的示例:

static const gapAdStructure_t advScanStruct[3] = {
  {
    .length = NumberOfElements(adData0) + 1,
    .adType = gAdFlags_c,
    .aData = (uint8_t *)adData0
  },
  {
    .length = NumberOfElements(uuid_service_qpps) + 1,
    .adType = gAdComplete128bitServiceList_c,
    .aData = (uint8_t *)uuid_service_qpps
  },
  {
    .adType = gAdShortenedLocalName_c,
    .length = 8,
    .aData = (uint8_t*)"NXP_QPPS"
  }
};

详细格式请参照Core Spec中GAP章节以及CSS(核心规范补充)。

广播参数说明

typedef struct gapAdvertisingParameters_tag {
    uint16_t                         minInterval;
    uint16_t                         maxInterval;
    bleAdvertisingType_t             advertisingType;
    bleAddressType_t                 ownAddressType
    bleAddressType_t                 peerAddressType;
    bleDeviceAddress_t               peerAddress;
    gapAdvertisingChannelMapFlags_t  channelMap;
    gapAdvertisingFilterPolicy_t     filterPolicy;           
} gapAdvertisingParameters_t;
成员 取值范围 说明
minInterval 0x20 - 0x4000 20ms - 10.24s(步进0.625ms),最小建议广播间隔
maxInterval 0x20 - 0x4000 20ms - 10.24s(步进0.625ms),最大建议广播间隔
advertisingType gAdvConnectableUndirected_c, gAdvDirectedHighDutyCycle_c, gAdvScannable_c, gAdvNonConnectable_c, gAdvDirectedLowDutyCycle_c 四种广播策略(其中定向广播还分两种)
ownAddressType gBleAddrTypePublic_c 或 gBleAddrTypeRandom_c 该参数决定广播包的地址类型
peerAddressType gBleAddrTypePublic_c 或 gBleAddrTypeRandom_c 该参数只在定向广播时有效,决定了定向广播包中填写的对方地址类型
peerAddress 48位蓝牙地址 该参数只在定向广播时有效,决定了定向广播包中填写的对方蓝牙地址
channelMap gAdvChanMapFlag37_c, gAdvChanMapFlag38_c,gAdvChanMapFlag39_c 广播通道的bitmap
filterPolicy gProcessAll_c,gProcessConnAllScanWL_c,gProcessScanAllConnWL_c,gProcessWhiteListOnly_c 广播白名单策略

扫描(scan)

协议栈涉及到扫描有3个API:

// 设置扫描策略
bleResult_t Gap_SetScanMode(gapScanMode_t           scanMode, 
                            gapAutoConnectParams_t* pAutoConnectParams);

// 开启扫描
bleResult_t Gap_StartScanning(gapScanningParameters_t*  ScanningParameters,
                              gapScanningCallback_t     scanningCallback,
                              bool_t                    enableFilterDuplicates);

// 停止扫描
bleResult_t Gap_StopScaning(void);

同广播API,他们也都是异步执行的。其中Gap_SetScanMode是一个可选的API,用户可以在开启扫描之前通过这个函数来配置扫描的策略,以决定是否将所有扫描到的从设备都抛给应用层(默认),或者仅对‘LimitedDiscovery’或'GeneralDiscovery'的广播进行上报。同时还定义是否主机自动连接指定的从机,无需用户在应用层显示调用第三部分要介绍的Gap_Connect动作,在此模式下不会上报被扫描的从机。枚举类型gapScanMode_tag的定义如下:

typedef enum gapScanMode_tag {
    gDefaultScan_c,
    gLimitedDiscovery_c,
    gGeneralDiscovery_c,
    gAutoConnect_c
} gapScanMode_t;

Gap_StartScanning 的参数是自解释的,主要工作也是在app_config.c中填写扫描参数结构体ScanningParameters,下面小节对每个参数作了说明。

扫描参数说明

typedef struct gapScanningParameters_tag {
    bleScanType_t               type; 
    uint16_t                    interval; 
    uint16_t                    window; 
    bleAddressType_t            ownAddressType;
    bleScanningFilterPolicy_t   filterPolicy;
} gapScanningParameters_t;
成员 取值范围 说明
type gScanTypePassive_c 或 gScanTypeActive_c 选择被动或者主动扫描
interval 0x04 - 0x4000 2.5ms - 10.24s(步进0.625ms),两次扫描的间隔
window 0x04 - 0x4000 2.5ms - 10.24s(步进0.625ms),每次扫描的持续事件,当window = interval时连续扫描
ownAddressType gBleAddrTypePublic_c 或 gBleAddrTypeRandom_c 该参数决定在Scan request时主机发出的包的地址类型
filterPolicy gScanAll_c 或 gScanWithWhiteList_c 是否启用Scan白名单

连接 (connect)

主设备在完成扫描流程后,获取了周围设备的列表和基本信息。如果要进一步与某一个设备进行用户数据交互则双方需要进入连接状态(非beacon的应用)。
建立(和断开)BLE连接只涉及到2个API和2个Event:

// 建立连接,仅主设备允许调用
bleResult_t Gap_Connect(gapConnectionRequestParameters_t*   pParameters,
                        gapConnectionCallback_t             connCallback);

// 断开连接,主从都可以调用
bleResult_t Gap_Disconnect(deviceId_t deviceId);

这两个API各自对应了一个'连接'和'断开连接'事件,通知应用层示连接已经建立或者已经断开:

事件Tag 触发函数 回调函数
gConnEvtConnected_c * Gap_Connect 连接回调函数BleApp_ConnectionCallback
gConnEvtDisconnected_c * Gap_Disconnect 连接回调函数BleApp_ConnectionCallback

注意:这两个事件并非只有当调用函数才会产生,如果对端是主设备与我们建立连接,作为从设备我们也会得gConnEvtConnected_c 。同样,如果对端主动与我们断开,或者链路因某些异常原因断开,协议栈也会抛出gConnEvtDisconnected_c事件。这种会被协议栈主动触发的事件在SDK中还有不少,我们可以称为异步事件。

在NXP BLE SDK开发时,Gap_Connect的API被包在App_Connect内的,用户一般是调用App_Connect,她传入的连接回调函数会在用户线程上下文中被调用。在实时操作系统环境下,事件处理中即使有长时间占用CPU的行为(比如打印出事件参数),也不会对协议栈造成影响。这个我们在前面架构介绍中有讲解这个机制,有困惑的同学可以回去上一篇文章。

连接请求参数

调用Gap_Connect时需要主设备填充一组连接参数,这组参数将由协议栈送给Controller,由Controller最终决定如何与对端设备建立连接(Controller将考虑其他连接的时序要求)

typedef struct gapConnectionRequestParameters_tag {
    uint16_t                    scanInterval; 
    uint16_t                    scanWindow;
    bleInitiatorFilterPolicy_t  filterPolicy;
    bleAddressType_t            ownAddressType;
    bleAddressType_t            peerAddressType;
    bleDeviceAddress_t          peerAddress;
    uint16_t                    connIntervalMin;
    uint16_t                    connIntervalMax;
    uint16_t                    connLatency;
    uint16_t                    supervisionTimeout;
    uint16_t                    connEventLengthMin;
    uint16_t                    connEventLengthMax; 
    bool_t                      usePeerIdentityAddress;
} gapConnectionRequestParameters_t;
成员 取值范围 说明
scanInterval 0x04 - 0x4000 2.5ms - 10.24s(步进0.625ms),两次扫描的间隔
scanWindow 0x04 - 0x4000 2.5ms - 10.24s(步进0.625ms),每次扫描的持续事件,当window = interval时连续扫描
filterPolicy gUseDeviceAddress_c, gUseWhiteList_c 是否启动Initiator白名单
ownAddressType gBleAddrTypePublic_c 或 gBleAddrTypeRandom_c 该参数决定在Connect request中自己的地址类型
peerAddressType gBleAddrTypePublic_c 或 gBleAddrTypeRandom_c 该参数决定在Connect request时被连接设备的地址类型
peerAddress 48位蓝牙地址 该参数决定在Connect request时被连接设备的地址
connIntervalMin 0x06 - 0x0C80 7.5ms - 4s(步进1.25ms)建议最小的连接间隔
connIntervalMax 0x06 - 0x0C80 7.5ms - 4s(步进1.25ms)建议最大的连接间隔
connLatency 0 - 499 从机可忽略的连接事件数量
supervisionTimeout 0x0A - 0x0C80 100ms - 32s(步进10ms)最大超时断链间隔
connEventLengthMin 0 - 0xFFFF 连接事件最小长度
connEventLengthMax 0 - 0xFFFF 连接事件最大长度
usePeerIdentityAddress TRUE or FALSE 当Controller Privacy开启时是否使用

NXP BLE SDK作为主设备类型的例程在app_config.c都会给出一个连接参数配置gConnReqParams,用户直接根据自己需求修改即可,peerAddresspeerAddressType两个参数则来自于扫描得到的数据。至于主设备连接哪一个扫描到的从设备,这完全是用户的定义的行为了,通过广播数据的字段来判断是一种常用的做法。

同时广播与扫描

有的产品担任双角色,既可以作为主设备扫描并连接其他从设备,同时也可以作为从设备,广播被其他主设备发现并连接。甚至需要同一时刻做这两件事(同时广播和扫描)。

虽然通常蓝牙芯片只有一个RF端口,但广播和扫描这两项功能一个是只发送数据(Tx),一个仅接收数据(Rx),因此射频上不存在问题。从BLE 4.1协议规范后,定义了BLE芯片在链路层支持多个状态机,这样就完整的支持了同时进行广播和扫描,以及建立多个连接,和混合的拓扑结构。

在NXP BLE SDK中实现这个功能是非常简单的,首先确认使用的协议栈库文件是支持主从设备的,而非仅支持从设备(peripheral)的库,然后按照文章前面介绍的广播与扫描API调用和Event处理流程操作即可,二者并不冲突。

连接之后

建立完连接只是万里长征第一步,连接建立后双方通常还不能直接进行用户数据交互,后面还要进行一系列BLE协议规范所要求的过程,如配对加密,客户端对服务器端的服务进行查询,若知后事如何,请听下回分解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容