nrf52840蓝牙协议栈LED灯的读写任务

  编写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;

  至此,将处理函数指针注册进进了协议栈内。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值