Contiki OS and Sparrow

Contiki considers itself the “The Operating System for Connecting the Next Billion Devices - the Internet of Things.” It's an Internet-connected multi-tasking OS for low-end platforms, like Microcontrollers. Contiki is great for the amazing level of connected functionality it will squeeze out of a five dollar chip. It runs on a vast array of platforms and CPU's.

Installing Contiki

Toolchain

There are two approaches to the toolchain. One is to install all the necessary tools natively on your machine. The other is to get instant-contiki, which is a VM image that works on Windows, Mac or Linux.

Download Contiki

You need to download a special fork of Contiki to try these changes out. Open a Terminal, and then:

$ cd ~ 
$ git clone https://github.com/narcisaam/contiki-sparrow.git -b sparrow 
$ cd contiki-sparrow/platform/sparrow

If Contiki has a problem with the AVR tools, you will need to follow these steps. It ships with an old version of the tools which does not support the Atmega128rfa1 found on the Sparrow, and then sets those as the default AVR tools even though the tools that ship with this version of Ubuntu are newer.

Open a Terminal, and type these:

$ sed -i '/avr/d' /home/user/.profile 
$ sudo aptitude update 
$ sudo apt-get install avr-libc gcc-avr binutils-avr avrdude git libc6-i386

Then log out of the machine and back in again (or restart it).

Setting Up

Default avrdude port

Connect the Sparrow board to your computer through USB. Once this is done, you'll likely see a new ttyUSB port appear in /dev. In the folder /contiki-sparrow/platform/sparrow edit Makefile.sparrow, and change the default avrdude port to match your local settings. This only needs to be done once ever. From now on in this tutorial we'll assume the board is connected to ttyUSB0, but you can modify according to your local settings.

 AVRDUDE_PORT ?= /dev/ttyUSB0 

MAC Address / Node Number

Each node needs a unique MAC address. The sparrow platform expects to find the MAC address as the first 8 bytes in EEPROM.

There is a special program just to program the MAC address. In the /contiki-sparrow/platform/sparrow folder, check up tools/set-eeprom/set-eeprom.c - it should look like this:

typedef unsigned int uint8_t __attribute__((__mode__(__QI__)));
uint8_t rf_channel[2] __attribute__((section(".eeprom"))) = {26, ~26};
uint8_t mac_address[8] __attribute__((section(".eeprom"))) = {0x02, 0x11, 0x22, 0xff, 0xfe, 0x33, 0x44, NODE};

It uses the same first 7 bytes, and lets you choose a different number for the last. From this point forward, we will refer to that last digit of the MAC address as the “Node number”. To assign a node number to your node:

$ cd tools/set-eeprom 
$ make NODE=3 AVRDUDE_PORT=/dev/ttyUSB0 

Hello World

Go back in your terminal to /contiki-sparrow/platform/sparrow and type…

$cd tests/hello-world 
$make upload 
$make login 

Working correctly, you should see this:

*******Booting Contiki 2.5*******
MAC address 2:11:22:ff:fe:33:44:3
nullmac sicslowmac, channel 26, check rate 65535 Hz
Routing Enabled
Autostart other processes

IP addresses [4 max]
fdfd::3
fe80::11:22ff:fe33:4403
Hello, world

Ping

Now that we know the node itself is working and printing OK, we want to make sure two nodes can talk. The best way to do this is with a ping.

This requires two nodes. If you already have hello-world on one node, the easiest thing to do is to put ping-ipv6 on a second node, and ping the other. So for this example, say we already have hello-world working on node 3 on /dev/ttyUSB0.

We'll connect a second node to the computer, and presume it's connected to /dev/ttyUSB1 and it's already been configured with node address 1. For this node, we'll need to compile and upload the ping-ipv6 app and specify the node we'll want to ping.

$ cd tests/ping-ipv6 
$ make upload AVRDUDE_PORT=/dev/ttyUSB1 NODE=3 
$ make login AVRDUDE_PORT=/dev/ttyUSB1 

Note that you have to pass the node number of the node you want to ping (NODE=3 in this example). Working correctly, you will see this:

