Da C a Rust: un caso reale di sviluppo firmware bare-metal

Progetto Proiettore Laser

In Develer amiamo sperimentare. Ogni anno formiamo dei gruppi di lavoro (GdL): piccoli team che si dedicano per alcuni giorni a studiare a fondo una tecnologia, analizzandone pro, contro e possibilità di adottarla in azienda.
L’obiettivo è condividere conoscenza e individuare tecnologie promettenti da portare nei progetti futuri.

Tra i GdL del 2025, uno in particolare ha affrontato un tema che sta facendo molto parlare di sé nel mondo dell’embedded: l’uso del linguaggio Rust in ambiente bare-metal.

Obiettivo: valutare Rust per lo sviluppo firmware

Il gruppo, composto da Pietro Lorefice, Luca Bennati, Giovan Battista Rolandi, Stefano Fedrigo, Marco Meloni, Mirko Banchi e Francesco Sacchi, si è posto una domanda concreta:

Quanto è maturo Rust per essere usato nello sviluppo firmware real-time, in alternativa al C?

Il focus principale è stato Embassy, un framework asincrono che si propone come riferimento per lo sviluppo embedded in Rust.
L’obiettivo era duplice:

  1. Analizzare pro e contro del linguaggio, con attenzione a performance, determinismo e sicurezza.
  2. Verificare sul campo la produttività e maturità di Embassy in un progetto realistico.

Rust e Embassy: un binomio interessante

Rust è un linguaggio general purpose che punta a unire memory safety, type safety e performance comparabili al C, grazie al backend LLVM e a un ecosistema in costante crescita.
Il compilatore supporta ufficialmente numerosi target embedded (inclusi Cortex-M e RISC-V), garantendo toolchain affidabili e cross-compilabili.

Embassy, invece, è un framework completo per applicazioni embedded asincrone:

In altre parole, Embassy consente di scrivere codice di alto livello che gira su hardware di bassissimo livello, mantenendo il controllo tipico del C, ma con i benefici di Rust.

Il caso di studio: il firmware di un proiettore laser

Per il test pratico, il gruppo ha scelto di riscrivere in Rust il firmware di un proiettore laser utilizzato nelle macchine industriali per proiettare forme su piani di lavoro.

Il sistema si presta bene per l’esperimento perché è:

Il proiettore laser funziona modulando un diodo laser che proietta un fascio su due specchietti galvanometrici controllati in posizione. In particolare, un fascio laser modulato incide su due specchietti XY controllati in posizione; gli specchietti deflettono il fascio e proiettano su un piano; la rotta da seguire viene inviata da un PC via rete ed eseguita ciclicamente.

Il sistema hardware

Il loop di controllo principale è real-time e gira a 50 kHz, quindi con vincoli stringenti di temporizzazione e jitter.

Architettura software e metodo di lavoro

Il firmware è stato riscritto da zero in Rust, mantenendo la stessa struttura modulare dell’originale in C.

Il progetto è stato suddiviso in più crate Rust, uno per ogni componente del firmware:

L’organizzazione in crate e workspace Cargo ha permesso di sviluppare e testare i componenti in parallelo:

laser-v2/
├─ crc16/          # Calcolo CRC pacchetti
├─ trpv2/          # Parsing protocollo UDP
├─ routes/        # Gestione rotte laser
├─ sigproc/      # Filtri, PID, logica di controllo
├─ firmware/   # Applicazione principale (target)
├─ logic-test/  # Test logici su host
└─ xtask/          # Script CLI per build e deploy

Tutti i moduli sono stati scritti in Rust safe, senza uso diretto di unsafe, sfruttando le astrazioni del linguaggio per garantire sicurezza a compile-time.

La build è completamente riproducibile grazie alla toolchain e alle dipendenze vendorizzate.

Le principali componenti

TRPV2

Gestisce la ricezione dei pacchetti di rete contenenti le rotte:

Routes

Riceve le rotte e ne gestisce:

Firmware

È il collante tra tutti i moduli.
Divide il lavoro in task Embassy asincroni:

Lo scheduler di Embassy esegue due esecutori in parallelo:
uno cooperativo per i task di background, e uno preemptive ad alta priorità per il loop real-time.

Il loop principale del laser è scandito da un Ticker a 50 kHz, che legge i punti da un canale condiviso e aggiorna DAC e PWM:

let mut ticker = Ticker::every(Duration::from_micros(20));
loop {
    ticker.next().await;
    let p = POINTS_CHAN.receive().await;
    dac_x.set(Value::Bit12Right(p.x() >> 2));
    dac_y.set(Value::Bit12Right(p.y() >> 2));
    diode_pwm.set_duty_cycle_percent(...);
}

Il risultato? Jitter di poche decine di nanosecondi – circa tre ordini di grandezza inferiore al periodo, ottimo per un’applicazione real-time.

Analisi del linguaggio Rust

Pro

Contro

Embassy: vantaggi e limiti

Pro

Contro

Le conclusioni del gruppo

Abbiamo riscritto in tre giorni l’80% del firmware originale, con performance equivalenti al C e senza bug subdoli.

Il gruppo ha concluso che Rust è assolutamente utilizzabile in ambiente bare-metal, anche per applicazioni real-time, purché si accetti l’iniziale complessità del linguaggio.
L’esperienza con Embassy è stata molto positiva: il framework semplifica lo sviluppo e consente un livello di astrazione elevato senza sacrificare efficienza.

Rust non è (ancora) il sostituto universale del C, ma in molti casi può già esserlo – in modo più sicuro, moderno e collaborativo.