“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:

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:

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:

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:

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:

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.

Related posts:

Across the globe, people seem to enjoy decorating their homes, communities, and outdoor spaces with lights and ornaments during the winter holidays. Maybe it helps with the depressingly early sunsets for those of us who don’t live near the equator. Anyways, I thought it’d be fun to make some ornaments with multi-color addressable LEDs last year, and I figured I’d write about what worked and what didn’t.

I didn’t have many microcontrollers at the time because I was visiting family for the holidays, so I ended up coding the lighting patterns for a cheap little STM32F103 “black pill” board which was in the bottom of my backpack. And it’s a convenient coincidence that I just started learning about the very similar GD32VF103 chips with their fancy RISC-V CPUs and nearly-identical peripheral layout, so this also seems like a good opportunity to write about how to cross-compile the same code for two different CPU architectures.

Pretty holiday stars! “Frosted white” acrylic sheets aren’t the best way to diffuse light, but they are cheap and easy to work with.

This was a fun and festive project, and it might not be a bad way to introduce people to embedded development since there are so many ways to drive these ubiquitous “NeoPixel” LEDs. Sorry that this post is a little bit late for the winter holidays – I’ve been traveling for the past few months – but maybe it’ll get you thinking about next year 🙂

I’ll talk about how I assembled the stars and what I might do differently next time, then I’ll review how to light them up with an STM32F103, and how to adapt that code for a GD32VF103. But you could also use a MicroPython or Arduino board to set the LED colors if you don’t want to muck around with peripheral registers.

I’ve written a little bit in the past about how to design a basic STM32 breakout board, and how to write simple software that runs on these kinds of microcontrollers. But let’s be honest: there’s still a bit of a gap between creating a small breakout board to blink an LED, and building hardware / software for a ‘real-world’ application. Personally, I would still want a couple of more experienced engineers to double-check any designs that I wanted to be reliable enough for other people to use, but building more complex applications is a great way to help yourself learn.

So in this post, I’m going to walk through the process of designing a small ‘gameboy’-style handheld with a GPS receiver and microSD card slot, for exploring the outdoors instead of video games. Don’t get me wrong, you could still write games to run on this if you wanted to, and that would be fun, but everyone and their dog has made a Cortex-M-based handheld game console by now; there are plenty of better guides for that, and many of those authors put a lot more time into their designs and firmware than I ever did.

Assembled GPS Doohicky. I left too much room between the ribbon connector footprint and the edge of the board on this first revision, so the display couldn’t fold over quite right. Oh well, you live and learn.

The board design isn’t too complicated, but there are several different parts and it gets easier to make small-but-important mistakes as a design gets larger. It mostly uses peripherals that I’ve talked about previously, but there are a couple of new ones too. The display will be driven over SPI, the speaker uses a DAC, the GPS receiver talks over UART, the battery and light levels will be read using an ADC, and the buttons will be listened to using interrupts. But I haven’t written about the USB or SD card (“MMC”) peripherals, and those will need to go in a future post since I haven’t actually worked them out myself yet. Note that SD cards can technically use either SPI or SD/MMC to communicate, but the microcontroller that I picked has a dedicated SD/MMC peripheral, and I wanted to learn about it.

If you choose to pursue embedded development beyond the occasional toy project, it probably won’t take long before you want to design something which runs off of battery power. Many types of devices would not be useful if they had to be plugged into a wall outlet all the time, and power efficiency is one of the biggest advantages that microcontrollers still have over application processors like the one in a Raspberry Pi.

When you do move an application to battery power, you’ll quickly discover that it is very important for your device to be able to A) charge its battery and B) alert you when its battery is running low. Not knowing whether something has hours or seconds of life left can be really annoying, and trying to use a nearly-dead battery can cause strange behavior, especially if the battery’s power drops off slowly as it dies. Most lithium-based batteries also last longer if you avoid fully discharging them – there’s some good information about lithium battery aging in this article.

So in this post, I’m going to go over a very basic circuit to power an STM32 board off of a single lithium-ion battery and monitor its state of charge. I will also talk briefly about how to add a simple battery charger to your design, but you should always independently verify any circuitry which interacts with lithium batteries! This circuit seems to work to the best of my knowledge, but don’t take my word for it; it’s very important to double- and triple-check your li-po battery circuits, because they can easily become serious fire hazards if they are handled improperly. It’s also good practice to avoid leaving lithium-ion batteries unattended while they are charging, and you should try to get batteries with built-in protection circuitry to help mitigate bad situations like over-current, under-voltage, etc.

So with those brief and not comprehensive safety warnings out of the way, let’s get started! I’ll use an STM32L4 chip for this example, but the ADC peripheral doesn’t seem to change much across STM32s. And here is a GitHub repository containing design files for a simple board which demonstrates the concepts described in this post.