connecting to /dev/ttyUSB1 (57600) [OK] Power-on reset. External reset! 
*******Booting Contiki 2.5******* 
MAC address 2:11:22:ff:fe:33:44:1 nullmac sicslowmac, channel 26 
In Process PING6 Wait for DAD IP addresses [4] 
fdfd::1 fe80::11:22ff:fe33:4401 
Sending Echo Request to fdfd:0000:0000:0000:0000:0000:0000:0003 
from fdfd:0000:0000:0000:0000:0000:0000:0001 
Echo reply received. Sending Echo Request to fdfd:0000:0000:0000:0000:0000:0000:0003 
from fdfd:0000:0000:0000:0000:0000:0000:0001 
Echo reply received. Sending Echo Request to fdfd:0000:0000:0000:0000:0000:0000:0003 
from fdfd:0000:0000:0000:0000:0000:0000:0001 
Echo reply received. 
Sending Echo Request to fdfd:0000:0000:0000:0000:0000:0000:0003 
from fdfd:0000:0000:0000:0000:0000:0000:0001 
Echo reply received. Sending Echo Request to fdfd:0000:0000:0000:0000:0000:0000:0003 
from fdfd:0000:0000:0000:0000:0000:0000:0001 
Echo reply received. END PING6 

UDP

Finally, we will want to be sure that two nodes can exchange real application data between them. We can do so with the UDP client/server test. Again, this requires two nodes. One node contains the server program, the other contains the client. When making the client, we need to tell it where to find the server by specifying NODE= the Node# of the server.

$ cd tests/udp-ipv6 
$ make NODE=3 -j10 
$ make udp-client.sparrow.u AVRDUDE_PORT=/dev/ttyUSB1 NODE=3 
$ make udp-server.sparrow.u login AVRDUDE_PORT=/dev/ttyUSB0 

Working correctly, you'll see something like the following. The exact numbers will depend on the timing of when you do the 'make login'.

 connecting to /dev/ttyUSB0 (57600) [OK] 
Power-on reset. External reset! 
*******Booting Contiki 2.5******* 
MAC address 2:11:22:ff:fe:33:44:3 nullmac sicslowmac, channel 26 
IP addresses [4] fdfd::3 fe80::11:22ff:fe33:4403 
UDP server started Server IPv6 addresses: fdfd::3 fe80::11:22ff:fe33:4403 
+READY Server received: 'Hello 2 from the client' from fdfd::1 
Responding with message: Hello from the server! (1) 
Server received: 'Hello 3 from the client' from fdfd::1 
Responding with message: Hello from the server! (2) 
Server received: 'Hello 4 from the client' from fdfd::1 
Responding with message: Hello from the server! (3) 
Server received: 'Hello 5 from the client' from fdfd::1 
Responding with message: Hello from the server! (4) 
Server received: 'Hello 6 from the client' from fdfd::1 
Responding with message: Hello from the server! (5) 
Server received: 'Hello 7 from the client' from fdfd::1 +OK PASS 

Timers & Events in Contiki

This tutorial will show how to make use of timers in Contiki. It will also give a basic into to events.

Processes

All Contiki programs are processes. A process is a piece of code that is executed regularly by the Contiki system. Processes in Contiki are typically started when the system boots, or when a module that contains a process is loaded into the system. Processes run when something happens, such as a timer firing or an external event occurring.

Code in Contiki can run in one of two execution contexts: cooperative or preemptive. Code running in the cooperative execution context is run sequentially with respect to other code in the cooperative context. Cooperative code must run to completion before other cooperatively scheduled code can run. Preemptive code may stop the cooperative code at any time. When preemptive code stops the cooperative code, the cooperative code will not be resumed until the preemptive code has completed. The concept of Contiki's two scheduling contexts is illustrated above.

Processes always run in the cooperative context. The preemptive context is used by interrupt handlers in device drivers and by real-time tasks that have been scheduled for a specific deadline.

An example process that receives events and prints out their number:

 #include "contiki.h"
 
 PROCESS(example_process, "Example process");
 AUTOSTART_PROCESSES(&example_process);
 
 PROCESS_THREAD(example_process, ev, data)
 {
   PROCESS_BEGIN();
 
   while(1) {
     PROCESS_WAIT_EVENT();
     printf("Got event number %d\n", ev);
   }
 
   PROCESS_END();
 }

The complete reference on Contiki processes can be found here.

Contiki provides three kinds of timers:

  • Simple timer: The timer library provides functions for setting, resetting and restarting timers, and for checking if a timer has expired. An application must “manually” check if its timers have expired, meaning that this library does not post an event when the timer expires, so we must implement a routine that checks the timer for expiration.
  • Callback timer: The callback timer library provides the same functions as above, but when the timer expires can callback a C function.
  • Event timer: The same as above, with the difference that instead of calling a function, when the timer expires it post an event signalling the timer expiration.

More information on timer can be found on the Contiki's wiki page on timers.

The Event Timer

The etimer (Event timer) will post an event when the timer is expiring. Since it posts an event you will need to have a process around it to handle the event.

