900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > nrf52840蓝牙协议栈从机BLE串口

nrf52840蓝牙协议栈从机BLE串口

时间:2024-07-21 15:15:51

相关推荐

nrf52840蓝牙协议栈从机BLE串口

nrf52840蓝牙协议栈从机BLE串口,参考蓝牙SDK的example中的ble_app_uart样例。本文主要是分析ble_app_uart样例。

蓝牙从机串口的工作模式是:主机通过蓝牙发送数据到从机,从机接收到蓝牙数据后通过串口转发出去;从机从串口接收数据,将数据通过蓝牙发送给主机。

蓝牙串口主要有三部分的工作,第一部分是建立串口,第二部分是建立BLE,第三部分是搭建蓝牙和串口的双向数据通道。本文只重点分析串口的建立和蓝牙服务的建立及处理,其余的蓝牙通用的配置分析参见文章nrf52840蓝牙协议栈样例分析

一、串口

1.1、串口初始化

串口的接收数据使用串口中断,所以需要在需要在 sdk_config.h 文件中勾选 UARTE功能。在main.c文件中,串口初始化函数为:

/**@brief Function for initializing the UART module.*//**@snippet [UART Initialization] */static void uart_init(void){uint32_t err_code;app_uart_comm_params_t const comm_params ={.rx_pin_no = RX_PIN_NUMBER,.tx_pin_no = TX_PIN_NUMBER,.rts_pin_no = RTS_PIN_NUMBER,.cts_pin_no = CTS_PIN_NUMBER,.flow_control = APP_UART_FLOW_CONTROL_DISABLED,.use_parity = false,#if defined (UART_PRESENT).baud_rate = NRF_UART_BAUDRATE_115200#else.baud_rate = NRF_UARTE_BAUDRATE_115200#endif};APP_UART_FIFO_INIT(&comm_params,UART_RX_BUF_SIZE,UART_TX_BUF_SIZE,uart_event_handle,APP_IRQ_PRIORITY_LOWEST,err_code);APP_ERROR_CHECK(err_code);}

在串口初始化函数里面,首先声明一个comm_params结构体,这个结构体是串口参数配置的基本内容。uart 的 初 始 化 函 数有两个:APP_UART_FIFO_INIT 和APP_UART_INIT, 一个是带 FIFO 缓冲的初始化串口函数, 一个是不带 FIFO 缓冲的初始化函数,一般情况下使用带软件缓冲的 FIFO 的函数, 减小数据溢出错误的发生几率。

APP_UART_FIFO_INIT函数为:

/**@brief Macro for safe initialization of the UART module in a single user instance when using* a FIFO together with UART.** @param[in] P_COMM_PARAMS Pointer to a UART communication structure: app_uart_comm_params_t* @param[in] RX_BUF_SIZESize of desired RX buffer, must be a power of 2 or ZERO (No FIFO).* @param[in] TX_BUF_SIZESize of desired TX buffer, must be a power of 2 or ZERO (No FIFO).* @param[in] EVT_HANDLER Event handler function to be called when an event occurs in the*UART module.* @param[in] IRQ_PRIO IRQ priority, app_irq_priority_t, for the UART module irq handler.* @param[out] ERR_CODE The return value of the UART initialization function will be*written to this parameter.** @note Since this macro allocates a buffer and registers the module as a GPIOTE user when flow* control is enabled, it must only be called once.*/#define APP_UART_FIFO_INIT(P_COMM_PARAMS, RX_BUF_SIZE, TX_BUF_SIZE, EVT_HANDLER, IRQ_PRIO, ERR_CODE) \do \{\app_uart_buffers_t buffers; \static uint8_trx_buf[RX_BUF_SIZE]; \static uint8_ttx_buf[TX_BUF_SIZE]; \\buffers.rx_buf= rx_buf; \buffers.rx_buf_size = sizeof (rx_buf); \buffers.tx_buf= tx_buf; \buffers.tx_buf_size = sizeof (tx_buf); \ERR_CODE = app_uart_init(P_COMM_PARAMS, &buffers, EVT_HANDLER, IRQ_PRIO); \} while (0)

函数内部申请两个软件 BUF 缓冲空间提供给 RX 和 TX。 然后调用函数 app_uart_init 进行串口的初始化。 app_uart_init 的原型为:

uint32_t app_uart_init(const app_uart_comm_params_t * p_comm_params,app_uart_buffers_t * p_buffers,app_uart_event_handler_t event_handler,app_irq_priority_t irq_priority)

该函数的参数的作用为:

p_comm_params Pin 和通信参数。

p_buffers RX 和 TX 缓冲区, NULL 是 FIFO 不使用。

event_handler中断回调函数。

irq_priority 中断优先级。

1.2、串口中断接收数据

我们在uart_init函数内初始化串口的时候设置带 FIFO 缓冲的串口, 在 APP_UART_FIFO_INIT 函数中, 申请一个 uart_event_handle中断回调处理函数, 具体代码如下所示:

/**@brief Function for handling app_uart events.** @details This function will receive a single character from the app_uart module and append it to*a string. The string will be be sent over BLE when the last character received was a*'new line' '\n' (hex 0x0A) or if the string has reached the maximum data length.*//**@snippet [Handling the data received over UART] */void uart_event_handle(app_uart_evt_t * p_event){static uint8_t data_array[BLE_NUS_MAX_DATA_LEN];static uint8_t index = 0;uint32_t err_code;switch (p_event->evt_type){case APP_UART_DATA_READY:UNUSED_VARIABLE(app_uart_get(&data_array[index]));index++;if ((data_array[index - 1] == '\n') ||(data_array[index - 1] == '\r') ||(index >= m_ble_nus_max_data_len)){if (index > 1){NRF_LOG_DEBUG("Ready to send data over BLE NUS");NRF_LOG_HEXDUMP_DEBUG(data_array, index);do{uint16_t length = (uint16_t)index;err_code = ble_nus_data_send(&m_nus, data_array, &length, m_conn_handle);if ((err_code != NRF_ERROR_INVALID_STATE) &&(err_code != NRF_ERROR_RESOURCES) &&(err_code != NRF_ERROR_NOT_FOUND)){APP_ERROR_CHECK(err_code);}} while (err_code == NRF_ERROR_RESOURCES);}index = 0;}break;case APP_UART_COMMUNICATION_ERROR:APP_ERROR_HANDLER(p_event->data.error_communication);break;case APP_UART_FIFO_ERROR:APP_ERROR_HANDLER(p_event->data.error_code);break;default:break;}}

这个派发函数根据分配的事件类型来分配中断事件处理类型。在接收到数据事件类型里面调用app_uart_get()函数来接收数据。该函数原型为:

uint32_t app_uart_get(uint8_t * p_byte);

参数 p_byte 指针指向下一个接收字节存放的地址。

返回值: 如果收到成功收到字节, 则返回成功。

在这个中断函数内要发送蓝牙数据。实现蓝牙从机接收串口数据,通过蓝牙发送给主机的功能。

1.3、串口发送数据

串口发送数据函数的原型为:uint32_t app_uart_put(uint8_t byte)

返回值: NRF_SUCCESS 如果通过 TX 缓冲把字节发送出去, 则返回成功。

返回值: NRF_ERROR_NO_MEM 如果在 TX 缓冲中没有更多的空间。 常用在流控控制中

返回值: NRF_ERROR_INTERNAL 如果串口驱动报错。

二、BLE

2.1、蓝牙服务初始化

蓝牙服务初始化函数为:

/**@brief Function for initializing services that will be used by the application.*/static void services_init(void){uint32_t err_code;ble_nus_init_tnus_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);}

在添加服务的时候,现将nus_data_handler函数的指针指向蓝牙数据处理的地方。

2.1.1、初始化蓝牙服务

初始化蓝牙服务函数为

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;cd_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] */}

