libopencm3 on STM32 Nucleo board

Posted on 2015/04/19

1


ST Nucleo-F103RB

ST Nucleo-F103RB

This article is part of a series about developing for STM32 micro-controllers on Linux.

A few days ago I talked about libopencm3, an open source library to access Cortex-M functionalities and peripherals of many micro-controllers, especially STM32 variants. I wanted to use this library to develop on a Nucleo board that I have in my hand, specifically the Nucleo-F103RB, which mounts an STM32F103RB.

Required tools

On my Linux (Debian testing) PC I managed to compile and run the software using:

When I installed the GCC toolchain I got from the download page the Linux installation tarball (gcc-arm-none-eabi-4_9-2015q1-20150306-linux.tar.bz2) and decompressed it in /opt/ as root:

# cd /opt/
# tar xjf <download path>/gcc-arm-none-eabi-4_9-2015q1-2015

then as user I added the bin directory to the PATH running the following commands, that can be also placed in “~/.bashrc” so that they are run before opening a terminal.

$ PATH="/opt/gcc-arm-none-eabi-4_9-2015q1-2015/bin/:${PATH}"
$ export PATH

OpenOCD, needed to connect with the JTAG debug port of the STM32, needs to be at least version 0.8.0 because it’s the version that adds support for STLink v2.1 that is present on the Nucleo board. On my Debian box I simply ran “aptitude install openocd” as root.

Setting up development with libopencm3

The libopencm3 project maintains also a repository of examples, that contains also the instructions on how to integrate the library into your own project. Taking inspiration from those example, I made an empty directory and then ran the following commands to download the current state of libopencm3 (as of 2014/04/19), compile it and get the common Makefiles to build with it:

$ git clone git://github.com/libopencm3/libopencm3.git
$ git reset --hard 7dbb93c78411b37bec64b5ca5be55076b0ab1b15
$ make -C libopencm3
$ wget https://raw.githubusercontent.com/libopencm3/libopencm3-examples/master/examples/Makefile.rules
$ wget https://raw.githubusercontent.com/libopencm3/libopencm3-examples/master/examples/stm32/f1/Makefile.include

Then I changed “Makefile.include” last line as: “include Makefile.rules“, since I am going to build my program in the same directory as these Makefiles.

Note that the “git reset” command will take you to the commit that I used; to retrieve the latest code afterwards, run “git pull“. Note also that doing “make -C directory” is similar to changing to that directory, running “make” there and changing back.

In my case libopencm3 doesn’t ship with a linker script for the STM32F103RB, so I had to write one. Fortunately the library already provides the complex parts of a linker script in a generic file, leaving to the developer just the description of the “MEMORY” part, so I wrote the following file and called it “stm32f103rb.ld“:

MEMORY
{
  rom (rx)  : ORIGIN = 0x08000000, LENGTH = 128K
  ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}

INCLUDE libopencm3_stm32f1.ld

The included linker script is present in the libopencm3 directory that we downloaded earlier.

Note that it’s possible to generate an HTML documentation of libopencm3 with doxygen by running the command “make -C libopencm3 html“.

Develop and run a simple blink program

The Nucleo board has a LED and I would like to make it blink. The information I need to do so are present in the STM32 Nucleo user manual and in the STM32F103RB reference manual. In the schematics the LED identified with LD2 is connected to PA5 pin of the STM32: it’s the GPIOA port, pin 5, that we should configure as an output in the code. From the STM32 manual we can get that the clock of the GPIOA port should be enabled first, through the Reset and Clock Control (RCC) peripheral.

libopencm3 offers some functionalities to access peripherals. The first way is to access registers directly, and since they are memory mapped it means writing and reading from memory addresses, conveniently defined as macros such as GPIOA_CRL (low part of configuration register) that can be used as 32-bit unsigned variables. The second way is to use convenience functions that allow to hide some complexity of the peripherals. Both these approaches are described in the STM32F1 page of libopencm3 documentation, the first one in the “defined constants and types” section, the other in the “libraries” section. In my example below I created a “blink.c” source file with the second approach, so using the function libraries, taking inspiration from the miniblink program in the examples:

#include <stdint.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>

static void delay_loop(int32_t loops)
{
 while(loops > 0)
 {
 asm("nop");
 loops--;
 }
}

int main(void)
{
 rcc_periph_clock_enable(RCC_GPIOA);
 gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO5);
 while(1)
 {
 gpio_toggle(GPIOA, GPIO5);
 delay_loop(1000000);
 }
}

The program enables GPIOA clock, configures pin 5 as output, and loops with a delay to make the blinking visible.

In order to build the program, I created the following “Makefile“, where the BINARY variable also implicitly tells make to search for a file named “blink.c“.

BINARY = blink
OPENCM3_DIR = libopencm3
LDSCRIPT = stm32f103rb.ld
OOCD_INTERFACE = stlink-v2-1
OOCD_BOARD = st_nucleo_f103rb

include Makefile.include

Then it’s simply a matter of plugging the Nucleo board in the USB slot and run “make flash” to compile, create a binary and write it inside the STM32 embedded flash through the ST-Link adapter. I am pasting here the output of running the command with higher verbosity:

$ make V=1 flash
Using libopencm3 path to library
arm-none-eabi-gcc -Os -g -Wextra -Wshadow -Wimplicit-function-declaration -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes -fno-common -ffunction-sections -fdata-sections -MD -Wall -Wundef -Ilibopencm3/include -DSTM32F1 -mthumb -mcpu=cortex-m3 -msoft-float -mfix-cortex-m3-ldrd -o blink.o -c blink.c
arm-none-eabi-gcc --static -nostartfiles -Llibopencm3/lib -Tstm32f103rb.ld -Wl,-Map=blink.map -Wl,--gc-sections -mthumb -mcpu=cortex-m3 -msoft-float -mfix-cortex-m3-ldrd blink.o -lopencm3_stm32f1 -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group -o blink.elf
arm-none-eabi-objcopy -Oihex blink.elf blink.hex
  FLASH   blink.hex
openocd -f interface/stlink-v2-1.cfg \
	    -f board/st_nucleo_f103rb.cfg \
	    -c "init" -c "reset init" \
	    -c "flash write_image erase blink.hex" \
	    -c "reset" \
	    -c "shutdown"
Open On-Chip Debugger 0.8.0 (2014-10-20-22:02)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.sourceforge.net/doc/doxygen/bugs.html
Warn : Interface already configured, ignoring
Error: already specified hl_layout stlink
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
Info : This adapter doesn't support configurable speed
Info : STLINK v2 JTAG v23 API v2 SWIM v6 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 3.267240
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000220 msp: 0x20002000
auto erase enabled
Info : device id = 0x20036410
Info : flash size = 128kbytes
target state: halted
target halted due to breakpoint, current mode: Thread
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20002000
wrote 1024 bytes from file blink.hex in 0.218966s (4.567 KiB/s)
shutdown command invoked

Note that there are some errors printed but it’s because OpenOCD is told to use stlink-v2-1 interface and then board st_nucleo_f103rb, but the board configuration file contains also the configuration of the same interface, so some settings are duplicated. Anyway the program is flashed, the board is reset and LED LD2 starts to blink.

With libopencm3 it is possible to develop software in the same way for other peripherals such as the Analog-Digital Converter (ADC), the USART serial communication, timers and so on. The convenience functions provide a platform to start using the peripheral, and then with the macros to access registers directly it is possible to develop very efficient programs both in terms of execution time and code size, without sacrificing readability. I like this library and the code style that has been applied consistently, I think it will become my library of choice for my next STM32 projects.

Posted in: Embedded