打开主菜单

谷雨文档中心 β

NRF52832DK协议栈实验

Jinx讨论 | 贡献2019年7月25日 (四) 11:56的版本 实验简介

目录

1 蓝牙协议简介

2 蓝牙协议实验

蓝牙协议实验部分,我们借由串口透传实验,一步一步拆分,给大家介绍蓝牙的协议方面。

2.1 低功耗实验

2.1.1 实验简介

低功耗实验1.0_ble_central_pm与2.0_ble_peripheral_pm,这两个实验给大家带来的是最精简的主机以及从机例程,精简到什么程度呢,只保留了协议栈初始化以及电源管理部分。利用此实验,大家可以测试一下我们的BLE工程进入低功耗模式下的功耗情况。

2.1.2 实验现象

我们将万用表串联到电路中,并且打到电流档,此时我们可以看到功耗如下。

2.1.3 工程及源码讲解

2.1.3.1 主机部分
2.1.3.1.1 main()函数

首先我们查看一下main.c文件,在此文件的mian()函数中,我们首先初始化了电源管理模块,然后初始化了BLE栈堆,最后在while大循环中我们调用空闲状态处理的函数。

接下来我们分别针对这三个部分进行介绍。

170 //******************************************************************
171 // fn : main
172 //
173 // brief : 主函数
174 //
175 // param : none
176 //
177 // return : none
178 int main(void)
179 {
180     // 初始化
181     power_management_init();// 初始化电源控制
182     ble_stack_init();       // 初始化BLE栈堆
183     
184     // 进入主循环
185     for (;;)
186     {
187         idle_state_handle();   // 空闲状态处理
188     }
189 }
2.1.3.1.2 ble_stack_init()函数及ble_evt_handler()回调函数

我们查看一下BLE协议栈初始化,这个部分是一个格式化的东西。

首先调用nrf_sdh_enable_request()函数请求使能softdevice,原因在于ble协议、时钟、错误的回调以及中断的配置等,都需要这个sd(softdevice)支持。

接下来我们要配置默认的ble协议,主要包含了RAM起始地址、本工程的角色(根据设备支持连接的角色来判断)、MTU的大小及UUID和属性表大小。

RAM的起始地址在下面的位置可以看到,我们打开\ble_ghostyu\1.0_ble_central_pm\LaunchIOT\s132\iar下的ble_app_ghostyu_iar_nRF5x.icf文件(将此文件拖到IAR中就可以打开,可以看到__ICFEDIT_region_RAM_start__ = 0x200029e0)。

然后我们携带RAM起始地址,使能BLE协议栈。

在最后,我们注册了一个ble_evt_handler回调,在这个回调中我们处理BLE的事件返回。

102 //******************************************************************
103 // fn : ble_stack_init
104 //
105 // brief : 用于初始化BLE协议栈
106 // details : 初始化SoftDevice、BLE事件中断
107 //
108 // param : none
109 //
110 // return : none
111 static void ble_stack_init(void)
112 {
113     ret_code_t err_code;
114 
115     // SD使能请求,配置时钟,配置错误回调,中断(中断优先级栈堆默认设置)
116     err_code = nrf_sdh_enable_request();
117     APP_ERROR_CHECK(err_code);
118 
119     // SD默认配置(如下),SD RAM起始地址配置(0x200029e0)
120     // 作为从机时的最大连接数量0
121     // 作为主机时的最大连接数据1(本工程是主机)
122     // 初始化MTU大小23
123     // 供应商特定的UUID数量1
124     // 属性表大小248(必须是4的倍数,以字节为单位)
125     // 配置服务更改特性数量0
126     uint32_t ram_start = 0;
127     err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
128     APP_ERROR_CHECK(err_code);
129 
130     // 使能BLE栈堆
131     err_code = nrf_sdh_ble_enable(&ram_start);
132     APP_ERROR_CHECK(err_code);
133 
134     // 注册BLE事件的处理程序,所有BLE的事件都将分派ble_evt_handler回调
135     NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
136 }

ble_evt_handler回调函数,用于处理BLE的事件回调,包含了COMMON、GAP、GATT Client、GATT Server、L2CAP等多种事件类型。在这个例程中,我们只给大家保留了最基础的两个GAP状态,分别为连接状态和断开连接状态。

 68 //******************************************************************
 69 // fn : ble_evt_handler
 70 //
 71 // brief : BLE事件回调
 72 // details : 包含以下几种事件类型:COMMON、GAP、GATT Client、GATT Server、L2CAP
 73 //
 74 // param : ble_evt_t  事件类型
 75 //         p_context  未使用
 76 //
 77 // return : none
 78 static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
 79 {
 80     ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt;
 81 
 82     switch (p_ble_evt->header.evt_id)
 83     {
 84         // 连接
 85         case BLE_GAP_EVT_CONNECTED:
 86             NRF_LOG_INFO("Connected. conn_handle: 0x%x",p_gap_evt->conn_handle);
 87             break;
 88 
 89         // 断开连接
 90         case BLE_GAP_EVT_DISCONNECTED:
 91 
 92             NRF_LOG_INFO("Disconnected. conn_handle: 0x%x, reason: 0x%x",
 93                          p_gap_evt->conn_handle,
 94                          p_gap_evt->params.disconnected.reason);
 95             break;
 96 
 97         default:
 98             break;
 99     }
100 }
2.1.3.1.3 power_management_init()及idle_state_handle()函数

power_management_init()函数调用底层的nrf_pwr_mgmt_init()函数去初始化电源管理的部分。

idle_state_handle()函数调用底层的nrf_pwr_mgmt_run()函数,用于处理空闲状态的功能(处理完所有的挂起事件,然后进入休眠,直到下一个事件发生)。

138 //******************************************************************
139 // fn : power_management_init
140 //
141 // brief : 初始化电源管理
142 //
143 // param : none
144 //
145 // return : none
146 static void power_management_init(void)
147 {
148     ret_code_t err_code;
149     err_code = nrf_pwr_mgmt_init();
150     APP_ERROR_CHECK(err_code);
151 }
152 
153 //******************************************************************
154 // fn : idle_state_handle
155 //
156 // brief : 处理空闲状态的功能(用于主循环)
157 // details : 处理任何挂起的日志操作,然后休眠直到下一个事件发生
158 //
159 // param : none
160 //
161 // return : none
162 static void idle_state_handle(void)
163 {
164     if (NRF_LOG_PROCESS() == false)
165     {
166         nrf_pwr_mgmt_run();
167     }
168 }
2.1.3.2 从机部分
2.1.3.2.1 ble_stack_init()函数

从机部分大体上是和主机一样的,在nordic的协议栈例程中,(如果大家对BLE协议有一定的了解或者使用过其他厂家的BLE芯片)我们可以发现,nordic为了简化BLE的开发难度,可谓是不择手段,他减掉了很多的ble协议相关的内容(这里的减掉指的是放到底层处理,不需要开发者去配置),这其中就包含了GAP Role,也就是蓝牙的角色。

ble_satck_init()函数在主机与从机中,唯一不同的点在初始化参数配置。

 99 //******************************************************************
100 // fn : ble_stack_init
101 //
102 // brief : 用于初始化BLE协议栈
103 // details : 初始化SoftDevice、BLE事件中断
104 //
105 // param : none
106 //
107 // return : none
108 static void ble_stack_init(void)
109 {
110     ret_code_t err_code;
111 
112     // SD使能请求,配置时钟,配置错误回调,中断(中断优先级栈堆默认设置)
113     err_code = nrf_sdh_enable_request();
114     APP_ERROR_CHECK(err_code);
115 
116     // SD默认配置(如下),SD RAM起始地址配置(0x20002a98)
117     // 作为从机时的最大连接数量1(本工程为从机)
118     // 作为主机时的最大连接数据0
119     // 初始化MTU大小23
120     // 供应商特定的UUID数量1
121     // 属性表大小248(必须是4的倍数,以字节为单位)
122     // 配置服务更改特性数量0
123     uint32_t ram_start = 0;
124     err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
125     APP_ERROR_CHECK(err_code);
126 
127     // 使能BLE栈堆
128     err_code = nrf_sdh_ble_enable(&ram_start);
129     APP_ERROR_CHECK(err_code);
130 
131     // 注册BLE事件的处理程序,所有BLE的事件都将分派ble_evt_handler回调
132     NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
133 }

主机初始化时设置的是作为主机时最大连接数量1,从机初始化时设置的是作为从机时最大连接数量1。这个配置的宏定义是在sdk_config.h文件中。我们go to define,进入nrf_sdh_ble_default_cfg_set()默认参数配置函数中查看,可以找到如下部分。

NRF_SDH_BLE_PERIPHERAL_LINK_COUNT与NRF_SDH_BLE_CENTRAL_LINK_COUNT,在定义最大连接设备数量的同时,也决定了它本身的角色属性。

BLE中我们称Central中心设备为主机(发起连接的设备)、Peripheral外部设备为从机(广播等待被连接的设备)。

103 ret_code_t nrf_sdh_ble_default_cfg_set(uint8_t conn_cfg_tag, uint32_t * p_ram_start)
104 {
105     uint32_t ret_code;
106 
107     ...
108 
109     // Configure the connection roles.
110     memset(&ble_cfg, 0, sizeof(ble_cfg));
111     ble_cfg.gap_cfg.role_count_cfg.periph_role_count  = NRF_SDH_BLE_PERIPHERAL_LINK_COUNT;
112 #ifndef S112
113     ble_cfg.gap_cfg.role_count_cfg.central_role_count = NRF_SDH_BLE_CENTRAL_LINK_COUNT;
114     ble_cfg.gap_cfg.role_count_cfg.central_sec_count  = MIN(NRF_SDH_BLE_CENTRAL_LINK_COUNT,
115                                                             BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT);
116 #endif
117 
118    ...
119 
120     return NRF_SUCCESS;
121 }

2.1.4 实验总结

在低功耗主从机的学习中,我们可以了解到最精简的主从机工程。也就是只包含了softdevice与ble协议栈初始化、以及电源管理初始化。

重点问题:

1.了解如何定义一个BLE工程是主机还是从机,或者其他的属性(主从一体等等)。

2.了解如何进行低功耗处理(main()函数中while循环的idle_state_handle空闲任务处理函数)。

