Embedded C++: Como escrever código para microcontroladores sem enlouquecer

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: Por que C++ em embedded não é masoquismo, mas um superpoder

\n\n

Quando se ouve "desenvolvimento embedded", geralmente vem à mente um engenheiro severo com um ferro de solda, que escreve em C puro e reza para o datasheet. Ou, ao contrário, um entusiasta que carrega digitalWrite() no Arduino e fica feliz por piscar um LED. Mas entre esses dois mundos existe um meio-termo dourado — desenvolvimento embedded em C++.

\n\n

Sim, aquele mesmo C++ que assusta com templates e funções virtuais. Na verdade, microcontroladores modernos (ARM Cortex-M, RISC-V) são perfeitamente capazes de processar código C++. Além disso, o uso inteligente de OOP, encapsulamento e até templates pode tornar seu código embedded mais limpo, seguro e rápido.

\n\n

Neste artigo, vamos explorar como escrever código C++ eficiente para microcontroladores, evitar as armadilhas das tabelas virtuais e aprender a trabalhar com hardware no nível de registradores, mas no estilo C++. Vamos nessa?

\n\n

1. Base: Como o embedded C++ difere do comum

\n\n

A regra principal do desenvolvimento embedded: os recursos são limitados. Você não tem gigabytes de RAM nem um processador de gigahertz. Você tem 32 KB de flash e 8 KB de RAM. Cada byte conta. Portanto, C++ em embedded não é sobre "escrever uma classe com 10 níveis de herança", mas sobre controle sobre abstrações.

\n\n

O que pode ser usado sem medo:

\n
    \n
  • Classes e estruturas — para agrupar dados e funções. Isso é gratuito (em memória) se não houver funções virtuais.
  • \n
  • Namespaces — para não proliferar prefixos como hal_gpio_init().
  • \n
  • constexpr e templates — cálculos em tempo de compilação, economia de ciclos.
  • \n
  • Referências e passagem por referência constante — mais seguros que ponteiros.
  • \n
\n\n

O que é melhor não tocar sem necessidade urgente:

\n
    \n
  • Memória dinâmica (new/delete) — o heap fragmenta, o alocador pode falhar. Em projetos bare-metal — tabu.
  • \n
  • Exceções (try/catch) — exigem suporte de runtime, ocupam muito espaço.
  • \n
  • Funções virtuais — cada função virtual cria uma tabela (vtable) em RAM/Flash, e a chamada passa por dupla indireção. Mas às vezes são justificadas (drivers).
  • RTTI (typeid) — não é necessário.
\n\n

A essência é simples: o compilador C++ gera código tão eficiente quanto C, se você não usar recursos "pesados". Verifique o disassembler — e você será feliz.

\n\n

2. Prática: Trabalhando com registradores no estilo C++

\n\n

No embedded C clássico, escrevemos algo como:

\n
#define GPIOA_BASE 0x40020000\n#define GPIOA_MODER (*(volatile uint32_t*)(GPIOA_BASE + 0x00))\nGPIOA_MODER |= (1 << 10); // Configura o bit 10 (saída)
\n\n

Isso funciona, mas parece arcaico. Em C++, podemos criar um wrapper conveniente sem overhead.

\n\n

Exemplo: Classe para trabalhar com pino

\n
#include <cstdint>\n\nclass GpioPin {\npublic:\n    // Proíbe cópia\n    GpioPin(const GpioPin&) = delete;\n    GpioPin& operator=(const GpioPin&) = delete;\n\n    // Enumeração para modos\n    enum class Mode : uint32_t {\n        Input = 0b00,\n        Output = 0b01,\n        AltFunc = 0b10,\n        Analog = 0b11\n    };\n\n    // Construtor: recebe endereço da porta e número do pino (0-15)\n    GpioPin(volatile uint32_t* port_base, uint8_t pin)\n        : port_(port_base), pin_(pin) {\n        // Pode configurar o pino imediatamente\n    }\n\n    // Configurar modo de operação\n    void setMode(Mode mode) {\n        volatile uint32_t* moder = port_ + 0x00; // Offset do registrador MODER\n        *moder &= ~(0b11 << (pin_ * 2));\n        *moder |= (static_cast<uint32_t>(mode) << (pin_ * 2));\n    }\n\n    // Configurar nível alto\n    void high() {\n        volatile uint32_t* bsrr                

Blogs

Book Recommendations