打开主菜单

谷雨文档中心 β

NRF52832DK协议栈高级实验

Jinx讨论 | 贡献2019年8月20日 (二) 17:35的版本 实验现象

目录

1 实验简介

工程编号 实验名称 工程简介
3.0_ble_peripheral_profile_led LED控制实验 实现了LED服务,可以通过手机控制LED灯的点亮和熄灭
3.1_ble_peripheral_profile_btn 按键控制实验 实现了按键服务,可以将按键按下的状态发送给手机
3.2_ble_peripheral_beacon 基站实验 BEACON格式广播
3.3_ble_peripheral_nus 从机NUS实验(串口透传) 实现了NUS服务,可以实现主从机的串口透传
3.4_ble_central_nus 主机NUS实验(串口透传) 实现了NUS服务,可以实现主从机的串口透传
3.5_ble_peripheral_multi 一从多主实验 一个从机支持被多个主机连接
3.6_ble_central_multi 一主多从实验 一个主机支持连接多个从机
3.7_ble_central_peripheral 主从一体实验 异常

2 LED控制实验

2.1 实验简介

我们的蓝牙实战实验将从蓝牙控制IO输出高低电平,以此来控制开发板上LED点亮和熄灭开始。

在这个实验中,我们主要会展示一下,有关特征值的write以及read属性,其中由于read属性相对而言使用的较少。我们主要会给大家介绍write属性,也就是主机给从机发送数据的属性。

2.2 硬件说明

nRF52DK开发板采用与Nordic官方开发板相同的指示灯电路,原则协议栈例程可以直接控制指示灯,无需修改代码。

指示灯与芯片引脚对应关系如下表格。

网络标号 芯片引脚号 连接方式
LED1 P0.17 直连,低电平亮灯
LED2 P0.18 直连,低电平亮灯
LED3 P0.19 直连,低电平亮灯
LED4 P0.20 直连,低电平亮灯
 

2.3 实验现象

手机端通过nordic的app"nrf master control panel",发起对设备的扫描和连接,连接成功之后,我们通过UUID FFF1给开发板发送数据。

例如发送0x00,0x00,0x00,0x00给开发板,此时开发板的4个LED灯均被点亮;同样的我们给将某位数据改成0x01,对应的LED就会熄灭。

2.4 源码讲解

2.4.1 gy_profile_led.c\.h

我们首先查看一下他的服务文件,也就是gy_profile_led,我们我们就是通过这个服务来接收手机端发送的LED控制数据的。

可以看到这个服务初始化ble_led_init函数中对于服务以及他的特征值属性的初始化过程,首先我们先初始化一个回调(p_led->led_write_handler = p_led_init->led_write_handler;),这个回调是用来将gy_profile_led这一层的数据,上传给mian文件去处理。

接下来是服务的添加,首先是调用sd_ble_uuid_vs_add去添加服务,然后给这个ble_uuid的服务的参数赋值,最后调用sd_ble_gatts_service_add函数去注册这个服务,这边我们注册的服务句柄是p_led->service_handle。

注册完服务之后,我们就要开始添加我们的特征值characteristic,特征值的添加也是一样的,首先配置特征值的参数,这些参数中我们主要关注一下ble_gatt_char_props_t,这个参数是用来定义特征值的属性的,可以看到我们的属性有如下几种:

/**@brief GATT Characteristic Properties. */
typedef struct
{
  /* Standard properties */
  uint8_t broadcast       :1; /**< 广播 */
  uint8_t read            :1; /**< 读 */
  uint8_t write_wo_resp   :1; /**< 写指令 */
  uint8_t write           :1; /**< 写*/
  uint8_t notify          :1; /**< 通知*/
  uint8_t indicate        :1; /**< 暗示 */
  uint8_t auth_signed_wr  :1; /**< 签名写指令 */
} ble_gatt_char_props_t;

因为我们的led的服务,是手机写数据给开发板控制LED,所以我们要定义特征值写使能(add_char_params.char_props.write = 1;),另外当我们需要知道上次是发送的什么控制数据给开发板时,我们需要读一下数据,所以这边我们同样定义一下读使能(add_char_params.char_props.read = 1;),当我们配置完特征值的属性之后,我们调用characteristic_add函数,去向刚刚注册的p_led->service_handle服务中添加我们的特征值。

