打开主菜单

谷雨文档中心 β

更改

NRF52832DK协议栈实验

添加12,664字节2020年1月8日 (三) 14:00
主机部分
其中我们的从机设备一般是作为GATT服务器去提供服务的,主机设备作为GATT客户端去向服务的特性数据库中写入或者读取数据。
 
{{Note|text=GATT特征值属性包含如下4个:
 
Write(写)、Read(读)、Notify(通知)、Indicate(暗示)
 
这4个属性当中,其中大家最常用的是两个,通俗的来讲:Write属性是用于主机给从机发送数据;
Notify属性是用于从机给主机发送数据|type=info}}
 
====实验现象====
主机设备流程:
===== 主机部分 =====
====== gy_profile_led_c.c\.h h与main.c ======我们首先查看一下主机的服务客户端文件,里面包含了好几个函数,我们按个介绍一下这些函数的功能。我们首先查看一下主机的服务客户端文件,里面包含了好几个函数,我们挨个介绍一下这些函数的功能。
第一个还是客户端的初始化函数,第一个还是客户端的初始化函数,对应从机的ble_led_init()注册服务的函数,我们需要利用ble_db_discovery_evt_register()函数注册一个待会服务发现的UUID。 这里我们需要注意一下,不论是服务的发现,还是某一个特征的发现,都是需要根据UUID来判断的。这边可以看到我们注册UUID发现是和从机的服务中一样的(LED_UUID_BASE以及LED_UUID_SERVICE)。<syntaxhighlight lang="c" line="1" start="83">
//******************************************************************************
// fn :ble_led_c_init
return ble_db_discovery_evt_register(&led_uuid);
}
</syntaxhighlight>看完初始化函数,我们接下来得看下当我们注册好客服端,并且在main函数中调用发现函数之后,底层如何给我们返回,这里我们需要结合main.c文件的内容一起给大家介绍。
 
在mian函数的中,我们可以看到注册了服务发现的功能,也就是数据库发现db_discovery_init();,并且注册了GATT初始化gatt_init();,以及我们的LED服务的客户端初始化led_c_init();。<syntaxhighlight lang="c" line="1" start="526">
//******************************************************************
// fn : main
//
// brief : 主函数
//
// param : none
//
// return : none
int main(void)
{
// 初始化
log_init(); // 初始化LOG打印,由RTT工作
timer_init(); // 初始化定时器
GPIOTE_Init(); // 初始化IO
BTN_Init(btn_evt_handler_t); // 初始化按键
power_management_init();// 初始化电源控制
ble_stack_init(); // 初始化BLE栈堆
db_discovery_init(); // 初始化数据库发现(用于发现服务)
gatt_init(); // 初始化GATT
led_c_init(); // 初始化LED_C
scan_init(); // 初始化扫描
// 打印例程名称
NRF_LOG_INFO("demo0:simple central");
scan_start(); // 开始扫描
// 进入主循环
for (;;)
{
idle_state_handle(); // 空闲状态处理
}
}
</syntaxhighlight>当我们初始化好以上说明的3个函数(具体每个函数的代码,大家可以看下代码中的注册),一旦我们主机发现我们的从机,并成功连接之后,会进入BLE_GAP_EVT_CONNECTED状态。
 
在这个状态下,我们就需要开始我们的服务发现了,调用ble_db_discovery_start()函数开始发现服务。<syntaxhighlight lang="c" line="1" start="345">
//******************************************************************
// 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)
{
ret_code_t err_code;
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
);
m_conn_handle = p_gap_evt->conn_handle;
err_code = ble_led_c_handles_assign(&m_ble_led_c, m_conn_handle, NULL);
APP_ERROR_CHECK(err_code);
 
