【WCH蓝牙系列芯片】-基于CH592开发板—动态协商更新连接间隔

------------------------------------------------------------------------------------------------------------------------------------

  在蓝牙连接过程中,一般都包含这几个连接参数,最小连接间隔、最大连接间隔、从机潜伏周期、连接超时时间这四个参数。

  连接间隔参数:在一个 BLE 连接中跳频机制需要被使用,让两个设备之间可以在一个特定的通道上进行数据收发,在一个特定的时间之后会跳到一个新的通道上,LL 层负责通道切换。这个设备收发数据就是连接事件。尽管没有应用程序数据需要收发,两个设备之间仍然会交换链路层数据来保持连接。连接间隔是两个连接事件之间的时间,使用一个单元值为1.25ms 的步进。连接间隔从最小值 6(7.5ms)到最大值 3200(4.0s)。不同的应用也许需要不同的连接间隔,如果一个长时间的连接间隔将会节约更多的功耗,因为设备可以在两个连接事件之间睡眠更长的时间。但是会导致数据发送不及时,如果有数据要发送那么只能够在下一次连接事件到来时才能被发送。

  从机潜伏周期:这个参数描述了从机跳过连接事件的次数。可以让外围设备具有一定的灵活性,如果它不具有任何数据传送,它可以选择跳过连接事件,并保持睡眠,从而降低功耗。这一决定取决于外围设备,也可以成为从设备延迟,从设备能够忽略主设备的链接事件的最大值。比如:从设备延迟为 6 的话,那么从设备每隔 6 个连接事件可以监听到主设备发送来的数据包,如果从设备延迟为 0,从设备在每个连接事件都可以监听主设备的数据包

  监督超时时间:这是两个成功的连接事件之间间隔的最大值。如果超过这个时间还未出现成功的连接事件,那么设备将会考虑失去连接,返回一个未连接状态。这个参数值使用 10ms一个单位。监督超时时间从最小 10(100ms)到最大 3200(32.0s)。同时超时时间必须大于有效连接事件。

                    有效连接事件时间 = 连接间隔 * (1 + 从机延迟值)

  在某些情况下,蓝牙主机与蓝牙从机建立连接包含连接参数对从机是不利的。蓝牙从机可以在连接过程,可以主动请求蓝牙主机改变连接参数,设置连接参数的更新请求。这个请求是被协议栈的 L2CAP 处理的。这个更新请求的四个参数就是包含:最小连接间隔、最大连接间隔、从机潜伏周期、连接超时时间。这些值代表了从机针对连接的期望参数,连接间隔是以范围的形式提供的,当蓝牙主机接收到这个请求,主机有权利决定是接受还是拒绝这些参数,这些参数对通信质量和通信功耗有很大的影响。

  这次以CH59为例,在蓝牙从机去做一个整个动态更新连接参数的过程, 在CH592蓝牙从机与主机刚连接上,以快速的连接间隔进行交互,成功连接之后,持续一段时间之后,从机就主动协商长时间连接参数,从而来降低整体的连接功耗,然后再持续一段时间,再次主动协商短时间连接参数,为了发送数据。

image

// 建立连接后,延迟多久开始检查初始快速档
#define PARAM_INITIAL_DELAY             MS1_TO_SYSTEM_TIME(1000)
// 每个连接档位保持10秒
#define PARAM_MODE_HOLD_TIME            MS1_TO_SYSTEM_TIME(10000)
// 参数请求未成功时,每3秒重试一次
#define PARAM_RETRY_DELAY               MS1_TO_SYSTEM_TIME(6000)
// 每个档位最多请求5次
#define PARAM_MAX_RETRY                 5

// 快速连接间隔:连接间隔单位为1.25ms
// 8 × 1.25ms  = 10ms
// 12 × 1.25ms = 15ms
#define PARAM_FAST_MIN_INTERVAL         8
#define PARAM_FAST_MAX_INTERVAL         12
#define PARAM_FAST_LATENCY         0
#define PARAM_FAST_TIMEOUT         600  // 6秒

// 慢速连接间隔:
// 396 × 1.25ms = 495ms
// 408 × 1.25ms = 510ms
#define PARAM_SLOW_MIN_INTERVAL         300
#define PARAM_SLOW_MAX_INTERVAL         400
#define PARAM_SLOW_LATENCY         0
// 监督超时单位10ms:
#define PARAM_SLOW_TIMEOUT         600  // 6秒

/* 整个动态过程允许的总范围 */
#define PARAM_ALLOWED_MIN_INTERVAL    PARAM_FAST_MIN_INTERVAL
#define PARAM_ALLOWED_MAX_INTERVAL    PARAM_SLOW_MAX_INTERVAL

