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
L0 lines. Here are some key examples for these chips in particular:
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
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
RAM size to
8K, and update the
_estack value to
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
L031 chip requires a few extra device header files. Like with the
F031, we include device-specific
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
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 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
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
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
#endif to check for the compiler options that we set earlier in the Makefile. The generic
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
#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!
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.