2.2 LOG打印实验

2.2.1 实验简介

LOG打印实验1.1_ble_central_log与2.1_ble_peripheral_log,LOG打印实验是在低功耗实验的基础上,新增了LOG打印部分。

那么为什么我们需要添加log功能,主要是因为在开发的过程中,我们几乎很难一次调通我们需求的功能,这个时候我们就需要一种好的方式去帮助我们调通,我们出现问题点在哪(查找bug),以及我们的流程进行到哪边了(流程监视)。说到这里,大家会说为什么不使用在线调试的方式解决问题呢,下面谈一谈我个人的使用感觉(仅针对BLE)。

何时使用在线调试:

1.针对细小的问题点,例如某个参数的数值是否正确

2.针对不影响程序整机蓝牙功能运行的问题点,例如外设功能异常

何时使用log:

1.不能使用在线调试功能的时候(例如蓝牙连接状态下的通信调试,由于连接参数的限制,如果在线打断点调试,会导致异常断开)

2.监测关键节点信息(有些问题会出现在一些特殊情况下,可能需要监测N多次,才会出现一次异常)

注意:我们的LOG工程,仅针对RTT部分,没有介绍UART,UART的使用大家可以查看基础实验部分

2.2.2 实验现象

我们打开J-Link RTT Viewer,选择我们的Jlink仿真器,可以看到log打印如下。

2.2.3 工程及源码讲解

2.2.3.1 主机部分
2.2.3.1.1 工程说明

我们在工程中,新增加了nRF_Log与nRF_Segger_RTT分组,这两个分组中分别包含了log打印的相关函数,以及RTT的相关函数。

使用RTT,可以从目标微控制器输出信息,并以非常高的速度向应用程序发送输入,而不会影响目标的实时行为。

SEGGER RTT可与任何J-Link模型和任何支持目标处理器一起使用,后者允许后台存储器访问,即Cortex-M和RX目标。

RTT在两个方向上支持多个通道,直到主机和目标,可以用于不同的目的,并为用户提供最大的自由。

默认实现每个方向使用一个通道,用于可打印的终端输入和输出。使用J-Link RTT Viewer,该通道可用于多个“虚拟”终端,允许使用一个目标缓冲区打印到多个窗口(例如一个用于标准输出,一个用于错误输出,一个用于调试输出)。例如,可以使用附加的up(到主机)通道来发送分析或事件跟踪数据。

2.2.3.1.2 main()函数

log打印实验相对于前一章节的低功耗实验,新增的功能并不多,我们仅仅添加了LOG的RTT打印功能,这边我们首先还是看下mian函数。

可以看到,我们新增了log_init()的LOG初始化函数。

188 //******************************************************************
189 // fn : main
190 //
191 // brief : 主函数
192 //
193 // param : none
194 //
195 // return : none
196 int main(void)
197 {
198     // 初始化
199     log_init();             // 初始化LOG打印,由RTT工作
200     power_management_init();// 初始化电源控制
201     ble_stack_init();       // 初始化BLE栈堆
202 
203     // 打印例程名称
204     NRF_LOG_INFO("1.1_ble_central_log");
205     
206     // 进入主循环
207     for (;;)
208     {
209         idle_state_handle();   // 空闲状态处理
210     }
211 }
2.2.3.1.3 log_init()函数以及底层调用

我们查看到log_init()函数,先调用NRF_LOG_INIT()函数初始化LOG,然后我们调用NRF_LOG_DEFAULT_BACKENDS_INIT()函数来决定LOG的底层调用。

140 //******************************************************************
141 // fn : log_init
142 //
143 // brief : 初始化log打印
144 //
145 // param : none
146 //
147 // return : none
148 static void log_init(void)
149 {
150     ret_code_t err_code = NRF_LOG_INIT(NULL);
151     APP_ERROR_CHECK(err_code);
152 
153     NRF_LOG_DEFAULT_BACKENDS_INIT();
154 }

接下来我们追踪NRF_LOG_DEFAULT_BACKENDS_INIT()函数,在这个函数中,我们会引用到RTT功能。 首先我们go to define,找到NRF_LOG_DEFAULT_BACKENDS_INIT()函数的定义,sdk_config.h中我们定义了NRF_LOG_ENABLED为1,也就是使能LOG打印。

61 /**
62  * @def NRF_LOG_DEFAULT_BACKENDS_INIT
63  * @brief Macro for initializing default backends.
64  *
65  * Each backend enabled in configuration is initialized and added as a backend to the logger.
66  */
67 #if NRF_LOG_ENABLED
68 #define NRF_LOG_DEFAULT_BACKENDS_INIT() nrf_log_default_backends_init()
69 #else
70 #define NRF_LOG_DEFAULT_BACKENDS_INIT()
71 #endif

接下来我们追踪nrf_log_default_backends_init()函数,在sdk_config.h中,定义了NRF_LOG_BACKEND_RTT_ENABLED为1,也就是使能了RTT打印。

在这个地方,我们会调用nrf_log_backend_rtt_init()函数初始化RTT,然后调用nrf_log_backend_add()添加新的后端接口,并调用nrf_log_backend_enable()将这个新的后端接口使能。

这样处理之后,我们便可以使用面向RTT的log功能了。

RTT部分的源码,这个大家有兴趣的可以自行了解,我们使用RTT功能的时候,只需要将RTT分组下的SEGGER_RTT_XX.c的三个文件添加到工程即可,不一定要了解这个源码
58 void nrf_log_default_backends_init(void)
59 {
60     int32_t backend_id = -1;
61     (void)backend_id;
62 #if defined(NRF_LOG_BACKEND_RTT_ENABLED) && NRF_LOG_BACKEND_RTT_ENABLED
63     nrf_log_backend_rtt_init();
64     backend_id = nrf_log_backend_add(&rtt_log_backend, NRF_LOG_SEVERITY_DEBUG);
65     ASSERT(backend_id >= 0);
66     nrf_log_backend_enable(&rtt_log_backend);
67 #endif
68 
69 #if defined(NRF_LOG_BACKEND_UART_ENABLED) && NRF_LOG_BACKEND_UART_ENABLED
70     nrf_log_backend_uart_init();
71     backend_id = nrf_log_backend_add(&uart_log_backend, NRF_LOG_SEVERITY_DEBUG);
72     ASSERT(backend_id >= 0);
73     nrf_log_backend_enable(&uart_log_backend);
74 #endif
75 }
2.2.3.1.4 NRF_LOG_XX()函数说明

上面说明了如何去初始化LOG向RTT打印的功能,下面我们将给大家介绍一下LOG打印的函数,毕竟这个才是我们真正要用到的部分。

LOG打印的函数一共有4个,分别为打印ERROR、WARNING、INFO、DEBUG。他们的功能看字面意思就可以明白,分别是打印错误、警告、用户信息、调试信息。

111 #define NRF_LOG_ERROR(...)                     NRF_LOG_INTERNAL_ERROR(__VA_ARGS__)
112 #define NRF_LOG_WARNING(...)                   NRF_LOG_INTERNAL_WARNING( __VA_ARGS__)
113 #define NRF_LOG_INFO(...)                      NRF_LOG_INTERNAL_INFO( __VA_ARGS__)
114 #define NRF_LOG_DEBUG(...)                     NRF_LOG_INTERNAL_DEBUG( __VA_ARGS__)

底层的打印函数大家自己go to define查看,我这边给大家各举一个例子,方便大家使用。 其实他们的使用方式,都是和printf一样的,只是打印消息的级别不同而已。printf函数的使用大家不清楚的,可以百度查看一下。

1 NRF_LOG_ERROR("sd_ble_cfg_set() returned %s when attempting to set BLE_CONN_CFG_GAP.",
2                       nrf_strerror_get(ret_code));
3                       
4 NRF_LOG_WARNING("Change the RAM start location from 0x%x to 0x%x.",
5                       app_ram_start_link, *p_app_ram_start);
6                       
7 NRF_LOG_INFO("Shutdown started. Type %d", m_pwr_mgmt_evt);
8 
9 NRF_LOG_DEBUG("BLE event: 0x%x.", p_ble_evt->header.evt_id);

上面一段话和大家说了nordic协议栈中4个标准的LOG打印函数,他们其实拥有相同的功能,但是打印的级别不同(这个级别是认为规定的),我们我们如何控制这个打印的级别呢,也是在我们的sdk_config.h中,大家可以看到NRF_LOG_DEFAULT_LEVEL。默认的只能打印到info,如果大家需要使用debug打印,需要修改此处的值为4。

7569 // <o> NRF_LOG_DEFAULT_LEVEL  - Default Severity level
7570  
7571 // <0=> Off 
7572 // <1=> Error 
7573 // <2=> Warning 
7574 // <3=> Info 
7575 // <4=> Debug 
7576 
7577 #ifndef NRF_LOG_DEFAULT_LEVEL
7578 #define NRF_LOG_DEFAULT_LEVEL 3
7579 #endif
2.2.3.2 从机部分

从机部分与主机部分新增log功能相同,这边不再赘述。

2.2.4 实验总结

LOG打印实验,大家需要掌握的三个要点如下:

1.了解什么是RTT。

2.了解nordic协议栈中LOG面向RTT功能的初始化、及打印函数的使用。

3.要善于使用log功能,这样有利于我们程序的流程开发及异常问题的查找和处理。

2.3 通用扫描实验

2.3.1 实验简介

通用扫描实验1.2_ble_central_scan_all与2.2_ble_peripheral_adv_all,给大家带来的是主机的扫描功能展示,以及从机的广播功能展示。

也就是从这一实验开始,我们才是真正进入到BLE协议的学习实验,我们将按照扫描、连接、获取服务、通信的流程,在接下来的实验中给大家介绍BLE协议。

大家都清楚,低功耗蓝牙主从机间交互数据的方式一般来说是有两种,一种是连接之后通信(这个是蓝牙的主要功能),另一种就是本实验带来的主机扫描获取从机的广播数据。而上述的两种方式,不管是哪一种,都是需要扫描功能的,毕竟连接通信之前,我们的主机也是需要扫描到从机设备才可以发起连接。

2.3.2 实验现象

主机log打印如下,先打印当前例程的名称1.2_ble_central_scan_all,然后将会打印扫描到的从机设备的信息,格式如下:

