STM32/ARM Cortex-M3 HOWTO: Development under Ubuntu (Debian)

The Olimex blinky example

I have used this example before in this howto, but never touched the inner work of this simple example.

If you open main.c, you will see that there is a lot of other stuff other than a pure main that just toggle the bits. Just as a example there is some strange init code just to get the mcu up and running.

And when you build it, you notice that you don't just compile and run. You compile, link and convert... and in the middle you throw in some strange file called stm_h103_blink_rom.cmd.

So let's dissect this simple example, so we have a chance of understanding it a little better.

Download it, and do some cleaning

Let's get it again...

mkdir -p ~/stm32/stm32-example
cd ~/stm32/stm32-example
wget http://olimex.com/dev/soft/arm/STR/STM32-BLINK-LED-GCC-ECLIPSE-projects.rar
unrar x STM32-BLINK-LED-GCC-ECLIPSE-projects.rar 
cd projects/stm_h103/

And before we continue, let's ignore the output from the build since we already covered those files on the "Compile Olimex blinky" page. So let's remove them (and some others) so we can focus on the others.

rm main.bin main.list main.out main.dmp
make clean

Then there is some strange legacy openocd.cfg config file...

rm lpc2xxx_armusbocd.cfg

Good to know is that all the files that begin with stm32f10x (and cortexm3_macro.h) is from a firmware lib called "stm32f10x_stdperiph_lib", and that is provided by ST. Please note that the files we have in this example is outdated, so my plan is to ignore those for now (as much as possible anyway) and then tell you about the current version later.

That means that we should focus on:

arm_comm.h
bits.h
cortexm3_macro.h
cs3.h
main.c
makefile
stm_h103_blink_rom.cmd

Olimex specials

Then we can take away some of the low hanging fruit like, bits.h. If you open it you will find that they have some defines that don't have any practical meaning, it is more a cosmetical thing.

#define   BIT0        0x00000001
#define   BIT1        0x00000002
....
#define   BIT30       0x40000000
#define   BIT31       0x80000000

Not much to say about arm_comm.h. It is not used, and the comment on top tells us all we need to know. Let's remove it, since it's no good for gcc anyway.

**    Common definition for IAR EW ARM
**
**    Used with ARM IAR C/C++ Compiler and Assembler.
**
**    (c) Copyright IAR Systems 2006

And with cs3.h, it's the same story.

 * Copyright 2007 CodeSourcery
 * Version:Sourcery G++ 4.2-62

That also mean that we only have 3 files left to focus on.

main.c
makefile
stm_h103_blink_rom.cmd

makefile

This makefile has a classical layout, a couple of variables to start with and then some actions. So just find the first action and see what it depends on. That would be the name before the :, and the dependences comes after the :.

If you feel very unfamiliar with this, google may provide some better howto on how Makefiles work. But in this Makefile, we find that it all begin with a empty one that depends on something called "test".

all: test

So let's look at test

test: main.out
    @ echo "...copying"
    $(CP) $(CPFLAGS) main.out main.bin
    $(OD) $(ODFLAGS) main.out > main.list

Now we are getting closer, since test depends on main.out, and main.out was the elf-file with all the information.

We can see that we will get main.out (from some other action), and that we will use main.out to create main.bin and main.list. But we need to look at the vars, so we can see how.

CP      = arm-none-eabi-objcopy
CPFLAGS = -Obinary
OD      = arm-none-eabi-objdump
ODFLAGS = -S

So the make action could be rewritten as

test: main.out
    @ echo "...copying"
    arm-none-eabi-objcopy -Obinary main.out main.bin
    arm-none-eabi-objdump -S main.out > main.list

Now to avoid going to deep, you should go to the man pages and look what objcopy and objdump does.

man objcopy
man objdump

The man pages will tell you:

-S
--source
  Display source code intermixed with disassembly, if possible.  Implies -d.
-O bfdname
--output-target=bfdname
  Write the output file using the object format bfdname.

bfdname is binary in this case, and that will be what we flash the stm32 with.

But now, let's get back on track. How do we create main.out?

main.out: main.o stm32f10x_rcc.o stm32f10x_gpio.o stm_h103_blink_rom.cmd
    @ echo "..linking"
    $(LD) $(LFLAGS) -o main.out  main.o stm32f10x_rcc.o stm32f10x_gpio.o 

Firstly we can see that that it is created from main.o, some stm32f10x-files and stm_h103_blink_rom.cmd. But let's repeat the step where we look what the vars mean.

LD      = arm-none-eabi-ld -v
LFLAGS  = -Tstm_h103_blink_rom.cmd -nostartfiles
main.out: main.o stm32f10x_rcc.o stm32f10x_gpio.o stm_h103_blink_rom.cmd
    @ echo "..linking"
    arm-none-eabi-ld -v -Tstm_h103_blink_rom.cmd -nostartfiles -o main.out main.o stm32f10x_rcc.o stm32f10x_gpio.o

And if we look in the ld man page, we find this:

