Contiki Programming Guide

From Contiki
Jump to: navigation, search

Back to Contiki Tutorials

Introduction

• Contiki is developed by a group of developers from industry and academia lead by Adam Dunkels from the Swedish Institute of Computer Science. The Contiki team currently consists of tens of developers from SICS, SAP AG, Cisco, Atmel, NewAE and TU Munich(Technical University of Munich).


• Contiki is multi-tasking operating system, especially designed for microcontrollers with small amount of memory(35KB of ROM and around 3K of RAM), which are used in networked embedded systems and wireless sensor networks.


• Contiki is written in the C programming language.


• It is freely available as open source under a BSD-style license.


• Contiki was the first OS which introduced IP communication in low-power sensor networks

You will learn

Understand the idea of threads and Events for Contiki


Code Review for Protothreads and Events

Define system processes

procinit(&etimer_process, &mac_process, &tcpip_process);
#define PROCINIT(...)
const struct process *prooinit[] = {__VA_ARGS__, NULL}
void procinit_init(void){
    int i;
    for(i = 0; procinit[i] != NULL; ++i){
        process_start((struct process *)procinit[i], NULL);
    }
}

int main(void){
    /*initialize hardware*/
    intit_lowlevel();
    clock_init();
    process_init();
    procinit_init();
    autostart_start(autostart_processes);
    printf("*****************BOOTING CONTIKI****************\n");
    while(1){
        process_run();
    }
    return 0;
}

The process thread in this part

PROCESS_THREAD(tcpip_process, ev, data);
PROCESS_THREAD(mac_process, ev, data);
PROCESS_THREAD(etimer_process, ev, data){
    struct etimer *t, *u;
    PROCESS_BEGIN();
    timerlist = NULL;
    while(1){
        PROCESS_YIELD();
    }
}

Start system processes

void
process_start(struct process *p, cnst char *arg){
    struct process *q;
    /*first make sure that we do not try to start a process that is already running*/
    for(q = process_list; q != p && q != NULL; q = q->next);
    /*If we found the process on the process list, we will bail out*/
    if(q == p){
         return;
    }
    /*Put on the process list*/
    p->next = process_list;
    process_list = p;
    p->state = PROCESS_STATE_RUNNING;
    PT_INIT(&p->pt);
    
    printf("process: starting '%s'\n", p->name);
    /*Post a synchronous initialization event to the process*/
    process_post_synch(p, PROCESS_EVENT_INIT, (process_data_t)arg);
}
/*----------------------------------------------------------------------------------*/
/* PROCESS_POST_SYNCH will execute code between PROCESS_BEGIN(); and PROCESS_YIELD();*/
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){
                     t = timerlist -> next;
                 }
            }
        }
    }
}

Start user processes

/*PROCINIT(&etimer_process, &mac_process, &tcpip_process);*/
/*follow by define system processes main function*/
AUTOSTART_PROCESSES(&temp_measure_cmd_sender, &serial_cmd_process){
     int i;
     for(i = 0; processes[i] != NULL; ++i){
          process_start(processes[i], NULL);
          PRINTF("autostart_start: starting process '%s'\n", processes[i]->name);
     }
}

The process thread in this part

PROCESS_THREAD(temp_measure_cmd_sender, ev, data);
PROCESS_THREAD(serial_cmd_process, ev, data){
     PROCESS_BEGIN();
     while(1){
         PROCESS_WAIT_EVEN();
         if(ev = SERIAL_CMD){
             process_post(&temp_measure_cmd_sender, TEMP_CMD, 0);
         } 
     }
     PROCESS_END();
}

Main scheduler loop

/*loop until poll_requested == 1 || nevents > 0*/
int process_run(void){
    /*process poll events*/
    if(pull_requested){
        do_poll();
    }
    /*Process one event from the queue*/
    do_event();
    
    return nevents + poll_requested;    
}

static void do_event(void){
    /*
    if there are any events in the queue, take the first one and walk through the
    list of processes to see if the event should be delivered to any of them. If 
    so, we call the event handler function for the process. We only process one 
    event at a time and call the poll handlers inbetween.
    */
    if(nevents >0){
       ...
       call_process(receiver, ev, data);  
    }
}

Method: POLL

ISR(AVR_OUTPUT_COMPARE_INT){
    ++count;
    if(etimer_pending()){
         etimer_request_poll();
    }
}
void etimer_request_poll(void){
    process_poll(&etimer_process);
}
void process_poll(struct process *p){
    if(p != NULL){
        if(p->state == PROCESS_STATE_RUNNING ||
           p->state == PROCESS_STATE_CALLED){
           p->needspoll = 1;
           poll_requested = 1;
        }   
    }
}
int process_run(void){
    /*process poll events*/
    if(poll_requested){
        do_poll();
    }
    /*Process one event from the queue*/
    do_event();
    return nevents + poll_requested;
}
static void do_poll(void){
    struct process *p;
    poll_requested = 0;
    /*Call the processes that needs to be polled*/
    for(p = process_list; p != NULL; p = p->next){
        if(p->needspoll){
            p->state = PROCESS_STATE_RUNNING;
            p->needspoll = 0;
            call_process(p, PROCESS_EVENT_POLL, NULL);
        }
    }
}

