Skip to content

Latest commit

 

History

History
1760 lines (1297 loc) · 75.2 KB

3.asc

File metadata and controls

1760 lines (1297 loc) · 75.2 KB

Introduction to Contiki

Contiki is an open source operating system for the Internet of Things, it connects tiny low-cost, low-power microcontrollers to the Internet.

Contiki provides powerful low-consumption Internet communication, it supports fully standard IPv6 and IPv4, along with the recent low-power wireless standards: 6lowpan, RPL, CoAP. With Contiki’s ContikiMAC and sleepy routers, even wireless routers can be battery-operated.

With Contiki, development is easy and fast: Contiki applications are written in standard C, with the Cooja simulator Contiki networks can be emulated before burned into hardware, and Instant Contiki provides an entire development environment in a single download.

Visit the Contiki OS site for more information.

The remainder of the chapter intends to be a thoughtful introduction to Contiki 3.0, its core components and features. The following references are the must-go places to search for detailed information:

Install Contiki

There are several ways to install Contiki, from scratch by installing from sources or using virtual environments, depending on the flavour and time availability.

To work with Contiki you will need three items:

  • The Contiki source code.

  • A target platform (virtual platform or a real hardware one).

  • A toolchain to compile the source code for such target platform.

The remainder of the book assumes Contiki will run in an Unix environment, as the virtualized environments run on Ubuntu.

Fresh Contiki Installation

The following instructions will guide you to install Contiki on your machine. These instructions were tested in Ubuntu-like devices (version 12.04 and onwards). If you are looking for a ready to use setup, skip this section and download one of the Virtual Machines in the next section.

This will install support for the ARM Cortex-M3 and MSP430 platforms, as well as support for Cooja, the Contiki’s emulator to be discussed in the next sections.

The "IoT in five days" Virtual Machine has a comprehensive lists of additional tools and libraries installed, check the next section for a complete list.

Install the toolchain and required libraries

The following are the minimal recomended libraries to run Contiki.

Ubuntu (Linux) installation

To install the toolchain and required dependencies, run in a terminal the following:

sudo apt-get update
sudo apt-get install gcc-arm-none-eabi gdb-arm-none-eabi
sudo apt-get -y install build-essential automake gettext
sudo apt-get -y install gcc-arm-none-eabi curl graphviz unzip wget
sudo apt-get -y install gcc gcc-msp430
sudo apt-get -y install openjdk-7-jdk openjdk-7-jre ant
OSX (MAC) installation (RE-Mote only)
# Install Homebrew - http://brew.sh/
brew tap PX4/homebrew-px4
brew update

# Install GCC Arm Toolchain
brew install gcc-arm-none-eabi-49
Windows installation (RE-Mote only)

Download the GCC ARM toolchain (Windows installer) from:

Tested with gcc-arm-none-eabi-5_3-2016q1-20160330-win32.exe.

Execute and select the add path to environment variable option.

Next download MINGW, install and make sure the following packages are selected: mingw32-base, mingw32-gcc-g++, msys-base.

Under "All Packages" select the msys-mintty package for terminal support. This will install MINTTY in the default location C:\MinGW\msys\1.0\bin\mintty.exe. Create a shortcut and add this to the Shortcut target command:

C:\MinGW\msys\1.0\bin\mintty.exe /bin/bash -l

Add the following paths to your $path environment variable:

C:\MinGW\bin;C:\MinGW\msys\1.0\bin

Run MINTTY and execute the following commands:

mingw-get update
mingw-get install msys-wget
mingw-get install msys-zlib
mingw-get install msys-unzip
Get Contiki on your machine

Contiki source code is actively supported by contributors from universities, research centers and developers from all over the world.

The source code is hosted at Contiki GitHub repository:

The latest Contiki release is 3.0, the release tag is available at:

Nevertheless you should use the latest commit available, as Contiki releases are produced on a yearly base. Many bug fixes, new features and improved support is normally present on the latest master branch.

To grab the source code open a terminal and execute the following:

sudo apt-get -y install git
git clone --recursive https://github.com/contiki-os/contiki.git
Note

At the moment of this release, the current Contiki commit corresponds to the a8989f9f1c9794233a712ef4f689d1ba35a9a405 HASH. You can use the following GIT command to place yourself on this commit:

git checkout HASH

Replace HASH with the actual value.

As Contiki ensures the platform and application support by using a strict code revision procedure and regression tests, this is a safe point if you encounter any problem. Be sure to update your Contiki local repository if using Instant Contiki!

What is git?

Git is a free and open source distributed version control system, designed for speed and efficiency.

The main difference with other change control tools is the possibility to work locally since your local copy is a repository, and you can commit to it and get all benefits of source control.

There are some great tutorials online to learn more about git:

GitHub is a GIT repository web-based hosting service, which offers all of the distributed revision control and source code management (SCM) functionality of Git as well as adding its own features. GitHub provides a web-based graphical interface and desktop as well as mobile integration. It also provides access control and several collaboration features such as wikis, task management, bug tracking and feature requests for every project.

The advantage of using GIT and hosting the code at github is that of allowing people to fork the code, further develop it, and then contribute back to share their improvements.

Additionally there is a branch available with the IoT in five days content in a workshop-like format. The branch is available at:

It is recomended to add this branch as a remote repository of the already cloned Contiki repository, so you can keep track of the development done in the master branch.

cd /home/user/contiki
git remote add iot-workshop https://github.com/alignan/contiki
git fetch iot-workshop
git checkout iot-workshop

The above commands will add the https://github.com/alignan/contiki repository to the list of remote repositories, making the iot-workshop branch available in your machine as a working copy. Now you can track any changes and updates.

This branch has ready to use examples and applications to guide you through Contiki and building real IoT applications. In the next sections this content will be further discussed.

Using a virtualized environment

Even if installing Contiki should be straightforward, there are already built Virtual Machines available for you to download and use out of the box.

The available Virtual Machines images are available for VMWare.

Download VMWare player for Windowws and Linux to run Contiki’s virtual machine, it is free and widely used.

In OSX you can download VMWare Fusion

Instant Contiki Virtual Machine

Instant Contiki is an entire Contiki development environment in a single download. It is an Ubuntu Linux virtual machine and has Contiki OS and all the development tools, compilers, and simulators required already pre-installed.

Grab Instant Contiki from the Contiki website:

The latest Instant Contiki release is 3.0, following the Contiki 3.0 source code release.

Using VMWare just open the Instant_Contiki_Ubuntu_12.04_32-bit.vmx file, if prompted about the VM source just choose I copied it then wait for the virtual Ubuntu Linux boot up.

Log into Instant Contiki. The password and user name is user. Don’t upgrade right now.

Remember to update the Contiki repository and get the latest upgrades:

cd /home/user/contiki
git fetch origin
git pull origin master

Notice the Instant Contiki does not have the IoT workshop branch with the examples of this book, read the previous section on how to clone.

Official "IoT in five days" Virtual Machine

A Virtual Machine with the content of the book is provided as a free download from the following location:

In a nutshell it packs everything the Instant Contiki has, but it has been built using Ubuntu Server 16.04 LTS (Xenial) instead. Additionally several packages has been installed to make development easier, including a fully-configured Eclipse IDE workspace.

image003
Figure 1. IoT in five Days Virtual Machine

The "IoT in five days" book, its source and ready to use Contiki examples are included.

Log into the Virtual Machine. The password and user name is user.

The "IoT in five days" Virtual Machine has already the iot-workshop branch cloned and available, with the suggested book examples and a ready-to-use Contiki configuration.

Test Contiki installation

Let us first check the toolchain installation. The MSP430 toolchain can be tested with:

msp430-gcc --version
msp430-gcc (GCC) 4.7.0 20120322 (mspgcc dev 20120716)
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

And for the ARM Cortex-M3 toolchain:

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GNU Tools for ARM Embedded Processors) 4.9.3 20150529 (release) [ARM/embedded-4_9-branch revision 224288]
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Contiki structure

Contiki has the following file structure:

Folder Description Zolertia files

examples

Ready to build examples

examples/zolertia, examples/cc2538-common

app

Contiki applications

-

cpu

Specific MCU files

msp430, cc2538

dev

External chip and devices

cc2420, cc1200

platform

Specific files and platform drivers

z1, zoul

core

Contiki core files and libraries

-

tools

