We write our simplest program for ARM Cortex-M3

    imageGood day! Today I want to tell you how to write a minimal program that runs on ARM Cortex-M3 and at the same time prints “Hello, World!”. We will try to sort through the necessary minimum that we need for this. We will run on the QEMU emulator. Therefore, anyone can reproduce, even if he does not have a piece of iron on hand.

    So let's go!

    The QEMU emulator supports the Cortex-M3 core and emulates the Stellaris LM3S811 from Texas Instruments based on it. We will run on this platform. We need the armchain arm-none-eabi- (you can download it here ). Next, we need to write the main logic of our program, the start code that will transfer control to the program, and the linker script.

    On Habré already quite good articles about how to flash a diode on a piece of iron from scratch. Therefore, here I will not delve into what and how it works, but I will give only the minimum set of necessary knowledge needed to start.

    Our hello world in the test.c file:

    static volatile unsigned int * const UART_DR = (unsigned int *)0x4000c000;
    static void uart_print(const char *s) {
    	while (*s != '\0') {
    		*UART_DR = *s;
    		s++;
    	}
    }
    void c_entry(void) {
    	uart_print("Hello, World!\n");
    	while (1)
    	    ;
    }
    

    This same address 0x4000c000 is taken from the documentation, there lies the DR register of the zero Huart. We will not be engaged in setting up the wart (on the hardware it will have to be done), but we will try to directly put symbols into it immediately.

    Now, we need to somehow transfer control to our c_entry function in the test.c. file. To do this, create a code as follows (file startup.S), and then put it in the final ELF image at the beginning.

    .type start, %function
    .word stack_top /* Вот это вершина стека */
    .word start         /* А здесь инициализируем PC */
    .global start
    start:
    	ldr r1, =c_entry
    	bx r1
    

    The first word at 0x0 should be a pointer to the top of the stack (SP). At the address 0x4 is a PC, which, like SP, is loaded into registers. Note that start is declared as a function, and not as a label due to the fact that the code on the Cortex-M is executed in Thumb mode (this is a simplified set of ARM commands), and it is required that the addresses of functions in the interrupt vector are in the form (address | 0x1) - i.e. The last bit of the address should be 1.

    Next, the start function simply loads the address of our c_entry () function from the test.c file and transfers control through “bx r1”.

    It remains only to successfully link our program. To do this, you need to set the memory card of our microcontroller. In the documentation you can find the addresses and sizes of flash memory (ROM) and RAM (RAM). I'll give the linker script test.ld:

    SECTIONS
    {
    	 . = 0x0; /* Это флэшка (ROM) */
    	.text : {
    		startup.o(.text)
    		test.o(.text) 
    	} 
    	. = 0x20000000; /* С этого адреса начинается RAM */
    	.data : { *(.data) }
    	.bss : { *(.bss) }
    	. = ALIGN(8);
    	. = . + 0x1000; /* Отдаем под стек 4кБ */
    	stack_top = .;
    }
    

    It is important to pay attention to the addresses. “.” In the linker script indicates the current position. We put in the beginning of the ROM (address 0x0) section .text observing the sequence - the first is startup.o (.text). Next, go to the RAM (. = 0x20000000;) and put the data (initialized global data) and bss (uninitialized global data) there. Below we see ALIGN (8) - ARM requires leveling SP (Stack Pointer) by 8. Since the stack grows down, the allocation of space for the stack is just an addition. ” =. + 0x1000 ”. We know our program well, so the 4kB stack is enough with a large margin.

    That's all, it remains to put it all together. I bring build.sh:

    #!/bin/sh
    
    arm-none-eabi-as -c -mthumb -mlittle-endian -march=armv7-m -mcpu=cortex-m3 startup.S -o startup.o
    arm-none-eabi-gcc -c -mthumb -ffreestanding -mlittle-endian -march=armv7-m -mcpu=cortex-m3 test.c -o test.o
    arm-none-eabi-ld -T test.ld test.o startup.o -o test.elf
    

    Everything should be more or less understandable here, except for the -ffreestanding flag. In this case, it is not necessary to add it (you can check it), but since we are preparing a bare-metal image from scratch, it is better to tell the compiler so that it does not pay attention to such functions as main ().

    As a result, we have an ELF file test.elf. Run it on QEMU:

    $ qemu-system-arm -M lm3s811evb -kernel test.elf -nographic
    Hello, World!
    

    Works.

    Of course, this is an educational example designed to understand what is happening. If you need more meaningful functionality, you should use ready-made things. We have added support for this platform in Embox . This template is called platform / stellaris / lm3s811evb. Therefore, if someone wants to try to run a slightly more serious thing (console, timer, interrupts), then you can collect and try. At the same time, I repeat, you do not need to have a hardware board.

    And those who still have little emulator, or who want to ask us questions and play with the glands, we will wait this Saturday and Sunday at the IT festival techtrain.ru in St. Petersburg. We will have various glands on the stand, and at the demo zone we will try to tell how to program them.

    Also popular now: