STM32F0 Discovery Development

By | 2013-03-11

In my previous article about the STM32F0-discovery board, I wrote this:

I believe it will be possible, with a bit of research, to use the arm-linux-eabi version, and hence to get the cross-compiler direct from the emdebian project.

Today’s article is going to be looking at exactly that. I’d like to be able to use the non-bare-metal version of the ARM cross-compiler that is available in emdebian’s toolchain repository (follow the instructions on that link to get access to the repository). The advantage of a packaged version is that it will almost certainly “just work”, it will be installed in a good place to fit with the rest of your installation, and it will be easy to upgrade when new versions are released. Once you’ve added the appropriate repositories to your /etc/apt/sources.list, you should do this:

$ apt-get install emdebian-archive-keyring
$ apt-get install gcc-4.7-arm-linux-gnueabi

You can now use arm-linux-gnueabi- as your BINUTILSPREFIX, and get cross compiled versions of your normal Linux software. I’m more concerned with using this compiler in place of the bare-metal version for building for the (for now) STM32F0-discovery board. Balau82’s blog has an article that gives us as an excellent start, and I’m essentially going to rehash that but for the SM32F0 instead.

I’m also going to use it as an opportunity to build us a development environment containing just the core of the stm32f0-discovery-basic-template that we used previously.

First, let’s make ourselves the most basic program we can.

void main( void )
{
    while(1) {
    }
}

Let’s compile it, to see the lay of the land. The magic flag for removing the Linux libraries and startup from the build is -nostdlib, which turns out to be the essential piece for using non-bare metal for bare-metal work. We also need a few extra flags to define the CPU:

arm-linux-gnueabi-gcc -g \
    -mcpu=cortex-m0 -march=armv6s-m -mlittle-endian -mthumb \
    -o main.o -c main.c
arm-linux-gnueabi-gcc -Wl,--gc-sections -Wl,-Map=main.map \
    -Xlinker "--build-id=none" -ffunction-sections -fdata-sections \
    -nostdlib -o main.elf main.o
/usr/lib/gcc/arm-linux-gnueabi/4.7/../../../../arm-linux-gnueabi/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000008034

Unsurprisingly, the compile is fine, but the link fails with missing symbols. This is because the default linker script refers to a symbol that is no longer linked in, “_start”. Let’s address that by replacing the default linker script. I’ve removed a lot of, ultimately, necessary parts of this script, but we need a place to begin. Note: all our symbols are prefixed with “_”, the C standard dictates that symbols prefixed with underscore are for system use only – in this case, that’s us.

/* stm32f0.ld */
MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
    RAM  (xrw) : ORIGIN = 0x20000000, LENGTH = 8K
}

SECTIONS
{
    /* program code -- in FLASH */
    .text : {
        . = ALIGN(4);
        *(.text)
        *(.text.*)
        . = ALIGN(4);
        _end_of_text = .;
    } >FLASH

    /* initialised data -- in RAM */
    .data : {
        . = ALIGN(4);
        _start_of_data = .;
        *(.data)
        *(.data.*)
        . = ALIGN(4);
        _end_of_data = .;
    } >RAM

    /* uninitialised data -- in RAM */
    .bss : {
        . = ALIGN(4);
        _start_of_bss = .;
        *(.bss)
        *(.bss.*)
        *(COMMON)
        . = ALIGN(4);
        _end_of_bss = .;
    } >RAM
}

With this in place; we can successfully build an object file.

arm-linux-gnueabi-gcc -g -mcpu=cortex-m0 -march=armv6s-m -mlittle-endian \
    -mthumb -o main.o -c main.c
arm-linux-gnueabi-gcc -Tstm32f0.ld -ffunction-sections -fdata-sections \
    -nostdlib -Xlinker "--build-id=none" -Wl,--gc-sections \
    -Wl,-Map=main.map -o main.elf main.o
arm-linux-gnueabi-size main.elf
   text    data     bss     dec     hex filename
      0       0       0       0       0 main.elf

Note that we’ve got no actual code. That’s because our code is in a section that is subsequently garbage collected. Here’s the symbol table, though:

main.elf:     file format elf32-littlearm

SYMBOL TABLE:
20000000 g       *ABS*  00000000 _start_of_data
20000000 g       *ABS*  00000000 _end_of_bss
20000000 g       *ABS*  00000000 _end_of_data
08000000 g       *ABS*  00000000 _end_of_text
20000000 g       *ABS*  00000000 _start_of_bss

Our next step is to put some startup code. We’ll use the _start symbol. We add it as an entry point to the linker script:

ENTRY(_start)

_top_of_stack = 0x20002000;

