Getting Started With The STM32F0 Discovery

By | 2013-03-08

Introduction

I’ve added another nice cheap dev board to my collection, the STM32F0 Discovery, which I got from RS in the UK for £. It’s an SGS Thompson (ST) kit, based around their STMF051R8T6 device, which is in turn based on an Arm Cortex M0.

I like this sort of dev board so much because they allow you to work on the hardest part of getting going with a new embedded microcontroller, cheaply – getting the first program compiled, programmed into the device, and running. It’s hard because you have to piece together all the software components, a dev environment, and some sort of programmer. This is made even harder if you’re a Linux user, as (for a reason I don’t understand) chip manufacturers promote closed source, expensive compilers like Keil and IAR over free GCC based systems. They could even use Eclipse if they felt a graphical interface was necessary. C’est la vie. Dev kits for new microcontrollers used to run into the hundreds of pounds, when all you really want is a board with a powersupply, some pins and a header for programming/debugging. That’s exactly what these new style dev boards are.

For a while I was playing around with a similar device/board from Texas Instruments – their Stellaris Launchpad board, which I chose because I like the MSP430 Launchpad board so much – but as I investigated it seemed like TI have put uncomfortable licenses on a few of their example files, and have made it not straight forward to program from Linux. (I’m also not very impressed with their attitude to not supporting SDCC/GCC for their ZigBee and Bluetooth Low Energy software stacks). So, I was pleased to be told about this discovery board from ST (to be fair these two are slightly different, this ST board is a Cortex-M0 device; and the Stellaris Launchpad is a Cortex-M4 device, but there’s a Cortex-M4 discovery board if we outgrow the STM32F0).

These discovery boards are between £5 and £10 pounds; and the chips themselves, in one-off prices are just over £2 – very pleasant; and you get a lot of CPU for your buck.

Enough waffle. On to the meat.

Programming the flash memory

On the board is an STM32 (the product line) F (general purpose) 051 (Cortex-M0, “051” range of devices) R (64-pin) 8 (8kB RAM; 64kB flash) T (LQFP) 6 (-40 to 84 working temp). The ST website, has a datasheet (which isn’t a lot of use) and, more important, the reference manual (all 748 pages) easily downloadable. On the whole: points for ST, I got the data I wanted, and there’s lots of it.

Plugging the board in to a USB slot, powers it up and runs the sample program. An LED flasher. Good.

Make sure you’ve got access to the USB device, by adding a rule like this to your /etc/udev/rules.d/ directory (use lsusb to check the vendor and product ids):

# 51-stm32-discovery.rules
ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE="0660", GROUP="plugdev"

Reload the rules with /etc/init.d/udev reload.

We can be pretty confident that, being an ARM core, that we’ll be able to find a compiler for Linux (ARM being everywhere now, and the big boy varieties actually run Linux), so our first hurdle is a programmer. Fortunately, some clever chaps have already done the hard work – openocd. Double fortunately, it’s already in Debian, with STM32F0 discovery support (the power of open source communities).

OpenOCD is very flexible, and does far more than just what we’ll use it for, so it can appear complicated, but it’s just recipes that you can fire and forget after they’re written. Here’s my Makefile for running OpenOCD:

# Makefile.ocd
OPENOCD_SCRIPT_DIR := /usr/share/openocd/scripts
BINARY := $(shell pwd)/$(BINARY)

default:
    @echo "make read BINARY=yourbinary.bin"
    @echo "make program BINARY=yourbinary.bin"

read:
    @[ ! -e "$(BINARY)" ] || (echo "$(BINARY) already exists, won't overwrite" && exit 1)
    openocd \
        -f $(OPENOCD_SCRIPT_DIR)/board/stm32f0discovery.cfg \
        -c "init" \
        -c "reset halt" \
        -c "sleep 100" \
        -c "wait_halt 2" \
        -c "dump_image $(BINARY) 0x08000000 65536" \
        -c "reset halt" \
        -c "shutdown"

program: $(BINARY)
    openocd \
        -f $(OPENOCD_SCRIPT_DIR)/board/stm32f0discovery.cfg \
        -c "init" \
        -c "reset halt" \
        -c "sleep 100" \
        -c "wait_halt 2" \
        -c "flash write_image erase $(BINARY) 0x08000000" \
        -c "sleep 100" \
        -c "verify_image $(BINARY) 0x08000000" \
        -c "sleep 100" \
        -c "reset halt" \
        -c "shutdown"

Our openocd command read 65536 bytes starting at device memory address 0x08000000. That address can be seen in the memory map in the reference manual as being the address that the internal flash is mapped to. Being a 32-bit CPU, the ARM actually has a 4GB address space – have a look at the documentation for the specifics.