Tools for flashing, debuging, simulating, etc.

zolertia, sky

doc

Self-generated doxygen documentation

-

regression-tests

nightly regression tests

-

If you cloned the iot-workshop branch (or if you are using the "IoT in five days" Virtual Machine), you will find also the following folder:

contiki/examples/zolertia/tutorial

The content of the tutorial is:

  • 01-basics: Contiki basics (timers, GPIO, LEDs).

  • 02-ipv6: Wireless networking, RF basic, IPv6/6LoWPAN implementation (UDP/TCP) and the Border Router

  • 03-coap: Example on how to implement a CoAP server

  • 04-mqtt: Example on how to implement a MQTT client

The examples work out of the box for both Zolertia Z1 and RE-Mote platforms to be shown next.

The examples are written to be followed in order, so be sure to start from the very first and make your way to the last in an ordered fashion.

In the following sections we will cover the examples and specific platform files for the Zolertia platforms.

Run Contiki on real hardware

For the remainder of the book we will use the Zolertia Z1 and RE-Mote hardware development platforms. Other platforms can be used as well, but are out of the scope of this book.

Contiki drivers and libraries are (normally) platform independent, most examples can be run in both Z1 and RE-Mote platforms by taking into consideration specific platform settings.

There are platform-specific examples for the Z1 platform at examples/zolertia/z1, and at examples/zolertia/zoul for the RE-Mote. Additionaly CC2538 specific examples can be found at examples/cc2538-common. This folder has CC2538 ARM Cortex-M3 related examples and applications.

More information about both platforms and updates guides can be found at:

Zolertia Zoul module and the RE-Mote development platform

The Zoul is a module based in the CC2538 ARM Cortex-M3 system on chip (SoC), with an on-board 2.4 GHz IEEE 802.15.4 RF interface, running at up to 32 MHz with 512 kB of programmable flash and 32 kB of RAM, bundled with a CC1200 868/915 MHz RF transceiver to allow dual band operation.

The Zoul allows fast reusability on designs and quick scaling from prototyping to production.

image001
Figure 2. Zolertia Zoul module and the RE-Mote platform

The RE-Mote (revision A) has a Zoul on board and also packs:

  • ISM 2.4-GHz IEEE 802.15.4 & Zigbee compliant radio.

  • ISM 863-950-MHz ISM/SRD band radio.

  • AES-128/256, SHA2 Hardware Encryption Engine.

  • ECC-128/256, RSA Hardware Acceleration Engine for Secure Key Exchange.

  • Consumption down to 150 nA using the shutdown mode.

  • Programming over BSL without requiring to press any button to enter bootloader mode.

  • Built-in battery charger (500 mA), facilitating Energy Harvesting and direct connection to Solar Panels and to standards LiPo batteries.

  • Wide range DC Power input: 2-16 V.

  • Small form-factor.

  • MicroSD over SPI.

  • On board RTC (programmable real time clock) and external watchdog timer (WDT).

  • Programmable RF switch to connect an external antenna either to the 2.4 GHz or to the Sub 1 GHz RF interface through the RP-SMA connector.

The RE-Mote has been developed jointly with universities and industrial partners in the frame of the European research project RERUM(RERUM: REliable, Resilient and secUre IoT for sMart city applications).

Zolertia Z1 mote

The Z1 mote features a second generation MSP430F2617 low power 16-bit RISC CPU @ 16 MHz MCU, 8 kB RAM and a 92 kB Flash memory. It includes the well known CC2420 transceiver, IEEE 802.15.4 compliant, which operates at 2.4 GHz with an effective data rate of 250 kbps.

image002
Figure 3. Zolertia Z1 mote

The Zolertia Z1 mote can run TinyOS, Contiki OS, OpenWSN and RIOT, and has been used actively for over 5 years in universities, research and development centers and in commercial products in more than 43 countries, being featured in more than 50 scientific publications.

The Z1 mote is fully emulated in both MSPSIM and Cooja.

What are the differences between the RE-Mote and the Z1 platforms?

In a nutshell: power and coolness.

The RE-Mote has 4 times more RAM than the Z1 mote, 5 times more flash memory, twice the frequency of operation and 120 times less power consumption in its lowest power mode (shutdown mode).

Another major difference is that (at the time of writing this book) the Z1 mote is supported by Cooja, the Contiki emulator, while the RE-Mote is not. However efforts are on going to provide emulation framework support in the EMUL8 project.

To check in depth the differences between the RE-Mote and the Z1 mote, and also obtain guidelines to port applications developed for the Z1 to the RE-Mote, visit the "Migrate from Z1 to RE-Mote" wiki page.

Start with Contiki!

Let’s compile our first Contiki example! Open a terminal and write:

cd examples/zolertia/tutorial/01-basics
make TARGET=zoul savetarget

This will tell Contiki to compile the hello world example for the RE-Mote platform from now on. Alternatively, to use the Z1 mote instead, just run:

make TARGET=z1 savetarget

You need to do this only once per application. Not let’s compile the application:

make 01-hello-world

if everything works OK you should see something like for the Z1 mote:

CC        symbols.c
AR        contiki-z1.a
CC        01-hello-world.c
CC        ../../../../../platform/z1/./contiki-z1-main.c
LD        01-hello-world.z1
rm obj_z1/contiki-z1-main.o 01-hello-world.co

The 01-hello-world.z1 file should have been created and we are ready to flash the application to the device.

And likewise if we were to compile for the RE-Mote platform:

make TARGET=zoul 01-hello-world
  CC        01-hello-world.c
  LD        01-hello-world.elf
arm-none-eabi-objcopy -O binary --gap-fill 0xff 01-hello-world.elf 01-hello-world.bin
Tip

If a Makefile.target already exists and you don’t want to change the current defined TARGET, you can just compile your code specifying a TARGET. Running make TARGET=zoul for example will ignore the saved Makefile.target file and use the target zoul instead.

In the following sections and chapters the examples can be compiled for both the Z1 and RE-Mote platforms.

Note

The RE-Mote takes two arguments: TARGET and BOARD, while the Z1 mote only uses the first one with the z1 string as noted before. For the RE-Mote, TARGET is always zoul and BOARD is remote, but when compiling, if the BOARD flag is not explicitly defined, it will default to remote. This approach is because there are other Zoul based platforms which share the same code base and modules, but the RE-Mote has its own specific platform files and definitions.

Hello world explained

Let’s see the main components of the Hello World example. Browse the code with:

gedit 01-hello-world.c

Or your preferred text editor (Sublime text, Eclipse, etc).

When starting Contiki, you declare processes with a name. In each code you can have several processes. You declare the process like this:

PROCESS(hello_world_process, "Hello world process"); // (1)
AUTOSTART_PROCESSES(&hello_world_process); // (2)
  1. hello_world_process is the name of the process and "Hello world process" is the readable name of the process when you print it to the terminal.

  2. The AUTOSTART_PROCESSES(&hello_world_process) tells Contiki to start that process when it finishes booting.

/*-------------------------------------------------*/
PROCESS(hello_world_process, "Hello world process");
AUTOSTART_PROCESSES(&hello_world_process);
/*-------------------------------------------------*/
PROCESS_THREAD(hello_world_process, ev, data) (1)
{
  PROCESS_BEGIN(); (2)
  printf("Hello, world\n"); (3)
  printf("%s\n", hello);
  printf("This is a value in hex 0x%02X, the same as %u\n", num, num);
  PROCESS_END(); (4)
}
  1. You declare the content of the process in the process thread. You have the name of the process and callback functions (event handler and data handler).

  2. Inside the thread you begin the process,

  3. do what you want and

  4. finally end the process.

In this concrete example it is shown how to print values of different types: a numeric value (using different format qualifiers) and a string (literal or stored in a variable).

Makefile explained

Applications require a Makefile to compile, let us take a look at the hello-world Makefile:

CONTIKI_PROJECT = 01-hello-world     (1)
all: $(CONTIKI_PROJECT)              (2)
CONTIKI = ../../../..                (3)
include $(CONTIKI)/Makefile.include  (4)
  1. Tells the build system which application to compile

  2. If using make all it will compile the defined applications

  3. Specify our indentation level respect to Contiki root folder

  4. The system-wide Contiki Makefile, also points out to the platform’s Makefile

