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_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 中断优先级。
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_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函数的指针指向蓝牙数据处理的地方。
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;
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函数主要是进行数据接收的处理。最后添加蓝牙接收和蓝牙传输的特征值。
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_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。
三、搭建双向数据通道
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函数内可以直接写一个串口发送的函数,而不用通过指针进行跳转。