Programming STM32 using Standard Peripheral Library and free tools – Part #1: A first example

8 bit and 32 bit microcontrollers are widely used in billions of electronic devices. Their development can be achieved by using free tools only. In this tutorial, I show you how to use the GNU tools (GCC / make) to develop firmware for a STM32 microcontroller in an Ubuntu environment.

We start with a basic example that uses the STMicroelectronics Standard Peripheral Library. This library provides several modules that can be used to program the STM32 peripherals like ADC, GPIO or SPI. This first example is about a blinking LED. It explains how to setup the project folder and the files that are required for the project. This also includes a working main.c and makefile.

The makefile is that part of the project that puts it all together – the (third party) Standard Peripheral Library and your project files (main.c etc.). I use a simple project structure that results in a simple makefile. I also describe the makefile in detail, so the reader will be able to understand how it works and how to adjust it for his own needs.

This first part of this tutorial is about the development environment. It describes the installation of the Linux operating system. I use Ubuntu in the version 16.04, and I installed it in a VirtualBox.


Here is the complete project Part #1: A first example available for download. Extract it to your Home folder, so that you get the following result:

+- <GCCDevelopment>
   +- <STM32F10x_StdPeriph_Lib_v3.5.0> 
   +- <Blinky>

The build environment

VirtulBox is a virtualization software and can be installed on all common operating systems. It supports the creation and management of guest virtual machines running versions and derivations of Windows, Linux and others.

There are several tutorials available on the internet. This is one of them.

Ubuntu 16.04 running in VirtualBox

There are a view tools that we need for development. One of them is the GCC ARM embedded toolchain. In this tutorial, i chose version 7-2018-q2-update (Linux, 64 bit) from ARM’s website. There is a nice tutorial about how to install the toolchain that i will follow here.


lib32ncurses5 is a prerequisite for the GCC ARM toolchain. Open a Terminal window and type in the following commands:

> sudo apt-get -y install lib32ncurses5

The toolchain will be installed locally in our Home folder. The following commands creates a sub-folder (/opt), moves to this folder and extracts the downloaded GCC ARM toolchain to it.

> mkdir -p "${HOME}"/opt
> cd "${HOME}"/opt
> tar xjf ~/Downloads/gcc-arm-none-eabi-7-2018-q2-update-linux.tar.bz2
> chmod -R -w "${HOME}"/opt/gcc-arm-none-eabi-7-2018-q2-update

You can prove your build environment by typing

> "${HOME}"/opt/gcc-arm-none-eabi-7-2018-q2-update/bin/arm-none-eabi-gcc --version

The result should look like this:

Validating the toolchain

Our toolchain is now up and running, so let’s move on to the first example project Blinky.

A first example Blinky

The first example project Blinky is based on the Standard Peripheral Library from STMicroelectronics. You can download it from here:

The Standard Peripheral Library includes the software modules for the STM32 peripherals like the real time clock control (RCC) or the general purpose IO’s (GPIO). It eases much of the life of a STM32 developer, and so I also use it as a base.

Every project that is build for STM32 requires some special files (and functions):

  • register definition file (stm32f10x.h)
  • system files (system_stm32f10x.c and system_stm32f10x.h)
  • configuration file (stm32f10x_conf.h)
  • interrupt handlers (stm32f10x_it.c and stm32f10x_it.h)
  • startup code (startup_stm32f10x_md.s)
  • linker file (stm32_flash.ld)
  • main function / main file (main.c)
  • makefile

Finally, we also need some modules from the Standard Peripheral Library (misc.c, stm32f10x_rcc.c and stm32f10x_gpio.c) for this sample project.

Extract the Standard Peripheral Library that you did download to StdPeriphLib in the GCCDevelopment folder. This folder also will include the Blinky project. Here are all files that you must copy from the Standard Peripheral Library folder to our new Blinky project folder. Except for main.c and makefile (see below).

+- <GCCDevelopment>
   +- <StdPeripLib>
   +- <Blinky>
      +- stm32f10x.h            (from <StdPeripLib>/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/)
      +- system_stm32f10x.h     (from <StdPeripLib>/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/)
      +- system_stm32f10x.c     (from <StdPeripLib>/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/)
      +- startup_stm32f10x_md.s (from <StdPeripLib>/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/TrueSTUDIO/)
      +- stm32f10x_conf.h       (from <StdPeripLib>/Project/STM32F10x_StdPeriph_Template/)
      +- stm32f10x_it.h         (from <StdPeripLib>/Project/STM32F10x_StdPeriph_Template/)
      +- stm32f10x_it.c         (from <StdPeripLib>/Project/STM32F10x_StdPeriph_Template/)
      +- stm32_flash.ld         (from <StdPeripLib>/Project/STM32F10x_StdPeriph_Template/TrueSTUDIO/STM3210B-EVAL/)
      +- misc.c                 (from <StdPeripLib>/Libraries/STM32F10x_StdPeriph_Driver/src/)
      +- stm32f10x_rcc.c        (from <StdPeripLib>/Libraries/STM32F10x_StdPeriph_Driver/src/)
      +- stm32f10x_gpio.c       (from <StdPeripLib>/Libraries/STM32F10x_StdPeriph_Driver/src/)
      +- main.c                 (see below)
      +- makefile               (see below)

After copying all these files to your Blinky folder, it should look something like this:

Image: Blinky folder

Here is a short description of the files deserved by Blinky.

The register definition file (stm32f10x.h)

This file contains all the peripheral register’s definitions, bits definitions and memory mapping for STM32F10x devices. it also contains a configuration section that allows to select the device used in the target application.

It is unique to the project and the device, therefore it is recomended to create a local copy of it in the project folder and to adjust it to reflect our target device.

The configuration file (stm32f10x_conf.h)

This file is used for peripheral header inclusion. Just add it to your project folder and leave it untouched.

The interrupt handlers (stm32f10x_it.c and stm32f10x_it.h)

The Cortex-M3 provides a feature-packed exception architecture that supports a number of system exceptions and external interrupts. Exceptions are numbered 1–15 for system exceptions and 16 and above for external interrupt inputs. The stm32f10x_it.c and stm32f10x_it.h files provides template for all exceptions handler and peripherals interrupt service routine.

You can leave these files untouched.

The startup code (startup_stm32f10x_md.s)

All CPU- and board-specific low-level initialization that needs to occur before entering the main() function should be handled. This is done in the startup code.

The startup code that comes with the Standard Peripheral Library is written in assembly language. It is a generic file that you can leave untouched.

The linker file (stm32_flash.ld)

The linker script must match the startup code described above for all the section names and other linker symbols. It cannot be generic, because it must define the specific memory map of the target device, as well as other application-specific information.

This file was developed for your target device, so there is no reason to change it. You can leave this file untouched too.

The main function / main file (main.c)
#include "stm32f10x.h"

void initGPIO()
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_SET);

int main(void)
    uint32_t i;


    while (1) 
        for (i = 0; i < 5000000; i++) ;
        GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_SET);

        for (i = 0; i < 5000000; i++) ;
        GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_RESET);

The main() function initializes Pin 5 of Port B to output and toggles it in the while() loop. You can copy and paste that code snipped to your main.c file.

The makefile
# Project dependencies
PROJECT := Blinky

# Build tools
CC      = ~/opt/gcc-arm-none-eabi-7-2018-q2-update/bin/arm-none-eabi-gcc
OBJCOPY = ~/opt/gcc-arm-none-eabi-7-2018-q2-update/bin/arm-none-eabi-objcopy

# Compiler options
CFLAGS  = -ggdb -O0 -Wall -Wextra -Warray-bounds -mlittle-endian
CFLAGS  += -mthumb -mcpu=cortex-m3 -mthumb-interwork

# Linker file / options
LFLAGS  = -T$(wildcard *.ld)
LFLAGS += --specs=nano.specs --specs=nosys.specs 
LFLAGS += -Wl,-Map -Wl,$(PROJECT).map

# Directories to be searched for header files
INCLUDE = -I../STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/CMSIS/CM3/CoreSupport 
INCLUDE += -I../STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/STM32F10x_StdPeriph_Driver/inc

# Object files to be created
OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c)) $(patsubst %.s, %.o, $(wildcard *.s))

# Add Standard Peripheral Library to the build configuration

# Build targets
.PHONY: all
all: $(PROJECT).elf

%.o: %.c
	$(CC) $(INCLUDE) $(DEFS) $(CFLAGS) -c $< -o $@

%.o: %.s
	$(CC) $(INCLUDE) $(DEFS) $(CFLAGS) -c $< -o $@

	$(OBJCOPY) -O binary $(PROJECT).elf $(PROJECT).bin

.PHONY: clean
	rm -rf *.o *.bin *.elf *.map

The job of the makefile is to compile and link the project files to a binary file, which can be used then to flash the microcontroller. It defines the build tools (GCC and OBJCOPY), the compiler and linker flags (specific for the STM32 target device) and creates the object files from their C and assembly sources. Finally, the object files are put together to a binary and .elf file.

Just copy and paste that code to your makefile.

Build your project

Switch to your project folder Blinky and type in

> make

You might see this error message:

"Please select first the target STM32F10x device used in your application (in stm32f10x.h file)"

Compare to the following image:

Image: Missing definition in stm32f10x.h

You have to select the target STM32F10x device used in your application first!

The stm32f10x.h header file describes the memory maps and pin definitions for the whole STM32 F1 device family. Depending on your target device – I use a medium density device (MD) – unmask one of the #defines in lines 66 to 73.

Image: Select your target device in stm32f10x.h file

Again, type

> make

and your project should compile and link.

Image: Compiling and linking

Setting up a project for STM32 microcontrollers is not a simple thing. You have to know about a whole lot of files that you must include to your project. You need to configure stm32f10x.h and main.c must be written too. Last but not least, a matching makefile to compile and link your project is required. As a result, binary and .elf files are created which you can use to flash your device.

Flashing your device requires some additional tools: OpenOCD and a JTAG device.
How this is done, I show you in the next part of this tutorial.

Leave a Reply

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

52 + = 62