Understanding C runtime environment (ARM) - where to start

前端 未结 5 1895
青春惊慌失措
青春惊慌失措 2021-02-06 01:54

I\'am embedded developer working with ARM Cortex-M devices mainly. Recently I\'ve switched to Linux and decided to learn more about the build/assemble/link process, how to write

5条回答
  •  不思量自难忘°
    2021-02-06 02:31

    1) I've received an MCU (let's say STM32F4xx) and I should create a blinking LED example. All this should be done from scratch, own startup code, no usage external libraries etc.

    I have an MCU say an STM32F4xx and I want to blink the led on PA5 with no libraries, from scratch, nothing external.

    blinker01.c

    void PUT32 ( unsigned int, unsigned int );
    unsigned int GET32 ( unsigned int );
    void dummy ( unsigned int );
    
    #define RCCBASE 0x40023800
    #define RCC_AHB1ENR (RCCBASE+0x30)
    
    #define GPIOABASE 0x40020000
    #define GPIOA_MODER     (GPIOABASE+0x00)
    #define GPIOA_OTYPER    (GPIOABASE+0x04)
    #define GPIOA_BSRR      (GPIOABASE+0x18)
    
    int notmain ( void )
    {
        unsigned int ra;
        unsigned int rx;
    
        ra=GET32(RCC_AHB1ENR);
        ra|=1<<0; //enable GPIOA
        PUT32(RCC_AHB1ENR,ra);
    
        ra=GET32(GPIOA_MODER);
        ra&=~(3<<10); //PA5
        ra|=1<<10; //PA5
        PUT32(GPIOA_MODER,ra);
        //OTYPER
        ra=GET32(GPIOA_OTYPER);
        ra&=~(1<<5); //PA5
        PUT32(GPIOA_OTYPER,ra);
    
        for(rx=0;;rx++)
        {
            PUT32(GPIOA_BSRR,((1<<5)<<0));
            for(ra=0;ra<200000;ra++) dummy(ra);
            PUT32(GPIOA_BSRR,((1<<5)<<16));
            for(ra=0;ra<200000;ra++) dummy(ra);
        }
        return(0);
    }
    

    flash.s

    .thumb
    
    .thumb_func
    .global _start
    _start:
    stacktop: .word 0x20001000
    .word reset
    .word hang
    .word hang
    .word hang
    .word hang
    .word hang
    .word hang
    .word hang
    .word hang
    .word hang
    .word hang
    .word hang
    .word hang
    .word hang
    .word hang
    
    .thumb_func
    reset:
        bl notmain
        b hang
    .thumb_func
    hang:   b .
    
    .align
    
    .thumb_func
    .globl PUT16
    PUT16:
        strh r1,[r0]
        bx lr
    
    .thumb_func
    .globl PUT32
    PUT32:
        str r1,[r0]
        bx lr
    
    .thumb_func
    .globl GET32
    GET32:
        ldr r0,[r0]
        bx lr
    
    .thumb_func
    .globl dummy
    dummy:
        bx lr
    

    linker script flash.ld

    MEMORY
    {
        rom : ORIGIN = 0x08000000, LENGTH = 0x1000
        ram : ORIGIN = 0x20000000, LENGTH = 0x1000
    }
    
    SECTIONS
    {
        .text : { *(.text*) } > rom
        .rodata : { *(.rodata*) } > rom
        .bss : { *(.bss*) } > ram
    }
    

    this is all using gcc/gnu tools

    arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m4 flash.s -o flash.o
    arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding  -mcpu=cortex-m4 -mthumb -mcpu=cortex-m4 -c blinker01.c -o blinker01.flash.o
    arm-none-eabi-ld -o blinker01.flash.elf -T flash.ld flash.o blinker01.flash.o
    arm-none-eabi-objdump -D blinker01.flash.elf > blinker01.flash.list
    arm-none-eabi-objcopy blinker01.flash.elf blinker01.flash.bin -O binary
    

    to make sure it will boot right and it linked right check the vector table from the list file

    08000000 <_start>:
     8000000:   20001000 
     8000004:   08000041 
     8000008:   08000047 
     800000c:   08000047 
     8000010:   08000047 
     8000014:   08000047 
    

    these should be odd numbers, the handler address orred with one

    08000040 :
     8000040:   f000 f80a   bl  8000058 
     8000044:   e7ff        b.n 8000046 
    
    08000046 :
     8000046:   e7fe        b.n 8000046 
    

    and start at 0x08000000 in the case of these STM32 parts (some vendors you build for zero)(on powerup zero is mirrored from 0x08000000 so the vector will take you to the proper place in flash).

    As far as the led goes make the gpio pin a push-pull output and turn it off and on. in this case burn some cpu cycles then change state. by using a function not in the blinker01.c it forces the compiler to perform those counts (rather than doing a volatile thing), simple optimization trick. PUT32/GET32 personal preference, insuring the correct instruction is used, compilers dont always use the correct instruction and if the hardware requires a certain sized operation you could get in trouble. Abstracting has more pros than cons, IMO.

    Fairly simple to configure and use these parts. Good to learn it this way as well as using the libraries, professionally you may have to deal with both extremes, perhaps you get to be the one that writes the libraries for others and need to know both at the same time.

    Knowing your tools is about the most important thing and yes most folks dont know how to do that in this business, they rely on a tool, work around the warts of the tool or library rather than understand what is going on and/or fix it. the point of this answer is 1) you asked and 2) to show just how easy it is to use the tools.

    could have made it even simpler if I got rid of the functions in assembly and only used assembly as a very simple way to make the vector table. the cortex-m is such that you can do everything in C except the vector table (which you can but it is ugly) and then use something like the well tested and working assembler to create the vector table.

    Note cortex-m9 vs the others

     8000074:   f420 6140   bic.w   r1, r0, #3072   ; 0xc00
     8000078:   f441 6180   orr.w   r1, r1, #1024   ; 0x400
    

    the cortex-m0 and (m1 if you come across one) are armv6m based where the rest are armv7m which has like 150 more thumb2 extensions to the thumb instruction set (formerly undefined instructions used to make variable length instructions). all the cortex-ms run thumb, but the cortex-m0 does not support the armv7m specific extensions, you can modify the build to say cortex-m0 instead of m4 and it will work just fine on the m4, take code like this (patch up the addresses as needed perhaps the gpio is different for your specific part perhaps not) and build for m0 it will run on m0...Just like the need to periodically check to see the vector table is being built right, you can examine the dissassembly to see that the right flavor of instructions are being used.

提交回复
热议问题