Rust Embarcados: IoT, ESP32 e ARM Cortex-M | Rust Brasil

Guia de Rust para sistemas embarcados: no_std, embedded-hal, probe-rs, RTIC, ESP32 com esp-rs e ARM Cortex-M em 2026.

Introdução

Programação de sistemas embarcados tradicionalmente é dominada por C e, em menor grau, C++. Mas Rust está mudando esse cenário rapidamente. A combinação de segurança de memória sem garbage collector, abstrações zero-cost, suporte a no_std (programação sem a biblioteca padrão) e um ecossistema crescente de HALs (Hardware Abstraction Layers) torna Rust uma alternativa real e cada vez mais adotada para microcontroladores e dispositivos IoT.

Ao contrário de C, onde buffer overflows, dangling pointers e data races são bugs comuns e frequentemente explorados em dispositivos conectados, Rust elimina essas classes inteiras de vulnerabilidades em tempo de compilação. Para dispositivos IoT que operam em ambientes hostis e raramente recebem atualizações de segurança, essa garantia é especialmente valiosa.

O Ecossistema Embedded Rust

Conceitos Fundamentais

  • no_std: Permite compilar Rust sem a biblioteca padrão (std), necessário para microcontroladores sem sistema operacional. Você ainda tem acesso ao core (tipos primitivos, iteradores, Option/Result) e opcionalmente ao alloc (alocação dinâmica)
  • embedded-hal: Traits que definem interfaces genéricas para GPIO, SPI, I2C, UART, PWM, etc. Permite escrever drivers portáteis entre diferentes microcontroladores
  • PAC (Peripheral Access Crate): Gerado automaticamente a partir de arquivos SVD, fornece acesso de baixo nível aos registradores do chip
  • HAL (Hardware Abstraction Layer): Implementa os traits de embedded-hal para um chip específico, oferecendo uma API segura e idiomática

Plataformas Suportadas

PlataformaCrate PrincipalExemplos de Chips
ARM Cortex-Mcortex-m, cortex-m-rtSTM32, nRF52, RP2040
ESP32esp-hal, esp-idf-halESP32, ESP32-S3, ESP32-C3
RISC-Vriscv, riscv-rtESP32-C3, GD32V
AVRavr-deviceATmega328P (Arduino)
Raspberry Pi Picorp-halRP2040

Ferramentas de Desenvolvimento

FerramentaFunção
probe-rsFlash, debug e RTT logging para ARM e RISC-V
cargo-embedIntegração probe-rs com Cargo (flash + monitor)
espflashFlash para chips ESP32
defmtFramework de logging ultra-eficiente para embarcados

Cargo.toml para um Projeto Embarcado (STM32)

[package]
name = "sensor-iot"
version = "0.1.0"
edition = "2021"

