Rust

Embarcados e Bare Metal — Rust sem Sistema Operacional Já leu

16 min de leitura

Embarcados e Bare Metal — Rust sem Sistema Operacional
Rust é uma das linguagens mais empolgantes para sistemas embarcados. Enquanto C dominou esse espaço por décadas, as garantias de memória de Rust eliminam classes inteiras de bugs que são especialmente devastadores em sis

 

Rust é uma das linguagens mais empolgantes para sistemas embarcados. Enquanto C dominou esse espaço por décadas, as garantias de memória de Rust eliminam classes inteiras de bugs que são especialmente devastadores em sistemas onde não há sistema operacional para conter danos, depurar é difícil, e reinicializar pode ser impossível.

Neste artigo descemos ao nível mais baixo: microcontroladores sem sistema operacional, acesso direto a registradores de hardware, interrupts, e o ecossistema embedded-hal que torna o código Rust portável entre diferentes plataformas de hardware.


O que significa "bare metal"

Em um computador normal, o sistema operacional isola seu programa do hardware. Quando você abre um arquivo, o kernel lida com os detalhes do controlador de disco. Em sistemas embarcados bare metal:

  • Não há kernel nem sistema operacional
  • Seu programa controla o hardware diretamente
  • Não há alocador de memória por padrão — você gerencia a stack e a heap manualmente
  • Não há threads do OS — você implementa concorrência com interrupts
  • Um bug pode danificar hardware permanentemente

O Rust resolve o problema mais crítico dessa categoria: corrupção de memória. As garantias de ownership e borrowing são verificadas em tempo de compilação — sem runtime, sem GC, perfeito para sistemas com kilobytes de RAM.


O ecossistema embarcado Rust

# Camadas do ecossistema embarcado Rust:
#
# ┌─────────────────────────────────────┐
# │  Aplicação (seu código)             │
# ├─────────────────────────────────────┤
# │  Board Support Crate (BSP)          │  ex: rp-pico, stm32f4xx-hal
# ├─────────────────────────────────────┤
# │  embedded-hal (traits abstratos)    │  interface portável
# ├─────────────────────────────────────┤
# │  HAL específico do chip             │  ex: stm32f4xx-hal
# ├─────────────────────────────────────┤
# │  PAC (Peripheral Access Crate)      │  registradores gerados por svd2rust
# ├─────────────────────────────────────┤
# │  cortex-m / riscv                   │  intrínsecos do processador
# ├─────────────────────────────────────┤
# │  Hardware                           │
# └─────────────────────────────────────┘

Configurando para o Raspberry Pi Pico (RP2040)

O RP2040 é um microcontrolador acessível e popular. Vamos usá-lo como exemplo:

# Instalar toolchain para ARM Cortex-M0+
rustup target add thumbv6m-none-eabi

# Instalar ferramenta de debug/flash
cargo install probe-rs --features cli

Cargo.toml:

[package]
name = "pico_embarcado"
version = "0.1.0"
edition = "2021"

[dependencies]
# HAL do RP2040
rp2040-hal = { version = "0.10", features = ["rt"] }

# Suporte ao board Raspberry Pi Pico
rp-pico = "0.9"

# Traits abstratos — código portável entre chips
embedded-hal = "1.0"

# Runtime para Cortex-M
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"

# Sem alocação dinâmica por padrão
# Mas podemos habilitar um alocador estático
embedded-alloc = "0.5"

# Formatação sem std
ufmt = "0.2"

# Panic handler para sistemas sem OS
panic-halt = "0.2"

[profile.release]
opt-level = "s"   # menor binário possível
lto = true
debug = false
codegen-units = 1

# Configuração do target
[build]
target = "thumbv6m-none-eabi"

memory.x — mapa de memória do RP2040:

MEMORY {
    BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
    FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
    RAM   : ORIGIN = 0x20000000, LENGTH = 256K
}

EXTERN(BOOT2_FIRMWARE)

SECTIONS {
    .boot2 ORIGIN(BOOT2) :
    {
        KEEP(*(.boot2));
    } > BOOT2
} INSERT BEFORE .text;

O atributo #![no_std]

Código bare metal não tem acesso à biblioteca padrão — não há sistema de arquivos, não há heap por padrão, não há threads do OS:

