A template project for STM32 on Linux

I am a beginner in electronics and programming for microcontrollers, so traditionally I started from driving an LED which is a ‘Hello World!’ project in microcontrollers world. I started with STM32 controller (STM32F103) which is ARM, and I can use C language.
When my LED finally started blinking, I thought it might be useful from me (and hope I can be useful for someone else) if I create a simple re-usable template project. As a result, now there is one more repository on GitHub.
I use Linux, no fancy GUI, sorry.

STM32 board, ST-LINK/V2 debugger/programmer

Before I started, of course I asked Google how people work with STM32 on Linux. I found two good articles which helped me a lot:
  1. STM32 Discovery Development On Linux – this article is about STM32F4Discovery which has an ST-LINK/V2 debugger/programmer. But I had only STM32F103 board which is similar, but still a little bit different.
  2. Programming ARM Cortex (STM32) under GNU/Linux – this one is about STM32F4 as well.
Probably there are articles about STM32F103 on the Internet, but I gave up googling.

STM32, what else?

Let’s talk about hardware.
To be honest, it’s much easier to use some STM32 Discovery board. But it’s so big … Some of these boards even contain an LCD display which is cool but … Is there anything smaller?
Luckily there is a Maple Mini project which is based on STM32 microcontroller. But I found a clone from guys in China … It was only $1.90. I was so happy, so I ordered two. Ah, no free shipping … fine, it’s $4.24 in total. Not too much I guess.
This board is based on STM32F103C8T6, but it doesn’t have a built-in debugger/programmer like STM32F4Discovery. So I needed a programmer then. Guys from China saved me again. I found a clone of ST-LINK/V2 programmer. It was only $3.45. And free shipping, hooray!
I also needed an LED and a resistor. The resistor may be 200 or 330 Ohms. I wrote a separate post about connecting an LED to a microcontroller, and choosing a current limiting resistor.

Connecting an LED to STM32 with a current limiting resistor

By the way, all stuff arrived in a couple of weeks which I think is pretty fast for AliExpress. But maybe I just was lucky. An LED should be connected to pin PA1.

STM32 board, ST-LINK/V2 debugger/programmer

We need a C compiler for ARM

I used GNU Tools for ARM Embedded Processors toolchain. It can be downloaded here, but I just installed it with apt-get on myUbuntu 16.04:
sudo aptitude install gcc-arm-none-eabi

As a sanity check, you can just print arm-none-eabi-gcc version:

arm-none-eabi-gcc --version
arm-none-eabi-gcc (15:4.9.3+svn227297-1) 4.9.3 20150529 (prerelease)
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

We have hardware, compiler, that’s all?

Not really.
I needed STLINK software to upload the code to microcontroller. Sources are available on GitHub. I just cloned the repo, and built it. It needs a couple of tools and libs to install. On Ubuntu 16.04 I did the following:
sudo apt-get install automake autoconf pkg-config libusb-1.0 git

When it’s done, STLINK can be built with the following commands:

git clone https://github.com/texane/stlink.git stlink
cd stlink
./autogen.sh
./configure
make

Can we already start driving an LED?

Yeah, finally.
STMicroelectronics provides STM32 standard peripheral library which contains APIs to work with STM32 microcontrollers. I used version 3.5.0. The library can be downloaded here. Another option is to use STM32Cube which seems to be recommended by STMicroelectronics.
Now we can finally start writing code. As I mentioned earlier, I uploaded the code on GitHub:
git clone https://github.com/artem-smotrakov/stm32f103-template

I wanted to keep it simple, so it doesn’t have much stuff.

main.c is the main program. First, main() function initializes pinPA1. Then, it starts an infinite loop which turns on/off the LED with a delay.
#include "main.h"

void delay(int millis) {
while (millis-- > 0) {
volatile int x = 5971;
while (x-- > 0) {
__asm("nop");
}
}
}

int main(void) {

// GPIO structure for port initialization
GPIO_InitTypeDef GPIO_InitStructure;

// enable clock on APB2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// configure port A1 for driving an LED
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // output push-pull mode
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // highest speed
GPIO_Init(GPIOA, &GPIO_InitStructure) ; // initialize port

// main loop
while(1) {
GPIO_SetBits(GPIOA, GPIO_Pin_1); // turn the LED on
delay(DELAY);

GPIO_ResetBits(GPIOA, GPIO_Pin_1); // turn the LED off
delay(DELAY);
}
}
main.h is a header file which includes a couple of other header files from STM32 standard peripheral library.
#ifndef __MAIN_H
#define __MAIN_H

#include <stm32f10x.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>

#define DELAY 1500 // in millis

#endif
stm32f10x_conf.h is a header file which is used by STM32 standard peripheral library. It contains only assert_param() function which does nothing.
 
#ifndef __STM32F10x_CONF_H
#define __STM32F10x_CONF_H

// no asserts
#define assert_param(expr) ((void)0)

#endif

