Getting Started with Contiki for TI CC2538DK

This guide's aim is to help you start using Contiki for TI's CC2538 Development Kit. By CC2538DK we mean a TI CC2538 Evaluation Module (EM), either standalone and powered by USB or attached to a SmartRF06 Evaluation Board (EB) or Battery Board (BB). The general usage scenario assumes that the EM is attached to a SmartRF06EB and is powered by it.

This guide assumes that you have basic understanding of how to use the command line and perform basic admin tasks on UNIX family OSs.

Port Features

The platform has the following key features:

  • Deep Sleep support with RAM retention for ultra-low energy consumption.
  • Native USB support (CDC-ACM). SLIP over UART for border routers is no longer a bottleneck.
  • DMA transfers for increased performance (RAM to/from RF, RAM to/from USB).

In terms of hardware support, the following drivers have been implemented:

  • CC2538 System-on-Chip:
    • Standard Cortex M3 peripherals (NVIC, SCB, SysTick)
    • Sleep Timer (underpins rtimers)
    • SysTick (underpins the platform clock and Contiki's timers infrastructure)
    • RF
    • UART
    • Watchdog (in watchdog mode)
    • USB (in CDC-ACM)
    • uDMA Controller (RAM to/from USB and RAM to/from RF)
    • Random number generator
    • Low Power Modes
    • General-Purpose Timers. NB: GPT0 is in use by the platform code, the remaining GPTs are available for application development.
    • ADC
    • PWM
    • Cryptoprocessor (AES-ECB/CBC/CTR/CBC-MAC/GCM/CCM-128/192/256, SHA-256)
    • Public Key Accelerator (ECDH, ECDSA)
    • Flash-based port of Coffee
  • SmartRF06 EB and BB peripherals
    • LEDs
    • Buttons
    • ADC sensors (on-chip VDD / 3 and temperature, ambient light sensor)
    • UART connectivity over the XDS100v3 backchannel (EB only)


To start using Contiki, you will need the following:

  • A toolchain to compile Contiki for the CC2538.
  • Drivers so that your OS can communicate with your hardware.
  • Software to upload images to the CC2538.

Different tasks can be performed under different operating systems. The table below summarises what task can be performed on which OS:

                   Windows     Linux     OS-X
Building Contiki      Y          Y         Y
Node Programming      Y          Y         Y
Console output
  (UART)              Y          Y         Y
  (USB CDC-ACM)       Y          Y         Y
Border Routers
  (UART)              N          Y         Y
  (USB CDC-ACM)       N          Y         Y
  (UART)              N          Y         Y
  (USB CDC-ACM)       N          Y         Y

The platform has been developed and tested under Windows XP, Mac OS X 10.9.1 and Ubuntu 12.04 and 12.10. The matrix above has been populated based on information for those OSs.

Install a Toolchain

The toolchain used to build contiki is arm-gcc, also used by other arm-based Contiki ports. If you are using Instant Contiki, you may have a version pre-installed in your system.

The platform is currently being used/tested with "GNU Tools for ARM Embedded Processors" ( The current recommended version and the one being used by Contiki's regression tests on Travis is shown below.

$ arm-none-eabi-gcc --version
arm-none-eabi-gcc (GNU Tools for ARM Embedded Processors) 5.2.1 20151202 (release) [ARM/embedded-5-branch revision 231848]
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO


You will need to install drivers so that your Operating System can communicate with the hardware

For the SmartRF06 EB (UART)

The SmartRF communicates with the PC with a piece of hardware called the TI XDS100v3 Emulator (from now on simply XDS). This is a combined JTAG/UART interface and is used to program the EM, for debugging and for UART character I/O.

You will need to install XDS drivers if you want to do anything useful with the CC2538 UART.

  • For Windows: Installing SmartRF Studio will install the drivers (A beta version is needed, not the one currently distributed on the TI site). Read the SmartRF User Guide for more detailed instructions. After driver installation, the XDS will appear as a COM port.
  • For Linux: The XDS is based on an FTDI chip and new Linux kernels provide very good support for FTDI chips. If the kernel module does not kick in automatically, perform the following steps:
    • Connect the SmartRF to the linux box. Find the device's VID and PID (0403:a6d1 in the output below):

        $ lsusb
        Bus 001 Device 002: ID 0403:a6d1 Future Technology Devices International, Ltd
    • As root or with sudo, run the command below (if necessary, replace the vendor and product arguments with the values you got from lsusb):

        modprobe ftdi_sio vendor=0x403 product=0xa6d1
    • From Kernel 3.12 run the command below:

        modprobe ftdi_sio
        echo 0403 a6d1 > /sys/bus/usb-serial/drivers/ftdi_sio/new_id
    • You may have have to remove package brltty, if it's installed.

    • The board should have enumerated as /dev/ttyUSB{0,1}. ttyUSB1 will be the UART backchannel.

  • For OS X: We need to hack the kernel extension (kext) a little bit:
    • First, install the FTDI VCP driver from

    • Edit /System/Library/Extensions/FTDIUSBSerialDriver.kext/Contents/Info.plist with a text editor.

    • Add the following block somewhere under IOKitPersonalities.

    • If the kext is loaded at the time you perform this change, you will have to either reload it or reboot the Mac. At the time of writing this guide, reloading the kext would fail with errors so rebooting appears to be the only solution.

    • After you have rebooted, plug in the SmartRF, turn it on and then load the kext manually:

        sudo kextload /System/Library/Extensions/FTDIUSBSerialDriver.kext

If everything worked, the XDS will have enumerated as /dev/tty.usbserial-<serial-number>

For the CC2538EM (USB CDC-ACM)

The CC2538 EM's USB Vendor and Product IDs are the following:

  • VID 0x0451
  • PID 0x16C8

The implementation in Contiki is pure CDC-ACM: The Linux and OS X kernels know exactly what to do and drivers are not required.

On windows, you will need to provide a driver. You have two options:

  • Use the signed or unsigned driver provided by TI in CC2538 Foundation Firmware. You will find them both under the drivers directory.
  • Download a generic Virtual Serial Port driver and modify it so it works for the CC2538.

For the latter option:

  • Download this LUFA CDC-ACM driver.
  • Adjust the VID and PID near the end with the values at the start of this section.
  • Next time you get prompted for the driver, include the directory containing the .inf file in the search path and the driver will be installed.

Improve Stability on Linux

There are some issues under recent Ubuntu versions (e.g. 12.10). The problem manifests itself as frequent connects/disconnects for the first approximately 30 seconds after the device has been connected to the host (Both UART and USB). The reason for this is that, as soon as the device is connected, the modem manager kicks in and starts probing it. To prevent this, we can tell the modem manager to leave this device alone:

  • edit /lib/udev/rules.d/77-mm-usb-device-blacklist.rules

  • Add the following line somewhere:

      ATTRS{idVendor}=="0451", ATTRS{idProduct}=="16c8", ENV{ID_MM_DEVICE_IGNORE}="1"
  • This line will instruct modem-manager to ignore the EM's USB port. To achieve the same for the SmartRF's XDS port, add a similar line but replace idVendor and idProduct with the XDS' VID/PID: 0403/a6d1.

  • restart the modem-manager process:

      sudo service modemmanager restart

This will tell modem manager to suppress probing for these VID/PID combinations. Keep in mind that the blacklist.rules file may get overwritten by future modem-manager updates and you may have to re-apply the fix in the future.

Jumper Settings

Be careful with jumper settings on the CC2538 EM. The EM can be powered from the SmartRF or it can be powered from its own USB port.

  • Locate the pair of adjacent jumpers on the EM.
  • To power the EM from the SmartRF, place the jumper on the inner two pins (the ones closest to the SoC).
  • To power the EM from its USB, place the jumper on the two pins nearest to the USB port.

The USB functionality will work on both situations, the jumper only controlls power supply.

Device Enumerations

For the UART, serial line settings are 115200 8N1, no flow control.

Once all drivers have been installed correctly:

On windows, devices will appear as a virtual COM port (applies to both the UART/XDS as well as USB CDC-ACM).

On Linux and OS X, devices will appear under /dev/.

On OS X:

  • XDS backchannel: tty.usbserial-<serial number>
  • EM in CDC-ACM: tty.usbmodemf<X><ABC> (X a letter, ABC a number e.g. tty.usbmodemfd121)

On Linux:

  • XDS backchannel: ttyUSB1
  • EM in CDC-ACM: ttyACMn (n=0, 1, ....)

Software to Program the Nodes

The CC2538 can be programmed via the jtag interface or via the serial boot loader on the chip.

  • On Windows:

    • Nodes can be programmed with TI's ArmProgConsole or the SmartRF Flash Programmer 2. The README should be self-explanatory. With ArmProgConsole, upload the file with a .bin extension. (jtag + serial)
    • Nodes can also be programmed via the serial boot loader in the cc2538. In tools/cc2538-bsl/ you can find this is a python script that can download firmware to your node via a serial connection. If you use this option you just need to make sure you have a working version of python installed. You can read the README in the same directory for more info. (serial)
  • On Linux:

    • Nodes can be programmed with TI's UniFlash tool. With UniFlash, use the file with .elf extension. (jtag + serial)
    • Nodes can also be programmed via the serial boot loader in the cc2538. No extra software needs to be installed. (serial)
  • On OSX:

    • The script in tools/cc2538-bsl/ is the only option. No extra software needs to be installed. (serial)

The file with a .cc2538dk extension is a copy of the .elf file.

Use the Port

The following examples are intended to work off-the-shelf:

  • Examples under examples/cc2538dk
  • Border router: examples/ipv6/rpl-border-router
  • Webserver: examples/webserver-ipv6

We can also use the CoAP example from examples/er-rest-example/. However, the example's Makefile is slightly problematic at the time of writing this guide. As a workaround, open it and delete this entire block:

ifneq ($(TARGET), minimal-net)
ifneq ($(TARGET), native)
ifneq ($(TARGET), econotag)
ifneq ($(findstring avr,$(TARGET)), avr)
PROJECT_SOURCEFILES += static-routing.c

The key is to prevent compilation of static-routing.c. If you're curious about more info, see the discussion here:

and the related bug report here:

Build your First Examples

It is recommended to start with the cc2538-demo and timer-test examples under examples/cc2538dk/. These are very simple examples which will help you get familiar with the hardware and the environment.

Strictly speaking, to build them you need to run make TARGET=cc2538dk. However, the example directories contain a which is automatically included and specifies the correct TARGET= argument. Thus, for examples under the cc2538dk directory, you can simply run make.

If you want to upload the compiled firmware to a node via the serial boot loader you need to manually enable the boot loader and then use make cc2538-demo.upload. On the SmartRF06 board you enable the boot loader by resetting the board (EM RESET button) while holding the select button. (The boot loader backdoor needs to be enabled on the chip for this to work, see README in the tools/cc2538-bsl directory for more info)

For the cc2538-demo, the comments at the top of cc2538-demo.c describe in detail what the example does.

To generate an assembly listing of the compiled firmware, run make cc2538-demo.lst. This may be useful for debugging or optimizing your application code. To intersperse the C source code within the assembly listing, you must instruct the compiler to include debugging information by adding CFLAGS += -g to the project Makefile and rebuild by running make clean cc2538-demo.lst.

Node IEEE/RIME/IPv6 Addresses

Nodes will generally autoconfigure their IPv6 address based on their IEEE address. The IEEE address can be read directly from the CC2538 Info Page, or it can be hard-coded. Additionally, the user may specify a 2-byte value at build time, which will be used as the IEEE address' 2 LSBs.

To configure the IEEE address source location (Info Page or hard-coded), use the IEEE_ADDR_CONF_HARDCODED define in contiki-conf.h:

  • 0: Info Page
  • 1: Hard-coded

If IEEE_ADDR_CONF_HARDCODED is defined as 1, the IEEE address will take its value from the IEEE_ADDR_CONF_ADDRESS define. If IEEE_ADDR_CONF_HARDCODED is defined as 0, the IEEE address can come from either the primary or secondary location in the Info Page. To use the secondary address, define IEEE_ADDR_CONF_USE_SECONDARY_LOCATION as 1.

Additionally, you can override the IEEE's 2 LSBs, by using the NODEID make variable. The value of NODEID will become the value of the IEEE_ADDR_NODE_ID pre-processor define. If NODEID is not defined, IEEE_ADDR_NODE_ID will not get defined either. For example:

make NODEID=0x79ab

This will result in the 2 last bytes of the IEEE address getting set to 0x79 0xAB

Note: Some early production devices do not have am IEEE address written on the Info Page. For those devices, using value 0 above will result in a Rime address of all 0xFFs. If your device is in this category, define IEEE_ADDR_CONF_HARDCODED to 1 and specify NODEID to differentiate between devices.

Scripted multi-image builds

You can build multiple nodes with different NODEIDs sequentially. The only platform file relying on the value of NODEID (or more accurately IEEE_ADDR_NODE_ID) is ieee-addr.c, which will get recompiled at each build invocation. As a result, the build system can be scripted to build multiple firmware images, each one with a different MAC address. Bear in mind that, if you choose to do such scripting, you will need to make a copy of each firmware before invoking the next build, since each new image will overwrite the previous one. Thus, for example, you could do something like this:

for image in 1 2 3 4; do make cc2538-demo NODEID=$image && \
cp cc2538-demo.cc2538dk cc2538-demo-$image.cc2538dk; done

Which would build cc2538-demo-1.cc2538dk, cc2538-demo-2.cc2538dk etc

As discussed above, only ieee-addr.c will get recompiled for every build. Thus, if you start relying on the value of IEEE_ADDR_NODE_ID in other code modules, this trick will not work off-the-shelf. In a scenario like that, you would have to modify your script to touch those code modules between every build. For instance, if you are using an imaginary foo.c which needs to see changes to NODEID, the script above could be modified like so:

for image in 1 2 3 4; do make cc2538-demo NODEID=$image && \
cp cc2538-demo.cc2538dk cc2538-demo-$image.cc2538dk && \
touch foo.c; done

Build a 6LoWPAN Testbed

Once you are familiar with the basics, get a mini 6LoWPAN testbed.

Start by building a border router from examples/ipv6/rpl-border-router

  • Turn on debugging output by changing #define DEBUG DEBUG_NONE to #define DEBUG DEBUG_PRINT in border-router.c.

  • The border router's configuration (project-conf.h), sets the maximum size of the uIP buffer (UIP_CONF_BUFFER_SIZE). This is a bit restrictive for this platform: we can afford to allocate more memory of we want to. It's not necessary, but feel free to remove the lines below from project-conf.h, allowing the platform to use its own default value.

    #define UIP_CONF_BUFFER_SIZE    140
  • make TARGET=cc2538dk

  • Flash your device with border-router.cc2538dk or border-router.bin.

  • Connect device to Linux or OS X over its XDS port.

  • cd $(CONTIKI)/tools

  • make tunslip6

  • sudo $(CONTIKI)/tools/tunslip6 -s /dev/<device> fd00::1/64

  • The router will print its own IPv6 address. Use it below.

    Got configuration message of type P
    Setting prefix fd00::
    created a new RPL dag
    Server IPv6 addresses:
  • ping6 <address>

  • curl -g "http://[<address-inside-the-brackets>]" and the border router will serve you a web-page. Try from a browser too.

Afterwards, build RPL nodes in examples/cc2538dk/udp-ipv6-echo-server

  • If you are not reading node MAC addresses from the Info Page, make sure you assign a new MAC address for each node by passing NODEID=xyz to the make command line, as discussed in an earlier section.
  • make (or make NODEID=xyz). You don't need to specify TARGET= as this is saved in
  • Flash device with udp-echo-server.cc2538dk or .bin.
  • If you want to see console output, connect the device to a PC over its XDS port. You don't need to do that though, this example will work on 'headless' nodes. This may be a good chance to try out your BB, if you have one.
  • Repeat for more nodes, each one with a new NODEID if necessary.

More things to play around with

  • Feel free to throw some webservers in the mix. In examples/webserver-ipv6, run make TARGET=cc2538dk NODEID=<value>
  • ping6 and netcat the RPL nodes (the echo server listens on UDP 3000): nc -6u <address> 3000
  • Retrieve a webpage from a node. Use curl as above, or you can wget or you can fire up a browser and navigate to the websever's address.

Build a Sniffer - Live Traffic Capture with Wireshark

There is a sniffer example in examples/sensniff/

Diverging from platform defaults, this example configures the UART to use a baud rate of 460800. The reason is that sniffers operating at 115200 are liable to corrupt frames. This is almost certain to occur when sniffing a ContikiMAC-based deployment. See more details on how to configure UART baud rates in the "Advanced Topics" section.

Once you have built it and flashed your device, download and run sensniff on your PC (Linux or OS X). Get it from:

Instructions on what to do with sensniff are in its README. Make sure to set the -b command line parameter correctly to match the sniffer's UART baud rate. Lastly, bear in mind that Host-to-Peripheral commands will not work with the CC2538 at this stage.

Mix & Match with CC2530s

Every aspect of the CC2538 port is interoprable with the existing CC2530 port. Same 6LoWPAN prefix, same .15.4 channel and PAN ID etc. Thus, you can throw in CC2530s at will, for as long as you are using NullRDC. For instance, you can have a CC2531 border router with SmartRF06 + CC2538 EMs as RPL nodes. Or you can have a CC2538 border router with SmartRF05 + CC2530EM RPL nodes etc.

If you want to add CC2530s to the network, make sure you have followed the CC2530 how-to on the main contiki wiki:

Advanced Topics

The platform's functionality can be customised by tweaking the various configuration directives in platform/cc2538dk/contiki-conf.h. Bear in mind that defines specified in contiki-conf.h can be over-written by defines specified in project-conf.h, which is a file commonly encountered in example directories.

Thus, if you want to modify the platform's default behaviour, change values in contiki-conf.h. If you want to configure custom behaviour for a specific example, modify this example's project-conf.h.

N.B. Some defines in contiki-conf.h are not meant to be modified.

Switching between UART and USB (CDC-ACM)

By default, everything is configured to use the UART (stdio, border router's SLIP, sniffer's output stream). If you want to change this, these are the relevant lines in contiki-conf.h (0: UART, 1: USB):

#define SLIP_ARCH_CONF_USB          0 /** SLIP over UART by default */
#define DBG_CONF_USB                0 /** All debugging over UART by default */

You can multiplex things (for instance, SLIP as well as debugging over USB or SLIP over USB but debugging over UART and other combinations).

Selecting UART0 and/or UART1

By default, everything is configured to use the UART0 (stdio, border router's SLIP, sniffer's output stream). If you want to change this, these are the relevant lines in contiki-conf.h (0: UART0, 1: UART1):

#define SERIAL_LINE_CONF_UART       0
#define SLIP_ARCH_CONF_UART         0
#define DBG_CONF_UART               0
#define UART1_CONF_UART             0

A single UART is available on CC2538DK, so all the configuration values above should be the same (i.e. either all 0 or all 1), but 0 and 1 could be mixed for other CC2538-based platforms supporting 2 UARTs.

The chosen UARTs must have their ports and pins defined in board.h:

#define UART0_RX_PORT            GPIO_A_NUM
#define UART0_RX_PIN             0
#define UART0_TX_PORT            GPIO_A_NUM
#define UART0_TX_PIN             1

Only the UART ports and pins implemented on the board can be defined.

UART Baud Rate

By default, the CC2538 UART is configured with a baud rate of 115200. It is easy to increase this to 230400 by changing the value of UART0_CONF_BAUD_RATE or UART1_CONF_BAUD_RATE in contiki-conf.h or project-conf.h, according to the UART instance used.

#define UART0_CONF_BAUD_RATE 230400
#define UART1_CONF_BAUD_RATE 230400


Transfers between RAM and the RF and USB will be conducted with DMA. If for whatever reason you wish to disable this, here are the relevant configuration lines.

#define USB_ARCH_CONF_DMA                    1
#define CC2538_RF_CONF_TX_USE_DMA            1
#define CC2538_RF_CONF_RX_USE_DMA            1

Low-Power Modes

The CC2538 port supports power modes for low energy consumption. The SoC will enter a low power mode as part of the main loop when there are no more events to service.

LPM support can be disabled in its entirety by setting LPM_CONF_ENABLE to 0 in contiki-conf.h or project-conf.h.

NOTE: If you are using PG2 version of the Evaluation Module, the SoC will refuse to enter Power Modes 1+ if the debugger is connected and will always enter PM0 regardless of configuration. In order to get real low power mode functionality, make sure the debugger is disconnected. The Battery Board is ideal to test this.

The Low-Power module uses a simple heuristic to determine the best power mode, depending on anticipated Deep Sleep duration and the state of various peripherals.

In a nutshell, the algorithm first answers the following questions:

  • Is the RF off?
  • Are all registered peripherals permitting PM1+?
  • Is the Sleep Timer scheduled to fire an interrupt?

If the answer to any of the above question is "No", the SoC will enter PM0. If the answer to all questions is "Yes", the SoC will enter one of PMs 0/1/2 depending on the expected Deep Sleep duration and subject to user configuration and application requirements.

At runtime, the application may enable/disable some Power Modes by making calls to lpm_set_max_pm(). For example, to avoid PM2 an application could call lpm_set_max_pm(1). Subsequently, to re-enable PM2 the application would call lpm_set_max_pm(2).

The LPM module can be configured with a hard maximum permitted power mode.

#define LPM_CONF_MAX_PM        N

Where N corresponds to the PM number. Supported values are 0, 1, 2. PM3 is not supported. Thus, if the value of the define is 1, the SoC will only ever enter PMs 0 or 1 but never 2 and so on.

The configuration directive LPM_CONF_MAX_PM sets a hard upper boundary. For instance, if LPM_CONF_MAX_PM is defined as 1, calls to lpm_set_max_pm() can only enable/disable PM1. In this scenario, PM2 can not be enabled at runtime.

When setting LPM_CONF_MAX_PM to 0 or 1, the entire SRAM will be available. Crucially, when value 2 is used the linker will automatically stop using the SoC's SRAM non-retention area, resulting in a total available RAM of 16MB instead of 32MB.

LPM and Duty Cycling Driver

LPM is highly related to the operations of the Radio Duty Cycling (RDC) driver of the Contiki network stack and will work correctly with ContikiMAC and NullRDC.

  • With ContikiMAC, PMs 0/1/2 are supported subject to user configuration.
  • When NullRDC is in use, the radio will be always on. As a result, the algorithm discussed above will always choose PM0 and will never attempt to drop to PM1/2.

Build headless nodes

It is possible to turn off all character I/O for nodes not connected to a PC. Doing this will entirely disable the UART as well as the USB controller, preserving energy in the long term. The define used to achieve this is (1: Quiet, 0: Normal output):

#define CC2538_CONF_QUIET      0

Setting this define to 1 will automatically set the following to 0:


Code Size Optimisations

The build system currently uses optimization level -Os, which is controlled indirectly through the value of the SMALL make variable. This value can be overridden by example makefiles, or it can be changed directly in platform/cc2538dk/Makefile.cc2538dk.

Historically, the -Os flag has caused problems with some toolchains. If you are using one of the toolchains documented in this README, you should be able to use it without issues. If for whatever reason you do come across problems, try setting SMALL=0 or replacing -Os with -O2 in cpu/cc2538/Makefile.cc2538.

Doxygen Documentation

This port's code has been documented with doxygen. To build the documentation, navigate to $(CONTIKI)/doc and run make. This will build the entire contiki documentation and may take a while.

If you want to build this platform's documentation only and skip the remaining platforms, run this:

make basedirs="platform/cc2538dk core cpu/cc2538 examples/cc2538dk"

Once you've built the docs, open $(CONTIKI)/doc/html/index.html and enjoy.

Other Versions of this Guide

If you prefer this guide in other formats, use the excellent pandoc to convert it.

  • pdf: pandoc -s --toc -o README.pdf
  • html: pandoc -s --toc -o README.html

More Reading

  1. SmartRF06 Evaluation Board User's Guide, (SWRU321)
  2. CC2538 System-on-Chip Solution for 2.4-GHz IEEE 802.15.4 and ZigBee®/ZigBee IP® Applications, (SWRU319B)