Blog for my various projects, experiments, and learnings

“Bare Metal” STM32 Programming (Part 3.5): Supporting Multiple Chips

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.

Leave a Reply

Your email address will not be published. Required fields are marked *