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.

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:

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. 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.

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 .

We need a C compiler for ARM

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 pin PA1 . 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!