A quick aside to talk about how the device is actually programmed. The two jumpers that we had to ensure were connected pass two of the pins through the USB part of the Discovery board (SWDIO on PA13, and SWDCLK on PA14). Those pins are set on reset to use the SWD mode as their alternate function. It probably isn’t wise, while using the DISCOVERY board to change away from that, as you won’t be able to debug. If you want those pins for your own application, you may also have to take the jumpers off after programming.

Inconveniently, USART1 (as PA9 and PA10) are connected to the USB-interface chip as STLINK_RX and STLINK_TX, but there is no USB serial port implemented on that interface device. The MSP430 Launchpad has the slight advantage here, the USB part also appears as a USB serial port, and those pins are connected to the device, so you have a ready made debug serial port to communicate with the device – no such luck on Discovery, you’ll have to get a USB-TTL serial converter (but they’re only a couple of pounds on eBay, and no embedded programmer should be without one anyway) then connect to JP1 (after adding solderbridges SB14 and SB15).

Toolchain

Before we can compile our own programs, we’re going to need a compiler.

You’ll want the bare-metal version of the toolchain, arm-none-eabi. Code Sourcery (now Mentor Graphics) distribute a convenient set of builds for the ARM cross-compiler and toolchain. I believe it will be possible, with a bit of research, to use the arm-linux-eabi version, and hence to get the cross-compiler direct from the emdebian project. For now though, fill in the annoying “what’s your name” form and download the bare-metal compiler from Mentor.

I keep mine in /opt/:

$ cd /opt
$ sudo tar axvf /home/user/download/arm-2012.09-63-arm-none-eabi-i686-pc-linux-gnu.tar.bz2

My own habit is not to add anything to the path; it’s easy enough to specify a toolchain prefix in most makefiles, and none of the tools themselves rely on an absolute path, or on being in the system search path, so why bother? Of course, you are welcome to do as you please.

Compiling

Finally, we can start programming. ARM devices are considerably more complicated that the little 8-bit devices I’ve talked about in the past. This is exacerbated by ARM not actually being a CPU, but rather being a CPU core that other manufacturers customise. This means that no two ARM-based CPUs are the same. That complexity is revealed in three key areas:

  • We will need a customised linker script
  • We will need customised startup code
  • There is no standardised peripheral model (although ARM I/O and peripheral configuration is almost universally memory mapped, meaning we will just need the magic addresses and register descriptions from the reference manual).

We can get ourselves a start because a kind soul has already prepared a reasonable starting template, and published it as open source on github.

$ cd discovery
$ git clone https://github.com/szczys/stm32f0-discovery-basic-template.git

We’ll being by building this minimal project simply as it is. Edit the top-level Makefile to point at your cross-compiler. Similarly the Libraries/Makefile. Here’s my diff:

diff --git a/Libraries/Makefile b/Libraries/Makefile
index 045985a..b7c9eb3 100755
--- a/Libraries/Makefile
+++ b/Libraries/Makefile
@@ -1,5 +1,6 @@
-CC=arm-none-eabi-gcc
-AR=arm-none-eabi-ar
+BINUTILSPREFIX=/opt/arm-2012.09/bin/arm-none-eabi-
+CC=$(BINUTILSPREFIX)gcc
+AR=$(BINUTILSPREFIX)ar
 
 ###########################################
 
diff --git a/Makefile b/Makefile
index 15ea40b..6ef995f 100755
--- a/Makefile
+++ b/Makefile
@@ -20,10 +20,11 @@ OPENOCD_PROC_FILE=extra/stm32f0-openocd.cfg
 
 ###################################################
 
-CC=arm-none-eabi-gcc
-OBJCOPY=arm-none-eabi-objcopy
-OBJDUMP=arm-none-eabi-objdump
-SIZE=arm-none-eabi-size
+BINUTILSPREFIX=/opt/arm-2012.09/bin/arm-none-eabi-
+CC=$(BINUTILSPREFIX)gcc
+OBJCOPY=$(BINUTILSPREFIX)objcopy
+OBJDUMP=$(BINUTILSPREFIX)objdump
+SIZE=$(BINUTILSPREFIX)size
 
 CFLAGS  = -Wall -g -std=c99 -Os  
 #CFLAGS += -mlittle-endian -mthumb -mcpu=cortex-m0 -march=armv6s-m

Then build the sample project (which is in src/main.c and src/system_stm32f0xx.c)

$ make
   ... deleted for brevity ...
/opt/arm-2012.09/bin/arm-none-eabi-size main.elf
   text    data     bss     dec     hex filename
    856       4       4     864     360 main.elf
