Blog for my various projects, experiments, and learnings

STM32 Baremetal Examples

Reading Battery Voltage with the STM32’s ADC

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.

Battery charged

Hooray, it’s fully charged!

New 8-pin ARM Core: the STM32G031J6

It has been about nine months since ST released their new STM32G0 line of microcontrollers to ordinary people like us, and recently they released some new chips in the same linup. It sounds like ST wants this new line of chips to compete with smaller 8-bit micros such as Microchip’s venerable AVR cores, and for that market, their first round of STM32G071xB chips might be too expensive and/or too difficult to assemble on circuit boards with low dimensional tolerances.

Previously, your best bet for an STM32 to run a low-cost / low-complexity application was probably one of the cheaper STM32F0 or STM32L0 chips, which are offered in 16- and 20-pin TSSOP packages with pins spaced 0.65mm apart. They work great, but they can be difficult to use for rapid prototyping. It’s hard to mill or etch your own circuit board with tight enough tolerances, and it’s not very easy to solder the chips by hand. Plus, the aging STM32F031F6 still costs $0.80 each at quantities of more than 10,000 or so, and that’s pretty expensive for the ultra-cheap microcontroller market.

STM32G031J6 schematic

Pinout and minimal circuit for an STM32G031J6 – you only really need one capacitor if you have a stable 3.3V source.

Enter the STM32G031J6: an STM32 chip which comes in a standard SOIC-8 package with 32KB Flash, 8KB RAM, a 64MHz speed limit, and a $0.60 bulk price tag (closer to $1.20-1.40 each if you’re only buying a few). That all compares favorably to small 8-pin AVR chips, and it looks like they might also use a bit less power at the same clock speeds. Power consumption is a tricky topic because it can vary a lot depending on things like how your application uses the chip’s peripherals or what voltage the chip runs off of. But the STM32G0 series claims to use less than 100uA/MHz, and that is significantly less than the 300uA/MHz indicated in the ATTiny datasheets. Also, these are 32-bit chips, so they have a larger address space and they can process more data per instruction than an 8-bit chip can.

Considering how easy STM32 chips are to work with, it seems like a no-brainer, right? So let’s see how easy it is to get set up with one of these chips and blink an LED.

Hello, Rust: Blinking LEDs in a New Language

Rust is a fairly new language that has gotten to be very popular in recent years. And as the language matures, it has started to support a wider set of features, including compilation and linking for bare-metal targets. There is an excellent “Embedded Rust” ebook being written which covers the concepts that I’ll talk about here, but it’s still in-progress and there aren’t many turn-key code examples after the first couple of chapters.

The Rust language is less than 10 years old and still evolving, so some features which might change in the future are only available on the nightly branch at the time of writing; this post is written for rustc version 1.36. And the language’s documentation is very good, but it can also be a little bit scattered in these early days. For example, after I had written most of this post I found a more comprehensive “Discovery ebook” which covers hardware examples for an STM32F3 “Discovery Kit” board. That looks like a terrific resource if you want to learn how to use the bare-metal Rust libraries from someone who actually knows what they’re talking about.

As a new Rustacean, I’ll admit that the syntax feels little bit frustrating at times. But that’s normal when you learn a new language, and Rust is definitely growing on me as I learn more about its aspirations for embedded development. Cargo looks promising for distributing things like register definitions, HALs, and BSPs. And there’s an automated svd2rust utility for generating your own register access libraries from vendor-supplied SVD files, which is useful in a language that hasn’t had time to build up an extensive set of well-proven libraries. So in this post I’ll talk about how to generate a “Peripheral Access Crate” for a simple STM32L031K6 chip, and how to use that crate to blink an LED.

It’s kind of fun when languages have mascots, especially when they’re CC0-licensed.

The target hardware will be an STM32L031K6 Nucleo-32 board, but this should work with any STM32L0x1 chip. I also tried the same process with an STM32F042 board and the STM32F0x2 SVD file, which worked fine. It’s amazing how easy it is to get started with a new chip compared to C, although you do still need to read the reference manuals to figure out which registers and bits you need to modify. This post will assume that you know a little bit about how to use Rust, but only the very basics – I’m still new to the language and I don’t think I would do a good job of explaining anything. The free Rust ebook is an excellent introduction, if you need a quick introduction.

Keeping Up With the Moorses: Learning to Use STM32G0 Chips

Microcontrollers are just like any other kind of semiconductor product. As manufacturers learn from customer feedback and fabrication processes continue to advance, the products get better. One of the most visible metrics for gauging a chip’s general performance – and the basis of Moore’s Law – is how large each transistor is. Usually this is measured in nanometers, and as we enter 2019 the newest chips being made by companies like Samsung and Intel are optimistically billed as 7nm.