Method: POST

PROCESS_THREAD(etimer_process, ev, data){
    ...
    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)
             ...
        }
    }
}
int process_post(struct process *p, process_event_t ev, process_data_t data){
    ...
    anum = (fevent + nevents) % PROCESS_CONF_NUMEVENTS;
    events[snum].ev = ev;
    events[snum].data = data;
    events[snum].p = p;
    ++ nevents;
    ...
}
int process_run(void){
    /*Process poll events*/
    if(poll_requested){
        do_poll();
    }
    /*Process one event from the queue*/
    do_event();
    return nevents + poll_requested;
}
static void do_event(void){
    ...
    /*
    if there are any events in the queue, take the first one and walk through the
    list of processes to see if the event should be delivered to any of them. If 
    so, we call the event handler function for the process. We only process one 
    event at a time and call the poll handlers inbetween.
    */
    if(nevents > 0){
        ...
        call_process(reveiver, ev, data);
    }
}

It is clear to see according to the figure 1


1.jpg figure 1


Code Style: Names in Contiki In Contiki, all names are prefixed with the module name (example: process_start(), rime_init(), clock_time(), memb_alloc(), list_add()...) Prefix makes it possible to mentally locate all function calls All code must abide by this: There are a few exceptions in the current code, but those are to be removed in the future


Code Style: What to avoid

-noCamelCase()
-No_Capital_letters()
-#define EXCEPT_FOR_MACROS()

Code Sytle: File names Contiki uses hyphenated-file-names rather than underscore_in_file_names

The Motivation – Safe Tiny OS

The Safe Tiny OS tool chain is shown in Figure 2. The most immediate benefit of Safe TinyOS was that during its development 4 severe bugs in TinyOS could be found out and were corrected.


2.png figure 2


Building A Safe Application

To build an application Safe, we have to provide the parameter ‘safe’ in the command line of ‘make’.

 cd $TOSROOT/apps/Blink 
 make micaz safe

Making Safety Optional

Implementation Overview

The source code of Contiki consists of nearly 1200 files, including both the OS core and the applications. The OS core itself consists of around 300 files. The work comprises of annotating each pointer access in all these files and recompiling with Deputy. Once the core is free of Safety errors, we can go for the Safety of applications. After this step, we set Deputy as the default compiler of Contiki. Programmers will have to adapt to the Annotations of Deputy, which are very simple and easy to learn.


The flow graph of Safe Contiki development is given in Figure 3


3.jpg figure 3


Modifying the Makefiles

The implementation was started by editing the makefiles associated with the ‘native’ processor. The Makefile.native mainly contains definitions for the C compiler used for the native processor. The file is located in contiki-2.x/cpu/native/ directory. The current contents of the 6th and 7th lines are

- CC = gcc
- LD = gcc

which indicates that for the native CPU, the compiler and the linker are the same, the gcc. We replaced this with

- CC = deputy
- LD = deputy

A New Argument To Make

As an illustration of how the SAFETY argument is used, the following commands will create the Safe version of the ‘hello-world’ program for the ‘native’ platform.

 cd /home/user/contiki-2.x/examples/hello-world/
 make TARGET=native SAFETY=yes

Safety is an optional feature; programs are made Unsafe, by default. The following command makes the program without Safety features, for the ‘native’ platform.

make TARGET=native

Conditional Making And The gcc -D Flag

In order to make ‘making’ conditional, depending on the value of the variable SAFETY, we further modified the Makefile.native file in the contiki-2.x/cpu/native/ directory. Makefile.native is shown below

ifeq ($(SAFETY),yes)
CC = deputy
LD = deputy
else
CC = gcc
LD = gcc
CFLAGS += -DNTS='' -DSAFE='' \
-DCOUNT\(x\)='' \
-DTC\(x\)=x -DBOUND\(x,y\)='' \
-DTRUSTED='' -DNTDROP\(x\)=x \
-DNTEXPAND\(x\)=x
endif


Reference

[1]The Safe TinyOS, 2008. http://docs.tinyos.net/index.php/Safe_TinyOS [2]The Contiki OS, http://www.sics.se/contiki/ [2]Contiki Programming, 2011. http://wenku.baidu.com/view/


Back to Contiki Tutorials