NRF52832DK协议栈实验
目录
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多次,才会出现一次异常)
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功能了。
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设备(不做扫描限制)。
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对这部分处理的很细心,