// 开始发现服务,NUS客户端等待发现结果
err_code = ble_db_discovery_start(&m_db_disc, p_ble_evt->evt.gap_evt.conn_handle);
APP_ERROR_CHECK(err_code);
break;
</syntaxhighlight>当成功发现服务之后,会进入db_disc_handler回调函数,在这个回调函数之中,因为我们这个工程仅需要处理led的服务,所以我们调用ble_led_c_on_db_disc_evt去发现led相关的特征值内容,其中会携带我们的ble_db_discovery_evt_t参数(底层返回的所有和服务数据库相关的信息都在这个参数里面)。<syntaxhighlight lang="c" line="1" start="316">
//******************************************************************
// fn : db_disc_handler
//
// brief : 用于处理数据库发现事件的函数
// details : 此函数是一个回调函数,用于处理来自数据库发现模块的事件。
// 根据发现的UUID,此功能将事件转发到各自的服务。
//
// param : p_event -> 指向数据库发现事件的指针
//
// return : none
static void db_disc_handler(ble_db_discovery_evt_t * p_evt)
{
ble_led_c_on_db_disc_evt(&m_ble_led_c, p_evt);
}
</syntaxhighlight>所以接下来,我们需要先判断一下,底层返回的ble_db_discovery_evt_t中携带的类型是否是BLE_DB_DISCOVERY_COMPLETE,也就是数据库成功的完成发现,且发现的UUID是LED_UUID_SERVICE。
 
如果确实成功的发现我们的LED服务,接下来我们就需要从服务中取出我们需要的特征值,也就是LED_UUID_CHAR。我们需要从这个特征值当中获取我们用于通信的句柄(handle_value)。
 
当我们一切都是按照正确的流程跑完,可以看到在这个函数的最后,它会给我们返回一个p_ble_led_c->evt_handler(p_ble_led_c, &evt);,也就是向mian.c文件中给我们一个回调(ble_led_c_init初始化函数时注册的回调),其中携带的任务参数类型是BLE_LED_C_EVT_DISCOVERY_COMPLETE。<syntaxhighlight lang="c" line="1" start="32">
//******************************************************************************
// fn :ble_led_c_on_db_disc_evt
//
// brief : 处理led服务发现的函数
//
// param : p_ble_led_c -> 指向LED客户端结构的指针
// p_evt -> 指向从数据库发现模块接收到的事件的指针
//
// return : none
void ble_led_c_on_db_disc_evt(ble_led_c_t * p_ble_led_c, ble_db_discovery_evt_t const * p_evt)
{
// 判断LED服务是否发现完成
if (p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE &&
p_evt->params.discovered_db.srv_uuid.uuid == LED_UUID_SERVICE &&
p_evt->params.discovered_db.srv_uuid.type == p_ble_led_c->uuid_type)
{
ble_led_c_evt_t evt;
 
evt.evt_type = BLE_LED_C_EVT_DISCOVERY_COMPLETE;
evt.conn_handle = p_evt->conn_handle;
 
for (uint32_t i = 0; i < p_evt->params.discovered_db.char_count; i++)
{
const ble_gatt_db_char_t * p_char = &(p_evt->params.discovered_db.charateristics[i]);
switch (p_char->characteristic.uuid.uuid)
{
// 根据LED特征值的UUID,获取我们句柄handle_value
case LED_UUID_CHAR:
evt.params.peer_db.led_handle = p_char->characteristic.handle_value;
break;
 
default:
break;
}
}
 
NRF_LOG_DEBUG("Led Button Service discovered at peer.");
// 如果实例是在db_discovery之前分配的,则分配db_handles
if (p_ble_led_c->conn_handle != BLE_CONN_HANDLE_INVALID)
{
if (p_ble_led_c->peer_led_db.led_handle == BLE_GATT_HANDLE_INVALID)
{
p_ble_led_c->peer_led_db = evt.params.peer_db;
}
}
 
p_ble_led_c->evt_handler(p_ble_led_c, &evt);
}
}
</syntaxhighlight>那么接下来,我们再去看一下mian.c中此回调函数下的处理。
 
在ble_led_c_evt_handler回调函数下,我们判断传入的事件类型,可以看到正是刚刚的BLE_LED_C_EVT_DISCOVERY_COMPLETE事件,也就是代表我们已经成功的获取了我们指定服务(LED_UUID_SERVICE)下的指定特征值(LED_UUID_CHAR)的句柄(handle_value)。
 
