contiki系統分析:時鐘

contiki系統分析:時鐘

  contiki系統提供了一系列的時鐘庫,可以供contiki系統或者用戶態的程序調用.

  時鐘庫包括時鐘到期檢查.在調度時鐘時低功耗的模塊被喚醒,實時的任務調度.

  定時器也可以讓執行具體的事情過程中進入休眼狀態.

contiki的定時器的種類

  contiki包抱一個時鐘模塊,但是有多個時鐘模型:timer, stimer, ctimer, etimer, rtimer.不同的時鐘有不同的作用.有的定時器運行時間長,但是間隔時間短,有的間隔時間長,但是運行時間短.有些能用於中斷的上下文中(rtimer),但是有些不行.

  定時器模塊提供系統時鐘,並且可以短時間的阻塞CPU.整個時鐘庫就是基於定時器來做的.

  timer和stimer是提供了最簡單的時鐘操作,即檢查時鐘週期是否已經結束.應用程序需要從timer中讀出狀態,判斷時鐘是否過期.兩種時鐘最大的不同在於,tmiers是使用的系統時鐘的ticks,而stimers是使用的秒,也就是stimers的一個時鐘週期要長一些.和其它的時鐘不同,這兩個時鐘能夠在中斷中安全使用.可以用到低層的驅動代碼上.

  etimer庫主要提供的是事件時鐘(event timer),在一個時鐘週期後,contiki系統可以使用這個時鐘做事件調度.主要用在contiki進程當系統的其它功能工作或休眼時,這個進程在等待一個時鐘週期.

  ctimer庫主要用於回調時鐘(callback timers),主要用一個時鐘週期完了之後,去調度回調函數.當其它進程進入工作或者休眼狀態時,這個進程仍然可以等待ctimer.由於ctimer本來就是當一個時鐘週期結束時,去調用一個函數並且執行.ctimer運用的場合一般在沒有明顯的進程的地方,比如協議的執行時可以用到ctimer.ctimer可以通過rime協議棧去管理通訊是否超時的.

  rtimer庫主要是用來調度實時任務的.可以用到任何運行的contiki進程中,用時鐘調度的方式去讓實時任務運行.rtimer可用在對時間要求極嚴的的場合,比如無線電模塊要在不延時的情況下開啓或者關閉.

timer模塊

  實現文件core/sys/timer.c,對應的頭文件是用來外部調用的.

  contiki的timer是用來提供定時器的基本設置重啓,清零.或者是檢查時間到期.應用程序必須去主動檢查定時器是否過期,不能自動的的獲得過期的消息.

  timer模塊用clock_time()來獲得當前的系統時間.但是對於不同的MCU,讀取定時器的方法會有所有同,所以這個函數對不同的MCU有一套不同的定義.

  比如CC2530芯片,clock_time()的實現在cpu/cc253x/dev/clock.c中,是contiki的系統時鐘.

  timer結構體定義在core/sys/timer.h中,同樣,其它的不同類弄的模塊都有符合自身功能的一個結構體的定義.

  struct timer { clock_time_t start; clock_time_t interval; };


其中clocl_time_t是一個unsigned short型的變量,定義在cpu/cc253x/8051def.h中.這裏有一個8051的文件,是由於sdcc是基於8051的編譯器,而CC2530也是一個增強型的8051.

  timer結構體中包含兩個變量,start,開始計數點,interval,過期時問.

  timer庫的API如下所示

  void timer_set(struct timer *t, clock_time_t interval) : 開始定時器
void timer_reset(struct timer *t) : 以過期的時間間隔(interval)爲起始點,重啓timer
void timer_restart(struct timer *t) : 以當前時間,即clock_time()爲起始點,重啓tiemr
int timer_expired(struct timer *t) : 檢查定時器是否到期
clock_time_t timer_remaining(struct timer *t) : 定時器計時結束的話,返回值是不可預料的.反回正數表示定時器還差多長時間(只是一個估計值)

  值得注意的是,初始化timer必須要第一步調用timer_set(),它會初始化start和interval的值.

  timer庫可以安全的用在中斷中.下面有個例子,就是說明在中斷中如何去探測定時器結束.

  static struct timer rxtimer; void init(void) { timer_set(&rxtimer, CLOCK_SECOND / 2); } interrupt(UART1RX_VECTOR) uart1_rx_interrupt(void) { if(timer_expired(&rxtimer)) { /* Timeout */ /* ... */ } timer_restart(&rxtimer); /* ... */ }


上面的這個例子中的interrupt函數有許多侷限性,是根據CPU的特點定義的,所以需要看MCU層支持的代碼.

  另外,關於clock_time()這個函數的實現,也牽涉到MCU,現在以2530爲例來分析一下.

  在cpu/cc253x/dev/clock.c這個文件中,有兩個函數.

  /*---------------------------------------*/ CCIF clock_time_t clock_time(void) { return count; } /*---------------------------------------*/ CCIF unsigned long clock_seconds(void) { return seconds; }

裏面實現了clock_time和clock_seconds,同時也引出了兩個全局變量count和seconds.這兩個變量正是timer和stimer實現的關鍵.前面提到過,timer是以tick爲單位來計數的,但是stimer是以seconds爲單位計數的.我們看一下這兩個全局變量的定義cpu/cc253x/dev/clock.c:

  /* Sleep timer runs on the 32k RC osc. */ /* One clock tick is 7.8 ms */ #define TICK_VAL (32768/128) /* 256 */ /*---------------------------------------*/ #if CLOCK_CONF_STACK_FRIENDLY volatile __bit sleep_flag; #else #endif /*---------------------------------------*/ /* Do NOT remove the absolute address and do NOT remove the initialiser here */ __xdata __at(0x0000) static unsigned long timer_value = 0; static volatile __data clock_time_t count = 0; /* Uptime in ticks */ static volatile __data clock_time_t seconds = 0; /* Uptime in secs */


對於CC2530的CPU來說,總共包含了兩個高頻振盪器(32MHz的晶振和16MHz的RC振盪器)和兩個低頻振盪器(32KHz的晶振和低頻振盪器).

  休眼時鐘是運行在32KHz的RC振盪器上的.精確的振盪週期爲32.768KHz,

  而系統的一個tick就是#define TICK_VAL (32768/128) /* 256 */來規定的.

  這裏做一下簡單的計算,1/(32768/256)也就是7.8ms.

  在CC2530的clock實現中,每一次增加count就加1.

  然後在cpu/cc253x/8051def.h中間有

  /* Defines tick counts for a second. */ #define CLOCK_CONF_SECOND 128


這個定義是7.8msx128=1s,也就是說,seconds的值與count的值是128倍的關係.

  這樣cc2530同時保留了timer和stimer的增量.

stimer模塊

  實際上stimer這個模塊跟timer的用法,及API的功能完全一致,只不過是以seconds爲單位計時的.而且在timer中也提到了如何實現的.

  API如下

  void stimer_set(struct stimer *t, unsigned long interval)
void stimer_reset(struct stimer *t)
void stimer_restart(struct stimer *t)
int stimer_expired(struct stimer *t)
unsigned long stimer_remaining(struct stimer *t)

etimer模塊

  contiki中的etimer是提供了一個時鐘來產生計時事件.當結束計時時,會給進程發送一個PROCESS_EVENT_TIMER類型的事件.etimer使用的時鐘是clock_time(),即系統時鐘.

  etimer時鐘最核心的內容就是etimer這個結構體.

  struct etimer { struct timer timer; struct etimer *next; struct process *p; };


單看這個結構體,不看其它內容.可以知道etimer是用鏈表形式管理的,每一個etimer對應於一個進程.

  void etimer_set(struct etimer *t, clock_time_t interval) : 開始時鐘