We can define specific compilation flags in the Makefile, although the recommended way would be to add a project-conf.h header, and define there any compilation flag or value. This is done by adding this to the Makefile:

DEFINES+=PROJECT_CONF_H=\"project-conf.h\"

And then creating a project-conf.h header file in the example location.

Tip

The make login command creates a connection to a RE-Mote or a Z1 over the USB, and allows to visualize the debug output when using printf. The make login uses the PORT argument to chose to which Zolertia device we want to connect:

make login PORT=/dev/ttyUSB0

Note this will keep a connection open in the terminal, if you close the terminal the connection will be closed. Also, as programming the devices uses the USB port, if there is an existing connection open with the make login command, a programming attempt to the port will fail.

You can also concatenate commands:

make 01-hello-world.upload && make login

The 01-hello-world shows the following when connected over the USB with the make login command:

using saved target 'zoul'
../../../../tools/sky/serialdump-linux -b115200 /dev/ttyUSB0
connecting to /dev/ttyUSB0 (115200) [OK]
Contiki-3.x-2612-g1d456b1
Zolertia RE-Mote platform
CC2538: ID: 0xb964, rev.: PG2.0, Flash: 512 KiB, SRAM: 32 KiB, AES/SHA: 1, ECC/RSA: 1
System clock: 16000000 Hz
I/O clock: 16000000 Hz
Reset cause: External reset
Rime configured with address 00:12:4b:00:06:15:ab:25
 Net: sicslowpan
 MAC: CSMA
 RDC: ContikiMAC
Hello, world
Hello world, again!
This is a value in hex 0xABCD, the same as 43981

Test the LEDs and Button

This section will show how to use the LED (light emitting diode) to interact with our application. Also it will be shown how to use the on-board user button to trigger specific events and change the way our application works.

You have to add the dev/leds.h which is the library to manage the LEDs. To check the available functions go to core/dev/leds.h.

Available LEDs commands:

unsigned char leds_get(void);
void leds_set(unsigned char leds);
void leds_on(unsigned char leds);
void leds_off(unsigned char leds);
void leds_toggle(unsigned char leds);

Normally all platforms comply to the following available LEDs:

LEDS_GREEN
LEDS_RED
LEDS_BLUE
LEDS_ALL

In the Z1 mote these LEDs are defined in platform/z1/platform-conf.h, as well as other hardware definitions.

The RE-Mote uses an RGB LED, basically 3-channel LEDs in a single device, allowing to show any color by the proper combination of Blue, Red and Green. In platforms/zoul/remote/board.h header the following are defined:

LEDS_LIGHT_BLUE
LEDS_YELLOW
LEDS_PURPLE
LEDS_WHITE

Accordingly you need to add dev/button-sensor.h to add support for the user button.

The 02-led-and-button.c example show how the configuration is done.

#include "contiki.h"
#include "dev/leds.h"
#include "dev/button-sensor.h"
#include <stdio.h>
/*-------------------------------------------------*/
PROCESS(led_button_process, "LEDs and button example process");
AUTOSTART_PROCESSES(&led_button_process);
/*-------------------------------------------------*/
PROCESS_THREAD(led_button_process, ev, data)
{
  PROCESS_BEGIN();
  SENSORS_ACTIVATE(button_sensor);      (1)

  while(1) {                            (2)
    printf("Press the User Button\n");
    PROCESS_WAIT_EVENT_UNTIL(ev == sensors_event && data == &button_sensor);  (3)
    /* When the user button is pressed, we toggle the LED on/off... */
    leds_toggle(LEDS_GREEN);            (4)

    /*
     * And we print its status: when zero the LED is off, else on.
     * The number printed when the sensor is on is the LED ID, this value is
     * used as a mask, to allow turning on and off multiple LED at the same
     * time (for example using "LEDS_GREEN + LEDS_RED" or "LEDS_ALL"
     */
    printf("The sensor is: %u\n", leds_get());  (5)
  }

  PROCESS_END();
}
  1. The LED initialization is done at booting by the platform itself, so there is no need to initialize again. The user button is required to initialize

  2. Loop forever: the code inside the while-loop will run always and not reach PROCESS_END() macro.

  3. To avoid wasting processing cycles, the application is paused until we press the user button

  4. When the user button is pressed, alternate between turning the LED on and off

  5. Prints the current LED mask, that is, which LED channels are on and off

Tip
The RE-Mote platform has extra button functionalities, such as detect long-press sequences, enabling to further expand the events that can be triggered using the button. Check platform/zoul/dev/button-sensor.c for more details, and examples/zolertia/zoul/zoul-demo.c for an example.

Now let’s compile and upload the new project with:

make clean && make 02-led-and-button.upload

The make clean command is used to erase previously compiled objects.

Caution

If you make changes to the source code, you must rebuild the files, otherwise your change might not be pulled in. It is always recommended to do a make clean command before compiling.

The RE-Mote user and reset buttons are shown below:

image016
Figure 4. RE-Mote buttons and micro USB ports
Tip
Exercise: try to toggle the other LEDs and write down the LED mask values…​ Are these the same numbers for the Z1 and the RE-Mote? In case of the RE-Mote, try combining colours to produce new ones (remember is RGB!).

Timers

Using timers will allow us to trigger events at a given time, speeding up the transition from one state to another and automating a given process or task, for example blinking an LED every 5 seconds, without the user having to press the button each time.

Contiki OS provides 4 kind of timers:

  • Simple timer: A simple ticker, the application should check manually if the timer has expired. More information at core/sys/timer.h.

  • Callback timer: When a timer expires it can callback a given function. More information at core/sys/ctimer.h.

  • Event timer: Same as above, but instead of calling a function, when the timer expires it posts an event signalling its expiration. More information at core/sys/etimer.h.

  • Real time timer: The real-time module handles the scheduling and execution of real-time tasks, there’s only 1 timer available at the moment. More information at core/sys/rtimer.h

The 03-timers.c example implements the aforementioned timers and shows their functionality.

First let’s see how the timer’s libraries are included:

/* The event timer library */
#include "sys/etimer.h"

/* The seconds timer library */
#include "sys/stimer.h"

/* The regular timer library */
#include "sys/timer.h"

/* The callback timer library */
#include "sys/ctimer.h"

/* The "real-time" timer library */
#include "sys/rtimer.h"

Next each timer needs to have its corresponding timer structure defined, this will allow the application to store the configuration and operational values of each timer at request.

/* The following are the structures used when you need to include a timer, it
 * serves to keep the timer information.
 */
static struct timer  nt;
static struct stimer st;
static struct etimer et;
static struct ctimer ct;
static struct rtimer rt;

Then we need to implement callbacks, generally these are functions called by the application whenever something occurs. As said before, both ctimer and rtimer are capable of invoking a function whenever an event happens (like a timer expiration). Below is the format of the callbacks:

/*---------------------------------------------------------------------------*/
static void
rtimer_callback_example(struct rtimer *timer, void *ptr)
{
  /* Something happens here */
}
/*---------------------------------------------------------------------------*/
static void
ctimer_callback_example(void *ptr)
{
  /* Something happens here */
}
/*---------------------------------------------------------------------------*/

The actual 03-timers.c has the missing snippet of code in each callback, we will return later to these.

Here’s what our thread looks like:

/*-------------------------------------------------*/
PROCESS(test_timer_process, "Test timer");
AUTOSTART_PROCESSES(&test_timer_process);
/*-------------------------------------------------*/
PROCESS_THREAD(test_timer_process, ev, data)
{
  PROCESS_BEGIN();
  static uint32_t ticks = 0;
  timer_set(&nt, CLOCK_SECOND);    (1)(2)
  while(!timer_expired(&nt)) {     (3)
    ticks++;
  }
  printf("timer, ticks: \t%ld\n", ticks);
}
  1. CLOCK_SECOND is a value related to the number of the microcontroller’s ticks per second. As Contiki runs on different platforms with different hardware, the value of CLOCK_SECOND also differs.

  2. The most basic timer type is timer, it will keep running up to CLOCK_SECOND

  3. We need to manually check if the timer has expired

Tip
Exercise: can you print the value of CLOCK_SECOND to count how many ticks you have in one second?

The remaining of our thread is as follows:

  ticks = 0;
  stimer_set(&st, 1);               (1)
  while(!stimer_expired(&st)) {     (2)
    ticks++;
  }
  printf("stimer, ticks: \t%ld\n", ticks);
  1. Same as timer, but its time-base is seconds

  2. And we also need to check if the stimer has expired

Probably now in this point it is clear the disadvantages of using the previous timers: we need to manually check for expiration in a while-loop, which means a lot of wasted CPU cycles.

The next timer allow us to yield our application, meaning it will generate an event informing the application about its expiration, thus the system can perform other tasks in the meantime.

  ticks = 0;
  etimer_set(&et, CLOCK_SECOND * 2);

  ticks++;
  PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER);
  printf("etimer, now: \t%ld\n", ticks);

When the etimer is scheduled, we yield our application and wait for a PROCESS_EVENT_TIMER event, this is a type of process event sent by the timer to notify applications.

Tip

If you run the example the first noticeable thing is the ticks value, what do you think this value will be in this case?

The next timer to test is the ctimer. Let’s see how it is implemented in the example:

  ticks++;
  ctimer_set(&ct, CLOCK_SECOND * 2, ctimer_callback_example, &ticks);

  /* And we keep the process halt while we wait for the callback timer to
   * expire.
   */

  while(1) {
    PROCESS_YIELD();
  }

As comented before the ctimer will invoke a function whenever it expires, in this case the ctimer_callback_example function after 2 seconds since triggered. Notice we are also passing to the ctimer_callback_example the content of the variable ticks, meaning we can pass as argument any type of variable to the callback function, as it defines this parameter as void *ptr. Let’s check now the complete callback implementation:

static void
ctimer_callback_example(void *ptr)
{
  uint32_t *ctimer_ticks = ptr;                     (1)
  printf("ctimer, now: \t%ld\n", *ctimer_ticks);

  /* The real timer allows execution of real-time tasks (with predictable
   * execution times).
   * The function RTIMER_NOW() is used to get the current system time in ticks
   * and RTIMER_SECOND specifies the number of ticks per second.
   */

  (*ctimer_ticks)++;                                (2)
  rtimer_set(&rt, RTIMER_NOW() + RTIMER_SECOND, 0,  (3)
             rtimer_callback_example, ctimer_ticks);
}
  1. Create a pointer to the variable passed as argument

  2. Increment the variable ticks using our pointer

  3. Schedule a rtimer to expire after 1 second and pass as argument our pointer to the ticks variable

In our example when ctimer expires it will set a real-timer event using the rtimer type of timer. The RTIMER_NOW() is a macro used to retrieve the current tick value (rtimer is always ticking thus running in the background), we use this as base value then add the number of ticks in which we want our event to be triggered in the future. The RTIMER_SECOND in the same fashion as CLOCK_SECOND defines the number of ticks in a second.

Tip

Print the value of RTIMER_SECOND and compare to the value of CLOCK_SECOND.

When rtimer expires it invokes its corresponding callback function, let’s check its full implementation in our example:

static void
rtimer_callback_example(struct rtimer *timer, void *ptr)
{
  uint32_t *rtimer_ticks = ptr;
  printf("rtimer, now: \t%ld\n", *rtimer_ticks);

  /* We can restart the ctimer and keep the counting going */
  (*rtimer_ticks)++;
  ctimer_restart(&ct);
}

As done in the ctimer callback we also create a pointer to the original ticks variable, print and increment its value. We restart the ctimer using the initial expiration value configured before. From now on, ctimer callback will configure a new rtimer timer and viceversa, incrementing the ticks value in a linear way.

Processes in Contiki

We learned in previous section how to create a PROCESS and the meaning of the PROCESS_BEGIN() and PROCESS_END() macros. Let’s take a closer look at how processes are implemented in Contiki.

A process thread contains the code of the process. The process thread is a single protothread that is invoked from the process scheduler. A protothread is a way to structure code in a way that allows the system to run other activities when the code is waiting for something to happen. The concept of protothreads was developed within the context of Contiki.

Protothread provides a way for C functions to work in a way that is similar to threads, without the memory overhead of threads. Reducing the memory overhead is important on the memory-constrained systems on which Contiki runs.

The protothreads starts and ends with two special macros, PROCESS_BEGIN and PROCESS_END(). Between these macros, a set of protothread functions can be used.

Libraries and applications in Contiki uses processes to run and to communicate with other modules, by creating its own processes or using the already available. The timers for example use the existing PROCESS_EVENT_TIMER, and applications could also use the PROCESS_EVENT_EXITED process to indicate an early exit. These are event identifiers reserved by the Contiki Kernel.

As probably noticed by now, we normally refer to yielding and waiting for a process. Contiki has two execution contexts: cooperative and preemptive. Processes are cooperative and sequential, interrupts (button, sensors events) and the real-timer are preemptive.

image004
Figure 5. Execution contexts: processes and interrupts

As we have learned before, there are two ways so far to yield a protothread:

PROCESS_YIELD() will wait for any type of process event to happen, it will not check which process has occured. Often a check like if(ev == PROCESS_EVENT_NAME){} is used to catch one or more processes.

On the other hand the PROCESS_WAIT_EVENT_UNTIL(…​) allows to catch a process event direclty, in the case of the etimer as previously shown, we can yield until:

PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER)

Or simply:

PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et))

To catch just an etimer event from timer et.

Similar to PROCESS_YIELD(), the PROCESS_PAUSE() macro will pause the thread, but instead of waiting for an event to happen it will temporatily pause the process, by posting an event to itself and yielding. This allows the system to execute other processes and whenever done then resume our process.

The PROCESS_EXIT() macro will allow the application to exit before reaching PROCESS_END(), it will send a PROCESS_EVENT_EXITED process notification whenever that happens.

The 04-processes.c example shows how to create and terminate process, more importantly how to communicate between process.

Processes are useful as they allow to run different parts of our application, in its own semantic context, allowing a cleaner and efficient implementation. For example imagine a process running all the networking part, while separate processes take readings from individual sensors, each one running on its own context.

The example consist of 3 processes, one will spawn another process and receive events about a variable being incremented. When the variable value reaches a limit, the first process will terminate its child process. A third process listening in stand-by will receive a notification about the terminated process and restart it again.

PROCESS(process1, "Main process");
PROCESS(process2, "Auxiliary process");
PROCESS(process3, "Another auxiliary process");

/* But we are only going to automatically start the first two */
AUTOSTART_PROCESSES(&process1, &process2);

We define the three processes to run, but notice we only automatically start the first two. As opposite to what we have been working with before, after the Z1 or RE-Mote platform finishes booting it will only call the processes declared within the AUTOSTART_PROCESSES(…​) macro, leaving process3 not started for now.

Let’s review how the first process is implemented:

PROCESS_THREAD(process1, ev, data)
{
  PROCESS_BEGIN();
  static uint8_t counter;
  printf("Process 1 started\n");
  process_start(&process3, "Process 1");      (1)

  while(1) {
    PROCESS_YIELD();                          (2)
    if(ev == event_from_process3) {           (3)
      counter = *((uint8_t *)data);           (4)
      printf("Process 3 has requested shutdown in %u seconds\n", counter);
      etimer_set(&et1, CLOCK_SECOND);
    }

    if(ev == PROCESS_EVENT_TIMER) {           (5)
      if(counter <= 0) {
        process_exit(&process3);              (6)
      } else {
        printf("Process 3 will be terminated in: %u\n", counter);
        counter--;
        leds_toggle(LEDS_RED);
        etimer_reset(&et1);
      }
    }
  }
  PROCESS_END();
}
  1. When process1 starts, it will launch process3, when starting a process one can pass variables or data as a argument (in this example a string "Process 1")

  2. And then will wait for any event…​

  3. Note the process1 has two additional arguments: ev and data, when a process posts information to another process, it uses these arguments to pass information such as a pointer to a variable.

  4. If we receive an event from process3 it will mean that process has requested an early termination, so we start an etimer…​

  5. And when our etimer expires…​

  6. We will terminate process3

As seen above process3 is terminated by process1 whenever "something" happens at process3, but what about process2?

PROCESS_THREAD(process2, ev, data)
{
  PROCESS_BEGIN();
  printf("Process 2 started\n");

  while(1) {
    PROCESS_YIELD();
    if(ev == PROCESS_EVENT_EXITED) {          (1)
      printf("* Process 3 has been stopped by Process 1!\n");
      etimer_set(&et2, CLOCK_SECOND * 5);     (2)
    }

    if(ev == PROCESS_EVENT_TIMER) {           (3)
      printf("Process 2 is restarting Process 3\n");
      process_start(&process3, "Process 2");  (4)
    }
  }

  PROCESS_END();
}
  1. When process3 is terminated by process1, we receive a notification

  2. And start a 5-second timer…​

  3. When the etimer expires…​

  4. process2 will launch back again process3, and will send its own name as string

We have now read the implementation of process1 and process2 and learned how to start and end processes, and receive data using the data argument. Let’s check how process3 is implemented:

PROCESS_THREAD(process3, ev, data)
{
  PROCESS_BEGIN();
  static char *parent;
  parent = (char * )data;                        (1)
  static uint8_t counter;                        (2)

  printf("Process 3 started by %s\n", parent);
  event_from_process3 = process_alloc_event();   (3)
  etimer_set(&et3, CLOCK_SECOND);

  counter = 0;

  while(1) {
    PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et3));
    counter++;
    leds_toggle(LEDS_GREEN);

    if(counter == 10) {
      process_post(&process1, event_from_process3, &counter);  (4)
    }
    etimer_reset(&et3);                          (5)
  }
  PROCESS_END();
}
  1. Create a pointer to read the processes name passed as string argument to us

  2. Create a variable to increment

  3. Allocate a process event to use and communicate to other processes

  4. When the counter reaches 10, send an event_from_process3 event to process1 with a pointer to the counter variable

  5. Reset the etimer using the current expiration value previously configured

The event_from_process3 process event was previously declared in our example as:

process_event_t event_from_process3;

Sensors

A sensor is a transducer whose purpose is to sense or detect a characteristic of its environment, providing a corresponding output, generally as an electrical or optical signal, related to the quantity of the measured variable.

Sensors in Contiki are implemented as follow:

SENSORS_SENSOR (sensor, SENSOR_NAME, value, configure, status);

This means that a method to configure the sensor, poll the sensor status and request a value have to be implemented. The sensor structure contains pointers to these functions. The arguments for each function are shown below.

struct sensors_sensor {
   char *       type;
   int          (* value)     (int type);
   int          (* configure) (int type, int value);
   int          (* status)    (int type);
};
Tip

If you need to further expand the available functions (change the int return value of the value function to return a pointer instead, etc) or add your own, you can edit this structure, but keep in mind it will probably break other sensors developed based on this. You can define additional sensor and actuators functions in your header file instead.

To better understand please refer to the platform/zoul/dev/adc-sensors.c, for an example of how analogue external sensors can be implemented (to be further discussed in the next sections).

The following functions and macros can be used to work with the sensor:

SENSORS_ACTIVATE(sensor)    (1)
SENSORS_DEACTIVATE(sensor)  (2)
sensor.value(type);         (3)
  1. Enable the sensor, typically configures and turns the sensor on

  2. Disables the sensor, it is useful to save power

  3. Request a value to the sensor. As one sensor chip might have different types of readings (temperature, humidity, etc.), this is used to specify which measure to take.

Important

Although this is the default way to implement sensors in Contiki, there might be alternatives, depending on the sensor and developer. Always check the specific sensor driver implementation for details. Normally sensors are provided in the same location as the example, in the dev folder, or in the platform/zoul/dev directory for example.

The SENSORS macro provides external linkage to the sensors defined for the platform and application, allowing to iterate between the available sensors. The following are the sensors defined as default for the RE-Mote platform:

SENSORS (&button_sensor, &vdd3_sensor, &cc2538_temp_sensor, &adc_sensors);
Note

The button is considered a sensor in Contiki, and it shares the same sensor implementation

This macro extends to:

const struct sensors_sensor *sensors[] = {
  &button_sensor,
  &vdd3_sensor,
  &cc2538_temp_sensor,
  &adc_sensors,
  ((void *)0)
};

For the Z1 mote the SENSORS macro is defined within the platforms/z1/contiki-z1-main.c:

SENSORS(&button_sensor);

The number of sensors can be found with the SENSORS_NUM macro:

#define SENSORS_NUM (sizeof(sensors) / sizeof(struct sensors_sensor *))

Sensors can be of two types: analogue or digital. In the following sections examples will be shown for both types, detailing how to connect and use both the platform’s internal sensors as well as the external ones.

Tip

Enable the DEBUG by changing to 1 or DEBUG_PRINT, this will print debug information to the console. Normally you should do it in every driver file you wish to debug.

You can use printf to visualize on the console what is happening in your application. It is really useful to debug your code, as you can print values of variables, when certain block of code is being executed, etc.

Analogue Sensors

Analogue sensors typically require being connected to an ADC (analogue to digital converter) to translate the analogue (continuous) reading to an equivalent digital value in millivolts. The quality and resolution of the measure depends on both the ADC resolution (up to 12 bits in the Z1 and RE-Mote) and the sampling frequency.

As a rule of thumb, the sampling frequency must be at least twice that of the phenomenon you are measuring. As an example, if you want to sample human speech (which may contain frequencies up to 8 kHz) you need to sample at twice that frequency (16 kHz).

The Z1 mote and the RE-Mote have a built-in voltage sensor provided as an ADC input channel, as well as a generic implementation to read external analogue sensors.

Tip

Analogue sensors can be connected to both the Z1 and the RE-Mote over analogue_ ports, which are basically 3-pin connectors (Ground, VCC and signal) with 2.54 mm spacing. This port is compatible with Phidget sensors. Nowadays there are also sensors from other providers, such as seeedstudio that have the same pin-out but with a different pin spacing (2 mm). This is not a problem, as there are cables to adapt the pin spacing.

image008
Figure 6. Analogue sensors

The ADC results in the RE-Mote returns the reading in voltage directly, there’s no need to further conversion.

For the Z1 mote the ADC output must be converted by means of the following formula: We multiply the measured value by the voltage reference and divide the product by the ADC’s maximum output. As the resolution of the ADC is 12 bits (212 = 4096), we get:

Voltage, mV = (units * Vref) / 4096;

The voltage reference limits the range of our measure, meaning if we use a Vref of 2.5 V, the sensor will saturate when reading a higher voltage. The reference can be chosen while configuring the ADC, for both the Z1 and RE-Mote normally 3.3 V is used.

Both the Z1 and the RE-Mote allow up to 6 analogue sensors to be connected, but only two analogue connectors can be soldered at the same time.

For the RE-Mote it is possible to have one analogue connector for a 3.3 V analogue sensor (ADC1) and another for 5 V sensors (ADC3).

image011
Figure 7. RE-Mote ADC pin-out

Note the location of the analogue connectors in the image below.

image010
Figure 8. Available connectors in the RE-Mote
Tip

The RE-Mote is based on an ARM Cortex-M3, this allows to map any given pin to a controller (as opposite to the MSP430, for which a pin has predefined functions other than GPIO), but only pins in the PA port can be used as ADC.

For the Z1 mote is possible to have the following combinations (see Figure below):

  • Two phidget connectors for 3.3 V sensors.

  • Two phidget connectors for 5 V sensors.

  • Two phidget connectors, one for 3.3 V and one for 5 V sensors.

image005
Figure 9. Pin assignment

In the snippet below the ADC channels ADC0 (5V1), ADC3 (5V2), ADC1 (3V1) and ADC7 (3V2) are mapped by default to be used as input for external analogue sensors.

/* MemReg6 == P6.0/A0 == 5V 1 */
ADC12MCTL0 = (INCH_0 + SREF_0);
/* MemReg7 == P6.3/A3 == 5V 2 */
ADC12MCTL1 = (INCH_3 + SREF_0);
/* MemReg8 == P6.1/A1 == 3V 1 */
ADC12MCTL2 = (INCH_1 + SREF_0);
/* MemReg9 == P6.7/A7 == 3V_2 */
ADC12MCTL3 = (INCH_7 + SREF_0);

To read from a sensor connected to the ADC7 channel of the Z1 mote, in my code I would need to make the following steps:

#include "dev/z1-phidgets.h"                                (1)
SENSORS_ACTIVATE(phidgets);                                 (2)
printf("Phidget 5V 1:%d\n", phidgets.value(PHIDGET3V_2));   (3)
  1. Include the driver header

  2. Enable the ADC sensors, as default all 4 ADC channels are enabled

  3. Request a reading

The Z1 is powered at 3.3 V, but when connected over the USB (standard voltage 5 V) it allows to connect 5 V sensors to the phidget ports, namely 5V1 (ADC3) and 5V2 (ADC0) as there is a voltage divider in the input to adapt the reading from 5 V to 3.3V.

Details about the Z1 implementation of the analogue sensors driver are available at platform/z1/dev/z1-phidgets.c.

There is also a driver for the MSP430 internal voltage sensor at platform/z1/dev/battery-sensor.c, with an example at examples/zolertia/z1/test-battery.c.

#include "dev/battery-sensor.h"
/*---------------------------------------------------------------------------*/
PROCESS(test_battery_process, "Battery Sensor Test");
AUTOSTART_PROCESSES(&test_battery_process);
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(test_battery_process, ev, data)
{
  static uint32_t battery;
  PROCESS_BEGIN();
  SENSORS_ACTIVATE(battery_sensor);

  while(1) {
    battery = battery_sensor.value(0);
    battery *= 5000;
    battery /= 4096;
    printf("Battery: %u.%02uV\n", (uint16_t)battery / 1000, (uint16_t)battery % 1000);
  }
  PROCESS_END();
}

When the USB is connected or the battery is full, the result is similar to:

Battery: 3.21V
Battery: 3.18V
Battery: 3.20V
Battery: 3.18V
Battery: 3.20V
Battery: 3.20V
Battery: 3.18V
Battery: 3.20V
Battery: 3.18V

It is possible also to connect an external analogue sensor, like the Phidget 1142 precision Light Sensor or the Grove/Seeedstudio Light Sensor (P).

image006
Figure 10. Phidget 1142 Light sensor
image012
Figure 11. Seeedstudio (Grove) Light sensor

The example called test-phidgets.c in examples/zolertia/z1 will read values from an analog sensor and print them to the terminal.

Connect the light sensor to the 3V2 Phidget connector and compile the example:

make clean && make test-phidgets.upload && make z1-reset && make login
Tip

You can pipeline commands to be executed one after another, in this case the make z1-reset restarts the mote after flashed, then make login prints to console the output of any printf command in our code. You may need to use the argument MOTES=/dev/ttyUSB[0…​9] to specify a mote over a particular USB port.

This is the result:

Starting 'Test Button & Phidgets'
Please press the User Button
Phidget 5V 1:123
Phidget 5V 2:301
Phidget 3V 1:1710
Phidget 3V 2:2202

The light sensor is connected to the 3V2 connector, so the raw value is 2202. Try to illuminate the sensor with a flashlight (from your mobile phone, for example) and then cover it with your hand so that no light can reach it.

From the Phidget website we have the following information about the sensor:

Parameter Value

Sensor type

Light

Light level min

1 lux

Supply Voltage Min

2.4 V

Supply Voltage Max

5.5 V

Max current consumption

5mA

Light level max (3.3 V)

660 lux

Light level max (5 V)

1000 lux

As you can see, the light sensor can be connected to either the 5 V or the 3.3 V analogue connector. The max measurable value depends on where you connect it.

The formula to translate SensorValue into luminosity is: Luminosity(lux)=SensorValue

The RE-Mote platform also allows to connect 5 V sensors to the ADC3 port, using the same voltage divider as the Z1 mote. As seen in the next snippet, the ADC3 port is mapped to the PA2 pin.

/*
 * This driver supports analogue sensors connected to ADC1, ADC2 and AND3 inputs
 * This is controlled by the type argument of the value() function. Possible
 * choices are:
 * - REMOTE_SENSORS_ADC1 (channel 5)
 * - REMOTE_SENSORS_ADC2 (channel 4)
 * - REMOTE_SENSORS_ADC3 (channel 2)
 */
image009
Figure 12. RE-Mote ADC3 voltage divider for 5V analogue sensors

Then to read data from an attached sensor (as we did in the previous example):

#include "dev/zoul-sensors.h"                                               (1)
adc_sensors.configure(SENSORS_HW_INIT, REMOTE_SENSORS_ADC_ALL);             (2)
printf("ADC1 = %d raw\n", adc_sensors.value(REMOTE_SENSORS_ADC1));          (3)
printf("ADC3 = %d raw\n", adc_sensors.value(REMOTE_SENSORS_ADC3));
  1. Include the ADC driver header

  2. Enable and configure the ADC channels (can selectively choose which to enable)

  3. Request a reading

Note the ADC3 voltage reading has to be multiplied by 3/2 to get the actual 0-5V range value, as there is a voltage divider with a 500K/200K relationship.

Now let’s connect a Grove Light Sensor to the RE-Mote:

image013
Figure 13. RE-Mote and Grove light sensor

Compile and program the RE-Mote example at example/zolertia/zoul/zoul-demo.c, the output should be similar to:

connecting to /dev/ttyUSB0 (115200) [OK]
-----------------------------------------
Counter = 0x00000000
VDD = 3299 mV
Temperature = 33333 mC
ADC1 = 112 raw
ADC3 = 1516 raw

The above result was taken with the light sensor covered, so aproximately with no light the voltage value is 0.112V. This light sensor has a light-dependent resistor (LDR), the output in voltage units correlates to the equivalent LDR. The Grove wiki page shows how the resistance/luxes graphs to obtain proper lux values, but probably a better option is to calibrate by using a known lux source, or just knowing the smaller the value the darkest the ambient is.

Tip
Exercise: make the sensor take readings as fast as possible. Print to the screen the ADC raw values and the millivolts (as this sensor is linear, the voltage corresponds to the luxes). What are the max and min values you can get? What is the average light value of the room? Create an application that turns the red LED on when it is dark and the green LED on when the room is bright. In between, all the LEDs should be off. Add a timer and measure the light every 10 seconds.

Note the RE-Mote has built-in voltage and core temperature sensors. The voltage sensor provides the voltage level of the CC2538 system on chip, whereas the core temperature gives the operation temperature. As shown in the previous code snippet, the zoul-demo.c example also shows how to use both sensors:

  printf("VDD = %d mV\n",
         vdd3_sensor.value(CC2538_SENSORS_VALUE_TYPE_CONVERTED));

  printf("Temperature = %d mC\n",
          cc2538_temp_sensor.value(CC2538_SENSORS_VALUE_TYPE_CONVERTED));

Both sensors are initialized at booting by the RE-Mote.

Digital Sensors

Digital sensors are normally interfaced over a digital communication protocol such as I2C, SPI, 1-Wire, Serial or depending on the manufacturer, a proprietary protocol normally on a ticking clock.

Digital sensors allow a more extended set of commands (turn on, turn off, configure interrupts). With a digital light sensor for example, you could set a threshold value and let the sensor send an interrupt when reached, without the need for continuous polling.

Remember to check the specific sensor information and data sheet for more information.

The Z1 mote has two built in digital sensors: temperature and 3-axis accelerometer. The ADXL345 accelerometer has an example named test-adxl345.c in examples/zolertia/z1 showing how the sensor works.

The ADXL345 is an I2C ultra-low power sensor able to read up to 16g, well suited for mobile device applications. It measures the static acceleration of gravity in tilt-sensing applications, as well as dynamic acceleration resulting from motion or shock. Its high resolution (3.9mg/LSB) enables measurement of inclination changes smaller than 1.0°.

[37] DoubleTap detected! (0xE3) -- DoubleTap Tap
x: -1 y: 12 z: 223
[38] Tap detected! (0xC3) -- Tap
x: -2 y: 8 z: 220
x: 2 y: 4 z: 221
x: 3 y: 5 z: 221
x: 4 y: 5 z: 222

The accelerometer can read data in the x, y and z axis and has three types of interrupts: a single tap, a double tap and a free-fall (be careful to not damage the mote!).

Having an accelerometer on-board is quite useful: you can detect if anyone is tampering with the device, or detect if someone falls, monitor vibration and generate an alarm if exceeds a given threshold (causing a component to break), etc.

The code has two threads, one for the interruptions and the other for the LEDs. When Contiki starts, it triggers both processes.

The led_process thread triggers a timer that waits before turning off the LEDs. This is mostly done to filter the rapid signal coming from the accelerometer. The other process is the acceleration. It assigns the callback for the led_off event. Interrupts can happen at any given time, are non periodic and totally asynchronous.