Device MAC 0x010203040506         // 从机设备MAC地址

adv data: 0x0102...xx...xx          // 从机设备广播数据

scan data: 0x0102...xx...xx         // 从机设备扫描回调数据

rssi: -xxdBm                        // 从机设备相当于当前主机的信号强度

主机log打印如下,先打印当前例程的名称2.2_ble_peripheral_adv_all。

2.3.3 工程及源码讲解

2.3.3.1 工程说明

相对于LOG打印的工程,我们新增了nRF_BLE分组,这个分组下包含的就是BLE协议相关的文件,我们在本实验中只使用了nrf_ble_scan.c下的扫描功能函数。

2.3.3.2 主机部分
2.3.3.2.1 main()函数

在mian函数中,我们新增了扫描功能的scan_init()初始化函数,并且在初始化所有功能之后,我们调用了发起扫描的函数scan_start()。

296 //******************************************************************
297 // fn : main
298 //
299 // brief : 主函数
300 //
301 // param : none
302 //
303 // return : none
304 int main(void)
305 {
306     // 初始化
307     log_init();             // 初始化LOG打印,由RTT工作
308     power_management_init();// 初始化电源控制
309     ble_stack_init();       // 初始化BLE栈堆
310     scan_init();            // 初始化扫描
311     
312     // 打印例程名称
313     NRF_LOG_INFO("1.2_ble_central_scan_all");
314     
315     scan_start();           // 开始扫描
316     
317     // 进入主循环
318     for (;;)
319     {
320         idle_state_handle();   // 空闲状态处理
321     }
322 }
2.3.3.2.2 scan_init()函数及scan_evt_handler()回调函数

首先我们看一下我们的扫描功能的初始化函数,可以看到我们最终调用的是库函数中的nrf_ble_scan_init()去初始化我们扫描,在这个函数中,有两个参数需要我们关注,一个是init_scan(这个参数携带的我们对于扫描的设置),另一个是scan_evt_handler(当我们扫描到设备之后,将会由这个回调函数返回事件信息给我们)。

155 //******************************************************************
156 // fn : scan_init
157 //
158 // brief : 初始化扫描(未设置扫描数据限制)
159 //
160 // param : none
161 //
162 // return : none
163 static void scan_init(void)
164 {
165     ret_code_t          err_code;
166     nrf_ble_scan_init_t init_scan;
167 
168     // 清空扫描结构体参数
169     memset(&init_scan, 0, sizeof(init_scan));
170     
171     // 配置扫描的参数
172     init_scan.p_scan_param = &m_scan_params;
173     
174     // 初始化扫描
175     err_code = nrf_ble_scan_init(&m_scan, &init_scan, scan_evt_handler);
176     APP_ERROR_CHECK(err_code);
177 }

首先我们看一下扫描的参数设置,这边我们设置为主动扫描,扫描间隔是100ms,扫描窗口是50ms,扫描的持续时间设置为0(设置为0,则一直扫描,除非我们调用停止扫描的函数),另外我们设置扫描附近的所有BLE设备(不做扫描限制)。

扫描模式分为两种:

1.被动扫描:只扫描从机设备的广播数据

2.主动扫描:扫描从机设备的广播数据以及扫描回调数据

55 // 定义扫描参数
56 static ble_gap_scan_params_t m_scan_params = 
57 {
58     .active        = 1,                            // 1为主动扫描,可获得广播数据及扫描回调数据
59     .interval      = NRF_BLE_SCAN_SCAN_INTERVAL,   // 扫描间隔:160*0.625 = 100ms  
60     .window        = NRF_BLE_SCAN_SCAN_WINDOW,     // 扫描窗口:80*0.625 = 50ms   
61     .timeout       = NRF_BLE_SCAN_SCAN_DURATION,   // 扫描持续的时间:设置为0,一直扫描,直到明确的停止扫描
62     .filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL,   // 扫描所有BLE设备,不做限制
63     .scan_phys     = BLE_GAP_PHY_1MBPS,            // 扫描1MBPS的PHY
64 };

接下来我们查看一下我们处理扫描回调事件的函数,因为我们这个实验带给大家的是扫描附近所有的BLE广播设备,所以可以看到,我们判断的设备ID为NRF_BLE_SCAN_EVT_NOT_FOUND,这个ID代表的未被过滤的扫描数据。 在这个事件ID下,我们根据p_scan_evt->params.p_not_found->type.scan_response判断扫描到的数据是广播数据还是扫描回调数据,并且我们可以从这个消息结构体当中获取到上述的两包数据、以及设备的MAC、设备的RSSI信号强度等等。并且我们将扫描到的这些信息通过LOG向RTT格式化打印出来。

 99 //******************************************************************
100 // fn : scan_evt_handler
101 //
102 // brief : 处理扫描回调事件
103 //
104 // param : scan_evt_t  扫描事件结构体
105 //
106 // return : none
107 static void scan_evt_handler(scan_evt_t const * p_scan_evt)
108 {
109     switch(p_scan_evt->scan_evt_id)
110     {
111         // 未过滤的扫描数据
112         case NRF_BLE_SCAN_EVT_NOT_FOUND:
113         {
114           // 判断是否为扫描回调数据
115           if(p_scan_evt->params.p_not_found->type.scan_response)
116           {
117             if(p_scan_evt->params.p_not_found->data.len)    // 存在扫描回调数据
118             {
119               NRF_LOG_INFO("scan data:  %s",
120                             Util_convertHex2Str(
121                             p_scan_evt->params.p_not_found->data.p_data,
122                             p_scan_evt->params.p_not_found->data.len));
123             }
124             else
125             {
126               NRF_LOG_INFO("scan data:  %s","NONE");
127             }
128             NRF_LOG_INFO("rssi:  %ddBm",p_scan_evt->params.p_not_found->rssi);
129           }
130           else  // 否则为广播数据
131           {
132             // 打印扫描的设备MAC
133             NRF_LOG_INFO("Device MAC:  %s",
134                          Util_convertBdAddr2Str((uint8_t*)p_scan_evt->params.p_not_found->peer_addr.addr));
135             
136             if(p_scan_evt->params.p_not_found->data.len)    // 存在广播数据
137             {
138               NRF_LOG_INFO("adv data:  %s",
139                             Util_convertHex2Str(
140                             p_scan_evt->params.p_not_found->data.p_data,
141                             p_scan_evt->params.p_not_found->data.len));
142             }
143             else
144             {
145               NRF_LOG_INFO("adv data:  %s","NONE");
146             }
147           }
148         } break;
149 
150         default:
151            break;
152     }
153 }
2.3.3.2.3 scan_start()函数

scan_start()发起扫描的函数,调用的是nordic提供的底层的nrf_ble_scan_start()函数实现的。

83 //******************************************************************
84 // fn : scan_start
85 //
86 // brief : 开始扫描
87 //
88 // param : none
89 //
90 // return : none
91 static void scan_start(void)
92 {
93     ret_code_t ret;
94 
95     ret = nrf_ble_scan_start(&m_scan);
96     APP_ERROR_CHECK(ret);
97 }
2.3.3.3 从机部分
2.3.3.3.1 工程说明

相对于LOG实验,从机工程也是新增了nRF_BLE的分组,但是我们可以看到看到分组下的文件与主机工程是不同的。

主机部分主要是扫描和获取服务相关,从机部分则是广播、连接参数和服务注册相关。

2.3.3.3.2 main()函数

在mian()函数,我们新增了广播初始化的advertising_init()函数、以及发起广播的advertising_start()函数。并且我们还初始化了GAP,调用的是gap_params_init()函数。

本来我们这一实验,只是想给大家带来广播和广播数据配置的展示的,但是为了能够让大家更直观的“看到”设备,所以我们便需要在广播数据中携带设备名称,那么问题来了,在nordic的协议栈中,除非我们去修改他提供的ble_advdata.c文件,不然我们没法直接去配置这个广播数据中的设备名称(这边给大家一个建议,除非对于协议栈的理解有把握,否则尽可能的不要去修改它)。

为了完成上述的添加设备名称的宏愿,所以我们只能“委曲求全”的添加了gap的初始化。

260 //******************************************************************
261 // fn : main
262 //
263 // brief : 主函数
264 //
265 // param : none
266 //
267 // return : none
268 int main(void)
269 {
270     // 初始化
271     log_init();             // 初始化LOG打印,由RTT工作
272     power_management_init();// 初始化电源控制
273     ble_stack_init();       // 初始化BLE栈堆
274     gap_params_init();      // 初始化GAP
275     advertising_init();     // 初始化广播
276     
277     // 打印例程名称
278     NRF_LOG_INFO("2.2_ble_peripheral_adv_all");
279 
280     advertising_start();    // 开启广播
281     
282     // 进入主循环
283     for (;;)
284     {
285         idle_state_handle();  // 空闲状态处理
286     }
287 }
2.3.3.3.3 gap_params_init()函数

由于广播数据介绍的篇幅会大一些,所以我们先给大家说明一下gap_params_init()函数。

在这个gap初始化的函数中,我们设置了设备名称设备的名称为GY-NRF52832。

52 #define DEVICE_NAME  "GY-NRF52832"
124 //******************************************************************
125 // fn : gap_params_init
126 //
127 // brief : 初始化GAP
128 // details : 此功能将设置设备的所有必需的GAP(通用访问配置文件)参数。它还设置权限和外观。
129 //
130 // param : none
131 //
132 // return : none
133 static void gap_params_init(void)
134 {
135     uint32_t                err_code;
136 
137     // 设置设备名称
138     err_code = sd_ble_gap_device_name_set(NULL,
139                                           (const uint8_t *) DEVICE_NAME,
140                                           strlen(DEVICE_NAME));
141     APP_ERROR_CHECK(err_code);
142 }
2.3.3.3.4 advertising_init()函数
在这里给大家科普一下,BLE的广播数据和扫描回调数据都是由用户自定义的,对于数据的内容没有任何的限制。我们只需要遵循下面的广播数据结构要求就行。

advdata[] = {

 长度A,
 类型A,
 数据A,
 长度B,
 类型B,
 数据B

}

