1.前言
contiki是一款小型开源,易于移植的多任务操作系统,专门为无线传感网设计,适合内存受限制的网络系统。国内的研究和应用还处于初级阶段,甚至还不知道这个contiki如何发音,也没有那么响亮的中文名称。如果还没有中文名称的话,就干脆叫它“康提基”(来自wifi百科)。
本文先详细描述clock部分,clock部分是contiki运行的基础,和任务调度、网络协议都有关系。从clock部分的分析也可获得移植contiki的一般方法。
平台说明
硬件平台:CC2530
软件平台:IAR EW8051
2.相关文件
contiki的组织结构应该是清晰的,但是由于开发者众多便出现了若干矛盾的地方。最严重的便是同名文件,出现最多的地方便是 contiki/cpu/中的某个CPU文件夹和contiki/platform/某个平台文件夹。其实两个文件夹中的文件放在任何一个文件夹中都可以,也可以实现相同的功能,关键是把功能看懂熟悉。
2.1 clock.h
clock.h位于contiki/core/sys。该文件为内核文件,定义了clock模块实现的接口和必要条件。最好保持不变。
2.2 clcok.c文件
有clock.h必有clock.c,该文件可以位于contiki/cpu/cc253x,也可以位于contiki/platform/cc2530dk。
2.3 contiki-conf.h
该文件中有非常多的定义,其中和clock模块有关的定义有两个
/* Time type. */
typedef unsigned short clock_time_t;
/* Defines tick counts for a second. */
#define CLOCK_CONF_SECOND 128
clock_time_t被定义为无符号16位整数,在IAR平台int和short同为16位长度。CLOCK_CONF_SECOND为clock经历一秒的嘀嗒总数。选择128是因为clcok模块的参考时钟源为32768Hz。
3.clock模块的实现
clock模块实现需要4个步骤。初始化函数clock_init(),获得时钟时间clock_time(),更新时钟中断函数,简单的软件延时函数clock_delay()。
3.1 clock_init()函数
在CC2530这个平台上,zigbee和contiki不约而同的选择睡眠定时器,睡眠定时器有一个计数寄存器和一个捕获寄存器组成,值得说明的是计数寄存器和捕获寄存器均由三个8位寄存器组成。在clcok_init()函数中初始化了CC2530的时钟,该部分对系统运行和rtimer模块都非常重要。
具体代码如下:
void
clock_init(void)
{
/* Make sure we know where we stand */
CLKCONCMD = CLKCONCMD_OSC32K | CLKCONCMD_OSC;
/* Stay with 32 KHz RC OSC, Chance System Clock to 32 MHz */
CLKCONCMD &= ~CLKCONCMD_OSC;
while(CLKCONSTA & CLKCONCMD_OSC);
/* Tickspeed 500 kHz for timers[1-4] */
CLKCONCMD |= CLKCONCMD_TICKSPD2 | CLKCONCMD_TICKSPD1;
while(CLKCONSTA != CLKCONCMD);
/*Initialize tick value*/
timer_value = ST0;
timer_value += ((unsigned long int) ST1) << 8;
timer_value += ((unsigned long int) ST2) << 16;
timer_value += TICK_VAL;
ST2 = (unsigned char) (timer_value >> 16);
ST1 = (unsigned char) (timer_value >> 8);
ST0 = (unsigned char) timer_value;
STIE = 1; /* IEN0.STIE interrupt enable */
}
几点说明
1.32K时钟源选择片内RC。
2.系统时钟源选择片外OSC,32MHz
3.定时器时钟选择500K,和之后的rtimer有关
4.系统运行时钟选择32M。
5.休眠定时器的累加值为256。和时钟源23768有关。
6.使能休眠定时器中断,勿忘其他函数中应启动全局中断。
CLKCONCMD寄存器配置可以查看下图。
3.2 clock_timer()函数
非常简单,返回一个counter即可。该counter应在睡眠定时器中不断增加。
CCIF clock_time_t
clock_time(void)
{
return count;
}
3.3睡眠定时器中断
睡眠定时器中断主要做三件事情
1.更新睡眠定时器捕获寄存器
2.更新etimer模块(以后会详细分析etimer和rtimer)
3.若经历一个整数秒,增累加second
4.最后清除中断标志位。
请查看代码:
#pragma vector=ST_VECTOR
__near_func __interrupt void clock_isr(void)
{
DISABLE_INTERRUPTS();
ENERGEST_ON(ENERGEST_TYPE_IRQ);
timer_value = ST0;
timer_value += ((unsigned long int) ST1) << 8;
timer_value += ((unsigned long int) ST2) << 16;
timer_value += TICK_VAL;
ST2 = (unsigned char) (timer_value >> 16);
ST1 = (unsigned char) (timer_value >> 8);
ST0 = (unsigned char) timer_value;
++count;
if(count % CLOCK_CONF_SECOND == 0) {
++seconds;
}
if(etimer_pending()
&& (etimer_next_expiration_time() - count - 1) > MAX_TICKS) {
etimer_request_poll();
}
STIF = 0; /* IRCON.STIF */
ENERGEST_OFF(ENERGEST_TYPE_IRQ);
ENABLE_INTERRUPTS();
}
3.4辅助部分clock_delay()和clock_seconds()
请看代码:
CCIF unsigned long
clock_seconds(void)
{
return seconds;
}
void
clock_delay(unsigned int len)
{
unsigned int i;
for(i = 0; i< len; i++) {
ASM(nop);
}
}
ASM为一个汇编宏定义,由于平台的关系,ASM宏定义其实什么事情也没有做。
4.范例
4.1范例1
使用clock_delay和clock_time函数。先记录下当前timer时间,假设为t1,调用clock_delay函数延时一定的时间,clock_delay本质为软件延时函数,clock_delay运行完之后再次记录timer时间,假设为t2。t2和t1之间的差值便可获得clock_time运行的大致时间(可精确到ms)。
实验代码(部分)
PROCESS_THREAD(clock_test_process, ev, data)
{
PROCESS_BEGIN();
printf("Clock delay test, (10,000 x i) cycles:\n");
i = 1;
while(i < 6) {
start_count = clock_time(); // 记录开始timer
clock_delay(10000 * i); // 软件延时
end_count = clock_time(); // 记录结束timer
diff = end_count - start_count; // 计算差值,单位为tick
printf("Delayed %u \n%u ticks =~ %u ms\n", 10000 * i, diff, diff * 8);
i++;
}
printf("Done!\n");
PROCESS_END();
}
实验结果
结果分析
clock_delay(10000)的时间约为8ms。由于clock的时钟源为片内RC晶振,频率为32768Hz。clock一秒钟嘀嗒的最大值为128,那么每嘀嗒一次的时间约为8ms。通过这个8ms的关系便可把嘀嗒差值换算为具体时间。从这个实验虽然简单但是非常重要。
4.2范例2
使用etimer模块间隔5s读取系统运行时间,分析间隔是否为5S。这里已经假定etimer模块调试成功了,指示验证一下clock_second。这样的验证过程让我想起了测试驱动开发TDD。
实验代码(部分)
PROCESS_THREAD(clock_test_process, ev, data)
{
PROCESS_BEGIN();
printf("Clock Seconds Test (5s):\n"); // 间隔为5S
i = 0;
while(i < 10) {
etimer_set(&et, 5 * CLOCK_SECOND); // etimer溢出时间为5s
PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et)); // 等待定时器溢出
sec = clock_seconds(); // 系统运行时间 单位s
printf("%lu Seconds\n", sec); // 打印
i++;
}
printf("Done!\n");
PROCESS_END();
}
实验结果
结果分析
从运行的可过看,每次打印的时间间隔的确为5S。验证通过。
5.总结
由于contiki是一款开源的操作系统,许多工程师或者科研工作者都可以提交代码,扩充contiki支持的CPU和Platform。正由于这样不严密的合作关系,使得我个人初次阅读contiki的代码总觉得非常的混乱,乱到几乎没有头绪,遥想中国第一批linux开发者也遇到这样头疼的问题。软件环境和开发环境是一个,还遇到硬件平台的困难。国内和欧美的环境毕竟有所差异,欧美开发者熟悉的CPU国人并不了解。在无线传感网领域还没有出现一款像S3C2440一样遍地开花的芯片,如果已经有的话那就算CC2530了。
从嵌入式的发展来看,国人从事的嵌入式行业从没有操作系统到接受uCOS,FreeRTOS甚至Linux,期间有一个漫长的过程,在这个过程中同仁逐渐熟悉的任务调度,信号量和消息邮箱等概念。无线传感网络也会有一个从陌生到熟悉的过程,其中诸多的概念会让人沮丧甚至崩溃。