2
votes

I have an ARM board with ROM at 0x80000000 and RAM at 0x20000000. Board starts execution of raw binary code at 0x80000000.

I managed to get a simple ARM assembler program running on it, but I have to use C instead of ASM. I know I will need to use some kind of linker script, and then manually copy .data section to RAM and clear .bss, setup stack etc. but I haven't found a reliable solution how to do this yet, especially the linker script (it's very messy in my opinion).

Also, I can't get the linker to output a raw binary instead of ELF, but it's not a big deal as I can use objcopy later.

Thanks in advance.

2
What tool-chain are you using (GNU I presume?). What ARM part are you using (or specify the board perhaps)? Is the RAM on-chip or external? Will the code execute from ROM or RAM? The answer to these questions will get you a better answer. But at least I can tell you now that objcopy is exactly how you generate a raw binary (or hex file for that matter); normally as a rule in your makefile, or a post-build step in your IDE.Clifford
GNU toolchain, on-chip RAM, code will execute from ROM.m_chojnacki
For blinky if you don't use static variables (only stack), then you don't need to copy data from FLASH to SRAM.vlk
@m_chojnacki : You should add those details by editing the question rather then adding a comment. But you still did not answer all the questions.Clifford

2 Answers

5
votes

This example is for STM32F051 MCU:

Very simple application as "blinky" (without delays):

// define used registers
#define RCC_AHB1 *(volatile unsigned int *)(0x40021014)
#define GPIOC_MODER *(volatile unsigned int *)(0x48000800)
#define GPIOC_BSRR *(volatile unsigned int *)(0x48000818)

// main program
void mainApp() {
    RCC_AHB1 = 1 << 19;  // enable clock for GPIOC
    GPIOC_MODER = 1 << (9 * 2);  // set output on GPIOC.P9
    while (1) {
        GPIOC_BSRR = 1 << 9;  // set output on GPIOC.P9
        GPIOC_BSRR = 1 << (9 + 16);  // clear output on GPIOC.P9
    }
}

// variables for testing memory initialisation
int x = 10;
int y = 0;
int z;

Here is also very simple startup file written in C (also will work in C++), which start application mainApp and initialise static variables .data initialised from ROM and .bss only set to zero, there are variables initialised to zero and uninitialised variables.

extern void mainApp();

// external variables defined in linker script
// address in FLASH where are stored initial data for .data section
extern unsigned int _data_load;
// defines start and end of .data section in RAM
extern unsigned int _data_start;
extern unsigned int _data_end;
// defines start and end of .bss section in RAM
extern unsigned int _bss_start;
extern unsigned int _bss_end;

void resetHandler() {
    unsigned int *src, *dst;

    // copy .data area
    src = &_data_load;
    dst = &_data_start;
    while (dst < &_data_end) {
        *dst++ = *src++;
    }

    // clear .bss area
    dst = &_bss_start;
    while (dst < &_bss_end) {
        *dst++ = 0;
    }

    mainApp();

    while(1);
}

// _stacktop is defined in linker script
extern unsigned int _stacktop;

// vector table, will be placed on begin of FLASH memory, is defined in linker script
// only reset vector defined, need add other used vectors (especially NMI)
__attribute__((section(".vectors"), used)) void *isr_vectors[] = {
    &_stacktop,  // first vector is not vector but initial stack position
    (void *)resetHandler,  // vector which is called after MCU start
};

And finaly linker script which define location and sizes of memories, sections for vector, program (text), data (.data and .bss) a stacktop

MEMORY {
    FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 64K
    SRAM(rwx) : ORIGIN = 0x20000000, LENGTH = 8K
}

SECTIONS {
    . = ORIGIN(FLASH);
    .text : {
        *(.vectors)
        *(.text)
    } >FLASH

    . = ORIGIN(SRAM);
    .data ALIGN(4) : {
        _data_start = .;
        *(.data)
        . = ALIGN(4);
        _data_end = .;
    } >SRAM AT >FLASH

    .bss ALIGN(4) (NOLOAD) : {
        _bss_start = .;
        *(.bss)
        . = ALIGN(4);
        _bss_end = .;
    } >SRAM

    _stacktop = ORIGIN(SRAM) + LENGTH(SRAM);
    _data_load = LOADADDR(.data);
}

Build this with these command (build and link at once):

$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -nostartfiles main.c startup.c -T stm32f051x8.ld -o main.elf

In symbol table is possible to see where are stored data:

$ arm-none-eabi-nm -C -l -n -S main.elf
08000000 00000008 T isr_vectors
08000008 00000034 T mainApp
0800003c 0000005c T resetHandler
08000098 A _data_load
20000000 D _data_start
20000000 00000004 D x
20000004 D _data_end
20000004 B _bss_start
20000004 00000004 B y
20000008 B _bss_end
20000008 00000004 B z
20002000 A _stacktop

Also you can look into listing:

arm-none-eabi-objdump -S main.elf

Raw binary:

arm-none-eabi-objcopy -O binary main.elf main.bin
2
votes
MEMORY
{
    bob : ORIGIN = 0x8000, LENGTH = 0x1000
    ted : ORIGIN = 0xA000, LENGTH = 0x1000
}

SECTIONS
{
   .text : { *(.text*) } > bob
   __data_rom_start__ = .;
   .data : {
    __data_start__ = .;
    *(.data*)
   } > ted AT > bob
   __data_end__ = .;
   __data_size__ = __data_end__ - __data_start__;
   .bss  : {
   __bss_start__ = .;
   *(.bss*)
   } > ted
   __bss_end__ = .;
   __bss_size__ = __bss_end__ - __bss_start__;
}

of course change the addresses as you see fit. clearly the names of the sections mean nothing, you might try rom and ram if it helps you.

If you were to do something like this

MEMORY
{
    rom : ORIGIN = 0x80000000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}

SECTIONS
{
    .text : { *(.text*) } > rom
    .bss : { *(.bss*) } > ram
    .rodata : { *(.rodata*) } > rom
    .data : { *(.data*) } > ram
}

and then objcopy it then you are going to end up with a huge file, even if you only have one instruction of .text and one byte of data. because a binary format has to cover everything as in the memory so it will make a file that is 0x80000000+sizeof(text)-0x20000000. If you had one instruction only in ram and one byte of data then the file would be 0x60000004 bytes or 1.6 gig. leave it as an elf and use objdump -D until you have your linker script worked out THEN make a .bin if you really need one. or make an intel hex or s record and again you can examine it to see if it is all in the same address space before trying a binary.

the first key to your problem is the AT

MEMORY
{
    bob : ORIGIN = 0x8000, LENGTH = 0x1000
    ted : ORIGIN = 0xA000, LENGTH = 0x1000
}

SECTIONS
{
   .text : { *(.text*) } > bob
   .data : { *(.data*) } > ted AT > bob
   .bss  : { *(.bss*) } > bob
}

it says I want the .data in the address space ted, but place it in the binary in the bob space. it compiles for the ted address space, but the bits are loaded from bob space. exactly what you want. except you dont know how much .data to copy from rom to ram. you have to be super careful where you place the variables in the more complicated one or it wont work right.