Bare-Metal STM32: del poder al Hola mundo
Algunos pueden preguntar por qué querría programar un microcontrolador Cortex-M como la serie STM32 usando nada más que el kit de herramientas ARM y la hoja de datos y el manual de referencia proporcionados por ST Microelectronics. Si su primera respuesta a esa pregunta no fue una inmersión en pánico hasta la salida de emergencia más cercana, entonces quizás esa pregunta despertó su interés. ¿Por qué realmente?
Sin lugar a dudas, se podrían usar algunos de los marcos existentes para programar STM32 MCU, ya sea el marco ST HAL, CMSIS simple o incluso algo más arduo. Pero, ¿dónde está la diversión en eso cuando al final del día uno todavía depende completamente de la documentación de ese marco y sus desarrolladores? De manera más sucinta, si el contenido de los manuales de referencia de STM32 todavía parece una tontería, ¿se entiende realmente la plataforma?
Echemos un vistazo a cómo funciona la programación de metal desnudo STM32 y hagamos el ejemplo más básico, ¿verdad?
Como una computadora, simplemente diferente
En principio, hay poca diferencia entre un microcontrolador y una computadora completa basada en Intel o AMD. Todavía tiene al menos un núcleo de CPU que se inicializa después de que la alimentación externa se haya estabilizado, luego el firmware inicial se lee desde una ubicación fija. En su escritorio, este es el BIOS. En el caso de una MCU, este es el código almacenado desde un desplazamiento específico en la memoria de solo lectura (ROM) (generalmente) integrada. Pase lo que pase a continuación, depende de este código.
En general, en este código inicial queremos hacer lo básico, cómo configurar la tabla de vectores de interrupción y los contenidos básicos de registros específicos. Inicializar el puntero de pila (SP) es esencial, así como copiar algunas partes de la ROM en la RAM e iniciar algunos registros. Finalmente, se llama a la función principal, similar a cuando se inicia el sistema operativo de una computadora después de que el BIOS ha terminado de configurar el entorno.
El ejemplo de empuje
Probablemente, el ejemplo útil más básico sería lo que cariñosamente llamo "Push" en mi marco Nodate STM32. Es más básico que el ejemplo tradicional de 'Blink', ya que solo usa los registros Reset & Clock Control (RCC) y un periférico GPIO básico. Todo lo que hace es leer el registro de entrada del pin GPIO y establecer una salida dependiendo del valor de entrada, pero eso aún le da a alguien el poder de encender o apagar el LED a voluntad:
.gist tabla {borde-fondo: 0; }
#incluir | |
int main () { | |
// const uint8_t led_pin = 3; // Nucleus-f042k6: Puerto B, pin 3. | |
// const GPIO_ports led_port = GPIO_PORT_B; | |
// const uint8_t led_pin = 13; // Descubrimiento STM32F4: Puerto D, pin 13 (naranja) | |
// const GPIO_ports led_port = GPIO_PORT_D; | |
// const uint8_t led_pin = 7; // Core-F746ZG: Puerto B, pin 7 (azul) | |
// const GPIO_ports led_port = GPIO_PORT_B; | |
const uint8_t led_pin = 13; // Pastilla azul: Puerto C, pin 13. | |
const GPIO_ports led_port = GPIO_PORT_C; | |
// const uint8_t butono_pin = 1; // Core-f042k6 (PB1) | |
// const GPIO_ports buttonport = GPIO_PORT_B; | |
// const uint8_t butono_pin = 0; // Descubrimiento STM32F4 (PA0) | |
// const GPIO_ports buttonport = GPIO_PORT_A; | |
// const uint8_t butono_pin = 13; // Core-F746ZG (PC13) | |
// const GPIO_ports buttonport = GPIO_PORT_C; | |
const uint8_t butono_pin = 10; // Pastilla azul | |
const GPIO_ports buttonport = GPIO_PORT_B; | |
// Establece el modo pin en el pin LED. | |
GPIO :: set_output (puerto_ led, pin_ led, GPIO_PULL_UP); | |
GPIO :: escribir (puerto_ led, pin_ led, GPIO_LEVEL_LOW); | |
// Establecer el modo de entrada en el pin del botón. | |
GPIO :: set_input (puerto_botón, pin_botón, GPIO_FLOATING); | |
// Si el botón baja al suelo (de mayor a menor), 'button_down' es bajo cuando se presiona. | |
// Si el botón se eleva a Vdd (de menor a mayor), 'button_down' es alto cuando se presiona. | |
uint8_t button_ down; | |
dum (1) { | |
botón_abajo = GPIO :: leer (botón_puerto, botón_dedo); | |
si (button_down == 1) { | |
GPIO :: escribir (puerto_ led, pin_ led, GPIO_LEVEL_HIGH); | |
} | |
una mentira { | |
GPIO :: escribir (puerto_ led, pin_ led, GPIO_LEVEL_LOW); | |
} | |
} | |
return 0; | |
} |
ver stm32_example_nodatan.cpp sin procesar alojado con ❤ de GitHub
Aquí podemos ver los dos elementos más visibles: el primero es el main()
función llamada, el segundo es el módulo GPIO incluido. Este contiene una clase C ++ estática que se llama escribir en la salida GPIO, con un LED conectado, y también leer desde otra entrada que tiene un botón conectado. También podemos ver que la llamada 'Blue Pill' (STM32F103C8) tiene sus pines definidos, pero el ejemplo tiene algunos más predefinidos que podemos cambiar comentando las líneas correspondientes.
RCC_AHBENR de STM32F0xx registra una descripción en el RM.
Entonces, ¿dónde juegan los récords de RCC aquí? Como sugiere su nombre, controlan los dominios de reloj dentro de la MCU, actuando esencialmente como interruptores de conmutación para partes de la MCU. Si miramos por ejemplo el RCC_AHBENR
registrar una descripción en el Manual de referencia STM32F0xx (sección 6.4), podemos ver un poco aquí con una etiqueta IOPAEN
(Entrada / Salida Puerto A ENable), que alterna el reloj para el periférico GPIO A. Lo mismo ocurre con los demás periféricos GPIO.
Como se indica en el cuadro anterior, AHBENR
significa el registro de habilitación para el AHB
, que es uno de los buses dentro de la MCU al que están conectados el núcleo del procesador, la SRAM, la ROM y los periféricos:
Arquitectura del sistema STM32F0xx (sección 2.1 de RM).
El AHB (bus funcional avanzado) junto con el APB (bus periférico avanzado) están cubiertos por la especificación Arm AMBA. En general, el AHB es el bus más rápido, conectando el núcleo del procesador con SRAM, ROM y periféricos de alta velocidad. Los dispositivos laterales más lentos se colocan en el APB más lento, con un puente de AHB a APB que permite la comunicación.
Es hora de reunirse
Como se mencionó anteriormente, el primer código que se lanza es el código inicial. Para el STM32F042x6-MCU, aquí se puede ver un programa inicial general en el ensamblaje Thumb. Este es el ASM general proporcionado por ST (por ejemplo, para STM32F0xx) junto con el paquete del dispositivo CMSIS. Inicializa la MCU y llama al SystemInit()
operar en el código CMSIS C de bajo nivel, p. ej. por STM32F0xx.
Esta SystemInit()
La función restablece los registros del reloj del sistema al estado de restablecimiento deseado: utilizando el oscilador HSI interno, a la velocidad predeterminada. Correo libc
rutinas de configuración (aquí Newlib, biblioteca de soporte C / C ++), finalmente lanza el main()
trabajar con:
bl main
Esta instrucción significa "Bifurcación con enlace", lo que hace que la ejecución salte básicamente a la etiqueta especificada. En este punto, estamos firmemente en nuestros ejemplos "Push" main()
. Ahora todo depende de la clase GPIO para armar las cosas.
GPIO
El método de primera clase que llamamos es GPIO::set_output()
establezca un determinado pin como salida con resistencia habilitada. Aquí, también, encontramos nuestras primeras diferencias entre las familias STM32, ya que la familia Cortex M3 más antigua basada en F1 tiene periféricos GPIO muy diferentes a sus hermanos más nuevos F0, F4 y F7. Esto significa que para el STM32F1xx tenemos que disputar varias opciones con un pin en un registro:
.gist tabla {borde-fondo: 0; }
// Los registros de entrada / salida se distribuyen en dos registros combinados (CRL, CRH). | |
si (pin <8) { | |
// Configurar registro CRL (CNF & MODE). | |
uint8_t pinmode = pin * 4; | |
uint8_t pincnf = pinmode + 2; | |
if (velocidad == GPIO_LOW) {instance.regs-> CRL | = (0x2 << modo pin); } | |
else if (velocidad == GPIO_MID) {instance.regs-> CRL | = (0x1 << modo pin); } | |
else if (velocidad == GPIO_HIGH) {instance.regs-> CRL | = (0x3 << modo pin); } | |
if (escriba == GPIO_PUSH_PULL) {instancia.registros-> CRL & = ~ (0x1 << pincnf); } | |
else if (escriba == GPIO_OPEN_DRAIN) {instance.regs-> CRL | = (0x1 << pincnf); } | |
} | |
una mentira { | |
// Configurar un registro CRH. | |
uint8_t pinmode = (pin - 8) * 4; | |
uint8_t pincnf = pinmode + 2; | |
if (velocidad == GPIO_LOW) {instance.regs-> CRH | = (0x2 << modo pin); } | |
else if (velocidad == GPIO_MID) {instance.regs-> CRH | = (0x1 << modo pin); } | |
else if (velocidad == GPIO_HIGH) {instance.regs-> CRH | = (0x3 << modo pin); } | |
if (escriba == GPIO_PUSH_PULL) {instancia.registros-> CRH & = ~ (0x1 << pincnf); } | |
else if (escriba == GPIO_OPEN_DRAIN) {instancia.regs-> CRH | = (0x1 << pincnf); } | |
} |
ver stm32_gpio_output_f1.cpp sin procesar alojado con ❤ desde GitHub
Pero para las otras familias mencionadas tenemos un registro diferente para cada opción (modo, velocidad, pull up / down, type):
.gist tabla {borde-fondo: 0; }
uint8_t pin2 = pin * 2; | |
instancia.regs-> MODER & = ~ (0x3 << pin2); | |
instance.regs-> MODER | = (0x1 << pin2); | |
instancia.regs-> PUPDR & = ~ (0x3 << pin2); | |
si (pupd == GPIO_PULL_UP) { | |
instance.regs-> PUPDR | = (0x1 << pin2); | |
} | |
más si (pupd == GPIO_PULL_DOWN) { | |
instance.regs-> PUPDR | = (0x2 << pin2); | |
} | |
si (escriba == GPIO_PUSH_PULL) { | |
instance.regs-> OTYPER & = ~ (0x1 << pin); | |
} | |
si no (escriba == GPIO_OPEN_DRAIN) { | |
instance.regs-> OTYPER | = (0x1 << pin); | |
} | |
si (velocidad == GPIO_LOW) { | |
instancia.regs-> OSPEEDR & = ~ (0x3 << pin2); | |
} | |
más si (velocidad == GPIO_MID) { | |
instancia.regs-> OSPEEDR & = ~ (0x3 << pin2); | |
instance.regs-> OSPEEDR | = (0x1 << pin2); | |
} | |
si no (velocidad == GPIO_HIGH) { | |
instancia.regs-> OSPEEDR & = ~ (0x3 << pin2); | |
instance.regs-> OSPEEDR | = (0x3 << pin2); | |
} |
ver stm32_gpio_output_f0-4-7.cpp sin procesar alojado con ❤ de GitHub
La configuración de una opción en un registro se realiza mediante operaciones bit a bit para establecer los bits de destino mediante la manipulación del mapa de bits. El nombre del registro suele ser bastante descriptivo, por ejemplo PUPDR
lo que significa Registro de Pull-Up Pull-Down.
El estilo que uno prefiere está principalmente en el ojo del espectador. Sin embargo, en el caso de establecer un pin como entrada, prefiero el estilo periférico GPIO más nuevo, con el siguiente código bastante compacto en lugar del impresionante espectáculo entrelazado STM32F1xx:
.gist tabla {borde-fondo: 0; }
uint8_t pin2 = pin * 2; | |
instancia.regs-> MODER & = ~ (0x3 << pin2); | |
instancia.regs-> PUPDR & = ~ (0x3 << pin2); | |
si (pupd == GPIO_PULL_UP) { | |
instance.regs-> PUPDR | = (0x1 << pin2); | |
} | |
una mentira { | |
instance.regs-> PUPDR | = (0x2 << pin2); | |
} |
ver stm32_gpio_input_f0-4-7.cpp sin procesar alojado con ❤ de GitHub
Para leer de la entrada pin, nos referimos al registro de datos de entrada (GPIO_IDR
) para este banco GPIO:
.gist tabla {borde-fondo: 0; }
uint32_t idr = instancia.regs-> IDR; | |
exterior = (idr >> pin) & 1U; // Leer el bit deseado. |
ver stm32_gpio_read.cpp sin procesar alojado con él desde GitHub
Del mismo modo, utilizamos el registro de datos de resultados (ODR
) cuando escribimos en un pin:
.gist tabla {borde-fondo: 0; }
si (nivel == GPIO_LEVEL_LOW) { | |
instance.regs-> ODR & = ~ (0x1 << pin); | |
} | |
si no (nivel == GPIO_LEVEL_HIGH) { | |
instance.regs-> ODR | = (0x1 << pin); | |
} |
ver stm32_gpio_write.cpp sin procesar alojado con él desde GitHub
Finalmente, el instance
en los fragmentos de código anteriores es una referencia a una entrada en std::vector
que se creó estáticamente después de la puesta en marcha. Registra las propiedades de cada periférico:
.gist tabla {borde-fondo: 0; }
std :: vector | |
Instancia GPIO_case; | |
std estático :: vector | |
#si está definido RCC_AHBENR_GPIOAEN || definido RCC_AHB1ENR_GPIOAEN || definido RCC_APB2ENR_IOPAEN | |
((* Casos estáticos))[GPIO_PORT_A].regs = GPIOA; | |
#terminara si | |
#si está definido RCC_AHBENR_GPIOBEN || definido RCC_AHB1ENR_GPIOBEN || definido RCC_APB2ENR_IOPBEN | |
((* Casos estáticos))[GPIO_PORT_B].regs = GPIOB; | |
#terminara si | |
[..] | |
return casesStatic; | |
} | |
std estático :: vector |
ver stm32_gpio_instances.cpp sin procesar alojado con él desde GitHub
Si existe un periférico (es decir, listado en el encabezado CMSIS para esa MCU, por ejemplo, STM32F042), se crea una entrada en GPIO_instance
struct apuntando a sus registros mapeados en memoria ('regs
'). A continuación, se puede hacer referencia a estos casos junto con alguna metainformación en ellos, como si aún se han activado:
.gist tabla {borde-fondo: 0; }
GPIO_instance & instance = (* instancesStatic)[port]; | |
// Compruebe si un puerto está activo, si no, intente activarlo. | |
if (! instance.active) { | |
if (Rcc :: enablePort ((RccPort) puerto)) { | |
instancia.activo = verdadero; | |
} | |
una mentira { | |
falso retorno; | |
} | |
} |
ver stm32_gpio_instances_get.cpp sin procesar alojado con ❤ desde GitHub
La ventaja de esto es, como hemos visto antes, que luego se puede usar el mismo código, sin importar con qué periférico estemos tratando, ya que todos son idénticos según un acuerdo gubernamental.
RCC
La clase RCC también rastrea si existe un dispositivo periférico utilizando el mismo preprocesador CMSIS definido para evitar sorpresas. Después de eso, habilitar un reloj periférico es bastante fácil:
.gist tabla {borde-fondo: 0; }
bool Rcc :: enable (periferia RccPeripheral) { | |
uint8_t perNum = (uint8_t) periférico; | |
RccPeripheralHandle & ph = (* perHandlesStatic)[perNum]; | |
if (ph.exists == false) { | |
falso retorno; | |
} | |
// Verifica el estado actual del periférico. | |
if (ph.count> 0) { | |
if (ph.count> = handle_max) { | |
falso retorno; | |
} | |
// Incrementa el número de administradores en uno. | |
ph.count ++; | |
} | |
una mentira { | |
// Activar el periférico. | |
ph.count = 1; | |
* (ph.enr) | = (1 << ph.enable); | |
} | |
devuelve verdadero; | |
} |
ver stm32_rcc_enable.cpp sin procesar alojado con ❤ de GitHub
Además de alternar la posición del bit (ph.enable
), también realizamos el recuento de referencias, para no deshabilitar accidentalmente un lado periférico cuando otra parte del código todavía lo está usando.
Ejecutando el ejemplo
Después de revisar el material anterior, deberíamos tener una idea de cómo funciona el ejemplo "Push" en un nivel fundamental. Ahora podemos construirlo y operarlo. Para esto necesitamos, como se mencionó, la cadena de herramientas ARM y el marco Nodate instalados. El primero se puede obtener a través del administrador de paquetes favorito de alguien (paquete: arm-none-eabi-gcc) o el sitio web de Arm. El marco de Nodate se obtiene a través de Github, después de lo cual la ubicación del directorio raíz de Nodate debe especificarse en global NODATE_HOME variable de sistema.
Una vez que se haya resuelto esto, vaya a la carpeta Nodate y al examples/stm32/pushy
directorio. Aquí, abra el Makefile y seleccione algunas de las placas preseleccionadas (actualmente Blue Pill, Core-F042K6, STM32F4-Discovery o Core-746ZG). Entonces, ábrelo src/pushy.cpp
y asegúrese de que las líneas apropiadas para el tablero de destino no estén comentadas.
Luego, en la carpeta con el Makefile, compile con make
. Con la placa de destino conectada a través de ST-Link, asegúrese de que OpenOCD esté instalado y parpadeando make flash
. Esto es para escribir la imagen del firmware en la placa.
Con un botón conectado al pin especificado y Vdd
, al presionar este botón, se encenderá un LED en la placa. Esto muestra el uso básico de un periférico GPIO STM32, y ya está un paso más allá de "parpadear".
Con suerte, esto ha demostrado cómo el desarrollo de STM32 es bastante simple. Estén atentos a temas más avanzados a medida que avanzamos en este tema.
Greg A. dice:
No quería usar bibliotecas de lenguaje C o arduine o IDE completos para stm32, y recopilé los recursos que necesitaba aquí http://galexander.org/stm32
Vinalon dice:
Buena introducción: es posible que también desee mencionar brevemente cómo instalar el kit de herramientas GNU ARM.
También podría ayudar a explicar cómo se suele acceder a la CPU y los registros de periféricos como punteros a ubicaciones de memoria específicas, y cómo todas esas estructuras como "GPIO_instance" se relacionan con esas direcciones de registro y mapas de bits.
Parece que esta publicación pasó directamente a usar HAL, pero tal vez me perdí una publicación anterior de esta serie.
Vinalon dice:
(Lo siento, la mayoría de esas cosas se mencionan, simplemente no me acerqué lo suficiente para verlas. ¡Buen artículo!)
Rastersoft dice:
Para un proyecto para mi trabajo, tuve que usar la cadena de herramientas directamente, como dices aquí, y lo primero que hice fue crear una cola de eventos / mensajes muy simple. Simplifica enormemente el trabajo con interrupciones.
gja dice:
FreddieChopin enseñó cosas como hace 10 años. Vale la pena leer sus tutoriales porque no juega con pelotas, sino que se centra en la enseñanza en bruto.
Alain Royer dice:
Hago todos mis controladores bare metal pero en C ++, más rápido, más pequeño y te da una mejor comprensión del chip y el periférico. Estoy construyendo un sistema GUI gráfico completo para STMF7 y completamente desnudo ... ni siquiera una inclusión que no sea STM32f746.h, después de unas semanas incluso lanzaré una versión GRBL usando mi GUI a través del descubrimiento stm32f7 ... can ' No espere a ver si a la gente le gusta. Si alguien dice C ++ si no es eficaz con el código de tamaño, debería leer el documento IAR sobre lo que puede o no puede usarse en C ++ incrustado.
Vazhnov dice:
¿Está planeando publicar el código bajo alguna licencia de código abierto?
Alain Royer dice:
Si.
Alain Royer dice:
Se acabará
https://github.com/aroyer-qc
cuando esté listo !!
Tim B dice:
Genial, me sentí como el único que hacía esto, p. Ej. Durante el tiempo de compilación, se generó un conjunto de manejadores / ganchos de interrupciones dinámicas: https://gist.github.com/timblakely/a1c9b1795043ebfc3e5023825433dc9b. Ahora, si solo WG21 se uniera y hiciera una cosa std :: start_lifetime_as (nee std :: bless) ...
¿Dónde podemos seguir los detalles?
TK dice:
¿Cómo es que hacer bare metal en C ++ “te da una mejor comprensión del chip y del periférico”?
Roberto dice:
¡Buen articulo! (me recordó esto https://github.com/dwelch67/raspberrypi)
Paulvdh dice:
No me siento inclinado a leer el artículo completo hoy, pero me trae recuerdos de:
http://pandafruits.com/stm32_primer/stm32_primer_hardware.phpFue el artículo más informativo que pude encontrar cuando comencé con STM32.
También maneja los aspectos básicos de la parte inferior, desde escribir su propio script de enlace, hasta manejar la compensación por prohibiciones de registro de E / S y los primeros pasos para conectarse a GDB para la depuración.Potrillo dice:
Si está interesado en la programación ARM de metal desnudo, este proyecto puede interesarle: https://libtungsten.io/. Es para la familia ATSAM4, no para el STM32, pero es un MCU Cortex-M4 similar de Atmel / Microchip y los principios son los mismos.
Básicamente es un HAL que pretende estar bien documentado, con código bien comentado, para ser leído, entendido y personalizado. Comenzó como un ejercicio para aprender más sobre los problemas de bajo nivel (así como este artículo), luego evolucionó a una biblioteca (en su mayoría) estable que cubre la mayoría de las capacidades del chip. Ejecuta todos mis proyectos integrados.
A los documentos todavía les faltan algunas cosas, pero ya hay mucho que leer para aquellos que estén interesados (sin embargo, no hay muchos ejemplos). El tutorial de GDB trata sobre cómo usar esta herramienta para inspeccionar el comportamiento de su programa directamente en el chip: https://libtungsten.io/tutorials/gdb.
(Descargo de responsabilidad: soy el autor de este proyecto, si eso no estaba claro)io358 dice:
¿No se opone HAL casi a la programación de metal desnudo? Y realmente no entiendo cómo abstraer características exclusivas de un chip / familia en particular. Al igual que el DMA, STM32F1xx HW puede circular en círculos: escanee las posibilidades de ADC por sí solo e incluso coloque los resultados en la ubicación deseada. Buena suerte persuadiendo, p. Ej. AtMega hace algo así. Su HW no es tan genial, para empezar, no tiene DMA. A lo sumo, puedo imaginar un administrador de IRQ + irq imitando esto, pero aún necesita una configuración específica del dispositivo y un controlador de irq y en ningún lugar tan genial como el combo mencionado STM puede hacer donde todo el trabajo pesado se realiza en el hardware sin la intervención del software en absoluto, que es todo punto de DMA.
Wallace Owen dice:
Sí, pero el código HAL en sí es la belleza, porque él mismo está desnudo. código de metal.
RubyPanther dice:
"¿No se opone HAL casi a la programación de metal desnudo?"
No. En la programación básica, todavía tiene bibliotecas y uso de código, por lo que HAL es solo una interfaz para el código de la biblioteca que se puede compartir en diferentes sistemas. No hay una capa de tiempo de ejecución, esa es la diferencia de OS HAL.
Con CMSIS solo obtiene una interfaz compatible que implementan las bibliotecas, principalmente a través de macros. Por lo tanto, no hay un sistema operativo, pero todavía hay una interfaz coherente definida por ARM que implementan los distintos proveedores. Por ejemplo, las bibliotecas de controladores de TI generalmente implementan tanto la interfaz "driverlib" de TI como la interfaz CMSIS. Por lo tanto, la mayor parte del código anterior sería fácilmente portátil.
omzlo dice:
Bueno, Maya, las mentes se encuentran!
Después de escribir recientemente un artículo sobre la programación de Baremetal en etaAVR-0 (https://www.omzlo.com/articles/baremetal-programming-on-the-tinyavr-0-micro-controllers), que apareció recientemente en La-Tecnologia, Comencé a escribir una entrada de blog titulada temporalmente "Programación de Baremetal en el STM32 desde cero". Tomo un ángulo ligeramente diferente, buceando aún más profundo, ¡pero veo cosas muy similares!
También estoy experimentando con C ++ en el STM32L031. Sin embargo, mi enfoque aquí es completamente diferente al suyo, ya que trato los periféricos como los GPIO como objetos, p.
"
// Declare Status_LED como pin 0 en el puerto B
GpioClass Status_LED (GPIOB, 0);// usar
principal () {
...
Status_LED.configureAsOutput ();
Status_LED.digitalSet (); // Pon el pin en alto
...
}"
Esto me permite pasar GPIO (cualquier otro periférico) como parámetros para funciones, por ejemplo.
De todos modos, ¡buen artículo!
Daños severos a los neumáticos dice:
¡Buen articulo!
Soy un gran admirador de la serie STM32 y, para mí, el bare metal es la única forma.
Podría dar muchas razones, pero sobre todo es más divertido.
Pero quería transmitir este pensamiento divertido de un chico con el que estoy colaborando. Ambos detestamos el entorno Arduino y él dijo: "¡Es como enamorarse de un abrigo de piel!". No es la primera metáfora que se me ocurrió, pero creo que resume bastante bien la situación.
Algunos de mis ejercicios básicos están aquí:
https://github.com/trebisky/stm32f103
Daños severos a los neumáticos dice:
Buen articulo
Soy un gran admirador de la serie STM32 y, para mí, el metal desnudo es el único juego en la ciudad.
Es un placer.
Hablé con un colaborador sobre esto. Tanto él como yo detestamos el entorno Arduino. Dijo: "¡Es como enamorarse de un abrigo de piel!" No es la primera metáfora en la que pensaría, pero creo que muy adecuada.
io358 dice:
Personalmente disfruto del hardware STM32F1xx. Una obra maestra muy bien pensada. Incluso el F1xx más simple puede obtener hasta 16 canales ADC en un bucle, volver con DMA y continuar conectando todo completamente en el hardware.
Sorpresas igualmente agradables en muchos otros lugares. Bueno, algunas cosas raras también, pero todavía veo mejor hardware.
Lo mismo podría decirse del núcleo de Cortex M3 y alrededores como nvic, etc. Ni demasiado diseñado ni lisiado, se siente ... como un 6502 bien hecho. Realmente, realmente correcto. Usando acceso moderno y técnicas de ie en las que no es necesario guardar todos los transistores a expensas de un hardware de mala calidad "api".
darkspr1te dice:
Me encanta la metáfora, llevé el firmware táctil de bigtreetech a varias pantallas táctiles LCD y también pirateé varios dispositivos stm32, incluido el descarte de chips bloqueados y la reversión del método de cifrado, para acelerar y ahorrar tiempo. Tengo un método de dos pasos para hacer "hola" al mundo. "para el dispositivo stm, uso el generador stmcube para seleccionar las velocidades de reloj / gpio / HALS y exportar con una opción de Makefile seleccionado, el drop en platformio.ini con platform = stm32cube como su entorno de compilación, y presiono compilar. stlink / bmp y muchos otros depuradores admitidos, luego puedo probar pines / hardware desconocidos y crear una placa base para un dispositivo desconocido. Los pasillos me dan la capacidad de probar los elementos sin tener que profundizar en un registro en cualquier manual cuando una plataforma me permite saltar a los valores de registro de nivel inferior en los archivos .h. los archivos make se crearán en Windows con WSL habilitado y arm-gcc instalado en la ruta. Este método también me permite omitir, por ejemplo, cmsis de HAL al probar otras fuentes de código. No soy un experto y hago esto por diversión, así que este es el método que encontré para saludar a un mundo en ideas con un buscador de errores. también funciona con linux / atom / wine para stmcube. después de acostumbrarme a un tablero, omito la parte cúbica y profundizo en un proyecto anterior y elimino las líneas de código requeridas. Un consejo que daré es usar el controlador UART / hard fualt al depurar, un ejemplo aquí https://github.com/darkspr1te/mkstft35_V1_open_bootloader/blob/master/Src/stm32f4xx_it.c, junto con agregar el archivo syscalls .cy su redirección de printf.
Wallace Owen dice:
Puede que no entienda que sus amigos utilicen la frase. Tal vez sea yo, pero ese "amor por los abrigos de piel" suena bien.
¿Pero tal vez en la metáfora los amantes están en mantos separados?
Geoffrey McRae dice:
MIT ha autorizado una plantilla de proyecto GNU C STM32F103 que soluciona todos los defectos aquí, como configurar el reloj HSE (External Fast External) y los relojes del bus periférico para 72 MHz.
https://github.com/gnif/STM32F103-BareMetal/
io358 dice:
Whoa, mojosa. Creo que hice algo similar ... aunque agregué macros un poco más elegantes para divertir a los MCU y definir un aspecto más llamativo y formas incorrectas difíciles de usar.
io358 dice:
Me divertí lanzando STM32F1xx desde abajo, con mi propio código. Es decir, ¡no utilicé código extranjero en absoluto! Ni siquiera los archivos de inicio, absolutamente todo sucede en mi código, mi código y mi código, así que lo domino hasta el último bit. Bueno, y mis enlaces. Esto eventualmente progresó un poco más, como pilas separadas para cuidadores y antecedentes, y la pila está debajo de todo lo demás. Entonces, aunque ocurriría un exceso de fallas duras, incluso a pesar de la falta de MMU, las fallas estilo Toyota se verían obstaculizadas desde el principio del problema.
Para hacerlo más divertido, también pongo todo en archivos clásicos * nix Make, y está contento con, por ejemplo, Compilador armado-eabi-none-stock de Debian. Así que es bastante compatible con Linux, y puedo usar mi editor favorito para escribir firmware STM32, sí. Sí, vete al diablo, querido Arduino, tu IDE es una basura terrible en comparación con un IDE ligero Geany, GTK + (o un editor de desarrollador avanzado).
Primero, creé algunas herramientas para jugar con los "registros de dispositivos" un poco más convenientemente (tienes que girar las piezas y demás, ya sabes, y C por sí solo no es muy agradable en esta parte). Luego leí una hoja de datos y definí algunos archivos secundarios en un archivo .h, a mi manera, aunque no basado en archivos cmsis / stmicro ni nada por el estilo. Codifiqué un temporizador automático, en C, que configura una arena C adecuada. Parece que C podría ser lo suficientemente genial como para comenzar él mismo, sin usar una reunión en absoluto, sí. Aunque luego tuve que codificar algunas cosas, de lo contrario no hay formas rápidas de habilitar o deshabilitar las IRQ a nivel mundial.
Con el tiempo, las cosas mejoraron. Ahora ... mencioné los poderes DMA de esta manera, dirigiendo F1 ADC para hacer SCAN + CONT en bucles, secuenciando algunos escaneos, y luego un motor DMA también se repite en un círculo coincidente, transmitiendo datos a un búfer. Entonces, al final del día, el hardware coloca mágicamente estas muestras en este búfer y las repite por sí solo. Así que hay un búfer "mágico" que de alguna manera contiene valores "nuevos" de muchos canales ADC. Todo lo que tiene que hacer el código es ... acceder a esta tabla y usar muestras como quiera. Bueno, también puede lanzar opcionalmente (alto favoritismo) IRQ después de la finalización cíclica si el procesamiento necesita una base de tiempo precisa de muestra.
El código ps es ... discutible. No estoy seguro de que deba publicarlo. Me gustan algunas cosas al respecto. Y algunas cosas no me gustan. Pero, en general, supongo que me divierto mucho a pedido.
tekkieneet dice:
Parece que su ejemplo es "Cómo escribir su propia biblioteca GPIO" en lugar de escribir código en bare metal.
es decir, si necesita nombrar una función con una docena de líneas de código tratando de analizar cuál es su función de E / S, por definición no es bare metal.En su lugar, debería escribir directamente en un registro. Personalice la macro / enumeración de esos bits para que sea más legible.
if (pupd == GPIO_PULL_UP) {instancia.registros-> PUPDR | = (0x1 << pin2);
En lugar de hacer un código como este, defina GPIO_PULL_UP como los valores que ingresa directamente. El objetivo del bare metal es omitir las capas. Simplemente reemplaza el teléfono de la biblioteca de otra persona por el tuyo.
io358 dice:
Bueno, sin embargo, en algún momento, hacer SÓLO “registrar una transferencia” se vuelve bastante ... difícil.
1. Es inseguro, propenso a errores y fácil de malinterpretar. Puedes conseguirlo con cualquier tamaño de AtTiny. Pero si desea un firmware un poco más grande con una lógica más complicada, las cosas pueden complicarse bastante, y podría ser difícil tener una idea de por qué. Porque una pila larga de HW REG xfers es bastante difícil de entender "aquí y ahora". Dos veces si olvidó poner comentarios y nombres sensibles.2. No es muy legible. Es posible agregar azúcar de lujo con #definitions, pero al final una función como una macro (¡que no le cuesta nada!), O una función real (a menudo optimizada para el equivalente de una macro / define) podría ser mucho más legible. Y menos propenso a los errores. Posiblemente al mismo precio y eficiencia que otras cosas. P.ej GCC puede ser muy inteligente con la conexión en línea y LTO puede hacer una gran magia en el tamaño de la empresa.
3. Una función también puede controlar los parámetros de entrada, rechazando ideas obviamente inválidas. La eficiencia es buena. Pero la seguridad y la protección también son buenas. Entonces, dependiendo de lo que queramos ... los compromisos pueden variar.
Como un buen ejemplo: cuando estaba codificando transferencias DMA, me las arreglé para confundirme un poco, hasta que pasé una dirección DMA de un puntero a ... bueno, un puntero adecuado cuya dirección tenía que ponerse en DMA. El problema es que DMA no cancela el informe del puntero por mí. Así que me puse en camino al fracaso de inmediato.
No hace falta decir que un DMA salvaje que hace algo inesperado puede causar muchas cosas raras. Lo cual también es difícil de depurar o tener una idea de por qué sucede. ¡Pero no fue así! Porque codifiqué un molde delgado sobre la configuración de la dirección DMA, y me dijo básicamente "pícaro, ¿qué diablos es esta dirección?" - luego, el problema se manifestó directamente durante el primer intento de utilizar DMA. Pero estoy seguro de que una verificación adicional agregó un código adicional pequeño, pero medible. Es posible, por ejemplo, tener versiones de "debug" y "release", donde se depuran demasiadas palabras y se mejoran algunos controles. Pero tendrás que vivir inesperadamente con firmware aleatorio. No es un gran problema si parpadea. Pero es un gran problema si controla algo peligroso o si algo puede explotar debido, por ejemplo, a una combinación incorrecta de MOSFET o algo así.
Potrillo dice:
Quizás "HAL" no sea la palabra adecuada para describirlo. Lo que pretendía, y creo que se relaciona con el artículo, es que esta biblioteca está intentando Le recomendamos que mire el código y vea cómo funciona MCU a un nivel básico y lo adapte a sus necesidades. Es por eso que la biblioteca se copia directamente en el directorio de su proyecto, es fácilmente accesible y modificable por proyecto, y también por qué la documentación se relaciona directamente con la hoja de datos (principalmente en la sección Hacking) y muestra el código del módulo. para mostrar cómo se configuran los registros en este periférico en particular. La documentación incluye un tutorial que intenta desmitificar la hoja de datos y animar al usuario a verla.
También estoy de acuerdo con usted en las características únicas de un chip, y no me gustan los HAL de propósito general por esa razón (en mi opinión, generalmente parece un "denominador común" que intenta ser demasiado universal o demasiado complicado de usar porque de todas las características de todos los chips compatibles). Esta es la razón por la que esta biblioteca solo admite un chip (o más precisamente una familia de chips, con las únicas diferencias en la tabla de mapa de pines y el tamaño de Flash / RAM que están configurados en el Makefile).Potrillo dice:
Lo sentimos, esto fue pensado para ser una respuesta al comentario de @ Something358 anterior
io358 dice:
Tengo la impresión de que, después de todo, algunas personas tienden a seguir patrones algo similares.
Aunque parece que he elegido más "eficiencia" y "características específicas del chip" que "portátil" o "avanzado", pero de nuevo no es una opción binaria ni una opción. Incluso ha evolucionado un poco con el tiempo para mí. Sin embargo, todavía apunté a algo de magia negra con macro y C. Porque todo esto podría ser calculado previamente por un compilador durante el tiempo de compilación, por lo que es adecuado para un código compacto y eficiente. Es como la mitad de una ventaja para un metal desnudo a mi gusto.
Básicamente, cuando escribo LED2_ON; Estoy bastante seguro de que se está desarrollando para BIT_SET (reg, BIT), que es una macro que generalmente predice en un conjunto bastante eficiente. Cuando hago esto de una manera más avanzada, estoy mucho menos seguro de cuál sería el código resultante.
También tuve algunas tonterías. ADC abstracto ... bueno, bueno, factible hasta cierto punto, características únicas del módulo. Al abstraer DMA ... bueno, comparte las mismas ideas en diferentes lugares. Resumen de la interacción ADC + DMA orquestada que es específica de STM32 y una configuración muy particular ... esa es mi imaginación falló. No tengo idea de cómo hacer esto portátil. A lo sumo, puedo imaginar que otros pueden copiar eso, p. Ej. Un controlador ADC IRQ + regular, pero en realidad no es igual al 100 /% (no le importa si las IRQ están activadas; el código necesita tiempo en el bus).
fede.tft dice:
Parece que hay muchos que programan sus STM32 en C ++, muy bien.
Finalmente, para GPIO, me conformé con un enfoque de metaprograma de plantilla, mira aquí: https://github.com/fedetft/miosix-kernel/blob/master/miosix/arch/cortexM3_stm32/common/interfaces-impl/gpio_impl. hTambién es fácil de usar:
//example.cpp
usando button = Gpio;
usando led = Gpio;int main ()
{
botón :: modo (Modo :: ENTRADA);
led :: modo (Modo :: SALIDA);
guiado :: bajo ();
while (botón :: valor () == 0);
guiado :: alto ();
por (;;);
}fede.tft dice:
Y los corchetes angulares se han eliminado del comentario ... parece que publicar un código de plantilla porque un comentario en una publicación de la-tecnologia no es fácil ...
El código debería ser así, pero con corchetes angulares en lugar de corchetes ...
usando button = Gpio (GPIOA_BASE, 0);
usando led = Gpio (GPIOA_BASE, 1);Grapadoras dice:
Sí, de hecho, ser capaz de formatear el código aquí sería una mejora.
jacques1956 dice:
¡Qué casualidad! Recibí una tarjeta STM32G431 STM32 NUCLEUS hoy y comencé a escribir archivos de encabezado C para registros. Pasaré a tu git anudado para ahorrarme un trabajo.
jacques1956 dice:
"El primer método de clase que llamamos es GPIO :: set_output () para establecer un determinado pin como salida con una resistencia habilitada".
Corrija esta oración. Probablemente pretendía escribir "push-pull" porque las resistencias pull-up (down) se utilizan en el modo de entrada.Paul Simon dice:
Gran recurso para todas las personas que ingresan a stm32 sin usar el HAL (hinchado y demasiado complejo):
https://vivonomicon.com/
No conozco al autor, pero sus ejemplos me han dirigido bien en la dirección correcta. Esto se convirtió en la construcción de mi propio inversor solar de 10kW con mppt conectado directamente a nuestra red de 230V / 50Hz.WestfW dice:
Me temo que no me convencen los argumentos sobre "mi biblioteca de Nodate es más" bare metal "que HAL o Arduino de ST". Sin embargo, una buena edición. (Supongo que no se trata realmente de las herramientas que terminas usando, sino de la profundidad con la que miras cómo funcionan ...)
¡Hace unos 6 años!
https://github.com/WestfW/Minimal-ARM
(Sin embargo, no fue muy interesante. Aparentemente, no mucha gente quiere programar ARM en lenguaje ensamblador).Maya Posch dice:
El objetivo es principalmente mostrar cómo hacer programación de metal desnudo, utilizando un código de muestra con el que estoy lo suficientemente familiarizado.
Eso es solo por casualidad el código que escribí.
rpavlik dice:
Ojo, si te atreves a programar el periférico STM32F103 I2C directamente, tiene algunos errores casi fatales que me costaron innumerables horas una vez cuando no aguantaba ni un poco la paliza. Sin embargo, incluso una de las dos bibliotecas proporcionadas por ST tergiversa la solución. No es divertido. Si lo golpeas un poco, probablemente sea bastante rápido.
Ivan Stepaniuk dice:
¡Buena y buena edición! Definitivamente comprobaré esto.
Es muy difícil navegar por el interpipe buscando ejemplos de estas cosas. Gran parte de él nunca se comparte, los ingenieros de la vieja escuela que trabajan en firmware tampoco han oído hablar de github. Puede ser bastante alto.
Independientemente de la plantilla de marco que use, cualquier cosa más que parpadear (cuando necesite configurar temporizadores completos, OCC, ADC, DMA, etc.) Realmente recomiendo usar la propia herramienta ST de CubeMX (si no el IDE, al menos el paquete generador de códigos). Realmente simplifica el proceso para comenzar.
Lo que estoy haciendo ahora es comenzar con CubeMX y el complemento CubeMX de CLion para iniciar un proyecto. Logra su objetivo de "parpadear" con OpenOCD en 1 minuto. Luego voy desde allí, pero cuando estoy contento con el código generado, dejo el .ioc (de todos modos se queda en el git rap como referencia si es necesario) y continúo con el proyecto como un proyecto completamente CMake.
Esto es algo en lo que trabajé, prueba un sonido a 72 kHz, usando DMA, para detectar si hay un tono de "bip":
https://github.com/istepaniuk/dishwasher-beep-detector
algún chico dice:
Este artículo puede ser útil, debo leerlo con atención más adelante. Tengo una pastilla azul por ahí, pero hasta ahora el manual de referencia de 1000 páginas me ha asustado un poco ...
Grapadoras dice:
Disfruta de Rust en https://me.kxd.dev/2020/10/04/rust-on-px-her0-part-1/
Zerg dice:
Cuando un micro tiene un manual de referencia de 1000 páginas, ya no es un micro. Es un sistema completo en un chip. Y cuando necesita HAL para ocultar su desagradable complejidad y hacerlo accesible a simples mortales. Bueno, no es un chip para aficionados.
I; Estoy seguro de que es un buen micro, si solo eso es lo que haces 10 horas al día en el trabajo. Pero parece un verdadero PITA estropear de otra manera.
Abbadon dice:
¡Yo apoyo esto! Leí y trabajé en bastantes artículos / tutoriales en este sitio. Me dio una gran comprensión de cómo usar el ensamblaje de arranque, escribir un archivo de carga y pasar de restablecer a main.
Abbadon dice:
Esto fue en respuesta a 'Paul Simon' en https://vivonomicon.com/
tyhjtyhj dice:
Yo prefiero eRuby
Seb dice:
Aquí hay una demostración de barmetal de DMA en el STM32 que hice https://github.com/spanceac/dma-demo
No se utiliza biblioteca, ni siquiera libc
io358 dice:
Parece que algunas ideas están convergiendo un poco ... aunque he ido un poco más allá, ni siquiera usando títulos ST. Mirando toda la programación divertida, supongo que probablemente pondría lo mío en la red.
jawnhenry dice:
Durante mucho tiempo ha quedado muy claro que la frase "programación básica" es otra forma de decir "programación en lenguaje ensamblador"; uno no puede acercarse más a las instrucciones escritas que funcionan "el (medio) metal" que con el lenguaje ensamblador. El mnemónico del lenguaje ensamblador es, después de todo, reemplazos uno por uno para las instrucciones binarias de la máquina, o código binario.
No está del todo claro cómo (quizás “por qué” sería una palabra más precisa) este tutorial de ejemplo logra igualar los procedimientos, resúmenes, otros lenguajes utilizados y herramientas utilizadas, con la programación en lenguaje ensamblador; una explicación sería más que bienvenida y muy apreciada.
“Al comprender el lenguaje orientado a las máquinas [i.e., Assembly Language] el programador tenderá a utilizar un método mucho más eficiente; está mucho más cerca de la realidad. ""Donald Knuth".
Zerg dice:
No es puro metal en absoluto. Una vez más, el código de enlace de ARM no es tan divertido o leer miles de páginas de un libro de datos de ARM para averiguar qué está pasando, etc.
jawnhenry dice:
Pero pero ...
el título dice ...
Bare-Metal STM32: del poder al Hola mundo;
y, en el cuerpo del artículo, se dice ...
(...)"... Echemos un vistazo a cómo funciona la programación desnuda de STM32 ..."
Entonces, ¿es este un artículo sobre programación en lenguaje ensamblador ... o no? Todo indica que no lo es.
Danie Conradie dice:
¡Gracias Maya, estaba esperando esta publicación!
Charles dice:
¡Eso es perfecto! Me gusta la simplicidad, ningún marco estúpido para mirar. ¡Gracias!
fanoush dice:
¿Ese chip tiene un reloj a 180Mhz? Todavía tengo un emulador de Frodo ejecutándose en PalmOS Tungsten T2 (OMAP1510 - 168MHz ARM) y funciona a toda velocidad con sonido. La emulación no es exactamente un ciclo, por lo que los cargadores de velocidad de disco no funcionan, pero por lo demás, los juegos simplemente funcionan.
fanoush dice:
oh lo siento, página incorrecta, pertenece a https://la-tecnologia.com/2020/11/18/c64-runs-on-stm32f429-discovery/
Sargento dice:
Parece que hay muchos costos adicionales. Usa vector y masa para los microcontroladores no es una buena idea. En su lugar, puede utilizar un polimorfo estático.
Para acceder al registro puedes ver las llegadas aquí
https://m.habr.com/ru/post/459204/
y aquí
https://m.habr.com/ru/post/459642/
Para puertos y pines
https://m.habr.com/ru/post/473612/John dice:
Siempre debe utilizar las herramientas adecuadas para el trabajo. * No es * siempre * necesario hacer metal desnudo. Trabajo en la industria médica, y hay algo de metal desnudo allí, pero también se usan muchos marcos.
Aprendí mucho sobre la programación efectiva de metal desnudo de una serie de YouTube.
Este chico enseña programación de metal desnudo. Desde el suelo. Por Cortex M-series.Para todos los interesados, compruébelo, busque Miro Samek en youtube.
Ray Pasco dice:
¡Está bien, estoy enganchado! ¿Cómo descargo, instalo, configuro y uso el marco Nodate STM32? Su enlace apunta al repositorio de GitHub, pero no puedo averiguar cómo instalarlo o usarlo.
? Me gustaría usar STM32CubeIDE sin su biblioteca HAL. Debido a que el IDE es realmente el Eclipse IDE adaptado para el desarrollo de STM32, es muy fácil de usar: abra mi proyecto, edite cualquiera de los archivos enumerados y luego presione el botón verde "Ir" para compilar, ensamblar y cargar automáticamente en mi MCU y déjalo correr.