// Declara que não usamos std
#![no_std]
// Declara que fornecemos nosso próprio ponto de entrada
#![no_main]

use panic_halt as _;  // panic handler: trava o processador em pânico
use rp_pico::entry;
use rp_pico::hal;
use hal::pac;
use embedded_hal::digital::OutputPin;

// Ponto de entrada — o runtime cortex-m-rt chama esta função
#[entry]
fn main() -> ! {  // ! significa que nunca retorna
    // Obtém acesso aos periféricos
    let mut pac = pac::Peripherals::take().unwrap();

    // Inicializa o watchdog (necessário para inicialização)
    let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);

    // Inicializa os clocks
    let clocks = hal::clocks::init_clocks_and_plls(
        rp_pico::XOSC_CRYSTAL_FREQ,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();

    // Delay baseado em SysTick
    let mut delay = cortex_m::delay::Delay::new(
        unsafe { pac::CorePeripherals::steal() }.SYST,
        clocks.system_clock.freq().to_Hz(),
    );

    // Configura os pinos GPIO
    let sio = hal::Sio::new(pac.SIO);
    let pins = rp_pico::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );

    // LED onboard do Pico está no pino 25
    let mut led_pin = pins.led.into_push_pull_output();

    // Loop principal — pisca o LED
    loop {
        led_pin.set_high().unwrap();
        delay.delay_ms(500);
        led_pin.set_low().unwrap();
        delay.delay_ms(500);
    }
}

embedded-hal — traits portáveis

O poder do ecossistema embarcado Rust está nos traits do embedded-hal. Um driver escrito contra esses traits funciona em qualquer chip que os implemente:

// Driver para sensor de temperatura DS18B20
// Funciona em qualquer chip que implemente embedded-hal

use embedded_hal::digital::{InputPin, OutputPin};
use embedded_hal::delay::DelayNs;

#[derive(Debug)]
pub enum ErroBus {
    SemPresenca,
    CrcInvalido,
    Timeout,
}

pub struct Ds18b20<P, D>
where
    P: InputPin + OutputPin,
    D: DelayNs,
{
    pino: P,
    delay: D,
}

impl<P, D> Ds18b20<P, D>
where
    P: InputPin + OutputPin,
    D: DelayNs,
{
    pub fn novo(pino: P, delay: D) -> Self {
        Ds18b20 { pino, delay }
    }

    fn reset(&mut self) -> Result<(), ErroBus> {
        // Sinalização de reset do protocolo 1-Wire
        self.pino.set_low().ok();
        self.delay.delay_us(480);
        self.pino.set_high().ok();
        self.delay.delay_us(70);

        // Verifica pulso de presença
        let presenca = self.pino.is_low()
            .map_err(|_| ErroBus::Timeout)?;

        self.delay.delay_us(410);

        if presenca {
            Ok(())
        } else {
            Err(ErroBus::SemPresenca)
        }
    }

    fn escrever_bit(&mut self, bit: bool) {
        self.pino.set_low().ok();
        self.delay.delay_us(if bit { 1 } else { 60 });
        self.pino.set_high().ok();
        self.delay.delay_us(if bit { 60 } else { 1 });
    }

    fn ler_bit(&mut self) -> bool {
        self.pino.set_low().ok();
        self.delay.delay_us(1);
        self.pino.set_high().ok();
        self.delay.delay_us(14);

        let bit = self.pino.is_high().unwrap_or(false);
        self.delay.delay_us(45);
        bit
    }

    fn escrever_byte(&mut self, byte: u8) {
        for i in 0..8 {
            self.escrever_bit((byte >> i) & 1 == 1);
        }
    }

    fn ler_byte(&mut self) -> u8 {
        let mut byte = 0u8;
        for i in 0..8 {
            if self.ler_bit() {
                byte |= 1 << i;
            }
        }
        byte
    }

    pub fn ler_temperatura(&mut self) -> Result<f32, ErroBus> {
        // Inicia conversão
        self.reset()?;
        self.escrever_byte(0xCC); // Skip ROM (único dispositivo)
        self.escrever_byte(0x44); // Convert T

        // Aguarda conversão (750ms para 12-bit)
        self.delay.delay_ms(750);

        // Lê scratchpad
        self.reset()?;
        self.escrever_byte(0xCC);
        self.escrever_byte(0xBE); // Read Scratchpad

        let byte_lsb = self.ler_byte();
        let byte_msb = self.ler_byte();

        // Verifica CRC (simplificado)
        let raw = u16::from_le_bytes([byte_lsb, byte_msb]) as i16;

        // Converte para Celsius (resolução 0.0625°C)
        Ok(raw as f32 * 0.0625)
    }
}

Este driver compila para qualquer plataforma — RP2040, STM32, AVR (Arduino), ESP32, ou até seu desktop para testes.


Interrupts

Interrupts são a forma de responder a eventos de hardware sem polling constante:

#![no_std]
#![no_main]

use cortex_m::interrupt::Mutex;
use core::cell::Cell;
use rp_pico::entry;
use rp_pico::hal::{self, pac, pac::interrupt};
use embedded_hal::digital::OutputPin;

// Estado compartilhado entre código principal e interrupt
// Mutex de Cortex-M — desabilita interrupts durante acesso
static CONTADOR_BOTAO: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
static LED_ESTADO: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));

// Handler de interrupt para o pino GPIO
#[interrupt]
fn IO_IRQ_BANK0() {
    // Acesso seguro — interrupts desabilitados durante critical section
    cortex_m::interrupt::free(|cs| {
        let contador = CONTADOR_BOTAO.borrow(cs);
        contador.set(contador.get() + 1);

        let led = LED_ESTADO.borrow(cs);
        led.set(!led.get()); // toggle

        // Limpa o interrupt (necessário para receber próximo)
        // Na implementação real: limpar flag no periférico
    });
}

#[entry]
fn main() -> ! {
    let mut pac = pac::Peripherals::take().unwrap();
    let sio = hal::Sio::new(pac.SIO);
    let pins = rp_pico::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );

    let mut led = pins.led.into_push_pull_output();

    // Configura pino do botão com interrupt na borda de descida
    let botao = pins.gpio15.into_pull_up_input();
    botao.set_interrupt_enabled(
        hal::gpio::Interrupt::EdgeLow, true
    );

    // Habilita interrupts globalmente
    unsafe { pac::NVIC::unmask(pac::Interrupt::IO_IRQ_BANK0) };

    let mut ultimo_contador = 0u32;

    loop {
        // Lê estado de forma segura
        let (contador, estado_led) = cortex_m::interrupt::free(|cs| {
            (
                CONTADOR_BOTAO.borrow(cs).get(),
                LED_ESTADO.borrow(cs).get(),
            )
        });

        // Atualiza LED apenas quando estado mudou
        if contador != ultimo_contador {
            if estado_led {
                led.set_high().ok();
            } else {
                led.set_low().ok();
            }
            ultimo_contador = contador;
        }

        // WFI — Wait For Interrupt, economiza energia
        cortex_m::asm::wfi();
    }
}

RTIC — Real-Time Interrupt-driven Concurrency

RTIC é um framework para sistemas embarcados que usa o sistema de tipos de Rust para garantir ausência de data races em código concorrente com interrupts:

[dependencies]
rtic = { version = "2", features = ["thumbv6-backend"] }
rtic-monotonics = { version = "2", features = ["rp2040"] }
#![no_std]
#![no_main]

#[rtic::app(device = rp_pico::hal::pac, peripherals = true)]
mod app {
    use rp_pico::hal::{self, pac, clocks, gpio};
    use embedded_hal::digital::OutputPin;
    use rtic_monotonics::rp2040::Timer;
    use rtic_monotonics::Monotonic;

    // Recursos compartilhados entre tasks
    #[shared]
    struct Shared {
        contador: u32,
    }

    // Recursos locais — exclusivos de uma task
    #[local]
    struct Local {
        led: gpio::Pin<gpio::bank0::Gpio25, gpio::FunctionSio<gpio::SioOutput>, gpio::PullNone>,
        botao: gpio::Pin<gpio::bank0::Gpio15, gpio::FunctionSio<gpio::SioInput>, gpio::PullUp>,
    }