53 //******************************************************************************
54 // fn :ble_led_init
55 //
56 // brief : 初始化LED服务
57 //
58 // param : p_led -> led服务结构体
59 //         p_led_init -> led服务初始化结构体
60 //
61 // return : uint32_t -> 成功返回SUCCESS,其他返回ERR NO.
62 uint32_t ble_led_init(ble_led_t * p_led, const ble_led_init_t * p_led_init)
63 {
64     uint32_t              err_code;
65     ble_uuid_t            ble_uuid;
66     ble_add_char_params_t add_char_params;
67 
68     // 初始化服务结构体
69     p_led->led_write_handler = p_led_init->led_write_handler;
70 
71     // 添加服务(128bit UUID)
72     ble_uuid128_t base_uuid = {LED_UUID_BASE};
73     err_code = sd_ble_uuid_vs_add(&base_uuid, &p_led->uuid_type);
74     VERIFY_SUCCESS(err_code);
75 
76     ble_uuid.type = p_led->uuid_type;
77     ble_uuid.uuid = LED_UUID_SERVICE;
78 
79     err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_led->service_handle);
80     VERIFY_SUCCESS(err_code);
81 
82     // 添加LED特征值(属性是Write和Read、长度是4)
83     memset(&add_char_params, 0, sizeof(add_char_params));
84     add_char_params.uuid             = LED_UUID_CHAR;
85     add_char_params.uuid_type        = p_led->uuid_type;
86     add_char_params.init_len         = LED_UUID_CHAR_LEN;
87     add_char_params.max_len          = LED_UUID_CHAR_LEN;
88     add_char_params.char_props.read  = 1;
89     add_char_params.char_props.write = 1;
90 
91     add_char_params.read_access  = SEC_OPEN;
92     add_char_params.write_access = SEC_OPEN;
93 
94     return characteristic_add(p_led->service_handle, &add_char_params, &p_led->led_char_handles);
95 }

看完服务的初始化,我们来看下我们的服务注册之后是怎么来进行工作的,首先我们看下ble_led_on_ble_evt这个函数,这个函数在我们mian函数中注册BLE_LED_DEF(m_led);实例的时候被引用。

15 //******************************************************************************
16 // fn :BLE_LED_DEF
17 //
18 // brief : 初始化LED服务实例
19 //
20 // param : _name -> 实例的名称
21 //
22 // return : none
23 #define BLE_LED_DEF(_name)                                                                          \
24 static ble_led_t _name;                                                                             \
25 NRF_SDH_BLE_OBSERVER(_name ## _obs,                                                                 \
26                      BLE_LED_BLE_OBSERVER_PRIO,                                                     \
27                      ble_led_on_ble_evt, &_name)

这个m_led实例注册,涉及到NRF_SDH_BLE_OBSERVER的使用,简单的理解就是利用这个注册了实例之后,当底层有GAP或者GATT消息返回的时候,就会触发ble_led_on_ble_evt函数。 因为我们LED服务这里需要接收手机端write的LED控制数据,所以我们在事件判断中,判断是否出现GATT Write事件,一旦出现了,我们调用on_write函数去处理这个事件。

28 //******************************************************************************
29 // fn :ble_led_on_ble_evt
30 //
31 // brief : BLE事件处理函数
32 //
33 // param : p_ble_evt -> ble事件
34 //         p_context -> ble事件处理程序的参数(暂时理解应该是不同的功能,注册时所携带的结构体参数)
35 //
36 // return : none
37 void ble_led_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
38 {
39     ble_led_t * p_led = (ble_led_t *)p_context;
40 
41     switch (p_ble_evt->header.evt_id)
42     {
43         // GATT Client Write事件
44         case BLE_GATTS_EVT_WRITE:
45             on_write(p_led, p_ble_evt);
46             break;
47 
48         default:
49             break;
50     }
51 }

on_write函数用于处理接收的write数据,我们判断一下接收的数据是否符合我们的要求,如果符合,那么我们通过初始化函数中的回调函数,将接收到的值上传到main函数中去处理。

 7 //******************************************************************************
 8 // fn :on_write
 9 //
10 // brief : 处理Write事件的函数。
11 //
12 // param : p_led -> led服务结构体
13 //         p_ble_evt -> ble事件
14 //
15 // return : none
16 static void on_write(ble_led_t * p_led, ble_evt_t const * p_ble_evt)
17 {
18     ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
19 
20     if (   (p_evt_write->handle == p_led->led_char_handles.value_handle)
21         && (p_evt_write->len <= LED_UUID_CHAR_LEN)
22         && (p_led->led_write_handler != NULL))
23     {
24         p_led->led_write_handler((uint8_t*)p_evt_write->data);
25     }
26 }
2.4.2 gy_serial_led.c\.h

有关外设处理,请大家查看基础实验部分

2.4.3 main.c

main文件中也不给大家全部介绍了,这个和蓝牙协议实验部分是重合的,我们只关注实验改动的部分。

我们看下服务初始化的部分,可以看到调用了我们gy_profile_led中的ble_led_init函数初始化注册了我们的LED服务,并且通用注册了一个回调函数。

在这个回调函数led_write_handler中,我们可以获取到gy_profile_led中上传上来的接收到的wirte数据,并且利用这个数据进行LED的控制。

195 //******************************************************************
196 // fn : nus_data_handler
197 //
198 // brief : 用于处理来自Nordic UART服务的数据的功能
199 // details : 该功能将处理从Nordic UART BLE服务接收的数据并将其发送到UART模块
200 //
201 // param : ble_nus_evt_t -> nus事件
202 //
203 // return : none
204 static void led_write_handler(uint8_t * new_state)
205 {
206     NRF_LOG_INFO("Recive State:%02X,%02X,%02X,%02X",new_state[0],new_state[1],new_state[2],new_state[3]);
207     LED_Control(BSP_LED_0, new_state[0]);
208     LED_Control(BSP_LED_1, new_state[1]);
209     LED_Control(BSP_LED_2, new_state[2]);
210     LED_Control(BSP_LED_3, new_state[3]);
211 }
212 
213 //******************************************************************
214 // fn : services_init
215 //
216 // brief : 初始化复位(本例程展示NUS:Nordic Uart Service)
217 //
218 // param : none
219 //
220 // return : none
221 static void services_init(void)
222 {
223     uint32_t           err_code;
224     ble_led_init_t     led_init;
225 
226     // Initialize NUS.
227     memset(&led_init, 0, sizeof(led_init));
228 
229     led_init.led_write_handler = led_write_handler;
230 
231     err_code = ble_led_init(&m_led, &led_init);
232     APP_ERROR_CHECK(err_code);
233 }

3 按键控制实验

3.1 实验简介

按键控制实验,就是开发板上的按键按下,然后我们将通过蓝牙上报手机是哪一个按键被按下。

在上一个LED控制实验中,我们主要讲了Write属性,也就是主机给从机发送数据。在这个实验中,我们将会给大家展示从机如何给主机发送数据,也就是notify属性的使用。

3.2 硬件说明

nRF52DK开发板采用与Nordic官方开发板相同的按键电路,原则协议栈例程可以直接使用按键,无需修改代码。

按键与芯片引脚对应关系如下表格。

网络标号 芯片引脚号 连接方式
BTN1 P0.13 直连,下降沿(低电平)触发
BTN2 P0.14 直连,下降沿(低电平)触发
BTN3 P0.15 直连,下降沿(低电平)触发
BTN4 P0.16 直连,下降沿(低电平)触发
 

3.3 实验现象

首先还是使用nordic的app "nrf master control panel"去连接我们的开发板,连接成功之后,我们点击UUID FFE1特征值的使用notify的按钮(三个向下箭头的图标)。

使能了notify之后,我们分别按下开发板上的按键S1-S4,可以看到手机上显示对应的上报状态数据。

3.4 源码讲解

3.4.1 gy_profile_btn.c\.h

首先我们还是先看一下服务配置文件,首先还是注册一下服务,注册的服务句柄是p_btn->service_handle。服务注册完成之后,我们注册按键的特征值,可以看到我们分别使能了按键的notify通知属性(add_char_params.char_props.notify = 1;),并且同样使能了read属性(add_char_params.char_props.read = 1;)。

这里我们需要注意的是下面的cccd_write_access参数被使能,上一实验大家都好理解需要使能write_access和read_access,因为我们需要使用读写,那么为什么这个例程要使能cccd_write_access呢,如果大家了解什么是CCCD,那么其实也很简单,

50 //******************************************************************************
51 // fn :ble_btn_init
52 //
53 // brief : 初始化BTN服务
54 //
55 // param : p_btn -> btn服务结构体
56 //
57 // return : uint32_t -> 成功返回SUCCESS,其他返回ERR NO.
58 uint32_t ble_btn_init(ble_btn_t * p_btn)
59 {
60     uint32_t              err_code;
61     ble_uuid_t            ble_uuid;
62     ble_add_char_params_t add_char_params;
63 
64     // 添加服务(128bit UUID)
65     ble_uuid128_t base_uuid = {BTN_UUID_BASE};
66     err_code = sd_ble_uuid_vs_add(&base_uuid, &p_btn->uuid_type);
67     VERIFY_SUCCESS(err_code);
68 
69     ble_uuid.type = p_btn->uuid_type;
70     ble_uuid.uuid = BTN_UUID_SERVICE;
71 
72     err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_btn->service_handle);
73     VERIFY_SUCCESS(err_code);
74 
75     // 添加BTN特征值(属性是Write和Read、长度是4)
76     memset(&add_char_params, 0, sizeof(add_char_params));
77     add_char_params.uuid             = BTN_UUID_CHAR;
78     add_char_params.uuid_type        = p_btn->uuid_type;
79     add_char_params.init_len         = BTN_UUID_CHAR_LEN;
80     add_char_params.max_len          = BTN_UUID_CHAR_LEN;
81     add_char_params.char_props.read  = 1;
82     add_char_params.char_props.notify = 1;
83     
84     add_char_params.read_access  = SEC_OPEN;
85     add_char_params.cccd_write_access = SEC_OPEN;
86 
87     return characteristic_add(p_btn->service_handle, &add_char_params, &p_btn->btn_char_handles);
88 }

4 基站实验

4.1 实验简介

4.2 实验现象

4.3 源码讲解

5 从机NUS实验

5.1 实验简介

5.2 实验现象

5.3 源码讲解

6 主机NUS实验

6.1 实验简介

6.2 实验现象

6.3 源码讲解

7 一主多从实验

7.1 实验简介

7.2 实验现象

7.3 源码讲解

8 一从多主实验

8.1 实验简介

8.2 实验现象

8.3 源码讲解