STM32 Parte 2: Inicialización

La programación es dividir algo grande e imposible en algo pequeño y muy real.


Hola a todos, para empezar, me gustaría agradecer a los moderadores por perderse mi primera publicación (desagradable) y saludar a mamá. Y también me gustaría agradecer a todos los lectores y personas que señalaron mis errores y ayudaron a corregirlos. Inmediatamente hago una reserva de que en ruso no escribí desde el sexto grado, en general, no te enfades.

STM32 Parte 1: Lo básico

Entonces comencemos. En un artículo anterior, rápidamente repasé los primeros puntos. Este fue nuestro archivo startup.c, que fue responsable de 2 vectores (stack, Reset_Handler) y un poco sobre el script del enlazador. Hoy complementaremos nuestro código de inicialización, analizaremos el vinculador para piezas de repuesto y descubriremos cómo funciona todo.


extern void *_estack;

void Reset_Handler();

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    &_estack,
    &Reset_Handler
};

void __attribute__((naked, noreturn)) Reset_Handler()
{
    while(1);
}


Si este código no está claro para nadie, puede leerlo aquí (el artículo no es una fuente, pero trató de explicarlo).

Ahora veamos qué falta aquí y cómo agregarlo. Obviamente, las micras tienen otros vectores. Por ejemplo, el vector Hard_Fault es un vector que se invoca con acciones inadecuadas con μ, por ejemplo, si intenta ejecutar una instrucción que el procesador no comprende, saltará a la dirección del vector Hard_Fault. Hay muchos vectores de este tipo y no es un hecho que en nuestro programa usaremos todo. Además, los vectores de interrupción están en la misma tabla.

(Para aquellos que aún no saben qué es un vector, este es un puntero a una dirección, también conocido como "puntero").

Complementamos nuestro código y luego explicaré lo que hice.

extern void *_estack;

void Reset_Handler();
void Default_Handler();

void NMI_Handler()   __attribute__((weak, alias("Default_Handler")));
void HardFault_Handler()   __attribute__((weak, alias("Default_Handler")));
//     

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    &_estack,
    &Reset_Handler
    &NMI_Handler,
    &HardFault_Handler
};

void __attribute__((naked, noreturn)) Reset_Handler()
{
    while(1);
}

void __attribute__((naked, noreturn)) Default_Handler(){
    while(1);
}


Aquí agregué una nueva función Default_Handler (); que manejará todas las interrupciones, absolutamente. Pero también con la ayuda, __attribute__((weak, alias("Default_Handler")));noté que si se declara una función con el mismo nombre, será el controlador de esta interrupción. En otras palabras, en este momento es solo un apodo para Default_Handler. Entonces podemos agregar todos los vectores en la lista de su manual de referencia. Esto se hace de modo que si de repente decide usar una interrupción, pero se olvidó de crear un controlador de funciones, se llama al habitual Default_Handler.

Creo que en este punto con vectores puedes terminar e ir a la inicialización de sectores y al enlazador. Para comenzar, vamos a actualizar nuestro startup.c nuevamente agregando la inicialización de los sectores de datos y bss al cuerpo Reset_Handler, así como varias variables externas.

extern void *_sidata, *_sdata, *_edata, *_sbss, *_ebss;

void __attribute__((naked, noreturn)) Reset_Handler()
{


	void **pSource, **pDest;
	for (pSource = &_sidata, pDest = &_sdata; pDest != &_edata; pSource++, pDest++)
		*pDest = *pSource;

	for (pDest = &_sbss; pDest != &_ebss; pDest++)
		*pDest = 0;

	while(1);
}


Entonces, nuevas variables, y nuevamente fueron declaradas en nuestro script de enlazador. Recordemos el guión anterior . Y también compárelo con uno nuevo.

MEMORY{
	ROM(rx) : ORIGIN = 0x08000000, LENGTH = 1M
	SRAM (rwx):     ORIGIN = 0x20010000, LENGTH = 240K
}

_estack = LENGTH(SRAM) + ORIGIN(SRAM);

SECTIONS{
	.isr_vector : {
	KEEP(*(.isr_vector))
	} >ROM
	
	.text : {
        . = ALIGN(4);
        *(.text)
    } >ROM
	
	_sidata = LOADADDR(.data);
        .data : {
		. = ALIGN(4);
		_sdata = .;
		*(.data)
		. = ALIGN(4);
		_edata = .;
	} >SRAM AT>ROM
	
	.bss : {
		. = ALIGN(4);
		_sbss = .;
		*(.bss)
		. = ALIGN(4);
		_ebss = .;
	} >SRAM
}


Se han agregado dos nuevas secciones, estas son .data y .bss. ¿Por qué los necesitamos? bueno .data dejará variables globales y static irá a bss. Ambas secciones están en RAM, y si todo es simple con la sección bss (estos son solo ceros), entonces hay algunas dificultades con la sección de datos. Primero, los datos ya son datos inicializados que se conocen en tiempo de compilación. Por lo tanto, inicialmente la sección de datos se ubicará en algún lugar de la memoria flash. En segundo lugar, tenemos que indicar de alguna manera al enlazador el script que está en la memoria flash, pero se transferirá a la RAM durante la ejecución del programa. Echemos un vistazo a una parte del script donde describimos la sección de datos.


        _sidata = LOADADDR(.data);
        .data : {
		. = ALIGN(4);
		_sdata = .;
		*(.data)
		. = ALIGN(4);
		_edata = .;
	} >SRAM AT>ROM


Comienza con la declaración de sidata y la asignación, en este momento, de un significado que no nos queda claro. A continuación, comienza una descripción de la sección misma. Le aconsejo que lea sobre la función ALINEAR aquí , pero en pocas palabras, asignamos a nuestro punto (dirección actual) un valor que nuestro procesador acepta más fácilmente cuando intenta cargar datos o instrucciones desde allí.

También se incluyen _sdata, _edata y el final de la sección. Pero al final de la sección hay justo lo que necesitamos.SRAM AT>ROM. Esta línea le dice a nuestro enlazador que la sección está en SRAM pero esta sección está cargada en ROM o memoria flash. Ahora volvamos a nuestra variable _sidata y a la función LOADADDR. Esta función nos devolverá un valor que será igual a la dirección de la sección de carga. Es decir, indicamos que la sección se cargó en la memoria flash, pero la usamos en la RAM y, para transferir esta sección, necesitamos saber su dirección en la memoria flash, que es lo que devuelve la función LOADADDR. Mientras tanto, las variables _sdata y _edata indican la ubicación de la sección en la RAM, lo que nos da la oportunidad de cargar la sección de datos en la memoria correcta. Permítame recordarle que esto es con lo que trata el código de inicialización que complementamos anteriormente.

¿Por qué necesitamos este enlazador y tantas dificultades con la distribución de secciones de nuestra aplicación? La sección Datos es solo un pequeño ejemplo de por qué necesitamos poder colocar y cambiar secciones en la memoria. A continuación se muestra un ejemplo de la sección de texto que uso.

.text (ORIGIN(ROM_ITCM) + SIZEOF(.isr_vector)): {
        . = ALIGN(4);
        *(.text)
    } AT>ROM_AXIM


La esencia de este bloque es que también cargo la sección de texto en la memoria flash, pero uso un bus diferente para acceder a las instrucciones y un acelerador ART (algo así como un caché normal). Y para que todo funcione, indiqué la dirección de carga y la dirección durante la ejecución del código, pero no funcionaría si no indicara que la sección se debe compensar. Hay muchos más inventos de este tipo, no los mostraré a todos.

En esta etapa, las secciones se cargan donde es necesario y se inicializan. Tenemos una base de trabajo para la posterior escritura de código. En el próximo artículo, escribiremos nuestro primer controlador y parpadearemos un LED.

Gracias a todos por su atención y éxito en sus esfuerzos.

All Articles