contiki系统分析六:时钟

来源:互联网 发布:遗传算法 优缺 编辑:程序博客网 时间:2024/04/27 15:52

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_tclock_time(void){  return count;}/*---------------------------------------------------------------------------*/CCIF unsigned longclock_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_FRIENDLYvolatile __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 voidupdate_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实现中

voidetimer_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 具体作用.

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 尿路口长疮疼痛怎么办 来月经吃了辣的怎么办 泳衣打湿后特别难脱怎么办 脚臭怎么办教你除臭方法 袜子没干就穿了进湿气怎么办 狗喜欢往床上跑怎么办 泰迪在床上睡觉怎么办 泰迪睡觉换地方怎么办 猫一定要和人睡怎么办 狗喜欢跳到床上怎么办 幼猫晚上不睡觉怎么办 宝宝不在床上睡觉怎么办呢 瑜伽球表面粘了怎么办 鞋子洗了很臭怎么办 在社区开瑜伽馆怎么办 腿被开水烫伤了怎么办 狗狗的腿脱臼了怎么办 手火辣辣的烧疼怎么办 网贷收到告知函怎么办 收到捷信催收律师函怎么办 快钱贷款不还怎么办 快易花逾期3个月怎么办 欠微粒贷一万多没还找上门了怎么办 装修公司骚扰电话太多了怎么办 总有大便的感觉怎么办 黎明杀机无网络连接怎么办 监控视频电脑播放不了怎么办 绝地手游击倒了怎么办? 绝地求生全军出击倒地后怎么办 黑魂3杀了npc怎么办 菜刀背容易割手怎么办 商铺厨房太热怎么办 农村自建房厨房卫生间怎么办 宾利车门不会开怎么办 两岁的宝宝好动怎么办 被别人坏了名声怎么办 在单位混臭了怎么办 在公司名声臭了怎么办 怀孕三个月吐的厉害怎么办 孕38周轻微贫血怎么办 孕38周中度贫血怎么办