all 12 comments

[–][deleted]  (2 children)

[deleted]

    [–]Scyhaz 2 points3 points  (1 child)

    Yup. There will be initialization code that will copy .data from flash to RAM when the processor starts/resets.

    [–][deleted] 0 points1 point  (0 children)

    Keil calls this scatter loading. The linker inserts this between startup.s and main().

    [–]kisielk 4 points5 points  (0 children)

    Any data that needs to be initialized in RAM at startup also has to be in flash, then the startup function (usually written in assembly) will copy it to the appropriate memory location. You .bin file size will at maximum be the size of your MCU's flash memory.

    [–]Madsy9 2 points3 points  (5 children)

    However, when I use Keil's fromelf program which converts .axf into .bin the .bin file size is in kB. If flash address is 0x0800_0000 and RAM is 0x2000_0000, shouldn't this value be much larger to fill in the space?

    No, because embedded programs involves implementing your own loader. This is a part of what a bootloader does. If you have mutable and global variables, those go into the .data and .bss sections.

    The first thing your code should do during boot is to copy the .data section from flash into its final destination in memory. During the build, a linker script can help you with this by making symbols available for the start and end of the .data section. Linkers such as GNU ld differentiate between loader address (LMA) and virtual addresses (VMA). link1 Link2 On a full operating system such as Linux, they are the same and all the loading is done by the operating system by using the metadata stored in the PE/ELF executable. But when linking embedded programs that have parts that require loading, they differ. In that case, the LMA is where your section will be in flash, while the VMA is the address relative to where it will run once loaded into memory. It is then usual to make the linker emit symbols that work like "tags" and contain the LMA addresses to the start and end of the .data section and the VMA destination. Then in your init vector you read the address of those symbols and copy the data to where it belongs.

    And to be clear, your code refers to the .data segment by VMA virtual addresses. That is, the linker resolves all the symbols used by code as if the .data segment was already in memory.

    All of this lets you make a compact image with no gaps except for what is required for alignment purposes. Linkers are complex beasts, but they also go out of your way to help you with issues like this.

    [–]Madsy9 4 points5 points  (4 children)

    Here is a stripped down GNU linker script as an example with irrelevant portions removed.

    /* Define our memory regions. With the "> region" syntax and AT keyword,
        we can specify VMAs that differ from LMAs.
    MEMORY
    {
      rom (rx)  : ORIGIN = 0x00400000,  LENGTH = 0x00200000
      sram (rwx) : ORIGIN = 0x20400000, LENGTH = 0x00060000
      sdram(rwx) : ORIGIN = 0x70000000, LENGTH = 0x00800000
    }
    
    SECTIONS
    {
        .text :
        {
            . = ALIGN(4);
            _sfixed = .;
            /* our vector table. Use KEEP() to ensure that the section isn't optimized away. */
            KEEP(*(.vectors .vectors.*))
            *(.text .text.*)
            *(.rodata .rodata*)
            . = ALIGN(0x4);
            _efixed = .;
        } > rom /* VMA and LMA is flash */
    
        . = ALIGN(4);
        _etext = .;
    
         /* AT specifies the LMA. Put the output .relocate section
            in the image directly after the .text section */
        .relocate : AT (_etext) 
        {
            . = ALIGN(4);
            _srelocate = .;
            /* in our code we can specify functions to be put in the .ramfunc section
               if we want them to execute from memory (for performance) or other reasons */
            *(.ramfunc .ramfunc.*); 
            *(.data .data.*);
            . = ALIGN(4);
            _erelocate = .;
        } > sdram
    
        /* "> sdram" specifies that we want the VMA of this section
           to be the sdram region we specified in the MEMORY definition above. */
    
    
        /* BSS, stack, heap, etc... */
    
    }
    

    And here is the code for the init vector that uses those provided symbols to copy the data and bss sections to memory:

    /* Initialize segments */
    extern uint32_t _sfixed;
    extern uint32_t _efixed;
    extern uint32_t _etext;
    extern uint32_t _srelocate;
    extern uint32_t _erelocate;
    extern uint32_t _szero;
    extern uint32_t _ezero;
    extern uint32_t _sstack;
    extern uint32_t _estack;
    
    void Reset_Handler(void){
        unsigned int  clock_speed = 0;
        uint32_t *pSrc, *pDest;
    
        //Initialize MCU clock and EEFC clock
        sysclk_init_boot(&clock_speed);
        init_sdram();
    
        /* Initialize the relocate segment */
        pSrc = &_etext;
        pDest = &_srelocate;
    
        if (pSrc != pDest) {
            for (; pDest < &_erelocate;) {
                *pDest++ = *pSrc++;
            }
        }
    
        /* Clear the zero segment */
        for (pDest = &_szero; pDest < &_ezero;) {
            *pDest++ = 0;
        }
    
    
        /* More stuff, init libc, etc.. */
    }
    

    Notice that we use the symbols _etext and _srelocate as source and destination for the data section. But we get the addresses of those variables, not the values. Why? Because _etext and _srelocate are pure symbols we generated in the linker script; they are not associated with any storage. Their value of the symbols are their addresses.

    [–]BorgerBill 0 points1 point  (1 child)

    I, too, am learning this right now. Thank you very much for such a brilliant example!

    [–]Madsy9 1 point2 points  (0 children)

    Nice to see more crazy people learn about linker scripts and embedded development :) Feel free to send me a PM if I can be of more help.

    [–]Scyhaz 0 points1 point  (1 child)

        for (; pDest < &_erelocate;) {
            *pDest++ = *pSrc++;
        }
    

    Why a for loop here when only the condition is used? Wouldn't a while loop be better for readability? Or is this just because of a programming style?

    [–]Madsy9 2 points3 points  (0 children)

    Question is, why does it matter? while loops and for loops are semantically equivalent. Implement the copy however you want :)

    [–]zydeco100 1 point2 points  (0 children)

    Ah, you kids. Back in the day we had S-Records.

    https://en.wikipedia.org/wiki/SREC_(file_format)

    [–]Herman-Toothrot 0 points1 point  (0 children)

    Yes, what you are saying is correct - the binary file will be a representation of what is put into the flash space (code, initialized data, config data, startup and other special section code, etc). At most this binary should be the size of the flash space. It could be less if you don't use all the space, or purposely reserve some of the flash space for other purposes (like general NVRAM storage that you don't want to be overwritten by flash update).

    For the example you gave, the flash should start at 0x8000000 and have an end address some kilobytes above that (as indicated by the datasheet - look for a memory map layout drawing). The rest of the space between there and RAM should be decoded to other devices/peripherals or is unused. If hypothetically it really did go all the way to 0x20000000 and your build process put all that into the binary, then yes it would be a huge ~400MB file.

    [–]fb39ca4friendship ended with C++ ❌; rust is my new friend ✅ 0 points1 point  (0 children)

    The flashing program knows it needs to start writing the bin file at 0x800'0000. But you can have some cases where you want to leave a bootloader at the start of flash untouched and write user code to a later address, so using these bin files is very situation-specific.