打开主菜单

谷雨文档中心 β

NRF52832DK协议栈实验

Jinx讨论 | 贡献2019年7月9日 (二) 16:32的版本 工程说明

目录

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打印实验是在低功耗实验的基础上,新增了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 实验简介

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

也就是从这一实验开始,我们才是真正进入到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 实验简介

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

上面的疑问答案是肯定的,因为我们在扫描的时候,可以先判断一下扫描到的广播数据或者扫描回调数据,这样就可以找到我们的设备。nordic对这部分处理的很细心,

2.4.2 实验现象

2.4.3 工程及源码讲解

2.4.4 实验总结

2.5 白名单扫描实验

2.5.1 实验简介

2.5.2 实验现象

2.5.3 工程及源码讲解

2.5.4 实验总结

2.6 通用连接实验

2.6.1 实验简介

2.6.2 实验现象

2.6.3 工程及源码讲解

2.6.4 实验总结

2.7 过滤连接实验

2.7.1 实验简介

2.7.2 实验现象

2.7.3 工程及源码讲解

2.7.4 实验总结

2.8 连接参数更新实验

2.8.1 实验简介

2.8.2 实验现象

2.8.3 工程及源码讲解

2.8.4 实验总结

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 实验总结