STM32F0 with libopencm3 - Part 0: Simple GPIO
One of the simplest projects to get started with the STM32 microcontroller series: turn on the lights!
Windows Users
This series of write-ups assumes the reader is on a Linux operating system. Windows users can utilize the Windows Subsystems for Linux though your mileage may vary!Straight to the Chase
For those that want to cut to the chase and save time, here is the full source code with friendly names to get you started:
Source Code
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/rcc.h>
#define LED_PORT GPIOC
#define LED_BLU GPIO8
#define LED_GRN GPIO9
int main(void) {
rcc_periph_clock_enable(RCC_GPIOC);
gpio_mode_setup(LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, LED_BLU | LED_GRN);
gpio_set_output_options(LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_LOW, LED_BLU | LED_GRN);
gpio_set(LED_PORT, LED_BLU | LED_GRN);
while (1);
}
Getting Started with libopencm3
libopencm3 is a very powerful,
useful, open-source firmware library for use in writing programs for various
different ARM Cortex-M microcontrollers. It’s read me contains plenty of
information on the basics of getting started (typically done via git submodule
).
Additionally, there is a libopencm3-template repository to help in getting started.
Dependencies
Prior to doing any ARM Cortex-M development, the necessary dependencies need to be installed in order to successfully build/compile source code into a binary capable of being flashed (written) onto the microcontroller:
- GNU Arm Embedded Toolchain1: Typically available from the package manager
(i.e.
arm-none-eabi-gcc
,arm-none-eabi-binutils
,arm-none-eabi-newlib
, and optionallyarm-none-eabi-gdb
) - make: Usually pre-installed with most Linux distributions, a build automation tool exceptionally useful for C/C++ compiling.
- Text Editor or IDE: Anything, really.
Flashing the STM32F0 Discovery Board
The discovery series boards provided by ST come with an on-board ST-LINK/V2 programmer. There are several ways to flash your build programs using this, though my preference is stlink by Texane.
The GCC ARM GDB (GNU Debugger) does let you write programs, but requires some additional know-how and minor legwork that may complicate understandings. However, it is an immensely powerful debugging tool that should not be overlooked for too long! For the sake of brevity, this guide will omit diving into that until later.
Makefile
The aforementioned libopencm3-examples
repository provides a useful, yet
overly complex, Makefile. For the reader, this has been boiled down (assuming
they are also using stlink
mentioned above) the following, simple Makefile2 on
my GitLab3.
To flash, it’s as simple as make flash
(will also build the binary for your
convenience).
Linker Script
The loader (.ld
) file is specific to the flavor of ARM Cortex-M
microcontroller being used. The authors of libopencm3
provide example
loader files that can be used for most projects (e.g. located in
libopencm3/lib/stm32/f0/
of the repo). However, these may not always be
available and may need to be modified or created from scratch, by the developer,
for proper use. There are several articles online that go into detail about
linker scripts
Project Structure
The Makefile, as of writing this, assumes your project directory structure has
libopencm3
either cloned, copied, or initialized as a git submodule within the
same directory of your main.c
. It is advised that you look through the
Makefile’s variables of things you may want to change:
.
├── libopencm3
├── main.c
├── Makefile
└── stm32f0.ld
Explanation
Naming Convention
As a note to the reader: below I will not refer to the GPIO port or pins using the#define
friendly names from above. This is purely for the sake
of clarity in hopes of avoiding confusion.
Although the source code is fairly simple, lets dive into it at least somewhat.
For starters, why were pins GPIO8
and GPIO9
on the GPIOC
port being used?
The answer can be found after a quick review of the STM32F0 Discovery User Manual4:
The Discovery board comes with two LEDs for use by the user, tied to Port C pins 8 (blue LED), and 9 (green LED).
Reset and Clock Control (RCC)
The RCC, and it’s registers, are an important part in using the STM32
microcontroller’s peripherals. Luckily, utilizing libopencm3
we can forego
bit-banging our way through each register’s bits found in the reference
manual5 and simply utilize the GPIO port that we need – in this case
GPIOC
:
rcc_periph_clock_enable(RCC_GPIOC);
GPIO Setup
Next, we need to define what mode we want the GPIO pins on their respective port to be along with the internal pull-up or pull-down resistor mode:
GPIO Mode | Description |
---|---|
GPIO_MODE_INPUT |
(default) Digital input |
GPIO_MODE_OUTPUT |
Digital output |
GPIO_MODE_AF |
Alternate Function (requires defining which alternate function desired) |
GPIO_MODE_ANALOG |
Analog (for use with ADC or DAC capable GPIO) |
PUPD Mode | Description |
---|---|
GPIO_PUPD_NONE |
(default) No internal pull-up or pull-down resistor |
GPIO_PUPD_PULLUP |
Internal pull-up resistor |
GPIO_PUPD_PULLDOWN |
Internal pull-down resistor |
libopencm3
authors, along with the function definition can be found
hereHaving clarified that, as we want to drive the LEDs, we will need to configure the pins as outputs with no internal pull-up or pull-down resistor:
gpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO8);
gpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO9);
Simplified using bitwise6 OR:
gpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO8 | GPIO9);
GPIO Output Options Setup
Now that the GPIO mode has been set up, the GPIO output options need to be defined as well. This will encompass the output type, and output speed:
Output Type | Description |
---|---|
GPIO_OTYPER_PP |
(default) Push-pull “totem pole” output |
GPIO_OTYPER_OD |
Open-drain output |
Output Speed | Description |
---|---|
GPIO_OSPEED_HIGH |
High output speed |
GPIO_OSPEED_MED |
Medium output speed |
GPIO_OSPEED_LOW |
(default) Low output speed |
GPIO_OSPEED_100MHZ |
Up to 100MHz output speed (equivalent to high) |
GPIO_OSPEED_50MHZ |
Up to 50MHz output speed |
GPIO_OSPEED_25MHZ |
Up to 25MHz output speed (equivalent to medium) |
GPIO_OSPEED_2MHZ |
Up to 2MHz output speed (equivalent to low) |
We’ll be driving an output LED, as opposed to sinking it (typical
open-drain/open-collector sink configuration), push-pull output mode will be
required. Since there isn’t any switching to be done aside from the initial
“on”, we don’t require any “speed” – “no speed” not being an option
GPIO_OSPEED_LOW
will suffice:
gpio_set_output_options(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_LOW, GPIO8);
gpio_set_output_options(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_LOW, GPIO9);
Simplified6:
gpio_set_output_options(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_LOW, GPIO8 | GPIO9);
Turn it On
There are no additional options required for the user to be able to now set, or clear, the desired GPIO pins. Thus, we set it and forget it:
gpio_set(GPIOC, GPIO8);
gpio_set(GPIOC, GPIO9);
Simplified6:
gpio_set(GPIOC, GPIO8 | GPIO9);
Lastly, we need to make sure our program never exits and does something undesirable by keeping it inside a loop:
while(1);
This is just a condensed version of the following:
while(1) {
; // Do nothing
}
Voila!