Introduction

When doing small projects, an 8-bit AVR microcontroller or a Microchip PIC will usually suffice. However, as soon as you target larget projects (for example a MP3 decoder in software or advanced Ethernet processing), the capacity of those small devices soon runs out. A processor familiy which is often overlooked by hobbyists is the ARM Cortex-M3. Those are manufactured by TI, NXP and ST and offer a vast arary of internal peripherals while offering the power of a 32-bit processor to quite low cost. Since I have the Stellaris DK-LM3S9B96 development kit from TI, I tried getting the thing running using Linux and tried out some cool stuff with it using entirely open source tools. Here's how it goes.

Toolchain

As a toolchain for an ARM processor, the native ARM GCC comes to mind immediately. We're going to do this step by step, don't worry.

Preperation

First, choose where you want to install it and set those two variables which will make it easier for you later on. You can also set BLD_PREFIX to somewhere within your home directory, i.e. "${HOME}/bin/${BLD_TARGET}" or something, whichever you like. If you want to install system-wide (root privileges needed, obviously), do it as below:

joequad joe [~/CortexM3]: export BLD_TARGET="arm-elf"
joequad joe [~/CortexM3]: export BLD_RAWPREFIX="/usr/local"
joequad joe [~/CortexM3]: export BLD_PREFIX="${BLD_RAWPREFIX}/${BLD_TARGET}"
joequad joe [~/CortexM3]: export PATH=${PATH}:${BLD_PREFIX}/bin

Or if you want to install locally only do something like:

joequad joe [~/CortexM3]: export BLD_TARGET="arm-elf"
joequad joe [~/CortexM3]: export BLD_RAWPREFIX="${HOME}/bin"
joequad joe [~/CortexM3]: export BLD_PREFIX="${BLD_RAWPREFIX}/${BLD_TARGET}"
joequad joe [~/CortexM3]: export PATH=${PATH}:${BLD_PREFIX}/bin

Which I will use from here on. To test your settings, you can do:

joequad joe [~/CortexM3]: echo "Will install ${BLD_TARGET} into ${BLD_PREFIX}"
Will install arm-elf into /home/joe/bin/arm-elf
joequad joe [~/CortexM3]: env | grep BLD_
BLD_TARGET=arm-elf
BLD_PREFIX=/home/joe/bin/arm-elf
BLD_RAWPREFIX=/home/joe/bin

1. The Assembler (binutils)

First download the binutils package from here. I chose the latest stable version, 2.20, available at http://ftp.gnu.org/gnu/binutils/binutils-2.20.tar.gz.

joequad joe [~/CortexM3]: wget http://ftp.gnu.org/gnu/binutils/binutils-2.20.tar.gz
[...]
joequad joe [~/CortexM3]: tar xfz binutils-2.20.tar.gz
joequad joe [~/CortexM3]: cd binutils-2.20
joequad joe [~/CortexM3/binutils-2.20]: ./configure --prefix=${BLD_PREFIX} --target=${BLD_TARGET} --disable-werror --disable-interwork
[...]
joequad joe [~/CortexM3/binutils-2.20]: make -j4
[...]
joequad joe [~/CortexM3/binutils-2.20]: make install
[...]
joequad joe [~/CortexM3/binutils-2.20]: cd ..

You shold then already have a working assembler, which you will need to build a compiler. To check that, do:

joequad joe [~/CortexM3]: ls -lh ${BLD_PREFIX}/bin
total 45M
-rwxr-xr-x 1 joe users 2,9M 10. Jul 11:39 arm-elf-addr2line
-rwxr-xr-x 2 joe users 3,1M 10. Jul 11:39 arm-elf-ar
-rwxr-xr-x 2 joe users 4,6M 10. Jul 11:39 arm-elf-as
-rwxr-xr-x 1 joe users 2,9M 10. Jul 11:39 arm-elf-c++filt
-rwxr-xr-x 1 joe users 3,4M 10. Jul 11:39 arm-elf-gprof
-rwxr-xr-x 2 joe users 3,9M 10. Jul 11:40 arm-elf-ld
-rwxr-xr-x 2 joe users 3,0M 10. Jul 11:39 arm-elf-nm
-rwxr-xr-x 2 joe users 3,7M 10. Jul 11:39 arm-elf-objcopy
-rwxr-xr-x 2 joe users 4,1M 10. Jul 11:39 arm-elf-objdump
-rwxr-xr-x 2 joe users 3,1M 10. Jul 11:39 arm-elf-ranlib
-rwxr-xr-x 1 joe users 811K 10. Jul 11:39 arm-elf-readelf
-rwxr-xr-x 1 joe users 3,0M 10. Jul 11:39 arm-elf-size
-rwxr-xr-x 1 joe users 2,9M 10. Jul 11:39 arm-elf-strings
-rwxr-xr-x 2 joe users 3,7M 10. Jul 11:39 arm-elf-strip

2. The Compiler (gcc)

Again, download the gcc package from here. I chose 4.4.3, available at http://ftp.gnu.org/gnu/gcc/gcc-4.4.3/gcc-4.4.3.tar.gz. GCC cannot be built in its own directory, so just follow the steps below and all will be fine.

joequad joe [~/CortexM3]: mkdir gcc
joequad joe [~/CortexM3]: cd gcc
joequad joe [~/CortexM3/gcc]: tar xfz ../gcc-4.4.3.tar.gz
joequad joe [~/CortexM3/gcc]: gcc-4.4.3/configure --prefix=${BLD_PREFIX} --target=${BLD_TARGET} 
       --disable-nls --disable-shared --disable-threads --with-gnu-ld --with-gnu-as --disable-multilib 
       --disable-libssp --disable-libmudflap --disable-libgomp --with-dwarf2 --with-newlib -v 
       --disable-werror --with-cpu=cortex-m3 --with-tune=cortex-m3 --with-mode=thumb --enable-target-optspace
       --with-float=soft --enable-languages=c --disable-interwork
[...]
joequad joe [~/CortexM3/gcc]: make -j4
[...]
joequad joe [~/CortexM3/gcc]: make install
[...]
joequad joe [~/CortexM3/gcc]: cd ..

After that last step, you should already have a fully functioning ARM C compiler. However, you still have no libc. This means you can program, but you cannot use printf, strlen, memcpy or anything else. Let's try it out first. Create a file (or download it here) which does not rely on the libc and save it as example.c:

#define PORT_FOO (*((volatile long*)0x11223344))
#define PORT_BAR (*((volatile long*)0xc0ffee))

int main() {
    int i;
    for (i = 0; i < 128; i++) {
        PORT_FOO = i;
        PORT_BAR = (~i) & 0xc3;
    }
    if (PORT_FOO & PORT_BAR) {
        PORT_FOO = 0x77;
    } else {
        PORT_BAR = 0x99;
    }
    return 0;
}
joequad joe [~/CortexM3/tmp]: ${BLD_PREFIX}/bin/arm-elf-gcc -nostdlib -O2 -Wall -mtune=cortex-m3 -o example example.c
/home/joe/bin/arm-elf/lib/gcc/arm-elf/4.4.3/../../../../arm-elf/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000008000
joequad joe [~/CortexM3/tmp]: ${BLD_PREFIX}/arm-elf/bin/objdump -d example

example:     file format elf32-littlearm


Disassembly of section .text:

00008000 
: 8000: b410 push {r4} 8002: f64f 7cee movw ip, #65518 ; 0xffee 8006: f243 3444 movw r4, #13124 ; 0x3344 800a: 2300 movs r3, #0 800c: f2c1 1422 movt r4, #4386 ; 0x1122 8010: f2c0 0cc0 movt ip, #192 ; 0xc0 8014: 43d8 mvns r0, r3 8016: 6023 str r3, [r4, #0] 8018: 3301 adds r3, #1 801a: f243 3144 movw r1, #13124 ; 0x3344 801e: f000 00c3 and.w r0, r0, #195 ; 0xc3 8022: f64f 72ee movw r2, #65518 ; 0xffee 8026: 2b80 cmp r3, #128 ; 0x80 8028: f2c1 1122 movt r1, #4386 ; 0x1122 802c: f2c0 02c0 movt r2, #192 ; 0xc0 8030: f8cc 0000 str.w r0, [ip] 8034: d1ee bne.n 8014 8036: 680b ldr r3, [r1, #0] 8038: 6810 ldr r0, [r2, #0] 803a: 4218 tst r0, r3 803c: d104 bne.n 8048 803e: 2399 movs r3, #153 ; 0x99 8040: 6013 str r3, [r2, #0] 8042: 2000 movs r0, #0 8044: bc10 pop {r4} 8046: 4770 bx lr 8048: 2377 movs r3, #119 ; 0x77 804a: 600b str r3, [r1, #0] 804c: e7f9 b.n 8042 804e: bf00 nop