在我们的广播数据初始化函数中,我们首先定义了init.advdata.name_type为BLE_ADVDATA_FULL_NAME,这意味着我们的广播数据中将携带有刚刚GAP初始化中的全部设备名称。

然后我们定义了init.srdata.include_ble_device_addr为true,这意味着我们的扫描回调设备中将会携带有设备的MAC地址。

接下来我们设置了快慢广播,如下代码配置的结果是先快速广播18s(周期40ms),再慢速广播18s(周期100ms),最后停止广播。

最后我们调用nordic提供的广播初始化函数ble_advertising_init()。

我们展示的仅仅是nordic配置广播和扫描回调数据中的部分,除了上述的名称和设备地址。广播数据中还可以携带设备的类型、信号发射强度、连接参数、uuid等等,具体的大家可以go to define,查看ble_advdata_t结构体参数说明。
 85 //******************************************************************
 86 // fn : advertising_init
 87 //
 88 // brief : 用于初始化广播
 89 //
 90 // param : none
 91 //
 92 // return : none
 93 static void advertising_init(void)
 94 {
 95     uint32_t               err_code;
 96     ble_advertising_init_t init;
 97 
 98     memset(&init, 0, sizeof(init));
 99 
100     // 广播数据包含所有设备名称(FULL NAME)
101     init.advdata.name_type = BLE_ADVDATA_FULL_NAME;
102 //    // 广播数据只包含部分设备名称(SHORT NAME,长度为6)
103 //    init.advdata.name_type =  BLE_ADVDATA_SHORT_NAME;
104 //    init.advdata.short_name_len = 6;
105     
106     // 扫描回调数据中包含设备MAC地址
107     init.srdata.include_ble_device_addr = true;
108     
109     
110     // 配置广播周期,先快速广播18s(周期40ms),再慢速广播18s(周期100ms),最后停止广播
111     init.config.ble_adv_fast_enabled  = true;
112     init.config.ble_adv_fast_interval = 64;       // 64*0.625 = 40ms
113     init.config.ble_adv_fast_timeout  = 1800;     // 1800*10ms = 18s
114     init.config.ble_adv_slow_enabled  = true;
115     init.config.ble_adv_slow_interval = 160;      // 160*0.625 = 100ms
116     init.config.ble_adv_slow_timeout  = 1800;     // 1800*10ms = 18s
117     
118     err_code = ble_advertising_init(&m_advertising, &init);
119     APP_ERROR_CHECK(err_code);
120 
121     ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
122 }
2.3.3.3.5 advertising_start()函数

advertising_start()作为我们开启广播功能的函数,我们调用了nordic协议栈中的ble_advertising_start()函数去实现功能。

71 //******************************************************************
72 // fn : advertising_start
73 //
74 // brief : 用于开启广播
75 //
76 // param : none
77 //
78 // return : none
79 static void advertising_start(void)
80 {
81     uint32_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
82     APP_ERROR_CHECK(err_code);
83 }

2.3.4 实验总结

经过通用扫描实验的学习,我们需要掌握的蓝牙协议有如下几点。

主机部分:

1.如何配置扫描的参数

2.如何发起扫描,并触类旁通,自行了解如何停止(大家自己了解一下,很容易)

3.如何获取扫描到的附近的BLE从机设备的信息

从机部分:

1.如何配置从机的广播数据、以及广播的参数(从机广播部分)

2.了解如何设置从机设备的名称(这个是正式项目经常需要使用到的)

3.了解如何开启从机广播,并触类旁通,自行了解如何关闭广播(大家自己了解一下,很容易)

2.4 过滤扫描实验

2.4.1 实验简介

过滤扫描实验1.3_ble_central_scan_filter与2.3_ble_peripheral_adv_filter。经过通用扫描实验的学习,大家应该对于蓝牙的扫描和广播有一定的了解了。那么在我们实际的项目使用中,可能会出现由于我们的附近可能存在的BLE设备太多,导致我们去扫描的时候,没法在第一时间找到我们想要的设备的情况,那么这个时候,我们有没有好的处理方法呢。

上面的疑问答案是肯定的,因为我们在扫描的时候,可以先判断一下扫描到的广播数据或者扫描回调数据,仅返回我们需要的设备(也就是做一些数据的判断和限制),这样就可以快速的找到我们的设备。nordic对这部分处理的很细心,在BLE协议的扫描和广播中,都给我们开发者留好了限制的数据属性,我们以scan为例,可以看到限制的广播数据如下几种:名称,简称,地址,UUID以及容貌。

typedef enum

{

   SCAN_NAME_FILTER,       /**< Filter for names. */
   SCAN_SHORT_NAME_FILTER, /**< Filter for short names. */
   SCAN_ADDR_FILTER,       /**< Filter for addresses. */
   SCAN_UUID_FILTER,       /**< Filter for UUIDs. */
   SCAN_APPEARANCE_FILTER, /**< Filter for appearances. */

} nrf_ble_scan_filter_type_t;

2.4.2 实验现象

主机部分,可以看到上电先打印1.3_ble_central_scan_filter字样,然后会周期打印扫描到的2.3例程的从机设备信息(MAC、扫描回调数据、RSSI)。

在扫描回调的数据的末尾,我们可以看到03030100的字样,这个就是我们实验限制扫描的UUID。

03,// UUID数据长度

03,// 16bit UUID数据类型

01,00 // UUID 0x0001,这边是低位在前

从机部分,可以看到上电先打印2.3_ble_peripheral_adv_filter字样。

2.4.3 工程及源码讲解

2.4.3.1 主机工程
2.4.3.1.1 工程说明

本工程相对于上一章节的通用扫描实现,改动较小,只是对扫描返回的设备做出限制,不再像之前那样返回所有扫描到的设备。

所以我们的修改内容集中在scan_init()函数以及scan_evt_handler()回调函数当中。

2.4.3.1.2 scan_init()函数

相对于通用扫描的主机程序,我们在限制扫描的初始化函数当中,新增了nrf_ble_scan_filter_set()函数用于限制扫描的设备,在这个例程中,我们给大家展示的是限制UUID扫描,大家也可以根据其他信息去进行限制。

我们限制了扫描的UUID是16bit的0x0001,并且调用nrf_ble_scan_filters_enable()使能了这个限制。

65 // 定义扫描限制的UUID
66 static ble_uuid_t const m_nus_uuid = {BLE_UUID_NUS_SERVICE, BLE_UUID_TYPE_BLE};
162 //******************************************************************
163 // fn : scan_init
164 //
165 // brief : 初始化扫描(未设置扫描数据限制)
166 //
167 // param : none
168 //
169 // return : none
170 static void scan_init(void)
171 {
172     ret_code_t          err_code;
173     nrf_ble_scan_init_t init_scan;
174 
175     // 清空扫描结构体参数
176     memset(&init_scan, 0, sizeof(init_scan));
177     
178     // 配置扫描的参数
179     init_scan.p_scan_param = &m_scan_params;
180 
181     // 初始化扫描
182     err_code = nrf_ble_scan_init(&m_scan, &init_scan, scan_evt_handler);
183     APP_ERROR_CHECK(err_code);
184     
185     // 设置扫描的UUID限制
186     err_code = nrf_ble_scan_filter_set(&m_scan, SCAN_UUID_FILTER, &m_nus_uuid);
187     APP_ERROR_CHECK(err_code);
188 
189     // 使能扫描的UUID限制
190     err_code = nrf_ble_scan_filters_enable(&m_scan, NRF_BLE_SCAN_UUID_FILTER, false);
191     APP_ERROR_CHECK(err_code);
192 }
2.4.3.1.3 scan_evt_handler()回调函数

大家可以看到,我们对于扫描回调的事件ID判断进行了更改,上一章的通用扫描我们的判断的是NRF_BLE_SCAN_EVT_NOT_FOUND,这一章的限制扫描我们判断的是NRF_BLE_SCAN_EVT_FILTER_MATCH。

由于上面的一系列对于扫描UUID的限制操作,所以我们的1.3主机实验的现象,才会仅扫描并返回给我们的2.3实验的从机设备。

为了给大家更直观的找到限制的UUID,我们这边临时注视掉了广播数据的打印,只保留了包含了UUID的扫描回调数据打印。

102 //******************************************************************
103 // fn : scan_evt_handler
104 //
105 // brief : 处理扫描回调事件
106 //
107 // param : scan_evt_t  扫描事件结构体
108 //
109 // return : none
110 static void scan_evt_handler(scan_evt_t const * p_scan_evt)
111 {
112     switch(p_scan_evt->scan_evt_id)
113     {
114         // 匹配的扫描数据(也就是过滤之后的)
115         case NRF_BLE_SCAN_EVT_FILTER_MATCH:
116         {
117           // 下面这一段我们只保留了扫描回调数据获取的部分,因为从机筛选广播的UUID在扫描回调数据
118           // 判断是否为扫描回调数据
119           if(p_scan_evt->params.filter_match.p_adv_report->type.scan_response)
120           {
121             NRF_LOG_INFO("Device MAC:  %s",
122                          Util_convertBdAddr2Str((uint8_t*)p_scan_evt->params.filter_match.p_adv_report->peer_addr.addr));
123             
124             if(p_scan_evt->params.filter_match.p_adv_report->data.len)    // 存在扫描回调数据
125             {
126               NRF_LOG_INFO("scan data:  %s",
127                             Util_convertHex2Str(
128                             p_scan_evt->params.filter_match.p_adv_report->data.p_data,
129                             p_scan_evt->params.filter_match.p_adv_report->data.len));
130             }
131             else
132             {
133               NRF_LOG_INFO("scan data:  %s","NONE");
134             }
135             NRF_LOG_INFO("rssi:  %ddBm",p_scan_evt->params.filter_match.p_adv_report->rssi);
136           }
137 //          else  // 否则为广播数据
138 //          {
139 //            // 打印扫描的设备MAC
140 //            NRF_LOG_INFO("Device MAC:  %s",
141 //                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt->params.filter_match.p_adv_report->peer_addr.addr));
142 //            
143 //            if(p_scan_evt->params.filter_match.p_adv_report->data.len)    // 存在广播数据
144 //            {
145 //              NRF_LOG_INFO("adv data:  %s",
146 //                            Util_convertHex2Str(
147 //                            p_scan_evt->params.filter_match.p_adv_report->data.p_data,
148 //                            p_scan_evt->params.filter_match.p_adv_report->data.len));
149 //            }
150 //            else
151 //            {
152 //              NRF_LOG_INFO("adv data:  %s","NONE");
153 //            }
154 //          }
155         } break;
156 
157         default:
158            break;
159     }
160 }
2.4.3.2 从机部分
2.4.3.2.1 工程说明

