CN / EN
CN / EN

写技术文章

蓝牙连接参数更新 --(2)连接参数更新的API和参考例程

chang

2022-05-17 12:26:25

文章主要介绍连接参数更新API的使用,关于蓝牙连接参数的介绍请参考这里

文中提到的所有API,均基于 GR551x_SDK_V1.7.0 版本。


一、更新连接参数的API

1、更新连接参数

路径:ble_gapc.h

uint16_t ble_gap_conn_param_update (uint8_t conn_idx, const gap_conn_update_param_t *p_conn_param);
  • conn_idx:连接索引。
  • p_conn_param:指向新的连接参数。
typedef struct{
    uint16_t interval_min;       //最小连接间隔
    uint16_t interval_max;       //最大连接间隔
    uint16_t slave_latency;      //从设备延迟
    uint16_t sup_timeout;        //监控超时
    uint16_t ce_len;             //LE连接所需的连接事件长度。
                                 //取值范围:0x0002~0xFFFF,单位:0.625 ms,时间范围:1.25 ms ~ 40.9 s。
                                 //推荐值:1M phy为0x0002, coded phy为0x0006
} gap_conn_update_param_t;

主从设备均可使用该函数更新连接参数。要实现连接参数的更新,程序中还需实现回调函数app_gap_connection_update_req_cb():


static void app_gap_connection_update_req_cb(uint8_t conn_idx,
                                             const gap_conn_param_t *p_conn_param_update_req)
{
    ble_gap_conn_param_update_reply(conn_idx, true);
}

使用ble_gap_conn_param_update()进行连接参数更新时,连接参数需要符合《蓝牙连接参数更新 --(1)连接参数说明和连接参数更新流程》中连接参数说明部分的要求。更新连接参数时,连接间隔参数优先选择interval_max的值。允许interval_max和interval_min的取值相同,如果需要将连接间隔修改为某一指定值,可将该值同时赋给interval_max和interval_min。


2、更新连接参数函数调用现象说明

表格持续补充中......


二、判断连接参数是否更新成功

1、通过连接参数更新完成的回调函数


这个回调函数将在连接更新完成时被调用。

static void app_gap_connection_update_cb(uint8_t conn_idx, uint8_t status,
                                         const gap_conn_update_cmp_t *p_conn_param_update_info)
{
    if(BLE_SUCCESS == status)
    {
        APP_LOG_INFO("conn_idx:%d connection update completed, intvl %0.2fms, ltcy %d, to %dms",
                     conn_idx,
                     p_conn_param_update_info->interval * 1.25,
                     p_conn_param_update_info->latency,
                     p_conn_param_update_info->sup_timeout * 10);
    }
}


2、通过抓包记录的时间分布判断


注:该方法适用于连接间隔发生改变时的判断。从设备延迟和监控超时参数,可以根据抓包内容判断。


3、通过芯片电流曲线判断

使用PPK板和GRPPK工具,监控芯片的电流变化。在蓝牙数据交互的过程中,电流会随着连接事件的出现,产生周期性的峰值变化,峰值之间的时间即为连接间隔。


注:该方法适用于连接间隔发生改变时的简单判断。


三、更新连接参数时,导致连接断开的情况

1、参数设置不合理。如监控超时设置过短,降低链路的容错能力,在链路信号较差或有干扰的情况下,出现监控超时并断开连接。

2、主设备发送连接参数更新指令(LL_CONNECTION_UPDATE_IND)时,如果链路质量不好,出现了很多重传,那么很可能在预期的时间点(Instant),从设备还没有接收到包,等接收到了后,更新连接参数的时间点都错过了,导致从设备上报 Instant Passed,并断开连接。

3、从设备收到连接参数更新指令(LL_CONNECTION_UPDATE_IND)后,更新连接参数过程中,在信号较差或有干扰的情况下,从设备没有在更改连接参数的位置(Instant)接收到对端的数据,导致连接断开。

4、从设备没有实现app_gap_connection_update_req_cb()回调函数,无法响应主设备的更新连接参数请求,导致主设备断开连接。


四、更新连接参数的建议

1、连接参数可根据实际应用场景调整,但要符合规范。

