摘要:
本文剖析Contiki最简单的实例LED闪烁,深入源码分析,详解本实例用到的各个宏,进而给出一份完整展开的代码。最后把本实例用到的宏总结成API,并给出了创建一个进程的模型。
1.Contiki背景
Contiki是一个小型、开源、极易移植的多任务操作系统。它专门设计以适用于一系列的内存受限的网络系统,包括从8位电脑到微型控制器的嵌入系统。Contiki只需几千字节的代码和几百字节的内存就能提供多任务环境和内建TCP/IP支持。Contiki由标准C语言开发,具有很强的移植性,已被移植到多种平台,包括8051、SP340、AVR、ARM,并得到广泛应用。
2.MCU简介
这次移植,我使用的是STC8A8K64S4A12,比51单片机快12倍以上。算是很强大的。
有些同学可能会觉得我使用的这款MCU的Flash有64K已经足够大的去放程序了,那为什么要使用操作系统呢?
3.选择使用操作系统的原因
- 当单片机使用上操作系统后,就会有很多资源可以利用、系统的内存管理、线程进程的使用。文件系统的使用,图形系统的使用,这个是你在没有上操作系统的时候不能使用的。
- 对于多任务的处理更加简单了,原来比方说我了个机器人的程序,用裸机进行的,我有5个传感器,我还有一个液晶屏,还有1个按键,我就得用定时器来进行,进一个定时器,我就做一件事,主程序里只能做一件事,而如果我上了操作系统,我在程序里就可以开多个线程,来同时做这样的事,不用我自己去管理这种处理顺序,由操作系统替我进行管理。
4.Contiki移植综述
Contiki的内核是基于事件驱动的,系统运行可以视为不断处理事件的过程。Contiki整个运行时通过事件触发完成,一个事件绑定相应的进程。Contiki内核提供了5种定时器 ,即timer(描述一段时间,以系统时钟嘀嗒数位单位)、stimer(描述一段时间,以秒为单位)、ctimer(定时器到期,调用某函数,用于Rime协议栈)、etimer(定时器到期,触发一个事件)、rtimer(实时定时器,在一个精确的事件调用函数)。如果想要移植Contiki,至少要移植event-process模型和timer,etimer定时器。
event-process进程是在process.c中实现,timer在timer.c和etimer.c里实现。时钟模块在clock.c中实现,我们主要移植的就是clock.c文件。
5.Contiki移植过程
在移植前,我们需要下载Contiki-os的源文件。
下载完Contiki源文件后,我们还是要懂各个文件夹的代表的含义,否则到移植完都会云里雾里。
5.1Contiki源代码目录结构解析
Contiki的根目录如图5.1所示。根目录下包含了:apps、core、cpu、dev、doc、examples、lib、platform、regression-tests、tools
文件夹。其余几个文件是说明文档以及Makefile.include脚本文件。
图5.1
各目录功能如下:
apps:包含了许多在Contiki系统上的应用程序,如数据库antelope、web服务器、基于串口的shell命令、telnet程序、coap应用层协议等。
core:Contiki系统的核心代码目录,包含了Contiki中与硬件无关的代码、网络协议栈、硬件驱动程序头文件等。
cpu:包含了与微控制器移植相关代码,包含了寄存器定义、Contiki内核与硬件相关的代码(如时钟、定时器等)、微控制器的驱动程序。Contiki对硬件的要求很低,移植十分方便,目前已经支持8051系列、AVR、MSP430、ARM、ARM-Cortex、X86等处理器。其中cpu/cc253x目录包含了8051的一些移植代码。
dev:包含了设备驱动API。
doc:包含了各个文件夹的介绍文件。
examples:包含了许多Contiki编程示例代码,用户编程时可以参考这些程序,或者直接在这些代码中进行更改。
platform:与电路板相关的移植代码和驱动。电路板中包含核心微控制器,还包括各类外围通信器件、传感器器件,如USB通信、串口通信、温湿度传感器等,CPU目录中只有为控制器相关的移植代码,而相关的外围设备驱动程序在platform中定义。
regression-tests:之前写的测试程序,用于退回程序。(对我们帮助不是很大)
tools:包含了调试、开发、下载等相关的各类程序,如网络仿真工具Cooja、MSP430指令级网络仿真工具MSPsim、tunslip和tunslip6工具等。
5.1.2Core目录说明
Core目录下包含的子目录如图5.2所示,Core子目录下包含了:cfs、ctk、dev、lib、loader、net、sys
文件夹,还有一些.h控制头文件。
图5.2
cfs:cfs是Coffe file system的简写,是contiki之上的一个小型的基于Flash寄存器的文件系统,是针对无线传感器网络资源受限的特点而设计得,其特点是:减少内存使用、支持大规模存储。
ctk:ctk是The Contiki Toolkit的简写,该目录中的代码是给Contiki操作系统提供图形化的操作界面。
dev:该目录包含了Contiki操作系统中一些常用的驱动程序的头文件定义以及驱动程序中与硬件无关的代码,用户移植contiki时根据这些头文件定义的api编写驱动程序,一些典型的驱动包括spi.h、slip.h、leds.h、watchdog.h等。
lib:包含了contiki操作系统以及其他程序用到的一些常用库函数,如链表数据结构list、环形缓冲区ringbuf、随机函数发生器rando反傅里叶变换ifft、字符串比较strncasecmp等函数,用户需要相应的库函数,只需要把对应的库文件添加到工程中,进行编译即可。
loader:contiki是面向无线传感网络应用的小型操作系统,在这类小型嵌入式系统中,通常是整个程序编译后烧写到设备中,程序如果需要修改则必须重新编译和烧写,而传感网络中节点数量多,重新烧写困难。因此,contiki实现了一个小型的动态加载模块loader,它允许用户在需要的时候动态加载应用程序,提高灵活性。
net:包含了contiki上网络协议相关的代码,包含了IPv4、IPv6、Lowpan、RPL等基于IP的网络层代码,还包括MAC层协议,如IEEE802.15.4、ContikiMAC等。此外contiki还包含了rime协议栈。
sys:包含了Contiki操作系统内核的所有代码,实现任务调度、事件驱动、定时器等相关功能,是操作西戎的核心文件。
其余的头文件:contiki.h包含contiki相关的所有头文件、contiki-lib.h包含了常用库的头文件、contiki-net.h包含了net相关的头文件、contiki-version h包含了当前contiki的版本号字符串。以上目录中,sys、 net、lib、 dev中的源代码通常是需要的,而cfs、 ctk、 loader中的文件则根据实际应用需要进行添加。Contiki中的代码大多是模块化结构,可以根据需要进行灵活裁剪,满足多种应用的需要。
5.2开始移植吧
移植时,我们根据项目的需要移植相关的文件,需要移植的文件如下图所示:
目前网上找到的中文教程都不给文件地址,当时找文件就找了好久。
- cpu文件夹主要放的是系统结构代码。
- core文件夹主要放的是Contiki内核的代码。
-
- etimer.c文件在
contiki-master\contiki-master\core\sys\etimer.c
- process.c文件在
contiki-master\contiki-master\core\sys\process.c
- timer.c文件在
contiki-master\contiki-master\core\sys\timer.c
- etimer.c文件在
- platform文件夹放main函数。
- mine文件夹放开发板上的设备底层驱动代码。
5.2.1 移植clock.c文件
我们找到contiki-master\core\sys\clock.h
/*初始化Contiki时钟*/ void clock_init(void); /*获取当前系统时钟嘀嗒数*/ CCIF clock_time_t clock_time(void); /*获取当前系统时钟秒数*/ CCIF unsigned long clock_seconds(void); /*设置当前系统时钟秒数*/ void clock_set_seconds(unsigned long sec); /*等待给定的嘀嗒数*/ void clock_wait(clock_time_t t); /*延时给定的毫秒数*/ void clock_delay_usec(uint16_t dt);
- 实现clock的初始化函数
void clock_init(void) { TIM_AUXR |= 0x80; //不分频 TMOD &= 0xF0; //定时器0/模式0 TL0 = 0x33; //设置定时初值 TH0 = 0xf5; //设置定时初值 TR0 = 1; //定时器0开始计时 ET0 = 1; //使能定时器中断 EA =1; //总中断开启 }
使用定时器0模式0作为clock的时钟源,即Contiki的系统时基,timer0每250us中断一次。
- 实现clock的中断函数
中断函数中CLOCK_CONF_SECOND(非常重要)设置了每秒的系统嘀嗒数,这里设置成128,即每秒Contiki时基是128。
count记录了Contiki的滴答总数,因此可以算出Contiki时基长度是
上面clock初始化设置定时器0每250us中断一次,因此设置了counter参数,32次才会使count递增1,。
所以,seconds参数记录了系统运行的秒数。
static int counter=0; void inter0(void) interrupt 1 { ++counter; if(counter<32)return; counter=0; DISABLE_INTERRUPTS(); //关闭中断 ++count; //嘀嗒数(中断数) if(count % CLOCK_CONF_SECOND == 0) { //每128次中断就是7.8ms ++seconds; //秒数 } if(etimer_pending() //是否有未到期的事件计时器 && (etimer_next_expiration_time() - count - 1) > MAX_TICKS) { etimer_request_poll(); //etimer到期,设置poll标志 } ENABLE_INTERRUPTS(); //开启中断 }
移植完Contiki系统下的clock心脏,其实移植差不多已经结束了,但是由于51单片机的特殊性,需要再更改一些参数。
5.2.2 移植process.c文件
- 在51中不能使用可变参宏,所以需要删去第86-89行PRINTF的定义。
- 因为操作系统最基本的功能就是多线程,在process.h中,有一个函数指针成员为thread,光使用thread会报错,因此需要在后面加入关键字reentrant(reentrant声明的函数为可重入函数,可重入函数能够被多个进程同时调用)
/*315-326行*/ struct process { struct process *next; const char *name; PT_THREAD((* thread)(struct pt *, process_event_t, process_data_t))reentrant; struct pt pt; unsigned char state, needspoll; };
- 因为在51编译器内的关键字是data,所以需要把process下定义的data都改成dataa,避免冲突。
- 修改process.c中的call_process函数(上面说到的都需要改一下,该注释的注释掉,类型改变的变一下)
/*174-199行*/ static void call_process(struct process *p, process_event_t ev, process_data_t dataa) { int ret; //#if DEBUG // if(p->state == PROCESS_STATE_CALLED) { // printf("process: process '%s' called again with event %d\n", PROCESS_NAME_STRING(p), ev); // } //#endif /* DEBUG */ if((p->state & PROCESS_STATE_RUNNING) && p->thread != NULL) { //判断当前进程状态 // PRINTF("process: calling process '%s' with event %d\n", PROCESS_NAME_STRING(p), ev); process_current = p; p->state = PROCESS_STATE_CALLED; //被通知运行进程 ret = p->thread(&p->pt, ev, data); //PT_THREAD((* thread)(struct pt *, process_event_t, process_data_t))reentrant; if(ret == PT_EXITED || ret == PT_ENDED || ev == PROCESS_EVENT_EXIT) { exit_process(p, p); //退出进程 } else { p->state = PROCESS_STATE_RUNNING; } }
当我们做完这些移植步骤后,我们就完成了Contiki在51上的移植了。
5.3 测试
- 我们可以根据如下图来编写main函数,并且了解Contiki是如何工作的
int main(void) { clock_init(); //时钟初始化 process_init(); //进程初始化 /* start services */ process_start(&etimer_process, NULL); //启用系统进程 process_start(&led, NULL); //启动LED进程 while(1)process_run(); //运行 return 0; }
- user_led.h
/* LED线程 */ PROCESS(led,"led"); //定义led任务 PROCESS_THREAD(led, ev, dataa) //实现led任务的函数 { static struct etimer et; PROCESS_BEGIN(); //进程开启 etimer_set(&et,CLOCK_SECOND/2); //设置一个0.5秒的时钟 while(1) { PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et)); //等待0.5s到期 LED_ON(ON,OFF,OFF,ON,OFF,OFF); //亮灯 etimer_restart(&et); //复位0.5s定时器 PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et)); LED_ON(OFF,OFF,OFF,OFF,OFF,OFF); //灭灯 etimer_restart(&et); } PROCESS_END(); //进程结束 } /* LED线程 */
- 下载程序进开发板测试
Referance:
1.https://zhuanlan.zhihu.com/p/103601715
2.https://www.cnblogs.com/songdechiu/p/5793717.html