编写nrf52840蓝牙协议栈LED灯的读写任务,可以参考蓝牙SDK的example中的ble_app_blinky样例。
在编写nrf52840蓝牙协议栈LED灯的读写任务前需要先了解nrf52840的蓝牙协议,然后再了解nrf52840协议栈事件处理的基本原理,最后再分析代码流程。本文只重点分析LED灯的控制和蓝牙服务的建立及处理,其余的蓝牙通用的配置分析参见文章nrf52840蓝牙协议栈样例分析。
一、nrf52840蓝牙协议概述
nrf52840蓝牙协议栈分为三层,分别为:应用层、主协议层、控制层。
1.1、应用层
应用层profile就是建立的蓝牙应用任务。蓝牙应用任务可以分为两类:标准蓝牙任务规范profile(公有任务)、非标准蓝牙任务规范profile(私有任务)。标准蓝牙任务规范profile指的是SIG发布的GATT规范列表。非标准蓝牙任务规范profile(私有任务)是供应商自定义的任务。
1.2、主协议层
主协议层包括通用访问规范GAP、通用属性配置GATT、逻辑链路控制与适配协议L2CAP、属性协议ATT、安全管理协议。
1.2.1、GAP(Generic Access Profile)
GAP是对LL层有效数据包如何进行解析的两种方式中的一种,GAP主要用来进行广播,扫描和发起连接等。
BLE的物理信道一共40个,分成了两组的,一组是广播(Advertising)信道(3个),一组是数据(Data)信道(37个)。BLE设备在连接时要区分角色,主机或从机。在连接发起前,从机要主动在广播信道发起广播数据,一般为31个字节。主机在广播信道里面接收到广播数据后,可以发起连接,然后连接该从机设备。
1.2.2、GATT(Generic attribute profile)
GATT用来规范attribute中的数据内容,并运用分组的概念对attribute进行分类管理。
GATT是真正传输数据所在的层,GATT定义了两类角色:服务器和客户端。提供数据的设备称为GATT服务器,获取数据的设备称为GATT客户端。我们可以通过一个服务配置列表文件,定义多个服务,而这个服务配置列表文件称为profile。服务作为最小的数据传输单元,又包含一个或多个特性characteristic。
1.2.3、L2CAP(Logic link control and adaptation protocol)
L2CAP对LL进行了简单的封装,就是区分是加密通道还是普通通道。
1.2.4、SMP(Secure manager protocol)
SMP用来管理BLE连接的加密和安全。
1.2.5、ATT(Attribute protocol)
ATT层用来定义用户命令及命令操作的数据,比如读取某个数据或者写某个数据。
1.3、控制层
控制层分为三部分.
1、主机控制器(HCI),也称为设备管理器。设备管理器是基带中的一个功能模块,负责所有与数据无关的蓝牙系统操作。
2、链路层(LL)。链路层主要负责链路管理,链路控制。
3、物理层(PHY)。物理层负责从物理信道传输和接收信息数据包。
二、协议栈事件处理
nrf52840蓝牙协议栈LED灯的读写任务,包含主机向从机写数据,从机进行响应的动作响应。这里面用到了协议栈调度系统。
因为nrf52840蓝牙协议栈是不公开的,所以,在处理协议栈的相关任务时需要用到协议栈调度系统。所谓的协议栈调度系统,相当于编写一个协议的任务处理函数,通过一定的方法或者API注册进入协议栈内。或者也可以理解为回调函数注册的方法称为协议栈调度系统。
ble_app_blinky工程中添加协议栈事件处理的方法步骤为:
2.1、声明调用函数
在ble_lbs.h文件中添加如下代码。
/**@brief Macro for defining a ble_lbs instance.
*
* @param _name Name of the instance.
* @hideinitializer
*/
#define BLE_LBS_DEF(_name) \
static ble_lbs_t _name; \
NRF_SDH_BLE_OBSERVER(_name ## _obs, \
BLE_LBS_BLE_OBSERVER_PRIO, \
ble_lbs_on_ble_evt, &_name)
首先添加了一个服务的数据结构为全局静态变量。
然后通过观察者模式的方法,将调度事件派发函数ble_lbs_on_ble_evt和名字_name绑定。
2.2、定义调用函数
在main.c文件中将上述声明进行定义,即在RAM中分配内存空间
BLE_LBS_DEF(m_lbs);
2.3、编写回调函数
当协议栈需要通知应用程序一些关于它的事情的时候,协议栈事件就被上抛给应用(回调函数)。
void ble_lbs_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{
ble_lbs_t * p_lbs = (ble_lbs_t *)p_context;
switch (p_ble_evt->header.evt_id)
{
case BLE_GATTS_EVT_WRITE:
on_write(p_lbs, p_ble_evt);
break;
default:
// No implementation needed.
break;
}
}
2.4、添加(注册)协议栈事件处理函数
在services_init服务初始化函数中注册协议栈事件处理函数。
/**@brief Function for initializing services that will be used by the application.
*/
static void services_init(void)
{
ret_code_t err_code;
ble_lbs_init_t init;
nrf_ble_qwr_init_t qwr_init = {0};
// Initialize Queued Write Module instances.
qwr_init.error_handler = nrf_qwr_error_handler;
for (uint32_t i = 0; i < LINK_TOTAL; i++)
{
err_code = nrf_ble_qwr_init(&m_qwr[i], &qwr_init);
APP_ERROR_CHECK(err_code);
}
// Initialize LBS.
init.led_write_handler = led_write_handler;
err_code = ble_lbs_init(&m_lbs, &init);
APP_ERROR_CHECK(err_code);
ble_conn_state_init();
}
三、ble_app_blinky代码分析
3.1、声明和定义协议栈事件处理
在ble_lbs.h文件内添加。
/**@brief Macro for defining a ble_lbs instance.
*
* @param _name Name of the instance.
* @hideinitializer
*/
#define BLE_LBS_DEF(_name) \
static ble_lbs_t _name; \
NRF_SDH_BLE_OBSERVER(_name ## _obs, \
BLE_LBS_BLE_OBSERVER_PRIO, \
ble_lbs_on_ble_evt, &_name)
在main.c 文件内添加
BLE_LBS_DEF(m_lbs); /**< LED Button Service instance. */
3.2、初始化LED服务
3.2.1、初始化服务
在main函数中调用services_init函数进行服务的初始化。
/**@brief Function for initializing services that will be used by the application.
*/
static void services_init(void)
{
ret_code_t err_code;
ble_lbs_init_t init = {0};
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 LBS.LED写回调函数
init.led_write_handler = led_write_handler;
//初始化LED写服务
err_code = ble_lbs_init(&m_lbs, &init);
APP_ERROR_CHECK(err_code);
}
服务的初始化里面会调用ble_lbs_init函数进行LED写服务的初始化。
3.2.2、初始化LED写服务
初始化LED写服务内容如下
uint32_t ble_lbs_init(ble_lbs_t * p_lbs, const ble_lbs_init_t * p_lbs_init)
{
uint32_t err_code;
ble_uuid_t ble_uuid;
ble_add_char_params_t add_char_params;
// Initialize service structure.
p_lbs->led_write_handler = p_lbs_init->led_write_handler;
// Add service.
ble_uuid128_t base_uuid = {LBS_UUID_BASE};
err_code = sd_ble_uuid_vs_add(&base_uuid, &p_lbs->uuid_type);
VERIFY_SUCCESS(err_code);
ble_uuid.type = p_lbs->uuid_type;
ble_uuid.uuid = LBS_UUID_SERVICE;
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_lbs->service_handle);
VERIFY_SUCCESS(err_code);
// Add Button characteristic.
memset(&add_char_params, 0, sizeof(add_char_params));
add_char_params.uuid = LBS_UUID_BUTTON_CHAR;
add_char_params.uuid_type = p_lbs->uuid_type;
add_char_params.init_len = sizeof(uint8_t);
add_char_params.max_len = sizeof(uint8_t);
add_char_params.char_props.read = 1;
add_char_params.char_props.notify = 1;
add_char_params.read_access = SEC_OPEN;
add_char_params.cccd_write_access = SEC_OPEN;
err_code = characteristic_add(p_lbs->service_handle,
&add_char_params,
&p_lbs->button_char_handles);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
// Add LED characteristic.
memset(&add_char_params, 0, sizeof(add_char_params));
add_char_params.uuid = LBS_UUID_LED_CHAR;
add_char_params.uuid_type = p_lbs->uuid_type;
add_char_params.init_len = sizeof(uint8_t);
add_char_params.max_len = sizeof(uint8_t);
add_char_params.char_props.read = 1;
add_char_params.char_props.write = 1;
add_char_params.read_access = SEC_OPEN;
add_char_params.write_access = SEC_OPEN;
return characteristic_add(p_lbs->service_handle, &add_char_params, &p_lbs->led_char_handles);
}
在函数里面先用sd_ble_uuid_vs_add函数添加一个服务,这个函数有三个参数。第一个参数为服务类型(主服务或者次服务),第二个参数是基础UUID,第三个参数为服务句柄,这是个输出变量,这个句柄可以在后面识别不同的服务。
然后函数characteristic_add添加特征值,添加LED特征值具体为:
// Add LED characteristic.
memset(&add_char_params, 0, sizeof(add_char_params));
add_char_params.uuid = LBS_UUID_LED_CHAR;
add_char_params.uuid_type = p_lbs->uuid_type;
add_char_params.init_len = sizeof(uint8_t);
add_char_params.max_len = sizeof(uint8_t);
add_char_params.char_props.read = 1;
add_char_params.char_props.write = 1;
add_char_params.read_access = SEC_OPEN;
add_char_params.write_access = SEC_OPEN;
return characteristic_add(p_lbs->service_handle, &add_char_params, &p_lbs->led_char_handles);
特征值添加characteristic_add函数有三个参数,第一个参数为服务句柄,是上面服务添加函数里面输出的句柄。第三个参数是协议栈上抛事件句柄。当协议栈出现相应的事件时,协议栈会上抛此句柄,在上层应用的协议栈事件处理函数,就可以根据此句柄判断事件是否触发。第二个参数为 一个ble_add_char_params_t类型的结构体。ble_add_char_params_t定义如下:
/**@brief Add characteristic parameters structure.
* @details This structure contains the parameters needed to use the @ref characteristic_add function.
*/
typedef struct
{
uint16_t uuid; /**< Characteristic UUID (16 bits UUIDs).*/
uint8_t uuid_type; /**< Base UUID. If 0, the Bluetooth SIG UUID will be used. Otherwise, this should be a value returned by @ref sd_ble_uuid_vs_add when adding the base UUID.*/
uint16_t max_len; /**< Maximum length of the characteristic value.*/
uint16_t init_len; /**< Initial length of the characteristic value.*/
uint8_t * p_init_value; /**< Initial encoded value of the characteristic.*/
bool is_var_len; /**< Indicates if the characteristic value has variable length.*/
ble_gatt_char_props_t char_props; /**< Characteristic properties.*/
ble_gatt_char_ext_props_t char_ext_props; /**< Characteristic extended properties.*/
bool is_defered_read; /**< Indicate if deferred read operations are supported.*/
bool is_defered_write; /**< Indicate if deferred write operations are supported.*/
security_req_t read_access; /**< Security requirement for reading the characteristic value.*/
security_req_t write_access; /**< Security requirement for writing the characteristic value.*/
security_req_t cccd_write_access; /**< Security requirement for writing the characteristic's CCCD.*/
bool is_value_user; /**< Indicate if the content of the characteristic is to be stored in the application (user) or in the stack.*/
ble_add_char_user_desc_t *p_user_descr; /**< Pointer to user descriptor if needed*/
ble_gatts_char_pf_t *p_presentation_format; /**< Pointer to characteristic format if needed*/
} ble_add_char_params_t;
3.3、添加协议栈事件处理函数
在声明协议栈事件时,定义了协议栈事件处理函数ble_lbs_on_ble_evt,定义如下
void ble_lbs_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{
ble_lbs_t * p_lbs = (ble_lbs_t *)p_context;
switch (p_ble_evt->header.evt_id)
{
case BLE_GATTS_EVT_WRITE:
on_write(p_lbs, p_ble_evt);
break;
default:
// No implementation needed.
break;
}
}
当LED特征被写入数据时,产生了GATT写事件,就会调用on_write函数。
/**@brief Function for handling the Write event.
*
* @param[in] p_lbs LED Button Service structure.
* @param[in] p_ble_evt Event received from the BLE stack.
*/
static void on_write(ble_lbs_t * p_lbs, ble_evt_t const * p_ble_evt)
{
ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
if ( (p_evt_write->handle == p_lbs->led_char_handles.value_handle)
&& (p_evt_write->len == 1)
&& (p_lbs->led_write_handler != NULL))
{
p_lbs->led_write_handler(p_ble_evt->evt.gap_evt.conn_handle, p_lbs, p_evt_write->data[0]);
}
}
on_write函数会调用led_write_handler函数进行处理。在这里led_write_handler是一个指针函数。指向的函数为:
/**@brief Function for handling write events to the LED characteristic.
*
* @param[in] p_lbs Instance of LED Button Service to which the write applies.
* @param[in] led_state Written/desired state of the LED.
*/
static void led_write_handler(uint16_t conn_handle, ble_lbs_t * p_lbs, uint8_t led_state)
{
if (led_state)
{
bsp_board_led_on(LEDBUTTON_LED);
NRF_LOG_INFO("Received LED ON from link 0x%x!", conn_handle);
}
else
{
bsp_board_led_off(LEDBUTTON_LED);
NRF_LOG_INFO("Received LED OFF from link 0x%x!", conn_handle);
}
}
在 3.2.1章节的初始化服务函数services_init中有如下定义
// Initialize LBS.
init.led_write_handler = led_write_handler;
err_code = ble_lbs_init(&m_lbs, &init);
将函数的指针传入到ble_lbs_init函数内。在3.2.2章节初始化LED写服务函数ble_lbs_init内有
// Initialize service structure.
p_lbs->led_write_handler = p_lbs_init->led_write_handler;
至此,将处理函数指针注册进进了协议栈内。