cc2538dk: TI cc2538 development kit

This guide’s aim is to help you start using Contiki-NG 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).

  • 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-NG’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-NG, you will need the following:

  • A toolchain to compile Contiki-NG 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-NG   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.

Toolchain Installation

The toolchain used to build Contiki-NG is arm-gcc, also used by other arm-based Contiki-NG ports.

If you use the docker image or the vagrant image, this will be pre-installed for you. Otherwise, depending on your system, please follow the respective installation instructions (native Linux / native mac OS).


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-NG 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 SmartRF Flash Programmer 2. You can use the .bin file or the .hex file. If you use the .hex file, you do not need to take any further action. However if you are using the .bin, you need to specify the correct address on flash to start writing the firmware. The default value in SmartRF flash programmer is 0x00200000. If you are building with CFS support, you will need to change this to 0x00202000. If you are building without CFS, leave it as 0x00200000. If you have changed the configuration of CFS (e.g. to increase its size), you will need to adjust the start address location accordingly. CFS support is currently enabled by default, so if you are having trouble getting your device to work, check the start address or programme your device using the .hex file.

    • 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

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 DEBUG = 1 to the project Makefile and rebuild by running make clean cc2538-demo.lst.

Node IEEE and 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. 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 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. 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.

Advanced Topics

The platform’s functionality can be customized by tweaking the various configuration directives in platform/cc2538dk/contiki-conf.h. See how the Contiki-NG configuration system works at doc:configuration-system.

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.

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 arch/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 page, 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 arch/cpu/cc2538/Makefile.cc2538.

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)