$ ls -l main.bin
-rwxrwxr-x 1 andyp andyp   860 Mar  8 18:41 main.bin

You can have a look at the assembler output in main.lst should you be interested, it’s not long and helps familiarise you with the chip. Particularly useful is the symbol table included at the beginning. Here’s some highlights:

08000000 l    d  .isr_vector    00000000 .isr_vector
20000000 l    d  .data          00000000 .data
20000004 l    d  .bss           00000000 .bss
f108f85f l       *ABS*          00000000 BootRAM

We learn that the interrupt vectors are stored at 0x8000000 (although remember that when BOOT0 is zero, 0x8000000 will be mapped to 0x00000000). The reference manual (section 11.1.3) gives us the interrupt vector table definition; but we’ll expect the compiler and linker to take care of that for us.

Speaking of which, you’ll find the sample linker scripts in Device/ldscripts, and some startup code in Device/startup_stm32f0xx.s. If we had nothing else from this published repository, these would be enough to get us started. Linker scripts are a little arcane to read, but you can probably get the gist fairly quickly. Broadly, the three scripts relate as follows:

sections_flash.ld

This lists the common elf sections that all C programs use (and a few extras). It positions them relative to each other but symbolically in the memory map (it uses FLASH and RAM as the symbolic names for those absolute memory sections).

stm32f0discovery_def.ld

This puts numbers on FLASH and RAM and sets their sizes, it also sets the top of the stack (_estack). Importantly it also defines the entry point as being the symbol Reset_Handler.

stm32f0.ld

This file defines nothing and simply includes the other two. The linker should be told to use this script alone.

Personally, I think the three files are incorrectly named (there is nothing specific to the discovery board for example), but their contents is sound.

The startup code is ST’s own file, and is mostly just loops to initialise .bss and .data. The more important parts are these:

Reset_Handler:
  ldr   r0, =_estack
  mov   sp, r0          /* set stack pointer */

This is pretty standard; the very first thing you ever do is initialise the stack. It’s done symbolically to the number that was set in stdm32f0discovery_def.ld, 0x20002000, the top of RAM.

/* Call the clock system intitialization function.*/
    bl  SystemInit
/* Call static constructors */
    bl __libc_init_array
/* Call the application's entry point.*/
  bl main

__libc_init_array won’t be doing anything important (for the CPU, it’ll be important for your program), so SystemInit is were it’s all happening.

I won’t go through it here (it’s not very long), but you’ll find SystemInit() as a C function in Libraries/CMSIS/Device/ST/STM32F0xx/Source/Templates/system_stm32f0xx.c. This sort of code is not complicated in what it does, but is filled with magic numbers that you need will need the reference manual to interpret. Primarily it’s configuring clocks.

Enough prevarication, let’s burn it (there’s actually a ‘program’ recipe in the sample makefile, but we’ll use mine:

$ make -f Makefile.ocd program BINARY=main.bin   
openocd \
                -f /usr/share/openocd/scripts/board/stm32f0discovery.cfg \
                -c "init" \
                -c "reset halt" \
                -c "sleep 100" \
                -c "wait_halt 2" \
                -c "flash write_image erase main.bin 0x08000000" \
                -c "sleep 100" \
                -c "verify_image main.bin 0x08000000" \
                -c "sleep 100" \
                -c "reset halt" \
                -c "shutdown"
Open On-Chip Debugger 0.6.1 (2012-12-06-12:40)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.sourceforge.net/doc/doxygen/bugs.html
adapter speed: 1000 kHz
srst_only separate srst_nogate srst_open_drain
Info : clock speed 1000 kHz
Info : stm32f0x.cpu: hardware has 4 breakpoints, 2 watchpoints
target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0xc1000000 pc: 0x080002ec msp: 0x20002000
auto erase enabled
Info : device id = 0x20006440
Info : flash size = 64kbytes
target state: halted
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20002000
wrote 1024 bytes from file basic-template.bin in 0.113688s (8.796 KiB/s)
target state: halted
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000002e msp: 0x20002000
verified 860 bytes in 0.033818s (24.834 KiB/s)
target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0xc1000000 pc: 0x080002ec msp: 0x20002000
shutdown command invoked

For some reason the chip needs a power cycle after you’ve done this (perhaps I’m missing a command in the OpenOCD script), which you can do by removing and re-attaching JP2 (pushing reset isn’t enough). The blue LED should start flashing.

Done.

There is more to say about actually writing code this device, but that will be for another day, much of what I’ve said in other embedded articles is entirely relevant (programming is all the same in the end).

Leave a Reply