void etimer_reset(struct etimer *t) : 以前的時間間隔重新沒置時鐘
void etimer_restart(struct etimer *t) : 以當前的時間爲間隔設置時鐘
void etimer_stop(struct etimer *t) : 停止時鐘
int etimer_expired(struct etimer *t) : 檢查時鐘是否終止
int etimer_pending() : 檢查還有沒有時鐘在工作
clock_time_t etimer_next_expiration_time() :得到下一個事件時鐘的終止時間.
void etimer_request_poll() :這一個接口在後面再詳細描述。

  要注意,由於時鐘事件本身就是用用contiki系統的事件時鐘調度的。當一個事件時鐘需要從另一個contiki的process裏面回調一個函數時。可以用PROCESS_CONTEXT_BEGIN() 和PROCESS_CONTEXT_END() 來臨時的改變一下進程的上下文。
還要注意的是,etimer不能用在中斷中。因爲它是進程間的調度。

  下面是一個例子,使用了一個事件定時器,讓一個進程每分鐘執行一次。

  PROCESS_THREAD(example_process, ev, data) { static struct etimer et; PROCESS_BEGIN(); /* Delay 1 second */ etimer_set(&et, CLOCK_SECOND); while(1) { PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et)); /* Reset the etimer to trig again in 1 second */ etimer_reset(&et); /* ... */ } PROCESS_END(); }


對於etimer的實現core/sys/etimer.c,還有一部分內容需要說明。

  timerlist是把整個的etimer做爲了個鏈表來維護的。然後在add_timer中採用前序插入法,把最新的etimer放到最前面。然後供etimer_set等API去做內部調用。

  next_expiration變量。主要是用於獲得下一次的etimer的時間間隔。用來實現etimer時鐘的校準。遍歷了鏈表上所有的etimer,如果哪個etimer的間隔時間最短,就把它做爲next_expiration。即最小值。

  static struct etimer *timerlist; static clock_time_t next_expiration; .... /*---------------------------------------*/ static void update_time(void) { clock_time_t tdist; clock_time_t now; struct etimer *t; if (timerlist == NULL) { next_expiration = 0; } else { now = clock_time(); t = timerlist; /* Must calculate distance to next time into account due to wraps */ tdist = t->timer.start + t->timer.interval - now; for(t = t->next; t != NULL; t = t->next) { if(t->timer.start + t->timer.interval - now < tdist) { tdist = t->timer.start + t->timer.interval - now; } } next_expiration = now + tdist; } }


其中在etimer_stop實現中

  void etimer_stop(struct etimer *et) { struct etimer *t; /* First check if et is the first event timer on the list. */ if(et == timerlist) { timerlist = timerlist->next; update_time(); } else { /* Else walk through the list and try to find the item before the et timer. */ for(t = timerlist; t != NULL && t->next != et; t = t->next); if(t != NULL) { /* We've found the item before the event timer that we are about to remove. We point the items next pointer to the event after the removed item. */ t->next = et->next; update_time(); } } /* Remove the next pointer from the item to be removed. */ et->next = NULL; /* Set the timer as expired */ et->p = PROCESS_NONE; }


注意一下中間的for循環,它是用於找出下一個etimer是傳入參數et的情況。然後再把et這個鏈表的指向銷燬。然後把進程標記爲空,即PROCESS_NONE。

  這兒有一個疑問。爲什麼在創建,銷燬時鐘時,沒有使用malloc,free之類的操作。其根本原因還是由於contiki本身適用於內存受限的操作系統,而且在編譯時,把程序的各個段己經規定好了,不使用HEAP區域。etimer的內存空間最多也只能放在data或者是bss段中。所以纔有了只清除內部的變量,而不釋放本身的內存的做法。實際上,個人建議,把etimer的分配放到前面內存分配討論過的mmem方式在初始化系統時去動態分配內存比較好。

  etimer是依賴於MCU平臺的。並且它需要依靠etimer_request_poll() 去回調進程,用來管理事件定時器。這也意味着,etimer可以讓系統從休眼狀態中喚醒,關於MCU的休眼和喚醒,在硬件上有特定的定時器。比如在cc2530中,就有專門的sleep timer來實現。etimer庫提供了3個接口來實現操作。

  etimer_pending() 檢查是否有還沒有過期的etimer