typedef enum
{
    PARAM_PHASE_IDLE = 0,
    // 刚连接,正在确认或请求初始快速档
    PARAM_PHASE_REQUEST_INITIAL_FAST,
    // 快速档保持10秒
    PARAM_PHASE_FAST_HOLD,
    // 请求慢速档
    PARAM_PHASE_REQUEST_SLOW,
    // 慢速档保持10秒
    PARAM_PHASE_SLOW_HOLD,
    // 请求重新回到快速档
    PARAM_PHASE_REQUEST_FAST,
    // 本轮动态切换完成
    PARAM_PHASE_FINISHED
} paramPhase_t;

static paramPhase_t paramPhase = PARAM_PHASE_IDLE;
static uint8_t paramRetryCount = 0;  //重试次数

static uint8_t peripheralIntervalInRange(uint16_t interval,
                                         uint16_t minInterval,
                                         uint16_t maxInterval);
//判断主机最终采用的连接间隔是否落入目标范围
static uint8_t peripheralIntervalInRange(uint16_t interval,uint16_t minInterval, uint16_t maxInterval)
{
    return ((interval >= minInterval) &&
            (interval <= maxInterval));
}
//确认当前实际连接间隔满足目标,停止重试,并进入下一个状态
static void peripheralParamTargetReached(void)
{
    // 当前目标参数已经达到,不需要继续更新参数
    tmos_stop_task(Peripheral_TaskID, SBP_PARAM_UPDATE_EVT);
    paramRetryCount = 0;  //清零重试次数
//状态机判断
    switch(paramPhase)
    {
        case PARAM_PHASE_REQUEST_INITIAL_FAST:   //刚连接,正在确认或请求初始快速档
        {
            paramPhase = PARAM_PHASE_FAST_HOLD;   //切换为保持 快速档保持10秒

            tmos_stop_task(Peripheral_TaskID, SBP_PARAM_SWITCH_EVT);  //先停止
            tmos_start_task(Peripheral_TaskID, SBP_PARAM_SWITCH_EVT, PARAM_MODE_HOLD_TIME); //再启动 ,10秒的任务运行

            PRINT("Initial fast interval reached; hold for 10 seconds\r\n");
            break;
        }

        case PARAM_PHASE_REQUEST_SLOW:  //慢速档达到
        {
            paramPhase = PARAM_PHASE_SLOW_HOLD;  // 慢速档保持10秒

            tmos_stop_task(Peripheral_TaskID, SBP_PARAM_SWITCH_EVT);
            tmos_start_task(Peripheral_TaskID,SBP_PARAM_SWITCH_EVT, PARAM_MODE_HOLD_TIME);  //启动更新任务

            PRINT("Slow interval reached; hold for 10 seconds\r\n");
            break;
        }

        case PARAM_PHASE_REQUEST_FAST:   // 请求重新回到快速档
        {
            paramPhase = PARAM_PHASE_FINISHED;  // 本轮动态切换完成
            tmos_stop_task(Peripheral_TaskID, SBP_PARAM_SWITCH_EVT);  //停止任务事件

            PRINT("Fast interval restored; dynamic sequence finished\r\n");
            break;
        }
        default:
            break;
    }
}

   在设备成功连接之后,将连接间隔的参数改为快速挡位,也就是将最大连接间隔和最小连接间隔都调整到8和12,这样整体的连接间隔是在10MS到15MS之间。

image

        /**********************发起协商,更新连接参数*****************************/
        if(events & SBP_PARAM_UPDATE_EVT)
        {
            uint16_t minInterval;
            uint16_t maxInterval;
            uint8_t targetIsFast;
            bStatus_t ret;
            uint16_t targetLatency;
            uint16_t targetTimeout;
            if(peripheralConnList.connHandle == GAP_CONNHANDLE_INIT)  //检查当前是否存在有效连接.如果没有连接
            {
                paramPhase = PARAM_PHASE_IDLE;  //状态机恢复为 IDLE
                paramRetryCount = 0;  //清零重试次数

                PRINT("Param update skipped: not connected\r\n");
                return (events ^ SBP_PARAM_UPDATE_EVT);  //清除本次任务
            }

            // 根据当前阶段决定要请求哪一档参数
            if((paramPhase == PARAM_PHASE_REQUEST_INITIAL_FAST) ||
               (paramPhase == PARAM_PHASE_REQUEST_FAST))   //连接间隔快速挡位
            {
                minInterval = PARAM_FAST_MIN_INTERVAL;  //最小连接间隔
                maxInterval = PARAM_FAST_MAX_INTERVAL;  //最大连接间隔
                targetLatency = PARAM_FAST_LATENCY;     //从机潜伏周期
                targetTimeout = PARAM_FAST_TIMEOUT;     //超时时间
                targetIsFast = TRUE;
            }
            else if(paramPhase == PARAM_PHASE_REQUEST_SLOW)  //连接间隔慢速挡请求
            {
                minInterval = PARAM_SLOW_MIN_INTERVAL;
                maxInterval = PARAM_SLOW_MAX_INTERVAL;
                targetLatency = PARAM_SLOW_LATENCY;
                targetTimeout = PARAM_SLOW_TIMEOUT;
                targetIsFast = FALSE;
            }
            else
            {
                return (events ^ SBP_PARAM_UPDATE_EVT);
            }

            PRINT("Param check: current=%d (%d ms), phase=%d, retry=%d\r\n",
                  peripheralConnList.connInterval,
                  peripheralConnList.connInterval * 5 / 4,
                  paramPhase,
                  paramRetryCount);    //打印当前的连接参数

            // 如果手机已经采用目标范围,不需要再次发送请求
            if(peripheralIntervalInRange(peripheralConnList.connInterval,
                                         minInterval,
                                         maxInterval))
            {
                peripheralParamTargetReached();  //确认当前实际连接间隔满足目标,停止重试,并进入下一个状态
                return (events ^ SBP_PARAM_UPDATE_EVT);
            }

            if(paramRetryCount >= PARAM_MAX_RETRY)  //判断是否超过最大请求次数
            {
                PRINT("Param negotiation failed: phase=%d, current=%d (%d ms)\r\n",
                      paramPhase,
                      peripheralConnList.connInterval,
                      peripheralConnList.connInterval * 5 / 4);  //打印当前的连接参数

                paramPhase = PARAM_PHASE_FINISHED;
                paramRetryCount = 0;  //清除重试参数

                return (events ^ SBP_PARAM_UPDATE_EVT);
            }

            ret = GAPRole_PeripheralConnParamUpdateReq(
                peripheralConnList.connHandle,
                minInterval,
                maxInterval,
                targetLatency,
                targetTimeout,
                Peripheral_TaskID);   //调用协议栈发起更新

            paramRetryCount++;  //增加请求计数

            PRINT("Request %s interval %d/%d: "
                  "min=%dms, max=%dms, latency=%d, "
                  "timeout=%dms, ret=0x%02x\r\n",
                  targetIsFast ? "FAST" : "SLOW",
                  paramRetryCount,
                  PARAM_MAX_RETRY,
                  minInterval * 5 / 4,
                  maxInterval * 5 / 4,
                  targetLatency,
                  targetTimeout * 10,
                  ret);  //打印请求后的连接参数,和发起更新的状态

            // 请求是异步的,3秒以后再检查回调更新后的实际参数
            tmos_start_task(Peripheral_TaskID,SBP_PARAM_UPDATE_EVT,PARAM_RETRY_DELAY);  // 参数请求未成功时,每6秒重试一次
            return (events ^ SBP_PARAM_UPDATE_EVT);
        }

  在SBP_PARAM_UPDATE_EVT发起的协商更新事件中,按照快速或者慢速状态来选择连接参数,然后先判断当前值是否已在目标范围;最后利用GAPRole_PeripheralConnParamUpdateReq函数发起更新协商请求,最后将协商结果的连接间隔参数打印出来做比较。如果参数请求没有成功时,还会每隔6秒的时间重新启动这个任务时间,再走一遍这个事件流程。

  在参数更新回调函数中,void peripheralParamUpdateCB(uint16_t connHandle, uint16_t connInterval, uint16_t connSlaveLatency, uint16_t connTimeout),回调先保存实际生效参数,再判断当前目标参数:目标参数达到后,程序停止重试,清零计数,并开始 10 秒保持定时任务事件。

static void peripheralParamUpdateCB(uint16_t connHandle, uint16_t connInterval,
                                    uint16_t connSlaveLatency, uint16_t connTimeout)
{
    if(connHandle == peripheralConnList.connHandle)
    {
        peripheralConnList.connInterval = connInterval;
        peripheralConnList.connSlaveLatency = connSlaveLatency;
        peripheralConnList.connTimeout = connTimeout;
//        PRINT("Update %x - Int %x \n", connHandle, connInterval);

        PRINT("Connection parameters updated:\r\n");
        PRINT("  handle   = 0x%04x\r\n", connHandle);
        PRINT("  interval = %d, %d ms\r\n",
              connInterval,
              connInterval * 5 / 4);
        PRINT("  latency  = %d\r\n", connSlaveLatency);
        PRINT("  timeout  = %d, %d ms\r\n",
              connTimeout,
              connTimeout * 10);   //打印最终实际生效的参数
    }
    else
    {
        PRINT("ERR..\n");
    }


    //根据当前状态机阶段,判断新的连接间隔
    // 初始快速档或最后恢复快速档
    if(((paramPhase == PARAM_PHASE_REQUEST_INITIAL_FAST) ||
        (paramPhase == PARAM_PHASE_REQUEST_FAST)) &&
       peripheralIntervalInRange(connInterval,
                                 PARAM_FAST_MIN_INTERVAL,
                                 PARAM_FAST_MAX_INTERVAL))  ////判断主机最终采用的连接间隔是否落入目标范围
    {
        peripheralParamTargetReached();  //停止重试、清零重试次数,并推进状态机:
    }
    // 慢速档
    else if((paramPhase == PARAM_PHASE_REQUEST_SLOW) &&
            peripheralIntervalInRange(connInterval,
                                      PARAM_SLOW_MIN_INTERVAL,
                                      PARAM_SLOW_MAX_INTERVAL))
    {
        peripheralParamTargetReached();  //停止重试、清零重试次数,并推进状态机:
    }
    // 快速档保持期间,手机又把参数改出了目标范围
    else if((paramPhase == PARAM_PHASE_FAST_HOLD) &&
            !peripheralIntervalInRange(connInterval,
                                       PARAM_FAST_MIN_INTERVAL,
                                       PARAM_FAST_MAX_INTERVAL))
    {
        tmos_stop_task(Peripheral_TaskID, SBP_PARAM_SWITCH_EVT); //停止切换慢速挡位的事件任务

        paramPhase = PARAM_PHASE_REQUEST_INITIAL_FAST;  //重新获取快速挡位标志
        paramRetryCount = 0;

        tmos_start_task(Peripheral_TaskID, SBP_PARAM_UPDATE_EVT,PARAM_RETRY_DELAY);  //完整保持10秒,启动任务切到慢速档

        PRINT("Phone changed fast interval; renegotiate FAST\r\n");
    }
    // 慢速档保持期间,手机又把参数改出了目标范围
    else if((paramPhase == PARAM_PHASE_SLOW_HOLD) &&
            !peripheralIntervalInRange(connInterval,
                                       PARAM_SLOW_MIN_INTERVAL,
                                       PARAM_SLOW_MAX_INTERVAL))
    {
        tmos_stop_task(Peripheral_TaskID, SBP_PARAM_SWITCH_EVT);  //停止切换快速挡位的事件任务

        paramPhase = PARAM_PHASE_REQUEST_SLOW;  //重新获取慢速挡位标志
        paramRetryCount = 0;

        tmos_start_task(Peripheral_TaskID, SBP_PARAM_UPDATE_EVT,PARAM_RETRY_DELAY);   //完整保持10秒,启动任务切到快速档

        PRINT("Phone changed slow interval; renegotiate SLOW\r\n");
    }
}

在SBP_PARAM_SWITCH_EVT的任务事件中,切换不同挡位,保持时间运行结束后的档位切换的操作

        /**********************切换不同挡位,保持时间运行,保持结束后的档位切换任务*****************************/
        if(events & SBP_PARAM_SWITCH_EVT)
        {
            if(peripheralConnList.connHandle == GAP_CONNHANDLE_INIT)  //检查连接是否仍存在
            {
                paramPhase = PARAM_PHASE_IDLE;
                return (events ^ SBP_PARAM_SWITCH_EVT);  //结束本次任务事件
            }

            if(paramPhase == PARAM_PHASE_FAST_HOLD)  //快速档保持结束
            {
                // 快速档已经保持10秒,开始请求约500ms
                paramPhase = PARAM_PHASE_REQUEST_SLOW;  //请求慢速挡位
                paramRetryCount = 0;

                PRINT("Fast hold finished; request slow interval\r\n");
                tmos_set_event(Peripheral_TaskID, SBP_PARAM_UPDATE_EVT);  //启动更新连接参数任务事件
            }
            else if(paramPhase == PARAM_PHASE_SLOW_HOLD)  //慢速档保持结束
            {
                // 慢速档已经保持10秒,重新请求约10~15ms
                paramPhase = PARAM_PHASE_REQUEST_FAST;  // 请求重新回到快速档
                paramRetryCount = 0;

                PRINT("Slow hold finished; request fast interval\r\n");
                tmos_set_event(Peripheral_TaskID, SBP_PARAM_UPDATE_EVT);  //启动更新连接参数任务事件
            }

            return (events ^ SBP_PARAM_SWITCH_EVT);
        }

  通过串口日志和功耗分析仪的电流显示,整个连接间隔的参数的协商更新过程,与手机的APP建立连接之后,先是协商快速的连接间隔参数,连接间隔更新至15ms,运行10秒之后,有重新协商更新连接间隔参数,慢速的连接间隔为460ms,再运行10秒之后,又再次协商到快速的15ms的连接间隔参数。

image

 

image

 

image

 

image

 

image

 

posted on 2026-08-31 18:09  凡仕  阅读(8)  评论(0)    收藏  举报