Nimble Advertising Start time计算

参考文献:

  1. Nordic API docs
  2. rtt+nimble揭秘系列——phy
  3. nRF52832 Datasheet

在start advertising的时候,从Host角度看非常的简单,只是通过如下API送几个参数给Controller,然后等着event回来做相应的处理就好了,而Controller则要考虑很多细节来找到比较合适的真正的开始发送Adv data的时间,本文介绍下Nimble计算这个时间的一个流程。

1
2
3
4
5
int
ble_gap_adv_start(uint8_t own_addr_type, const ble_addr_t *direct_addr,
int32_t duration_ms,
const struct ble_gap_adv_params *adv_params,
ble_gap_event_fn *cb, void *cb_arg)

以下图为参考,总结下Nimbble controller处理adv start time的几个步骤:
StartAdvTime

图中横轴为时间,每个小格子为一个tick,30.5us。为了便于理解,假定Controller在收到Host的请求,执行到函数ble_ll_adv_sm_start() 开始计算Adv的start time时,tick刚好为100,后续的各个关键步骤以及相关的时间点如下:

  1. 依照Spec,一般会在Adv开始点加一个随机的AdvDelay(小于10ms)以避免adv event与Scan window 完美错开,导致对方怎么扫都扫不到。图中假定这个AdvDelay是15个tick,也就是adv_pdu_start_time 是115
  2. 启动Radio开始发送数据,需要考虑Radio的settling time,以及为Radio准备要发送的数据耗费的时间,所以要想在115时把数据真正的发送出去,就要提前开始行动,这个提前开始行动的时间在nimble中定义如下:

    1
    2
    3
    4
    5
    6
    /*
    * This is the offset from the start of the scheduled item until the actual
    * tx/rx should occur, in ticks. We also "round up" to the nearest tick.
    */
    g_ble_ll_sched_offset_ticks =
    (uint8_t) os_cputime_usecs_to_ticks(XCVR_TX_SCHED_DELAY_USECS + 30);
  3. g_ble_ll_sched_offset_ticks,在不同的chip上是不同的,

    • nRF51是 370us ,其中140us为Radio的settling time,230us为给Radio准备数据的时间。
    • nRF52系列是 193us ,其中40us为Radio的settling time,153为给Radio准备数据的时间。
      所以对于nRF51来说,这个offset ticks为 (370+30)/30.5 = 13 tick115提前13个tick,start_time就是102
  4. 接着将这个预期的的start_time通过函数

    1
    ble_ll_sched_adv_new()

    送给scheduler去确定是否能在这个时间点正式开始。假如很幸运,这个adv的sched没有跟已有的sched overlap,那就直接到到第#6步。

  5. 如果第#4步中有发现该sched跟其他的sched有overlap,那这个start time就只能由Scheduler来找个合适的位置了,假定找到的位置是122
  6. 122正式开始发送数据,由#2知,真正能开始发送数据的时间时g_ble_ll_sched_offset_ticks后,即13个tick后,也就是135才是Radio真正开始发送PDU的时候。
  7. 而要在135发送数据,就要提前140us(nRF51) 就把Radio开启,以保证在135时Radio能正常工作。这个工作主要是在如下这个函数执行。
    1
    ble_phy_set_start_time(uint32_t cputime, uint8_t rem_usecs)
  • 函数使用RTC0和TIMER0两个定时器来定时完成,TIMER0时由1M clock驱动,精准度为1us。如下这段函数将精准表示Radio开启时间点(绿色旗帜),先用RTC0来定位到蓝色旗帜时间点最近的tick(cputime),之后用TIMER0来定位从这个tick开始再过多少个us到达蓝色旗帜的位置,要启动Radio。

    1
    2
    3
    4
    5
    6
    7
    if (rem_usecs <= 18) {
    cputime -= 5;
    rem_usecs += 12;
    } else {
    cputime -= 4;
    rem_usecs -= 18;
    }
  • 后面这段是设定好RTC0 和TIMER0后再启动RTC0 和 TIMER0。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    next_cc = cputime & 0xffffff;
    cur_cc = NRF_RTC0->CC[0];
    cntr = NRF_RTC0->COUNTER;

    delta = (cur_cc - cntr) & 0xffffff;
    if ((delta <= 3) && (delta != 0)) {
    return -1;
    }
    delta = (next_cc - cntr) & 0xffffff;
    if ((delta & 0x800000) || (delta < 3)) {
    return -1;
    }

    /* Clear and set TIMER0 to fire off at proper time */
    NRF_TIMER0->TASKS_CLEAR = 1;
    NRF_TIMER0->CC[0] = rem_usecs;
    NRF_TIMER0->EVENTS_COMPARE[0] = 0;

    /* Set RTC compare to start TIMER0 */
    NRF_RTC0->EVENTS_COMPARE[0] = 0;
    NRF_RTC0->CC[0] = next_cc;
    NRF_RTC0->EVTENSET = RTC_EVTENSET_COMPARE0_Msk;

    /* Enable PPI */
    NRF_PPI->CHENSET = PPI_CHEN_CH31_Msk;
  • 对于Nordic的系列chip,PPI(Programmable peripheral interconnect)得一定了解下,具体可参考Nordic的Datasheet。PPI是一种不通过CPU就可以让Periphral之间相互通信的一套机制,由一个16M的clock来驱动,如下图,可以通过config,将PPI的Channel m的EEP(Event End Point)和另外一个Channel n的TEP(Task End Point)建立联系,这样Channel m就与Channel n建立联系了。

    对于上面一段code segment,RTC0,TIMER0及RADIO之间的关系是通过下面这段内置的config来建立的。 NRF_PPI->CHENSET = PPI_CHEN_CH31_Msk 将Channel 31 enable,所以RTC0在COMPARE_EVENT[0]被trigger的时候就启动TIMER0,而TIMER0的COMPARE_EVENT[0]被trigger的时候,RADIO的TX就Enable了。