The venerable and popular STM32F1 series is more than a decade old now, and it is produced using a 130nm process. But ST’s newer lines of chips like the STM32L4 use a smaller 90nm process. Smaller transistors usually mean that chips can run at lower voltages, be more power-efficient, and run at faster clock speeds. So when ST moved to this smaller process, they introduced two types of new chips: faster ‘mainline’ chips like the F4 and F7 lines which run at about 100-250MHz, and more efficient ‘low-power’ chips like the L0 and L4 lines which have a variety of ‘sleep’ modes and can comfortably run off of 1.8V. They also have an H7 line which uses an even smaller 40nm process and can run at 400MHz.

Now as 2018 fades into history, it looks like ST has decided that it’s time for a fresh line of ‘value-line’ chips, and we can order a shiny new STM32G0 from retailers like Digikey. At the time of writing there aren’t too many options, but it looks like they’re hoping to branch out and there are even some 8-pin variants on the table. I could be misreading things, but these look like a mix between the F0 and L0 lines, with lower power consumption than F0 chips and better performance than the L0 chips. The STM32G071GB that I made a test board with has 128KB of Flash, 36KB of RAM, and a nice set of communication peripherals.

STM32G0 breakout board

Simple STM32G0 breakout board

So what’s the catch? Well, this is still a fairly new chip, so “Just Google It” may not be an effective problem-solving tool. And it looks like ST made a few changes in this new iteration of chips to provide more GPIO pins in smaller packages, so the hardware design will look similar but slightly different from previous STM32 lines. Finally, with a new chip comes new challenges in getting an open-source programming and debugging toolchain working. So with all of that said, let’s learn how to migrate!

“Bare Metal” STM32 Programming (Part 8): Learn to Debug Timing Issues with Neopixels

I haven’t written about STM32 chips in a little while, but I have been learning how to make fun displays and signage using colorful LEDs,  specifically the WS2812B and SK6812 ‘Neopixels’. I talked about the single-wire communication standard that these LEDs use in a post about running them from an FPGA, and I mentioned there that it is a bit more difficult for microcontrollers to run the communication standard. If you don’t believe me, take a look at what the poor folks at Adafruit needed to do to get it working on a 16MHz AVR core. Yikes!

When you send colors, the 1 bits are fairly easy to encode but the 0 bits require that you reliably hold a pin high for just 250-400 nanoseconds. Too short and the LED will think that your 0 bit was a blip of noise, too long and it will think that your 0 is a 1. Using timer peripherals is a reasonable solution, but it requires a faster clock than 16MHz and we won’t be able to use interrupts because it takes about 20-30 clock cycles for the STM32 to jump to an interrupt handler. At 72MHz it takes my code about 300-400 nanoseconds to get to an interrupt handler, and that’s just not fast enough.

There are ways to make it faster, but this is also a good example of how difficult it can be to calculate how long your C code will take to execute ahead of time. Between compiler optimizations and hardware realities like Flash wait-states and pushing/popping functions, the easiest way to tell how long your code takes to run is often to simply run it and check.

Working Neopixel Timing

Pulseview diagram of ‘101’ in Neopixel. I can’t be sure, but I think the ‘0’ pulse might be about 375 nanoseconds long.

Which brings us to the topic of this tutorial – we are going to write a simple program which uses an STM32 timer peripheral to draw colors to ‘Neopixel’ LEDs. Along the way, we will debug the program’s timing using Sigrok and Pulseview with an affordable 8-channel digital logic analyzer. These are available for $10-20 from most online retailers; try Sparkfun, or Amazon/eBay/Aliexpress/etc. I don’t know why Adafruit doesn’t sell these; maybe they don’t want to carry cheap generics in the same category as Salae analyzers. Anyways, go ahead and install Pulseview, brush up a bit on STM32 timers if you need to, and let’s get started!

“Bare Metal” STM32 Programming (Part 7): Embedded C++ Inheritance

As you start to re-use components like sensors and displays, you might start to get frustrated with how long it takes to set up new projects. Copying and cleaning code between old working examples and new ideas can be time-consuming and tedious. It’s much easier to simply copy a few portable library files around. While there are plenty of existing libraries for these sorts of peripherals and external devices, it’s good to learn how to write your own, and this is also a good way to demonstrate a few ‘gotchas’ that you should be aware of when using C++ in an embedded application.

In this tutorial, we will walk through setting up a couple of object-oriented classes to represent a common communication ‘I/O’ peripheral model:

One example of what a simple common communication model could look like.

One example of what a simple common communication model could look like.

