参考文献:
在start advertising的时候,从Host角度看非常的简单,只是通过如下API送几个参数给Controller,然后等着event回来做相应的处理就好了,而Controller则要考虑很多细节来找到比较合适的真正的开始发送Adv data的时间,本文介绍下Nimble计算这个时间的一个流程。1
2
3
4
5int
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的几个步骤:
图中横轴为时间,每个小格子为一个tick,30.5us。为了便于理解,假定Controller在收到Host的请求,执行到函数ble_ll_adv_sm_start() 开始计算Adv的start time时,tick刚好为100,后续的各个关键步骤以及相关的时间点如下:
- 依照Spec,一般会在Adv开始点加一个随机的AdvDelay(小于10ms)以避免adv event与Scan window 完美错开,导致对方怎么扫都扫不到。图中假定这个AdvDelay是15个tick,也就是adv_pdu_start_time 是115。
启动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);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 tick 。115提前13个tick,start_time就是102。
接着将这个预期的的start_time通过函数
1
ble_ll_sched_adv_new()
送给scheduler去确定是否能在这个时间点正式开始。假如很幸运,这个adv的sched没有跟已有的sched overlap,那就直接到到第#6步。
- 如果第#4步中有发现该sched跟其他的sched有overlap,那这个start time就只能由Scheduler来找个合适的位置了,假定找到的位置是122。
- 在122正式开始发送数据,由#2知,真正能开始发送数据的时间时g_ble_ll_sched_offset_ticks后,即13个tick后,也就是135才是Radio真正开始发送PDU的时候。
- 而要在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
7if (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
25next_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了。