“NRF52832DK协议栈实验”的版本间的差异
(→实验现象) |
(→工程说明) |
||
第1,310行: | 第1,310行: | ||
====== 工程说明 ====== | ====== 工程说明 ====== | ||
通用连接的从机工程,是在通用广播的从机工程的基础上修改的。 | 通用连接的从机工程,是在通用广播的从机工程的基础上修改的。 | ||
+ | |||
+ | 由于从机设备是一直保持广播,等待被连接的,所以相对于主机程序,我们就不用去找发起连接的函数以及对方设备的MAC了。因此我们只需要关注BLE协议的回调函数的事件返回。 | ||
+ | |||
+ | ====== ble_evt_handler()函数 ====== | ||
+ | 从机的连接与断开的状态和主机是一样的,能够获得的对方设备的信息也是一样的。 | ||
+ | |||
+ | 连接成功的状态BLE_GAP_EVT_CONNECTED,我们可以在这边获取到连接的设备的MAC、连接参数等等。 | ||
+ | |||
+ | 断开连接的状态BLE_GAP_EVT_DISCONNECTED,我们可以获取到断开的连接的原因等等。<syntaxhighlight lang="c" line="1" start="148"> | ||
+ | //****************************************************************** | ||
+ | // fn : ble_evt_handler | ||
+ | // | ||
+ | // brief : BLE事件回调 | ||
+ | // details : 包含以下几种事件类型:COMMON、GAP、GATT Client、GATT Server、L2CAP | ||
+ | // | ||
+ | // param : ble_evt_t 事件类型 | ||
+ | // p_context 未使用 | ||
+ | // | ||
+ | // return : none | ||
+ | static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context) | ||
+ | { | ||
+ | ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt; | ||
+ | ble_gap_evt_connected_t const * p_connected_evt = &p_gap_evt->params.connected; | ||
+ | switch (p_ble_evt->header.evt_id) | ||
+ | { | ||
+ | // 连接 | ||
+ | case BLE_GAP_EVT_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_connected_evt->peer_addr.addr), | ||
+ | p_gap_evt->conn_handle, | ||
+ | p_connected_evt->conn_params.min_conn_interval, | ||
+ | p_connected_evt->conn_params.max_conn_interval, | ||
+ | p_connected_evt->conn_params.slave_latency, | ||
+ | p_connected_evt->conn_params.conn_sup_timeout | ||
+ | ); | ||
+ | break; | ||
+ | // 断开连接 | ||
+ | case BLE_GAP_EVT_DISCONNECTED: | ||
+ | NRF_LOG_INFO("Disconnected. conn_handle: 0x%x, reason: 0x%04x", | ||
+ | p_gap_evt->conn_handle, | ||
+ | p_gap_evt->params.disconnected.reason); | ||
+ | break; | ||
+ | |||
+ | default: | ||
+ | // No implementation needed. | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
==== 实验总结 ==== | ==== 实验总结 ==== | ||
+ | 经过主从机通用连接的实验,我们需要弄懂的问题点如下: | ||
+ | |||
+ | 主机部分: | ||
+ | |||
+ | 1.主机部分一定要先发起扫描,当扫描到设备之后,再去发起连接。(不扫描直接发起连接,如果周围没有这个从设备,会进入异常,并只能RST恢复) | ||
+ | |||
+ | 2.主机发起连接的时候,是利用从机的MAC地址进行连接 | ||
+ | |||
+ | 3.连接以及断开连接的状态返回,可以获取对方设备信息以及断开原因 | ||
+ | |||
+ | 从机部分: | ||
+ | |||
+ | 1.从机是一直广播等待被动连接的设备 | ||
+ | |||
+ | 2.连接以及断开连接的状态返回,可以获取对方设备信息以及断开原因 | ||
=== 过滤连接实验 === | === 过滤连接实验 === | ||
==== 实验简介 ==== | ==== 实验简介 ==== | ||
+ | 过滤连接实验1.6_ble_central_conn_filter与2.6_ble_peripheral_conn_filter。 | ||
+ | |||
+ | 过滤连接实验是从过滤扫描的实验集成下来的,它的目的是和过滤扫描实验一样的,也是为了快速的从一堆BLE设备中,找到我们的设备,并与之快速建立连接。 | ||
==== 实验现象 ==== | ==== 实验现象 ==== | ||
+ | 主机上电后先打印实验名称1.6_ble_central_conn_filter,然后发起扫描,如果扫描到符合过滤的从机设备,则会打印扫描到的设备信息,然后发起连接,连接成功后会打印连接的一些信息。 | ||
+ | [[文件:Nrf rtt 16.png|边框|居中|无框|656x656像素]] | ||
+ | 从机上电后先打印实验名称2.6_ble_peripheral_conn_filter,如果被主机扫描连接之后,则会打印连接的信息。 | ||
+ | [[文件:Nrf rtt 26.png|边框|居中|无框|656x656像素]] | ||
==== 工程及源码讲解 ==== | ==== 工程及源码讲解 ==== | ||
+ | |||
+ | ===== 主机部分 ===== | ||
+ | |||
+ | ====== 工程说明 ====== | ||
+ | 过 | ||
+ | |||
+ | ===== 从机部分 ===== | ||
+ | 从机部分就不给大家介绍了,和前面的过滤广播的例程是重复的,仅修改了打印连接的对方设备的信息的部分。 | ||
==== 实验总结 ==== | ==== 实验总结 ==== |
2019年7月11日 (四) 17: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打印实验1.1_ble_central_log与2.1_ble_peripheral_log,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 实验简介
通用扫描实验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设备(不做扫描限制)。
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。
从机部分,可以看到上电先打印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地址,去限制扫描。
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后将打印他的广播和扫描回调数据、以及型号强度。
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 // 连接参数
|
2.6.3 工程及源码讲解
2.6.3.1 主机部分
2.6.3.1.1 工程说明
通用连接主机工程,是在通用扫描主机工程的基础上,新增了连接的过程,主要的修改集中在扫描事件处理函数,以及ble协议栈事件处理函数。
2.6.3.1.2 scan_evt_handler()函数
我们可以看到,在扫描回调事件处理的函数中,前面一部分还是和之前一样的,就是获取扫描到的从机设备的信息。
在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 工程说明
过
2.7.3.2 从机部分
从机部分就不给大家介绍了,和前面的过滤广播的例程是重复的,仅修改了打印连接的对方设备的信息的部分。