赞
踩
nrf52840蓝牙协议栈从机BLE串口,参考蓝牙SDK的example中的ble_app_uart样例。本文主要是分析ble_app_uart样例。
蓝牙从机串口的工作模式是:主机通过蓝牙发送数据到从机,从机接收到蓝牙数据后通过串口转发出去;从机从串口接收数据,将数据通过蓝牙发送给主机。
蓝牙串口主要有三部分的工作,第一部分是建立串口,第二部分是建立BLE,第三部分是搭建蓝牙和串口的双向数据通道。本文只重点分析串口的建立和蓝牙服务的建立及处理,其余的蓝牙通用的配置分析参见文章nrf52840蓝牙协议栈样例分析
串口的接收数据使用串口中断,所以需要在需要在 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_SIZE Size of desired RX buffer, must be a power of 2 or ZERO (No FIFO). * @param[in] TX_BUF_SIZE Size 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_t rx_buf[RX_BUF_SIZE]; \ static uint8_t tx_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 中断优先级。
我们在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 指针指向下一个接收字节存放的地址。
返回值: 如果收到成功收到字节, 则返回成功。
在这个中断函数内要发送蓝牙数据。实现蓝牙从机接收串口数据,通过蓝牙发送给主机的功能。
串口发送数据函数的原型为:uint32_t app_uart_put(uint8_t byte)
返回值: NRF_SUCCESS 如果通过 TX 缓冲把字节发送出去, 则返回成功。
返回值: NRF_ERROR_NO_MEM 如果在 TX 缓冲中没有更多的空间。 常用在流控控制中
返回值: NRF_ERROR_INTERNAL 如果串口驱动报错。
蓝牙服务初始化函数为:
/**@brief Function for initializing services that will be used by the application. */ static 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); }
在添加服务的时候,现将nus_data_handler函数的指针指向蓝牙数据处理的地方。
初始化蓝牙服务函数为
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] */ }
该函数首先设置主服务的UUID,然后将nus_data_handler函数的指针指向 p_nus->data_handler。nus_data_handler函数主要是进行数据接收的处理。最后添加蓝牙接收和蓝牙传输的特征值。
当底层协议栈需要通知应用程序一些有关它的事情的时候,就会发生对应的协议栈事件,这个事件就会上抛给应用,触发应用层执行响应的操作。
在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_nus Nordic 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_t err_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->tx_handles.cccd_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。
在蓝牙中断函数uart_event_handle内,通过app_uart_get函数读取串口数据,再通过ble_nus_data_send函数将数据发送出去。
在蓝牙事件的派发函数ble_nus_on_ble_evt内处理蓝牙写事件,调用on_write函数进行蓝牙写数据的处理。on_write内通过函数指针指向了 nus_data_handler函数,nus_data_handler函数调用app_uart_put函数将数据通过串口发送出去。
如果是为了方便理解,on_write函数内可以直接写一个串口发送的函数,而不用通过指针进行跳转。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。