文章目录
-
前言1
通过"GR5x系列BLE知识及应用专题" 系统介绍一些 BLE 涉及的知识概念, 以及如何使用 Goodix GR5xx 系列芯片的SDK进行BLE应用开发。
可以从下面网页获得 GR5xx 系列蓝牙芯片的介绍和选型参考.如果博客有内容引用自网络, 会在文末添加参考来源.
-
前言2:
本文主要介绍了如何基于汇顶(goodix.com) GR5x SDK, 从无到有在工程中引入BLE功能。主要分为以下几步:
a. 自定义服务; b. 添加服务相关文件; c. 实现用户代码,如初始化gap参数、服务初始化和相关事件处理函数等; d. 协议栈初始化;
上篇会说明如何自定义服务及添加服务文件;下篇会介绍实现用户代码及协议栈初始化. 可以从汇顶官网(www.goodix.com)下载 GR5526 SDK进行代码对照.
3. 实现用户代码
3.1 初始化GAP参数
3.1.1 广播参数
广播参数主要包括ble_gap_adv_param_t和ble_gap_adv_time_param_t:
typedef struct
{
uint8_t adv_mode; /**< Advertising mode (see enum @ref ble_gap_adv_mode_t). */
uint8_t disc_mode; /**< Discovery mode (see enum @ref ble_gap_disc_mode_t). */
uint8_t filter_pol; /**< Advertising filtering policy (see enum @ref ble_gap_adv_filter_policy_t). */
ble_gap_bdaddr_t peer_addr; /**< Peer address configuration (only used in case of directed advertising, or used to locate the IRK list). */
uint16_t adv_intv_min; /**< Minimum advertising interval (in unit of 625 μs). Must be greater than 20 ms. */
uint16_t adv_intv_max; /**< Maximum advertising interval (in unit of 625 μs). Must be greater than 20 ms. */
uint8_t chnl_map; /**< Advertising channel map. See @ref BLE_GAP_ADV_CHANNEL. */
bool scan_req_ind_en; /**< Indicate if the application should be informed when receiving a scan request from the scanner. */
int8_t max_tx_pwr; /**< Maximum power level at which the advertising packets have to be transmitted (between -127 dBm and 127 dBm). For the real value, please refer to GR551x Datasheet. */
} ble_gap_adv_param_t;
- adv_mode
typedef enum
{
BLE_GAP_ADV_TYPE_ADV_IND = 0, /**< Undirected connectable and scannable mode. */
BLE_GAP_ADV_TYPE_ADV_NONCONN_IND, /**< Non-connectable and non-scanable mode. */
BLE_GAP_ADV_TYPE_ADV_SCAN_IND, /**< Undirected scannable mode. */
BLE_GAP_ADV_TYPE_ADV_HIGH_DIRECT_IND, /**< Directed high duty cycle mode. */
BLE_GAP_ADV_TYPE_ADV_LOW_DIRECT_IND, /**< Directed low duty cycle mode. */
} ble_gap_adv_mode_t;
○ BLE_GAP_ADV_TYPE_ADV_IND是最常用的广播模式,可连接的非定向广播,它表示当前设备可以接收其他任何设备的连接请求;
○ BLE_GAP_ADV_TYPE_ADV_NONCONN_IND是不可连接也不可被扫描的广播,设备仅仅发送广播数据,而不想被扫描或者连接,一般用于mesh或者beacon项目;
○ BLE_GAP_ADV_TYPE_ADV_SCAN_IND 是可扫描的非定向广播,又称可发现广播,这种广播不能用于发起连接,但允许其他设备扫描该广播设备;
○ BLE_GAP_ADV_TYPE_ADV_HIGH_DIRECT_IND是可连接的高占空比的定向广播(一个广播事件中连续两个在同一个广播信道索引处发出个月的ADV_DIRECT_IND PDU的间隔时间需要小于或等于3.75ms);
○ BLE_GAP_ADV_TYPE_ADV_LOW_DIRECT_IND时可连接的低占空比模式的定向广播(一个广播事件中连续两个ADV_DIRECT_IND PDU的间隔时间需要小于或等于10ms)
定向广播类型是为了尽可能快的建立连接,这种报文包含了两个地址:广播者的地址和发起者的地址。发起者收到发给自己的定向广播报文后,可以立即发送连接请求作为回应。
- disc_mode
typedef enum
{
BLE_GAP_DISC_MODE_NON_DISCOVERABLE = 0, /**< Non-discoverable mode. */
BLE_GAP_DISC_MODE_GEN_DISCOVERABLE, /**< General-discoverable mode. */
BLE_GAP_DISC_MODE_LIM_DISCOVERABLE, /**< Limited-discoverable mode. */
BLE_GAP_DISC_MODE_BROADCASTER, /**< Broadcaster mode. */
} ble_gap_disc_mode_t;
○ BLE_GAP_DISC_MODE_NON_DISCOVERABLE是指不可发现模式,当设备端处于不可发现模式并发广播时,对端设备解析到它的AD Type,将不在UI界面中显示该设备;
○ BLE_GAP_DISC_MODE_GEN_DISCOVERABLE是指一般可发现模式,跟“有限可发现模式”对比,它可以一直发出广播报文;
○ BLE_GAP_DISC_MODE_LIM_DISCOVERABLE是指有限可发现模式,处于发广播状态的时间一般小于180s;
○ BLE_GAP_DISC_MODE_BROADCASTER指设备只能发广播;
- filter_pol
typedef enum
{
BLE_GAP_ADV_ALLOW_SCAN_ANY_CON_ANY = 0, /**< Allow both scan and connection requests from anyone. */
BLE_GAP_ADV_ALLOW_SCAN_WLST_CON_ANY, /**< Allow scan req from white-list devices only and connection req from anyone. */
BLE_GAP_ADV_ALLOW_SCAN_ANY_CON_WLST, /**< Allow scan req from anyone and connection req from white-list devices only. */
BLE_GAP_ADV_ALLOW_SCAN_WLST_CON_WLST, /**< Allow scan and connection requests from white-list devices only. */
} ble_gap_adv_filter_policy_t;
○ BLE_GAP_ADV_ALLOW_SCAN_ANY_CON_ANY可以收到任何设备的扫描和连接请求;
○ BLE_GAP_ADV_ALLOW_SCAN_WLST_CON_ANY只能收到白名单设备的扫描请求和任意设备的连接请求;
○ BLE_GAP_ADV_ALLOW_SCAN_ANY_CON_WLST可以收到任意设备的扫描请求和白名单设备的连接请求;
○ BLE_GAP_ADV_ALLOW_SCAN_WLST_CON_WLST只能收到白名单设备的扫描请求和连接请求;
-
peer_addr:设置对端设备地址,只在广播为直连广播时或者定位IRK列表时才有效;
当own_addr_type为可解析随机地址时,peer_addr参数主要用来在解析列表中定位对应的本地IRK,以生成发广播时使用的广播地址;
(These parameters are used to locate the corresponding local IRK in the resolving list; this IRK is used to generate the own address used in the advertisement); -
adv_intv_min、adv_intv_max:最小广播间隔和最大广播间隔;
spec中要求controller基于最小广播间隔和最大广播间隔来决定最合适的广播间隔;我们的协议栈默认采用最大广播间隔。 -
chnl_map:可以设置广播信道,广播途中不能跳信道;
-
scan_req_ind_en:设置在接收到扫描请求时是否通知应用层;
-
max_tx_pwr:设置发射功率;
typedef struct
{
uint16_t duration; /**< Advertising duration (in unit of 10ms). 0 means that advertising continues until the host disables it. If Advertising discovery mode is GAP_DISC_MODE_LIM_DISCOVERABLE (see enum @ref ble_gap_disc_mode_t), the setting duration range is [1, 18000].
If adv mode is high duty, duration time range is [1, 128]. */
uint8_t max_adv_evt; /**< Maximum number of extended advertising events. The controller shall attempt to send prior to terminating the extending advertising. The range is [0, 255]. 0 means no maximum number of advertising events. Valid only if the created advertising is an extended advertising. */
} ble_gap_adv_time_param_t;
- duration:广播持续时间;若当前可发现模式为有限可先限模式时,该值的设置范围为10ms~180s;
- max_adv_evt:最大扩展广播事件数,仅在广播为扩展广播时有效;
在蓝牙5.0协议中把广播信道分为两类,一种是主广播信道,工作在37、38、39信道上;另一种叫第二广播信道,工作在0~36信道中。
扩展广播的主广播数据类型为ADV_EXT_IND,当扫描设备接收到该广播包并且能识别其携带的数据时,根据包中携带的信息可以去第二广播信道监听其他辅助包。
设置广播参数的接口如下:
uint16_t ble_gap_adv_param_set(uint8_t adv_idx, ble_gap_own_addr_t own_addr_type, ble_gap_adv_param_t* p_adv_param);
-
adv_idx为广播索引;
-
own_addr_type为设备的BD address:
○ BLE_GAP_OWN_ADDR_STATIC:设置地址类型为静态随机地址,地址随机生成,在一个上电周期内不改变(也可以一直不变); ○ BLE_GAP_OWN_ADDR_GEN_RSLV :设置地址类型为可解析的随机地址,地址由一个随机数和IRK生成,只能被拥有相同IRK的设备扫描到,地址会定时更新; ○ BLE_GAP_OWN_ADDR_GEN_NON_RSLV:设置地址类型为不可解析的随机地址,地址随机生成,且定时更新,该私有地址不可被解析,该类地址的唯一目的就是防止其他任何BLE设备进行跟踪,有时会在Beacon应用上使用;
注:
- BLE设备的地址必须是公共地址或静态地址,不可解析或可解析地址是可选的。即使用了不可解析或可解析地址,设备也必须有公共地址或静态地址。不可解析和可解析地址主要用于解决隐私问题。
- BLE为了保护隐私,支持设备使用完全随机的地址,并让设备随时改变自己的随机地址。不过这样的话,那些可信任的设备也将无法追踪它们或与之建立连接。关键在于如何做到技能使用随机地址保护隐私,又能被可信任设备识别。BLE采用IRK来产生随机地址。地址分为两个部分:一个随机部分和一个使用IRK对该随机部分进行哈希预算获得的值。利用这两部分构造地址字段,这样,已经知道IRK的对端设备就可已检查二者是否匹配。
因此,设备可能有遗传名单记录了每一个已绑定设备的IRK,当它收到私有地址的时候将其与所有IRK进行穷举匹配。一旦匹配上就意味着发现了正确的设备。
开广播的接口如下:
uint16_t ble_gap_adv_start(uint8_t adv_idx, ble_gap_adv_time_param_t* p_timeout);
- adv_idx为广播索引;
设置隐私相关参数的接口如下:
uint16_t ble_gap_privacy_params_set(uint16_t renew_dur, bool enable_flag);
-
renew_dur:启用隐私后重新生成设备地址的周期,建议设为15min;
-
enable_flag:是否启用隐私功能;
3.1.2 广播数据
传统广播包最大长度为31字节,扩展广播为255字节。若想要发送的广播数据过长,可以放一部分数据到扫描响应包中。
广播包需按照固定的广播数据格式组包,格式如下:
length + AD Type + AD Data
其中length为AD Type和AD Data段的总字节长度,AD Type为广播数据类型字段,可以参考ble_gapm.h中的ble_gap_ad_type_t,AD Data为具体数据。
示例如下:
static const uint8_t s_adv_data_set[] = /**< Advertising data. */
{
0x03, // length
BLE_GAP_AD_TYPE_COMPLETE_LIST_16_BIT_UUID, // AD Type,该AD Type表示16位服务UUID完整列表
0x01,
0x01,
};
在我们的SDK中,广播默认占用了3字节,即:
0x02, // length
BLE_GAP_ADTYPE_FLAGS, // Flags
BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED, // 表示设备处于通用可发现模式和不支持BR/EDR
如希望使用31字节的广播数据或者支持传统蓝牙,需要改变ble_gap_adv_param_t中的发现模式disc_mode为不可发现模式。
设置广播数据的接口如下:
uint16_t ble_gap_adv_data_set(uint8_t adv_idx, ble_gap_adv_data_type_t type, const uint8_t* p_data, uint16_t length);
- adv_idx为广播索引;
- type为广播数据类型:
- BLE_GAP_ADV_DATA_TYPE_DATA表示该包为广播数据;
- BLE_GAP_ADV_DATA_TYPE_SCAN_RSP表示该包为扫描响应数据;
- BLE_GAP_ADV_DATA_TYPE_PER_DATA表示该包为周期广播数据。
- p_data为指向广播包的指针,length为广播包长度。
3.1.3 扫描参数
typedef struct
{
ble_gap_scan_type_t scan_type; /**< Active scanning or passive scanning. */
ble_gap_scan_mode_t scan_mode; /**< Scan mode. */
ble_gap_scan_dup_filt_policy_t scan_dup_filt; /**< Duplicate filter policy. */
bool use_whitelist; /**< Filter policy. */
uint16_t interval; /**< Scan interval between 0x0004 and 0x4000 in 0.625 ms (range: 2.5 ms to 10.24s). */
uint16_t window; /**< Scan window between 0x0004 and 0x4000 in 0.625 ms (range: 2.5 ms to 10.24s). */
uint16_t timeout; /**< Scan timeout should be a value between 0x0001 and 0xFFFF(unit: 10 ms). 0x0000 indicates that the timeout has no effect. */
} ble_gap_scan_param_t;
- scan_type:可设置为主动扫描或被动扫描;主动扫描不光可以扫描到对端设备的广播数据包,还能扫描到对端设备的响应包,但被动扫描只能扫到广播包;
- scan_mode设置扫描模式:
- BLE_GAP_SCAN_GEN_DISC_MODE是最通用的扫描模式,可以扫描到广播包,也可以发起连接请求;
- BLE_GAP_SCAN_LIM_DISC_MODE只在一段时间内会开扫描,和广播数据中的BLE_GAP_DISC_MODE_LIM_DISCOVERABLE类似;
- BLE_GAP_SCAN_OBSERVER_MODE只设备只能扫描对端设备的广播,不能发起连接请求;
- scan_dup_filt:设置是否过滤重复包,慎重设置;
- use_whitelist:是否使用白名单过滤策略;
- interval:扫描间隔;
- window:扫描窗口;
- timeout:扫描持续时间;
设置扫描参数的接口如下:
uint16_t ble_gap_scan_param_set(ble_gap_own_addr_t own_addr_type, ble_gap_scan_param_t* p_scan_param);
其中own_addr_type为设备地址类型。
开扫描的接口如下:
uint16_t ble_gap_scan_start(void);
3.1.4 连接参数
typedef struct
{
uint8_t type; /**< Initiating type (see enum @ref ble_gap_init_type_t). */
ble_gap_bdaddr_t peer_addr; /**< Peer device address. */
uint16_t interval_min; /**< Minimum value for the connection interval (in unit of 1.25ms). Shall be less than or equal to interval_max value. Allowed range is 7.5 ms to 4s. */
uint16_t interval_max; /**< Maximum value for the connection interval (in unit of 1.25ms). Shall be greater than or equal to interval_min value. Allowed range is 7.5 ms to 4s. */
uint16_t slave_latency; /**< Slave latency. Number of events that can be missed by a connected slave device. */
uint16_t sup_timeout; /**< Link supervision timeout (in unit of 10ms). Allowed range is 100 ms to 32s. */
uint16_t conn_timeout; /**< Timeout for connection establishment (in unit of 10ms).
Cancel the procedure if connection has not been connected when the timeout occurs.
0 means there is no timeout. */
} ble_gap_init_param_t;
-
type:
- BLE_GAP_INIT_TYPE_DIRECT_CONN_EST:
- BLE_GAP_INIT_TYPE_AUTO_CONN_EST:自动和白名单列表中的设备建连;
- BLE_GAP_INIT_TYPE_NAME_DISC:
-
peer_addr:对端设备地址;
-
interval_min、interval_max:连接间隔;
-
slave_latency:从设备延迟,表示从设备在必须监听之前可以忽略多少个连接事件,从设备延迟必须短于监控超时时间,
slave_latency = (( sup_timeout / (conn_interval * 2)) - 1 ); -
sup_timeout:监控超时时间,如果在这个时间内没有连接事件的话连接自动断开;
-
conn_timeout:处于发起态的超时时间。
为了发起连接,设备需要处于发起态。处于发起态的发起者,其接收机用于侦听其试图连接的设备。若收到了来自该设备的广播报文,LL层会向其发送连接请求并进入连接态。若发起者不再试图发起连接,就退回就绪态。此处的conn_timeout即为发起者侦听待连接设备广播的超时时间。
发起连接的接口如下:
uint16_t ble_gap_connect(ble_gap_own_addr_t own_addr_type, ble_gap_init_param_t* p_init_param);
其中own_addr_type为设备地址类型。
3.1.5 安全参数
未提供安全性的两个设备如果希望做一些需要安全性的操作,首先需要彼此配对。配对涉及到两个设备的身份认证、链路加密和随后的密钥分配,这会使两个设备在重连时的安全启动速度大大加快。
配对流程如下,有三个阶段:
-
在第一阶段,主机发送一个pairing request,该消息包含了IO capabilities,还包括其他配对信息,如OOB authentication data availability, authentication requirements, key size requirements and which transport specific key to distribute。
从机会回复一个Pairing Response message或Pairing Failed message;配对响应信息包含的信息和配对请求基本相同。
这些参数对应了如下结构体:typedef struct { ble_sec_mode_level_t level; /**< Set the minimum security level of the device, see @ref ble_sec_mode_level_t. */ ble_sec_io_cap_t io_cap; /**< Set the IO capability, see @ref ble_sec_io_cap_t. */ bool oob; /**< Indicate whether OOB is supported. */ uint8_t auth; /**< Set the auth, see @ref BLE_SEC_AUTH_FLAG. */ uint8_t key_size; /**< Indicate the supported maximum LTK size (range: 7-16). */ uint8_t ikey_dist; /**< Set the initial key distribution, see @ref BLE_SEC_KEY_DIST_FLAG. */ uint8_t rkey_dist; /**< Set the response key distribution, see @ref BLE_SEC_KEY_DIST_FLAG. */ } ble_sec_param_t;
-
level:
BLE_SEC_MODE1_LEVEL1指对认证没有要求,对加密也没有要求;
BLE_SEC_MODE1_LEVEL2指带加密的未认证配对,此时认证需求可以不包含MITM和SC字段;
BLE_SEC_MODE1_LEVEL3指带加密的认证配对,此时认证需求需要包含MITM字段,SC字段可以不包含;
BLE_SEC_MODE1_LEVEL4 指经过身份验证的LE Secure Connection,使用128bit强度加密密钥进行加密配对,此时认证需求需要包含MITM和SC字段;
BLE_SEC_MODE2_LEVEL1指带数据签名的未认证配对,此时认证需求可以不包含MITM和SC字段;
BLE_SEC_MODE2_LEVEL2指带数据签名的认证配对,此时认证需求需要包含MITM字段,SC字段可以不包含。 -
io_cap表示IO性能:
BLE_SEC_IO_DISPLAY_ONLY 表示设备只支持显示;
BLE_SEC_IO_DISPLAY_YES_NO表示设备可以显示,同时可以输入yes或no;
BLE_SEC_IO_KEYBOARD_ONLY表示设备只支持键盘输入;
BLE_SEC_IO_NO_INPUT_NO_OUTPUT 表示设备既无输入接口也无输出接口;
BLE_SEC_IO_KEYBOARD_DISPLAY表示设备既支持键盘输出也支持显示; -
oob指是否支持out of band;
-
auth表示设备的认证需求:
BLE_SEC_AUTH_NONE表示设备没有认证需求;
BLE_SEC_AUTH_BOND表示设备间需要绑定;
BLE_SEC_AUTH_MITM表示设备间需要防止中间人攻击;
BLE_SEC_AUTH_SEC_CON表示设备需要安全连接;
BLE_SEC_AUTH_KEY_PRESS_NOTIFY仅在Passkey Entry协议中使用,在其他协议中将被忽略;该字段设置为1时,应使用SMP配对按键通知PDU生成并发送按键通知;
BLE_SEC_AUTH_ALL; -
key_size表示设备支持的最大密钥长度;
-
ikey_dist表示发起者的密钥分配,rkey_dist表示响应者的密钥分配;
BLE_SEC_KDIST_NONE表示不需要密钥;
BLE_SEC_KDIST_ENCKEY
BLE_SEC_KDIST_IDKEY
BLE_SEC_KDIST_SIGNKEY
BLE_SEC_KDIST_LINKKEY
BLE_SEC_KDIST_ALL
BLE_BT_SEC_KDIST_ALL
设置安全参数的接口如下:
uint16_t ble_sec_params_set(ble_sec_param_t *p_sec_param);
当设备需要保证传输的安全性时,需要使能配对功能,接口如下:
void ble_gap_pair_enable(bool enable);
在第一阶段交换的IO capabilities、OOB authentication data availability和authentication requirements确定了在下一阶段的密钥生成方式,即决定了是使用LE Secure Connections还是LE legacy Pairing。
-
在第二阶段分为两种:LE Legacy Pairing和LE Secure Connections。第一种生成和使用两种key: TK和STK;第二种生成一种key: LTK。
-
在第三阶段分发特定的密钥,如IRK和身份地址信息。
-
补充介绍 - 配对涉及的安全模型:
a. Just Works
○ 适用于至少有一端设备不具备输入和输出的场景; ○ 在LE legacy pairing模式下,该model不提供任何被动窃听保护,也不能防止中间人攻击;
b. Numeric Comparison
○ 仅支持LE Secure Connections模式,在LE legacy pairing模式中不包含该model; ○ 适用于两端设备都能够显示6位数字并且都能让用户输入“YES”或“NO”的场景; ○ 两端设备都显示一个6位数字,由用户比较是否相同,若相同则输入“YES”,当两端都输入“YES”,则配对成功; ○ 可以防中间人攻击;
按照spec中说明,LE Secure Connection是通过提供MITM约百万分之一的成功机会来保护用户免受MITM攻击。选择次级别的MITM保护是因为在大多数情况下,当连接过程因MITM攻击失败而失败时,用户会收到存在潜在MITM攻击者的警报。同时使用6位数字符合FIPS标准,且对用户体验影响不大。
c. Out of Band
○ 适用于使用带外机制发现设备以及交换或传输配对过程中使用的加密数字的场景;
d. Passkey Entry
○ 适用于一个设备具有输入能力,但不具备输出能力,同时另一个设备具有输出能力; ○ 在LE legacy pairing模式下,该model不提供任何防窃听保护,但具备防MITM攻击的能力;
• 只要有一端设备是无输入无输出类型,则使用Just Works模型;
• 若两端设备中一段设备只能显示,另一端设备支持显示和输入YES\NO,则也是采用Just Works模型;
3.1.6 其他
设备名的设置方法可参考如下代码:
if (!strcmp((const char *)dev_name, BLE_GAP_DEVNAME_DEFAULT))
{
// Set the default Device Name.
error_code = ble_gap_device_name_set(BLE_GAP_WRITE_PERM_NOAUTH, DEVICE_NAME, strlen(DEVICE_NAME));
APP_ERROR_CHECK(error_code);
}
else
{
// Set the Device Name is writable from the peer.
error_code = ble_gap_device_name_set(BLE_GAP_WRITE_PERM_NOAUTH, NULL, 0);
APP_ERROR_CHECK(error_code);
}
设置L2CAP层相关参数的接口如下:
uint16_t ble_gap_l2cap_params_set(uint16_t max_mtu,uint16_t max_mps,uint8_t max_nb_lecb);
○ max_mtu: ATT层和L2CAP层可以交互的最大数据长度(SDU包的最大数据长度);
○ max_mps: 设置L2CAP PDU length的最大值;
○ max_nb_lecb: 基于LE Credit可建立的最大连接数,该参数主要用于流控;
L2CAP PDU/LL DATA PDU的数据格式如下图黄色部分所示:
uint16_t ble_gap_data_length_set(uint16_t sugg_max_tx_octet,uint16_t sugg_max_tx_time);
○ sugg_max_tx_octet: host建议Controller设置的最大传输长度;
○ sugg_max_tx_time: host建议controller设置的包含LL DATA PDU的数据包的最大传输时间;
参数范围由spec给出,sugg_max_tx_octet和sugg_max_tx_time呈对应关系,如当sugg_max_tx_octet为最大值时,sugg_max_tx_time也应为最大值;
3.2 服务初始化
每个服务在使用前都需要进行初始化,在初始化时需要注册一个事件处理函数,这个事件处理函数主要是为了让用户处理service相关事件,如发现服务完成、使能CCCD、接收到数据或者数据发送成功事件等。以ANCS服务举例:
static void ancs_c_evt_process(ancs_c_evt_t *p_evt)
{
sdk_err_t error_code;
switch(p_evt->evt_type)
{
case BLE_ANCS_C_EVT_DISCOVERY_CPLT:
error_code = ancs_c_ntf_source_notify_set(p_evt->conn_idx, true);
APP_ERROR_CHECK(error_code);
break;
case BLE_ANCS_C_EVT_NTF_SOURCE_NTF_ENABLED:
APP_LOG_INFO("ANCS notification source notification enabled.");
error_code = ancs_c_data_source_notify_set(p_evt->conn_idx, true);
APP_ERROR_CHECK(error_code);
break;
case BLE_ANCS_C_EVT_DATA_SOURCE_NTF_ENABLED:
APP_LOG_INFO("ANCS data source notification enabled.");
break;
case BLE_ANCS_C_EVT_NTF_SOURCE_RECEIVE:
break;
case BLE_ANCS_C_EVT_DATA_SOURCE_RECEIVE:
break;
case BLE_ANCS_C_EVT_WRITE_OP_ERR:
APP_LOG_INFO("Write error.");
break;
default:
break;
}
}
3.3 实现ble_evt_handler
协议栈初始化时需要传入一个处理各类BLE事件的事件处理函数,BLE相关事件都会在该函数中进行分发处理,具体示例如下:
void ble_evt_handler(const ble_evt_t *p_evt)
{
switch(p_evt->evt_id)
{
case BLE_COMMON_EVT_STACK_INIT:
ble_app_init(); // 在该处调用服务初始化、gap参数初始化等接口;
break;
case BLE_GAPM_EVT_ADV_START:
app_adv_start_handler(p_evt->evt.gapm_evt.index, p_evt->evt_status);
break;
case BLE_GAPM_EVT_ADV_STOP:
if (BLE_SUCCESS == p_evt->evt_status)
{
app_adv_stop_handler(p_evt->evt.gapm_evt.index, p_evt->evt.gapm_evt.params.adv_stop.reason);
}
break;
case BLE_GAPM_EVT_SCAN_START:
app_scan_start_handler(p_evt->evt_status);
break;
case BLE_GAPM_EVT_SCAN_STOP:
app_scan_stop_handler(p_evt->evt_status, p_evt->evt.gapm_evt.params.scan_stop.reason);
break;
case BLE_GAPM_EVT_ADV_REPORT:
app_adv_report_handler(p_evt->evt.gapm_evt.params.adv_report.data,
p_evt->evt.gapm_evt.params.adv_report.length,
&p_evt->evt.gapm_evt.params.adv_report.broadcaster_addr);
break;
case BLE_GAPC_EVT_CONNECTED:
if (BLE_SUCCESS == p_evt->evt_status)
{
APP_LOG_DEBUG("Connected.");
app_connected_handler(p_evt->evt.gapc_evt.index, p_evt->evt_status, &p_evt->evt.gapc_evt.params.connected);
}
break;
case BLE_GAPC_EVT_DISCONNECTED:
if (BLE_SUCCESS == p_evt->evt_status)
{
APP_LOG_DEBUG("Disconnected (0x%02X).", p_evt->evt.gapc_evt.params.disconnected.reason);
app_disconnected_handler(p_evt->evt.gapc_evt.index, p_evt->evt_status, p_evt-> evt.gapc_evt.params.disconnected.reason);
}
break;
case BLE_GAPC_EVT_CONN_PARAM_UPDATE_REQ:
ble_gap_conn_param_update_reply(p_evt->evt.gapc_evt.index, true); // 该事件必须处理
break;
}
}
需要注意的是,BLE_COMMON_EVT_STACK_INIT和BLE_GAPC_EVT_CONN_PARAM_UPDATE_REQ事件必须处理。在收到BLE_COMMON_EVT_STACK_INIT事件后需要调用初始化相关函数,收到BLE_GAPC_EVT_CONN_PARAM_UPDATE_REQ事件时必须回复一个是否接受连接参数更新请求的回应。
当设备作为central端时,需要扫描其他设备的广播并发起连接,这个流程就在BLE事件处理函数中完成。
○ 调用ble_gap_scan_start()接口开扫描,协议栈会上报BLE_GAPM_EVT_ADV_REPORT事件;
○ 处理BLE_GAPM_EVT_ADV_REPORT事件,在该事件中设备会上报扫描到的广播信息,用户可以基于地址、服务UUID或设备名获取想要连接的设备地址,并设置连接参数中的对端设备地址为该地址,同时停止扫描;
○ 处理扫描停止事件BLE_GAPM_EVT_SCAN_STOP,在该事件处理函数中调用发起连接请求的接口ble_gap_connect();
○ 随后监控BLE_GAPC_EVT_CONNECTED事件,查看是否建连成功。
4. 协议栈初始化
在main.c中,调用ble_stack_init()执行协议栈的初始化,该函数声明如下:
uint16_t ble_stack_init(ble_evt_handler_t evt_handler, stack_heaps_table_t *p_heaps_table);
其中evt_handler是ble事件处理函数ble_evt_handler,该处理函数一般由用户来维护;
p_heaps_table是协议栈使用的堆内存,可以使用宏STACK_HEAP_INIT来初始化该内存,这个表包含了指向四个数组的指针:
stack_heaps_table_t heaps_table = { (uint32_t *)env_heap_buf,\
(uint32_t *)att_db_heap_buf,\
(uint32_t *)ke_msg_heap_buf,\
(uint32_t *)non_ret_heap_buf,\
ENV_HEAP_SIZE,\
ATT_DB_HEAP_SIZE,\
KE_MSG_HEAP_SIZE,\
NON_RET_HEAP_SIZE,\
(uint8_t *)prf_buf,\
PRF_BUF_SIZE,\
(uint8_t *)bond_buf,\
BOND_BUF_SIZE,\
(uint8_t *)conn_buf,\
CONN_BUF_SIZE}
这四个数组的大小取决于USER_MAX_CONNECTIONS、USER_MAX_EATT_CHANNELS、USER_MAX_ADVS和USER_MAX_SCAN等由使用场景决定的参数。