Interrupts can be triggered by external sources (sensors, GPIOs, Watchdog Timer, etc) and should be cleared as soon as possible. When an interrupts happens, the interrupt handler (which is a process that checks the interrupt registers to find out which is the interrupt source) manages it and forwards it to the subscribed callback.

In this example, the accelerometer is initialized and then the interrupts are mapped to a specific callback functions. Interrupt source 1 is mapped to the free fall callback handler and the tap interrupts are mapped to the interrupt source 2.

/*
 * Start and setup the accelerometer with default
 * values, _i.e_ no interrupts enabled.
 */
SENSORS_ACTIVATE(adxl345);
/* Register the callback functions */
ACCM_REGISTER_INT1_CB(accm_ff_cb);
ACCM_REGISTER_INT2_CB(accm_tap_cb);

We then need to enable the interrupts like this:

accm_set_irq(ADXL345_INT_FREEFALL,
             ADXL345_INT_TAP +
             ADXL345_INT_DOUBLETAP);

In the while loop we read the values from each axis every second. If there are no interrupts, this will be the only thing shown in the terminal.

Tip
Exercise: put the mote in different positions and check the values of the accelerometer. Try to understand which is x, y and z. Measure the maximum acceleration by shaking the mote. Turn on and off the LED according to the acceleration on one axis.

The TMP102 digital temperature sensor on-board the Z1 platform has a +/-5ºC accuracy and can measure temperatures from -25ºC to 85ºC. There is an example ready to use at examples/zolertia/z1/test-tmp102.c showing how the sensor works.

PROCESS_THREAD(temp_process, ev, data)
{
  PROCESS_BEGIN();
  int16_t temp;
  SENSORS_ACTIVATE(tmp102);
  while(1) {
    etimer_set(&et, TMP102_READ_INTERVAL);
    PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));
    temp = tmp102.value(TMP102_READ);
    printf("Temp = %d.%02d\n", temp / 100, temp % 100);
  }
  PROCESS_END();
}

The output is similar to below:

Temp = 27.75
Temp = 28.12
Temp = 28.31
Temp = 28.18
Temp = 28.12
Temp = 28.06
Warning

The TMP102 is placed close to the CP2102 Serial to USB converter of the Z1 mote, thus when powered over USB it will overheat and increase the temperature nearby the sensor. Consider substracting 4-5ºC to the actual readings when powered over USB. If the Z1 is powered over battery the measurements will be OK.

External digital sensors can be connected to the Z1 and RE-Mote platforms.

As shown in Figure 8, the RE-Mote and the Z1 allows I2C and SPI sensors to be connected to the Ziglet port: a 5-pin connector with 2.54mm pitch spacing as follows:

image014
Figure 14. RE-Mote 5-pin digital port (I2C and/or SPI)

For the next example we will use the SHT25, an I2C digital temperature and humidity sensor from Sensirion.

image007
Figure 15. SHT25 Temperature and humidity sensor
Parameter Value

Sensor type

Temperature and Humidity

Supply Voltage [V]

2.1 - 3.6

Energy Consumption

3.2 uW (at 8 bit, 1 measurement / s)

Data range

0-100 % RH (humidity), -40-125ºC (temperature)

Max resolution

14 bits (temperature), 12 bits (humidity)

Max current consumption

300 uA

As usual you can check the driver implementation at the platform/z1/dev and platform/zoul/dev directories. Check out these locations for other sensors and examples to guide you to implement your own.

When adding an external sensor to your application, you will need to tell the compiler to include the sensor libraries. In the previous examples this was not required as the platforms include its on-board sensor automatically. Adding the external sensor libraries to the application is done in the Makefile. This is how the RE-Mote Makefile looks like:

DEFINES+=PROJECT_CONF_H=\"project-conf.h\"                                 (1)

CONTIKI_PROJECT = zoul-demo test-tsl2563 test-sht25 test-power-mgmt        (2)
CONTIKI_PROJECT += test-bmp085-bmp180 test-motion test-rotation-sensor
CONTIKI_PROJECT += test-grove-light-sensor test-grove-loudness-sensor
CONTIKI_PROJECT += test-weather-meter test-grove-gyro test-lcd test-iaq
CONTIKI_PROJECT += test-pm10-sensor test-vac-sensor test-aac-sensor
CONTIKI_PROJECT += test-zonik

CONTIKI_TARGET_SOURCEFILES += tsl2563.c sht25.c bmpx8x.c motion-sensor.c    (3)
CONTIKI_TARGET_SOURCEFILES += adc-sensors.c weather-meter.c grove-gyro.c
CONTIKI_TARGET_SOURCEFILES += rgb-bl-lcd.c pm10-sensor.c iaq.c zonik.c

all: $(CONTIKI_PROJECT)

CONTIKI = ../../..
include $(CONTIKI)/Makefile.include
  1. The location of the project-conf.h file for specific application configuration.

  2. The test examples to compile automatically if no application is defined. If you run make it will compile all of them at once!

  3. The name of the libraries to include. As default it will search the platform/dev folder

The sht25.c is the library of the SHT25 sensor.

The SHT25 sensor example is available in both examples/zolertia/zoul/test-sht25.c and examples/zolertia/z1/test-sht25.c.

/*---------------------------------------------------------------------------*/
#include "dev/sht25.h"
/*---------------------------------------------------------------------------*/
PROCESS(remote_sht25_process, "SHT25 test");
AUTOSTART_PROCESSES(&remote_sht25_process);
/*---------------------------------------------------------------------------*/
static struct etimer et;
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(remote_sht25_process, ev, data)
{
  int16_t temperature, humidity;

  PROCESS_BEGIN();
  SENSORS_ACTIVATE(sht25);                                                  (1)

  /* Check if the sensor voltage operation is over 2.25V */
  if(sht25.value(SHT25_VOLTAGE_ALARM)) {                                    (2)
    printf("Voltage is lower than recommended for the sensor operation\n");
    PROCESS_EXIT();
  }

  /* Configure the sensor for maximum resolution (14-bit temperature, 12-bit
   * relative humidity), this will require up to 85ms for the temperature
   * integration, and 29ms for the relative humidity (this is the default
   * setting at power on).  To achieve a faster integration time at the cost
   * of a lower resolution, change the value below accordingly, see sht25.h.
   */
  sht25.configure(SHT25_RESOLUTION, SHT2X_RES_14T_12RH);                    (3)

  /* Let it spin and read sensor data */

  while(1) {
    etimer_set(&et, CLOCK_SECOND);
    PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));
    temperature = sht25.value(SHT25_VAL_TEMP);                              (4)
    printf("Temperature %02d.%02d ºC, ", temperature / 100, temperature % 100);
    humidity = sht25.value(SHT25_VAL_HUM);                                  (5)
    printf("Humidity %02d.%02d RH\n", humidity / 100, humidity % 100);
  }
  PROCESS_END();
}
  1. Enable the sensor

  2. Check if the sensor powering voltage is below 2.25V. The sensor requires at least 2.25V to work properly, else it might yield unreliable measures

  3. Configure the sensor resolution

  4. Retrieve a temperature reading

  5. Retrieve a humidity reading

An output example is given below:

Starting 'SHT25 test'
Temperature 23.71 ºC
Humidity 42.95 % RH
Temperature 23.71 ºC
Humidity 42.95 % RH
Temperature 23.71 ºC
Humidity 42.95 % RH
Temperature 23.71 ºC
Humidity 42.98  %RH
Tip

Exercise: convert the temperature to Fahrenheit. Try to get the temperature and humidity as high as possible (without damaging the mote!). Try to print only “possible” values (if you disconnect the sensor, you should not print anything, or print an error message!).

The next section will continue with the walkthrough on how to work with the on-board sensors, shown in the analogue and digital sensors section.

Tip

Normally when working with sensors it is often recommeded to operate as follows:

SENSOR_ACTIVATE(...)
/* configure, retrieve readings, etc */
SENSOR_DEACTIVATE();

This ensures the sensor is configured as expected. It is quite common to have more than one digital sensor sharing the same communication bus (I2C/SPI), or having a driver configuring the ADC ports with a general configuration thus overriding the expected one.

On-board sensors and ADC

The 05-onboard-sensors.c example shows a mixture of the on-board sensors of each platforms shown in the section before. This example is compatible with both the Z1 and the RE-Mote platform, so it can be compiler for both.

The following snippet shows how is this done:

#if CONTIKI_TARGET_ZOUL
    batt = vdd3_sensor.value(CC2538_SENSORS_VALUE_TYPE_CONVERTED);
    printf("VDD = %u mV\n", (uint16_t)batt);

    temp = cc2538_temp_sensor.value(CC2538_SENSORS_VALUE_TYPE_CONVERTED);
    printf("Core temperature = %d.%u C\n", temp / 1000, temp % 1000);

#else /* Assumes Z1 mote */
    x_axis = adxl345.value(X_AXIS);
    y_axis = adxl345.value(Y_AXIS);
    z_axis = adxl345.value(Z_AXIS);
    temp   = tmp102.value(TMP102_READ);
    batt   = battery_sensor.value(1);

    /* Print the readings */
    printf("Acceleration: X %d Y %d Z %d\n", x_axis, y_axis, z_axis);
    printf("Temperature: %d.%u C\n", temp / 100, temp % 100);

    /* Convert the ADC readings to mV */
    batt *= 5000;
    batt /= 4095;
    printf("Battery: %u\n\n", (uint16_t)batt);
#endif

The key is the CONTIKI_TARGET_ZOUL preprocesor value.

Whenever we compile using TARGET=zoul the CONTIKI_TARGET_ZOUL is defined, thus allowing us to differentiate at compilation time which block of code will be included in our application.

For the Z1 mote the output is similar to:

Acceleration: X -13 Y -73 Z -38
Temperature: 28.50 C
Battery: 3018

Acceleration: X -14 Y -75 Z -39
Temperature: 28.43 C
Battery: 3020

And for the RE-Mote:

VDD = 3300 mV
Core temperature = 33.571 C
VDD = 3297 mV
Core temperature = 33.809 C

The 06-adc.c example also summarizes the analogue sensors into a single test application compatible for both platforms: The Z1 and the RE-Mote:

#if CONTIKI_TARGET_ZOUL
    printf("ADC1 = %u mV\n", adc_zoul.value(ZOUL_SENSORS_ADC1));
    printf("ADC3 = %u mV\n", adc_zoul.value(ZOUL_SENSORS_ADC3));
#else
    printf("Phidget 5V 1:%d\n", phidgets.value(PHIDGET5V_1));
    printf("Phidget 5V 2:%d\n", phidgets.value(PHIDGET5V_2));
    printf("Phidget 3V 1:%d\n", phidgets.value(PHIDGET3V_1));
    printf("Phidget 3V 2:%d\n\n", phidgets.value(PHIDGET3V_2));
#endif

General input/output pins (GPIO)

The General input/output pins are generic pins used either as input or output (0-3.3V), useful in case you need to actuate on something, or read the digital voltage level as high/low (3.3V or 0V).

Some examples are:

  • Turn a lamp on and off by controlling a relay

  • Check if a window is open or close using a reed switch

The way the LEDs works is setting a given pin as output, and setting its value high or low (depending on the actual pin logic and pull-up or pull-down resistors it might have).

The user button is actually a GPIO configured as input, and then detecting whenever the value transitions from one level to another (high to low or viceversa), thus generating an interrupt event.

The Z1 and the RE-Mote have a very different way to configure and work with GPIO, as currently Contiki lacks an unified GPIO API. The 07-gpio.c example covers both cases.

The RE-Mote and the CC2538 library implements macros to directly configure and set the pins/ports registers. The CC2538 has four ports (namely A, B, C, D) each one with a numeric value from zero to three:

/** \name Numeric representation of the four GPIO ports
 * @{
 */
#define GPIO_A_NUM              0 /**< GPIO_A: 0 */
#define GPIO_B_NUM              1 /**< GPIO_B: 1 */
#define GPIO_C_NUM              2 /**< GPIO_C: 2 */
#define GPIO_D_NUM              3 /**< GPIO_D: 3 */
/** @} */

Subsequently each port has up to eight pins, numbered from zero to seven. The pins then are normally referred to PA0 and alike, indicating is the pin zero at the port A. The image below shows the pin-out of the RE-Mote:

image015
Figure 16. RE-Mote pin-out

It is possible to configure and use one or more pins at the same time, this is done using masks. As we have 8 pins per port, we treat the pin relative position in a mask fashion. For example if we want to enable pins 2 and 4 of the port A at the same time:

Table 1. Pin mask example
bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0

0

0

0

1

0

1

0

0

So the value we would need to use is 0x14 in hexadecimal or 20 in decimal base.

The conversion of a single pin number to the required pin mask can be done using the following macro:

GPIO_PIN_MASK(pin number)

Also the actual port register address can be obtained from the port number by using the following macro:

GPIO_PORT_TO_BASE(port number)

In the 07-gpio.c example this is how a pin is configured as an output pin (PA5).

The GPIO_SOFTWARE_CONTROL(…​) macro configures the pin to be controlled by the user, instead of being driven by the hardware as other function (I2C, SPI, etc).

  /* The masks below converts the Port number and Pin number to base and mask values */
  #define EXAMPLE_PORT_BASE  GPIO_PORT_TO_BASE(GPIO_A_NUM)
  #define EXAMPLE_PIN_MASK   GPIO_PIN_MASK(5)

  /* We tell the system the application will drive the pin */
  GPIO_SOFTWARE_CONTROL(EXAMPLE_PORT_BASE, EXAMPLE_PIN_MASK);

  /* And set as output, starting as high */
  GPIO_SET_OUTPUT(EXAMPLE_PORT_BASE, EXAMPLE_PIN_MASK);
  GPIO_SET_PIN(EXAMPLE_PORT_BASE, EXAMPLE_PIN_MASK);

Then in a loop we read the pin status (if it is high or low), and toggle its state. Notice the GPIO_READ_PIN(…​) macro also uses a pin mask as argument, meaning you can read in a single instruction the status of all the pins of the port. The return of this macro will give a mask of pins, in which all pins set as high will have a value of one, and zero otherwise.

In this example if pin PA5 is active, the GPIO_READ_PIN(…​) will return a value of 0x20 in hexadecimal, or 20 in decimal base, as this is the pin 5 mask value (1 << 5).

while(1) {
    PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));

    if(GPIO_READ_PIN(EXAMPLE_PORT_BASE, EXAMPLE_PIN_MASK)) {
      GPIO_CLR_PIN(EXAMPLE_PORT_BASE, EXAMPLE_PIN_MASK);
    } else {
      GPIO_SET_PIN(EXAMPLE_PORT_BASE, EXAMPLE_PIN_MASK);
    }

    leds_toggle(LEDS_GREEN);
    etimer_reset(&et);
  }

The Z1 mote on the other hand uses directly the register values to configure and actuate on the pins:

  #define EXAMPLE_PIN_MASK  (1<<2)

  /* The MSP430 MCU names the pins as a tupple of PORT + PIN, thus P6.1 refers
   * to the Pin number 1 of the Port 6.
   * We need first to select the operation mode of the Pin, as a Pin can have
   * a special function (UART, ADC, I2C) also.  The GPIO operation is selected
   * by writting the pin's bit as zero to the PxSEL register.  The below snippet
   * changes 00010000 to 11101111, so when writting to the register we only
   * clear our pin, leaving the others untouched (as it is an AND operation)
   */
  P4SEL &= ~EXAMPLE_PIN_MASK;

  /* Next we set the direction of the pin.  Output means the pin can be high
   * (3.3V) or low (0V), while being as Input will allow us to read the digital
   * value the pin has.  To enable the pin as Output, write an 1.
   */
  P4DIR |= EXAMPLE_PIN_MASK;

We need to configure first the pin function (we select the pin as GPIO by setting the pin mas value on the PxSEL register to zero). The PxDIR configures the direction of the pin, if writting a zero to the pin mask value it will be configured as input, otherwise if set to one it will be configured as output.

    /* This toggles the pin, if low then sets the pin high, and viceversa.
     * Alternatively to set the pin high, use P4OUT |= EXAMPLE_PIN_MASK, and to
     * drive low use P4OUT &= ~EXAMPLE_PIN_MASK
     */
    P4OUT ^= EXAMPLE_PIN_MASK;

And then to set the pin high or low we use the PxOUT register (it assumes the pin is configured as output, its value is ignored otherwise). Writting a one to the pin mask value on the register will set the pin as high, and low otherwise is writting a zero.