    #[init]
    fn init(ctx: init::Context) -> (Shared, Local) {
        let mut pac = ctx.device;

        // Inicializa clocks
        let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
        let clocks = clocks::init_clocks_and_plls(
            rp_pico::XOSC_CRYSTAL_FREQ,
            pac.XOSC, pac.CLOCKS,
            pac.PLL_SYS, pac.PLL_USB,
            &mut pac.RESETS, &mut watchdog,
        ).ok().unwrap();

        // Inicializa timer
        let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks);
        Timer::start(timer, &mut pac.RESETS);

        // Configura pinos
        let sio = hal::Sio::new(pac.SIO);
        let pins = rp_pico::Pins::new(
            pac.IO_BANK0, pac.PADS_BANK0,
            sio.gpio_bank0, &mut pac.RESETS,
        );

        let led = pins.led.into_push_pull_output();
        let botao = pins.gpio15.into_pull_up_input();

        // Agenda task periódica
        piscar_led::spawn().ok();

        (
            Shared { contador: 0 },
            Local { led, botao },
        )
    }

    // Task periódica — pisca o LED a cada 500ms
    #[task(local = [led], shared = [contador])]
    async fn piscar_led(mut ctx: piscar_led::Context) {
        loop {
            ctx.local.led.set_high().ok();
            Timer::delay(500u32.millis()).await;

            ctx.local.led.set_low().ok();
            Timer::delay(500u32.millis()).await;

            // Acessa recurso compartilhado com lock automático
            ctx.shared.contador.lock(|c| {
                *c += 1;
            });
        }
    }

    // Handler de interrupt para botão
    #[task(binds = IO_IRQ_BANK0, local = [botao], shared = [contador])]
    fn botao_pressionado(mut ctx: botao_pressionado::Context) {
        ctx.shared.contador.lock(|c| {
            *c += 100; // Botão incrementa por 100
        });

        // Limpa interrupt
        ctx.local.botao.clear_interrupt(gpio::Interrupt::EdgeLow);
    }
}

RTIC garante em tempo de compilação que não há data races — você não precisa de Mutex explícito para recursos que só uma task acessa.


Comunicação serial — UART

UART é a interface de comunicação mais simples entre microcontrolador e computador:

#![no_std]
#![no_main]

use panic_halt as _;
use rp_pico::entry;
use rp_pico::hal::{self, pac, clocks, uart::{self, UartConfig}};
use fugit::RateExtU32;
use core::fmt::Write;

#[entry]
fn main() -> ! {
    let mut pac = pac::Peripherals::take().unwrap();
    let core = pac::CorePeripherals::take().unwrap();

    let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
    let clocks = clocks::init_clocks_and_plls(
        rp_pico::XOSC_CRYSTAL_FREQ,
        pac.XOSC, pac.CLOCKS,
        pac.PLL_SYS, pac.PLL_USB,
        &mut pac.RESETS, &mut watchdog,
    ).ok().unwrap();

    let sio = hal::Sio::new(pac.SIO);
    let pins = rp_pico::Pins::new(
        pac.IO_BANK0, pac.PADS_BANK0,
        sio.gpio_bank0, &mut pac.RESETS,
    );

    // Configura UART0 em 115200 baud
    let uart_pins = (
        pins.gpio0.into_function::<hal::gpio::FunctionUart>(),
        pins.gpio1.into_function::<hal::gpio::FunctionUart>(),
    );

    let mut uart = hal::uart::UartPeripheral::new(
        pac.UART0,
        uart_pins,
        &mut pac.RESETS,
    )
    .enable(
        UartConfig::new(115_200u32.Hz(), uart::DataBits::Eight, None, uart::StopBits::One),
        clocks.peripheral_clock.freq(),
    )
    .unwrap();

    let mut delay = cortex_m::delay::Delay::new(
        core.SYST,
        clocks.system_clock.freq().to_Hz(),
    );

    // Envia mensagens pela serial
    writeln!(uart, "Sistema iniciado!").ok();
    writeln!(uart, "RP2040 com Rust embarcado").ok();

    let mut contador = 0u32;
    loop {
        writeln!(uart, "Tick {contador}").ok();
        contador += 1;
        delay.delay_ms(1000);
    }
}

Alocação dinâmica com embedded-alloc

Quando você precisa de Vec ou String em sistemas embarcados:

#![no_std]
#![no_main]

extern crate alloc;

use alloc::vec::Vec;
use alloc::string::String;
use embedded_alloc::LlffHeap as Heap;

// Define o alocador global com 8KB de heap
#[global_allocator]
static HEAP: Heap = Heap::empty();

use panic_halt as _;
use rp_pico::entry;

#[entry]
fn main() -> ! {
    // Inicializa o heap — deve ser feito antes de qualquer alocação
    {
        use core::mem::MaybeUninit;
        const HEAP_SIZE: usize = 8192;
        static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] =
            [MaybeUninit::uninit(); HEAP_SIZE];
        unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
    }

    // Agora podemos usar Vec, String, Box, etc.
    let mut buffer: Vec<u8> = Vec::new();

    for i in 0..10u8 {
        buffer.push(i * i);
    }

    // String formatada sem std
    let mut msg = String::from("Valores: ");
    for &v in &buffer {
        // ufmt para formatação sem std (mais leve que core::fmt)
        // msg.push_str(&format!("{} ", v)); // requer std
    }

    loop {
        cortex_m::asm::wfi();
    }
}

Testando código embarcado no desktop

Uma vantagem enorme do Rust embarcado é que código escrito contra embedded-hal pode ser testado no desktop usando implementações mock:

[dev-dependencies]
embedded-hal-mock = "0.10"
#[cfg(test)]
mod testes {
    use super::*;
    use embedded_hal_mock::eh1::{
        digital::{Mock as PinoMock, State, Transaction as PinoTx},
        delay::NoopDelay,
    };

    #[test]
    fn ds18b20_responde_presenca() {
        // Define expectativas: o pino vai ser lido como LOW (presença)
        let expectativas = [
            PinoTx::set(State::Low),    // reset: puxar para baixo
            PinoTx::set(State::High),   // liberar
            PinoTx::get(State::Low),    // sensor puxa para baixo (presença)
        ];

        let pino = PinoMock::new(&expectativas);
        let delay = NoopDelay::new();
        let mut sensor = Ds18b20::novo(pino, delay);

        // O reset deve detectar presença
        // assert!(sensor.reset().is_ok());

        // Verifica que todas as expectativas foram cumpridas
        // sensor.pino.done();
    }

    #[test]
    fn temperatura_converte_corretamente() {
        // 0x0190 = 400 em decimal
        // 400 * 0.0625 = 25.0°C
        let raw: i16 = 0x0190;
        let temp = raw as f32 * 0.0625;
        assert!((temp - 25.0).abs() < 0.001);
    }

    #[test]
    fn temperatura_negativa() {
        // 0xFF5E = -162 em complemento de dois
        // -162 * 0.0625 = -10.125°C
        let raw: i16 = -162i16;
        let temp = raw as f32 * 0.0625;
        assert!((temp - (-10.125)).abs() < 0.001);
    }
}

Esses testes rodam com cargo test no seu computador — sem necessidade de hardware.


Depuração com probe-rs

# Flash e executa no hardware
cargo run --release

# Debug interativo com GDB
cargo embed --gdb

# Monitora saída RTT (Real-Time Transfer — debug sem serial)
probe-rs run --chip RP2040 target/thumbv6m-none-eabi/release/pico_embarcado

Embed.toml — configuração para probe-rs:

[default.general]
chip = "RP2040"

[default.reset]
halt_afterwards = false

[default.rtt]
enabled = true

[default.gdb]
enabled = false

Um projeto completo: termômetro com display

Combinando sensor de temperatura, display OLED e UART:

#![no_std]
#![no_main]

use panic_halt as _;
use rp_pico::entry;
use rp_pico::hal::{self, pac, clocks, i2c::I2C};
use embedded_hal::digital::OutputPin;
use core::fmt::Write;
use fugit::RateExtU32;

// Driver OLED SSD1306 via I2C
// (requer crate ssd1306)

