打开主菜单

谷雨文档中心 β

NRF52832DK协议栈实验

Jinx讨论 | 贡献2019年7月9日 (二) 14:31的版本 实验简介

目录

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 实验简介

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

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

2.3.2 实验现象

2.3.3 工程及源码讲解

2.3.4 实验总结

2.4 过滤扫描实验

2.4.1 实验简介

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