etimer_next_expiration_time() 得到下一個etimer的過期時間。

etimer_request_poll() 用來通知etimer庫系統時鐘要改變了或者是etimer要過期了,一般會週期性的調這個函數,然後檢查系統時鐘中否改變了。這個函數可以用在中斷中。

  由於etimer_repuest_poll()是用來調用etimer_process進程.我們把這個進程的代碼再分析一遍.

  /*---------------------------------------*/ PROCESS_THREAD(etimer_process, ev, data) { struct etimer *t, *u; PROCESS_BEGIN(); timerlist = NULL; while(1) { PROCESS_YIELD(); if(ev == PROCESS_EVENT_EXITED) { struct process *p = data; while(timerlist != NULL && timerlist->p == p) { timerlist = timerlist->next; } if(timerlist != NULL) { t = timerlist; while(t->next != NULL) { if(t->next->p == p) { t->next = t->next->next; } else t = t->next; } } continue; } else if(ev != PROCESS_EVENT_POLL) { continue; } again: u = NULL; for(t = timerlist; t != NULL; t = t->next) { if(timer_expired(&t->timer)) { if(process_post(t->p, PROCESS_EVENT_TIMER, t) == PROCESS_ERR_OK) { /* Reset the process ID of the event timer, to signal that the etimer has expired. This is later checked in the etimer_expired() function. */ t->p = PROCESS_NONE; if(u != NULL) { u->next = t->next; } else { timerlist = t->next; } t->next = NULL; update_time(); goto again; } else { etimer_request_poll(); } } u = t; } } PROCESS_END(); }


  在這個函數的實現中,我們可以看到:

  timerlist是整個系統統護的etimer的鏈表.由於protothread的作用,當前進程可能隨時改變.那麼,timerlist的值也會改變.

  etimer_process主要是收集要結束的etimer,並且給其它進程發出event timer類型的消息.

  接受PROCESS_EVENT_EXIT事件,找出要退出的etimer;

  接受PROCESS_EVENT_TIMER事件,然後再從事件隊列裏刪除已經到期的etimer,並且標記爲PROCESS_NONE。
PROCESS_EVENT_POLL的事件沒有做任何處理,大概屬於後期處理的代碼。

  實際上,etimer_process這個進程運行的目的也就是不斷的銷燬即將退出的etimer.

ctimer模塊

  contiki系統中的回調的計時器,底層照例用的clock_time()這個系統時鐘.

  首先關心的是ctimer的結構體:

  struct ctimer { struct ctimer *next; struct etimer etimer; struct process *p; void (*f)(void *); void *ptr; };

其中ctimer->f即回調函數的接口.ctimer->ptr表示回調函數的參數.

  列出了ctimer的API,由於使用方法與上面的timer相同,不做過多的解釋.

  void ctimer_set(struct ctimer *c, clock_time_t t, void(*f)(void *), void *ptr)
void ctimer_reset(struct ctimer *t)
void ctimer_restart(struct ctimer *t)
void ctimer_stop(struct ctimer *t)
int ctimer_expired(struct ctimer *t)

示例:

  static void callback(void *ptr) { ctimer_reset(&timer); /* ... */ } void init(void) { ctimer_set(&timer, CLOCK_SECOND, callback, NULL); }


  我們再來分析一下ctimer的啓動和設置情況.涉及到兩個函數

  void ctimer_init(void);

  void ctimer_set(struct ctimer *c, clock_time_t t,void (*f)(void *), void *ptr);

  init函數必需在contiki開始啓動時調用,一般放在初始化的變量中.

  set函數有4個傳入參數

  *c表示需要使用的ctimer的地址

  t表示使用的時鐘,有tick和second之分

  f是回調的函數

  *ptr是回調函數的參數,一般可以寫個結構體.如果沒有,可以把這個參數設爲NULL.

  set函數執行的時機,一般在這個ctimer過期時,就開始執行回調函數.

  我們再看看ctimer_process這個進程的實現.

  PROCESS(ctimer_process, "Ctimer process"); PROCESS_THREAD(ctimer_process, ev, data) { struct ctimer *c; PROCESS_BEGIN(); for(c = list_head(ctimer_list); c != NULL; c = c->next) { etimer_set(&c->etimer, c->etimer.timer.interval); } initialized = 1; while(1) { PROCESS_YIELD_UNTIL(ev == PROCESS_EVENT_TIMER); for(c = list_head(ctimer_list); c != NULL; c = c->next) { if(&c->etimer == data) { list_remove(ctimer_list, c); PROCESS_CONTEXT_BEGIN(c->p); if(c->f != NULL) { c->f(c->ptr); } PROCESS_CONTEXT_END(c->p); break; } } } PROCESS_END(); }


由於ctimer同樣也是event timer,只不過多了一個回調的功能,所以在初始化ctimer時,用etimer_set來初始化系統啓動時需要的ctimer.然後標記初始化完成.在ctimer_process中,主要是去輪詢過期的ctimer,然後再進行回調.請注意,由於回調函數在另一個進程中,所以根據protothread的設計,需要調用PROCESS_CONTEXT_BEGIN()和PROCESS_CONTEXT_END()來臨時的開闢其它進程中的上下文,最後關閉.

rtimer模塊

  第一步還是看一下rtimer的結構體

  struct rtimer { rtimer_clock_t time; rtimer_callback_t func; void *ptr; };


時鐘類型,回調函數名,然後回調函數的參數.

  函數指針定義

  typedef void (* rtimer_callback_t)(struct rtimer *t, void *ptr);

  這個timer不再是一個鏈表,只是一個timer,去提供實時性的需求.

  rtimer本來就是用做contiki的實時任務的.有自己的時鐘模塊.利用rtimer_time來得到當前的系統時鐘.然後用RTIMER_SECOND表示每一分鐘的tick數.這兒的時鐘跟timer模塊用的晶振不一樣.這已經是CPU上的時鐘了.在CC2530上,利用CLKCONCMD寄存器,設置成500KHz的時鐘.由於分頻置TICKSPD[2:0]設的32MHz,也就是說RTIMER_SECOND設置成 500KHz/32Hz 爲15625U,定義在cpu/cc253x/rtimer-arch.h中.是屬於體系結構相關的代碼.

  和contiki中的其它時鐘庫不一樣.rtimer要保證實時任務的優先級,並且立即執行.主要是大部分的函數都不具備優先執行權,所以用rtimer去優先執行是可取的.如果放在中斷安全的一些函數中例如process_poll()中執行的話,與其它非優先級的進程一起執行的話,容易產生衝突,造成非優線程只能異步去執行.

  有兩種類型的實時任務.一種是硬實時任務,一種是軟實時任務.其中硬實時任務的優先級要高點,

rtimer與其它的timer不一樣的是,它在平臺這一層只是提供了三個接口.

  rtimer_init, rtimer_set, rtimer_run_next這三個接口.但是這三個接口的實現是嚴重依賴於MCU的特性的.

  其中主要調用了rtimer_arch_schedule,在cc2530中是把時鐘的模式先捕獲,捕獲完了做輸出比較.

  rtimer的具體實現現在還是分析的相當明白.需要對cc2530的定時器模式和工作流程做好分析之後,才能回過頭來看rtimer 具體作用.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章