Compilation fails without this function with errors like the following:

/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.c: In function 'RCC_HSEConfig':
/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.c:273:3: warning: implicit declaration of function 'assert_param' [-Wimplicit-function-declaration]
   assert_param(IS_RCC_HSE(RCC_HSE));
   ^
/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_gpio.c: In function 'GPIO_DeInit':
/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_gpio.c:111:3: warning: implicit declaration of function 'assert_param' [-Wimplicit-function-declaration]
   assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
   ^
/tmp/ccvazqC6.o: In function `RCC_HSEConfig':
/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.c:273: undefined reference to `assert_param'
/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.c:273: undefined reference to `assert_param'
/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.c:273: undefined reference to `assert_param'
/tmp/ccvazqC6.o: In function `RCC_AdjustHSICalibrationValue':
/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.c:338: undefined reference to `assert_param'
/tmp/ccvazqC6.o: In function `RCC_HSICmd':
/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.c:357: undefined reference to `assert_param'
/tmp/ccvazqC6.o:/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.c:383: more undefined references to `assert_param' follow
collect2: error: ld returned 1 exit status
Makefile:39: recipe for target 'led.elf' failed
make: *** [led.elf] Error 1
Makefile is a makefile. Nothing surprising.
# path to STM32F103 standard peripheral library
STD_PERIPH_LIBS ?= ./STM32F10x_StdPeriph_Lib_V3.5.0/

# list of source files
SOURCES = main.c
SOURCES += $(STD_PERIPH_LIBS)/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c
SOURCES += $(STD_PERIPH_LIBS)/Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.c
SOURCES += $(STD_PERIPH_LIBS)/Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_gpio.c
SOURCES += $(STD_PERIPH_LIBS)/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/TrueSTUDIO/startup_stm32f10x_md.s

# name for output binary files
PROJECT ?= led

# compiler, objcopy (should be in PATH)
CC = arm-none-eabi-gcc
OBJCOPY = arm-none-eabi-objcopy

# path to st-flash (or should be specified in PATH)
ST_FLASH ?= st-flash

# specify compiler flags
CFLAGS = -g -O2 -Wall
CFLAGS += -T$(STD_PERIPH_LIBS)/Project/STM32F10x_StdPeriph_Template/TrueSTUDIO/STM3210B-EVAL/stm32_flash.ld
CFLAGS += -mlittle-endian -mthumb -mcpu=cortex-m4 -mthumb-interwork
CFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16
CFLAGS += -DSTM32F10X_MD -DUSE_STDPERIPH_DRIVER
CFLAGS += -Wl,--gc-sections
CFLAGS += -I.
CFLAGS += -I$(STD_PERIPH_LIBS)/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/
CFLAGS += -I$(STD_PERIPH_LIBS)/Libraries/CMSIS/CM3/CoreSupport
CFLAGS += -I$(STD_PERIPH_LIBS)/Libraries/STM32F10x_StdPeriph_Driver/inc

OBJS = $(SOURCES:.c=.o)

all: $(PROJECT).elf

# compile
$(PROJECT).elf: $(SOURCES)
$(CC) $(CFLAGS) $^ -o $@
$(OBJCOPY) -O ihex $(PROJECT).elf $(PROJECT).hex
$(OBJCOPY) -O binary $(PROJECT).elf $(PROJECT).bin

# remove binary files
clean:
rm -f *.o *.elf *.hex *.bin

# flash
burn:
sudo $(ST_FLASH) write $(PROJECT).bin 0x8000000

It has three targets:

  • “all” target compiles sources
  • “clean” remove binaries which were produced by “all” target
  • “burn” runs st-flash utility which uploads binaries to a microcontroller. It requires super-user permissions.

Note: the flags seems to be a bit redundant, please also see Ivan’s comment below about flags.

SOURCES variable has a list of source files to be compiled. Besides of main.c, it contains a couple of files from STM32 standard peripheral library.
Finally, the code can be build and uploaded to the device with commands like the following:
STD_PERIPH_LIBS=/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0/ make all
ST_FLASH=/home/artem/tools/stlink/st-flash make burn
STD_PERIPH_LIBS variable contains a path to STM32 standard peripheral library which was downloaded before. ST_FLASH variable contains a path to st-flash utility which was built earlier.
 
You can see something like the following if everything went smoothly:
arm-none-eabi-gcc -g -O2 -Wall -T/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Project/STM32F10x_StdPeriph_Template/TrueSTUDIO/STM3210B-EVAL/stm32_flash.ld -mlittle-endian -mthumb -mcpu=cortex-m4 -mthumb-interwork -mfloat-abi=hard -mfpu=fpv4-sp-d16 -DSTM32F10X_MD -DUSE_STDPERIPH_DRIVER -Wl,--gc-sections -I. -I/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/ -I/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/CMSIS/CM3/CoreSupport -I/home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/inc main.c /home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c /home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.c /home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_gpio.c /home/artem/projects/stm32/src/STM32F10x_StdPeriph_Lib_V3.5.0//Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/TrueSTUDIO/startup_stm32f10x_md.s -o led.elf
arm-none-eabi-objcopy -O ihex led.elf led.hex
arm-none-eabi-objcopy -O binary led.elf led.bin
sudo /home/artem/tools/stlink/st-flash write led.bin 0x8000000
2016-07-04T23:11:34 INFO src/common.c: Loading device parameters....
2016-07-04T23:11:34 INFO src/common.c: Device connected is: F1 Medium-density device, id 0x20036410
2016-07-04T23:11:34 INFO src/common.c: SRAM size: 0x5000 bytes (20 KiB), Flash: 0x10000 bytes (64 KiB) in pages of 1024 bytes
2016-07-04T23:11:34 INFO src/common.c: Attempting to write 4040 (0xfc8) bytes to stm32 address: 134217728 (0x8000000)
Flash page at addr: 0x08000c00 erased
2016-07-04T23:11:35 INFO src/common.c: Finished erasing 4 pages of 1024 (0x400) bytes
2016-07-04T23:11:35 INFO src/common.c: Starting Flash write for VL/F0/F3 core id
2016-07-04T23:11:35 INFO src/flash_loader.c: Successfully loaded flash loader in sram
  3/3 pages written
2016-07-04T23:11:35 INFO src/common.c: Starting verification of write complete
2016-07-04T23:11:35 INFO src/common.c: Flash written and verified! jolly good!
That’s pretty much it. Have a next day!
Share

11 thoughts on “A template project for STM32 on Linux

  1. AvatarКоляныч

    Привет Артём!
    Что это и для чего? Опиши на нашем могучем…

    Reply
    1. artemartem Post author

      Привет!

      Это простая программа для STM32F1 микроконтроллера, которая просто мигает светодиодом, который к нему подключен. Это своего рода ‘Hello World!’ в для микроконтроллера. Собственно, пользы от мигания светодиодом нет ровно никакой, но это можно брать за основу для какого-то более сложного проекта. Я там описал, как и чем компилировать код, как и чем его загружать в микроконтроллер, какие библиотеки можно использовать, некоторые проблемы, которые могут возникнуть. И все это для Linux, для других платформ немного по-другому.

      Надо перевести целиком, хорошая идея :)

      Reply
  2. AvatarDarioscorp

    Hello, good job, I’m also wanting to make my “Hello world” with a Maple Mini Clone, I had a question: Is there another way to record the mini maple without the STLink ?.
    Can it be done via USB?
    Maybe the maple has a preloaded bootloader?
    Greetings from Bolivia

    Reply
    1. artemartem Post author

      To be honest, I didn’t get a chance to look for other ways to flash STM32. If you mean that USB port that’s locate on the board, then I am not sure if it’s possible to use it to flash the board. I think you’ll need a programmer anyway. The programmer which I used works via USB. You can also try to use OpenOCD software for flashing, but I have not tried it. Let me know if you figure out what else can be used! Thanks!

      Reply
  3. AvatarEduardo

    Hi!
    Why in your Makefile you are using the flags:

    CFLAGS += -mlittle-endian -mthumb -mcpu=cortex-m4 -mthumb-interwork
    CFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16

    This looks like you are passing flags for STM32F4… Is right?

    Reply
    1. ArtemArtem Post author

      Hi Eduardo,

      Those flags are for compiler to help it build right binaries for STM32F103. The flags may be different for STM32F4. For example:

      -mcpu=cortex-m4 specifies the target processor
      -mlittle-endian compiles code for little endian target

      To be honest, I am not an expert in STM32 to give a good explanation why the rest of them are necessary. When I was writing the post, I used a couple of sources I mentioned there. The following post explains some options:

      http://regalis.com.pl/en/arm-cortex-stm32-gnulinux (it’s actually about F4 but apparently it mostly works for F1 as well)

      And the following page describes GCC options:

      https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html

      I would be happy if you can share more info about this topic in case if find something!

      Reply
  4. AvatarIvan

    Great article! Dude, you save my day. Just few remarks: 1) in the blue pill, the board shown on the picture, the build in green led is connected to PC13 . 2) There is no HW floating point unit in M3 and stm32f103 is M3 (not M4), so these flags should be changed in the Makefile (for the blinking program they are ok, but for some more complex project may bring issues)

    Reply
    1. ArtemArtem Post author

      I updated the post with a link to your comment. Thanks for your feedback!

      Reply
    1. ArtemArtem Post author

      Hey,

      The flags are used by STM32F10x standard peripheral library.

      Unfortunately I don’t remember the details, and I could not find the docs online. According to the stm32f10x.h file, the STM32F10x_MD flag should configure the library for medium-density devices. USE_STDPERIPH_DRIVER enables the peripherals drivers, so that the application will not directly access to peripherals registers.

      Reply

Leave a Reply

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