美文网首页物联网loT从业者物联网相关技术研究
NRF52832学习笔记(9)——GAP从机端广播

NRF52832学习笔记(9)——GAP从机端广播

作者: Leung_ManWah | 来源:发表于2020-02-06 21:27 被阅读0次

    一、背景

    1.1 蓝牙协议栈

    链路层(LL)控制设备的射频状态,有五个设备状态:待机、广播、扫描、初始化和连接。

    广播 为广播数据包,而 扫描 则是监听广播。

    GAP通信中角色,中心设备(Central - 主机) 用来扫描和连接 外围设备(Peripheral - 从机)

    大部分情况下外围设备通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。

    也有些情况是不需要连接的,只要外设广播自己的数据即可,用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。

    1.2 从机广播

    从机(外围设备)要被主机连接,那么它就必须先被主机发现。这个时候,从机设备把自身信息以广播形式发射出去。

    比如设备A需要先进行广播,即 设备A(Advertiser)不断发送如下广播信号,t 为广播间隔。每发送一次广播包,我们称其为一次 广播事件(advertising event),因此 t 也称为广播事件间隔,如下图所示。广播事件是一阵一阵的,每次会是有一个持续时间的,蓝牙芯片只有在广播事件期间才打开射频模块发射广播,这个时候功耗比较高,其余时间蓝牙芯片都处于idle待机状态,因此平均功耗就非常低。

    当广播发出的时候,每一个广播事件包含三个广播包,即分别在 37/38/39 三个通道上同时广播相同的信息。下图 observer 为主机观察者,advertiser 就是从机广播。

    二、配置广播参数

    2.1 广播参数相关宏及变量

    在 main.c 中

    #define DEVICE_NAME                     "Nordic_Template"       /**< Name of device. Will be included in the advertising data. */
    #define MANUFACTURER_NAME               "NordicSemiconductor"   /**< Manufacturer. Will be passed to Device Information Service. */
    #define APP_ADV_INTERVAL                300                     /**< The advertising interval (in units of 0.625 ms. This value corresponds to 187.5 ms). */
    
    #define APP_ADV_DURATION                0//18000                /**< The advertising duration (180 seconds) in units of 10 milliseconds. */
    
    // YOUR_JOB: Use UUIDs for service(s) used in your application.
    static ble_uuid_t m_adv_uuids[] =                                               /**< Universally unique service identifiers. */
    {
        {BLE_UUID_DEVICE_INFORMATION_SERVICE, BLE_UUID_TYPE_BLE}
    };
    

    2.2 初始化广播参数

    在 main.c 中

    /**@brief Function for application main entry.
     */
    int main(void)
    {
        ···
        ···
        /*-------------------------- 蓝牙协议栈初始化 ---------------------------*/
        advertising_init();                 // 广播初始化
        /*-------------------------- 开启应用 ---------------------------*/
        // Start execution.
        NRF_LOG_INFO("Template example started."); 
        advertising_start(erase_bonds);    // 开启广播  
    
        // Enter main loop.
        for(;;)
        {
            idle_state_handle();
        }
    }
    

    2.2.1 advertising_init

    /**@brief Function for initializing the Advertising functionality.
     */
    static void advertising_init(void)
    {
        ret_code_t err_code;
        ble_advertising_init_t init;
    
        memset(&init, 0, sizeof(init));
        
        // 初始化广播数据包内容
        init.advdata.name_type               = BLE_ADVDATA_FULL_NAME;  // 显示全名
        init.advdata.include_appearance      = true;  // 显示图标
        init.advdata.flags                   = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
        init.advdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
        init.advdata.uuids_complete.p_uuids  = m_adv_uuids;
    
        init.config.ble_adv_fast_enabled  = true;              // 广播类型,快速广播             
        init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;  // 广播间隔
        init.config.ble_adv_fast_timeout  = APP_ADV_DURATION;  // 广播超时时间,值0则保持一种广播模式不变
    
        init.evt_handler = on_adv_evt;
    
        err_code = ble_advertising_init(&m_advertising, &init);// 初始化广播,导入参数
        APP_ERROR_CHECK(err_code);
    
        ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);// 设置广播识别号
    }
    

    广播初始化实际上就是初始化两个结构体,一个是 &advdata 广播数据,一个是 &config 选择项。

    设置完广播参数后,通过函数 ble_advertising_init() 配置广播参数到协议栈中。

    2.3 广播参数定义

    &advdata 广播数据定义了广播的基本参数,比如名称类型,最短名称长度,设备模式,发射功率等等,不是所有参数你都需要在初始化函数中进行设置。根据需求选择。

    /**@brief Advertising data structure. This structure contains all options and data needed for encoding and
     *        setting the advertising data. */
    typedef struct
    {
        ble_advdata_name_type_t      name_type;                           /**< Type of device name. */
        uint8_t                      short_name_len;                      /**< Length of short device name (if short type is specified). */
        bool                         include_appearance;                  /**< Determines if Appearance shall be included. */
        uint8_t                      flags;                               /**< Advertising data Flags field. */
        int8_t *                     p_tx_power_level;                    /**< TX Power Level field. */
        ble_advdata_uuid_list_t      uuids_more_available;                /**< List of UUIDs in the 'More Available' list. */
        ble_advdata_uuid_list_t      uuids_complete;                      /**< List of UUIDs in the 'Complete' list. */
        ble_advdata_uuid_list_t      uuids_solicited;                     /**< List of solicited UUIDs. */
        ble_advdata_conn_int_t *     p_slave_conn_int;                    /**< Slave Connection Interval Range. */
        ble_advdata_manuf_data_t *   p_manuf_specific_data;               /**< Manufacturer specific data. */
        ble_advdata_service_data_t * p_service_data_array;                /**< Array of Service data structures. */
        uint8_t                      service_data_count;                  /**< Number of Service data structures. */
        bool                         include_ble_device_addr;             /**< Determines if LE Bluetooth Device Address shall be included. */
        ble_advdata_le_role_t        le_role;                             /**< LE Role field. Included when different from @ref BLE_ADVDATA_ROLE_NOT_PRESENT. @warning This field can be used only for NFC. For BLE advertising, set it to NULL. */
        ble_advdata_tk_value_t *     p_tk_value;                          /**< Security Manager TK value field. Included when different from NULL. @warning This field can be used only for NFC. For BLE advertising, set it to NULL.*/
        uint8_t *                    p_sec_mgr_oob_flags;                 /**< Security Manager Out Of Band Flags field. Included when different from NULL. @warning This field can be used only for NFC. For BLE advertising, set it to NULL.*/
        ble_gap_lesc_oob_data_t *    p_lesc_data;                         /**< LE Secure Connections OOB data. Included when different from NULL. @warning This field can be used only for NFC. For BLE advertising, set it to NULL.*/
    } ble_advdata_t;
    

    2.3.1 名称类型name_type

    设置广播里,显示广播名称的三种类型:

    /**@brief Advertising data name type. This enumeration contains the options available for the device name inside
     *        the advertising data. */
    typedef enum
    {
        BLE_ADVDATA_NO_NAME,                                              /**< Include no device name in advertising data. */
        BLE_ADVDATA_SHORT_NAME,                                           /**< Include short device name in advertising data. */
        BLE_ADVDATA_FULL_NAME                                             /**< Include full device name in advertising data. */
    } ble_advdata_name_type_t;
    

    2.3.2 展示图标include_appearance

    设置是否需要展示图标
    init.advdata.include_appearance = true; // 显示图标

    2.3.3 蓝牙设备模式flags

    /**@defgroup BLE_GAP_ADV_FLAGS GAP Advertisement Flags
     * @{ */
    #define BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE         (0x01)   /**< LE Limited Discoverable Mode. */
    #define BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE         (0x02)   /**< LE General Discoverable Mode. */
    #define BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED         (0x04)   /**< BR/EDR not supported. */
    #define BLE_GAP_ADV_FLAG_LE_BR_EDR_CONTROLLER         (0x08)   /**< Simultaneous LE and BR/EDR, Controller. */
    #define BLE_GAP_ADV_FLAG_LE_BR_EDR_HOST               (0x10)   /**< Simultaneous LE and BR/EDR, Host. */
    #define BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE   (BLE_GAP_ADV_FLAG_LE_LIMITED_DISC_MODE | BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED)   /**< LE Limited Discoverable Mode, BR/EDR not supported. */
    #define BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE   (BLE_GAP_ADV_FLAG_LE_GENERAL_DISC_MODE | BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED)   /**< LE General Discoverable Mode, BR/EDR not supported. */
    /**@} */
    

    蓝牙模式设置通过 flags 来进行标识:
    LE 是 BLE(低功耗蓝牙),BR/EDR 是 蓝牙基本速率/增强速率(传统蓝牙),一般为蓝牙耳机
    • Bit0:LE 有限发现模式
    • Bit1:LE 普通发现模式
    • Bit2:不支持 BR/EDR 模式
    • Bit3:同时支持 BLE 和 BR/EDR 模式(控制器)
    • Bit3:同时支持 BLE 和 BR/EDR 模式(主机)

    BLE设备通常设置两种情况:

    • BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE:LE 有限发现模式和不支持 BR/EDR 模式。
    • BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE:LE 普通发现模式和不支持 BR/EDR 模式。

    2.4 广播模式配置

    &config 定义了广播的模式配置,比如广播模式,广播间隔,广播时间等等

    /**@brief   Options for the different advertisement modes.
     *
     * @details This structure is used to enable or disable advertising modes and to configure time-out
     *          periods and advertising intervals.
     */
    typedef struct
    {
        bool     ble_adv_on_disconnect_disabled;     /**< Enable or disable automatic return to advertising upon disconnecting.*/
        bool     ble_adv_whitelist_enabled;          /**< Enable or disable use of the whitelist. */
        bool     ble_adv_directed_high_duty_enabled; /**< Enable or disable high duty direct advertising mode. Can not be used together with extended advertising. */
        bool     ble_adv_directed_enabled;           /**< Enable or disable direct advertising mode. */
        bool     ble_adv_fast_enabled;               /**< Enable or disable fast advertising mode. */
        bool     ble_adv_slow_enabled;               /**< Enable or disable slow advertising mode. */
        uint32_t ble_adv_directed_interval;          /**< Advertising interval for directed advertising. */
        uint32_t ble_adv_directed_timeout;           /**< Time-out (number of tries) for direct advertising. */
        uint32_t ble_adv_fast_interval;              /**< Advertising interval for fast advertising. */
        uint32_t ble_adv_fast_timeout;               /**< Time-out (in units of 10ms) for fast advertising. */
        uint32_t ble_adv_slow_interval;              /**< Advertising interval for slow advertising. */
        uint32_t ble_adv_slow_timeout;               /**< Time-out (in units of 10ms) for slow advertising. */
        bool     ble_adv_extended_enabled;           /**< Enable or disable extended advertising. */
        uint32_t ble_adv_secondary_phy;              /**< PHY for the secondary (extended) advertising @ref BLE_GAP_PHYS (BLE_GAP_PHY_1MBPS, BLE_GAP_PHY_2MBPS or BLE_GAP_PHY_CODED). */
        uint32_t ble_adv_primary_phy;                /**< PHY for the primary advertising. @ref BLE_GAP_PHYS (BLE_GAP_PHY_1MBPS, BLE_GAP_PHY_2MBPS or BLE_GAP_PHY_CODED). */
    } ble_adv_modes_config_t;
    

    2.4.1 广播模式

    • directed_high_duty 高速连接任务模式:利用的就是 BLE 中的直连广播,该模式是为了快速重连上刚刚断开的配对设备。比如利用在快速重连上意外断开的设备,已达到无缝恢复的目的。这种模式只有在扩展广播被关闭下才能够使用。扩展广播是蓝牙5.0新特性

    • directed 定向广播模式:这种方式比高速模式的广播周期要低。

    • fast 快速广播模式:就是普通的广播,不过连接间隔我们可以设置的快一点。

    • slow 慢速广播模式:普通广播,连接间隔设置的慢一点。

    • idle 空闲模式:停止广播。

    2.4.2 广播间隔

    广播包的广播时间间隔,不同的广播类型下都有自己对应的广播间隔。
    工程中设置为:init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;

    2.4.3 广播时间

    超过这个时间会发生广播超时处理,不同的广播类型下都有自己对应的广播时间。
    工程中设置为:init.config.ble_adv_fast_timeout = APP_ADV_DURATION;

    三、执行广播

    在从机的 ble_app_template 工程的 main.c 中,在广播初始化程序里设置为 init.config.ble_adv_fast_enabled = true; 广播类型为快速广播。那么开始广播时就直接进入快速广播。整个启动过程交给了主函数中 advertising_start() 中的 ble_advertising_start() 来实现。

    3.1 advertising_start

    未绑定情况下,开启广播。进入 ble_advertising_start()

    /**@brief Function for starting advertising.
     */
    void advertising_start(bool erase_bonds)
    {
        if(erase_bonds == true)
        {
            delete_bonds();
            // Advertising is started by PM_EVT_PEERS_DELETED_SUCEEDED event
        }
        else
        {
            ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
    
            APP_ERROR_CHECK(err_code);
        }
    }
    

    3.2 ble_advertising_start

    根据实参 BLE_ADV_MODE_FAST 在 switch case 中启动快速广播

    uint32_t ble_advertising_start(ble_advertising_t * const p_advertising,
                                   ble_adv_mode_t            advertising_mode)
    {
        uint32_t ret;
    
        if (p_advertising->initialized == false)
        {
            return NRF_ERROR_INVALID_STATE;
        }
    
        p_advertising->adv_mode_current = advertising_mode;
    
        memset(&p_advertising->peer_address, 0, sizeof(p_advertising->peer_address));
    
        if (  ((p_advertising->adv_modes_config.ble_adv_directed_high_duty_enabled) && (p_advertising->adv_mode_current == BLE_ADV_MODE_DIRECTED_HIGH_DUTY))
            ||((p_advertising->adv_modes_config.ble_adv_directed_enabled)           && (p_advertising->adv_mode_current == BLE_ADV_MODE_DIRECTED_HIGH_DUTY))
            ||((p_advertising->adv_modes_config.ble_adv_directed_enabled)           && (p_advertising->adv_mode_current == BLE_ADV_MODE_DIRECTED))
           )
        {
            if (p_advertising->evt_handler != NULL)
            {
                p_advertising->peer_addr_reply_expected = true;
                p_advertising->evt_handler(BLE_ADV_EVT_PEER_ADDR_REQUEST);
            }
            else
            {
                p_advertising->peer_addr_reply_expected = false;
            }
        }
    
        p_advertising->adv_mode_current = adv_mode_next_avail_get(p_advertising, advertising_mode);
    
        // Fetch the whitelist.
        if ((p_advertising->evt_handler != NULL) &&
            (p_advertising->adv_mode_current == BLE_ADV_MODE_FAST || p_advertising->adv_mode_current == BLE_ADV_MODE_SLOW) &&
            (p_advertising->adv_modes_config.ble_adv_whitelist_enabled) &&
            (!p_advertising->whitelist_temporarily_disabled))
        {
            p_advertising->whitelist_in_use         = false;
            p_advertising->whitelist_reply_expected = true;
            p_advertising->evt_handler(BLE_ADV_EVT_WHITELIST_REQUEST);
        }
        else
        {
            p_advertising->whitelist_reply_expected = false;
        }
    
        // Initialize advertising parameters with default values.
        memset(&p_advertising->adv_params, 0, sizeof(p_advertising->adv_params));
    
        p_advertising->adv_params.properties.type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED;
    
        // Use 1MBIT as primary phy if no phy was selected.
        if (phy_is_valid(&p_advertising->adv_modes_config.ble_adv_primary_phy))
        {
            p_advertising->adv_params.primary_phy = p_advertising->adv_modes_config.ble_adv_primary_phy;
        }
        else
        {
            p_advertising->adv_params.primary_phy = BLE_GAP_PHY_1MBPS;
        }
    
        if (p_advertising->adv_modes_config.ble_adv_extended_enabled)
        {
            // Use 1MBIT as secondary phy if no phy was selected.
            if (phy_is_valid(&p_advertising->adv_modes_config.ble_adv_primary_phy))
            {
                p_advertising->adv_params.secondary_phy = p_advertising->adv_modes_config.ble_adv_secondary_phy;
            }
            else
            {
                p_advertising->adv_params.secondary_phy = BLE_GAP_PHY_1MBPS;
            }
        }
        p_advertising->adv_params.filter_policy = BLE_GAP_ADV_FP_ANY;
    
        // Set advertising parameters and events according to selected advertising mode.
        switch (p_advertising->adv_mode_current)
        {
            case BLE_ADV_MODE_DIRECTED_HIGH_DUTY:
                ret = set_adv_mode_directed_high_duty(p_advertising, &p_advertising->adv_params);
                break;
    
            case BLE_ADV_MODE_DIRECTED:
                ret = set_adv_mode_directed(p_advertising, &p_advertising->adv_params);
                break;
    
            case BLE_ADV_MODE_FAST:
                ret = set_adv_mode_fast(p_advertising, &p_advertising->adv_params);
                break;
    
            case BLE_ADV_MODE_SLOW:
                ret = set_adv_mode_slow(p_advertising, &p_advertising->adv_params);
                break;
    
            case BLE_ADV_MODE_IDLE:
                p_advertising->adv_evt = BLE_ADV_EVT_IDLE;
                break;
    
            default:
                break;
        }
    
        if (p_advertising->adv_mode_current != BLE_ADV_MODE_IDLE)
        {
    
            ret = sd_ble_gap_adv_set_configure(&p_advertising->adv_handle, p_advertising->p_adv_data, &p_advertising->adv_params);
            if (ret != NRF_SUCCESS)
            {
                return ret;
            }
            ret = sd_ble_gap_adv_start(p_advertising->adv_handle, p_advertising->conn_cfg_tag);
    
            if (ret != NRF_SUCCESS)
            {
                return ret;
            }
        }
    
        if (p_advertising->evt_handler != NULL)
        {
            p_advertising->evt_handler(p_advertising->adv_evt);
        }
    
        return NRF_SUCCESS;
    }
    

    但是,我们蓝牙设备不可能一直广播下去,这样很费电,我们需要动态的切换广播状态这个工作由 广播回调函数 完成,在 BLE_ADVERTISING_H 文件中


    这个是回调函数必须有的部分,其中 ble_advertising_on_ble_evt() 广播蓝牙回调事件函数是主要作用。

    比如我们程序开始设置为快速广播,那么在超时后进入下一级慢速模式。如果是慢速广播,超时进入无效模式。在函数中我们只设置了快速广播超时时间,其他几种广播超时时间没有设置,那么就默认为0,那么整个状态就变为 快速广播超时的TIMEOUT时间内没有连接---> 进入慢速广播---> 立即进入无效模式

    ble_advertising_start 开始广播函数每次在超时的时候会调用 adv_mode_next_get 函数,这个函数调用一次会把模式自动加1。

    /**@brief Function for checking the next advertising mode.
     *
     * @param[in] adv_mode Current advertising mode.
     */
    static ble_adv_mode_t adv_mode_next_get(ble_adv_mode_t adv_mode)
    {
        return (ble_adv_mode_t)((adv_mode + 1) % BLE_ADV_MODES);
    }
    

    设置广播模式的时候是递进设置的,高速定向广播---> 普通定向广播---> 快速广播---> 慢速广播---> 无效广播 每一级模式到达超时时间后会进入到下一级广播模式。

    3.3 on_adv_evt

    在 main.c 中,on_adv_evt 为广播事件处理函数,设置广播模式成功后执行对应操作,在蓝牙样例工程里由于没有慢速超时时间,实际上只需用到了两种状态,快速广播和无效广播。在 case 中可以加入自己的一些操作,如快速广播开始后,LED灯会闪烁,而无效广播模式会进入休眠模式。

    /**@brief Function for handling advertising events.
     *
     * @details This function will be called for advertising events which are passed to the application.
     *
     * @param[in] ble_adv_evt  Advertising event.
     */
    static void on_adv_evt(ble_adv_evt_t ble_adv_evt)
    {
        ret_code_t err_code;
    
        switch(ble_adv_evt)
        {
            case BLE_ADV_EVT_FAST:  // 快速广播模式
                NRF_LOG_INFO("Fast advertising.");
                err_code = bsp_indication_set(BSP_INDICATE_ADVERTISING);
                APP_ERROR_CHECK(err_code);
                break;
    
            case BLE_ADV_EVT_IDLE:  // 无效模式 
                sleep_mode_enter();
                break;
    
            default:
                break;
        }
    }
    

    四、不进入IDLE无效模式

    广播模式的切换主要就是广播超时处理,如果没有这个超时时间,就不会切换到下一个模式,保持在一种广播模式不变。


    APP_ADV_DURATION 设置为 0 即可。

    五、扫描响应包

    广播包有两种:广播数据包(Advertising Data)和扫描相应包(Scan Response),其中广播数据包是每个设备必须广播的,而扫描响应包是可选的。

    每个包都是 31 个字节,数据包中分为有效数据(significant)和无效数据(non-significant)两部分。

    • 有效数据部分:包含若干个广播数据单元,称为 AD Structure。AD Structure 的组成是:第一个字节是长度值 Len,表示接下来的 Len 个字节是数据部分。数据部分的第一个字节表示数据的类型 AD Type,剩下的 Len - 1 个字节是真正的数据 AD Data。其中 AD Type 非常关键,决定了 AD Data 的数据代表的是什么和怎么解析。
    • 无效数据部分:因为广播包的长度必须是 31 个字节,如果有效数据部分不到 31 个字节,剩下的就用 0 补全。这部分的数据是无效的,解析时忽略即可。

    而扫描响应包是为了给广播一个额外的 31 字节数据,用于主机 主动扫描 情况下,反馈数据使用。

    比如私有任务的 128bit UUID,我们需要反馈给主机的时候,我们可以采用下面这种方式配置:



    设置广播数据包和扫描响应包的数据长度,最长为 31 字节。




    程序下载后,使用 nrf connect app 进行扫描,这个 APP 不点击连接,可以点击广播信息,实现主动扫描方式。


    用抓捕器抓取,会出现扫描请求包,请求后出现一个扫描响应包。



    扫描响应包 ScanRspData 里 11 和 07 分别表述空白字段和 128bit UUID

    后面的内容就是该 UUID 的数值了。这样就广播了一个额外的 31 个字节来扩展广播内容。

    • 由 Leung 写于 2020 年 2 月 6 日

    • 参考:青风电子社区

    相关文章

      网友评论

        本文标题:NRF52832学习笔记(9)——GAP从机端广播

        本文链接:https://www.haomeiwen.com/subject/kissxhtx.html