由于keil-mdk自带的编译器要钱的,所以转向使用免费的GNU的相关工具!
Simplest bare metal program for ARM
Posted on 2010/02/14
63
Bare metal programs run without an operating system beneath; coding on bare metal is useful to deeply understand how a hardware architecture works and what happens in the lowest levels of an operating system. I wanted to create a simple example of bare metal program for ARM using free open source tools: RealView Development Suite is the state of the art of ARM compilers, but it is expensive for hobbyists; Codesourcery is a company that provides a free version of the GNU gcc toolchain for ARM cores. In particular, the EABI toolchain must be downloaded from their download page; I fetched the IA32 GNU/Linux installer. During the graphical installation, the tools are installed in a sub-folder of the user’s home; this is fine if only a single person wants to use the toolchain on that computer, otherwise it is more efficient to install it system-wide. The path to the toolchain binaries must be added to the PATH environmental variable; usually the installation process does it for you, but if it doesn’t, the standard installation path is “~/CodeSourcery/Sourcery_G++_Lite/bin“.
I created a C file called test.c containing the simplest C code I wanted to compile:
To compile this code into an object file (test.o) run the following command, very similar to compiling code with gcc:
I created an assembly file called startup.s containing the following code:
For a deeper look into bare metal programming for ARM see also:
I created a C file called test.c containing the simplest C code I wanted to compile:
int c_entry() { return 0; }The classic printf(“Hello world!\n”); example is more complex because when coding bare metal the standard input/output must be defined: it could be a physical serial port for example. I called it c_entry instead of main because in this example some things that are usually assumed true when the program reaches the main code are not implemented: for example, variable initialized globally in C code could not be really initialized.
To compile this code into an object file (test.o) run the following command, very similar to compiling code with gcc:
$ arm-none-eabi-gcc -c -mcpu=arm926ej-s -g test.c -o test.oThe -mcpu flag indicates the processor for which the code is compiled. I wanted to target the ARM926EJ-S processor in this example for these reasons:
- It’s a widespread core in common products
- I worked on a project that used this core
- The QEMU emulator supports this core in the form of a VersatilePB
I created an assembly file called startup.s containing the following code:
.section INTERRUPT_VECTOR, "x" .global _Reset _Reset: B Reset_Handler /* Reset */ B . /* Undefined */ B . /* SWI */ B . /* Prefetch Abort */ B . /* Data Abort */ B . /* reserved */ B . /* IRQ */ B . /* FIQ */ Reset_Handler: LDR sp, =stack_top BL c_entry B .A brief explanation:
- Line 1 generates a section named INTERRUPT_VECTOR containing executable (“x”) code.
- Line 2 exports the name _Reset to the linker in order to set the program entry point.
- Line 3 to 11 is the interrupt vector table that contains a series of branches. The notation “B .” means that the code branches on itself and stays there forever like an endless for(;;);
- Line 14 initializes the stack pointer, that is necessary when calling C functions. The top of the stack (stack_top) will be defined during linking.
- Line 15 calls the c_entry function, and saves the return address in the link register (lr).
$ arm-none-eabi-as -mcpu=arm926ej-s -g startup.s -o startup.oNow we have test.o and startup.o, that must be linked together to become a program. The linking process also defines the address where the program is going to be executed and declares the placement of its sections. To give this information to the linker, a linker script is used. I wrote this linker script, called test.ld, following a simple example in the linker manual:
ENTRY(_Reset) SECTIONS { . = 0x0; .text : { startup.o (INTERRUPT_VECTOR) *(.text) } .data : { *(.data) } .bss : { *(.bss COMMON) } . = ALIGN(8); . = . + 0x1000; /* 4kB of stack memory */ stack_top = .; }The script tells the linker to place the INTERRUPT_VECTOR section at address 0, and then subsequently place the code (.text), initialized data (.data) and zero-initialized and uninitialized data (.bss). Line 11 and 12 tells the linker to move 4kByte from the end of the useful sections and then place the stack_top symbol there. Since the stack grows downwards the stack pointer should not exceed its own zone, otherwise it will corrupt lower sections. The script on line 1 tells the linker also that the entry point is at _Reset. To link the program, execute the following command:
$ arm-none-eabi-ld -T test.ld test.o startup.o -o test.elfThis will generate an ELF binary for ARM that can be executed with a simulator, or it can be loaded inside a real ARM core on a hardware board; for simplicity we can use the Codesourcery version of the gdb debugger:
$ arm-none-eabi-gdb test.elf [...] This GDB was configured as "--host=i686-pc-linux-gnu --target=arm-none-eabi". [...] (gdb) target sim Connected to the simulator. (gdb) load Loading section .text, size 0x50 vma 0x0 Start address 0x0 Transfer rate: 640 bits in <1 sec. (gdb) break c_entry Breakpoint 1 at 0x3c: file test.c, line 24. (gdb) run Starting program: /home/francesco/src/arm-none-eabi/startup/test.elf Breakpoint 1, c_entry () at test.c:24 24 return 0; (gdb) set $pc=0 (gdb) stepi Reset_Handler () at startup.s:34 34 LDR sp, =stack_top
- The target sim command tells the debugger to use its internal ARM simulator,
- the load command fills the simulator memory with the binary code,
- the debugger places a breakpoint at the beginning of the c_entry function,
- the program is executed and stops at the breakpoint,
- the program counter (pc register) of the ARM core is set to 0 to emulate a software reset,
- the execution flow can be examined step-by-step in the debugger.
$ ddd --debugger arm-none-eabi-gdb test.elfThis program is a starting point to begin to develop more elaborate solutions. The next step I want to take is using QEMU as the development target: with it I can interact with some peripherals, even if emulated, and create bare metal embedded programs more useful in the “real world” using only free open source software.
For a deeper look into bare metal programming for ARM see also: