These notes describe how to program a SAMD21 breakout board with the Atmel ICE, using the command line on Mac OS X or Linux Debian, without Atmel Studio or any other IDE, or even any hardware abstraction library. This updates a previous blog entry with more details and minor corrections.

To illustrate SAMD21 programming, we will use a Sparkfun SAMD21 dev breakout board as our target. It features the popular Atmel SAMD21G18 ARM Cortex M0 and our examples should work with little or no modifications on similar platforms such as the Arduino M0 or the TAU featured in the previous version of this article.

In terms of hardware, this article also assumes you own an Atmel ICE programmer.

You will need to install some software packages to program your SAMD21 board. On Debian Linux, you will use your package manager (e.g. 'aptitude'). On the Mac, we suggest using homebrew to install the different tools described here.

Step 1: Install the programming header

To program a SAMD21 board with OpenOCD you will need to connect that programmer to your board with the SWD header. On the Sparkfun SAMD21 breakout board this SWD header is present but unpopulated as shown in the picture below, on the left. You will need to fit a 2x5 pin 1.27mm male header, as shown in the picture below, on the right.

ARM Cortex SWD connector

You don't necessarily need to solder the header to the board: just plugging the header is usually good enough to establish electrical contact. You will then plug the corresponding connector on the Atmel ICE. Since the connector is not keyed, there are two possible ways to plug the Atmel ICE on the board. If you try one way and it doesn't work, just flip the connector around!

Step 2: Install the C compiler for ARM

The ARM developer tools (arm-none-eabi) need to be installed your system.

On the Mac, with homebrew, it boils down to one command:

$ brew cask install gcc-arm-embedded

On a Linux, with a Debian style OS, you will need to refer to your package manager (apt-get or aptitude).

Step 3: Install OpenOCD

Installing OpenOCD on the Mac is also a one-liner:

$ brew install openocd

On Debian Linux, a similar aptitude install openocd will do the trick.

Once installed, the next step is to set up OpenOCD correctly. For this purpose create a file called openocf.cfg, with the following content:

# Atmel-ICE JTAG/SWD in-circuit debugger.
interface cmsis-dap

# Chip info 
set CHIPNAME at91samd21g18
source [find target/at91samdXX.cfg]

You should change the value at91samd21g18 to match the microcontroller you are using.

You can test your openocd.cfg file by simply typing openocd. You should get an output similar to this:

$ openocd 
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
none separate
adapter speed: 400 kHz
cortex_m reset_config sysresetreq
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: JTAG Supported
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : CMSIS-DAP: FW Version = 01.26.0081
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 1 TDO = 1 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : clock speed 400 kHz
in procedure 'init' 
in procedure 'ocd_bouncer'

Now you can plug the SWD header in the board you want to program. Don't forget to power the board separately, with USB for example. If you launch OpenOCD again, you should get the following output:

$ openocd 
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
none separate
adapter speed: 400 kHz
cortex_m reset_config sysresetreq
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: JTAG Supported
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : CMSIS-DAP: FW Version = 01.26.0081
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 1 TDO = 1 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : clock speed 400 kHz
Info : SWD DPIDR 0x0bc11477
Info : at91samd21g18.cpu: hardware has 4 breakpoints, 2 watchpoints

If this fails, you may have plugged the SWD connector backward: just flip it around.

Now, while OpenOCD is still running, we can test that gdb works by typing arm-none-eabi-gdb -iex "target extended-remote localhost:3333" in another terminal window:

$ arm-none-eabi-gdb -iex "target extended-remote localhost:3333"
GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20151217-cvs
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-apple-darwin10 --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
Remote debugging using localhost:3333
0x00000168 in ?? ()
(gdb)

If you got this far, your OpenOCD is complete.

The Sparkfun SAMD21 breakout board has an LED on the pin labeled 'D13', which maps to the GPIO PA17: the 17th io port on port A. We will make it blink to test our setup. If you have another board it might have an LED on a different PIN or none at all, you will need to adapt the code below (e.g. the SAMD21 Xplained pro has an LED on PB30).

/*
 * main.c
 */
#include <samd21.h>

static void delay(int n)
{
    int i;

    for (;n >0; n--)
    {
        for (i=0;i<100;i++)
            __asm("nop");
    }
}

int main(void)
{
    REG_PORT_DIR0 |= (1<<17);
    while (1)
    {
        REG_PORT_OUT0 &= ~(1<<17);
        delay(500);
        REG_PORT_OUT0 |= (1<<17);
        delay(500);
    }
}

In the code above, blinking the LED PA17 is achieved by setting and clearing the 17th bit of a specific register called REG_PORT_DIR0, which corresponds to port A on the SAMD21. There are in fact several ways to achieve the same result on the SAMD21, we are just showing one for simplicity.

To compile this file, you will need a set of headers provided by Microchip/Atmel. First, download the Atmel Software Framework (ASF) from http://www.microchip.com/avr-support/advanced-software-framework-(asf). In fact, you will only be using a very small subset of this big framework, and you will be able to delete a large part of this framework if needed later.