从机部分相对于通用广播的从机,我们也仅仅增加了UUID的广播数据。所以这边我们查看一下广播数据初始化的函数advertising_init()。

2.4.3.2.2 advertising_init()

大家可以看到,在广播数据初始化函数中,我们在init.srdata中增加了uuids_complete的定义,这样我们的广播数据中就会携带16bit的UUID 0x0001数据(这个数据在m_adv_uuids中被定义)。

53 // 定义广播的UUID
54 static ble_uuid_t m_adv_uuids[] = {{BLE_UUID_NUS_SERVICE, BLE_UUID_TYPE_BLE}};
 87 //******************************************************************
 88 // fn : advertising_init
 89 //
 90 // brief : 用于初始化广播
 91 //
 92 // param : none
 93 //
 94 // return : none
 95 static void advertising_init(void)
 96 {
 97     uint32_t               err_code;
 98     ble_advertising_init_t init;
 99 
100     memset(&init, 0, sizeof(init));
101 
102     // 广播数据包含所有设备名称(FULL NAME)
103     init.advdata.name_type = BLE_ADVDATA_FULL_NAME;
104 //    // 广播数据只包含部分设备名称(SHORT NAME,长度为6)
105 //    init.advdata.name_type =  BLE_ADVDATA_SHORT_NAME;
106 //    init.advdata.short_name_len = 6;
107     
108     // 扫描回调数据中包含16bit UUID:0x0001
109     init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
110     init.srdata.uuids_complete.p_uuids  = m_adv_uuids;
111     
112     // 扫描回调数据中包含设备MAC地址
113     init.srdata.include_ble_device_addr = true;
114     
115     // 配置广播周期,先快速广播18s(周期40ms),再慢速广播18s(周期100ms),最后停止广播
116     init.config.ble_adv_fast_enabled  = true;
117     init.config.ble_adv_fast_interval = 64;       // 64*0.625 = 40ms
118     init.config.ble_adv_fast_timeout  = 1800;     // 1800*10ms = 18s
119     init.config.ble_adv_slow_enabled  = true;
120     init.config.ble_adv_slow_interval = 160;      // 160*0.625 = 100ms
121     init.config.ble_adv_slow_timeout  = 18000;    // 18000*10ms = 180s
122     
123     err_code = ble_advertising_init(&m_advertising, &init);
124     APP_ERROR_CHECK(err_code);
125 
126     ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
127 }

2.4.4 实验总结

经过这个扫描限制实验的学习,大家需要掌握的要点。

主机部分:

1.如何添加一个扫描的限制并使能

2.限制扫描的回调事件ID是NRF_BLE_SCAN_EVT_FILTER_MATCH

3.自行编程,完成通过名称、地址等其他限制的扫描

从机部分:

1.如何添加个人的广播数据,例如本章节的UUID(本质上还是广播数据的配置)

2.5 白名单扫描实验

2.5.1 实验简介

白名单扫描实验1.4_ble_central_scan_whitelist与2.4_ble_peripheral_adv_whitelist。有关扫描的实验,在第一章的通用扫描中,我们了解到可以主机发起扫描,可以获取从机设备的一些信息,包含广播数据和扫描回调数据,以及从机设备的MAC。在第二章的限制扫描中,我们已经学习过如果根据广播数据和扫描回调数据去获取我们指定的从机设备。那么在这一章节我们将给大家带来,根据从机设备的MAC地址,去限制扫描。

注意:白名单扫描是根据扫描到的设备MAC,而不是指的广播数据中携带的MAC(假设我们将MAC地址加入到广播数据中)。

2.5.2 实验现象

主机部分,上电打印例程名称1.4_ble_central_scan_whitelist,如果成功设置白名单,则打印Successfully set whitelist!,然后如果附近有白名单中的设备,则会打印扫描到的白名单设备的信息。

从机部分相较于前两章的内容,几乎没有改动,这边不做讲解。因为白名单的限制,是由主机部分完成。

2.5.3 工程及源码讲解

2.5.3.1 工程说明

本实验的修改内容集中在主机的扫描初始化以及扫描的回调事件处理函数中,所以下面只有主机部分的代码讲解,而没有从机部分的。

2.5.3.2 主机部分
2.5.3.2.1 scan_init()函数

首先我们查看一下主机扫描的初始化函数,我们可以看到filter_policy过滤扫描参数,我们配置为BLE_GAP_SCAN_FP_WHITELIST,也就是白名单扫描模式。

启用这个白名单模式,如果成功,则会在扫描回调事件处理函数中给我们返回NRF_BLE_SCAN_EVT_WHITELIST_REQUEST请求开启白名单的事件ID。

54 // 定义扫描参数
55 static ble_gap_scan_params_t m_scan_params = 
56 {
57     .active        = 1,                            // 1为主动扫描,可获得广播数据及扫描回调数据
58     .interval      = NRF_BLE_SCAN_SCAN_INTERVAL,   // 扫描间隔:160*0.625 = 100ms  
59     .window        = NRF_BLE_SCAN_SCAN_WINDOW,     // 扫描窗口:80*0.625 = 50ms   
60     .timeout       = NRF_BLE_SCAN_SCAN_DURATION,   // 扫描持续的时间:设置为0,一直扫描,直到明确的停止扫描
61     .filter_policy = BLE_GAP_SCAN_FP_WHITELIST,    // 扫描白名单设备
62     .scan_phys     = BLE_GAP_PHY_1MBPS,            // 扫描1MBPS的PHY
63 };
181 //******************************************************************
182 // fn : scan_init
183 //
184 // brief : 初始化扫描(未设置扫描数据限制)
185 //
186 // param : none
187 //
188 // return : none
189 static void scan_init(void)
190 {
191     ret_code_t          err_code;
192     nrf_ble_scan_init_t init_scan;
193 
194     // 清空扫描结构体参数
195     memset(&init_scan, 0, sizeof(init_scan));
196     
197     // 配置扫描的参数
198     init_scan.p_scan_param = &m_scan_params;
199     
200     // 初始化扫描
201     err_code = nrf_ble_scan_init(&m_scan, &init_scan, scan_evt_handler);
202     APP_ERROR_CHECK(err_code);
203 }
2.5.3.2.2 scan_evt_handler()函数

可以看到,当我们接收到NRF_BLE_SCAN_EVT_WHITELIST_REQUEST事件时,我们将去调用sd_ble_gap_whitelist_set函数设置我们的白名单设备。

我们这边首先获取了从机的MAC地址:0xD363BFCE5C46,然后将其添加到白名单设备列表当中。

当我们扫描到0xD363BFCE5C46后将打印他的广播和扫描回调数据、以及型号强度。

这个地方,需要大家设置为自己的从设备MAC,不然无法使用此例程
 99 //******************************************************************
100 // fn : scan_evt_handler
101 //
102 // brief : 处理扫描回调事件
103 //
104 // param : scan_evt_t  扫描事件结构体
105 //
106 // return : none
107 static void scan_evt_handler(scan_evt_t const * p_scan_evt)
108 {
109     uint32_t err_code;
110     ble_gap_addr_t peer_addr;
111     ble_gap_addr_t const * p_peer_addr;
112     switch(p_scan_evt->scan_evt_id)
113     {
114         // 白名单设置请求
115         case NRF_BLE_SCAN_EVT_WHITELIST_REQUEST:
116         {
117           memset(&peer_addr, 0x00, sizeof(peer_addr));
118           peer_addr.addr_id_peer = 1;
119           peer_addr.addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC;
120           peer_addr.addr[5] = 0xD3;
121           peer_addr.addr[4] = 0x63;
122           peer_addr.addr[3] = 0xBF;
123           peer_addr.addr[2] = 0xCE;
124           peer_addr.addr[1] = 0x5C;
125           peer_addr.addr[0] = 0x46;
126           p_peer_addr = &peer_addr; 
127           
128           // 设置白名单
129           err_code = sd_ble_gap_whitelist_set(&p_peer_addr, 0x01);
130           if (err_code == NRF_SUCCESS)
131           {
132             NRF_LOG_INFO("Successfully set whitelist!");
133           }
134           APP_ERROR_CHECK(err_code);
135         }break;
136         
137         // 扫描到的白名单设备数据
138         case NRF_BLE_SCAN_EVT_WHITELIST_ADV_REPORT:
139         {
140           // 判断是否为扫描回调数据
141           if(p_scan_evt->params.p_whitelist_adv_report->type.scan_response)
142           {
143             if(p_scan_evt->params.p_whitelist_adv_report->data.len)    // 存在扫描回调数据
144             {
145               NRF_LOG_INFO("scan data:  %s",
146                             Util_convertHex2Str(
147                             p_scan_evt->params.p_whitelist_adv_report->data.p_data,
148                             p_scan_evt->params.p_whitelist_adv_report->data.len));
149             }
150             else
151             {
152               NRF_LOG_INFO("scan data:  %s","NONE");
153             }
154             NRF_LOG_INFO("rssi:  %ddBm",p_scan_evt->params.p_whitelist_adv_report->rssi);
155           }
156           else  // 否则为广播数据
157           {
158             // 打印扫描的设备MAC
159             NRF_LOG_INFO("Device MAC:  %s",
160                          Util_convertBdAddr2Str((uint8_t*)p_scan_evt->params.p_whitelist_adv_report->peer_addr.addr));
161             
162             if(p_scan_evt->params.p_whitelist_adv_report->data.len)    // 存在广播数据
163             {
164               NRF_LOG_INFO("adv data:  %s",
165                             Util_convertHex2Str(
166                             p_scan_evt->params.p_whitelist_adv_report->data.p_data,
167                             p_scan_evt->params.p_whitelist_adv_report->data.len));
168             }
169             else
170             {
171               NRF_LOG_INFO("adv data:  %s","NONE");
172             }
173           }
174         } break;
175 
176         default:
177            break;
178     }
179 }
2.5.3.3 从机部分

2.5.4 实验总结

经过白名单实验的学习,我们需要明白以下几点:

1.我们使用白名单功能的前提是我们知道对方设备的MAC。

2.白名单功能只需要主机完成

3.了解NRF_BLE_SCAN_EVT_WHITELIST_REQUEST事件以及sd_ble_gap_whitelist_set()函数

2.6 通用连接实验

2.6.1 实验简介

通用连接实验1.5_ble_central_conn_all与2.5_ble_peripheral_conn_all。从这一实验开始,我们将给大家介绍蓝牙连接相关的协议,由此对于蓝牙协议的学习进入到第二阶段。

2.6.2 实验现象

主机部分,先打印实验名称1.5_ble_central_conn_all,然后开始扫描周围的从机设备。当我们将两块开发板靠近的时候(当RSSI大于-30dBm),则会发起连接。然后打印连接成功的消息,并且打印连接参数。

Connected. conn_DevAddr: 0xD363BFCE5C46 // 连接的对方设备MAC地址

conn_handle: 0x0000 // 连接的句柄,第一个连接的设备是0x0000,第二个是0x0001,以此类推

conn_Param: 24,24,0,400 // 连接参数

从机部分,先打印实验名称2.5_ble_peripheral_conn_all,当被主机成功连接之后,会打印连接成功的消息;当断开连接之后,会打印断开连接的句柄,并且会打印断开连接的原因。

Connected. conn_DevAddr: 0xE51F64D6BFF4 // 连接的对方设备的MAC

Connected. conn_handle: 0x0000 // 连接的句柄

Connected. conn_Param: 24,24,0,400 // 连接参数


Disconnected. conn_handle: 0x0, reason: 0x0008 // 断开连接的设备句柄,以及断开的原因

2.6.3 工程及源码讲解

2.6.3.1 主机部分
2.6.3.1.1 工程说明

通用连接主机工程,是在通用扫描主机工程的基础上,新增了连接的过程,主要的修改集中在扫描事件处理函数,以及ble协议栈事件处理函数。

2.6.3.1.2 scan_evt_handler()函数

定义连接的参数,这个参数将在sd_ble_gap_connect()函数中被调用。

65 // 定义连接参数
66 static ble_gap_conn_params_t m_conn_params = 
67 {
68     .min_conn_interval  = MSEC_TO_UNITS(NRF_BLE_SCAN_MIN_CONNECTION_INTERVAL, UNIT_1_25_MS),  // 最小连接间隔7.5ms
69     .max_conn_interval  = MSEC_TO_UNITS(NRF_BLE_SCAN_MAX_CONNECTION_INTERVAL, UNIT_1_25_MS),  // 最大连接间隔30ms  
70     .slave_latency      = NRF_BLE_SCAN_SLAVE_LATENCY,                                         // 隐藏周期0 
71     .conn_sup_timeout   = MSEC_TO_UNITS(NRF_BLE_SCAN_SUPERVISION_TIMEOUT, UNIT_10_MS),        // 超时时间4000ms 
72 };

我们可以看到,在扫描回调事件处理的函数中,前面一部分还是和之前一样的,就是获取扫描到的从机设备的信息。

在159行,我们对扫描到的从机设备的RSSI进行了判断,我们人为定义,当设备的RSSI大于-30dBm的时候,也就是信号强度非常好,我们就默认连接该设备。

首先,我们先定义准备连接的从机设备的地址和信息,配置m_addr.addr_type为BLE_GAP_ADDR_TYPE_RANDOM_STATIC,配置m_addr.addr为扫描到的设备地址p_scan_evt->params.p_not_found->peer_addr.addr。

配置好准备连接的设备的MAC信息,在发起连接之前,我们一定要先调用nrf_ble_scan_stop()函数去停止扫描,不然会出错。

停止扫描之后,我们调用sd_ble_gap_connect()函数,发起对从机设备的连接。

107 //******************************************************************
108 // fn : scan_evt_handler
109 //
110 // brief : 处理扫描回调事件
111 //
112 // param : scan_evt_t  扫描事件结构体
113 //
114 // return : none
115 static void scan_evt_handler(scan_evt_t const * p_scan_evt)
116 {
117     switch(p_scan_evt->scan_evt_id)
118     {
119         // 未过滤的扫描数据
120         case NRF_BLE_SCAN_EVT_NOT_FOUND:
121         {
122           // 判断是否为扫描回调数据
123           if(p_scan_evt->params.p_not_found->type.scan_response)
124           {
125             if(p_scan_evt->params.p_not_found->data.len)    // 存在扫描回调数据
126             {
127               NRF_LOG_INFO("scan data:  %s",
128                             Util_convertHex2Str(
129                             p_scan_evt->params.p_not_found->data.p_data,
130                             p_scan_evt->params.p_not_found->data.len));
131             }
132             else
133             {
134               NRF_LOG_INFO("scan data:  %s","NONE");
135             }
136             NRF_LOG_INFO("rssi:  %ddBm",p_scan_evt->params.p_not_found->rssi);
137           }
138           else  // 否则为广播数据
139           {
140             // 打印扫描的设备MAC
141             NRF_LOG_INFO("Device MAC:  %s",
142                          Util_convertBdAddr2Str((uint8_t*)p_scan_evt->params.p_not_found->peer_addr.addr));
143             
144             if(p_scan_evt->params.p_not_found->data.len)    // 存在广播数据
145             {
146               NRF_LOG_INFO("adv data:  %s",
147                             Util_convertHex2Str(
148                             p_scan_evt->params.p_not_found->data.p_data,
149                             p_scan_evt->params.p_not_found->data.len));
150             }
151             else
152             {
153               NRF_LOG_INFO("adv data:  %s","NONE");
154             }
155           }
156           
157           
158           // 如果扫描到的设备信号强度大于-30dBm
159           if(p_scan_evt->params.p_not_found->rssi > (-30))
160           {
161             ret_code_t          err_code;
162             
163             // 配置准备连接的设备MAC
164             ble_gap_addr_t m_addr;
165             m_addr.addr_id_peer = 1;
166             m_addr.addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC;
167             memcpy(m_addr.addr,p_scan_evt->params.p_not_found->peer_addr.addr,BLE_GAP_ADDR_LEN);
168             
169             // 停止扫描
170             nrf_ble_scan_stop();
171             // 发起连接
172             err_code = sd_ble_gap_connect(&m_addr,&m_scan_params,&m_conn_params,APP_BLE_CONN_CFG_TAG);
173             APP_ERROR_CHECK(err_code);
174           }
175           
176         } break;
177 
178         default:
179            break;
180     }
181 }
2.6.3.1.3 ble_evt_handler()函数

当我们的主机设备进行连接,或者断开连接的时候,BLE的事件回调中,都会给我们状态事件提示。

连接成功的状态BLE_GAP_EVT_CONNECTED,我们可以在这边获取到连接的设备的MAC、连接参数等等。

断开连接的状态BLE_GAP_EVT_DISCONNECTED,我们可以获取到断开的连接的原因等等。

210 //******************************************************************
211 // fn : ble_evt_handler
212 //
213 // brief : BLE事件回调
214 // details : 包含以下几种事件类型:COMMON、GAP、GATT Client、GATT Server、L2CAP
215 //
216 // param : ble_evt_t  事件类型
217 //         p_context  未使用
218 //
219 // return : none
220 static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
221 {
222     ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt;
223     ble_gap_evt_connected_t const * p_connected_evt = &p_gap_evt->params.connected;
224     switch (p_ble_evt->header.evt_id)
225     {
226         // 连接
227         case BLE_GAP_EVT_CONNECTED:
228             NRF_LOG_INFO("Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d",
229                          Util_convertBdAddr2Str((uint8_t*)p_connected_evt->peer_addr.addr),
230                          p_gap_evt->conn_handle,
231                          p_connected_evt->conn_params.min_conn_interval,
232                          p_connected_evt->conn_params.max_conn_interval,
233                          p_connected_evt->conn_params.slave_latency,
234                          p_connected_evt->conn_params.conn_sup_timeout
235                          );
236             break;
237         // 断开连接
238         case BLE_GAP_EVT_DISCONNECTED:
239             NRF_LOG_INFO("Disconnected. conn_handle: 0x%x, reason: 0x%04x",
240                          p_gap_evt->conn_handle,
241                          p_gap_evt->params.disconnected.reason);
242             // 如果需要异常断开重连,可以打开下面的注释
243             // scan_start();  // 重新开始扫描
244             break;
245 
246         default:
247             break;
248     }
249 }
2.6.3.2 从机部分
2.6.3.2.1 工程说明

通用连接的从机工程,是在通用广播的从机工程的基础上修改的。

由于从机设备是一直保持广播,等待被连接的,所以相对于主机程序,我们就不用去找发起连接的函数以及对方设备的MAC了。因此我们只需要关注BLE协议的回调函数的事件返回。

2.6.3.2.2 ble_evt_handler()函数

从机的连接与断开的状态和主机是一样的,能够获得的对方设备的信息也是一样的。

连接成功的状态BLE_GAP_EVT_CONNECTED,我们可以在这边获取到连接的设备的MAC、连接参数等等。

断开连接的状态BLE_GAP_EVT_DISCONNECTED,我们可以获取到断开的连接的原因等等。

148 //******************************************************************
149 // fn : ble_evt_handler
150 //
151 // brief : BLE事件回调
152 // details : 包含以下几种事件类型:COMMON、GAP、GATT Client、GATT Server、L2CAP
153 //
154 // param : ble_evt_t  事件类型
155 //         p_context  未使用
156 //
157 // return : none
158 static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
159 {
160     ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt;
161     ble_gap_evt_connected_t const * p_connected_evt = &p_gap_evt->params.connected;
162     switch (p_ble_evt->header.evt_id)
163     {
164         // 连接
165         case BLE_GAP_EVT_CONNECTED:
166             NRF_LOG_INFO("Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d",
167                          Util_convertBdAddr2Str((uint8_t*)p_connected_evt->peer_addr.addr),
168                          p_gap_evt->conn_handle,
169                          p_connected_evt->conn_params.min_conn_interval,
170                          p_connected_evt->conn_params.max_conn_interval,
171                          p_connected_evt->conn_params.slave_latency,
172                          p_connected_evt->conn_params.conn_sup_timeout
173                          );
174             break;
175         // 断开连接
176         case BLE_GAP_EVT_DISCONNECTED:
177             NRF_LOG_INFO("Disconnected. conn_handle: 0x%x, reason: 0x%04x",
178                          p_gap_evt->conn_handle,
179                          p_gap_evt->params.disconnected.reason);
180             break;
181 
182         default:
183             // No implementation needed.
184             break;
185     }
186 }