-v
--version
-V  Display the version number for ld.  The -V option also lists the supported emulations.

However I could not find -nostartfiles in my man file, so I had a look in the gnu gcc doc. http://gcc.gnu.org/onlinedocs/gcc/Link-Options.html

-nostartfiles
    Do not use the standard system startup files when linking. 
    The standard system libraries are used normally, 
    unless -nostdlib or -nodefaultlibs is used. 
-Tbss org
-Tdata org
-Ttext org
    Same as --section-start, with ".bss", ".data" or ".text" as the sectionname.

This seem a little bit to cryptic, what does "-Tstm_h103_blink_rom.cmd" actually do?

Let's have a look inside the file called stm_h103_blink_rom.cmd.

MEMORY
{
    ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
    rom (rx)  : ORIGIN = 0x00000000, LENGTH = 128K
}
SECTIONS
{
    .  = 0x0;          /* From 0x00000000 */
    .text : {
    *(vectors)      /* Vector table */
    *(.text)        /* Program code */
    *(.rodata)      /* Read only data */
    } >rom
    .  = 0x20000000;   /* From 0x20000000 */
    .data : {
    *(.data)        /* Data memory */
    } >ram AT > rom
    .bss : {
    *(.bss)         /* Zero-filled run time allocate data memory */
    } >ram AT > rom
}

This is the memory map that tells the linker where to place the code in memory. The basic idea is that RAM starts at address 0x20000000 and the flash starts at 0x00000000.

And those rules on how to use the memory is then used by the linker when it takes the data in the different object files and put those together into the resulting main.out.

That brings us all the way back to compile step since main.out depends on main.o.

main.o: main.c
    @ echo ".compiling"
    $(CC) $(CFLAGS) main.c

And that turns into:

CC      = arm-none-eabi-gcc
CFLAGS  =  -I./ -c -fno-common -O0 -g -mcpu=cortex-m3 -mthumb
main.o: main.c
    @ echo ".compiling"
    arm-none-eabi-gcc -I./ -c -fno-common -O0 -g -mcpu=cortex-m3 -mthumb main.c

Then we can look in the gcc man file to understand what is going on here.

-I dir
  Add the directory dir to the list of directories to be searched for header files.  
  Directories named by -I are searched before the standard system include directories.  
  If the directory dir is a standard system include directory, 
  the option is ignored to ensure that the default search order for system 
  directories and the special treatment of system headers are not defeated .  
  If dir begins with "=", then the "=" will be replaced by the sysroot prefix; 
  see --sysroot and -isysroot.

-c  
  Compile or assemble the source files, but do not link.  The linking stage simply is not done.  The ultimate
  output is in the form of an object file for each source file.

  By default, the object file name for a source file is made by replacing the suffix .c, .i, .s, etc., with .o.

-fno-common
  In C, allocate even uninitialized global variables in the data section of the object file, rather than
  generating them as common blocks.  This has the effect that if the same variable is declared (without "extern")
  in two different compilations, you will get an error when you link them.  The only reason this might be useful
  is if you wish to verify that the program will work on other systems which always work this way.

-mcpu=cpu
  Compile code for ARC variant cpu.  Which variants are supported depend on the configuration.  All variants
  support -mcpu=base, this is the default.

-mthumb
  Generate code for the Thumb instruction set.  The default is to use the 32-bit ARM instruction set.  This
  option automatically enables either 16-bit Thumb-1 or mixed 16/32-bit Thumb-2 instructions based on the
  -mcpu=name and -march=name options.

Then we have -g that will add debug information to the elf-file, this will be used by gdb to interpret what happens in the mcu when we debug.

And the last, but quite important flag that you must change if you use this as a base for future projects the -O flag. -O0 tells the compiler not to optimize for anything, in this case Olimex actually need this since they has a stupid buzy wait to time the blinks and if you optimize it will blink faster. Feel free to test with -O3 or -Os.

Even if this was a little bit quick and cryptic, I think you got the idea on what is going on.

main.c

Now over to the c-code...

This program has 3 major parts.

To understand the first 2 parts, you really need to dig into the manual. However "stm32f10x_stdperiph_lib" really helps and does most of the work.

Set up the system and start the clocks.

*NVIC_CCR = *NVIC_CCR | 0x200; /* Set STKALIGN in NVIC */
// Init clock system
Clk_Init();

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA, ENABLE);

Then configure the gpio the led is on.

// Configure PC.12 as output push-pull (LED)
GPIO_WriteBit(GPIOC,GPIO_Pin_12,Bit_SET);
GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);

Then enter the while loop that loops forever.

while(1)
{
    GPIOC->BRR |= 0x00001000;
    myDelay(500000);
    GPIOC->BSRR |= 0x00001000;
    myDelay(500000);
}

Please note that myDelay is just based on a counter, so if you change optimization the actual time will change.

If you feel that I speeded over part 1 and 2 in main.c, then you are correct... Let's dig into the firmware lib in the next section instead.

This page is licensed with Creative Commons ShareAlike 3.0