not only about programming…

This article is a complete intruduction to programming ARM Cortex microcontrolers under GNU/Linux. I will describe how to set up the environment to be able to code, compile, and flash applications into your STM32 MCU. There is no need to install non-free, proprietary, user subordinating software.

What do we need?

We need only few obvious things:

STM32 microcontroller

programmer and debugger

GNU/Linux operating system

In this article, I will use STM32 Nucleo board with STM32F401RE microcontroller. I really recommend this board for all beginners – it’s powerfull and cheap. One of the best advantage of STM32 Nucleo board is that it does not require any separate probe as it integrates the ST-LINK/V2-1 debugger/programmer. Integrated ST-LINK can be also used to program/debug other MCUs.

If you own any other board/MCU (like STM32 Discovery) it shouldn’t be a problem, all steps will be exactly the same except of selecting library version.

All of above packages should be available in your GNU/Linux distribution via default repositories. Note that they could have different names, for example in Debian GNU/Linux and Ubuntu the arm-none-eabi-gcc is gcc-arm-none-eabi.

Tough choice

Before we start, you need to choose between two extremely different possibilities. The first possibility is to use high-level libraries provided by STMicroelectronics (containing the hardware abstraction layer (HAL) for the STM32 peripherals). The second possibility is to learn how to interact with hardware and write your own functions to control MCU internals. As usual, both ways have their own advantages and disadvantages. High-level libraries are developed to be universal which mean you can use the same functions to control different devices, in this case it is obvious that some part of such libraries need to perform a lot extra operations (it will consume more memory and CPU time). As it comes to STM32 HAL library – redundant and overprotective code could be (in most cases) optimized at compile time. High-level libraries are also developed to be an easy to use proxy to complicated internals. What would such an approach mean in practice? Well, this mean that such a library hides as much internals as it is possible – in this case user can focus on what to do instead of how to do it.

I strongly recommend to start without high-level libraries and spend some time reading about MCU internals (datasheet, programming manual, reference manual), this will imply better understanding of the processes taking place inside the processor.

How much is a programmer worth if she/he does not understand the programmed device?

In this part of article I will describe how to start with no external libraries. Second part of article (coming soon) will describe how to compile and use STM32 HAL Driver.

CMSIS – Cortex Microcontroller Software Interface Standard

The ARM® Cortex® Microcontroller Software Interface Standard (CMSIS) is a vendor-independent hardware abstraction layer for the Cortex-M processor series and specifies debugger interfaces. The CMSIS consists of the following components:

<device>.h – contains device specific informations: interrupt numbers (IRQn) for all exceptions and interrupts of the device, definitions for the Peripheral Access to all device peripherals (all data structures and the address mapping for device-specific peripherals). It also provide additional helper functions for peripherals that are useful for programming of these peripherals.

startup_<device>.s – startup code and system configuration code (reset handler which is executed after CPU reset, exception vectors of the Cortex-M Processor, interrupt vectors that are device specific).

STM32Cube

STMCube is an STMicroelectronics original initiative to ease developers life by reducing development efforts, time and cost. (…)

As you can see, STMicroelectronics introduces STMCube as an initiative to ease developers life. They are sharing packages containing libraries, documentation and examples. Packages are delivered per series (such as STM32CubeF4 for STM32F4 series). In this article I will describe STM32CubeF4 package.

Getting STM32CubeF4

First of all, we need to download STM32CubeF4 package. You can get it from STMicroelectronics official site: here.

STM32CubeF4 content

After unpacking STM32CubeF4 package, we should have the following directory structure:

I need to mention one special file named stm32f4xx_hal_conf_template.h. It is the only one file we need to copy into our project directory and name it stm32f4xx_hal_conf.h. But for now – let’s forget about it.

Let’s start with describing MCU’s startup procedure. After reset (power on) MCU works with HSI (internal high-speed oscilator) as system clock source. In my case (STM32F401RE), HSI = 16MHz. Assuming that we boot from Main Flash memory, MCU starts code execution from the boot memory starting from 0×00000004. This is the place where we need to put an address of initialization function. This function is usually named Reset_Handler and must do the following job:

set stack pointer (usually at the end of SRAM)

copy .data section from flash to SRAM

zero fill the .bss section (in SRAM)

call CMSIS SystemInit() function

call libc __libc_init_array() function

call main()

STMicroelectronics provides startup code in file startup_stm32f401xe.s (assembler), we need to copy it from STM32CubeF4Root/Drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc/startup_stm32f401xe.s or write own implementation.

Now, let’s discuss the role of SystemInit() function:

configure embedded linear voltage regulator

configure clock source:

calibrate internal HSI

set HSI as PLL source

configure PLL

enable PLL

wait until PLL becomes stable

configure Flash memory:

enable instruction cache

enable prefetch buffer

set correct latency

set system clock source to PLL

configure HCLK

configure APB1 and APB2 prescallers

Note: I use HSI as an input clock for PLL. You can replace it with HSE if you are using external, more accurate clock source.

I already implemented all above steps for my board (Nucleo with STM32F401RE):

/*
*
* Copyright (C) Patryk Jaworski <regalis@regalis.com.pl>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/#include <stm32f4xx.h>#define LED_PIN 5#define LED_ON() GPIOA->BSRRL |= (1 << 5)#define LED_OFF() GPIOA->BSRRH |= (1 << 5)int main(){/* Enbale GPIOA clock */
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;/* Configure GPIOA pin 5 as output */
GPIOA->MODER |=(1<<(LED_PIN <<1));/* Configure GPIOA pin 5 in max speed */
GPIOA->OSPEEDR |=(3<<(LED_PIN <<1));/* Turn on the LED */
LED_ON();}

When we have all required files (system.c, main.c, startup_stm32f401xe.s), we can compile the project. I use the following command to compile single file:

-ISTM32CubeF4Root/Drivers/CMSIS/Include – append directory to compiler list of directories which will be used to search for headers included with #include preprocessor directive. Note: replace STM32CubeF4Root with an absolute path to your STM32 Cube root directory

-DSTM32F401xE – define target processor (used in device header files)

-Os – optimize for size

-c – do not run linker, just compile

system.c – input file name

-o system.o – output file name

You need to perform this operation for all your source files. After successfull compilation, you need to have .o files for all your .c and .s sources.

To link *.o files into single “executable”, I use the following command:

-TSTM32CubeF4Root/Projects/STM32F4xx-Nucleo/Templates/TrueSTUDIO/STM32F4xx-Nucleo/STM32F401CE_FLASH.ld – use specific linker script, I use script provided in STM32 Cube package. As above, you need to replace STM32CubeF4Root with an absolute path to your STM32 Cube root directory

After successfull connection, openocd will accept commands on localhost port 4444. We need to open new terminal and run:

$ telnet localhost 4444

Then, in openocd telnet session:

> reset halt
> flash write_image erase main.hex
> reset run

The best practice is to put all of above commands into single Makefile, I will describe how to do this in next part of this article (coming soon).

Happy hacking!

Copyright (C) 2014-2015 Patryk Jaworski <regalis@regalis.com.pl>.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
A copy of the license is included in the section entitled “GNU
Free Documentation License”.

Post navigation

42 thoughts on “Programming ARM Cortex (STM32) under GNU/Linux”

Hi,
your tutorial was quite helpful for me understanding how to compile source code using an arm-none-eabi-gcc toolchain for the nucleo F401RE board.
Could you provide a listing of the files in your final project folder too? (with the tree command for example?)
I struggle with the problem, that during startup the call to __libc_init_array seems to hang in an infinity loop … have you expericences something similar or have some advice?
Thx

The STM32F401CE_FLASH.ld in the STM32Cube zip has a really nasty copyright restriction \’This file may only be built (assembled or compiled and linked) using the Atollic TrueSTUDIO(R) product. The use of this file together with other tools than Atollic TrueSTUDIO(R) is not permitted.\’Nasty!

Hi,i took some time to write a simple and generic Makefile for the STM32Cube libraries. Maybe you want to link it to your post. https://github.com/stv0g/stm32cube-gccIt should work with most STM32Cube bundes.It downloads the Cube files automatically and setups a template/example for you.There\’s only one thing, I not sure about:How do I compile the HAL source code?Do you add those C files to your object list which will be compiled?Or do you build those separately to a library which gets linked to your actual code?Regards,Steffen

I\’m trying to adhust your example to stm32f3-discovery board. When I\’m tring to compile the main.c I\’ve got:main.c: In function \’main\’:main.c:9:5: error: \’RCC_TypeDef\’ has no member named \’AHB1ENR\’ RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; ^main.c:9:18: error: \’RCC_AHB1ENR_GPIOAEN\’ undeclared (first use in this function) RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; I guess it\’s caused by differences between control registers or something else. But which exactly?p.s. Of course, i\’m using stm32cubef3.

Hi! I guess you are using Nucleo, then from STM32 Nucleo boards User Manual I see D13 (LED) is connected to PA5 (Table 11, page 35). You need to enable GPIOA clock in peripheral clock enable register, GPIOA is connected to APB2 bus. You can find this in Reference Manual (Figure 1., page 48). To enable GPIOA clock you need to find the right register, this will be APB2 peripheral clock enable register (RCC_APB2ENR). The last thing to do is to find the corresponding bit, this will be IOPAEN (IO port A clock enable). Please note, that GPIO registers also differs, you need to update LED initialization and LED_ON/LED_OFF macros.

I had a problem when trying to build with the TRUESTUDIO linker script, which caused the output .elf file to be empty (zero size binary). I switched over to using the SW4STM32 linker script (I made stm32cube setup the project for SW4STM32) and it everything worked fine. Also, I was using the STM32F072RB chip on it\\’s corresponding nucleo board, if that makes any difference (I used STM32F072RBTx_FLASH.ld instead of what was described in the tutorial).

Hi, im only 16 and i love electronics, i study on my own and don’t have accesses to any paid books! i have an LPC1768 which i program using kail, but i wanted to do it on Linux and not just code like a robot and understand how they work
so i read your article and i didn’t understand half the stuff you mentioned like NVIC etc, where should i get the back knowadge before doing these stuff? and also figuring them on my own, i mean how did you know you have to do this and wrote an article ?

Hi man,Thanks for this really cool article I am midway through it and it is really helpful.One of the things that is kind of shady though is the fact that the linker is not open source at all and has a really nasty copyright restriction as someone else put it as well.It would be a nice touch to provide your own linker script.If I make it myself first I will share it with you.Thanks.

Apparently there are some changes and you need to change main.c
BSRRL and BSRRH have been merged into one BSRR.
This means that you should change
#define LED_ON() GPIOA->BSRRL |= (1 <BSRRH |= (1 <BSRR |= (1 <BSRR |= (1 << 21) //bit 5 reset (0)

Finally got it to work, yeah!!!So in order to get it to work instead of running telnet localhost 4444 I runarm-none-eabi-gdb main.elfthen inside the prompt>target remote localhost:3333>monitor reset halt> load> reset run or continueThe only thing that worries me is that the ST-Link is flashing between red and green instead of having a permanent green.

Hi Patrykyou lost me at the SystemInit function. I do not understand APB1, APB2, HCLK, some more. When I compared to a SystemInit function in the downloaded cube zip, that version does not seem to wait for a PLL to settle. Why the difference?Then I also saw that you\\’re using STM32F401RE, while I have the Discovery board with the STM32F407VGT6. I do not know if I can use your SystemInit with my chip and board.

Thanks for the tutorial. I got my Nucleo-64 board STM32F103RB flashing a LED mostly by following your instructions. I agree totally with you, especially when firing up a new board, that it is best to do an initial program from scratch and get familiar with the hardware and all the tools. It\’s the only way to be really competent.Thanks for such a clear tutorial.

trying to make my board work is driving me insane!So here\’s what i did:1. I made a folder for my lpc1768 named cortex2. i put these files in it: \”core_cm3.h\”, \”LPC17xx.h\”, \”LPC17xx.ld\”(got from a random git hub), \”startup_LPC17xx.s\”, \”system_LPC17xx.h and c\”3. compiled the 3 .c files, all gave no warnings, no errors4, trying to compile the .s file gives me loads of errors! mostly saying bad instruction :|5. i tried to pick the pre compiled from keil folders and tried to skip to next step with already compiled startup code!6. here\’s what i get \”startup_LPC17xx.o: file not recognized: File format not recognizedcollect2: error: ld returned 1 exit status\”can you help me

Hi I want to develop the Ethernet based GPIO on/off using STM32F103.I have planned to start development in ubuntu. for can you provide the free ide tool and it should have some tutorial for start my project.

You should check out this project, it’s a simple command-line tool that automates almost all of this. You just need to fire up a terminal and write a command to have a new STM32 project up and running with CMSIS, linker scripts, startup files and HAL libraries already configured and included.https://github.com/gdelazzari/STM32Tool

Wonderful!!!
Very Big Thanks to you…:D
You appeared online like a god to help me clarify my confusions.
I wanted to switch to embedded linux. But, the complicated kernel level programming documents which mainly focus x86 based system developers, kept me in dark. I used to think, whatever low level drivers like UART/I2C/SPI…etc i wrote for Cortex M, can not be used on embedded linux. But, you clarified it to a lot extent. The only difference is you are using STMxxxx CPU, i’m using LPCxxxx CPUs in my project. I did every thing on keil & used to afraid to switch to linux (which i want to switch to for a long time) just because of that CMSIS/HAL structures used in Cortex. Secondly all PDFs i found starts directly accessing peripherals like SCSI/NIC…etc that looks horrible. In embedded systems most commonly used peripherals are I2C/UART/SPI…etc.

Now, i know it is possible to use all those drivers written in CMSIS format as it is on linux & compiling the with GCC.

I’m a complete newbie. I would be thankful to you, if you can suggest relevant online/pdf to study, especially for those who are coming from keil background like me.

Like:
low level drivers written in CMSIS format are kernel space drivers only in linux terminology i think. Is it require/is their a way to register/include them in kernel, so that while booting up, kernel will load my low level drivers like it does for linux core system drivers like char driver/tty…etc. I don’t know whether my question itself is correct, it may sound a non sense/silly to a linux developer…but, please, clarify/suggest some document online/article on how to use low level drivers written in CMSIS format. like you explained this article.