When uncompressing the file, you'll get a directory named xdk-asf-3.37/ or something similar depending on the version you downloaded. Let's name ASF_ROOT the absolute path corresponding to that directory (e.g. set ASF_ROOT="/Users/pannetra/Src/xdk-asf-3.37").

Go to the directory where you put the openocd.cfg file and perform the following actions:

$ cp $ASF_ROOT/sam0/utils/cmsis/samd21/source/gcc/startup_samd21.c .

Now the following steps will need a small customization depending on the microcontroller you have. In the case of our Sparkfun board, it's a SAMD21G18A:

$ cp $ASF_ROOT/sam0/utils/linker_scripts/samd21/gcc/samd21g18a_flash.ld .

If you have a different microcontroller from the SAMD21G18A, you should change the file name samd21g18a_flash.ld to match your microcontroller.

LDSCRIPT = samd21g18a_flash.ld
PTYPE=__SAMD21G18A__

CC=arm-none-eabi-gcc
LD=arm-none-eabi-gcc
AR=arm-none-eabi-ar
AS=arm-none-eabi-as

ELF=$(notdir $(CURDIR)).elf

ASF_ROOT=../../Src/xdk-asf-3.37

INCLUDES= \
          sam0/utils/cmsis/samd21/include \
          sam0/utils/cmsis/samd21/source \
          thirdparty/CMSIS/Include \
          thirdparty/CMSIS/Lib/GCC 

OBJS = startup_samd21.o main.o 

LDFLAGS+= -T$(LDSCRIPT) -mthumb -mcpu=cortex-m0 -Wl,--gc-sections
CFLAGS+= -mcpu=cortex-m0 -mthumb -g
CFLAGS+= $(INCLUDES:%=-I $(ASF_ROOT)/%) -I .
CFLAGS+= -D$(PTYPE)
CFLAGS+=-pipe -Wall -Wstrict-prototypes -Wmissing-prototypes -Werror-implicit-function-declaration \
-Wpointer-arith -std=gnu99 -fno-strict-aliasing -ffunction-sections -fdata-sections \
-Wchar-subscripts -Wcomment -Wformat=2 -Wimplicit-int -Wmain -Wparentheses -Wsequence-point \
-Wreturn-type -Wswitch -Wtrigraphs -Wunused -Wuninitialized -Wunknown-pragmas -Wfloat-equal \
-Wundef -Wshadow -Wbad-function-cast -Wwrite-strings -Wsign-compare -Waggregate-return \
-Wmissing-declarations -Wformat -Wmissing-format-attribute -Wno-deprecated-declarations \
-Wpacked -Wredundant-decls -Wnested-externs -Wlong-long -Wunreachable-code -Wcast-align \
--param max-inline-insns-single=500

$(ELF):     $(OBJS)
        $(LD) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)

# compile and generate dependency info

%.o:    %.c
        $(CC) -c $(CFLAGS) $< -o $@
        $(CC) -MM $(CFLAGS) $< > $*.d

%.o:    %.s
        $(AS) $< -o $@

info:       
        @echo CFLAGS=$(CFLAGS)
        @echo OBJS=$(OBJS)

clean:
        rm -f $(OBJS) $(OBJS:.o=.d) $(ELF) $(CLEANOTHER)

debug:  $(ELF)
        arm-none-eabi-gdb -iex "target extended-remote localhost:3333" $(ELF)

-include    $(OBJS:.o=.d)

The above Makefile is derived from the great work of Geoffrey Brown on the STM32.

If you have a different microcontroller from the SAMD21G18A, you need to change the following two lines in the Makefile: LDSCRIPT = samd21g18a_flash.ld and PTYPE=__SAMD21G18A__, replacing references to the samd21g18a with your own.

Step 5: Compiling and running the code

If you followed all the steps above, you should have the following files in your current directory:

  • Makefile
  • openocd.cfg
  • startup_samd21.c
  • main.c
  • samd21g18a_flash.ld

We will check that the code compiles as expected.

$ make 

Now, you are ready to run the program. Connect the Atmel ICE and power your board. Launch OpenOCD as shown previously. In a separate terminal window, we will use gdb to load the program and run it:

$ make debug
arm-none-eabi-gdb -iex "target extended-remote localhost:3333" OPENOCD_TEST.elf
GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20151217-cvs
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-apple-darwin10 --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Remote debugging using localhost:3333
0x00000168 in ?? ()
Reading symbols from OPENOCD_TEST.elf...done.
(gdb) load
Loading section .text, size 0x488 lma 0x0
Loading section .relocate, size 0x428 lma 0x488
Start address 0x0, load size 2224
Transfer rate: 2 KB/sec, 1112 bytes/write.
(gdb) monitor reset halt
target halted due to debug-request, current mode: Thread 
xPSR: 0x81000000 pc: 0x00000270 msp: 0x20001418
(gdb) c
Continuing.

Note the gdb commands:

  • load uploads the code to the SAMD21
  • monitor reset halt resets the SAMD21
  • c starts running the code (c is short of continue)

At this point your LED should blink!