Contiki简介:
Contiki是一个小型、开源、极易移植的多任务操作系统。它专门设计以适用于一系列的内存受限的网络系统,包括从8位电脑到微型控制器的嵌入系统。Contiki只需几千字节的代码和几百字节内存就能提供多任务环境和内建TCP/IP支持。作为基础的内核以及大部分的核心功能是瑞典计算机科学研究所的网络内嵌系统小组的Adam Dunkels开发的。
51单片机简介:
51单片机是对所有兼容Intel 8031指令系统单片机的统称。该系列单片机的始祖是Intel的8004单片机,后来随着Flash rom技术的发展,8004单片机取得了长足的进展,成为应用最广泛的8位单片机之一,其代表型号是ATMEL公司的AT89系列,它广泛应用于工业测控系统之中。很多公司都有51系列的兼容机型推出,前些年占有大量市场。51单片机是基础入门的一个单片机,还是应用最广泛的一种。
背景:
最近闲来无事,把以前买的51开发板拿出来玩,榨干了51所有的片内外设后,开始把魔爪投向了OS。对比了当前比较成熟的RTOS和51可怜的性能,选择了使用Contiki。在精心裁剪的情况下,运行完整的Contiki内核,仅需100字节左右的RAM和1K字节左右的ROM。我的51开发板使用的MCU是STC89C52和STC90C516AD两种。分别有8K ROM 512B RAM和61K ROM 4K RAM。运行Contiki应该是毫无问题的。
移植综述:
Contiki的内核是由事件驱动的,event-process模型,内核提供了5种定时器(timer,stimer,etimer,ctimer,rtimer)。Contiki的最简移植,至少要移植event-process模型和timer,etimer定时器,并且实现和CPU体系结构相关的时钟模块。
其中event-process在process.c里实现,timer,etimer在timer.c和etimer.c里实现。时钟模块在clock.c里由自己实现,这也是移植要做的主要工作。如此,Contiki内核的基本结构清楚了。看截图
v2-b8663592e4ff5a1ed3997bd3214396a5_720w.jpg
我们创建3个目录,cpu core platform。其中cpu目录下实现和体系结构紧密相关的代码,比如clock.c,rtimer-arch.c是rtimer定时器的具体实现。core是Contiki内核的实现,我目前已经把5种内核时钟全部移植过来了。platform放main函数和一些开发板上的设备驱动。
移植过程:
接下来,我们开始移植的主要工作----cpu目录下的代码实现。
先说clock.c。在这里我们要实现clock的初始化和clock中断服务程序。
void clock_init(void){          TMOD &= 0xF0;                        TMOD |= 0x03; //设置定时器0模式3        TH0=TL0=0x05;//设置计数器初值        IE|=0x01<<1; //定时器0中断使能        IE|=0x01<<3;//定时器1中断使能        TR0=1;//定时器0开始计数        TR1=1;//定时器1开始计数        EA=1;//开中断}
简单解释一下上面的程序,设置计数器初值为5,计数溢出值是256,则每250us(12T,12MHZ)中断一次。定时器0的模式3,除了占用定时器0的中断,还会占据定时器1的中断。因此我们可以得到2个8位定时器。其中我们使用定时器0作为clock时钟源,即Contiki的系统时基。具体介绍可以参看相关手册。
static int counter=0;void intersvr1(void) interrupt 1                                 //定时器0产生系统时基 每秒中断128次 7.8ms中断1次{  counter++;  if(counter<32)return;  counter=0;  DISABLE_INTERRUPTS();  ++count;  if(count % CLOCK_CONF_SECOND == 0) {    ++seconds;  }  if(etimer_pending()      && (etimer_next_expiration_time() - count - 1) > MAX_TICKS) {    etimer_request_poll();         //etimer到期,设置poll标志  }  ENABLE_INTERRUPTS();}
CLOCK_CONF_SECOND 这个参数很重要,它设置了每秒的系统滴答数,这里设置成128,即每秒Contiki时基是128。其中count参数记录了Contiki的滴答总数。因此可以算出Contiki时基长度是1/128=7.8ms。而我们的timer0每250us中断1次,因此我们设置了counter参数,32次才会使count自增,0.25*32=7.8ms,刚刚好。second参数记录了系统运行的秒数。至此Contiki的移植理论上就结束了。
然而现在距离在51上面运行Contiki还有一些路要走。我们继续往下看。
我们在core目录下添加相关Contiki内核文件。如下
v2-9d30503dd5419a49cde8960badfd9700_720w.png
因为KEIL51编译器的古老性,我们要做一些修改,
1)删去PRINTF的宏定义,因为51编译器不支持可变参宏。
2)在process的声明中,有一个函数指针成员thread,51编译器不是传统的堆栈式结构,使用thread会报错,因此要在声明后面添加关键字reentrant,使编译器对thread使用堆栈式的结构处理。
struct process {  struct process *next;#if PROCESS_CONF_NO_PROCESS_NAMES#define PROCESS_NAME_STRING(process) ""#else  const char *name;#define PROCESS_NAME_STRING(process) (process)->name#endif  PT_THREAD((* thread)(struct pt *, process_event_t, process_data_t))reentrant;  struct pt pt;  unsigned char state, needspoll;};
3)把Contiki定义的data变量改为dataa,因为data是51编译器的内置关键字,要避免冲突。
4)修改process.c中的call_process函数
static voidcall_process(struct process *p, process_event_t ev, process_data_t dataa){  int ret;    if((p->state & PROCESS_STATE_RUNNING) &&     p->thread != NULL) {    process_current = p;    p->state = PROCESS_STATE_CALLED;    ret = p->thread(&p->pt, ev, dataa);    p=process_current;//添加这一句,避免reentrant带来的错误    if(ret == PT_EXITED ||       ret == PT_ENDED ||       ev == PROCESS_EVENT_EXIT) {      exit_process(p, p);    } else {      p->state = PROCESS_STATE_RUNNING;    }  }}
有了这些,我们已经完成了Contiki在51单片机上的移植了。接下来我们编写main函数测试Contiki。在contiki-main.c中定义main函数
int main(void){  hardware_init();//做一些硬件初始化的工作  clock_init();  process_init();  process_start(&etimer_process, NULL);//开始etimer定时器进程  process_start(&led, NULL);//开始led闪灯进程  for(;;)process_run();//这里执行  return 0;}
led进程的定义如下
PROCESS(led,"led");//定义led翻转任务PROCESS_THREAD(led, ev, dataa){        static struct etimer et;    PROCESS_BEGIN();        //延时0.5s的时钟        etimer_set(&et,CLOCK_SECOND/2);//每0.5秒调度1次        while(1)        {                //等待0.5s                PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));                        //点亮led                spin_set_gpio_bit_value(GPIO2,0,0);                //等待0.5s                etimer_restart(&et);                    PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));                //关闭led                spin_set_gpio_bit_value(GPIO2,0,1);                etimer_restart(&et);        }   PROCESS_END();}
代码下载到单片机中,复位之后,可以看到led每秒暗灭循环闪动。
至此Contiki在51上面的移植告一段落。以后再更新Contiki的内核机制和编程范式。
项目代码地址:https://github.com/zhangoneone/stc89c52 branch分支