LED intermitente en STM32 en ensamblador

Hace algún tiempo quería aprender ensamblador, y después de leer la literatura relevante, era hora de practicar. En realidad, se discutirá más a fondo. Al principio practiqué en Arduino Uno (Atmega328p), ahora decidí seguir adelante y tomé STM32. STM32F103C8 realmente cayó en mis manos sobre él y se realizarán más experimentos.

Las herramientas


Usé las siguientes herramientas:

  • Notepad ++ - para escribir código
  • Compilador GNU Assembler
  • STM32 ST-LINK Utility + ST-LINK V2: para actualizar el código en el microcontrolador y depurar

Inicio


El objetivo principal del lenguaje ensamblador para mí es aprender. Como nunca se sabe dónde encontrar otro problema interesante, se decidió escribir todo desde cero. La tarea principal era comprender cómo funciona el vector de interrupción. A diferencia de Atmega en STM32, el vector de interrupción no contiene instrucciones de salto:

jmp main 

En él se escriben direcciones específicas y, durante la interrupción, el propio procesador sustituye la dirección especificada en el vector en el registro de la PC. Aquí hay un ejemplo de mi vector de interrupción:

 .org 0x00000000 SP: .word STACKINIT RESET: .word main NMI_HANDLER: .word nmi_fault HARD_FAULT: .word hard_fault MEMORY_FAULT: .word memory_fault BUS_FAULT: .word bus_fault USAGE_FAULT: .word usage_fault .org 0x000000B0 TIMER2_INTERRUPT: .word timer2_interupt + 1 

Quiero llamar la atención del lector sobre el hecho de que la primera línea no es el vector de reinicio, sino los valores por los cuales se inicializará la pila. Inmediatamente después, hay un vector de reinicio seguido de 5 vectores de interrupción obligatorios (NMI_HANDLER - USAGE_FAULT).

Desarrollo


Lo primero con lo que me quedé atrapado fue la sintaxis del ensamblador ARM. Incluso durante el estudio del vector de interrupción, encontré referencias al hecho de que ARM tiene 2 tipos de instrucciones Thumb y no Thumb. Y ese Cortex-M3 (STM32F103C8, es decir, Cortex-M3) solo admite un conjunto de instrucciones Thumb. Escribí instrucciones estrictamente de acuerdo con la documentación, pero por alguna razón el ensamblador las maldijo.
registro no desplazado requerido
Resultó que al comienzo del programa
.sintaxis unificada
esto le dice al ensamblador que puede usar instrucciones Thumb y no Thumb al mismo tiempo.

Lo siguiente que encontré fueron los puertos GPOI predeterminados deshabilitados. Para que funcionen, entre otras cosas, debe establecer los valores apropiados en los registros RCC (reinicio y control de reloj). Utilicé el PUERTO C, se puede activar configurando el bit 4 (la numeración de los bits va desde cero) en RCC_APB2ENR (registro de habilitación de reloj periférico 2).

LED intermitente adicional. En primer lugar, como en Arduino, debe establecer un pin para la grabación. Esto se realiza a través de GPIOx_CRL (registro de control bajo) o GPIOx_CRH (registro de control alto). Aquí es necesario cancelar que para cada pin 4 bits son responsables en uno de estos registros (registros de 32 bits). 2 bits (MODEy) determinan la velocidad máxima de datos y la configuración de pin de 2 bits (CNF). Utilicé el pin 14 del PUERTO C, para esto configuré los bits [25:24] = 10 y los bits [27:26] = 00 en el registro GPIOx_CRH.

Para que el diodo se queme, debe configurar el bit correspondiente en GPIOx_ODR (registro de datos de salida). En mi caso, el bit 14. Esto podría terminar este simple ejemplo haciendo una función de retraso y poniendo todo en un bucle, pero no pude hacer esto. Decidí establecer interrupciones del temporizador ... Resultó que fue en vano, principalmente porque los temporizadores son demasiado rápidos para este tipo de tarea.

No describiré en detalle la configuración del temporizador, quienes están interesados ​​en el código en Github . La idea era simple: en un ciclo, envíe el procesador a Idle, salga de Idle por temporizador para encender / apagar el LED y nuevamente a Idle. Pero el temporizador funcionó mucho más rápido de lo que pude hacer todo lo anterior, por lo que tuve que ingresar un contador adicional.

El contador es una variable de 32 bits que debería haber estado en SRAM. Y luego otro rastrillo me estaba esperando. Cuando programé en Atmega para colocar una variable en SRAM, a través de .org configuré la dirección del comienzo de la memoria donde realmente se colocó el bloque de datos. Ahora, después de leer un poco sobre la inicialización de la memoria, no estoy seguro de si esto fue correcto, pero funcionó. Y decidí hacer lo mismo con STM32. La dirección de inicio de memoria en STM32F103C8 es 0x20000000. Y cuando hice .org en esta dirección, obtuve un binario de 512mb. Esto me envió un par de noches a fumar manuales. Todavía no entiendo al 100% cómo funciona esto, pero hasta donde entiendo, la sección .data coloca los valores por los cuales las variables deben inicializarse en un archivo ejecutable, pero en el tiempo de ejecución el programador debe inicializar los valores de las variables en la memoria. Corrígeme por favor si me equivoco. Terminé creando una variable como esta:

 .section .bss .offset 0x20000000 flash_counter: .word 

Inicializado al comienzo de la función principal y el LED parpadeó. Espero que este artículo ayude a alguien. Si tiene preguntas, con gusto las responderé.

Source: https://habr.com/ru/post/es405959/


All Articles