Saltar al contenido

Comprender el entorno de ejecución de C (ARM): por dónde empezar

Agradecemos tu ayuda para difundir nuestros enunciados con relación a las ciencias informáticas.

Solución:

  1. Recibí una MCU (digamos STM32F4xx) y debería crear un ejemplo de LED parpadeante. Todo esto debe hacerse desde cero, con código de inicio propio, sin uso de bibliotecas externas, etc.

Tengo una MCU que dice un STM32F4xx y quiero hacer parpadear el led en PA5 sin bibliotecas, desde cero, nada externo.

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

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

secuencia de comandos del enlazador flash.ld

MEMORY

    rom : ORIGIN = 0x08000000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000


SECTIONS

    .text :  *(.text*)  > rom
    .rodata :  *(.rodata*)  > rom
    .bss :  *(.bss*)  > ram

todo esto es usando herramientas gcc / gnu

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

para asegurarse de que arrancará correctamente y se vinculó correctamente, verifique la tabla de vectores del archivo de lista

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

Estos deben ser números impares, la dirección del manejador o rojo con uno

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

08000046 :
 8000046:   e7fe        b.n 8000046 

y comience en 0x08000000 en el caso de estas partes STM32 (algunos proveedores construyen para cero) (en el encendido, cero se refleja desde 0x08000000, por lo que el vector lo llevará al lugar adecuado en flash).

En cuanto al led, haga que el pin gpio sea una salida push-pull y apáguelo y vuelva a encenderlo. en este caso, queme algunos ciclos de la CPU y luego cambie de estado. al usar una función que no está en blinker01.c, obliga al compilador a realizar esos recuentos (en lugar de hacer algo volátil), un simple truco de optimización. PUT32 / GET32 preferencia personal, asegurándose de que se use la instrucción correcta, los compiladores no siempre usan la instrucción correcta y si el hardware requiere una operación de cierto tamaño, podría tener problemas. La abstracción tiene más ventajas que desventajas, en mi opinión.

Bastante simple de configurar y usar estas partes. Es bueno aprenderlo de esta manera, además de usar las bibliotecas, profesionalmente puede que tenga que lidiar con ambos extremos, tal vez sea el que escriba las bibliotecas para otros y necesite saber ambos al mismo tiempo.

Conocer sus herramientas es lo más importante y sí, la mayoría de la gente no sabe cómo hacer eso en este negocio, confían en una herramienta, trabajan alrededor de las verrugas de la herramienta o biblioteca en lugar de entender lo que está sucediendo y / o arreglarlo. . el objetivo de esta respuesta es 1) usted preguntó y 2) mostrar lo fácil que es usar las herramientas.

Podría haberlo hecho aún más simple si me deshiciera de las funciones en el ensamblaje y solo usara el ensamblaje como una forma muy simple de hacer la tabla de vectores. el cortex-m es tal que puede hacer todo en C excepto la tabla de vectores (que puede pero es fea) y luego usar algo como el ensamblador bien probado y funcional para crear la tabla de vectores.

Tenga en cuenta cortex-m0 frente a los demás

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

el cortex-m0 y (m1 si te encuentras con uno) están basados ​​en armv6m, donde el resto son armv7m, que tiene como 150 extensiones más del pulgar2 para el conjunto de instrucciones del pulgar (anteriormente instrucciones no definidas que se usaban para hacer instrucciones de longitud variable). todo el cortex-ms ejecuta el pulgar, pero el cortex-m0 no admite las extensiones específicas de armv7m, puede modificar la compilación para decir cortex-m0 en lugar de m4 y funcionará bien en el m4, tome un código como este (parche subir las direcciones según sea necesario, tal vez el gpio sea diferente para su parte específica, tal vez no) y compilar para m0 se ejecutará en m0 … Al igual que la necesidad de verificar periódicamente para ver que la tabla de vectores se está construyendo correctamente, puede examinar el desmontaje para comprobar que se están utilizando las instrucciones adecuadas.

Esa es una pregunta bastante importante, pero intentaré responderla y brindarle una descripción general de todos los pasos que se requieren para convertir un “hola mundo” en un ejecutable de brazo real. Me centraré en los comandos para mostrar cada paso en lugar de explicar cada detalle.

#include 

int main()

        printf("Hello world!rn");
        return 0;

Usaré gcc en ubuntu 17.04 para este ejemplo. arm-none-eabi-gcc (15:5.4.1+svn241155-1) 5.4.1 20160919

1. Preprocesamiento

Básicamente se ocupa de cada línea que comienza con un #. Para mostrar la salida del uso del preprocesador arm-none-eabi-gcc -E o arm-none-eabi-cpp.

arm-none-eabi-gcc -E main.c

La salida es muy larga debido a todas las cosas que suceden cuando #include y todavía contiene líneas “ilegibles” como # 585 "/usr/include/newlib/stdio.h" 3

Si usa los argumentos -E -P -C la salida se vuelve mucho más clara.

arm-none-eabi-gcc -E -P -C main.c -o main-preprocessed.c

Ahora puedes ver eso #include acabo de copiar todo el contenido de stdio.h a su código.

2. Compilación

Este paso traduce el archivo preprocesado en instrucciones de ensamblaje, que aún son legibles por humanos. Para obtener el uso del código de máquina -S.

arm-none-eabi-gcc -S main.c

Deberías terminar con un archivo llamado main.s que contiene sus instrucciones de montaje.

3. Montaje

Ahora comienza a ser mucho menos legible para los humanos. Aprobar -c para gcc para ver la salida. Este paso es también la razón por la que es posible el montaje en línea.

arm-none-eabi-gcc -c main.c

Deberías terminar con un main.o archivo que se puede mostrar con hexdump o xxd. yo recomendaria xxd porque te muestra la representación ascii junto a los números hexadecimales sin procesar.

xxd main.o

4. Vinculación

La etapa final, después de eso, su programa está listo para ser ejecutado por el sistema de destino. El vinculador agrega el código “faltante”. Por ejemplo, no haba ni rastro del printf() función o cualquier cosa de stdio.h.

arm-none-eabi-gcc main.c –specs = nosys.specs -o main

Para el --specs=nosys.specs ver aquí: https://stackoverflow.com/a/23922211/2394967

Esta es solo una descripción general aproximada, pero debería poder encontrar mucha más información sobre cada paso aquí en stackoverflow. (ejemplo para el enlazador: ¿Qué hacen los enlazadores?)

Los módulos a los que se refiere (ctr0.o, crti.o, _init, __libc_init_array, _exit) son bibliotecas / archivos de objetos / funciones precompilados por IAR y / o Keil. Como está diciendo, son necesarios para inicializar el entorno (inicialización de variables globales, tabla de vectores de interrupción, etc.) antes de ejecutar su función main ().

En algún momento de esas bibliotecas / archivos de objetos, habrá una función en C o ensamblado como esta:

void startup(void)
 
    ... init code ...

    main();

    while(1);   // or _exit()

Puede consultar estos ejemplos que compilan el código de inicio desde cero:

http://www.embedded.com/design/mcus-processors-and-socs/4007119/Building-Bare-Metal-ARM-Systems-with-GNU-Part-1–Getting-Started

https://github.com/payne92/bare-metal-arm

Aquí puedes ver las comentarios y valoraciones de los usuarios

Si conservas algún reparo y disposición de aclarar nuestro post puedes escribir un exégesis y con gusto lo ojearemos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)


Tags : / /

Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *