STM32 Baremetal Examples
Time may be an artificial construct, but try telling your boss that ‘Monday’ has no meaning. It is useful for a program to be able to schedule actions for a certain date, display the current time on a clock or calendar, or perform other tasks which use weird units of time like ‘seconds’ or ‘days’. For those types of tasks, an embedded developer might reach for an ‘RTC’ device, which stands for ‘Real-Time Clock’. They provide a way to keep accurate time, often with features like backup power supplies. Many RTCs also offer ‘wakeup’ alarms for other devices, so they are especially useful in energy-efficient designs.
The STM32 line of chips which I’ll continue to use in this tutorial have a built-in RTC peripheral, but they require an external 32.768KHz ‘LSE’ (Low-Speed External) crystal oscillator to keep accurate time. Also, managing the STM32’s backup power supply is sort of complicated.
Instead, this tutorial will walk through using the ‘I2C’ peripheral on an STM32 chip to communicate with a cheap
DS3231 RTC module. Specifically, I will talk about a widely-available board labeled
ZS-042, which includes 4KB of EEPROM on its I2C bus and space for a “coin cell” battery to provide several years of backup power. But the same commands should work with other
DS3231 boards, such as the smaller ones in the upper-left here:
In a previous entry in this tutorial series, I tried to walk through some basic steps for writing an example STM32 program which toggled an LED on and off when a button is pressed. But that program only checked the button’s status once every cycle of the ‘main’ loop, and in a complex application each loop iteration could take a fairly long time. If a button press were very short and our application was busy for a long time, the program could miss the input.
When you want to respond to input very quickly and consistently on a microcontroller, it is usually a good idea to use a ‘hardware interrupt’ to run some code when certain hardware events are detected. In this tutorial, we will look at the STM32’s ‘EXTI’ interrupt lines, which can be set to trigger when the state of a GPIO pin changes.
And once we have a simple ‘button press’ interrupt triggering, we can easily demonstrate a real-world use by extending it to listen for faster inputs such as “rotary encoder” dials:
This type of dial ‘clicks’ in small steps when turned in either direction; they are nice tactile inputs, but it can be difficult to read them without hardware interrupts because of the large number of rapid pulses that they can generate when you twist them. So let’s get started!
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!
In a previous post, I walked through creating a basic project for an ‘STM32F031K6’ microcontroller to boot the chip into a ‘main’ C method. But the actual program didn’t do much, so in this post we will learn how to use the STM32’s ‘GPIO’ peripheral to listen for a button press and blink an LED.
The ‘Nucleo’ boards provided by ST have an LED already built into the board, but they don’t have a button (besides the reset one,) so we’ll need to connect one externally:
The green ‘LD3’ LED is attached to pin B3 on the board. The 100nF capacitor across the button should help reduce noise, one side of the button connects to ground through a jumper wire, and I put a 470Ω resistor between the other side of the button and pin B1.
Strangely, the B1 pin is labeled ‘D6’ on the Nucleo boards; I think that ST wanted to use the same footprint and labeling as the popular Arduino Nano. You can find the actual pin mappings in section 6.11 of this reference document, or they’re also printed on the informational card that comes with the board. The resistor and capacitor are both optional – they’re just a very simple form of debouncing. Next up, the code!
In a previous post, I tried to walk through some very minimal code to get an STM32 chip to boot into a simple “Hello, World” assembly program. But that quick introduction left out some important concepts, and usually people don’t want to write an entire program in an assembly language.
So in this tutorial, we’re going to build on that ‘absolute minimum’ example, and write some more complete ‘startup’ code which will run a familiar C program’s “main” method when it finishes.
We’ll use the STM32F031K6 chip as an example again; it is one of ST’s simpler ARM chips, and you can buy a pre-made ‘Nucleo’ board for just a little over $10.
What Will We Write?
This example project will consist of a few different files, but there’s still a good chance you can count them on one hand:
- A more complete ‘Linker Script’ to map our C program’s individual sections of memory onto the chip.
- A ‘Vector Table’ file which will point every possible hardware interrupt to a default ‘interrupt handler’ – we’ll go over how to actually use these later.
- A ‘boot code’ file which will contain a reset handler to copy information to RAM and then jump into the ‘main’ method.
- A ‘main.c’ file which will contain our actual program logic.
- A ‘Makefile’ which will let GNU Make build the project for us, so we don’t have to copy/paste GCC commands into a console like last time.
Hopefully you’ll come out of this post with a decent starting point for STM32F0 projects, and a general understanding of what is required to create your own projects for other chips.
The STM32 line of ARM Cortex-M microcontrollers are a fun way to get started with embedded programming. The nice thing about these chips is that they don’t require much setup, so you can start to learn about them bit by bit, starting with almost no code. And they are much more capable than the 8-bit processors used in many ‘Arduino’-type boards – some can run at over 400MHz, and they can have advanced peripherals up to and including simple graphics accelerators.
But in this tutorial, we will just learn the absolute minimum required to get a program running on one of the simpler STM32 chips. We’ll cover how to support multiple chips in a later post, but this example will use the STM32F031K6 as an example. ST makes an affordable ‘Nucleo’ development board with this chip, which costs just over $10 from somewhere like Digikey, Mouser, etc.
This guide will assume some familiarity with C programming and the popular GCC compiler + GDB debugger, but I will try to explain all of the parts specific to coding for microcontrollers. I’d also like to make these posts more accessible, and would welcome feedback if anything is unclear or could be better explained.
On the bright side, the very low-level starting code demonstrated in these first few examples are things that you won’t have to worry about once it is set up. If you want to skip these examples, there are tools such as ST’s CubeMX which can generate these sorts of empty starting projects. But it’s nice to have some idea of what goes on inside of the chip, so let’s get started! You can view the entire minimal example project described in this post in this Github repository.