For simplicity’s sake, I’ll only cover a class for a bank of GPIO pins to demonstrate the core requirements for using C++ in an embedded application, but you can also find similar classes for the I2C peripheral and an SSD1306 OLED display in the example Github repository’s reference implementation of the concepts presented in this tutorial.

“Bare Metal” STM32 Programming (Part 6): Multitasking With FreeRTOS

In a previous tutorial, we walked through the process of setting up a hardware interrupt to run a function when a button was pressed or a dial was turned. Most chips have a broad range of hardware interrupts, including ones associated with communication and timer peripherals. There is also a basic ‘SysTick’ timer included in most ARM Cortex-M cores for providing a consistent timing without needing to count CPU cycles.

One good use of that evenly-spaced timing is to run a Real-Time Operating System (often called an ‘RTOS’) to process several tasks in parallel. As you add more parts to your projects, it will become awkward to communicate with them all using a simple ‘while’ loop. And while hardware interrupts can help, it’s usually good to do as little as possible inside of an interrupt handler function.

So let’s use FreeRTOS, an MIT-licensed RTOS, to run a couple of tasks at the same time. To demonstrate the concept, we’ll run two ‘toggle LED’ tasks with different delay timings to blink an LED in an irregular pattern.

Example LED Timing

Example LED timing with two ‘toggle’ tasks delaying for different amounts of time.

This example will also add support for some faster microcontrollers; the STM32F103C8 and STM32F303K8, which are Cortex-M3 and -M4F cores respectively. The F103C8 is available on cheap ‘blue pill‘ and ‘black pill‘ boards, and ST sells a ‘Nucleo’ board with the F303K8. Both of those chips can run at up to 72MHz, and they can also get more done per cycle since they have larger instruction sets.  And as usual, there is example code demonstrating these concepts on Github.

Drawing to a Small TFT Display: the ILI9341 and STM32

As you learn about more of your microcontroller’s peripherals and start to work with more types of sensors and actuators, you will probably want to add small displays to your projects. Previously, I wrote about creating a simple program to draw data to an SSD1331 OLED display, but while they look great, the small size and low resolution can be limiting. Fortunately, the larger (and slightly cheaper) ILI9341 TFT display module uses a nearly-identical SPI communication protocol, so this tutorial will build on that previous post by going over how to draw to a 2.2″ ILI9341 module using the STM32’s hardware SPI peripheral.

An ILI9341 display being driven by an STM32F0 chip.

An ILI9341 display being driven by an STM32F0 chip. Technically this isn’t a ‘Nucleo’ board, but the code is the same.

We’ll cover the basic steps of setting up the required GPIO pins, initializing the SPI peripheral, starting the display, and then finally drawing pixel colors to it. This tutorial won’t read any data from the display, so we can use the hardware peripheral’s MISO pin for other purposes and leave the TFT’s MISO pin disconnected. And as with my previous STM32 posts, example code will be provided for both the STM32F031K6 and STM32L031K6 ‘Nucleo’ boards.

“Bare Metal” STM32 Programming (Part 5): Timer Peripherals and the System Clock

As you start to create microcontroller projects with more complicated logic, it probably won’t take long before you to want to add some sort of time-based functionality. How should you ask your chip to do something on a schedule?

These days, we don’t have to count clock cycles in ‘delay’ methods. The STM32 line of chips have a variety of “timer” peripherals available, and they are flexible enough to use for all kinds of different things. The “advanced control” timer peripheral is particularly complicated and I won’t try to cover it in this quick overview, but the basic and general-purpose timers are easy to get started with for simple counters and interrupts.

In this tutorial, we will write code to enable a hardware interrupt triggered by a timer peripheral, and use that to blink an LED. Just like the last couple of posts in this series, the STM32F031K6 and STM32L031K6 chips will be used to demonstrate these concepts. Since this tutorial will only use a single LED, you won’t need anything other than an affordable “Nucleo” board with one of those chips and a micro-USB cable:

"Nucleo-32" Board with Blinking LED

You don’t even need a breadboard!

You can double-check that the clock speeds are what we expect by counting the LED’s on/off timings against a clock. It should change about 60 times every minute with the timer set to 1 second, although the internal oscillator is not quite as precise as an external “HSE” crystal oscillator would be. As usual, an example project demonstrating this code is available in a Github repository.

When is Now? The DS3231 Real-Time Clock

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:

DS3231 Modules

A handful of DS3231 modules and their backup batteries.

An example project demonstrating the concepts outlined in this post using either an STM32F031K6 or STM32L031K6 “Nucleo” board is available on Github.

“Bare Metal” STM32 Programming (Part 4): Intro to Hardware Interrupts

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:

Rotary Encoders

A couple of “rotary encoder” dials – the small resistors and capacitors on the back are for debouncing.

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!

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