2、设备建立连接后,需要进行服务发现的交互流程。设备连接时,连接间隔参数不宜设置过大,过大的连接间隔,会使设备完成服务发现的过程花费很长时间。建议建立连接时的连接间隔为30ms - 50ms,如果需要更快的完成交互过程,也可将连接间隔设为最小值7.5ms。

3、连接成功后不能太早的去更新连接参数,应该要确保设备完成服务发现后,再更新连接参数。连接参数的更新时间,建议在连接成功后1.5S - 5S。

4、设备在进行较大量的数据交互过程中,应避免更新连接参数。可在需要发送大量数据前更新连接参数(传输速度更快的参数),和发送完成后更新连接参数(更加省电的参数)。


五、参考例程说明

关于连接参数更新的使用,可以参考SDK中的鼠标示例,工程路径:

SDK_Folder\projects\ble\ble_peripheral\ble_app_hids_mouse

在这里只说明实例中连接参数更新的部分,关于该示例的具体使用方法请参考《GR551x鼠标示例手册》

在这个例程中,作为从设备的鼠标会在建立连接后或主设备端更新连接参数后,判断当前使用的连接参数是否符合程序中指定的范围,如果不符合便会启动定时器,在定时器定时时间到期后,主动更新连接参数。主要函数流程如下:

1、初始化函数app_conn_init()中,指定连接参数。并调用函数ble_connect_init()。

路径:工程目录下的user_app\user_app.c

#define APP_PREF_CONN_INTERVAL_MAX            160
#define APP_PREF_CONN_INTERVAL_MIN            160
#define APP_PREF_CONN_SLAVE_LATENCY           0
#define APP_PREF_CONN_TIMEOUT                 400

static void app_conn_init(void)
{
    sdk_err_t             err_code;
    ble_conn_init_t       conn_init;    
    //指定连接参数    
    gap_conn_param_t      gap_conn_param = {        
        .interval_min = APP_PREF_CONN_INTERVAL_MIN,        
        .interval_max = APP_PREF_CONN_INTERVAL_MAX,        
        .slave_latency = APP_PREF_CONN_SLAVE_LATENCY,        
        .sup_timeout = APP_PREF_CONN_TIMEOUT };   
    
     memset(&conn_init, 0, sizeof(conn_init));    
     conn_init.conn_param_manage_enable      = true;    
     conn_init.p_conn_param                  = &gap_conn_param;    
     conn_init.first_conn_param_update_delay = APP_FIRST_CONN_PARAM_UPDATE_DELAY;    
     conn_init.next_conn_param_update_delay  = APP_SECOND_CONN_PARAM_UPDATE_DELAY;    
     conn_init.max_conn_param_update_count   = APP_CONN_PARAM_UPDATE_TIMES;    
     conn_init.disconnect_on_fail            = true;    
     conn_init.evt_handler                   = ble_conn_evt_handler;    
     conn_init.err_handler                   = ble_conn_err_handler;
     
    //连接初始化
    err_code = ble_connect_init(&conn_init);    
    APP_ERROR_CHECK(err_code);
}


2、在函数ble_connect_init()中,将连接参数赋值给“优先连接参数”,并创建更新连接参数用的定时器。

路径:工程目录下的ble_module\ble_connect.c

sdk_err_t ble_connect_init(ble_conn_init_t *p_conn_init)
{
    ......省略部分代码    
    for (uint32_t i = 0; i < BLE_CONN_LINK_CNT_MAX; i++)    
    {        
        //将指定的连接参数赋值给“优先连接参数”        
        memcpy(&s_conn_env.link_info[i].pref_conn_param, p_conn_init->p_conn_param, sizeof(gap_conn_param_t));
        //创建更新连接参数用的定时器。
        error_code = app_timer_create(&s_conn_env.link_info[i].timer_id, ATIMER_ONE_SHOT,update_timeout_handler);
        RET_VERIFY_SUCCESS(error_code);
    }
    ......
}


3、先来看建立连接后的情况。

当连接建立成功后,会进入函数ble_conn_established():

路径:工程目录下的ble_module\ble_connect.c

//从延迟中可接受的最大偏差
#define BLE_CONN_PARAM_MAX_SLAVE_LATENCY_DEVIATION            10
//监督超时时可接受的最大偏差(10ms单位)。
#define BLE_CONN_PARAM_MAX_SUPERVISION_TIMEOUT_DEVIATION      100

