英文书名是《Educated》,翻译成中文后的书名搞的好复杂,看得出译者在非常努力的想表达这本书的内涵。
塔拉•韦斯特弗出生在美国爱达荷州山区的一个农民家庭,这本书是她的自传,记录了自己从一个乡村小姑娘成长为剑桥博士的心路历程。
作者
书中出场的所有重要角色主要为她的家庭成员,塔拉兄妹共8人:6个哥哥,1个姐姐。对整个家庭影响最大的就是父亲和哥哥肖恩。父亲独断专横,哥哥家暴,习惯性的殴打妹妹们,给小塔拉造成了极大的不安,塔拉在外出求学初期,几乎无法信任别人,过了很长时间才知道接受别人的帮助是正确的,之后她的人生就开挂了。塔拉最终通过教育重塑了自己,远离了家庭。在与家庭割裂的过程中,塔拉的心灵多次受到重创,深患抑郁症。她最终成为剑桥博士,而这个时候她已经完全回不去山区的老家了。
塔拉在自我觉醒的那刻写了这么段话来表明自己家庭的原罪:我已察觉出我们是如何被别人给予我们的传统塑造,而这个传统我们有意无意的忽略了。
Nimble Advertising Start time计算
参考文献:
在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了。
nimble Link layer 状态机
Link Layer总共有7个状态,这7个状态串起了整个Link layer的运作。
1 | #define BLE_LL_STATE_STANDBY (0) |
stateDiagram [*] --> STANDBY : reset STANDBY --> ADV STANDBY --> SCANNING STANDBY --> SYNC ADV --> STANDBY ADV --> SYNC ADV --> CONNECTION SCANNING --> STANDBY SCANNING --> INITIATING SCANNING --> SYNC INITIATING --> CONNECTION INITIATING --> STANDBY CONNECTION --> STANDBY CONNECTION --> DTM CONNECTION --> SYNC DTM --> STANDBY SYNC --> STANDBY
nimble advertising
今天顺利将美人鱼(Mermaid)插件装上,整理一张时序图:
sequenceDiagram NPL->>HCI:BLE_HCI_OCF_LE_SET_ADV_ENABLE HCI->>HCI:ble_ll_hci_cmd_proc() HCI->>ADV: ble_ll_hci_adv_set_enable() note over ADV: ble_ll_adv_sm_start() ADV->>Scheduler: ble_ll_sched_adv_new() Scheduler->>Scheduler: ble_ll_sched_run() note over Scheduler: ble_ll_sched_execute_item() Scheduler->>ADV: ble_ll_adv_tx_start_cb() ADV->>PHY: ble_phy_tx() PHY->>ADV: ble_ll_adv_tx_done() ADV->>NPL: adv_txdone_ev NPL->>ADV: ble_ll_adv_event_done() note over ADV: ble_ll_adv_done() note over ADV: ble_ll_adv_reschedule_event() ADV-->>Scheduler: ble_ll_sched_adv_reschedule() RADIO->>PHY: RADIO_IRQn note over PHY: ble_phy_isr() note over PHY: ble_phy_rx_start_isr() PHY->>ADV: ble_ll_adv_rx_isr_start() note over PHY: ble_phy_rx_end_isr() PHY->>ADV: ble_ll_adv_rx_isr_end() note over ADV: ble_ll_adv_rx_req() ADV->>PHY:ble_phy_tx(ble_ll_adv_scan_rsp_legacy_pdu_make) NPL->>LL: ble_ll_event_rx_pkt() LL->>LL: ble_ll_rx_pkt_in() LL->>ADV: ble_ll_adv_rx_pkt_in() note over ADV: ble_ll_adv_conn_req_rxd() ADV->>CONN: ble_ll_conn_slave_start() note over ADV: ble_ll_adv_sm_stop() note over CONN: ble_ll_conn_created() CONN->>Scheduler:ble_ll_sched_slave_new() note over CONN: ble_ll_conn_next_event()
将本地文件夹push到github
进入文件夹,git init 把这个目录变成git可以管理的仓库
1
git init
把文件添加到版本库中,使用命令 git add . 添加到暂存区里面去,不要忘记后面的小数点“.”,意为添加文件夹下的所有文件
1
git add .
用命令 git commit告诉Git,把文件提交到仓库。引号内为提交说明
1
git commit -m 'first version'
关联到远程库
1
git remote add origin https://github.com/reponame.git
把本地库的内容推送到远程,使用 git push命令,实际上是把当前分支master推送到远程。执行此命令后会要求输入用户名、密码,验证通过后即开始上传。
1
git push -u origin master
舞曲
几段喜欢的音乐,如有侵权,请随时通知。
-
“哦嘛哩嘛嘞,邦邦”
简单欢快,不知道是哪个国家舞蹈。 -
快板、慢板、快板三段,中间慢板最美,柔中带刚,刚中带柔,有一种飘来飘去的感觉。
一直没搞清楚奔腾的到底是骏马?还是套马的汉子? -
也算是一个拉丁舞种。
-
改编自腾格尔的歌曲,刘福洋和万玛尖措表演,表达两兄弟来开草原时恋恋不舍的内心。
-
探戈。
作为一个节奏控,跑步必须有鼓点,要不就跑不起来。下面几个蒙古神曲,伴我每天跑过5km
Nimble scheduler
Nimble controller 的Scheduler是由一个32K的timer来驱动的,一个tick是30.52us,完全执行在timer的isr里,所以timer callback里要做的事情不能太耗时(超过一个tick),否则scheduler就不准确了,如下为scheduler的主函数:1
2/* Initialize cputimer for the scheduler */
os_cputime_timer_init(&g_ble_ll_sched_timer, ble_ll_sched_run, NULL);
时间单位
- Tick:由clock决定,对于32K clock来说,一个tick就是 1000000us/(32*1024) = 30.52us.
- Slot:1250个毫秒
- Period:N * Slot,N是可配置的
- Epoch:M * Period,M是可配置的
Scheduler在调度的时候,有时要将Connection event放在Period的边界,scanning/initiating/advertising是尽可能发生在没有被用到的Period内。1
2
3/* Time per BLE scheduler slot */
#define BLE_LL_SCHED_USECS_PER_SLOT (1250)
#define BLE_LL_SCHED_32KHZ_TICKS_PER_SLOT (41) /* 1 tick = 30.517 usecs */
调度单位
每一个可被调度的单位叫做Sched item, 这些item放在FIFO的List中,每当timer到期后Scheduler就从List里拉一个出来做执行。
每一个item有如下几个属性:
- schedule type,类型
- enquened,是否入Q,入Q就代表是Ready,等待被执行
- remainder,剩余的执行时间
- start_time, 开始执行时间
- end_time, 结束执行时间
- cb_args, item执行函数的参数
- sched_cb, item被调度到的时候的执行函数
如下是相关的code segment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 struct ble_ll_sched_item
{
uint8_t sched_type;
uint8_t enqueued;
uint8_t remainder;
uint32_t start_time;
uint32_t end_time;
void *cb_arg;
sched_cb_func sched_cb;
TAILQ_ENTRY(ble_ll_sched_item) link;
};
/* Types of scheduler events */
#define BLE_LL_SCHED_TYPE_ADV (1)
#define BLE_LL_SCHED_TYPE_SCAN (2)
#define BLE_LL_SCHED_TYPE_CONN (3)
#define BLE_LL_SCHED_TYPE_AUX_SCAN (4)
#define BLE_LL_SCHED_TYPE_DTM (5)
#define BLE_LL_SCHED_TYPE_PERIODIC (6)
#define BLE_LL_SCHED_TYPE_SYNC (7)
调度原则
- Sched item之间没有优先级的关系,完全按照Sched item的start_time为序,一个一个的执行。
- 调度到某个item时就直接执行这个item的sched_cb,并将这个item从调度列表上删掉,callback执行完后,再起timer,等待下一个item的start_time到期。
- item在插入到调度列表的时候就已经排好序了。
- 在插入新的Adv Sched item的时候,新的Adv要避开每一个跟他有overlap的item,直到找到一个完全跟它没有overlap的位置,这意味着在有同样的start time的情况下,先入list的优先级高。
- 以插入一个Adv Schedule为例,假如Scheduler的ReadyList里有4个item,它们的排列如下。那么下图中绿色的item就是可以直接插入的item,而红色的item则需要一直往后移动,一直移到一个能插入>的地方,也许到list最后才能有它的一席之地。
- 在一个adv event执行完成后,需要再计算下一个adv event起来的时间,请参考下图。
Item中的Delay(0~10ms)指的是这个adv event最大允许delay的时间。
- 以插入一个Adv Schedule为例,假如Scheduler的ReadyList里有4个item,它们的排列如下。那么下图中绿色的item就是可以直接插入的item,而红色的item则需要一直往后移动,一直移到一个能插入>的地方,也许到list最后才能有它的一席之地。
- 但是对于type为CONN的Sched是有点优先级的
- 正在execute的type为CONN的Sched的优先级最高,要新建立的conn的sched (ble_ll_sched_slave_new())如果刚好跟正在execute的conn overlap,那直接就取消新的这个conn。
1
2
3
4
5/* The schedule item must occur after current running item (if any) */
if (ble_ll_sched_overlaps_current(sch)) {
OS_EXIT_CRITICAL(sr);
return rc;
}
- 正在execute的type为CONN的Sched的优先级最高,要新建立的conn的sched (ble_ll_sched_slave_new())如果刚好跟正在execute的conn overlap,那直接就取消新的这个conn。
- 在收到CONN_IND的时候,为了能尽快在windowOffset到达时发包出去回应对方,会将跟他overlap的type为CONN的sched 给盖掉
- 但这包如果type是其他的type,则会继续往后shift找合适的位置,但如果是回应CONN_IND的第一包的话,不在规定时间发包出去,link就建立不起来,这里还是有点问题。
1
2
3
4
5
6 if (ble_ll_sched_is_overlap(sch, entry)) {
/* If we overlap with a connection, we re-schedule */
if (ble_ll_sched_conn_overlap(entry)) {
break;
}
}
更正 #4
感谢jaydenh215的指导,上图中关于Adv reschedule的图例欠妥,正确的符合code行为的插入Sched的示意图如下。
如果超过了max_delay,那插入也是失败的,如下图,虽然在#3的前面有位置给sch插入,但是因为overdue了,所以插入会失败。