Adventures in libopencm3 - part 1

Recently I 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 a microcontroller.

Microcontroller I chose is a STM32F303K8T6, mostly because I have a Nucleo board with this chip laying around. Choosing libraries for programming was a bit more challenging. Libs I considered:

  • mbed - a bit bloated.
  • libopencm3 - open-source project.
  • ChibiOS - seems to have a good quality HAL component.
  • ST’s HAL - used it before.

In the end it came down to libopencm3 vs ChibiOS. For now I decided to go with libopencm3. There isn’t a lot of information on it so I want to document my process of learning it. Hope you will find it useful :) I’m not explaining some concepts and tools being used in this process - sorry, can’t get through everything.

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.

So you did everything like they said, but why? Because some code written in C/C++ needs to end up in processors memory so it can do whatever you wanted it to do. There is one catch - understanding what happens all the way through is important. Crucial I would say.

Binary soup needs to be created

If you 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.

Basically you have something like:

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

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

Now you want to compile your project’s source files to get the binary you can flash onto 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.

If you read through Makefile you will notice it defines what binary it will create (basically specifying your main source), where the libopencm3 libs are and which linker script should it use. We will get back to the linker script. The last part of the Makefile is including libopencm3.target.mk. If you then open libopencm3.target.mk you will find another include instruction which includes libopencm3.rules.mk. Files dependency looks as follows:

 +------------------------+
 |                        |
 |   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. Jump into your_project directory and invoke

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 be able too know where to look for more answers.

Next time a bit about uploading the binary to the processor.

comments powered by Disqus