Embedded C++: Cómo escribir código para microcontroladores sin volverse loco

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: Por qué C++ en embedded no es masoquismo, sino un superpoder

\n\n

Cuando escuchas «desarrollo embedded», normalmente te imaginas a un ingeniero severo con un soldador, que escribe en C puro y reza al datasheet. O, por el contrario, un entusiasta que carga digitalWrite() en Arduino y se alegra de que un LED parpadee. Pero entre estos dos mundos hay un punto medio dorado: el desarrollo embedded en C++.

\n\n

Sí, ese mismo C++ que asusta con plantillas y funciones virtuales. En realidad, los microcontroladores modernos (ARM Cortex-M, RISC-V) son perfectamente capaces de procesar código C++. Es más, el uso inteligente de POO, encapsulación e incluso plantillas puede hacer que tu código embedded sea más limpio, seguro y rápido.

\n\n

En este artículo, analizaremos cómo escribir código C++ eficiente para microcontroladores, evitaremos los problemas con las tablas virtuales y aprenderemos a trabajar con el hardware a nivel de registros, pero al estilo C++. ¿Empezamos?

\n\n

1. Base: En qué se diferencia el embedded C++ del normal

\n\n

La regla principal del desarrollo embedded: los recursos son limitados. No tienes gigabytes de RAM ni un procesador de gigahercios. Tienes 32 KB de flash y 8 KB de RAM. Cada byte cuenta. Por eso, C++ en embedded no se trata de «escribiré una clase con 10 niveles de herencia», sino de control sobre las abstracciones.

\n\n

Qué se puede usar sin miedo:

\n
    \n
  • Clases y estructuras — para agrupar datos y funciones. Esto es gratuito (en memoria) si no hay funciones virtuales.
  • \n
  • Espacios de nombres — para no crear prefijos como hal_gpio_init().
  • \n
  • constexpr y plantillas — cálculos en tiempo de compilación, ahorro de ciclos.
  • \n
  • Referencias y paso por referencia constante — más seguros que los punteros.
  • \n
\n\n

Qué es mejor no tocar a menos que sea estrictamente necesario:

\n
    \n
  • Memoria dinámica (new/delete) — el heap se fragmenta, el asignador puede fallar. En proyectos bare-metal es tabú.
  • \n
  • Excepciones (try/catch) — requieren soporte de runtime, ocupan mucho espacio.
  • \n
  • Funciones virtuales — cada función virtual crea una tabla (vtable) en RAM/Flash, y la llamada pasa por doble indirección. Pero a veces están justificadas (drivers).
  • \n
  • RTTI (typeid) — no es necesario en absoluto.
  • \n
\n\n

La idea es simple: el compilador de C++ genera código tan eficiente como C, si no usas funciones «pesadas». Revisa el desensamblador y serás feliz.

\n\n

2. Práctica: Trabajo con registros al estilo C++

\n\n

En el embedded C clásico escribimos algo como:

\n
#define GPIOA_BASE 0x40020000\n#define GPIOA_MODER (*(volatile uint32_t*)(GPIOA_BASE + 0x00))\nGPIOA_MODER |= (1 << 10); // Establecemos el bit 10 (salida)
\n\n

Esto funciona, pero se ve arcaico. En C++ podemos hacer un envoltorio cómodo sin overhead.

\n\n

Ejemplo: Clase para trabajar con un pin

\n
#include <cstdint>\n\nclass GpioPin {\npublic:\n    // Prohibimos la copia\n    GpioPin(const GpioPin&) = delete;\n    GpioPin& operator=(const GpioPin&) = delete;\n\n    // Enumeración para los modos\n    enum class Mode : uint32_t {\n        Input = 0b00,\n        Output = 0b01,\n        AltFunc = 0b10,\n        Analog = 0b11\n    };\n\n    // Constructor: recibe la dirección del puerto y el número de pin (0-15)\n    GpioPin(volatile uint32_t* port_base, uint8_t pin)\n        : port_(port_base), pin_(pin) {\n        // Se puede configurar el pin inmediatamente\n    }\n\n    // Establecer el modo de trabajo\n    void setMode(Mode mode) {\n        volatile uint32_t* moder = port_ + 0x00; // Desplazamiento del registro MODER\n        *moder &= ~(0b11 << (pin_ * 2));\n        *moder |= (static_cast<uint32_t>(mode) << (pin_ * 2));\n    }\n\n    // Establecer nivel alto\n    void high() {\n        volatile uint32_t* bsrr                

Blogs

Book Recommendations