C para sistemas embebidos: bare metal en microcontroladores ARM Cortex-M en 2026

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-sections con -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.elf conectando 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

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP