La programación embebida bare metal consiste en ejecutar código directamente sobre el hardware sin sistema operativo de por medio. Es el dominio donde C reina de forma indiscutible: acceso directo a registros, control total del consumo de memoria, sin overhead de runtime. En 2026, los microcontroladores ARM Cortex-M alimentan desde auriculares inalámbricos hasta sistemas de control industrial, y C es el lenguaje principal en todos ellos.
Familia ARM Cortex-M
La línea Cortex-M tiene varios núcleos con capacidades crecientes:
- M0/M0+: ultra bajo consumo, sin FPU, instrucciones mínimas. Para sensores simples y nodos IoT con batería.
- M3: conjunto de instrucciones completo, multiplicación de 32 bits, sin FPU. Muy usado en STM32F1xx.
- M4: M3 + DSP instructions + FPU opcional. STM32F4xx, nRF52. El más usado en aplicaciones de audio y control de motores.
- M7: pipeline más profundo, doble FPU, cache L1. STM32H7, iMXRT. Para aplicaciones de alto rendimiento.
- M33/M55: arquitecturas más recientes con TrustZone y soporte SIMD extendido.
Flags de compilación para Cortex-M4 con FPU
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -std=c11 -O2 -Wall -ffunction-sections -fdata-sections -o firmware.elf main.c startup_stm32f4.s -T stm32f4.ld -Wl,--gc-sections -lc -lm -lnosys
-mcpu=cortex-m4: genera instrucciones Thumb-2 para M4.-mfpu=fpv4-sp-d16 -mfloat-abi=hard: usa la FPU hardware de precisión simple (más rápido que soft-float).-ffunction-sections -fdata-sectionscon-Wl,--gc-sections: elimina funciones y datos no referenciados, reduciendo el tamaño del binario.-lnosys: stubs mínimos para syscalls de newlib (necesario sin OS).
El linker script
El linker script (.ld) define el mapa de memoria: dónde está la Flash, dónde la RAM, y cómo se organizan las secciones:
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 192K
}
SECTIONS {
.text : {
KEEP(*(.isr_vector)) /* tabla de vectores al inicio */
*(.text*)
*(.rodata*)
} > FLASH
.data : {
_sdata = .;
*(.data*)
_edata = .;
} > RAM AT > FLASH /* almacenado en Flash, copiado a RAM */
.bss : {
_sbss = .;
*(.bss*)
*(COMMON)
_ebss = .;
} > RAM
}
Startup code y la función main
Sin OS, el startup code (normalmente en ensamblador o C) inicializa el stack, copia .data de Flash a RAM, pone .bss a cero y llama a main(). En bare metal, main() nunca debe retornar:
/* Startup mínimo en C (simplificado) */
extern uint32_t _sdata, _edata, _sidata;
extern uint32_t _sbss, _ebss;
void Reset_Handler(void) {
/* Copiar .data de Flash a RAM */
uint32_t* src = &_sidata;
uint32_t* dst = &_sdata;
while (dst < &_edata) *dst++ = *src++;
/* Inicializar .bss a cero */
dst = &_sbss;
while (dst < &_ebss) *dst++ = 0;
main();
/* main() no debe retornar nunca */
while (1) {}
}
volatile para registros hardware
volatile es una de las palabras clave más importantes en embebido. Indica al compilador que una variable puede cambiar en cualquier momento (por hardware o por una ISR), impidiendo optimizaciones que eliminarían lecturas o escrituras:
/* Acceso a registro RCC del STM32 */
#define RCC_BASE 0x40023800UL
#define RCC_AHB1ENR (*(volatile uint32_t*)(RCC_BASE + 0x30))
/* Habilitar reloj del GPIO A */
RCC_AHB1ENR |= (1 << 0);
/* Sin volatile, el compilador podría optimizar y eliminar
lecturas repetidas del mismo registro */
volatile uint32_t* STATUS_REG = (volatile uint32_t*)0x40020010;
while (!(*STATUS_REG & 0x01)) { /* esperar flag */ }
CMSIS: capa de abstracción estándar
CMSIS (Cortex Microcontroller Software Interface Standard) es una capa de abstracción definida por ARM que estandariza el acceso a periféricos del núcleo (NVIC, SysTick, FPU) y los headers de los microcontroladores. Todos los fabricantes ST, NXP, Nordic proporcionan headers CMSIS para sus chips:
#include "stm32f4xx.h" /* header CMSIS del fabricante */
/* Acceso con nombres en lugar de direcciones crudas */
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
GPIOA->MODER |= GPIO_MODER_MODE5_0; /* PA5 como salida */
/* Parpadeo del LED (PA5 en Discovery) */
while (1) {
GPIOA->ODR ^= (1 << 5);
for (volatile int i = 0; i < 100000; i++) {}
}
Herramientas de desarrollo en 2026
- arm-none-eabi-gcc: toolchain GCC para ARM bare metal. Disponible en apt, brew, y como parte de ARM GNU Toolchain.
- OpenOCD: depurador/flasheador open source. Compatible con ST-Link, J-Link, CMSIS-DAP.
- GDB + OpenOCD: debugging remoto sobre el chip.
arm-none-eabi-gdb firmware.elfconectando a OpenOCD. - STM32CubeIDE / VS Code + Cortex-Debug: entornos con integración de debug por SWD/JTAG.
Si quieres profundizar en el debugging de código C, el artículo sobre gdb y AddressSanitizer cubre técnicas aplicables también en embebido. Para el sistema de construcción, adaptar un Makefile moderno con los flags de Cortex-M es el primer paso en cualquier proyecto bare metal.
Imagen: Pexels / Craig Dennis