[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "1.0"
stm32f4xx-hal = { version = "0.22", features = ["stm32f411"] }
embedded-hal = "1.0"
defmt = "0.3"
defmt-rtt = "0.4"

[profile.release]
opt-level = "z"     # Otimizar para tamanho
lto = true
codegen-units = 1
debug = true        # Manter debug info para probe-rs

Exemplo Prático: Sensor de Temperatura IoT com STM32

Vamos construir um dispositivo que lê temperatura de um sensor DHT22 via GPIO e envia os dados por UART.

#![no_std]
#![no_main]

use cortex_m_rt::entry;
use panic_halt as _;
use stm32f4xx_hal::{
    gpio::{Output, PushPull, Pin},
    pac,
    prelude::*,
    serial::{config::Config, Serial},
    timer::SysTimerExt,
};
use core::fmt::Write;

/// Estrutura para armazenar leitura do sensor
struct Leitura {
    temperatura: f32,
    umidade: f32,
}

/// Lê dados de um sensor DHT22 conectado ao pino especificado.
/// Simplificado para demonstração — em produção use a crate `dht-sensor`.
fn ler_sensor_simulado(contador: u32) -> Leitura {
    // Em um projeto real, você usaria a crate `dht-sensor`
    // com o embedded-hal para comunicação com o sensor
    Leitura {
        temperatura: 22.5 + (contador % 10) as f32 * 0.3,
        umidade: 65.0 + (contador % 5) as f32 * 1.2,
    }
}

#[entry]
fn main() -> ! {
    // Obter periféricos do chip
    let dp = pac::Peripherals::take().unwrap();
    let cp = cortex_m::Peripherals::take().unwrap();

    // Configurar clocks
    let rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr
        .use_hse(8.MHz())
        .sysclk(84.MHz())
        .freeze();

    // Configurar delay
    let mut delay = cp.SYST.delay(&clocks);

    // Configurar GPIO
    let gpioa = dp.GPIOA.split();
    let gpioc = dp.GPIOC.split();

    // LED de status (PC13 na maioria das placas STM32)
    let mut led: Pin<'C', 13, Output<PushPull>> = gpioc.pc13.into_push_pull_output();

    // Configurar UART (PA2 = TX, PA3 = RX)
    let tx_pin = gpioa.pa2;
    let rx_pin = gpioa.pa3;

    let mut serial = Serial::new(
        dp.USART2,
        (tx_pin, rx_pin),
        Config::default().baudrate(115200.bps()),
        &clocks,
    )
    .unwrap();

    writeln!(serial, "Sensor IoT iniciado - Rust Embedded").unwrap();
    writeln!(serial, "Lendo temperatura a cada 2 segundos...").unwrap();

    let mut contador: u32 = 0;

    // Loop principal
    loop {
        // Piscar LED para indicar leitura
        led.set_low();
        delay.delay_ms(100u32);
        led.set_high();

        // Ler sensor
        let leitura = ler_sensor_simulado(contador);

        // Enviar dados via UART
        writeln!(
            serial,
            "[{}] Temp: {:.1}°C | Umidade: {:.1}%",
            contador,
            leitura.temperatura,
            leitura.umidade
        )
        .unwrap();

        // Alerta se temperatura ultrapassar limiar
        if leitura.temperatura > 30.0 {
            writeln!(serial, "⚠ ALERTA: Temperatura elevada!").unwrap();
        }

        contador = contador.wrapping_add(1);
        delay.delay_ms(2000u32);
    }
}

Exemplo com ESP32 (usando esp-rs)

O ecossistema esp-rs permite programar chips ESP32 em Rust, tanto em modo no_std quanto com a framework ESP-IDF (que inclui Wi-Fi, Bluetooth, etc.).

[package]
name = "esp32-sensor"
version = "0.1.0"
edition = "2021"

[dependencies]
esp-idf-hal = "0.44"
esp-idf-svc = "0.50"
esp-idf-sys = { version = "0.36", features = ["binstart"] }
embedded-svc = "0.28"
anyhow = "1"
log = "0.4"
use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::gpio::PinDriver;
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_svc::wifi::{EspWifi, BlockingWifi, ClientConfiguration, Configuration};
use esp_idf_svc::eventloop::EspSystemEventLoop;
use esp_idf_svc::nvs::EspDefaultNvsPartition;
use esp_idf_svc::http::client::EspHttpConnection;
use embedded_svc::http::client::Client;
use anyhow::Result;
use log::info;

fn conectar_wifi(
    wifi: &mut BlockingWifi<EspWifi<'static>>,
    ssid: &str,
    senha: &str,
) -> Result<()> {
    wifi.set_configuration(&Configuration::Client(ClientConfiguration {
        ssid: ssid.try_into().unwrap(),
        password: senha.try_into().unwrap(),
        ..Default::default()
    }))?;

    wifi.start()?;
    wifi.connect()?;
    wifi.wait_netif_up()?;

    info!("Conectado ao Wi-Fi! IP: {:?}", wifi.wifi().sta_netif().get_ip_info()?);
    Ok(())
}

fn enviar_dados(temperatura: f32, umidade: f32) -> Result<()> {
    let mut cliente = Client::wrap(EspHttpConnection::new(&Default::default())?);

    let url = format!(
        "http://meu-servidor.com/api/sensor?temp={:.1}&umid={:.1}",
        temperatura, umidade
    );

    let resposta = cliente.get(&url)?.submit()?;
    info!("Dados enviados. Status: {}", resposta.status());
    Ok(())
}

fn main() -> Result<()> {
    esp_idf_svc::sys::link_patches();
    esp_idf_svc::log::EspLogger::initialize_default();

    let peripherals = Peripherals::take()?;
    let sysloop = EspSystemEventLoop::take()?;
    let nvs = EspDefaultNvsPartition::take()?;

    // Configurar LED
    let mut led = PinDriver::output(peripherals.pins.gpio2)?;

    // Configurar Wi-Fi
    let mut wifi = BlockingWifi::wrap(
        EspWifi::new(peripherals.modem, sysloop.clone(), Some(nvs))?,
        sysloop,
    )?;

    conectar_wifi(&mut wifi, "MinhaRede", "MinhaSenha123")?;

    // Loop principal
    loop {
        led.set_high()?;
        FreeRtos::delay_ms(100);
        led.set_low()?;

        // Simular leitura de sensor
        let temperatura = 25.3;
        let umidade = 68.0;

        info!("Temperatura: {:.1}°C, Umidade: {:.1}%", temperatura, umidade);

        if let Err(e) = enviar_dados(temperatura, umidade) {
            log::error!("Erro ao enviar dados: {:?}", e);
        }

        FreeRtos::delay_ms(5000);
    }
}

RTIC: Framework de Concorrência em Tempo Real

RTIC (Real-Time Interrupt-driven Concurrency) é um framework que utiliza o sistema de prioridades de interrupção do hardware para garantir concorrência segura sem overhead de runtime:

#![no_std]
#![no_main]

use panic_halt as _;

#[rtic::app(device = stm32f4xx_hal::pac, dispatchers = [EXTI0])]
mod app {
    use stm32f4xx_hal::{
        gpio::{Output, PushPull, Pin},
        prelude::*,
        timer::MonoTimerUs,
    };

    #[shared]
    struct Shared {
        temperatura: f32,
    }

    #[local]
    struct Local {
        led: Pin<'C', 13, Output<PushPull>>,
    }

    #[init]
    fn init(ctx: init::Context) -> (Shared, Local) {
        let rcc = ctx.device.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(84.MHz()).freeze();
        let gpioc = ctx.device.GPIOC.split();
        let led = gpioc.pc13.into_push_pull_output();

        // Agendar primeira leitura
        ler_sensor::spawn().unwrap();

        (
            Shared { temperatura: 0.0 },
            Local { led },
        )
    }

    #[task(shared = [temperatura], priority = 2)]
    async fn ler_sensor(mut ctx: ler_sensor::Context) {
        loop {
            // Ler sensor (simplificado)
            let temp = 23.5;
            ctx.shared.temperatura.lock(|t| *t = temp);

            // Reagendar para daqui a 1 segundo
            // Em produção, use monotonic timer
        }
    }

    #[task(local = [led], shared = [temperatura], priority = 1)]
    async fn piscar_led(ctx: piscar_led::Context) {
        loop {
            ctx.local.led.toggle();
        }
    }
}

Empresas Usando Rust em Embarcados

  • Espressif: Fabricante dos chips ESP32, mantém oficialmente o projeto esp-rs com suporte completo a Rust
  • Oxide Computer: Computadores para data center com firmware inteiro escrito em Rust (projeto Hubris OS)
  • Arm: Investindo em suporte a Rust para a plataforma Cortex-M e colaborando com o Embedded Working Group
  • Infineon: Suporte a Rust para microcontroladores PSoC e XMC
  • Volvo: Experimentando Rust para software automotivo embarcado
  • Framework (laptop): Componentes de firmware com Rust
  • System76: Firmware de laptops e teclados (projeto ec, firmware de embedded controller)

Como Começar

  1. Aprenda Rust primeiro: Domine ownership e borrowing com nosso tutorial de primeiros passos
  2. Configure cross-compilation: Siga nosso guia de cross-compilation para instalar toolchains para ARM e RISC-V
  3. Comece com Raspberry Pi Pico: O RP2040 é o chip mais acessível para aprender Rust embarcado, com documentação excelente
  4. Explore ESP32: Se precisa de Wi-Fi/Bluetooth, o ESP32 com esp-rs é a porta de entrada mais prática
  5. Pratique no_std: Comece escrevendo código sem a biblioteca padrão, entenda core e alloc
  6. Estude o Embedded Rust Book: Recurso oficial da comunidade Rust para desenvolvimento embarcado

Hardware Recomendado para Iniciantes

PlacaPreço Aprox.Destaques
Raspberry Pi PicoR$ 30RP2040, excelente documentação Rust
ESP32-C3 DevKitR$ 40RISC-V, Wi-Fi, Bluetooth, esp-rs
STM32 Nucleo-F411RER$ 80ARM Cortex-M4, ST-Link integrado
BBC micro:bit v2R$ 120Sensores integrados, LED matrix, BLE
nRF52840 DKR$ 200BLE 5.0, USB, excelente para IoT

Conclusão

Rust está se estabelecendo como uma alternativa séria ao C para desenvolvimento embarcado. A segurança de memória, o sistema de tipos e as abstrações zero-cost fazem de Rust uma escolha natural para dispositivos IoT que precisam ser seguros, confiáveis e eficientes. Com o suporte oficial da Espressif para ESP32 e o crescimento do ecossistema embedded-hal, nunca houve um momento melhor para começar com Rust embarcado.


Veja Também