Adventures in libopencm3 - part 1

Recently, I’ve started working on a new project. It will take some time before the goal of the project is even worth mentioning. The important part is that it is an electronics/hardware project and it needs an energy efficient computing unit.

For early development I chose an ARM Cortex M3 STM32F3 microcontroller. More precisely STM32F303K8T6, mostly because I have a Nucleo board, with this chip, laying around. It’s also a good time to try a new framework. Choices worth considering:

  • mbed - bloated, with a high level of abstraction.
  • libopencm3 - open-source project with a reasonably low level of abstraction.
  • ChibiOS - real-time OS but apparently with a good quality HAL component.
  • ST’s HAL - bloated but I’ve used it before.

In the end it came down to libopencm3 vs ChibiOS but what libopencm3 represents (Free Open Source, small, not bloated) makes it more interesting. There isn’t a lot of information on it so I want to document my process of learning it. Obviously I won’t be able to explain each and every step but I hope you’ll find something that will help you.

Setting up a project

The process of creating a new project with libopencm3 is nicely described here. The Reuse section is where you should start.

If you’ve followed their instructions you are close to your Hello world being ready. I do believe it’s important to understand what happens next. You will stumble on a problem sooner or later, because that’s the nature of this job. Without understanding the environment you are working with, you might struggle to solve your issue.

Well hello there, dear World!

If you’ve followed the steps from libopencm3 Github page you have libopencm3 folder with its source, two files with mk extension, folder with a copied project and a Makefile.

Something like this:

--- /the_project
   |-- /libopencm3
   |-- /your_project
   |  |-- /Makefile
   |  |-- /main.cpp
   |-- /libopencm3.rules.mk
   |-- /libopencm3.target.mk

You should jump into libopencm3 project and invoke make command. This will build libopencm3.

At this point what you would like to do is to compile your project’s source files to get the binary you can flash onto the microcontroller. That’s where the Makefile in your_project folder comes in. Just as before you could make your project, but since you just grabbed all those files from the internet it’s not really prepared for the platform you are targeting. It’ll build, it won’t work.

If you read through Makefile you will notice it defines what binary it will create (basically specifying your main source file), where the libopencm3 libs are and which linker script (*.ld file) should it use. We will get back to the linker script. The last part of the Makefile is including libopencm3.target.mk. If you open libopencm3.target.mk you will find another include instruction which includes libopencm3.rules.mk. That was confusing… I hope this diagram of files’ dependencies helps:

 +------------------------+
 |                        |
 |   libopencm3.rules.mk  |
 |                        |
 +-----------+------------+
             |
             |
 +-----------V------------+
 |                        |
 |  libopencm3.target.mk  |
 |                        |
 +-----------+------------+
             |
             |
 +-----------V------------+
 |                        |
 |       Makefile         |
 |                        |
 +------------------------+

In short:

  • libopencm3.rules.mk - describes the process of compiling/flashing. For now there is no point in changing anything in this file.
  • libopencm3.target.mk - describes the targeted processor, and communication between the processor and the PC. Important because that’s where you should define the hardware you are working with.

For me libopencm3.target.mk looked like so:

LIBNAME         = opencm3_stm32f3
DEFS           += -DSTM32F3

FP_FLAGS       ?= -mfloat-abi=hard -mfpu=fpv4-sp-d16
ARCH_FLAGS      = -mthumb -mcpu=cortex-m4 $(FP_FLAGS)

#####################################################################
# OpenOCD specific variables

OOCD           ?= openocd
OOCD_INTERFACE ?= stlink-v2-1
OOCD_TARGET    ?= stm32f3x

#####################################################################
# Black Magic Probe specific variables
# Set the BMP_PORT to a serial port and then BMP is used for flashing
BMP_PORT       ?=

#####################################################################
# texane/stlink specific variables
STLINK_PORT    ?= :3333


include ../libopencm3.rules.mk

A lot is happening in this file. I will go through it in the next part. Just be sure you have copied this file from a project which targets microcontroller most similar to yours. I targeted microcontroller from STM32F3 family. As you can see there are lines with stm32f3. Those are the most important for a successful build.

By including those files in the Makefile you provide the information on what is your targeted hardware and how to build the source files. There is one more component you need. That’s the linker script. This file is referenced in the Makefile. The thing is, it is also target platform specific. It described what parts of your program (instructions, data etc.) go where in the memory of the microcontroller. Unfortunately, libopencm3 doesn’t come with linker scripts for every platform.

In my case Makefile was using stm32f303xc.ld file. It wasn’t describing the memory layout of the processor I was targeting. Lets look at it:

/* Linker script for the STM32F303xC chip. */

/* Define memory regions. */
MEMORY
{
  rom (rx) : ORIGIN = 0x08000000, LENGTH = 256K
  ram (rwx) : ORIGIN = 0x20000000, LENGTH = 40K
}

/* Include the common ld script. */
INCLUDE libopencm3_stm32f3.ld

Once again INCLUDE instruction was used to get the generic information about the memory via libopencm3_stm32f3.ld. The MEMORY {...} part describes specifics about this processor. In the documentation I found that STM32F303K8T6 has 64K of ROM memory and 12K of RAM. Well that’s not what this linker scripts says. After quick edit the final linker script looks as follows:

/* Linker script for the STM32F303xC chip. <- well not anymore!*/

/* Define memory regions. */
MEMORY
{
  rom (rx) : ORIGIN = 0x08000000, LENGTH = 64K
  ram (rwx) : ORIGIN = 0x20000000, LENGTH = 12K
}

/* Include the common ld script. */
INCLUDE libopencm3_stm32f3.ld

I put the new linker script in the projects folder:

--- /the_project
   |-- /libopencm3
   |-- /your_project
   |  |-- /Makefile
   |  |-- /main.cpp
   |  |-- /stm32f303k8.ld       <--- linker script
   |-- /libopencm3.rules.mk
   |-- /libopencm3.target.mk

Changed one line in Makefile to use the new linker script:

LDSCRIPT = ./stm32f303k8.ld

The final files dependencies:

 +------------------------+
 |                        |
 |   libopencm3.rules.mk  |
 |                        |
 +-----------+------------+
             |
             |
 +-----------V------------+
 |                        |
 |  libopencm3.target.mk  |
 |                        |
 +-----------+------------+
             |
             |                +------------------+
             |                |                  |
             |                |  stm32f303k8.ld  |
             |                |                  |
             |                +------+-----------+
             |                       |
 +-----------V------------+          |
 |                        |          |
 |       Makefile         <----------+
 |                        |
 +------------------------+

At this point the project should be properly building and shouldn’t crash the microcontroller. Jump into your_project directory and run:

make V=1

This additional V=1 makes the building process print instructions, it’s using to compile.

That would be it for building. I barely scratched the surface but this write up should provide you with enough information to get you started.