打开主菜单

谷雨文档中心 β

更改

NRF52832DK协议栈实验

添加9,171字节2020年1月9日 (四) 10:48
实验现象
5、从机分别按下4个按键,给主机发送相应的notify数据包,控制主机LED点亮
[[文件:Nrf52832dk-ble-gattnotify1.png|边框|居中|无框|684x684像素695x695px]]
====工程及源码讲解====
===== 主机部分 =====
 
====== gy_profile_btn_c.c\.h及mian.c ======
这个实验主机部分的代码和上一章节的Write的主机是类似的,他的整个的服务发现流程是一样的,唯一不同的点是由于从机需要发送notify数据,所以我们的主机需要去使能从机的notify的功能。
 
所以我们来看一下主机是在哪边,以怎样的方式去使能从机的notify。以及最终是怎么接收来自从机的数据的。
 
首先看下使能notify的部分,通过上一实验的学习,我们已经知道当我们自己成功跑完获取服务的流程,最后会给main函数中的服务初始化的回调返回成功的事件。我们看下成功发现服务的事件BLE_BTN_C_EVT_DISCOVERY_COMPLETE,里面首先还是调用ble_btn_c_handles_assign函数将获取的句柄值和我们m_ble_btn_c实例绑定起来。然后就去调用ble_btn_c_tx_notif_enable函数去使能从机的notify功能。<syntaxhighlight lang="c" line="1" start="272">
//******************************************************************
// fn : ble_btn_c_evt_handler
//
// brief : BTN服务事件
//
// param : none
//
// return : none
static void ble_btn_c_evt_handler(ble_btn_c_t * p_ble_btn_c, ble_btn_c_evt_t * p_evt)
{
ret_code_t err_code;
 
switch (p_evt->evt_type)
{
case BLE_BTN_C_EVT_DISCOVERY_COMPLETE:
NRF_LOG_INFO("Discovery complete.");
err_code = ble_btn_c_handles_assign(&m_ble_btn_c, p_evt->conn_handle, &p_evt->params.peer_db);
APP_ERROR_CHECK(err_code);
NRF_LOG_INFO("Connected to device with Ghostyu BTN Service.");
err_code = ble_btn_c_tx_notif_enable(&m_ble_btn_c);
APP_ERROR_CHECK(err_code);
NRF_LOG_INFO("Enable notification.");
break;
 
</syntaxhighlight>我们来看下使能notify的函数的代码,不难发现其实就是一个write功能,不过不是上一个实验向我们的handle_value去发送数据,而是向cccd_handle去发送了一个0x01(BLE_GATT_HVX_NOTIFICATION),0x00的数据。<syntaxhighlight lang="c" line="1" start="179">
//******************************************************************************
// fn :cccd_configure
//
// brief : 用于向CCCD发送数据,控制使能或者禁能notify功能
//
// param : conn_handle -> 连接的句柄
// conn_handle -> CCCD的句柄
// enable -> 使能或者禁能标识
//
// return : none
static uint32_t cccd_configure(uint16_t conn_handle, uint16_t cccd_handle, bool enable)
{
uint8_t buf[BLE_CCCD_VALUE_LEN];
 
buf[0] = enable ? BLE_GATT_HVX_NOTIFICATION : 0;
buf[1] = 0;
 
ble_gattc_write_params_t const write_params =
{
.write_op = BLE_GATT_OP_WRITE_REQ,
.flags = BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE,
.handle = cccd_handle,
.offset = 0,
.len = sizeof(buf),
.p_value = buf
};
 
return sd_ble_gattc_write(conn_handle, &write_params);
}
 
//******************************************************************************
// fn :ble_btn_c_tx_notif_enable
//
// brief : 用于使能从机btn服务的notify功能
//
// param : p_ble_btn_c -> 指向要关联的BTN结构实例的指针
//
// return : none
uint32_t ble_btn_c_tx_notif_enable(ble_btn_c_t * p_ble_btn_c)
{
VERIFY_PARAM_NOT_NULL(p_ble_btn_c);
 
if ( (p_ble_btn_c->conn_handle == BLE_CONN_HANDLE_INVALID)
||(p_ble_btn_c->peer_btn_db.cccd_handle == BLE_GATT_HANDLE_INVALID)
)
{
return NRF_ERROR_INVALID_STATE;
}
return cccd_configure(p_ble_btn_c->conn_handle,p_ble_btn_c->peer_btn_db.cccd_handle, true);
}
</syntaxhighlight>看完了使能notify的函数,我们来看下接收从机数据的处理部分,首先是看到ble_btn_c_on_ble_evt函数,这个函数在我们调用BLE_BTN_C_DEF(m_ble_btn_c);注册实例的时候,就已经创建好了,用于接收底层的softdevice的消息返回。
 
我们看下其中的BLE_GATTC_EVT_HVX(Handle Value Notification or Indication event)事件,在这个事件下我们接收到从机发送给我们的数据。<syntaxhighlight lang="c" line="1" start="146">
//******************************************************************************
// fn :ble_btn_c_on_ble_evt
//
// brief : BLE事件处理函数
//
// param : p_ble_evt -> ble事件
// p_context -> ble事件处理程序的参数(暂时理解应该是不同的功能,注册时所携带的结构体参数)
//
// return : none
void ble_btn_c_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{
if ((p_context == NULL) || (p_ble_evt == NULL))
{
return;
}
 
ble_btn_c_t * p_ble_btn_c = (ble_btn_c_t *)p_context;
 
switch (p_ble_evt->header.evt_id)
{
case BLE_GATTC_EVT_HVX:
on_hvx(p_ble_btn_c, p_ble_evt);
break;
case BLE_GAP_EVT_DISCONNECTED:
on_disconnected(p_ble_btn_c, p_ble_evt);
break;
 
default:
break;
}
}
</syntaxhighlight>然后我们看下对于接收到的从机数据的处理,首先还是一样的,我们需要判断一下数据的来源是不是我们btn_handle。当确认都是正确的,然后我们将接收的数据复制给ble_btn_c_evt_t,然后通过它的回调上传到我们的main文件中,携带的事件ID为BLE_BTN_C_EVT_BTN_TX_EVT。<syntaxhighlight lang="c" line="1" start="32">
//******************************************************************************
// fn :on_hvx
//
// brief : 用于处理从SoftDevice接收到的通知
//
// param : p_ble_btn_c -> 指向BTN Client结构的指针
// p_ble_evt -> 指向接收到的BLE事件的指针
//
// return : none
static void on_hvx(ble_btn_c_t * p_ble_btn_c, ble_evt_t const * p_ble_evt)
{
if ( (p_ble_btn_c->peer_btn_db.btn_handle != BLE_GATT_HANDLE_INVALID)
&& (p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_btn_c->peer_btn_db.btn_handle)
&& (p_ble_btn_c->evt_handler != NULL))
{
ble_btn_c_evt_t ble_btn_c_evt;
 
ble_btn_c_evt.evt_type = BLE_BTN_C_EVT_BTN_TX_EVT;
ble_btn_c_evt.p_data = (uint8_t *)p_ble_evt->evt.gattc_evt.params.hvx.data;
ble_btn_c_evt.data_len = p_ble_evt->evt.gattc_evt.params.hvx.len;
 
p_ble_btn_c->evt_handler(p_ble_btn_c, &ble_btn_c_evt);
NRF_LOG_DEBUG("Client sending data.");
}
}
</syntaxhighlight>接下来返回到我们的main文件中,我们在ble_btn_c_evt_handler回调中可以看到BLE_BTN_C_EVT_BTN_TX_EVT事件的处理,我们将接收到的从机数据用于控制相应的LED灯点亮。<syntaxhighlight lang="c" line="1" start="297">
case BLE_BTN_C_EVT_BTN_TX_EVT:
NRF_LOG_DEBUG("Receiving data.");
NRF_LOG_HEXDUMP_DEBUG(p_evt->p_data, p_evt->data_len);
LED_Control(BSP_LED_0, p_evt->p_data[0]);
LED_Control(BSP_LED_1, p_evt->p_data[1]);
LED_Control(BSP_LED_2, p_evt->p_data[2]);
LED_Control(BSP_LED_3, p_evt->p_data[3]);
break;
default:
break;
}
}
</syntaxhighlight>
===== 从机部分 =====
</syntaxhighlight>服务部分剩下的处理流程和上一实验是类型的,只不过上一个实验是处理的wirte属性,而这个实验是处理notify属性。
首先在BLE事件处理的函数中,本来我们应该要处理CCCD_Write的数据的,但是这边我们偷懒了,不去做处理,验证一下主机不发送数据使能notify,我们也可以通过notify发送数据出去。首先在BLE事件处理的函数中,我们应该要处理CCCD_Write的数据的,所以在由softdevice返回消息的ble_btn_on_ble_evt函数中,我们需要处理一下BLE_GATTS_EVT_WRITE事件。<syntaxhighlight lang="c" line="1" start="3130">
//******************************************************************************
// fn :ble_led_on_ble_evt
//
// return : none
//******************************************************************************// fn :ble_btn_on_ble_evt//// brief : BLE事件处理函数//// param : p_ble_evt -> ble事件// p_context -> ble事件处理程序的参数(暂时理解应该是不同的功能,注册时所携带的结构体参数)//// return : nonevoid ble_led_on_ble_evtble_btn_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{
ble_btn_t * p_btn = (ble_btn_t *)p_context;
switch (p_ble_evt->header.evt_id)
{
case BLE_GATTS_EVT_WRITE: on_write(p_btn, p_ble_evt); break;
default:
break;
}
}
</syntaxhighlight>在这个on_write函数中,我们接收到了主机发送过来的使能从机notify的数据,我们需要判断一下接收的数据的句柄是不是cccd_handle,以及接收的数据长度是不是2字节(使能数据:01 00)。如果成功使能了notify,那么我们打印"notification enabled"。<syntaxhighlight lang="c" line="1" start="10">static void on_write(ble_btn_t * p_btn, ble_evt_t const * p_ble_evt){ ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write; if ((p_evt_write->handle == p_btn->btn_char_handles.cccd_handle) && (p_evt_write->len == 2)) { if (ble_srv_is_notification_enabled(p_evt_write->data)) { p_client.is_notification_enabled = true; NRF_LOG_INFO("notification enabled"); } else { p_client.is_notification_enabled = false; NRF_LOG_INFO("notification disabled"); } }}</syntaxhighlight>除了上述的3个函数,我们的从机服务文件,就只剩下一个发送notify数据的函数了。首先我们一定要先判断一下是否已经使能的notify使能,并且判断数据长度是否符合要求。 下面这个函数,就是我们notify发送数据的函数,他的参数我们只需要配置4个。
type配置为BLE_GATT_HVX_NOTIFICATION,代表是notify属性的数据;
handle我们需要配置为我们按键特征值的value.handle,代表的是按键特征值的Value这个列表的句柄;
剩下的p_data和p_len就是我们需要发送的数据以及数据的长度。<syntaxhighlight lang="c" line="1" start="795">
//******************************************************************************
// fn :ble_lbs_on_button_changeble_btn_data_send
//
// brief : 处理按键按下,状态更新的事件
//
// param : conn_handle p_btn -> btn结构体// p_data -> 连接的句柄数据指针// p_btn p_length -> btn结构体数据长度// button_state conn_handle -> 按键状态连接的句柄
//
// return : none
uint32_t ble_lbs_on_button_changeble_btn_data_send(uint16_t conn_handle, ble_btn_t * p_btn, uint8_t *button_statep_data, uint16_t p_length, uint16_t conn_handle)
{
ble_gatts_hvx_params_t params;
uint16_t len = BTN_UUID_CHAR_LEN;
memsetble_gatts_hvx_params_t hvx_params;  VERIFY_PARAM_NOT_NULL(&params, 0, sizeofp_btn);  if (params)conn_handle == BLE_CONN_HANDLE_INVALID) { return NRF_ERROR_NOT_FOUND; params}  if (!p_client.type = BLE_GATT_HVX_NOTIFICATIONis_notification_enabled) { return NRF_ERROR_INVALID_STATE; params.handle = p_btn-}  if (p_length >btn_char_handles.value_handle;BTN_UUID_CHAR_LEN) params.p_data = button_state{ return NRF_ERROR_INVALID_PARAM; params.p_len = &len;}
return sd_ble_gatts_hvxmemset(conn_handle&hvx_params, &params0, sizeof(hvx_params));}</syntaxhighlight>======gy_serial_btn hvx_params.c\.h=====type =BLE_GATT_HVX_NOTIFICATION;按键的外设处理驱动文件,这个详细的说明也是请大家查看基础实验部分的,这里我们只关注一下按键被触发,然后通过notify函数,去向手机发送通知。<syntaxhighlight lang hvx_params.handle ="c" line="1" start="41">//******************************************************************// fn : btn_timeout_handler//// brief : 按键定时器超时任务// // param : p_event p_btn-> 指向数据库发现事件的指针//// return : nonestatic void btn_timeout_handler(void * p_context){ uint8_t btnStateBuf[BTN_UUID_CHAR_LEN] = {0}btn_char_handles.value_handle; if(nrf_drv_gpiote_in_is_set(BUTTON_1) == 0) { NRF_LOG_INFO("BTN1"); btnStateBuf[0] hvx_params.p_data = 0x01p_data; btnState hvx_params.p_len = 1&p_length; }
if(nrf_drv_gpiote_in_is_set(BUTTON_2) == 0) { NRF_LOG_INFO("BTN2"); btnStateBuf[1] = 0x01; btnState = 1; } if(nrf_drv_gpiote_in_is_set(BUTTON_3) == 0) { NRF_LOG_INFO("BTN3"); btnStateBuf[2] = 0x01; btnState = 1; } if(nrf_drv_gpiote_in_is_set(BUTTON_4) == 0) { NRF_LOG_INFOreturn sd_ble_gatts_hvx("BTN4"); btnStateBuf[3] = 0x01; btnState = 1; } if(btnState) { ble_lbs_on_button_change(0x0000conn_handle, &m_btn, btnStateBufhvx_params); } btnState = 0;
}
</syntaxhighlight>
======main.c======
由于这个例程的按键通知我们是放到了按键驱动文件中处理,所以在main函数当中我们只需要去初始化一下这个服务就可以了。首先我们还是需要添加一下服务初始化函数。其他的处理都是在gy_profile_btn文件中,所以main文件中就只剩下一个按键触发后调用notify发送的部分。<syntaxhighlight lang="c" line="1" start="194">
//******************************************************************
// fn : services_init
err_code = ble_btn_init(&m_btn);
APP_ERROR_CHECK(err_code);
}
</syntaxhighlight>当有按键按下时,最终会将按键消息传递到这个回调中进行处理,我们根据按键触发的消息,对相应的buf值进行修改,最后调用ble_btn_data_send函数将数据发送给主机。<syntaxhighlight lang="c" line="1" start="351">
//******************************************************************
// fn : btn_evt_handler_t
//
// brief : 按键触发回调函数
//
// param : butState -> 当前的按键值
//
// return : none
void btn_evt_handler_t (uint8_t butState)
{
uint8_t buf[BTN_UUID_CHAR_LEN] = {0x01,0x01,0x01,0x01};
switch(butState)
{
case BUTTON_1:
buf[0] = 0x00;
break;
case BUTTON_2:
buf[1] = 0x00;
break;
case BUTTON_3:
buf[2] = 0x00;
break;
case BUTTON_4:
buf[3] = 0x00;
break;
default:
break;
}
ble_btn_data_send(&m_btn, buf, BTN_UUID_CHAR_LEN, m_conn_handle);
}
</syntaxhighlight>
==== <span> </span>实验总结 ====
经过本章内容的学习,我们需要对notify属性的服务有一定的了解,主要掌握以下3点:
 
1、我们需要了解主机如何去使能从机的notify功能
 
2、从机如何创建一个支持notify功能的特征值服务
 
3、从机如何发送一个notify功能的数据包给主机
=== NUS服务获取实验 ===
510
个编辑