static void ble_conn_established(uint8_t conn_idx, void *arg)
{
    ......
    //判断当前的连接参数是否符合更新规则
    s_conn_env.link_info[conn_idx].param_ok = is_conn_param_ok(&s_conn_env.link_info[conn_idx].pref_conn_param,
                                                               &conn_update_param,
                                                               BLE_CONN_PARAM_MAX_SLAVE_LATENCY_DEVIATION,
                                                               BLE_CONN_PARAM_MAX_SUPERVISION_TIMEOUT_DEVIATION);
    //启用了连接参数自动更新。
    if (s_conn_env.conn_param_manage_enable)
    {        
        //调用连接参数协商函数        
        conn_param_negotiation(conn_idx);    
    }
    ......
}

在ble_conn_established()函数中,通过函数is_conn_param_ok(),判断当前的连接参数是否符合更新规则,然后调用conn_param_negotiation()函数。


4、再来看主设备端更新连接参数后的情况

当主设备端更新连接参数成功后,会进入函数ble_conn_param_updated():

路径:工程目录下的ble_module\ble_connect.c

//从延迟中可接受的最大偏差
#define BLE_CONN_PARAM_MAX_SLAVE_LATENCY_DEVIATION            10
//监督超时时可接受的最大偏差(10ms单位)。
#define BLE_CONN_PARAM_MAX_SUPERVISION_TIMEOUT_DEVIATION      100

static void ble_conn_param_updated(uint8_t conn_idx, void *arg)
{
    ......    
    //判断当前的连接参数是否符合更新规则
    s_conn_env.link_info[conn_idx].param_ok = is_conn_param_ok(&s_conn_env.link_info[conn_idx].pref_conn_param,
                                                               &conn_update_param,
                                                               BLE_CONN_PARAM_MAX_SLAVE_LATENCY_DEVIATION,
                                                               BLE_CONN_PARAM_MAX_SUPERVISION_TIMEOUT_DEVIATION);
    
    //启用了连接参数自动更新。    
    if (s_conn_env.conn_param_manage_enable)    
    {        
        //调用连接参数协商函数        
        conn_param_negotiation(conn_idx);
    }
    ......
}

在ble_conn_param_updated()函数中,也是通过函数is_conn_param_ok(),判断当前的连接参数是否符合更新规则,然后调用conn_param_negotiation()函数。

5、is_conn_param_ok()函数路径,判断连接参数是否符合更新规则
路径:工程目录下的ble_module\ble_connect.c

static bool is_conn_param_ok(gap_conn_param_t const * p_preferred_conn_param,
                             gap_conn_update_cmp_t const * p_actual_conn_param,
                             uint16_t                      max_slave_latency_err,
                             uint16_t                      max_sup_timeout_err)
{
    uint32_t max_allowed_sl = p_preferred_conn_param->slave_latency + max_slave_latency_err;
    uint32_t min_allowed_sl = p_preferred_conn_param->slave_latency - \
                              MIN(max_slave_latency_err, p_preferred_conn_param->slave_latency);
    uint32_t max_allowed_to = p_preferred_conn_param->sup_timeout + max_sup_timeout_err;
    uint32_t min_allowed_to = p_preferred_conn_param->sup_timeout - \
                              MIN(max_sup_timeout_err, p_preferred_conn_param->sup_timeout);
    
    // 检查连接间隔是否在可接受范围内。    
    // NOTE: Using max_conn_interval in the received event data because this contains    
    //       the client's connection interval.    
    if ((p_actual_conn_param->interval < p_preferred_conn_param->interval_min) ||
        (p_actual_conn_param->interval > p_preferred_conn_param->interval_max))
    {        
        return false;    
    }    
    
    // 检查从设备延迟是否在可接受范围内。    
    if ((p_actual_conn_param->latency < min_allowed_sl)||
        (p_actual_conn_param->latency > max_allowed_sl))    
    {    
        return false;    
    }    
    
    // 检查监控超时是否在可接受范围内。    
    if ((p_actual_conn_param->sup_timeout < min_allowed_to) ||
        (p_actual_conn_param->sup_timeout > max_allowed_to))
    {     
        return false;    
    }
    
    return true;
}


6、在函数conn_param_negotiation()中,当连接参数不符合更新规则时,启动更新连接参数用的定时器。

路径:工程目录下的ble_module\ble_connect.c

static void conn_param_negotiation(uint8_t conn_idx)
{
    //当判断连接参数不符合设定的更新规则    
    if (!s_conn_env.link_info[conn_idx].param_ok)
    {
        sdk_err_t error_code;        
        uint32_t  timeout_ticks;        
        if (0 == s_conn_env.link_info[conn_idx].update_count)
        {            
            // First connection parameter update            
            timeout_ticks = s_conn_env.first_conn_param_update_delay;
        }        
        else        
        {   
            timeout_ticks = s_conn_env.next_conn_param_update_delay;
        }                
        
        //启动定时器        
        app_timer_stop(s_conn_env.link_info[conn_idx].timer_id);
        error_code = app_timer_start(s_conn_env.link_info[conn_idx].timer_id, timeout_ticks, (void *)(uint32_t)conn_idx);
        ble_connect_err_on_ble_capture(error_code);    
    }
    ......
}


7、在定时器定时时间到期后,最终调用ble_gap_conn_param_update()函数,进入更新连接参数流程。

路径:工程目录下的ble_module\ble_connect.c

//定时器回调
static void update_timeout_handler(void *p_context)
{    
    ......    
    //连接参数更新    
    bool update_sent = send_update_request(conn_idx, &s_conn_env.link_info[conn_idx].pref_conn_param);
    
    ......
}

static bool send_update_request(uint8_t conn_idx, gap_conn_param_t * p_new_conn_param)
{    
    sdk_err_t error_code;        
    gap_conn_update_param_t conn_update_param;    
    memcpy(&conn_update_param, p_new_conn_param, sizeof(gap_conn_param_t));
    conn_update_param.ce_len = 0;
    //调用连接参数更新API
    error_code = ble_gap_conn_param_update(conn_idx, &conn_update_param);
    if ((error_code != SDK_SUCCESS) && (error_code != SDK_ERR_BUSY))
    {        
        ble_connect_err_on_ble_capture(error_code);    
    }    
    return (error_code == SDK_SUCCESS);
}


六、默认使用逻辑链路控制和适配协议(L2CAP)来更新连接参数

在GR5515_SDK中,作为从设备,当调用ble_gap_conn_param_update()更新连接参数时,会先通过链路层控制程序(LLCP)来更新连接参数,当发送连接参数更新请求(LL_CONNECTION_PARAM_REQ)时,如果主设备返回LL_UNKNOWN_RSP,那就再通过逻辑链路控制和适配协议(L2CAP)来更新连接参数。

我们可以在调用ble_gap_conn_param_update()函数更新连接参数前,先调用函数ble_gap_update_conn_param_method_set(),修改更新连接参数的默认流程,跳过LLCP层,直接使用L2CAP层来更新连接参数。

路径:ble_gapm.h

uint16_t ble_gap_update_conn_param_method_set(uint8_t conn_idx, bool use_l2cap_flag);
  • conn_idx :连接索引。
  • use_l2cap_flag :为 true 使用L2CAP更新连接参数;为 false 使用LLCP更新连接参数。

该函数的用法,如修改第五节鼠标示例ble_app_hids_mouse中的send_update_request()函数:

static bool send_update_request(uint8_t conn_idx, gap_conn_param_t * p_new_conn_param)
{
    sdk_err_t error_code;        
    gap_conn_update_param_t conn_update_param;    
    memcpy(&conn_update_param, p_new_conn_param, sizeof(gap_conn_param_t));
    conn_update_param.ce_len = 0;    
    
    //默认使用逻辑链路控制和适配协议(L2CAP)来更新连接参数    
    ble_gap_update_conn_param_method_set(conn_idx, true);    
    //更新连接参数    
    error_code = ble_gap_conn_param_update(conn_idx, &conn_update_param);
    if ((error_code != SDK_SUCCESS) && (error_code != SDK_ERR_BUSY))
    {
        ble_connect_err_on_ble_capture(error_code);    
    }    
    
    return (error_code == SDK_SUCCESS);
}


1收藏

5赞成

您的评论
我们时刻倾听您的声音
联系销售

扫描关注公众号

打开微信,使用“扫一扫”即可关注