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.