2.6.4 实验总结

经过主从机通用连接的实验,我们需要弄懂的问题点如下:

主机部分:

1.主机部分一定要先发起扫描,当扫描到设备之后,再去发起连接。(不扫描直接发起连接,如果周围没有这个从设备,会进入异常,并只能RST恢复)

2.主机发起连接的时候,是利用从机的MAC地址进行连接

3.连接以及断开连接的状态返回,可以获取对方设备信息以及断开原因

从机部分:

1.从机是一直广播等待被动连接的设备

2.连接以及断开连接的状态返回,可以获取对方设备信息以及断开原因

2.7 过滤连接实验

2.7.1 实验简介

过滤连接实验1.6_ble_central_conn_filter与2.6_ble_peripheral_conn_filter。

过滤连接实验是从过滤扫描的实验集成下来的,它的目的是和过滤扫描实验一样的,也是为了快速的从一堆BLE设备中,找到我们的设备,并与之快速建立连接。

2.7.2 实验现象

主机上电后先打印实验名称1.6_ble_central_conn_filter,然后发起扫描,如果扫描到符合过滤的从机设备,则会打印扫描到的设备信息,然后发起连接,连接成功后会打印连接的一些信息。

从机上电后先打印实验名称2.6_ble_peripheral_conn_filter,如果被主机扫描连接之后,则会打印连接的信息。

2.7.3 工程及源码讲解

2.7.3.1 主机部分
2.7.3.1.1 工程说明

过滤连接,我们主要关注的是扫描初始化中对于过滤连接设备的设置,以及过滤连接成功或者失败返回的扫描回调函数的事件ID。

2.7.3.1.2 scan_init()函数

连接参数定义,用于配置init_scan.p_conn_param参数,也就是我们的过滤扫描之后发起连接的连接参数。

65 // 定义连接参数
66 static ble_gap_conn_params_t m_conn_params = 
67 {
68     .min_conn_interval  = MSEC_TO_UNITS(NRF_BLE_SCAN_MIN_CONNECTION_INTERVAL, UNIT_1_25_MS),  // 最小连接间隔7.5ms
69     .max_conn_interval  = MSEC_TO_UNITS(NRF_BLE_SCAN_MAX_CONNECTION_INTERVAL, UNIT_1_25_MS),  // 最大连接间隔30ms  
70     .slave_latency      = NRF_BLE_SCAN_SLAVE_LATENCY,                                         // 隐藏周期0 
71     .conn_sup_timeout   = MSEC_TO_UNITS(NRF_BLE_SCAN_SUPERVISION_TIMEOUT, UNIT_10_MS),        // 超时时间4000ms 
72 };

在扫描初始化函数中,我们可以看到我们初始化了init_scan.connect_if_match = 1,然后我们还配置了init_scan.p_conn_param = &m_conn_params,后面的限制UUID是和过滤扫描一样的。 这样配置好之后,我们发起扫描,一旦扫描到我们的过滤后的设备,就会使用我们配置好的连接参数,对这个从机设备发起连接。连接成功或者失败,都会在扫描回调函数中进行事件通知。

182 //******************************************************************
183 // fn : scan_init
184 //
185 // brief : 初始化扫描(未设置扫描数据限制)
186 //
187 // param : none
188 //
189 // return : none
190 static void scan_init(void)
191 {
192     ret_code_t          err_code;
193     nrf_ble_scan_init_t init_scan;
194 
195     // 清空扫描结构体参数
196     memset(&init_scan, 0, sizeof(init_scan));
197     
198     init_scan.connect_if_match = 1;
199     init_scan.conn_cfg_tag = APP_BLE_CONN_CFG_TAG;
200     
201     // 配置扫描的参数
202     init_scan.p_scan_param = &m_scan_params;
203 
204     // 配置连接的参数
205     init_scan.p_conn_param = &m_conn_params;
206     
207     // 初始化扫描
208     err_code = nrf_ble_scan_init(&m_scan, &init_scan, scan_evt_handler);
209     APP_ERROR_CHECK(err_code);
210     
211     // 设置扫描的UUID限制
212     err_code = nrf_ble_scan_filter_set(&m_scan, SCAN_UUID_FILTER, &m_nus_uuid);
213     APP_ERROR_CHECK(err_code);
214 
215     // 使能扫描的UUID限制
216     err_code = nrf_ble_scan_filters_enable(&m_scan, NRF_BLE_SCAN_UUID_FILTER, false);
217     APP_ERROR_CHECK(err_code);
218 }
2.7.3.1.3 scan_evt_handler()扫描回调函数

在扫描回调函数中,过滤扫描的事件是和之前一样的,我们不做介绍。

我们关注一下事件ID,NRF_BLE_SCAN_EVT_CONNECTED与NRF_BLE_SCAN_EVT_CONNECTING_ERROR,当我们设置init_scan.connect_if_match = 1后,如果扫描到并且成功连接上目标从设备,则返回连接成功的ID,如果失败了,则返回连接ERR。

注意:由于这两个事件返回的信息和ble协议栈返回的事件ID携带的信息相同,所以这边我们不重复打印。

case NRF_BLE_SCAN_EVT_CONNECTED: { NRF_LOG_INFO("SCAN CONNECTED!");

// NRF_LOG_INFO("Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d",

// Util_convertBdAddr2Str((uint8_t*)p_scan_evt->params.connected.p_connected->peer_addr.addr),

// p_scan_evt->params.connected.conn_handle,

// p_scan_evt->params.connected.p_connected->conn_params.min_conn_interval,

// p_scan_evt->params.connected.p_connected->conn_params.max_conn_interval,

// p_scan_evt->params.connected.p_connected->conn_params.slave_latency,

// p_scan_evt->params.connected.p_connected->conn_params.conn_sup_timeout

// );

}break;

case NRF_BLE_SCAN_EVT_CONNECTING_ERROR: { NRF_LOG_INFO("SCAN CONNECTING ERROR!");

// NRF_LOG_INFO("Disconnected. reason: 0x%04x",

// p_scan_evt->params.connecting_err.err_code);

}break;

111 //******************************************************************
112 // fn : scan_evt_handler
113 //
114 // brief : 处理扫描回调事件
115 //
116 // param : scan_evt_t  扫描事件结构体
117 //
118 // return : none
119 static void scan_evt_handler(scan_evt_t const * p_scan_evt)
120 {
121     switch(p_scan_evt->scan_evt_id)
122     {
123         // 匹配的扫描数据(也就是过滤之后的)
124         case NRF_BLE_SCAN_EVT_FILTER_MATCH:
125         {
126         ...
127         } break;
128 
129         
130         case NRF_BLE_SCAN_EVT_CONNECTED:
131         {
132             NRF_LOG_INFO("SCAN CONNECTED!"); 
133 //            NRF_LOG_INFO("Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d",
134 //                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt->params.connected.p_connected->peer_addr.addr),
135 //                         p_scan_evt->params.connected.conn_handle,
136 //                         p_scan_evt->params.connected.p_connected->conn_params.min_conn_interval,
137 //                         p_scan_evt->params.connected.p_connected->conn_params.max_conn_interval,
138 //                         p_scan_evt->params.connected.p_connected->conn_params.slave_latency,
139 //                         p_scan_evt->params.connected.p_connected->conn_params.conn_sup_timeout
140 //                         );
141         }break;
142         
143         case NRF_BLE_SCAN_EVT_CONNECTING_ERROR:
144         {
145             NRF_LOG_INFO("SCAN CONNECTING ERROR!");
146 //            NRF_LOG_INFO("Disconnected. reason: 0x%04x",
147 //                         p_scan_evt->params.connecting_err.err_code);
148         }break;
149         
150         default:
151            break;
152     }
153 }
2.7.3.2 从机部分

从机部分就不给大家介绍了,和前面的过滤广播的例程是重复的,仅修改了连接之后获取并打印对方设备的信息。

2.7.4 实验总结

经过本实验的学习,大家需要了解到如何让主机在扫描的时候,去对过滤后的从机设备发起连接。

1.了解init_scan.connect_if_match参数的功能。

2.了解扫描回调事件ID中的NRF_BLE_SCAN_EVT_CONNECTED与NRF_BLE_SCAN_EVT_CONNECTING_ERROR。

2.8 连接参数更新实验

2.8.1 实验简介

经过前两章的主从机扫描并连接的实验学习后,我们不难发现,连接之后主从机之间一个很重要的参数(返回连接成功的状态下,打印的数据),那就是连接参数。

有关连接参数的详细说明,请大家查看本手册的第一章节有关蓝牙协议的介绍。

主从机连接参数的配置流程:

1、主从机连接成功之后,我们将以主机携带的连接参数,作为主从机连接的参数

2、从机可以发起更新连接参数的请求

3、主机接收到从机的参数更新请求,发起参数更新

4、更新成功,将以新的参数作为连接参数

2.8.2 实验现象

主机上电后发起扫描,一旦扫描到我们过滤的设备之后,就会发起连接。

连接成功后,打印连接的handle,以及主从机之间的连接参数。

当从机发起连接参数更新请求后,我们使用从机申请的连接参数,去发起连接参数的更新。

从机上电后广播,被主机连接后打印连接的handle,以及连接参数。

5000ms后,从机发起参数更新请求,主机接收请求后发起更新,更新成功后,从机打印新的连接参数。

2.8.3 工程及源码讲解

2.8.3.1 主机部分

主机部分大部分和之前的过滤连接是一样的,唯一的区别在于接收从机连接参数更新请求,并发起更新。

2.8.3.1.1 ble_evt_handler()回调函数

在BLE事件回调中可以看到两个新增的事件处理,一个是连接参数更新请求BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST,另一个是连接参数更新BLE_GAP_EVT_CONN_PARAM_UPDATE。

BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:连接参数更新请求,这个状态是主机接收到从机发起更新参数的请求时返回,在这个状态下,我们可以获取从机申请更新的参数数值,并且调用sd_ble_gap_conn_param_update()函数去发起连接参数的更新。

BLE_GAP_EVT_CONN_PARAM_UPDATE:连接参数更新,当连接参数完成更新的时候,会返回这个状态,在这个状态中,我们可以获取到更新完成后的新的连接参数。

202 //******************************************************************
203 // fn : ble_evt_handler
204 //
205 // brief : BLE事件回调
206 // details : 包含以下几种事件类型:COMMON、GAP、GATT Client、GATT Server、L2CAP
207 //
208 // param : ble_evt_t  事件类型
209 //         p_context  未使用
210 //
211 // return : none
212 static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
213 {
214     ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt;
215     ble_gap_evt_connected_t const * p_connected_evt = &p_gap_evt->params.connected;
216     
217     switch (p_ble_evt->header.evt_id)
218     {
219         // 连接
220         case BLE_GAP_EVT_CONNECTED:
221             NRF_LOG_INFO("Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d",
222                          Util_convertBdAddr2Str((uint8_t*)p_connected_evt->peer_addr.addr),
223                          p_gap_evt->conn_handle,
224                          p_connected_evt->conn_params.min_conn_interval,
225                          p_connected_evt->conn_params.max_conn_interval,
226                          p_connected_evt->conn_params.slave_latency,
227                          p_connected_evt->conn_params.conn_sup_timeout
228                          );
229             break;
230 
231         // 断开连接
232         case BLE_GAP_EVT_DISCONNECTED:
233             NRF_LOG_INFO("Disconnected. conn_handle: 0x%x, reason: 0x%04x",
234                          p_gap_evt->conn_handle,
235                          p_gap_evt->params.disconnected.reason);
236             // 如果需要异常断开重连,可以打开下面的注释
237             // scan_start();  // 重新开始扫描
238             break;
239 
240         // 连接参数更新请求
241         case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
242             NRF_LOG_INFO("conn_Param Update  Request");
243             
244             sd_ble_gap_conn_param_update(p_gap_evt->conn_handle,
245                                                     &p_gap_evt->params.conn_param_update_request.conn_params);
246             break;
247             
248         // 连接参数更新
249         case BLE_GAP_EVT_CONN_PARAM_UPDATE:
250             NRF_LOG_INFO("conn_Param Update: %d,%d,%d,%d",
251                          p_ble_evt->evt.gap_evt.params.conn_param_update.conn_params.min_conn_interval,
252                          p_ble_evt->evt.gap_evt.params.conn_param_update.conn_params.max_conn_interval,
253                          p_ble_evt->evt.gap_evt.params.conn_param_update.conn_params.slave_latency,
254                          p_ble_evt->evt.gap_evt.params.conn_param_update.conn_params.conn_sup_timeout
255                          );
256             break;
257             
258         default:
259             break;
260     }
261 }
2.8.3.2 从机部分

从机部分,我们主要关注两个地方,一个是连接参数的更新请求conn_params_init()函数,另一个是和主机部分一样的,连接参数更新成功的状态返回。

2.8.3.2.1 conn_params_init()函数

连接参数初始化函数,在这个函数中,我们配置了新的连接参数,以及连接参数更新的时间及尝试的次数,并且包含了两个回调函数,一个是更新是否成功的返回,一个是携带的连接参数是否正确的返回。

我们配置了第一次更新的时间为连接成功后5s(由frist_xx_delay控制),后面的几次也是间隔5s(由next_xx_delay控制),然后尝试更新次数为3。这个尝试次数3的含义是,如果申请了3次更新,都未成功,那么将从on_conn_params_evt回调中返回更新失败。

245 //******************************************************************
246 // fn : conn_params_init
247 //
248 // brief : 初始化连接参数模块的功能
249 //
250 // param : none
251 //
252 // return : none
253 static void conn_params_init(void)
254 {
255     uint32_t               err_code;
256     ble_conn_params_init_t cp_init;
257 
258     memset(&cp_init, 0, sizeof(cp_init));
259 
260     cp_init.p_conn_params                  = &m_conn_params;
261     cp_init.first_conn_params_update_delay = APP_TIMER_TICKS(5000);
262     cp_init.next_conn_params_update_delay  = APP_TIMER_TICKS(5000);
263     cp_init.max_conn_params_update_count   = 3;
264     cp_init.start_on_notify_cccd_handle    = BLE_GATT_HANDLE_INVALID;
265     cp_init.disconnect_on_fail             = false;
266     cp_init.evt_handler                    = on_conn_params_evt;
267     cp_init.error_handler                  = conn_params_error_handler;
268 
269     err_code = ble_conn_params_init(&cp_init);
270     APP_ERROR_CHECK(err_code);
271 }

可以看到,我们新的连接参数,连接间隔40ms,隐藏周期0,超时时间4s。

57 // 定义连接参数(为了展示连接参数的更新,我们设置连接间隔为固定的20ms)
58 static ble_gap_conn_params_t m_conn_params = 
59 {
60     .min_conn_interval  = MSEC_TO_UNITS(40, UNIT_1_25_MS),  // 最小连接间隔40ms
61     .max_conn_interval  = MSEC_TO_UNITS(40, UNIT_1_25_MS),  // 最大连接间隔40ms  
62     .slave_latency      = 0,                                // 隐藏周期0 
63     .conn_sup_timeout   = MSEC_TO_UNITS(2000, UNIT_10_MS),  // 超时时间4000ms 
64 };

在on_conn_params_evt()回调函数中,我们可以看到连接参数更新成功或者失败的返回。

这边需要注意的问题点如下,以我们这个实验为例,例如我们的参数更新请求会尝试3次:

1、如果第一次就更新成功,那么会直接返回更新SUCCESS,并且后面2次更新请求将不会再起作用

2、如果三次更新都不成功(这个不成功,可能是更新失败,可能是主机不响应等等),那么才会返回更新failed

也就是3次只要有一次更新成功就认为我们更新完成了,如果3次都失败,才会认为失败。

211 //******************************************************************
212 // fn : on_conn_params_evt
213 //
214 // brief : 处理连接更新参数的事件回调
215 //
216 // param : p_evt -> 接收到的连接模块返回的任务事件
217 //
218 // return : none
219 static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)
220 {
221     if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_SUCCEEDED)
222     {
223         NRF_LOG_INFO("connParam Update Success"); 
224     }
225       
226     if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED)
227     {
228         NRF_LOG_INFO("connParam Update Failed"); 
229     }
230 }

这边的conn_params_error_handler()错误回调函数,当我们申请更新的连接参数不符合协议时才会返回,也就是提示我们连接参数异常。

232 //******************************************************************
233 // fn : conn_params_error_handler
234 //
235 // brief : 处理连接更新参数异常的事件回调
236 //
237 // param : nrf_error -> 异常标志
238 //
239 // return : none
240 static void conn_params_error_handler(uint32_t nrf_error)
241 {
242     APP_ERROR_HANDLER(nrf_error);
243 }
2.8.3.2.2 ble_evt_handler()回调函数

BLE事件回调函数,和主机一样的,当我们成功更新连接参数之后,返回BLE_GAP_EVT_CONN_PARAM_UPDATE事件,在这个事件中我们可以获取新的连接参数。

159 //******************************************************************
160 // fn : ble_evt_handler
161 //
162 // brief : BLE事件回调
163 // details : 包含以下几种事件类型:COMMON、GAP、GATT Client、GATT Server、L2CAP
164 //
165 // param : ble_evt_t  事件类型
166 //         p_context  未使用
167 //
168 // return : none
169 static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
170 {
171     ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt;
172     ble_gap_evt_connected_t const * p_connected_evt = &p_gap_evt->params.connected;
173     
174     switch (p_ble_evt->header.evt_id)
175     {
176         // 连接
177         case BLE_GAP_EVT_CONNECTED:
178             NRF_LOG_INFO("Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d",
179                          Util_convertBdAddr2Str((uint8_t*)p_connected_evt->peer_addr.addr),
180                          p_gap_evt->conn_handle,
181                          p_connected_evt->conn_params.min_conn_interval,
182                          p_connected_evt->conn_params.max_conn_interval,
183                          p_connected_evt->conn_params.slave_latency,
184                          p_connected_evt->conn_params.conn_sup_timeout
185                          );
186             break;
187 
188         // 断开连接
189         case BLE_GAP_EVT_DISCONNECTED:
190             NRF_LOG_INFO("Disconnected. conn_handle: 0x%x, reason: 0x%04x",
191                          p_gap_evt->conn_handle,
192                          p_gap_evt->params.disconnected.reason);
193             break;
194             
195         // 连接参数更新
196         case BLE_GAP_EVT_CONN_PARAM_UPDATE:
197             NRF_LOG_INFO("conn_Param Update: %d,%d,%d,%d",
198                          p_ble_evt->evt.gap_evt.params.conn_param_update.conn_params.min_conn_interval,
199                          p_ble_evt->evt.gap_evt.params.conn_param_update.conn_params.max_conn_interval,
200                          p_ble_evt->evt.gap_evt.params.conn_param_update.conn_params.slave_latency,
201                          p_ble_evt->evt.gap_evt.params.conn_param_update.conn_params.conn_sup_timeout
202                          );
203             break;
204             
205         default:
206           
207             break;
208     }
209 }

2.8.4 实验总结

这一章的实验,想要交给大家的内容是如何配置连接参数。需要了解的重点如下:

1、了解连接参数的含义,明白连接参数越小,通信的速率越快。

2、了解主从机之间的连接参数的配置流程。

3、了解从机如何申请更新连接参数,ble_conn_params_init()函数。

4、了解主机如何更新连接参数,sd_ble_gap_conn_param_update()函数。

2.9 MTU更新实验

2.9.1 实验简介

2.9.2 实验现象

2.9.3 工程及源码讲解

2.9.4 实验总结

2.10 NUS服务获取实验

2.10.1 实验简介

2.10.2 实验现象

2.10.3 工程及源码讲解

2.10.4 实验总结

2.11 NUS通信实验

2.11.1 实验简介

2.11.2 实验现象

2.11.3 工程及源码讲解

2.11.4 实验总结