#[entry]
fn main() -> ! {
    let mut pac = pac::Peripherals::take().unwrap();
    let core = pac::CorePeripherals::take().unwrap();

    let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
    let clocks = clocks::init_clocks_and_plls(
        rp_pico::XOSC_CRYSTAL_FREQ,
        pac.XOSC, pac.CLOCKS,
        pac.PLL_SYS, pac.PLL_USB,
        &mut pac.RESETS, &mut watchdog,
    ).ok().unwrap();

    let sio = hal::Sio::new(pac.SIO);
    let pins = rp_pico::Pins::new(
        pac.IO_BANK0, pac.PADS_BANK0,
        sio.gpio_bank0, &mut pac.RESETS,
    );

    // I2C para o display OLED
    let i2c = I2C::i2c0(
        pac.I2C0,
        pins.gpio4.into_function(),  // SDA
        pins.gpio5.into_function(),  // SCL
        400u32.kHz(),
        &mut pac.RESETS,
        &clocks.system_clock,
    );

    let mut delay = cortex_m::delay::Delay::new(
        core.SYST,
        clocks.system_clock.freq().to_Hz(),
    );

    // Interface I2C
    let interface = ssd1306::I2CDisplayInterface::new(i2c);
    let mut display = ssd1306::Ssd1306::new(
        interface,
        ssd1306::size::DisplaySize128x64,
        ssd1306::rotation::DisplayRotation::Rotate0,
    )
    .into_buffered_graphics_mode();

    display.init().unwrap();

    // Sensor de temperatura interno do RP2040
    let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS);
    let mut sensor_temp = hal::adc::AdcPin::new(
        hal::adc::TempSense::new(&mut adc)
    ).unwrap();

    loop {
        // Lê ADC do sensor interno
        let leitura: u16 = adc.read(&mut sensor_temp).unwrap();

        // Converte ADC para temperatura
        // Fórmula do datasheet RP2040:
        // T = 27 - (ADC_voltage - 0.706) / 0.001721
        let tensao = leitura as f32 * 3.3 / 4096.0;
        let temperatura = 27.0 - (tensao - 0.706) / 0.001721;

        // Exibe no display (implementação simplificada)
        display.clear_buffer();

        // Em produção: use embedded-graphics para renderizar texto
        // Por simplicidade, apenas demonstramos a estrutura

        display.flush().unwrap();

        delay.delay_ms(1000);
    }
}

Adicione ao Cargo.toml:

ssd1306 = "0.8"
embedded-graphics = "0.8"

O estado do Rust embarcado em 2024

O ecossistema embarcado Rust amadureceu rapidamente:

Plataformas bem suportadas: RP2040 (Raspberry Pi Pico), STM32 (toda a família), nRF52 (Bluetooth), ESP32 com suporte crescente, RISC-V.

Embassy — framework async para embarcados — permite escrever código assíncrono em microcontroladores sem OS, usando async/await com executores zero-cost.

Knurling — ferramentas de developer experience para embarcados: defmt para logging eficiente, flip-link para detecção de stack overflow.

Produção — empresas como Oxide Computer, Tweede Golf, e Ferrous Systems usam Rust em hardware crítico em produção.


Fontes e leituras recomendadas

  • "The Embedded Rust Book" — guia oficial, essencial — https://docs.rust-embedded.org/book/
  • "The Discovery Book" — tutorial prático com STM32 — https://docs.rust-embedded.org/discovery/
  • Embassy — framework async para embarcados — https://embassy.dev
  • RTIC — concorrência baseada em interrupts — https://rtic.rs
  • probe-rs — ferramenta de debug e flash — https://probe.rs
  • defmt — logging eficiente para embarcados — https://defmt.ferrous-systems.com
  • "Awesome Embedded Rust" — curadoria do ecossistema — https://github.com/rust-embedded/awesome-embedded-rust
  • Ferrous Systems Trainings — treinamentos profissionais em Rust embarcado — https://ferrous-systems.com/training/
Comentários

Mais em Rust

O Ecossistema Rust — Cargo, crates.io e as Bibliotecas Essenciais
O Ecossistema Rust — Cargo, crates.io e as Bibliotecas Essenciais

Nenhuma linguagem existe no vácuo. O que torna Rust produtivo além da linguag...

Módulos e Organização de Código — Estruturando Projetos Reais
Módulos e Organização de Código — Estruturando Projetos Reais

Até agora todos os nossos exemplos viveram em um único arquivo main.rs. Isso...

Testes Avançados — Propriedades, Mocks e Benchmarks
Testes Avançados — Propriedades, Mocks e Benchmarks

No Artigo #16 cobrimos os fundamentos de testes em Rust — testes unitários, d...