PROCESS_THREAD(etimer_process, ev, data)
{
  static struct etimer et;
  PROCESS_BEGIN();
  /* set timer to expire after 5 seconds */
  etimer_set(&et, CLOCK_SECOND * 5);
  while(1) {
    PROCESS_WAIT_EVENT(); /* Same thing as PROCESS_YIELD */
    if(etimer_expired(&et)) {
      /* Do the work here and restart timer to get it periodic !!! */
      printf("etimer expired.\n"); 
      etimer_restart(&et);
    }
  }
  PROCESS_END();
}

In the above code the process is defined by PROCESS_THREAD which takes a name, an event variable and an event data variable as the arguments. When an etimer expires the event variable will be set to PROCESS_EVENT_TIMER and the event data variable will be set to point to the specific timer. Using that information the above example could also look like:

PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER && data == &et);
/* Do the work here and restart timer to get it periodic! */
printf("etimer expired.\n"); 
etimer_restart(&et);

But when waiting for this event in this way – all other events will be ignored so the first approach is more flexible as this will enable handling multiple types of events more easily.

The Shell

The shell is a very powerful feature of Contiki. Within this OS lies a fully-featured command shell. The shell in the sparrow platform is a subset of the full Contiki shell, including only the shell modules which have been proven to work on this platform.

Building

$ cd tests/shell 
$ make upload 
$ make login

Running

If successful, you'll see this:

connecting to /dev/ttyUSB1 (57600) [OK] 
Power-on reset. External reset! 
*******Booting Contiki 2.5******* 
MAC address 2:11:22:ff:fe:33:44:1 nullmac sicslowmac, channel 26 
Contiki command shell 
Type '?' and return for help 68.1: 
Contiki> IP addresses [4] fdfd::1 fe80::11:22ff:fe33:4401 

help

Now, try some of the commands, like 'help':

Available commands: 
?: shows this help 
binprint: print binary data in decimal format 
blink [num]: blink LEDs ([num] times) 
echo <text>: print <text> 
exit: exit shell 
hd: print binary data in hexadecimal format 
help: shows this help 
kill <command>: stop a specific command 
killall: stop all running commands 
netstat: show UDP and TCP connections 
null: discard input 
ping <host>: ping an IP host 
ps: list all running processes 
quit: exit shell 
randwait <maxtime> <command>: wait for a random time before running a command 
repeat <num> <time> <command>: run a command every <time> seconds 
size: print the size of the input 
time [seconds]: output time in binary format, or set time in seconds since 1970 
timestamp: prepend a timestamp to data 
68.1: Contiki> 

Try “blink 100 &” to start blinking the LED's for 100 times. Notice the '&' at the end of that! You should now see Sparrow's LED flash on and off repeatedly.

Then type “kill blink” when you're done admiring your blinking lights.

ping

We can also ping from within the shell!

68.1: Contiki> ping fdfd::3 SEND 13 bytes ping fdfd::3 
Sending Echo Request to fdfd:0000:0000:0000:0000:0000:0000:0003 
from fdfd:0000:0000:0000:0000:0000:0000:0001 
Other ICMP6 message received. 
Echo reply received. 

Using SLIP

What is SLIP?

SLIP is the “mostly obsolete” wikipedia:Serial Line Internet Protocol. “On personal computers, SLIP has been largely replaced by the Point-to-Point Protocol (PPP), which is better engineered, has more features and does not require its IP address configuration to be set before it is established. On microcontrollers, however, SLIP is still the preferred way of encapsulating IP packets due to its very small overhead.”

Contiki uses SLIP to bridge the wireless IPv6 network onto a PC via a USB connection. So with your Sparrow plugged into your PC, and the right software running on each, traffic from the wireless IP network can reach your site-wide Ethernet network and potentially beyond.

On Sparrow, there is only one UART exposed. This means we have to choose between reading debug messages and connecting to our PC via SLIP. This change is exposed by adding “WITH_SLIP=1” to the makefile or command line for any particular project. Projects made “WITH_SLIP” will expect to talk to a slip tunnel on the PC side.

Speaking of the PC side… In order for SLIP to work, something on the host PC has to be listening. Using Instant Contiki, the 'tunslip6' will do this. Running it in Linux creates a 'tun0' interface which gives the connected Sparrow an address of aaaa::1 on your local network.

Building

First, built the tunslip6 tool. This works without modification on Instant Contiki.

 
$ cd tools 
$ make tunslip6 

Now make and upload the border router itself. Be sure to include “WITH_SLIP=1” to turn on slip for this node, and “WITH_WEBSERVER=0” to exclude a web server from this node.

 
$ cd examples/ipv6/rpl-border-router 
$ make TARGET=sparrow savetarget 
$ make WITH_SLIP=1 WITH_WEBSERVER=0 -j10 
$ make upload AVRDUDE_PORT=/dev/ttyUSB0 

Connecting

Ok, now it's built. Let's bring up the slip interface on Linux. Note that the baud rate here has to match the baud rate in the uart setup on the board, which is currently 38400. That's a little slow, but I'll work on bringing it up in future revisions. Also note the “v6” switch. That turns on maximum debugging output so we can follow along.

$ sudo ../../../tools/tunslip6 aaaa::1/64 -s /dev/ttyUSB0 -B 38400 -v6 
********SLIP started on ``/dev/ttyUSB0 opened tun device ``/dev/tun0 
ifconfig tun0 inet `hostname` up 
ifconfig tun0 add aaaa::1/64 
ifconfig tun0 
tun0 Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00             
     inet addr:127.0.1.1  P-t-P:127.0.1.1  Mask:255.255.255.255           
     inet6 addr: aaaa::1/64 Scope:Global           
     UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  Metric:1           
     RX packets:0 errors:0 dropped:0 overruns:0 frame:0           
     TX packets:0 errors:0 dropped:0 overruns:0 carrier:0           
     collisions:0 txqueuelen:500    RX bytes:0 (0.0 B)     TX bytes:0 (0.0 B) 
tun0 Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00             
     inet addr:127.0.0.1  P-t-P:127.0.0.1  Mask:255.255.255.255           
     inet6 addr: aaaa::1/64 Scope:Global           
     UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  Metric:1           
     RX packets:0 errors:0 dropped:0 overruns:0 frame:0           
     TX packets:0 errors:0 dropped:0 overruns:0 carrier:0           
     collisions:0 txqueuelen:500            
     RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B) 
IP addresses [4 max] fdfd::3 fe80::11:22ff:fe33:4403 RPL-Border router started 
*** Address:aaaa::1 => aaaa:0000:0000:0000 SIN: 10 Got configuration message of type P 
Setting prefix aaaa:: created a new RPL dag Server IPv6 addresses:  aaaa::11:22ff:fe33:4403  
fdfd::3  fe80::11:22ff:fe33:4403 

It's helpful that tunslip6 is putting through the debug messages from the Sparrow. So we can see the boot-up process complete successfully.

Ping

We should now be able to ping the border router from the host PC. First, we can ping its auto-configured aaaa::/64 address

$ ping6 aaaa::11:22ff:fe33:4403 
PING aaaa::11:22ff:fe33:4403(aaaa::11:22ff:fe33:4403) 56 data bytes 
64 bytes from aaaa::11:22ff:fe33:4403: icmp_seq=1 ttl=64 time=66.1 ms 
64 bytes from aaaa::11:22ff:fe33:4403: icmp_seq=2 ttl=64 time=68.6 ms 
64 bytes from aaaa::11:22ff:fe33:4403: icmp_seq=3 ttl=64 time=66.2 ms

Nodes Beyond the Border

We want to reach more nodes than just the one connected. So let's add a route on the host PC. This “route add” command tells Ubuntu that whenever it wants to reach a node whose IP starts with fdfd::/64, it can send that through the tun0 interface.

 
$ sudo route -A inet6 add fdfd::/64 dev tun0 
$ netstat -r6 Kernel IPv6 routing table 
Destination                    Next Hop                   Flag Met Ref Use If 
aaaa::/64                      ::                         U    256 0     0 tun0 
fdfd::/64                      ::                         U    1   0     0 tun0 

Now that we have the route set up, we can put another node on the network, and ping that.

Put anything that speaks RPL on fdfd::1, for example rpl-collect/sender

$ cd examples/ipv6/rpl-collect 
$ make TARGET=sparrow savetarget 
$ make udp-sender.sparrow.u AVRDUDE_PORT=/dev/ttyUSB1 -j10 

Now see that we can ping it ok:

$ ping6 fdfd::1 PING fdfd::1(fdfd::1) 56 data bytes 
64 bytes from fdfd::1: icmp_seq=1 ttl=64 time=67.8 ms 
64 bytes from fdfd::1: icmp_seq=2 ttl=64 time=66.3 ms

Using the Webserver

From a PC on our network, we want to view a web page served up by any node on our wireless IP network. This will allow us to look at sensor values or other data stored there.

Building

We'll put the rpl-border-router (with no webserver) on the node connected to the PC, and talk SLIP over USB between it and the PC. The other node will run webserver-ipv6 with 'webserver-nano'.

$ cd examples/ipv6/rpl-border-router 
$ make TARGET=sparrow savetarget 
$ make upload WITH_WEBSERVER=0 WITH_SLIP=1 AVRDUDE_PORT=/dev/ttyUSB0 -j10 
$ cd examples/webserver-ipv6 
$ make TARGET=sparrow savetarget 
$ make WITH_WEBSERVER=webserver-nano -j10 
$ make upload login WITH_WEBSERVER=webserver-nano AVRDUDE_PORT=/dev/ttyUSB1 

Running

This has to be done in another window, because “make login” above took over that window. Here we will bring up the tunnel, and try everything out, one thing at a time. Ping the router, ping the webserver, do the 'get'.

$ sudo ../../../tools/tunslip6 aaaa::1/64 -s /dev/ttyUSB0 -B 38400 -v6 
$ sudo route -A inet6 add fdfd::/64 dev tun0 
$ ping6 fdfd::3 $ ping6 fdfd::1 
$ curl -g "http://[aaaa::11:22ff:fe33:4401]/" 

Here's what success looks like!

$ curl -g "http://[aaaa::11:22ff:fe33:4401]/" <html> <head> <title>Contiki-nano</title> </head> <body> <pre> <a href="/">Front page</a>| <a href="status.shtml">Status</a>| <a href="tcp.shtml">Network connections</a>| <a href="processes.shtml">System processes</a>| <a href="files.shtml">File statistics</a>| <a href="/ttt/ttt.shtml">TicTacToe</a> </pre> Welcome to the <a href="http://www.sics.se/contiki/">Contiki</a>  nano web server!<p align="right"> <br><br> <i>This page has been sent 2 times</i> </body> </html> 

Plus here's the Wireshark summary of this conversation:

 No.     Time        Source                Destination           Protocol Info       
1 0.000000    aaaa::1               aaaa::11:22ff:fe33:4401 TCP      36067 > http [SYN] Seq=0 Win=5760 Len=0 MSS=1440 TSV=87387016 TSER=0 WS=5   
2 0.242296    aaaa::11:22ff:fe33:4401 aaaa::1               TCP      http > 36067 [SYN, ACK] Seq=0 Ack=1 Win=1220 Len=0 MSS=1220       
3 0.242351    aaaa::1               aaaa::11:22ff:fe33:4401 TCP      36067 > http [ACK] Seq=1 Ack=1 Win=5760 Len=0       
4 0.242589    aaaa::1               aaaa::11:22ff:fe33:4401 HTTP     GET / HTTP/1.1        
5 0.630341    aaaa::11:22ff:fe33:4401 aaaa::1               TCP      [TCP segment of a reassembled PDU]       
6 0.630388    aaaa::1               aaaa::11:22ff:fe33:4401 TCP      36067 > http [ACK] Seq=165 Ack=86 Win=5760 Len=0       
7 0.898326    aaaa::11:22ff:fe33:4401 aaaa::1               TCP      [TCP segment of a reassembled PDU]       
8 0.898360    aaaa::1               aaaa::11:22ff:fe33:4401 TCP      36067 > http [ACK] Seq=165 Ack=113 Win=5760 Len=0       
9 1.258323    aaaa::11:22ff:fe33:4401 aaaa::1               TCP      [TCP segment of a reassembled PDU]      
10 1.258369    aaaa::1               aaaa::11:22ff:fe33:4401 TCP      36067 > http [ACK] Seq=165 Ack=408 Win=6432 Len=0      
11 1.550390    aaaa::11:22ff:fe33:4401 aaaa::1               TCP      [TCP segment of a reassembled PDU]      
12 1.550426    aaaa::1               aaaa::11:22ff:fe33:4401 TCP      36067 > http [ACK] Seq=165 Ack=490 Win=6432 Len=0      
13 1.842334    aaaa::11:22ff:fe33:4401 aaaa::1               TCP      [TCP segment of a reassembled PDU]      
14 1.842364    aaaa::1               aaaa::11:22ff:fe33:4401 TCP      36067 > http [ACK] Seq=165 Ack=567 Win=6432 Len=0      
15 2.098333    aaaa::11:22ff:fe33:4401 aaaa::1               TCP      http > 36067 [FIN, ACK] Seq=567 Ack=165 Win=1220 Len=0      
16 2.098508    aaaa::1               aaaa::11:22ff:fe33:4401 TCP      36067 > http [FIN, ACK] Seq=165 Ack=568 Win=6432 Len=0      
17 2.334295    aaaa::11:22ff:fe33:4401 aaaa::1               TCP      http > 36067 [ACK] Seq=568 Ack=166 Win=1220 Len=0