NRF52832DK协议栈高级实验
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。
Client Characteristic Configuration Descriptor(CCCD)是客户端特征配置描述符。当主机向CCCD中写入0x0001,此时使能notify;当写入0x0000时,此时禁止notify。
在nordic的协议栈当中,他的这个notify使能是交给用户自己处理的,也是说即便主机没有向cccd中写入0x0001去使能notify,我们同样可以直接利用notify去发送数据,只能这样不符合规范。 |
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 }
服务部分剩下的处理流程和上一实验是类型的,只不过上一个实验是处理的wirte属性,而这个实验是处理notify属性。 首先在BLE事件处理的函数中,本来我们应该要处理CCCD_Write的数据的,但是这边我们偷懒了,不去做处理,验证一下主机不发送数据使能notify,我们也可以通过notify发送数据出去。
31 //******************************************************************************
32 // fn :ble_led_on_ble_evt
33 //
34 // brief : BLE事件处理函数
35 //
36 // param : p_ble_evt -> ble事件
37 // p_context -> ble事件处理程序的参数(暂时理解应该是不同的功能,注册时所携带的结构体参数)
38 //
39 // return : none
40 void ble_led_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
41 {
42 switch (p_ble_evt->header.evt_id)
43 {
44
45 default:
46 break;
47 }
48 }
下面这个函数,就是我们notify发送数据的函数,他的参数我们只需要配置4个。
type配置为BLE_GATT_HVX_NOTIFICATION,代表是notify属性的数据;
handle我们需要配置为我们按键特征值的value.handle,代表的是按键特征值的Value这个列表的句柄;
剩下的p_data和p_len就是我们需要发送的数据以及数据的长度。
7 //******************************************************************************
8 // fn :ble_lbs_on_button_change
9 //
10 // brief : 处理按键按下,状态更新的事件
11 //
12 // param : conn_handle -> 连接的句柄
13 // p_btn -> btn结构体
14 // button_state -> 按键状态
15 //
16 // return : none
17 uint32_t ble_lbs_on_button_change(uint16_t conn_handle, ble_btn_t * p_btn, uint8_t *button_state)
18 {
19 ble_gatts_hvx_params_t params;
20 uint16_t len = BTN_UUID_CHAR_LEN;
21
22 memset(¶ms, 0, sizeof(params));
23 params.type = BLE_GATT_HVX_NOTIFICATION;
24 params.handle = p_btn->btn_char_handles.value_handle;
25 params.p_data = button_state;
26 params.p_len = &len;
27
28 return sd_ble_gatts_hvx(conn_handle, ¶ms);
29 }
3.4.2 gy_serial_btn.c\.h
按键的外设处理驱动文件,这个详细的说明也是请大家查看基础实验部分的,这里我们只关注一下按键被触发,然后通过notify函数,去向手机发送通知。
41 //******************************************************************
42 // fn : btn_timeout_handler
43 //
44 // brief : 按键定时器超时任务
45 //
46 // param : p_event -> 指向数据库发现事件的指针
47 //
48 // return : none
49 static void btn_timeout_handler(void * p_context)
50 {
51 uint8_t btnStateBuf[BTN_UUID_CHAR_LEN] = {0};
52
53 if(nrf_drv_gpiote_in_is_set(BUTTON_1) == 0)
54 {
55 NRF_LOG_INFO("BTN1");
56 btnStateBuf[0] = 0x01;
57 btnState = 1;
58 }
59
60 if(nrf_drv_gpiote_in_is_set(BUTTON_2) == 0)
61 {
62 NRF_LOG_INFO("BTN2");
63 btnStateBuf[1] = 0x01;
64 btnState = 1;
65 }
66
67 if(nrf_drv_gpiote_in_is_set(BUTTON_3) == 0)
68 {
69 NRF_LOG_INFO("BTN3");
70 btnStateBuf[2] = 0x01;
71 btnState = 1;
72 }
73
74 if(nrf_drv_gpiote_in_is_set(BUTTON_4) == 0)
75 {
76 NRF_LOG_INFO("BTN4");
77 btnStateBuf[3] = 0x01;
78 btnState = 1;
79 }
80
81 if(btnState)
82 {
83 ble_lbs_on_button_change(0x0000, &m_btn, btnStateBuf);
84 }
85 btnState = 0;
86 }
3.5 main.c
由于这个例程的按键通知我们是放到了按键驱动文件中处理,所以在main函数当中我们只需要去初始化一下这个服务就可以了。
194 //******************************************************************
195 // fn : services_init
196 //
197 // brief : 初始化复位(本例程展示NUS:Nordic Uart Service)
198 //
199 // param : none
200 //
201 // return : none
202 static void services_init(void)
203 {
204 uint32_t err_code;
205
206 err_code = ble_btn_init(&m_btn);
207 APP_ERROR_CHECK(err_code);
208 }
4 基站(信标)实验
4.1 实验简介
无线技术中的信标是广播小块信息的概念。 信息可以是任何东西,范围从环境数据(温度,气压,湿度等)到微位置数据(资产跟踪,零售等)或方向数据(加速,旋转等)。
传输的数据通常是静态的,但也可以是动态的并随时间变化。 通过使用蓝牙低功耗,可以将信标设计为在单个纽扣电池上运行多年。 本应用报告介绍了信标的概念以及如何开始实施信标解决方案。 本文档中的命名约定可以概括为使用蓝牙低能耗技术广告来广播信息的信标,该技术可被称为蓝牙低功耗。
4.2 实验现象
4.3 源码讲解
首先我们先定义beacon相关的数据,其中我们用户需要关注的主要有3个参数,UUID、Major以及Minor,其他的参数大家可以理解为固定的格式(格式固定,但数据内容不固定,可能有不同的厂商信息)。另外还有一个值得我们关注的数据,那就是APP_COMPANY_IDENTIFIER,如果我们定义此参数为0x004C(也就是Apple id),那么我们的基站设备就被成为iBeacon。
54 // BEACON数据
55 #define APP_BEACON_INFO_LENGTH 0x17 // BEACON数据总长度
56 #define APP_ADV_DATA_LENGTH 0x15 // BEACON特殊字节的长度
57 #define APP_DEVICE_TYPE 0x02 // 字节0x02代表这个设备是BEACON
58 #define APP_MEASURED_RSSI 0xC3 // BEACON在1米距离处的信号强度
59 #define APP_COMPANY_IDENTIFIER 0x004C // 004C代表的是Apple id(只有这个ID,设备才会叫iBeacon)
60 #define APP_MAJOR_VALUE 0x27, 0x12 // major
61 #define APP_MINOR_VALUE 0x0B, 0x86 // minor
62 #define APP_BEACON_UUID 0xFD,0xA5,0x06,0x93, \
63 0xA4,0xE2,0x4F,0xB1, \
64 0xAF,0xCF,0xC6,0xEB, \
65 0x07,0x64,0x78,0x25 // UUID
66
67 // BEACON数据数组,用于初始化广播数据内容
68 static uint8_t m_beacon_info[APP_BEACON_INFO_LENGTH] =
69 {
70 APP_DEVICE_TYPE,
71 APP_ADV_DATA_LENGTH,
72 APP_BEACON_UUID,
73 APP_MAJOR_VALUE,
74 APP_MINOR_VALUE,
75 APP_MEASURED_RSSI
76 };
定义好基站的数据之后,我们需要把它添加到广播数据中,那么这边唯一的疑问的就是我们应该把基站数据作为什么type来添加。 从下面的代码中,我们不难看出,基站的数据是作为p_manuf_specific_data(也就是用户自定义)数据来添加的,在Beacon数据添加的时候,大家可以看到只有company_identifier是被独立出来配置的,这里我们将其配置为apple id,所以最终我们的设备类型会是iBeacon。
110 //******************************************************************
111 // fn : advertising_init
112 //
113 // brief : 用于初始化广播
114 //
115 // param : none
116 //
117 // return : none
118 static void advertising_init(void)
119 {
120 uint32_t err_code;
121 ble_advertising_init_t init;
122 ble_advdata_manuf_data_t manuf_specific_data;
123
124 memset(&init, 0, sizeof(init));
125
126 // 扫描回调数据包含短设备名称(方便大家看app显示BEACON标志)
127 init.srdata.name_type = BLE_ADVDATA_SHORT_NAME;
128 init.srdata.short_name_len = 9;
129
130 // 配置广播数据的flag,设置为beacon,配置为BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED
131 init.advdata.flags = BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE|BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED;
132
133 // 配置广播数据中的BEACON数据
134 manuf_specific_data.company_identifier = APP_COMPANY_IDENTIFIER;
135 manuf_specific_data.data.p_data = (uint8_t *) m_beacon_info;
136 manuf_specific_data.data.size = APP_BEACON_INFO_LENGTH;
137 init.advdata.p_manuf_specific_data = &manuf_specific_data;
138
139 // 配置广播周期,40ms快速广播,超时参数配置为0代表一直广播
140 init.config.ble_adv_fast_enabled = true;
141 init.config.ble_adv_fast_interval = 64; // 64*0.625 = 40ms
142 init.config.ble_adv_fast_timeout = 0; // 1800*10ms = 18s
143
144 err_code = ble_advertising_init(&m_advertising, &init);
145 APP_ERROR_CHECK(err_code);
146
147 ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
148 }
5 从机NUS实验
5.1 实验简介
NUS(Nordic Uart Service)实验,他本质上是和开始的LED与按键的实验是一样的,都是新建一个服务文件,注册好我们需要使用的特征值功能,然后传输数据。不过是要比他们稍微复杂一点,因为我们串口的数据是涉及到收发两个部分(也就是既有write也有notify),所以我们在注册特征值的时候,是要注册两个,一个负责write,一个负责notify。
剩下的就是串口外设的处理,我们这边对于串口驱动不做介绍,大家可以去看基础实验部分的说明。我们在这个实验中仅给大家讲解串口RX收到的数据如何通知(notify)给手机,手机下发(write)的数据如何通过串口TX打印显示出来。
5.2 硬件说明
串口一向是所有开发板必需的功能之一,nRF52DK开发板采用CH340C芯片,将芯片的UART信号转换为USB接口信号,方便电脑使用USB来虚拟一个串口调试。
CH340C外围电路简单,且不需要外部晶振,nRF52DK开发板采用与Nordic官方开发板相同的串口收发引脚,如下表格所示。
网络标号 | 芯片引脚号 | 连接方式 |
---|---|---|
CH340_TX | P0.08(nRF_RX信号) | 通过SW1拨动开关,选择连接 |
CH340_RX | P0.06(nRF_TX信号) | 通过SW1拨动开关,选择连接 |
图中P5引出了CH340C的流控制引脚,但是没有连接任何线路,所需要使用流控制,可自行连接测试。
5.3 实验现象
5.4 源码讲解
5.4.1 ble_nus.c\.h
先看下nus服务的初始化函数,首先我们还是先初始化了一个回调p_nus_init->data_handler,这个回调用于将nus层的数据传递给mian文件使用。
接下来我们初始化nus服务,还是和以前一样的,经过给参数的赋值,最终我们调用sd_ble_gatts_service_add函数注册了一个句柄为p_nus->service_handle的服务。
最后我们来注册我们需要使用的特征值characteristic,这边我们需要注册两个,一个是TX(发送),一个是RX(接收)。
首先我们看一下RX特征值的添加,我们配置他的属性是write,因为手机(主机)发送数据是通过write的方式;接下来我们看下TX特征值,我们做为从机设备,发送数据给手机(主机)使用的是notify方式,我们依次将这两个特征值添加到服务当中。
248 uint32_t ble_nus_init(ble_nus_t * p_nus, ble_nus_init_t const * p_nus_init)
249 {
250 ret_code_t err_code;
251 ble_uuid_t ble_uuid;
252 ble_uuid128_t nus_base_uuid = NUS_BASE_UUID;
253 ble_add_char_params_t add_char_params;
254
255 VERIFY_PARAM_NOT_NULL(p_nus);
256 VERIFY_PARAM_NOT_NULL(p_nus_init);
257
258 // Initialize the service structure.
259 p_nus->data_handler = p_nus_init->data_handler;
260
261 /**@snippet [Adding proprietary Service to the SoftDevice] */
262 // Add a custom base UUID.
263 err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type);
264 VERIFY_SUCCESS(err_code);
265
266 ble_uuid.type = p_nus->uuid_type;
267 ble_uuid.uuid = BLE_UUID_NUS_SERVICE;
268
269 // Add the service.
270 err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
271 &ble_uuid,
272 &p_nus->service_handle);
273 /**@snippet [Adding proprietary Service to the SoftDevice] */
274 VERIFY_SUCCESS(err_code);
275
276 // Add the RX Characteristic.
277 memset(&add_char_params, 0, sizeof(add_char_params));
278 add_char_params.uuid = BLE_UUID_NUS_RX_CHARACTERISTIC;
279 add_char_params.uuid_type = p_nus->uuid_type;
280 add_char_params.max_len = BLE_NUS_MAX_RX_CHAR_LEN;
281 add_char_params.init_len = sizeof(uint8_t);
282 add_char_params.is_var_len = true;
283 add_char_params.char_props.write = 1;
284 add_char_params.char_props.write_wo_resp = 1;
285
286 add_char_params.read_access = SEC_OPEN;
287 add_char_params.write_access = SEC_OPEN;
288
289 err_code = characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->rx_handles);
290 if (err_code != NRF_SUCCESS)
291 {
292 return err_code;
293 }
294
295 // Add the TX Characteristic.
296 /**@snippet [Adding proprietary characteristic to the SoftDevice] */
297 memset(&add_char_params, 0, sizeof(add_char_params));
298 add_char_params.uuid = BLE_UUID_NUS_TX_CHARACTERISTIC;
299 add_char_params.uuid_type = p_nus->uuid_type;
300 add_char_params.max_len = BLE_NUS_MAX_TX_CHAR_LEN;
301 add_char_params.init_len = sizeof(uint8_t);
302 add_char_params.is_var_len = true;
303 add_char_params.char_props.notify = 1;
304
305 add_char_params.read_access = SEC_OPEN;
306 add_char_params.write_access = SEC_OPEN;
307 add_char_params.cccd_write_access = SEC_OPEN;
308
309 return characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->tx_handles);
310 /**@snippet [Adding proprietary characteristic to the SoftDevice] */
311 }
看完nus服务初始化之后,我们需要来看下ble_nus_on_ble_evt函数,这个函数在LED实验中有过说明,它的注册是在main文件中,我们注册nus实例的时候注册好的,功能是用来接收底层返回的GAP与GATT事件。 从代码中可以看到,我们这个实验需要接收的底层返回有3个,分别是:连接成功的GAP事件(BLE_GAP_EVT_CONNECTED)、write属性的GATT事件(BLE_GATTS_EVT_WRITE)以及notify通知传输完成的事件(BLE_GATTS_EVT_HVN_TX_COMPLETE)。
218 void ble_nus_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
219 {
220 if ((p_context == NULL) || (p_ble_evt == NULL))
221 {
222 return;
223 }
224
225 ble_nus_t * p_nus = (ble_nus_t *)p_context;
226
227 switch (p_ble_evt->header.evt_id)
228 {
229 case BLE_GAP_EVT_CONNECTED:
230 on_connect(p_nus, p_ble_evt);
231 break;
232
233 case BLE_GATTS_EVT_WRITE:
234 on_write(p_nus, p_ble_evt);
235 break;
236
237 case BLE_GATTS_EVT_HVN_TX_COMPLETE:
238 on_hvx_tx_complete(p_nus, p_ble_evt);
239 break;
240
241 default:
242 // No implementation needed.
243 break;
244 }
245 }
首先我们看下GAP连接成功事件,我们处理了哪些消息,在连接成功指令,我们首先是调用blcm_link_ctx_get函数去获取蓝牙连接的相关信息(这个暂时没有明白它的具体意义,因为获取到的p_client并没有被任何地方使用)。 然后我们调用sd_ble_gatts_value_get函数去获取CCCD中的gatts_val数据,然后我们调用ble_srv_is_notification_enabled对这个数据进行判断,如果得到的gatts_val数据是0x0001,那么代表了主机已经使能了我们从机的notify功能,那么我们通过p_nus->data_handler向mian文件传递BLE_NUS_EVT_COMM_STARTED事件。
72 static void on_connect(ble_nus_t * p_nus, ble_evt_t const * p_ble_evt)
73 {
74 ret_code_t err_code;
75 ble_nus_evt_t evt;
76 ble_gatts_value_t gatts_val;
77 uint8_t cccd_value[2];
78 ble_nus_client_context_t * p_client = NULL;
79
80 err_code = blcm_link_ctx_get(p_nus->p_link_ctx_storage,
81 p_ble_evt->evt.gap_evt.conn_handle,
82 (void *) &p_client);
83 if (err_code != NRF_SUCCESS)
84 {
85 NRF_LOG_ERROR("Link context for 0x%02X connection handle could not be fetched.",
86 p_ble_evt->evt.gap_evt.conn_handle);
87 }
88
89 /* Check the hosts CCCD value to inform of readiness to send data using the RX characteristic */
90 memset(&gatts_val, 0, sizeof(ble_gatts_value_t));
91 gatts_val.p_value = cccd_value;
92 gatts_val.len = sizeof(cccd_value);
93 gatts_val.offset = 0;
94
95 err_code = sd_ble_gatts_value_get(p_ble_evt->evt.gap_evt.conn_handle,
96 p_nus->rx_handles.cccd_handle,
97 &gatts_val);
98
99 if ((err_code == NRF_SUCCESS) &&
100 (p_nus->data_handler != NULL) &&
101 ble_srv_is_notification_enabled(gatts_val.p_value))
102 {
103 if (p_client != NULL)
104 {
105 p_client->is_notification_enabled = true;
106 }
107
108 memset(&evt, 0, sizeof(ble_nus_evt_t));
109 evt.type = BLE_NUS_EVT_COMM_STARTED;
110 evt.p_nus = p_nus;
111 evt.conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
112 evt.p_link_ctx = p_client;
113
114 p_nus->data_handler(&evt);
115 }
116 }
接下来我们查看一下
124 static void on_write(ble_nus_t * p_nus, ble_evt_t const * p_ble_evt)
125 {
126 ret_code_t err_code;
127 ble_nus_evt_t evt;
128 ble_nus_client_context_t * p_client;
129 ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
130
131 err_code = blcm_link_ctx_get(p_nus->p_link_ctx_storage,
132 p_ble_evt->evt.gatts_evt.conn_handle,
133 (void *) &p_client);
134 if (err_code != NRF_SUCCESS)
135 {
136 NRF_LOG_ERROR("Link context for 0x%02X connection handle could not be fetched.",
137 p_ble_evt->evt.gatts_evt.conn_handle);
138 }
139
140 memset(&evt, 0, sizeof(ble_nus_evt_t));
141 evt.p_nus = p_nus;
142 evt.conn_handle = p_ble_evt->evt.gatts_evt.conn_handle;
143 evt.p_link_ctx = p_client;
144
145 if ((p_evt_write->handle == p_nus->tx_handles.cccd_handle) &&
146 (p_evt_write->len == 2))
147 {
148 if (p_client != NULL)
149 {
150 if (ble_srv_is_notification_enabled(p_evt_write->data))
151 {
152 p_client->is_notification_enabled = true;
153 evt.type = BLE_NUS_EVT_COMM_STARTED;
154 }
155 else
156 {
157 p_client->is_notification_enabled = false;
158 evt.type = BLE_NUS_EVT_COMM_STOPPED;
159 }
160
161 if (p_nus->data_handler != NULL)
162 {
163 p_nus->data_handler(&evt);
164 }
165
166 }
167 }
168 else if ((p_evt_write->handle == p_nus->rx_handles.value_handle) &&
169 (p_nus->data_handler != NULL))
170 {
171 evt.type = BLE_NUS_EVT_RX_DATA;
172 evt.params.rx_data.p_data = p_evt_write->data;
173 evt.params.rx_data.length = p_evt_write->len;
174
175 p_nus->data_handler(&evt);
176 }
177 else
178 {
179 // Do Nothing. This event is not relevant for this service.
180 }
181 }