So we get a warning that the gcc does not know where to place the code (obviously, it doesn't), but it compiles it just fine. The result already looks great!

3. The C-Library (newlib)

For a C library we will be using newlib. Again, retrieve it from here. I chose 1.18.0 available at ftp://sources.redhat.com/pub/newlib/newlib-1.18.0.tar.gz. This one also cannot be built in it's own directory, so be careful here.

joequad joe [~/CortexM3]: mkdir newlib
joequad joe [~/CortexM3]: cd newlib
joequad joe [~/CortexM3/newlib]: tar xfz ../newlib-1.18.0.tar.gz
joequad joe [~/CortexM3/newlib]: newlib-1.18.0/configure --target=${BLD_TARGET} --prefix=${BLD_PREFIX} --disable-multilib --disable-newlib-supplied-syscalls --disable-interwork
[...]
joequad joe [~/CortexM3/newlib]: make -j4
[...]
joequad joe [~/CortexM3/newlib]: make install
[...]
joequad joe [~/CortexM3/newlib]: cd ..

4. The debugger (gdb)

Everyone who writes code, writes bugs. To minimize the amount of bugs in your code, using a debugger is a good idea - who would've thought that? Retrieve it from here. I used 7.1 available at http://ftp.gnu.org/gnu/gdb/gdb-7.1.tar.gz.

joequad joe [~/CortexM3]: tar xfz gdb-7.1.tar.gz
joequad joe [~/CortexM3]: cd gdb-7.1
joequad joe [~/CortexM3/gdb-7.1]: ./configure --target=${BLD_TARGET} --prefix=${BLD_PREFIX}
[...]
joequad joe [~/CortexM3/gdb-7.1]: make -j4
[...]
joequad joe [~/CortexM3/gdb-7.1]: make install
[...]
joequad joe [~/CortexM3/gdb-7.1]: cd ..

5. The JTAG debugger/Firmware loader (openocd)

To get a firmware onto the board and for connecting arm-gdb with the board, we will use openocd. In all likelyhood, openocd will be part of your distribution already. However, you should really make sure you get a new version. For this, I will be fetching the GIT version. You should have development files of the libftdi installied (Gentoo: dev-embedded/libftdi) for this. If you do not want to get the GIT version, latest versions can be fetched from http://developer.berlios.de/project/showfiles.php?group_id=4148&release_id=17280.

joequad joe [~/CortexM3]: git clone git://openocd.git.sourceforge.net/gitroot/openocd/openocd
Initialized empty Git repository in /home/joe/CortexM3/openocd/.git/
remote: Counting objects: 33584, done.
remote: Compressing objects: 100% (13114/13114), done.
remote: Total 33584 (delta 27779), reused 24534 (delta 20368)
Receiving objects: 100% (33584/33584), 7.25 MiB | 1.04 MiB/s, done.
Resolving deltas: 100% (27779/27779), done.
joequad joe [~/CortexM3]: cd openocd
joequad joe [~/CortexM3/openocd]: ./bootstrap
+ aclocal
+ libtoolize --automake --copy
+ autoconf
+ autoheader
+ automake --gnu --add-missing --copy
configure.in:18: installing `./compile'
configure.in:26: installing `./config.guess'
configure.in:26: installing `./config.sub'
configure.in:6: installing `./install-sh'
configure.in:6: installing `./missing'
doc/Makefile.am:1: installing `doc/mdate-sh'
doc/Makefile.am:1: installing `doc/texinfo.tex'
src/Makefile.am: installing `./depcomp'
Makefile.am: installing `./INSTALL'
Bootstrap complete; you can './configure --enable-maintainer-mode ....'
joequad joe [~/CortexM3/openocd]: ./configure --enable-ft2232_libftdi --prefix=${BLD_PREFIX}
joequad joe [~/CortexM3/openocd]: make -j4
[...]
joequad joe [~/CortexM3/openocd]: make install
[...]

Playing around

Getting the Stellaris Software Examples

How you get started the fastest is with using TIs Stellaris CD which is provided with the kit. It is available from http://www.luminarymicro.com/products/dk-lm3s9b96.html (called "Development Kit CD for the DK-LM3S9B96"), the direct link is here. Once you downloaded the ZIP file and placed it in ~/CortexM3, do the following:

joequad joe [~/CortexM3]: 7z x DK-LM3S9B96-CD-562.zip

7-Zip 4.65  Copyright (c) 1999-2009 Igor Pavlov  2009-02-03
p7zip Version 4.65 (locale=de_DE.utf8,Utf16=on,HugeFiles=on,4 CPUs)

Processing archive: DK-LM3S9B96-CD-562.zip

Extracting  DK-LM3S9B96-CD-562/AUTORUN.INF
Extracting  DK-LM3S9B96-CD-562/Acrobat
Extracting  DK-LM3S9B96-CD-562/Acrobat/AdbeRdr910_en_US.exe
Extracting  DK-LM3S9B96-CD-562/Documentation
Extracting  DK-LM3S9B96-CD-562/Documentation/Application_Notes
Extracting  DK-LM3S9B96-CD-562/Documentation/Application_Notes/AN01237.pdf
Extracting  DK-LM3S9B96-CD-562/Documentation/Application_Notes/AN01239.pdf
Extracting  DK-LM3S9B96-CD-562/Documentation/Application_Notes/AN01241.pdf
Extracting  DK-LM3S9B96-CD-562/Documentation/Application_Notes/AN01243.pdf
[...]
joequad joe [~/CortexM3]: mkdir stellaris
joequad joe [~/CortexM3/stellaris]: cd stellaris
joequad joe [~/CortexM3/stellaris]: 7z x ../DK-LM3S9B96-CD-562/Tools/StellarisWare/SW-DK-LM3S9B96-6075.exe

7-Zip 4.65  Copyright (c) 1999-2009 Igor Pavlov  2009-02-03
p7zip Version 4.65 (locale=de_DE.utf8,Utf16=on,HugeFiles=on,4 CPUs)

Processing archive: ../DK-LM3S9B96-CD-562/Tools/StellarisWare/SW-DK-LM3S9B96-6075.exe

Extracting  EULA.txt
Extracting  license.html
Extracting  makedefs
Extracting  Makefile
Extracting  settings.ini
Extracting  boards
Extracting  boards/Makefile
Extracting  boards/dk-lm3s9b96
Extracting  boards/dk-lm3s9b96/cr_workspace.xml
Extracting  boards/dk-lm3s9b96/dk-lm3s9b96.eww
Extracting  boards/dk-lm3s9b96/dk-lm3s9b96.mpw
Extracting  boards/dk-lm3s9b96/dk-lm3s9b96.sgxw
Extracting  boards/dk-lm3s9b96/dk-lm3s9b96.uvmpw
Extracting  boards/dk-lm3s9b96/Makefile
Extracting  boards/dk-lm3s9b96/aes_expanded_key
Extracting  boards/dk-lm3s9b96/aes_expanded_key/aes_config_opts.h
Extracting  boards/dk-lm3s9b96/aes_expanded_key/aes_expanded_key.c
Extracting  boards/dk-lm3s9b96/aes_expanded_key/aes_expanded_key.ewd
Extracting  boards/dk-lm3s9b96/aes_expanded_key/aes_expanded_key.ewp
Extracting  boards/dk-lm3s9b96/aes_expanded_key/aes_expanded_key.icf
Extracting  boards/dk-lm3s9b96/aes_expanded_key/aes_expanded_key.ld
[...]

You should then have a working stellaris/ subdirectory. The examples from TI are made so they compile with gcc just like that, no problems at all. So let's try that out at first. Make sure that your newly compiled compiler chain is in the path:

joequad joe [~/CortexM3]: which arm-elf-gcc
/home/joe/bin/arm-elf/bin/arm-elf-gcc

If it isn't in the path, check above ("Preparation") and put it in the ${PATH} environment variable.

Now let's adapt the ~/CortexM3/stellaris/makedefs. Around line 54 sould be the prefix for the ARM compiler. Comment out the two lines above and write PREFIX=arm-elf below. Already done!

[...]
#******************************************************************************
#
# Definitions for using GCC.
#
#******************************************************************************
ifeq (${COMPILER}, gcc)

#
# Get the prefix for the tools to use.  Use arm-stellaris-eabi if it exists,
# otherwise fall back to arm-none-eabi.
#
#PREFIX=${shell type arm-stellaris-eabi-gcc > /dev/null 2>&1 && \
#         echo arm-stellaris-eabi || echo arm-none-eabi}
PREFIX=arm-elf

[...]

Then we compile the libraries first because the binaries already distributed in the package are incompatible with GCC. Don't worry, though, they are programmed quite well and will compile using GCC without even the slightest warning:

joequad joe [~/CortexM3/stellaris]: cd driverlib
joequad joe [~/CortexM3/stellaris/driverlib]: make
  CC    adc.c
  CC    can.c
  CC    comp.c
  CC    cpu.c
  CC    epi.c
  CC    ethernet.c
  CC    flash.c
  CC    gpio.c
  CC    hibernate.c
  CC    i2c.c
  CC    i2s.c
  CC    interrupt.c
  CC    mpu.c
  CC    pwm.c
  CC    qei.c
  CC    ssi.c
  CC    sysctl.c
  CC    systick.c
  CC    timer.c
  CC    uart.c
  CC    udma.c
  CC    usb.c
  CC    watchdog.c
  AR    gcc/libdriver.a
joequad joe [~/CortexM3/stellaris/driverlib]: cd ../grlib
joequad joe [~/CortexM3/stellaris/grlib]: make
[...]
  CC    string.c
  CC    widget.c
  AR    gcc/libgr.a
joequad joe [~/CortexM3/stellaris/grlib]: cd ..
joequad joe [~/CortexM3/stellaris]: cd boards/dk-lm3s9b96/hello
joequad joe [~/CortexM3/stellaris/boards/dk-lm3s9b96/hello]: make
  CC    hello.c
  CC    ../drivers/kitronix320x240x16_ssd2119_8bit.c
  CC    ../drivers/set_pinout.c
  CC    startup_gcc.c
  LD    gcc/hello.axf 
joequad joe [~/CortexM3/stellaris/boards/dk-lm3s9b96/hello]: ls -lh gcc/
total 84K
-rwx------ 1 joe users  69K 10. Jul 18:47 hello.axf
-rwx------ 1 joe users  15K 10. Jul 18:47 hello.bin
-rw------- 1 joe users  171 10. Jul 18:47 hello.d
-rw------- 1 joe users 1,6K 10. Jul 18:47 hello.o
-rw------- 1 joe users  453 10. Jul 18:47 kitronix320x240x16_ssd2119_8bit.d
-rw------- 1 joe users 6,3K 10. Jul 18:47 kitronix320x240x16_ssd2119_8bit.o
-rw------- 1 joe users  308 10. Jul 18:47 set_pinout.d
-rw------- 1 joe users 4,1K 10. Jul 18:47 set_pinout.o
-rw------- 1 joe users   33 10. Jul 18:47 startup_gcc.d
-rw------- 1 joe users 2,5K 10. Jul 18:47 startup_gcc.o

So we just compiled the "Hello World" example for the Devboard in just a few seconds!

Flashing it using OpenOCD

To use OpenOCD with your devboard, you need to create a configuration file in ${BLD_PREFIX}/share/openocd/scripts/board. I called mine dk-3s9b96.cfg which is pretty much a copy of the already existent ek-lm3s9b9x.cfg. It contains the following:

# Luminary Micro Stellaris LM3S9B9x Development Kit

source [find interface/luminary.cfg]
source [find target/lm3s9b9x.cfg]

jtag_nsrst_delay 100

# LM3S9B9x Evaluation Board has only srst
reset_config srst_only
You can then fire up OpenOCD for the first time:
joequad joe [~/CortexM3]: openocd -f ${BLD_PREFIX}/share/openocd/scripts/board/dk-3s9b96.cfg
Open On-Chip Debugger 0.4.0 (2010-07-10-13:59)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.berlios.de/doc/doxygen/bugs.html
jtag_nsrst_delay: 100
srst_only separate srst_gates_jtag srst_open_drain
Info : clock speed 6000 kHz
Info : JTAG tap: lm3s9b9x.cpu tap/device found: 0x4ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x4)
Info : lm3s9b9x.cpu: hardware has 6 breakpoints, 4 watchpoints

OpenOCD is a excellent and really wonderful, versatile tool. It opens up a socket for gdb (port 3333) and one for the user itself (4444) for telnet access (it's just really cool design!). Let's try it out on a separate terminal:

joequad joe [~/CortexM3]: telnet 127.0.0.1 4444
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Open On-Chip Debugger
> halt
target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x81000000 pc: 0x00000146 msp: 0x200000f8
> reset
JTAG tap: lm3s9b9x.cpu tap/device found: 0x4ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x4)
> mdw 0x0 16
0x00000000: 20000100 00000189 0000017d 00000181 00000185 00000185 00000185 00000000 
0x00000020: 00000000 00000000 00000000 00000185 00000185 00000000 00000185 00000185
> mdw 0x1000010 1
0x01000010: 00000054 

Here we stopped the device and restartet it and dumped the interrupt vector table. We can see the initially loaded stack pointer is 0x20000100 and the addresses of the ISRs. Then we dumped the ROM version, located at 0x1000010 (see Stellaris ROM Users Guide). It's version 84 (0x54). YMMV.

To flash a firmware image, start up OpenOCD in the directory where the firmware image is located (for us and the "Hello World" example above that would be ~/CortexM3/stellaris/boards/dk-lm3s9b96/hello/gcc). Then do:

joequad joe [~/CortexM3]: telnet 127.0.0.1 4444
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Open On-Chip Debugger
> halt
target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x81000000 pc: 0x0000015e msp: 0x200000f8
> flash write_image erase hello.bin
auto erase enabled
wrote 15360 bytes from file hello.bin in 3.018887s (4.969 kb/s)
> reset
JTAG tap: lm3s9b9x.cpu tap/device found: 0x4ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x4)

Presto, the image is on there!

A Useful(tm) Application

Just for fun, I've coded Pong on the DK-LM3S9B96. It was really easy and a cool "Hello World" application. It's basically just glued some code together that TI provided, I had to do zero to none work for myself. As for the license - I'm unsure. It relies heavily on code by TI and under their license may (I think) only be used with TI devices. Have fun! Download pong.tar.bz2 (includes source code and binary). If you don't have the device, you can at least watch the video of me playing Pong.