NRF52832学习笔记(25)

您所在的位置:网站首页 如何开启蓝牙权限 NRF52832学习笔记(25)

NRF52832学习笔记(25)

2023-08-08 04:21| 来源: 网络整理| 查看: 265

一、简介

图中,主从数据发送的数据包TX和RX表示方向性的数据通道,也就是蓝牙的空中属性,空中操作事件都是采用蓝牙操作句柄进行的,因为句柄能够唯一表示各个属性。空中特性的性质包括: 主机RX 从机TX 方向:

通知:从机端上传数据给主机,不需要主机回复一个响应指示:从机端上传数据给主机,需要主机端发一个确认给服务器

通知和指示之间不同之处在于指示有应用层上的确认,而通知没有。 主机TX 从机RX 方向:

写没有回应的写读

Client Characteristic Configuration Descriptor(CCCD) 是客户端特征配置描述符。当主机向CCCD中写入0x0001,此时使能notify;当写入0x0000时,此时禁止notify。 在nordic的协议栈当中,他的这个notify使能是交给用户自己处理的,也是说即便主机没有向cccd中写入0x0001去使能notify,我们同样可以直接利用notify去发送数据,只能这样不符合规范。

二、主机端 2.1 主机设备流程 扫描符合我们连接过滤要求的从机设备成功连接我们的从机设备,并且更新连接参数和 MTU发现服务成功使用了从机服务的 notify 功能 2.2 主机客户端声明

首先,在 main 主函数里,服务的初始化函数 lbs_c_init(),它的主要工作就是对客户端进行初始化,并声明一个LED服务客户端事件回调函数 lbs_c_evt_handler。

/**@brief LED Button client initialization. */ static void lbs_c_init(void) { ret_code_t err_code; ble_lbs_c_init_t lbs_c_init_obj; lbs_c_init_obj.evt_handler = lbs_c_evt_handler; err_code = ble_lbs_c_init(&m_ble_lbs_c, &lbs_c_init_obj); APP_ERROR_CHECK(err_code); } 2.3 主机客户端事件处理

成功发现服务的事件 BLE_LBS_C_EVT_DISCOVERY_COMPLETE,里面首先还是调用 ble_lbs_c_handles_assign 函数将获取的句柄值和我们 m_ble_lbs_c 实例绑定起来。然后就去调用 ble_lbs_c_button_notif_enable 函数去使能从机的 notify 功能。

