🛠 Bare-metal sur le STM32F401 : clignoter LD2 en écrivant les registres GPIO
Objetivos
Eliminar HAL, los encabezados de dispositivo CMSIS y el archivo de arranque, y hacer parpadear LD2 escribiendo directamente en los registros GPIO. Esta es la forma más rápida de entender qué está haciendo cada abstracción por encima — y muestra lo que el Cortex-M4F a 84 MHz te ofrece por el precio de una placa Nucleo-64.
Hardware
- 1× Nucleo-F401RE + cable micro-USB (el ST-LINK integrado realiza la programación)
- Cadena de herramientas
arm-none-eabi-gcc
El mapa de memoria (RM0368)
- Base de GPIOA 0x4002_0000:
MODERen el desplazamiento 0x00,ODRen el desplazamiento 0x140. RCC_AHB1ENRen 0x4002_3830, el bit 0 habilita el reloj de GPIOA — cada error de GPIO que tengas alguna vez comienza aquí.- LD2 es PA5 (encabezado de variante): los bits 11:10 de MODER seleccionan el modo del pin, el bit 5 de ODR lo controla.
- Sin configuración de reloj, el núcleo funciona con el HSI predeterminado de 16 MHz — el parpadeo es lento pero honesto.
Código
/* blink_bare.c — arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -O2 -nostdlib */
#define GPIOA_MODER (*(volatile unsigned int *)0x40020000u)
#define GPIOA_ODR (*(volatile unsigned int *)0x40020140u)
#define RCC_AHB1ENR (*(volatile unsigned int *)0x40023830u)
static void busy_wait(void) { for (volatile int i = 0; i < 2000000; ++i) {} }
void Reset_Handler(void)
{
RCC_AHB1ENR |= (1u << 0); /* enable GPIOA clock (default HSI 16 MHz) */
GPIOA_MODER &= ~(3u << 10);
GPIOA_MODER |= (1u << 10); /* PA5 -> general-purpose output */
while (1) {
GPIOA_ODR ^= (1u << 5); /* toggle LD2 */
busy_wait;
}
}
Pasos
- Escribe un script de enlace mínimo que coloque la tabla de vectores en 0x0800_0000: puntero de pila inicial, seguido de la dirección de
Reset_Handler. - Compila:
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -O2 -nostdlib -nostartfiles -T flash.ld blink_bare.c -o blink_bare.elf - Programa a través del ST-LINK integrado con STM32CubeProgrammer (o coloca el binario en la función de almacenamiento masivo del ST-LINK).
- Pulsa RESET: LD2 cambia de estado.
Resultado esperado
Un latido constante en PA5 con un binario de unas pocas docenas de bytes. Cada HAL_GPIO_WritePin que hayas escrito alguna vez es esto, más árboles de reloj, más la tabla de vectores, más un archivo de arranque — la llamada HAL oculta la contabilidad, no la magia.
Errores comunes
- Olvidar habilitar el reloj de GPIOA: las escrituras en MODER no hacen nada silenciosamente.
- Escribir en MODER con
=en lugar de lectura-modificación-escritura: sobreescribes los otros pines de PA. - Sin tabla de vectores o script de enlace incorrecto: el cargador de arranque ROM o basura se ejecuta en lugar de tu código. > SIN VERIFICAR: la lista de interfaces del cargador de arranque ROM (USART/SPI/I2C/USB DFU) proviene del manual de referencia RM0368, que no se pudo analizar automáticamente durante la investigación.
- Dejar la FPU inactiva: añade
-mfpu=fpv4-sp-d16 -mfloat-abi=hardcuando quieras la FPU de precisión simple y las instrucciones DSP que ofrece el Cortex-M4F.
Otros tutoriales para esta placa
- Prise en main de la Nucleo-F401RE : ST-LINK, LED LD2 et alimentation
- Premier Blink Arduino sur la Nucleo-F401RE avec le core STM32duino
- Console série sur le ST-LINK VCP : printf, ADC 12 bits et piège du USB
- STM32CubeF4 et HAL : blink + UART printf générés pour la Nucleo-F401RE
- ADC 12 bits et PWM sur la Nucleo-F401RE : oversampling, millivolts et LED en fondu
- Zephyr RTOS sur la Nucleo-F401RE : west build, devicetree et west flash