NOTE: an updated version of this article is available here

These notes describe how I program a SAMD21E17 breakout board with the Atmel ICE, using the command line on Mac OS X, without Atmel Sudio or any other IDE. I assume that the almost exact same procedure can be applied to linux.

The board I used is a TAU (see http://rabidprototypes.com/product/tau/), but the procedure would be the same for the more popular SAMD21G18 that can be found on the Arduino Zero for example.

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

OpenOCD

On the Mac, I used brew to install OpenOCD (see https://brew.sh/):

$ brew install openocd

The first 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
cmsis_dap_vid_pid 0x03eb 0x2141

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

You should change the value at91samd21e17 to match the microcontroller you are using (e.g. at91sam21g18).

If you have several Atmel-ICE debuggers connected to your machine, you need to distinguish them by their serial number in the openocd.cfg file by adding the following line with your own serial number substitued:

cmsis_dap_serial J418000123456

On a MAC, you can find the serial number by searching through the output of the command system_profiler SPUSBDataType and on linux you would look through the output of lsusb -v.

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

$ openocd 
Open On-Chip Debugger 0.9.0 (2015-11-15-05:39)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : only one transport option; autoselect 'swd'
adapter speed: 500 kHz
adapter_nsrst_delay: 100
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 500 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!). If you launch OpenOCD again, you should get the following output:

$ openocd 
Open On-Chip Debugger 0.9.0 (2015-11-15-05:39)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : only one transport option; autoselect 'swd'
adapter speed: 500 kHz
adapter_nsrst_delay: 100
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 500 kHz
Info : SWD IDCODE 0x0bc11477
Info : at91samd21e17.cpu: hardware has 4 breakpoints, 2 watchpoints

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

$ 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 TAU has an LED on Pin 25, which maps to the GPIO PA27: the 27th 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 a led on PB30).

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

void delay(int n)
{
    int i;

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

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

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.atmel.com/tools/avrsoftwareframework.aspx

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

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

$ cp -R $ASF_ROOT/sam0/utils/cmsis/samd21/include ./include
$ cp -R $ASF_ROOT/thirdparty/CMSIS/Include ./cmsis
$ cp $ASF_ROOT/sam0/utils/cmsis/samd21/source/system_samd21.h .
$ cp $ASF_ROOT/sam0/utils/cmsis/samd21/source/gcc/startup_samd21.c .
$ cp $ASF_ROOT/sam0/utils/cmsis/samd21/source/gcc/startup_samd21.h .

Now the following steps will need a small customisation depending on the microcontroller you have. In my case it's a SAMD21E17A:

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

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

LDSCRIPT = samd21e17a_flash.ld
STARTUP = startup_samd21.o system_samd21.o
PTYPE=__SAMD21E17A__

OBJS=$(STARTUP) main.o

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

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

LDFLAGS+= -T$(LDSCRIPT) -mthumb -mcpu=cortex-m0 -Wl,--gc-sections
CFLAGS+= -mcpu=cortex-m0 -mthumb -g
CFLAGS+= -I ./include -I ./cmsis -I .
CFLAGS+= -D$(PTYPE)

$(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 $@

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

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

# pull in dependencies

-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 SAMD21E17A, you need to change the following two lines in the Makefile: LDSCRIPT = samd21e17a_flash.ld and PTYPE=__SAMD21E17A__, replacing references to the samd21e17a with your own.

Compiling and running the code

First we will check that the code compiles as expected.

$ make 
arm-none-eabi-gcc -c -mcpu=cortex-m0 -mthumb -g -I ./include -I ./cmsis -I . -D__SAMD21E17A__ startup_samd21.c -o startup_samd21.o
arm-none-eabi-gcc -MM -mcpu=cortex-m0 -mthumb -g -I ./include -I ./cmsis -I . -D__SAMD21E17A__ startup_samd21.c > startup_samd21.d
arm-none-eabi-gcc -c -mcpu=cortex-m0 -mthumb -g -I ./include -I ./cmsis -I . -D__SAMD21E17A__ system_samd21.c -o system_samd21.o
arm-none-eabi-gcc -MM -mcpu=cortex-m0 -mthumb -g -I ./include -I ./cmsis -I . -D__SAMD21E17A__ system_samd21.c > system_samd21.d
arm-none-eabi-gcc -c -mcpu=cortex-m0 -mthumb -g -I ./include -I ./cmsis -I . -D__SAMD21E17A__ main.c -o main.o
arm-none-eabi-gcc -MM -mcpu=cortex-m0 -mthumb -g -I ./include -I ./cmsis -I . -D__SAMD21E17A__ main.c > main.d
arm-none-eabi-gcc -Tsamd21e17a_flash.ld -mthumb -mcpu=cortex-m0 -Wl,--gc-sections -o OPENOCD_TEST.elf startup_samd21.o system_samd21.o main.o 

Now, you are ready to run the program. Connect the OpenOCD and power your board. 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
(gdb) monitor halt
target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x81000000 pc: 0x00000270 msp: 0x20001418
(gdb) c
Continuing.

Note the gdb commands (load, monitor reset, monitor halt, c...).

Let the LED blink!

The example above does not include the HAL (Hardware Abstraction Layer), which is yet part of another directory in the ASF. A topic for another post I guess.