Then we need to define the symbol, which we’ll do in assembly.

.syntax unified

.section .text
_start:
    .global _start
    .weak   _start
    .type   _start, %function

    ldr     r0, =_top_of_stack
    mov     sp, r0
    bl      main

    .size   _start, . - _start

Most of this is assembler directives. The meat is a simple set of the stack pointer, and a jump to main.

arm-linux-gnueabi-as -mcpu=cortex-m0 -march=armv6s-m -mlittle-endian \
    -mthumb -o startup.o -c startup.s

Moving next to the ISR vector table. In the STM32F0 it’s stored as the first 196 bytes of the flash.

.section .text.isr,"ax",%progbits
unhandled_interrupt:
    .global unhandled_interrupt
infinite_loop:
    b       infinite_loop
    /* Calculate size of function*/
    .size   unhandled_interrupt, . - unhandled_interrupt


.section .isr_vector,"a",%progbits
interrupt_vector_table:
    .type   interrupt_vector_table, %object
    /* ----------- */
    /* 0x00000000 is the initial stack pointer */
    .word   _top_of_stack
    /* ISR table ; zero entries are reserved */
    .word   _ISR_Reset
    .word   _ISR_NMI
    .word   _ISR_HardFault
    .word   0
    .word   0
    .word   0
    .word   0
    .word   0
    .word   0
    .word   0
    .word   _ISR_SVC
    .word   0
    .word   0
    .word   _ISR_PendSV
    .word   _ISR_SysTick
    /* Peripheral IRQs */
    .word   _ISR_WWDG
    .word   _ISR_PVD
    .word   _ISR_RTC
    .word   _ISR_FLASH
    .word   _ISR_RCC
    .word   _ISR_EXTI0_1
    .word   _ISR_EXTI2_3
    .word   _ISR_EXTI4_15
    .word   _ISR_TS
    .word   _ISR_DMA1_Channel1
    .word   _ISR_DMA1_Channel2_3
    .word   _ISR_DMA1_Channel4_5
    .word   _ISR_ADC1_COMP
    .word   _ISR_TIM1_BRK_UP_TRG_COM
    .word   _ISR_TIM1_CC
    .word   _ISR_TIM2
    .word   _ISR_TIM3
    .word   _ISR_TIM6_DAC
    .word   0
    .word   _ISR_TIM14
    .word   _ISR_TIM15
    .word   _ISR_TIM16
    .word   _ISR_TIM17
    .word   _ISR_I2C1
    .word   _ISR_I2C2
    .word   _ISR_SPI1
    .word   _ISR_SPI2
    .word   _ISR_USART1
    .word   _ISR_USART2
    .word   0
    .word   _ISR_CEC
    .word   0
    /* ----------- */
    .word   _start_of_boot_ram
    /* ----------- */
    .size   interrupt_vector_table, .-interrupt_vector_table


