STM32F411 blink sketch using CMSIS, Vim, and a Makefile

Tired with all the IDE and library gubbings, I thought I’d try to make a blink sketch for the black pill using just basic tools.

You’ll need to install ST’s CMSIS, which you can download from here. This defines memory addresses. We need those. In the past I’ve even circumvented even these definitions, but I thought it would make sense to use vendor-supplied definitions.

You will also need to install GCC arm-none-eabi, which ought to be available from your Linux distros. I won’t go into the installation process.

To blink, you need the main source file, main.c:

     1	#define STM32F411xE
     2	#include "stm32f4xx.h"
     3	
     4	void __attribute__((optimize("O0"))) delay (uint32_t time) 
     5	{    
     6		static uint32_t i;
     7		for (i=0; i<time; i++) {}    
     8	}
     9	
    10	int main (void) 
    11	{   
    12	
    13		RCC->AHB1ENR     |= RCC_AHB1ENR_GPIOCEN; //RCC ON
    14	
    15		GPIOC->MODER    |= GPIO_MODER_MODER13_0; //mode out
    16		GPIOC->OTYPER   = 0;
    17		GPIOC->OSPEEDR  = 0;
    18	
    19		while (1) 
    20		{
    21			GPIOC->ODR ^=   GPIO_ODR_OD13;
    22			delay(1000000);
    23		}
    24	}

In lines 1 and 2, we need to define the MCU and an include file that we take from CMSIS.

Lines 4-8 provide a delay function. We use a GCC attribute to turn off any optimisations that GCC might be tempted to perform.

We’re going to toggle pin PC13 on the blackpill, which is the built-in LED. I have tested the binary on a Nucleo F411 board, and I confirm that the code still works. The Nucleo board uses a different GPIO pin for its built-in LED, so I opted to attach an LED and resistor in series with PC13.

Line 13 enables the clock for GPIO port C. Lines 15-17 sets pin 13 of port C as an output port. I haven’t set things up robustly, I just have coded something that works for testing purposes.

Line 21 toggles pin PC13. Line 22 adds a delay.

Makefiles are usually complicated affairs when it comes to microcontrollers, but I have managed to make mine very simple:

     1	CC = arm-none-eabi-gcc
     2	AS = arm-none-eabi-as
     3	LD = arm-none-eabi-ld
     4	BIN = arm-none-eabi-objcopy
     5	CFLAGS = -mthumb -mcpu=cortex-m4 -ggdb -Os
     6	#CFLAGS += -I.
     7	CFLAGS += -nostdlib
     8	CFLAGS += -fdata-sections -ffunction-sections # for unused code
     9	
    10	CMSIS = /home/pi/STM32Cube/Repository/STM32Cube_FW_F4_V1.25.2/Drivers/CMSIS
    11	CFLAGS += -I$(CMSIS)/Device/ST/STM32F4xx/Include/
    12	CFLAGS += -I$(CMSIS)/Include/
    13	
    14	OBJS = main.o startup_stm32f411retx.o
    15	
    16	LDFLAGS = -T STM32F411RETX_FLASH.ld
    17	
    18	all: app.bin
    19	
    20	app.elf: $(OBJS)
    21		$(LD) $(LDFLAGS) -o app.elf $(OBJS)
    22	
    23	app.bin: app.elf
    24		$(BIN) -O binary app.elf app.bin
    25		arm-none-eabi-objdump -d app.elf >app.txt
    26	
    27	%.o : %.c
    28		$(CC) $(CFLAGS) -c -o $@ $<
    29	
    30	%.o : %.s
    31		$(AS) -o $@ $<
    32	
    33	clean :
    34		rm -f *.o *.a .doc 

Lines 1-4 specify the compiler, linker, assembler and object dumper.

Line 5 configures compiler flags for the F411, which runs a cortex M4. If you use a different MCU, then you will likely need to adjust this line. I’ve also added debugging.

line 7 says that we won’t link in a C Library. This is important, because otherwise you would link with a Linux C library. There’s no chance of that working. When people want a C library, they either use a vendor-supplied one, or adapt something like the newlib library.

It’s something I played with in the past, kinda got working, but it was quite a chore. I’m now inclining more towards cobbling together bits from the internet and just rolling my own stuff. Tht was mostly the approach I used in Crunky, a kind of unikernel that I was developing for the Raspberry Pi 0. I would probably reuse a lot of that code if I wanted a library. It’s missing a lot of stuff, like malloc and free. But, meh, these are microcontrollers, we don’t need no stinking malloc.

Line 6 separates functions into separate sections. This is done so that at the linking stage, the compiler can remove unused code, thereby saving on binary size.

Line 10 sets a root directory for the CMSIS drivers. You will need to change that in your own code.

Lines 11-12 set include paths off from this root directory.

Line 14 sets two object files: the main file shown above, and some startup code in startup_stm32f411retx.s. I’m not going to go through it, as it is rather long and tedious. I used one which was autogenerated by CubeIDE. I think we can just reuse it in most projects. I did comment out the lines referring to __libc_init_array and SystemInit. Presumably the first routine does some kind of initialisation for a C library. Since we don’t have one, we can eliminate it. I’m not sure what SystemInit is supposed to contain, but I’ve taken a chance and figured that the uC should start up fine without it.

The linker script is likewise fairly long and tedious. It contains fairly typical stuff that you’d find in a linker script. I’m not going to cover any description here.

Line 16 of the Makefile names the linker script.

The rest of the lines compile and link the files in a typical way.

So there we have it, a complete project that should get you compiling a binary. It is 616 bytes long. It is way shorter, by a country mile, than I would likely expect any library system to produce.

I don’t think I’ve got the linker set up correctly to produce interrupts in the binary. That’s something that will need further investigation. It does work as-is for blinking, though, so at least it’s a start. The Makefile produces a disassembled version of the code in app.txt, which often contains clues that something might be amiss.

So I hope you found that useful. You can find all the code in a github directory. Your best bet is to download the whole repo and tinker with the Makefile. The linker script probably doesn’t need much adaption to different processors in the STM32 series. The startup script is likely to need some work on account of the fact that different processors are likely to have different interrupt tables.

Have fun.

About mcturra2000

Computer programmer living in Scotland.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s