该函数首先设置主服务的UUID,然后将nus_data_handler函数的指针指向 p_nus->data_handler。nus_data_handler函数主要是进行数据接收的处理。最后添加蓝牙接收和蓝牙传输的特征值。

2.2、蓝牙事件派发函数

当底层协议栈需要通知应用程序一些有关它的事情的时候,就会发生对应的协议栈事件,这个事件就会上抛给应用,触发应用层执行响应的操作。

在ble_nus.h文件内,有

/**@brief Macro for defining a ble_nus instance.** @param_name Name of the instance.* @param[in] _nus_max_clients Maximum number of NUS clients connected at a time.* @hideinitializer*/#define BLE_NUS_DEF(_name, _nus_max_clients) \BLE_LINK_CTX_MANAGER_DEF(CONCAT_2(_name, _link_ctx_storage), \(_nus_max_clients), \sizeof(ble_nus_client_context_t)); \static ble_nus_t _name = \{\.p_link_ctx_storage = &CONCAT_2(_name, _link_ctx_storage) \};\NRF_SDH_BLE_OBSERVER(_name ## _obs, \BLE_NUS_BLE_OBSERVER_PRIO,\ble_nus_on_ble_evt, \&_name)

在这里定义了一个ble_nus_on_ble_evt函数来处理协议栈事件。

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;}}