/* ---------------------------------------------------------------- */
/*
    Provide weak references for all the ISRs to the unhandled_interrupt
    function.
 */

    .weak _ISR_NMI
    .thumb_set _ISR_NMI, unhandled_interrupt

    .weak _ISR_HardFault
    .thumb_set _ISR_HardFault, unhandled_interrupt

    .weak _ISR_SVC
    .thumb_set _ISR_SVC, unhandled_interrupt

    .weak _ISR_PendSV
    .thumb_set _ISR_PendSV, unhandled_interrupt

    .weak _ISR_SysTick
    .thumb_set _ISR_SysTick, unhandled_interrupt

    .weak _ISR_WWDG
    .thumb_set _ISR_WWDG, unhandled_interrupt

    .weak _ISR_PVD
    .thumb_set _ISR_PVD, unhandled_interrupt

    .weak _ISR_RTC
    .thumb_set _ISR_RTC, unhandled_interrupt

    .weak _ISR_FLASH
    .thumb_set _ISR_FLASH, unhandled_interrupt

    .weak _ISR_RCC
    .thumb_set _ISR_RCC, unhandled_interrupt

    .weak _ISR_EXTI0_1
    .thumb_set _ISR_EXTI0_1, unhandled_interrupt

    .weak _ISR_EXTI2_3
    .thumb_set _ISR_EXTI2_3, unhandled_interrupt

    .weak _ISR_EXTI4_15
    .thumb_set _ISR_EXTI4_15, unhandled_interrupt

    .weak _ISR_TS
    .thumb_set _ISR_TS, unhandled_interrupt

    .weak _ISR_DMA1_Channel1
    .thumb_set _ISR_DMA1_Channel1, unhandled_interrupt

    .weak _ISR_DMA1_Channel2_3
    .thumb_set _ISR_DMA1_Channel2_3, unhandled_interrupt

    .weak _ISR_DMA1_Channel4_5
    .thumb_set _ISR_DMA1_Channel4_5, unhandled_interrupt

    .weak _ISR_ADC1_COMP
    .thumb_set _ISR_ADC1_COMP, unhandled_interrupt

    .weak _ISR_TIM1_BRK_UP_TRG_COM
    .thumb_set _ISR_TIM1_BRK_UP_TRG_COM, unhandled_interrupt

    .weak _ISR_TIM1_CC
    .thumb_set _ISR_TIM1_CC, unhandled_interrupt

    .weak _ISR_TIM2
    .thumb_set _ISR_TIM2, unhandled_interrupt

    .weak _ISR_TIM3
    .thumb_set _ISR_TIM3, unhandled_interrupt

    .weak _ISR_TIM6_DAC
    .thumb_set _ISR_TIM6_DAC, unhandled_interrupt

    .weak _ISR_TIM14
    .thumb_set _ISR_TIM14, unhandled_interrupt

    .weak _ISR_TIM15
    .thumb_set _ISR_TIM15, unhandled_interrupt

    .weak _ISR_TIM16
    .thumb_set _ISR_TIM16, unhandled_interrupt

    .weak _ISR_TIM17
    .thumb_set _ISR_TIM17, unhandled_interrupt

    .weak _ISR_I2C1
    .thumb_set _ISR_I2C1, unhandled_interrupt

    .weak _ISR_I2C2
    .thumb_set _ISR_I2C2, unhandled_interrupt

    .weak _ISR_SPI1
    .thumb_set _ISR_SPI1, unhandled_interrupt

    .weak _ISR_SPI2
    .thumb_set _ISR_SPI2, unhandled_interrupt

    .weak _ISR_USART1
    .thumb_set _ISR_USART1, unhandled_interrupt

    .weak _ISR_USART2
    .thumb_set _ISR_USART2, unhandled_interrupt

    .weak _ISR_CEC
    .thumb_set _ISR_CEC, unhandled_interrupt

To put this in the correct place in flash, we need to put the table section in the linker script.

/* interrupt vectors */
.isr_vector : {
    . = ALIGN(4);
    KEEP(*(.isr_vector))
    . = ALIGN(4);
} >FLASH

Use of KEEP here is what gets us, for the first time, a non-zero build (because of -Wl,--gc-sections). isr_vector sections are kept regardless of use, and hence anything they refer to becomes a dependency.

arm-linux-gnueabi-as -mcpu=cortex-m0 -march=armv6s-m -mlittle-endian -mthumb -o startup.o -c startup.s
arm-linux-gnueabi-gcc -Tstm32f0.ld -ffunction-sections -fdata-sections -nostdlib -Xlinker "--build-id=none" -Wl,--gc-sections -Wl,-Map=main.map -o main.elf main.o startup.o
arm-linux-gnueabi-size main.elf
   text    data     bss     dec     hex filename
    220       0       0     220      dc main.elf

After compile we get 220 bytes of code used. That might sound a lot for a does-nothing program, but remember we made a 196 byte interrupt vector table, which would exist whether the program did anything or not. That leaves 24 bytes of our program. Thumb instructions are 16-bits, so that’s at most (assuming no absolute jumps or loads from memory) twelve assembly instructions. Not unreasonable. Here’s the disassembly for interest’s sake.

080000c4 <main>:
void main( void )
{
 80000c4:   b580        push    {r7, lr}
 80000c6:   af00        add r7, sp, #0
    while(1) {
    }
 80000c8:   e7fe        b.n 80000c8 <main+0x4>
 80000ca:   46c0        nop         ; (mov r8, r8)

080000cc <_ISR_Reset>:
 80000cc:   4801        ldr r0, [pc, #4]    ; (80000d4 <_ISR_Reset+0x8>)
 80000ce:   4685        mov sp, r0
 80000d0:   f7ff fff8   bl  80000c4 <main>
 80000d4:   20002000    .word   0x20002000

080000d8 <_ISR_ADC1_COMP>:
 80000d8:   e7fe        b.n 80000d8 <_ISR_ADC1_COMP>

That’s about as far as I want to go at this point. The main thing is that we’ve used arm-linux-gnueabi-gcc to build a bare metal application; we’ve made up a minimal linker script for an STM32F0; and have verified our small object file has almost nothing in it.

If you wished, you could program this into your discovery board, but it does absolutely nothing, so you won’t get a lot of enjoyment.

We’ve got a minimal system, but there is far more we’re going to need. I’ll try to cover those things next time.

Leave a Reply