然后我们调用ble_led_c_handles_assign函数,去将我们的连接句柄connHandle以及特征值句柄handle_value,绑定给p_ble_led_c实例。<syntaxhighlight lang="c" line="1" start="272">
//******************************************************************
// fn : ble_led_c_evt_handler
//
// brief : LED服务事件
//
// param : none
//
// return : none
static void ble_led_c_evt_handler(ble_led_c_t * p_ble_led_c, ble_led_c_evt_t * p_evt)
{
ret_code_t err_code;
 
switch (p_evt->evt_type)
{
case BLE_LED_C_EVT_DISCOVERY_COMPLETE:
NRF_LOG_INFO("Discovery complete.");
err_code = ble_led_c_handles_assign(&m_ble_led_c, p_evt->conn_handle, &p_evt->params.peer_db);
APP_ERROR_CHECK(err_code);
NRF_LOG_INFO("Connected to device with Ghostyu LED Service.");
break;
default:
break;
}
}
</syntaxhighlight>当上述的流程都正确跑完,我们就可以进行最后一步的行动,也就是发送数据,在这个例程当中我们是利用按键触发来发送对应的LED的状态变化。
 
我们到mian.c中,查看按键触发会调用的btn_evt_handler_t回调函数,在这个函数中,我们最后会调用LED服务数据发送的功能函数ble_led_led_status_send。<syntaxhighlight lang="c" line="1" start="495">
//******************************************************************
// fn : btn_evt_handler_t
//
// brief : 按键触发回调函数
//
// param : butState -> 当前的按键值
//
// return : none
void btn_evt_handler_t (uint8_t butState)
{
uint8_t buf[LED_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_led_led_status_send(&m_ble_led_c,buf,LED_UUID_CHAR_LEN);
}
</syntaxhighlight>最后我们来分析一下这个发送函数,是如何使用我们刚刚一大圈代码处理,最终得到的connhandle以及handle_value的。
 
首先先判断下数据的长度,是不是符合我们的特征值的长度限制(不能超过我们定义的特征值的大小,否则返回参数错误),这个判断是很有必要的!
 
接下来我们判断一下connhandle是否为0xffff(BLE_CONN_HANDLE_INVALID),也就是尚未连接任何设备,如果没有连接,则返回状态无效。
 
最后我们定义了ble_gattc_write_params_t结构体用于赋值我们需要发送的数据,其中值得注意的是.handle = p_ble_led_c->peer_led_db.led_handle,这个就是我们刚刚获得的handle_value(特征值句柄),其他参数大家依葫芦画瓢,比较好理解,就不给大家介绍了。最终我们调用sd_ble_gattc_write函数将数据发送出去。<syntaxhighlight lang="c" line="1" start="148">
//******************************************************************************
// fn :ble_led_led_status_send
//
// brief : LED状态控制函数
//
// param : p_ble_led_c -> 指向要关联的LED结构实例的指针
// p_string -> 发送的LED相关的数据
// length -> 发送的LED相关的数据长度
//
// return : none
uint32_t ble_led_led_status_send(ble_led_c_t * p_ble_led_c, uint8_t * p_string, uint16_t length)
{
VERIFY_PARAM_NOT_NULL(p_ble_led_c);
 
if (length > LED_UUID_CHAR_LEN)
{
NRF_LOG_WARNING("Content too long.");
return NRF_ERROR_INVALID_PARAM;
}
if (p_ble_led_c->conn_handle == BLE_CONN_HANDLE_INVALID)
{
return NRF_ERROR_INVALID_STATE;
}
 
ble_gattc_write_params_t const write_params =
{
.write_op = BLE_GATT_OP_WRITE_CMD,
.flags = BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE,
.handle = p_ble_led_c->peer_led_db.led_handle,
.offset = 0,
.len = length,
.p_value = p_string
};
return sd_ble_gattc_write(p_ble_led_c->conn_handle, &write_params);
}
</syntaxhighlight>
}
</syntaxhighlight>
======gy_serial_led.c\.h======
有关外设处理,请大家查看基础实验部分
======main.c======
main文件中也不给大家全部介绍了,这个和蓝牙协议实验部分是重合的,我们只关注实验改动的部分。
==== <span> 实验总结</span> ====
通过这一章节的学习,我们需要掌握下面3个要点。
 
1、从机如何注册一个自定的服务,并且在服务下添加自己的特征值功能
 
2、主机如何针对指定的从机服务,去获取这个服务以及服务下指定特征值的句柄
 
3、主机如何通过Write属性,向从机发送数据
=== NUS服务获取实验 ===
510
个编辑