打开主菜单

谷雨文档中心 β

更改

NRF52832DK协议栈高级实验

添加26,185字节2019年8月29日 (四) 17:30
实验简介
==== 实验简介 ====
NUS(Nordic NUS_C(Nordic Uart Service)实验,他本质上是和开始的LED与按键的实验是一样的,都是新建一个服务文件,注册好我们需要使用的特征值功能,然后传输数据。不过是要比他们稍微复杂一点,因为我们串口的数据是涉及到收发两个部分(也就是既有write也有notify),所以我们在注册特征值的时候,是要注册两个,一个负责write,一个负责notify。Service Client)实验,这个实验就是针对NUS进行处理的。
剩下的就是串口外设的处理,我们这边对于串口驱动不做介绍,大家可以去看基础实验部分的说明。我们在这个实验中仅给大家讲解串口RX收到的数据如何通知(notify)给手机,手机下发(write)的数据如何通过串口TX打印显示出来。这点是nordic的特色,之前我们做ti的CC25和CC26开发,从机的服务文件是一样需要大家根据自己需求实现,而主机部分的客户端处理,则是在一个大的事件回调中处理,开发者只要知道从机服务的UUID即可。而在nordic的工程中,我们可以看到,所有的服务都有其对应的客户端服务处理的文件,这样的好处在于一对一处理,我们对于细节上的处理能够做的更好,缺点在于如果大家自己新增服务,那么还需要编辑一个对应的客户端文件,这样增加了大家的开发难度。 剩下的就是串口外设的处理,我们这边对于串口驱动不做介绍,大家可以去看基础实验部分的说明。我们在这个实验中仅给大家讲解串口RX收到的数据如何写(Write)给从机,从机通知(notify)的数据,主机如何接收并通过串口TX打印出来显示。
==== 硬件说明 ====
==== 源码讲解 ====
=== 一主多从实验 == ble_nus.c\.h =====NUS_C客户端相对于NUS服务,功能部分要多一些,因为除了客户端初始化以及通信等回调事件的处理,多了一个部分,也就是发起服务扫描的回调函数处理。 首先是第一部分,也就是客户端初始化,以及底层回调的GAP和GATT事件的处理。 NUS客户端初始化函数,我们首先同样是先调用sd_ble_uuid_vs_add函数,根据我们的NUS_BASE_UUID,去确定一下客户端的UUID Type(p_ble_nus_c->uuid_type)。 然后我们分别初始化了p_ble_nus_c的连接句柄(conn_handle)、NUS服务的TX和RX的句柄,以及配置了一个回调函数p_ble_nus_c->evt_handler。<syntaxhighlight lang="c" line="1" start="121">uint32_t ble_nus_c_init(ble_nus_c_t * p_ble_nus_c, ble_nus_c_init_t * p_ble_nus_c_init){ uint32_t err_code; ble_uuid_t uart_uuid; ble_uuid128_t nus_base_uuid = NUS_BASE_UUID;  VERIFY_PARAM_NOT_NULL(p_ble_nus_c); VERIFY_PARAM_NOT_NULL(p_ble_nus_c_init);  err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_ble_nus_c->uuid_type); VERIFY_SUCCESS(err_code);  uart_uuid.type = p_ble_nus_c->uuid_type; uart_uuid.uuid = BLE_UUID_NUS_SERVICE;  p_ble_nus_c->conn_handle = BLE_CONN_HANDLE_INVALID; p_ble_nus_c->evt_handler = p_ble_nus_c_init->evt_handler; p_ble_nus_c->handles.nus_tx_handle = BLE_GATT_HANDLE_INVALID; p_ble_nus_c->handles.nus_rx_handle = BLE_GATT_HANDLE_INVALID;  return ble_db_discovery_evt_register(&uart_uuid);}</syntaxhighlight>看完初始化函数,接下来我们还是一样的来看下ble_nus_c_on_ble_evt函数,这个是nus服务的部分是一样的,都是mian文件中注册nus_c实例的时候就初始化好的,用于接收底层返回的GAP和GATT消息事件。 这里面我们初始化了两个事件,一个是BLE_GATTC_EVT_HVX,用于接收notify数据的事件,另一个处理的是BLE_GAP_EVT_DISCONNECTED,这个事件处理的是,如果断开连接,我们需要将nus_c的状态改成断开状态。<syntaxhighlight lang="c" line="1" start="144">void ble_nus_c_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context){ ble_nus_c_t * p_ble_nus_c = (ble_nus_c_t *)p_context;  if ((p_ble_nus_c == NULL) || (p_ble_evt == NULL)) { return; }  if ( (p_ble_nus_c->conn_handle != BLE_CONN_HANDLE_INVALID) &&(p_ble_nus_c->conn_handle != p_ble_evt->evt.gap_evt.conn_handle) ) { return; }  switch (p_ble_evt->header.evt_id) { case BLE_GATTC_EVT_HVX: on_hvx(p_ble_nus_c, p_ble_evt); break;  case BLE_GAP_EVT_DISCONNECTED: if (p_ble_evt->evt.gap_evt.conn_handle == p_ble_nus_c->conn_handle && p_ble_nus_c->evt_handler != NULL) { ble_nus_c_evt_t nus_c_evt;  nus_c_evt.evt_type = BLE_NUS_C_EVT_DISCONNECTED;  p_ble_nus_c->conn_handle = BLE_CONN_HANDLE_INVALID; p_ble_nus_c->evt_handler(p_ble_nus_c, &nus_c_evt); } break;  default: // No implementation needed. break; }}</syntaxhighlight>接下来我们顺带的看一下on_hvx函数,我们接收到底层的BLE_GATTC_EVT_HVX事件之后,调用这个函数进行处理。首先是获取p_ble_evt携带的数据内容和长度,并且将ble_nus_c_evt.evt_type的任务ID改成是BLE_NUS_C_EVT_NUS_TX_EVT,这样我们会向main文件回调我们的事件和数据。<syntaxhighlight lang="c" line="1" start="103">static void on_hvx(ble_nus_c_t * p_ble_nus_c, ble_evt_t const * p_ble_evt){ // HVX can only occur from client sending. if ( (p_ble_nus_c->handles.nus_tx_handle != BLE_GATT_HANDLE_INVALID) && (p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_nus_c->handles.nus_tx_handle) && (p_ble_nus_c->evt_handler != NULL)) { ble_nus_c_evt_t ble_nus_c_evt;  ble_nus_c_evt.evt_type = BLE_NUS_C_EVT_NUS_TX_EVT; ble_nus_c_evt.p_data = (uint8_t *)p_ble_evt->evt.gattc_evt.params.hvx.data; ble_nus_c_evt.data_len = p_ble_evt->evt.gattc_evt.params.hvx.len;  p_ble_nus_c->evt_handler(p_ble_nus_c, &ble_nus_c_evt); NRF_LOG_DEBUG("Client sending data."); }}</syntaxhighlight>接下来的三个函数我们就放到一起说明了,功能比较好理解。 cccd_configure和ble_nus_c_tx_notif_enable函数,从名称上就不难看出,他们俩是用来配置我们的CCCD的,也就是使能NUS的notify功能。 ble_nus_c_string_send函数,这个是主机的write功能函数,就是调用这个函数向从机写数据。<syntaxhighlight lang="c" line="1" start="185">/**@brief Function for creating a message for writing to the CCCD. */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);}  uint32_t ble_nus_c_tx_notif_enable(ble_nus_c_t * p_ble_nus_c){ VERIFY_PARAM_NOT_NULL(p_ble_nus_c);  if ( (p_ble_nus_c->conn_handle == BLE_CONN_HANDLE_INVALID) ||(p_ble_nus_c->handles.nus_tx_cccd_handle == BLE_GATT_HANDLE_INVALID) ) { return NRF_ERROR_INVALID_STATE; } return cccd_configure(p_ble_nus_c->conn_handle,p_ble_nus_c->handles.nus_tx_cccd_handle, true);}  uint32_t ble_nus_c_string_send(ble_nus_c_t * p_ble_nus_c, uint8_t * p_string, uint16_t length){ VERIFY_PARAM_NOT_NULL(p_ble_nus_c);  if (length > BLE_NUS_MAX_DATA_LEN) { NRF_LOG_WARNING("Content too long."); return NRF_ERROR_INVALID_PARAM; } if (p_ble_nus_c->conn_handle == BLE_CONN_HANDLE_INVALID) { NRF_LOG_WARNING("Connection 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_nus_c->handles.nus_rx_handle, .offset = 0, .len = length, .p_value = p_string };  return sd_ble_gattc_write(p_ble_nus_c->conn_handle, &write_params);}</syntaxhighlight>了解完第一部分,我们来看下第二部分的内容,也就是发现服务的部分。 当我们在main文件中完成一系列获取服务的流程之后,最终会进入下面这个函数处理服务,可以看到我们分别根据TX和RX这两个特征值的UUID,获取了对应的handle。 并且会向main文件中返回一个BLE_NUS_C_EVT_DISCOVERY_COMPLETE事件,代表的是服务获取完成。<syntaxhighlight lang="c" line="1" start="55">void ble_nus_c_on_db_disc_evt(ble_nus_c_t * p_ble_nus_c, ble_db_discovery_evt_t * p_evt){ ble_nus_c_evt_t nus_c_evt; memset(&nus_c_evt,0,sizeof(ble_nus_c_evt_t));  ble_gatt_db_char_t * p_chars = p_evt->params.discovered_db.charateristics;  // Check if the NUS was discovered. if ( (p_evt->evt_type == BLE_DB_DISCOVERY_COMPLETE) && (p_evt->params.discovered_db.srv_uuid.uuid == BLE_UUID_NUS_SERVICE) && (p_evt->params.discovered_db.srv_uuid.type == p_ble_nus_c->uuid_type)) { for (uint32_t i = 0; i < p_evt->params.discovered_db.char_count; i++) { switch (p_chars[i].characteristic.uuid.uuid) { case BLE_UUID_NUS_RX_CHARACTERISTIC: nus_c_evt.handles.nus_rx_handle = p_chars[i].characteristic.handle_value; break;  case BLE_UUID_NUS_TX_CHARACTERISTIC: nus_c_evt.handles.nus_tx_handle = p_chars[i].characteristic.handle_value; nus_c_evt.handles.nus_tx_cccd_handle = p_chars[i].cccd_handle; break;  default: break; } } if (p_ble_nus_c->evt_handler != NULL) { nus_c_evt.conn_handle = p_evt->conn_handle; nus_c_evt.evt_type = BLE_NUS_C_EVT_DISCOVERY_COMPLETE; p_ble_nus_c->evt_handler(p_ble_nus_c, &nus_c_evt); } }}</syntaxhighlight>最后我们看下ble_nus_c_handles_assign函数,这个函数是用于给nus_c来分配句柄的。 他是在上面返回给mian函数的BLE_NUS_C_EVT_DISCOVERY_COMPLETE事件中进行处理,这个时候底层的handle句柄已经获取完成,这边是给我们定义的句柄结构体中的参数赋值。<syntaxhighlight lang="c" line="1" start="250">uint32_t ble_nus_c_handles_assign(ble_nus_c_t * p_ble_nus, uint16_t conn_handle, ble_nus_c_handles_t const * p_peer_handles){ VERIFY_PARAM_NOT_NULL(p_ble_nus);  p_ble_nus->conn_handle = conn_handle; if (p_peer_handles != NULL) { p_ble_nus->handles.nus_tx_cccd_handle = p_peer_handles->nus_tx_cccd_handle; p_ble_nus->handles.nus_tx_handle = p_peer_handles->nus_tx_handle; p_ble_nus->handles.nus_rx_handle = p_peer_handles->nus_rx_handle; } return NRF_SUCCESS;}</syntaxhighlight> ===== main.c =====mian文件中,主要也是有两个部分,一个是注册NUS_C的,一个是用于NUS服务发现的。我们从main函数开始看起。 mian函数中,我们分别初始化了数据库发现(db_discovery_init)以及nus_c(nus_c_init)。<syntaxhighlight lang="c" line="1" start="542">//******************************************************************// fn : main//// brief : 主函数//// param : none//// return : noneint main(void){ // 初始化 log_init(); // 初始化LOG打印,由RTT工作 timer_init(); // 初始化定时器 db_discovery_init(); // 初始化数据库发现 power_management_init();// 初始化电源控制 ble_stack_init(); // 初始化BLE栈堆 gatt_init(); // 初始化GATT nus_c_init(); // 初始化NUS scan_init(); // 初始化扫描 // 打印例程名称 //NRF_LOG_INFO("demo0: simple central"); // 初始化外设 UART_Init(APP_UartEvtHandle); //初始化串口 UART_Write("3.4_ble_central_nus", sizeof("3.4_ble_central_nus")); scan_start(); // 开始扫描 // 进入主循环 for (;;) { idle_state_handle(); // 空闲状态处理 }}</syntaxhighlight>我们看一下NUS_C的初始化函数,以及NUS_C的回调函数,可以看到都是调用的我们刚刚说的ble_nus_c.c中的函数去处理的,这里也没有太多的东西需要重复描述,我们主要看一下回调函数中的两个事件处理的内容。 BLE_NUS_C_EVT_DISCOVERY_COMPLETE,这个是我们服务发现完成之后返回的事件,所以我们可以在这个事件中调用ble_nus_c_handles_assign去获取特征值的句柄,并且可以调用ble_nus_c_tx_notif_enable使能从机的notify属性。 BLE_NUS_C_EVT_NUS_TX_EVT事件中,我们接收从机notify的数据,并通过串口打印出来。<syntaxhighlight lang="c" line="1" start="250">//******************************************************************// fn : ble_nus_c_evt_handler//// brief : NUS事件//// param : none//// return : nonestatic void ble_nus_c_evt_handler(ble_nus_c_t * p_ble_nus_c, ble_nus_c_evt_t const * p_ble_nus_evt){ ret_code_t err_code;  switch (p_ble_nus_evt->evt_type) { case BLE_NUS_C_EVT_DISCOVERY_COMPLETE: NRF_LOG_INFO("Discovery complete."); err_code = ble_nus_c_handles_assign(p_ble_nus_c, p_ble_nus_evt->conn_handle, &p_ble_nus_evt->handles); APP_ERROR_CHECK(err_code);  err_code = ble_nus_c_tx_notif_enable(p_ble_nus_c); APP_ERROR_CHECK(err_code); NRF_LOG_INFO("Connected to device with Nordic UART Service."); app_timer_start(m_timer_nus, APP_TIMER_TICKS(1000), NULL); break;  case BLE_NUS_C_EVT_NUS_TX_EVT: UART_Write((uint8_t*)p_ble_nus_evt->p_data, p_ble_nus_evt->data_len); NRF_LOG_DEBUG("Receiving data."); NRF_LOG_HEXDUMP_DEBUG(p_ble_nus_evt->p_data, p_ble_nus_evt->data_len); break;  case BLE_NUS_C_EVT_DISCONNECTED: NRF_LOG_INFO("Disconnected."); break; default: break; }} //******************************************************************// fn : nus_c_init//// brief : 初始化NUS客户端(Nordic UART Service client)//// param : none//// return : nonestatic void nus_c_init(void){ ret_code_t err_code; ble_nus_c_init_t init;  init.evt_handler = ble_nus_c_evt_handler;  err_code = ble_nus_c_init(&m_ble_nus_c, &init); APP_ERROR_CHECK(err_code);}</syntaxhighlight>接下来我们看一下初始化以及发起服务获取部分,首先是初始化函数,我们调用ble_db_discovery_init函数初始化了服务发现的功能,并且注册了一个回调函数db_disc_handler。这个回调函数中我们将对发现的服务进行功能判断,以及相应的参数赋值或者其他处理,处理调用的函数也是ble_nus_c.c中的ble_nus_c_on_db_disc_evt。<syntaxhighlight lang="c" line="1" start="309">//******************************************************************// fn : db_disc_handler//// brief : 用于处理数据库发现事件的函数// details : 此函数是一个回调函数,用于处理来自数据库发现模块的事件。// 根据发现的UUID,此功能将事件转发到各自的服务。// // param : p_event -> 指向数据库发现事件的指针//// return : nonestatic void db_disc_handler(ble_db_discovery_evt_t * p_evt){ ble_nus_c_on_db_disc_evt(&m_ble_nus_c, p_evt);} //******************************************************************// fn : db_discovery_init//// brief : 用于初始化数据库发现模块的函数//// param : none//// return : nonestatic void db_discovery_init(void){ ret_code_t err_code = ble_db_discovery_init(db_disc_handler); APP_ERROR_CHECK(err_code);}</syntaxhighlight>上面的发现服务初始化找到了,最终返回的消息事件也知道怎么处理了,那么我们是在哪边去开始服务发现这个过程的呢。 我们可以看到在ble协议栈任务回调事件中,当我们连接成功之后(BLE_GAP_EVT_CONNECTED事件返回),我们调用了ble_db_discovery_start函数去发现服务。<syntaxhighlight lang="c" line="1" start="338">//******************************************************************// fn : ble_evt_handler//// brief : BLE事件回调// details : 包含以下几种事件类型:COMMON、GAP、GATT Client、GATT Server、L2CAP//// param : ble_evt_t 事件类型// p_context 未使用//// return : nonestatic 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: UART_Write("Connected", sizeof("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;  // 开始发现服务,NUS客户端等待发现结果 err_code = ble_db_discovery_start(&m_db_disc, p_ble_evt->evt.gap_evt.conn_handle); APP_ERROR_CHECK(err_code); break;  // 断开连接 case BLE_GAP_EVT_DISCONNECTED: UART_Write("Disconnected", sizeof("Disconnected")); NRF_LOG_INFO("Disconnected. conn_handle: 0x%x, reason: 0x%04x", p_gap_evt->conn_handle, p_gap_evt->params.disconnected.reason); m_conn_handle = BLE_CONN_HANDLE_INVALID; // 如果需要异常断开重连,可以打开下面的注释 // scan_start(); // 重新开始扫描 break;  // 连接参数更新请求 case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST: NRF_LOG_INFO("conn_Param Update REQUEST: %d,%d,%d,%d", p_ble_evt->evt.gap_evt.params.conn_param_update_request.conn_params.min_conn_interval, p_ble_evt->evt.gap_evt.params.conn_param_update_request.conn_params.max_conn_interval, p_ble_evt->evt.gap_evt.params.conn_param_update_request.conn_params.slave_latency, p_ble_evt->evt.gap_evt.params.conn_param_update_request.conn_params.conn_sup_timeout ); break; case BLE_GAP_EVT_CONN_PARAM_UPDATE: { NRF_LOG_INFO("conn_Param Update Success: %d,%d,%d,%d", p_ble_evt->evt.gap_evt.params.conn_param_update_request.conn_params.min_conn_interval, p_ble_evt->evt.gap_evt.params.conn_param_update_request.conn_params.max_conn_interval, p_ble_evt->evt.gap_evt.params.conn_param_update_request.conn_params.slave_latency, p_ble_evt->evt.gap_evt.params.conn_param_update_request.conn_params.conn_sup_timeout ); }break; default: break; }}</syntaxhighlight> === 一从多主实验 ===
==== 实验简介 ====
一主多从以及一从多主,这个在大家的实际场景使用中,经常会要用到,所以我们将会在接下来的两个例程依次给大家做介绍。
 
首先是给大家带来的我们本实验的一从多主,顾名思义就是一个从机设备支持被多个主机设备同时连接。那么我们建立这样一对多的连接方式,代码的处理过程会变得非常麻烦吗,实际上并不是这样,对于我们开发者而言,如果大家对于连接句柄(conn_handle)的理解已经比较透彻,那么增加的难度真的很低。
 
==== 硬件说明 ====
串口一向是所有开发板必需的功能之一,nRF52DK开发板采用CH340C芯片,将芯片的UART信号转换为USB接口信号,方便电脑使用USB来虚拟一个串口调试。
CH340C外围电路简单,且不需要外部晶振,nRF52DK开发板采用与Nordic官方开发板相同的串口收发引脚,如下表格所示。
{| class="wikitable"
!网络标号
!芯片引脚号
!连接方式
|-
|CH340_TX
|P0.08(nRF_RX信号)
|通过SW1拨动开关,选择连接
|-
|CH340_RX
|P0.06(nRF_TX信号)
|通过SW1拨动开关,选择连接
|}[[文件:NRF52832 CH340C虚拟串口.png|居中|无框|609x609像素|link=]]图中P5引出了CH340C的流控制引脚,但是没有连接任何线路,所需要使用流控制,可自行连接测试。
==== 实验现象 ====
==== 源码讲解 ====
 
===== mian.c =====
mian文件中,我们首先看一下mian函数,可以看到并没有因为要建立多个链接而新增什么功能的初始化,这就说明我们只需要在原有的功能基础上去修改一些东西。那么接下来我们带大家一起看下我们修改了哪些地方。
 
第一点,ble协议栈初始化,咋一看好像没有什么改动,实际上在我们的nrf_sdh_ble_default_cfg_set初始化函数中,我们已经配置了本从机最大支持被4个主机连接,这个配置是的sdk_conifg.h中去修改了宏定义。<syntaxhighlight lang="c" line="1" start="11965">
// <o> NRF_SDH_BLE_PERIPHERAL_LINK_COUNT - Maximum number of peripheral links.
#ifndef NRF_SDH_BLE_PERIPHERAL_LINK_COUNT
#define NRF_SDH_BLE_PERIPHERAL_LINK_COUNT 4
#endif
 
// <o> NRF_SDH_BLE_CENTRAL_LINK_COUNT - Maximum number of central links.
#ifndef NRF_SDH_BLE_CENTRAL_LINK_COUNT
#define NRF_SDH_BLE_CENTRAL_LINK_COUNT 0
#endif
 
// <o> NRF_SDH_BLE_TOTAL_LINK_COUNT - Total link count.
// <i> Maximum number of total concurrent connections using the default configuration.
 
#ifndef NRF_SDH_BLE_TOTAL_LINK_COUNT
#define NRF_SDH_BLE_TOTAL_LINK_COUNT 4
#endif
</syntaxhighlight><syntaxhighlight lang="c" line="1" start="501">
//******************************************************************
// fn : ble_stack_init
//
// brief : 用于初始化BLE协议栈
// details : 初始化SoftDevice、BLE事件中断
//
// param : none
//
// return : none
static void ble_stack_init(void)
{
ret_code_t err_code;
 
// SD使能请求,配置时钟,配置错误回调,中断(中断优先级栈堆默认设置)
err_code = nrf_sdh_enable_request();
APP_ERROR_CHECK(err_code);
 
// SD默认配置
uint32_t ram_start = 0;
err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
APP_ERROR_CHECK(err_code);
 
// 使能BLE栈堆
err_code = nrf_sdh_ble_enable(&ram_start);
APP_ERROR_CHECK(err_code);
 
// 注册BLE事件的处理程序,所有BLE的事件都将分派ble_evt_handler回调
NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
}
</syntaxhighlight>初始化中我们已经定义好了最大支持被4个主机连接,接下来就轮到了GAP状态处理的部分,我们看一下BLE协议栈的回调函数,可以看到里面还是只处理了连接(BLE_GAP_EVT_CONNECTED)和断开(BLE_GAP_EVT_DISCONNECTED)这两个状态。<syntaxhighlight lang="c" line="1" start="473">
//******************************************************************
// 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)
{
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_CONNECTED:
on_connected(&p_ble_evt->evt.gap_evt);
break;
 
case BLE_GAP_EVT_DISCONNECTED:
on_disconnected(&p_ble_evt->evt.gap_evt);
break;
 
default:
// No implementation needed.
break;
}
}
</syntaxhighlight>接下来我们分别来看一下连接成功的处理函数on_connected以及断开的处理函数on_disconnected.
 
首先看一下连接的处理函数,相对于以前只支持一个连接的例程,我们这边新增了一个nrf_ble_qwr_conn_handle_assign函数处理,这个函数的功能是用于将连接句柄分配给Queued Writes模块的给定实例。
 
并且我们需要对当前连接的总数量进行判断,如果以及连接的主机数量少于4个,则继续开启广播,等待其他主机继续发起连接;如果连接的主机数量达到4个,则不再开启广播。<syntaxhighlight lang="c" line="1" start="407">
//******************************************************************
// fn : on_connected
//
// brief : 用于处理Connected事件的函数。
//
// param : p_gap_evt -> 从BLE堆栈收到的GAP事件
//
// return : none
static void on_connected(const ble_gap_evt_t * const p_gap_evt)
{
ret_code_t err_code;
uint32_t periph_link_cnt = ble_conn_state_peripheral_conn_count(); // Number of peripheral links.
 
UART_Printf("Connection with link 0x%x established.\n", p_gap_evt->conn_handle);
 
// Assign connection handle to available instance of QWR module.
for (uint32_t i = 0; i < NRF_SDH_BLE_PERIPHERAL_LINK_COUNT; i++)
{
if (m_qwr[i].conn_handle == BLE_CONN_HANDLE_INVALID)
{
err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr[i], p_gap_evt->conn_handle);
APP_ERROR_CHECK(err_code);
break;
}
}
 
if (periph_link_cnt == NRF_SDH_BLE_PERIPHERAL_LINK_COUNT)
{
 
}
else
{
// Continue advertising. More connections can be established because the maximum link count has not been reached.
advertising_start();
}
}
</syntaxhighlight>然后我们再看一下断开<syntaxhighlight lang="c" line="1" start="444">
//******************************************************************
// fn : on_disconnected
//
// brief : 用于处理Disconnected事件的函数。
//
// param : p_gap_evt -> 从BLE堆栈收到的GAP事件
//
// return : none
static void on_disconnected(ble_gap_evt_t const * const p_gap_evt)
{
ret_code_t err_code;
uint32_t periph_link_cnt = ble_conn_state_peripheral_conn_count(); // Number of peripheral links.
 
UART_Printf("Connection 0x%x has been disconnected. Reason: 0x%X\n",
p_gap_evt->conn_handle,
p_gap_evt->params.disconnected.reason);
 
if (periph_link_cnt == 0)
{
APP_ERROR_CHECK(err_code);
}
 
if (periph_link_cnt == (NRF_SDH_BLE_PERIPHERAL_LINK_COUNT - 1))
{
// Advertising is not running when all connections are taken, and must therefore be started.
advertising_start();
}
}
</syntaxhighlight>
=== 一从多主实验 ===
510
个编辑