/**@brief Handles events coming from the LED Button central module. */ static void lbs_c_evt_handler(ble_lbs_c_t * p_lbs_c, ble_lbs_c_evt_t * p_lbs_c_evt) { switch (p_lbs_c_evt->evt_type) { case BLE_LBS_C_EVT_DISCOVERY_COMPLETE: { ret_code_t err_code; err_code = ble_lbs_c_handles_assign(&m_ble_lbs_c, p_lbs_c_evt->conn_handle, &p_lbs_c_evt->params.peer_db); NRF_LOG_INFO("LED Button service discovered on conn_handle 0x%x.", p_lbs_c_evt->conn_handle); err_code = app_button_enable(); APP_ERROR_CHECK(err_code); // LED Button service discovered. Enable notification of Button. err_code = ble_lbs_c_button_notif_enable(p_lbs_c); APP_ERROR_CHECK(err_code); } break; // BLE_LBS_C_EVT_DISCOVERY_COMPLETE case BLE_LBS_C_EVT_BUTTON_NOTIFICATION: { NRF_LOG_INFO("Button state changed on peer to 0x%x.", p_lbs_c_evt->params.button.button_state); if (p_lbs_c_evt->params.button.button_state) { bsp_board_led_on(LEDBUTTON_LED); } else { bsp_board_led_off(LEDBUTTON_LED); } } break; // BLE_LBS_C_EVT_BUTTON_NOTIFICATION default: // No implementation needed. break; } } 2.4 主机客户端使能通知

使能 notify 的函数的代码,其实就是一个 write 功能,不过不是向 handle_value 去发送数据,而是向 cccd_handle 去发送了一个 0x01(BLE_GATT_HVX_NOTIFICATION),0x00 的数据。

uint32_t ble_lbs_c_button_notif_enable(ble_lbs_c_t * p_ble_lbs_c) { VERIFY_PARAM_NOT_NULL(p_ble_lbs_c); if (p_ble_lbs_c->conn_handle == BLE_CONN_HANDLE_INVALID) { return NRF_ERROR_INVALID_STATE; } return cccd_configure(p_ble_lbs_c->conn_handle, p_ble_lbs_c->peer_lbs_db.button_cccd_handle, true); } /**@brief Function for configuring the CCCD. * * @param[in] conn_handle The connection handle on which to configure the CCCD. * @param[in] handle_cccd The handle of the CCCD to be configured. * @param[in] enable Whether to enable or disable the CCCD. * * @return NRF_SUCCESS if the CCCD configure was successfully sent to the peer. */ static uint32_t cccd_configure(uint16_t conn_handle, uint16_t handle_cccd, bool enable) { NRF_LOG_DEBUG("Configuring CCCD. CCCD Handle = %d, Connection Handle = %d", handle_cccd,conn_handle); tx_message_t * p_msg; uint16_t cccd_val = enable ? BLE_GATT_HVX_NOTIFICATION : 0; // 是否是写CCCD p_msg = &m_tx_buffer[m_tx_insert_index++]; m_tx_insert_index &= TX_BUFFER_MASK; p_msg->req.write_req.gattc_params.handle = handle_cccd; p_msg->req.write_req.gattc_params.len = 2;//WRITE_MESSAGE_LENGTH; p_msg->req.write_req.gattc_params.p_value = p_msg->req.write_req.gattc_value; // 要写的值 p_msg->req.write_req.gattc_params.offset = 0; p_msg->req.write_req.gattc_params.write_op = BLE_GATT_OP_WRITE_REQ; p_msg->req.write_req.gattc_value[0] = LSB_16(cccd_val); p_msg->req.write_req.gattc_value[1] = MSB_16(cccd_val); p_msg->conn_handle = conn_handle; p_msg->type = WRITE_REQ; tx_buffer_process(); return NRF_SUCCESS; } 2.5 接收从机数据处理

接收从机数据的处理部分,首先是看到 ble_lbs_c_on_ble_evt 函数,这个函数在我们调用 BLE_LBS_C_DEF(m_lbs_c); 注册实例的时候,就已经创建好了,用于接收底层的 softdevice 的消息返回。 我们看下其中的 BLE_GATTC_EVT_HVX(Handle Value Notification or Indication event) 事件,在这个事件下我们接收到从机发送给我们的数据。

void ble_lbs_c_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context) { if ((p_context == NULL) || (p_ble_evt == NULL)) { return; } ble_lbs_c_t * p_ble_lbs_c = (ble_lbs_c_t *)p_context; switch (p_ble_evt->header.evt_id) // 解析发过来的事件ID { case BLE_GATTC_EVT_HVX: // 接收从机通知 on_hvx(p_ble_lbs_c, p_ble_evt); // 设置触发RX操作事件,接收蓝牙数据 break; case BLE_GATTC_EVT_WRITE_RSP: // 写从机 on_write_rsp(p_ble_lbs_c, p_ble_evt); break; case BLE_GAP_EVT_DISCONNECTED: // 断开连接 on_disconnected(p_ble_lbs_c, p_ble_evt); break; default: break; } }

对于接收到的从机数据的处理,首先还是一样的,我们需要判断一下数据的来源是不是我们 button_handle。当确认都是正确的,然后我们将接收的数据复制给 ble_lbs_c_evt_t,然后通过它的回调上传到我们的 main 文件中,携带的事件ID为 BLE_LBS_C_EVT_BUTTON_NOTIFICATION。

/**@brief Function for handling Handle Value Notification received from the SoftDevice. * * @details This function will uses the Handle Value Notification received from the SoftDevice * and checks if it is a notification of Button state from the peer. If * it is, this function will decode the state of the button and send it to the * application. * * @param[in] p_ble_lbs_c Pointer to the Led Button Client structure. * @param[in] p_ble_evt Pointer to the BLE event received. */ static void on_hvx(ble_lbs_c_t * p_ble_lbs_c, ble_evt_t const * p_ble_evt) { // Check if the event is on the link for this instance if (p_ble_lbs_c->conn_handle != p_ble_evt->evt.gattc_evt.conn_handle) { return; } // Check if this is a Button notification. if ( p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_lbs_c->peer_lbs_db.button_handle) { if (p_ble_evt->evt.gattc_evt.params.hvx.len > 0) { ble_lbs_c_evt_t ble_lbs_c_evt; ble_lbs_c_evt.evt_type = BLE_LBS_C_EVT_BUTTON_NOTIFICATION; // 触发TX操作,接收从机上传数据 ble_lbs_c_evt.conn_handle = p_ble_lbs_c->conn_handle; ble_lbs_c_evt.params.button.button_state = p_ble_evt->evt.gattc_evt.params.hvx.data[0]; ble_lbs_c_evt.data.size = p_ble_evt->evt.gattc_evt.params.hvx.len; // 数据长度 uint8_t temp[p_ble_evt->evt.gattc_evt.params.hvx.len]; memcpy(temp, p_ble_evt->evt.gattc_evt.params.hvx.data, p_ble_evt->evt.gattc_evt.params.hvx.len); ble_lbs_c_evt.data.p_data = temp; // 数据 p_ble_lbs_c->evt_handler(p_ble_lbs_c, &ble_lbs_c_evt); } } }

接下来返回到我们的 main 文件中,我们在 lbs_c_evt_handler 回调中可以看到BLE_LBS_C_EVT_BUTTON_NOTIFICATION 事件的处理,我们将接收到的从机数据用于控制相应的LED灯点亮。

case BLE_LBS_C_EVT_BUTTON_NOTIFICATION: { NRF_LOG_INFO("Button state changed on peer to 0x%x.", p_lbs_c_evt->params.button.button_state); if (p_lbs_c_evt->params.button.button_state) { bsp_board_led_on(LEDBUTTON_LED); } else { bsp_board_led_off(LEDBUTTON_LED); } } break; 三、从机端 3.1 从机设备流程 开启广播被主机成功连接,并交互连接参数等待主机获取服务(一般主机成功获取服务的时间在0.5s~1s之间,这个时间仅供大家参考)等待主机成功使能notify功能从机给主机发送相应的notify数据包 3.2 初始化服务

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

这里我们需要注意的是下面的 cccd_write_access 参数被使能 SEC_OPEN

uint32_t ble_nus_init(ble_nus_t * p_nus, ble_nus_init_t const * p_nus_init) { ret_code_t err_code; ble_uuid_t ble_uuid; ble_uuid128_t nus_base_uuid = NUS_BASE_UUID; ble_add_char_params_t add_char_params; VERIFY_PARAM_NOT_NULL(p_nus); VERIFY_PARAM_NOT_NULL(p_nus_init); // Initialize the service structure. p_nus->data_handler = p_nus_init->data_handler; /**@snippet [Adding proprietary Service to the SoftDevice] */ // Add a custom base UUID. err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type); VERIFY_SUCCESS(err_code); ble_uuid.type = p_nus->uuid_type; ble_uuid.uuid = BLE_UUID_NUS_SERVICE; // Add the service. err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_nus->service_handle); /**@snippet [Adding proprietary Service to the SoftDevice] */ VERIFY_SUCCESS(err_code); // Add the RX Characteristic. memset(&add_char_params, 0, sizeof(add_char_params)); add_char_params.uuid = BLE_UUID_NUS_RX_CHARACTERISTIC; add_char_params.uuid_type = p_nus->uuid_type; add_char_params.max_len = BLE_NUS_MAX_RX_CHAR_LEN; add_char_params.init_len = sizeof(uint8_t); add_char_params.is_var_len = true; add_char_params.char_props.write = 1; add_char_params.char_props.write_wo_resp = 1; add_char_params.read_access = SEC_OPEN; add_char_params.write_access = SEC_OPEN; err_code = characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->rx_handles); if (err_code != NRF_SUCCESS) { return err_code; } // Add the TX Characteristic. /**@snippet [Adding proprietary characteristic to the SoftDevice] */ memset(&add_char_params, 0, sizeof(add_char_params)); add_char_params.uuid = BLE_UUID_NUS_TX_CHARACTERISTIC; add_char_params.uuid_type = p_nus->uuid_type; add_char_params.max_len = BLE_NUS_MAX_TX_CHAR_LEN; add_char_params.init_len = sizeof(uint8_t); add_char_params.is_var_len = true; add_char_params.char_props.notify = 1; add_char_params.read_access = SEC_OPEN; add_char_params.write_access = SEC_OPEN; add_char_params.cccd_write_access = SEC_OPEN; return characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->tx_handles); /**@snippet [Adding proprietary characteristic to the SoftDevice] */ } 3.3 接收通知使能

在 BLE 事件处理的函数中,我们应该要处理 CCCD_Write 的数据的,所以在由 softdevice 返回消息的 ble_nus_on_ble_evt 函数中,我们需要处理一下BLE_GATTS_EVT_WRITE 事件。

void ble_nus_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context) { if ((p_context == NULL) || (p_ble_evt == NULL)) { return; } ble_nus_t * p_nus = (ble_nus_t *)p_context; switch (p_ble_evt->header.evt_id) { case BLE_GAP_EVT_CONNECTED: on_connect(p_nus, p_ble_evt); break; case BLE_GATTS_EVT_WRITE: on_write(p_nus, p_ble_evt); break; case BLE_GATTS_EVT_HVN_TX_COMPLETE: on_hvx_tx_complete(p_nus, p_ble_evt); break; default: // No implementation needed. break; } }

在这个 on_write 函数中,我们接收到了主机发送过来的使能从机 notify 的数据,我们需要判断一下接收的数据的句柄是不是 cccd_handle,以及接收的数据长度是不是2字节(使能数据:01 00)。

/**@brief Function for handling a GATT write event from the SoftDevice. * * @details To provide the start_on_notify_cccd_handle functionality. * * @param[in] p_ble_evt Event from the SoftDevice. */ static void on_write(ble_evt_t const * p_ble_evt) { ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write; // Check if this is the correct CCCD if ((p_evt_write->handle == m_conn_params_config.start_on_notify_cccd_handle) && (p_evt_write->len == 2)) { uint16_t conn_handle = p_ble_evt->evt.gap_evt.conn_handle; ble_conn_params_instance_t * p_instance = instance_get(conn_handle); if (p_instance != NULL) { // Check if this is a 'start notification' if (ble_srv_is_notification_enabled(p_evt_write->data)) { // Do connection parameter negotiation if necessary conn_params_negotiation(conn_handle, p_instance); } else { ret_code_t err_code; // Stop timer if running err_code = app_timer_stop(p_instance->timer_id); if (err_code != NRF_SUCCESS) { send_error_evt(err_code); } } } } } 3.4 从机发送notify数据

首先我们一定要先判断一下是否已经 notify 使能,并且判断数据长度是否符合要求。 下面这个函数,就是我们 notify 发送数据的函数,他的参数我们只需要配置4个。

type 配置为 BLE_GATT_HVX_NOTIFICATION,代表是 notify 属性的数据;handle 我们需要配置为我们按键特征值的 value.handle,代表的是按键特征值的 Value这个列表的句柄;p_data 就是我们需要发送的数据;p_len 数据的长度。 uint32_t ble_nus_data_send(ble_nus_t * p_nus, uint8_t * p_data, uint16_t * p_length, uint16_t conn_handle) { ret_code_t err_code; ble_gatts_hvx_params_t hvx_params; ble_nus_client_context_t * p_client; VERIFY_PARAM_NOT_NULL(p_nus); err_code = blcm_link_ctx_get(p_nus->p_link_ctx_storage, conn_handle, (void *) &p_client); VERIFY_SUCCESS(err_code); if ((conn_handle == BLE_CONN_HANDLE_INVALID) || (p_client == NULL)) { return NRF_ERROR_NOT_FOUND; } if (!p_client->is_notification_enabled) { return NRF_ERROR_INVALID_STATE; } if (*p_length > BLE_NUS_MAX_DATA_LEN) { return NRF_ERROR_INVALID_PARAM; } memset(&hvx_params, 0, sizeof(hvx_params)); hvx_params.handle = p_nus->tx_handles.value_handle; hvx_params.p_data = p_data; hvx_params.p_len = p_length; hvx_params.type = BLE_GATT_HVX_NOTIFICATION; return sd_ble_gatts_hvx(conn_handle, &hvx_params); } 3.5 main.c

首先我们还是需要添加一下服务初始化函数。

/**@brief Function for initializing services that will be used by the application. */ void services_init(void) { uint32_t err_code; ble_nus_init_t nus_init; nrf_ble_qwr_init_t qwr_init = {0}; // Initialize Queued Write Module. qwr_init.error_handler = nrf_qwr_error_handler; err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init); APP_ERROR_CHECK(err_code); // Initialize NUS. memset(&nus_init, 0, sizeof(nus_init)); nus_init.data_handler = nus_data_handler; err_code = ble_nus_init(&m_nus, &nus_init); APP_ERROR_CHECK(err_code); }

当有按键按下时,最终会将按键消息传递到这个回调中进行处理,我们根据按键触发的消息,对相应的 buf 值进行修改,最后调用 ble_btn_data_send 函数将数据发送给主机。

//****************************************************************** // 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_nus_data_send(&m_nus, buf, BTN_UUID_CHAR_LEN, m_conn_handle); }

• 由 Leung 写于 2020 年 9 月 7 日

• 参考:NRF52832DK协议栈实验——22 Notify属性服务实验



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3