当主机写入数据,会触发ble_nus_on_ble_evt函数内BLE_GATTS_EVT_WRITE的条件,进而触发on_write函数

/**@brief Function for handling the @ref BLE_GATTS_EVT_WRITE event from the SoftDevice.** @param[in] p_nusNordic UART Service structure.* @param[in] p_ble_evt Pointer to the event received from BLE stack.*/static void on_write(ble_nus_t * p_nus, ble_evt_t const * p_ble_evt){ret_code_terr_code;ble_nus_evt_t evt;ble_nus_client_context_t * p_client;ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;err_code = blcm_link_ctx_get(p_nus->p_link_ctx_storage,p_ble_evt->evt.gatts_evt.conn_handle,(void *) &p_client);if (err_code != NRF_SUCCESS){NRF_LOG_ERROR("Link context for 0x%02X connection handle could not be fetched.",p_ble_evt->evt.gatts_evt.conn_handle);}memset(&evt, 0, sizeof(ble_nus_evt_t));evt.p_nus = p_nus;evt.conn_handle = p_ble_evt->evt.gatts_evt.conn_handle;evt.p_link_ctx = p_client;if ((p_evt_write->handle == p_nus->cd_handle) &&(p_evt_write->len == 2))//判断是不是CCCD写使能,如果是,配置通知使能为真{if (p_client != NULL){if (ble_srv_is_notification_enabled(p_evt_write->data)){p_client->is_notification_enabled = true;evt.type= BLE_NUS_EVT_COMM_STARTED;}else{p_client->is_notification_enabled = false;evt.type= BLE_NUS_EVT_COMM_STOPPED;}if (p_nus->data_handler != NULL){p_nus->data_handler(&evt);}}}else if ((p_evt_write->handle == p_nus->rx_handles.value_handle) &&(p_nus->data_handler != NULL)){evt.type = BLE_NUS_EVT_RX_DATA;evt.params.rx_data.p_data = p_evt_write->data;evt.params.rx_data.length = p_evt_write->len;p_nus->data_handler(&evt);}else{// Do Nothing. This event is not relevant for this service.}}

最后一句的p_nus->data_handler(&evt);是调用data_handler函数处理接收的数据。data_handler是一个函数指针,指向的是nus_data_handler函数,该函数在服务初始化services_init时赋值给了data_handler。

三、搭建双向数据通道

3.1、串口转蓝牙

在蓝牙中断函数uart_event_handle内,通过app_uart_get函数读取串口数据,再通过ble_nus_data_send函数将数据发送出去。

3.2、蓝牙转串口

在蓝牙事件的派发函数ble_nus_on_ble_evt内处理蓝牙写事件,调用on_write函数进行蓝牙写数据的处理。on_write内通过函数指针指向了nus_data_handler函数,nus_data_handler函数调用app_uart_put函数将数据通过串口发送出去。

如果是为了方便理解,on_write函数内可以直接写一个串口发送的函数,而不用通过指针进行跳转。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。