The first few of these ‘STM32 programming’ tutorials have only supported a single microcontroller – the STM32F031K6
. But in reality, you will probably want to select a chip based on speed, price, power efficiency, which peripherals your application needs, etc. It’s nice to be able to compile your projects for different chips without needing to make changes to the code, and it’s also useful to be able to copy/paste code between projects which target different chips.
So in this tutorial, we will take the simple ‘button-controlled LED’ example project from last time, and extend it to work with an STM32L031K6
core. Again, you can buy a ‘Nucleo’ board with this chip for about $11. It is fairly similar to the STM32F031K6
that we used last time, but there are plenty of differences between ST’s F0
and L0
lines. Here are some key examples for these chips in particular:
STM32F031K6 | STM32L031K6 | |
MCU Core | Cortex-M0 | Cortex-M0+ |
SRAM | 4KB | 8KB |
Max. Speed | 48MHz | 32MHz |
These differences are fairly minor, but we will need to provide a slightly different linker script and startup logic. The vector table will also be different, and we will need to add a few more of ST’s “device header” files for the new chip. Also, since the ‘L’ series uses a newer core architecture, we will need to update the places where we specify cortex-m0
in our assembly files and compiler options to optionally use cortex-m0plus
instead.
This process is actually fairly painless, and all of the files discussed after the break are available in a Github repository if you haven’t read the previous tutorials which this example builds on. So, let’s get started!
The Linker Script
We barely have to change the linker script at all – both of these devices have 32KB of flash, and the basic memory sections of a C program aren’t about to change. The only real difference is that the L031K6
chips have twice as much RAM – 8KB.
They also have 1KB of EEPROM, but I’ll ignore that for now. So we just need to change the MEMORY
block’s RAM
size to 8K
, and update the _estack
value to 0x20002000
.
A New Vector Table
The vector table for the L031K6
is also similar to that of the F031K6
; the differences just reflect the different hardware peripherals available on each chip. Some of the L0
entries are prefixed with LP
for ‘Low Power’, and it has a few fewer timers. But like before, you don’t need to worry about these entries until you decide to enable a specific peripheral’s hardware interrupts.
Different Device Headers
The L031
chip requires a few extra device header files. Like with the F031
, we include device-specific stm32l0xx.h
and stm32l031xx.h
header files, as well as a dummy system_stm32l0xx.h
. In addition, the L0
line uses a newer ‘Cortex-M0+’ architecture so we need a CMSIS cortex_cm0plus.h
header file.
Updating the Makefile
The Make
utility lets us use ‘if/else’ logic to include different files and compilation options, or set different variables for future use. So we can make it very easy to compile for different chips by setting up our Makefile to accept a single ‘chip type’ value, and automatically select the right options and files. For the vector tables and startup logic, we can also include different .S
files from the Makefile depending on the type of chip. So, we can start by setting some basic definitions:
TARGET = main # Default target chip. Only leave one of these un-commented. #MCU ?= STM32F031K6 MCU ?= STM32L031K6 ifeq ($(MCU), STM32F031K6) MCU_FILES = STM32F031K6T6 ST_MCU_DEF = STM32F031x6 MCU_CLASS = F0 else ifeq ($(MCU), STM32L031K6) MCU_FILES = STM32L031K6T6 ST_MCU_DEF = STM32L031xx MCU_CLASS = L0 endif # Define the linker script location and chip architecture. LD_SCRIPT = $(MCU_FILES).ld ifeq ($(MCU_CLASS), F0) MCU_SPEC = cortex-m0 else ifeq ($(MCU_CLASS), L0) MCU_SPEC = cortex-m0plus endif
We can also use the MCU_CLASS
and MCU
values to tell our C source files what type of chip we’re using:
CFLAGS += -DVVC_$(MCU_CLASS) CFLAGS += -DVVC_$(MCU)
And we can use the MCU_FILES
value to define which vector table and ‘startup’ assembly script to use – we just have to name the files after the chip that they target. I also made separate directories for those files to avoid clutter:
AS_SRC = ./boot_code/$(MCU_FILES)_core.S AS_SRC += ./vector_tables/$(MCU_FILES)_vt.S C_SRC = ./src/main.c
Clock Speed
Another difference that we face is the chips’ core clock speeds. The F0
line of chips boot to an 8MHz ‘HSI’ (‘High-Speed Internal’) oscillator, but the L0
line is optimized for low power usage and boots to a 2.1MHz ‘MSI’ (‘Multi-Speed Internal’) oscillator instead. It has an HSI oscillator too, but that won’t be important until we configure the chip’s core system clocks in a later tutorial.
For now, just note that the L0
line of chips will run at a fairly slow clock speed when they first power on.
Updating Source Files
The main.c
and main.h
files also need a few changes, but they are very minor. In the header file, we just need to include different device headers depending on the target chip. We can use #ifdef
/#elif
/#endif
to check for the compiler options that we set earlier in the Makefile. The generic F0
and L0
device header files will try to detect which chip you are using and include the right file based on values passed in to the compiler; that’s what the ST_MCU_DEF
value in the Makefile is for:
#ifdef VVC_F0 #include "stm32f0xx.h" #elif VVC_L0 #include "stm32l0xx.h" #endif
Most of the C code will actually work on both chips – the device header files are intended to make peripheral access as seamless as possible across the different lines. But you can only do so much with naming conventions alone, and the L0
line’s RCC peripheral uses separate registers for its GPIO clocks, while the F0
line includes them in the AHB registers. So, we need different commands for enabling the GPIOB
peripheral:
#ifdef VVC_F0 RCC->AHBENR |= RCC_AHBENR_GPIOBEN; #elif VVC_L0 RCC->IOPENR |= RCC_IOPENR_IOPBEN; #endif
ST provides libraries that make things even more portable – search for their “CubeMX” packages – but that’s all that we need to change in the C files!
Conclusions
You should be able to build this project with either MCU
setting in the Makefile, and get the same behavior of a button toggling the onboard LED on a ‘Nucleo-32’ board. I’m excited to learn more about the STM32L0 and L4 chips; it seems like they have a lot of potential for low-power applications, if I can figure out how to use their sleep modes and RTC peripherals.
Anyways, this was a slight detour in that it didn’t introduce any new features on these chips, but it is still useful to be able to easily move your code between projects that use different types of microcontrollers.
And again, here is a Github repository with the project outlined in this post.