Linguaggi per sistemi embedded: quale scegliere e perché
Linguaggi per sistemi embedded: perché il C domina ancora, quando usare C++, Rust, Zig, Assembly o MicroPython, e cos'è davvero il linguaggio Arduino.
Vuoi programmare un sensore, una scheda che accende un relè, un piccolo dispositivo a batteria, e ogni guida che trovi usa un linguaggio diverso: chi dice C, chi dice Rust, chi dice Python, chi dice "il linguaggio Arduino". Il motivo della confusione è che nell'embedded il linguaggio si sceglie partendo dall'hardware, non dai gusti. In questo articolo vediamo quali sono i vincoli, che ruolo ha ciascun linguaggio e da dove conviene iniziare.
Qual è il linguaggio per l'embedded
Nei sistemi embedded il linguaggio di riferimento è il C, affiancato dal C++ usato in forma ridotta; Rust è l'alternativa in crescita, l'Assembly serve per poche parti specifiche, e MicroPython è adatto a prototipi e didattica: la scelta dipende da memoria, consumi e requisiti di tempo reale del dispositivo.
Questa pagina non rispiega i singoli linguaggi, per quello ci sono le pagine dedicate. Qui trovi il criterio per scegliere e il ruolo di ciascuno quando il codice deve girare su un chip piccolo. Il panorama generale dei linguaggi, fuori dall'embedded, è invece in tutti i linguaggi di programmazione.
Il vincolo che cambia tutto
Sul tuo computer hai gigabyte di memoria, un sistema operativo che gestisce tutto e corrente dalla presa. Su un microcontrollore niente di tutto questo è scontato:
- Memoria in kilobyte. Spesso la memoria per il programma e quella per i dati si misurano in decine o centinaia di kilobyte, non in gigabyte. Un runtime pesante o un garbage collector semplicemente non ci stanno.
- Nessun sistema operativo (bare metal), oppure un RTOS. Nel caso bare metal il tuo programma è l'unica cosa che gira: parte all'accensione e controlla direttamente l'hardware. Un RTOS (sistema operativo in tempo reale) è un nucleo minimo che gestisce più attività, ma è lontanissimo da Linux o Windows.
- Consumi. Un dispositivo a batteria deve dormire quasi sempre e svegliarsi solo quando serve. Ogni ciclo di processore sprecato è autonomia persa.
- Tempo reale. In molti casi non basta che il risultato sia giusto: deve arrivare entro un tempo preciso. Un freno, un motore, un dispositivo medico non possono aspettare che il linguaggio faccia pulizia della memoria.
Questi quattro vincoli spiegano quasi tutte le scelte che seguono. Un linguaggio è adatto all'embedded nella misura in cui ti lascia controllare memoria e tempi, senza costi nascosti.
Microcontrollore o computer a scheda singola?
Prima di scegliere il linguaggio, chiarisci su cosa girerà. Sono due mondi diversi.
Un microcontrollore è un chip che integra processore, memoria e periferiche, senza sistema operativo completo. Sono di questo tipo le schede del mondo Arduino e le famiglie ESP32 e STM32. Qui valgono tutti i vincoli di sopra.
Un computer a scheda singola, come il Raspberry Pi, è un computer vero e proprio in miniatura: ci gira un sistema operativo completo, di solito Linux, con gigabyte di memoria. Lì puoi usare qualunque linguaggio, da Python a Go a JavaScript, come su un server. Se il tuo progetto ha bisogno di rete, interfaccia web, database, è quasi sempre più semplice partire da lì.
Il resto di questo articolo riguarda i microcontrollori, dove la scelta del linguaggio conta davvero.
I linguaggi e il loro ruolo
C: lo standard di fatto
Il C è il linguaggio in cui è scritta la gran parte del firmware esistente, e non per abitudine. Le ragioni sono concrete:
- Nessun runtime: il codice compilato fa esattamente ciò che hai scritto, niente di più.
- Accesso diretto all'hardware: con i puntatori scrivi negli indirizzi di memoria dei registri delle periferiche.
- Compilatori per ogni chip: qualunque microcontrollore esca, il primo strumento disponibile è un compilatore C.
- Il materiale del produttore è in C: librerie, esempi, driver e documentazione dei chip arrivano quasi sempre in C.
Il prezzo è quello noto: gestisci la memoria a mano e un errore di puntatore non ti avvisa, corrompe qualcosa in silenzio. Su un dispositivo senza schermo, trovare quel bug può richiedere giorni.
C++: sì, ma un sottoinsieme
Il C++ è molto usato nell'embedded, ma quasi mai per intero. Si prende ciò che non costa niente a runtime (classi, template, controlli del compilatore più rigorosi) e si lascia fuori ciò che costa:
- Niente eccezioni, perché aggiungono codice e rendono imprevedibili i tempi.
- Poca o nessuna allocazione dinamica dopo l'avvio:
newedeletepossono frammentare una memoria già minuscola. - Attenzione a parti pesanti della libreria standard, che su alcuni chip non stanno o non sono disponibili.
Usato così, il C++ ti dà un codice più organizzato del C senza perdere il controllo.
Rust: sicurezza della memoria senza garbage collector
Rust è la novità più seria degli ultimi anni in questo campo. Promette la stessa assenza di runtime del C, ma con un compilatore che impedisce intere categorie di errori di memoria prima che il programma parta, senza garbage collector. In un ambito dove un bug può rendere inutilizzabile un dispositivo installato sul campo, è un argomento forte.
Il limite onesto: l'ecosistema embedded di Rust è ancora giovane rispetto a quello del C. Il supporto varia molto da chip a chip, i produttori forniscono di rado strumenti ufficiali in Rust, e in azienda trovi più codice e più persone che conoscono il C. È una scelta ragionevole per un progetto nuovo su un chip ben supportato, meno per mantenere un firmware esistente.
Zig: promettente, non ancora stabile
Zig nasce proprio come alternativa al C, con un'attenzione dichiarata a ciò che fa il codice senza costi nascosti. Si integra bene con il codice C esistente, e questo lo rende interessante per l'embedded. Ma il linguaggio non ha ancora raggiunto una versione stabile: cambia tra una release e l'altra, e costruirci sopra un prodotto significa accettare di dover riscrivere parti di codice. Da seguire, non da adottare in produzione a cuor leggero.
Assembly: solo dove serve
L'Assembly oggi non si usa per scrivere interi firmware. Resta però necessario in punti precisi:
- Il codice di avvio, che prepara lo stack e la memoria prima che possa girare il C.
- Gli interrupt e le istruzioni speciali del processore che il C non esprime.
- Ottimizzazioni puntuali su poche righe critiche, quando il compilatore non basta.
Anche se non lo scrivi, saperlo leggere è utilissimo per capire cosa ha generato il compilatore.
MicroPython: prototipi e didattica
MicroPython è un'implementazione ridotta di Python che gira direttamente su alcuni microcontrollori, tra cui schede delle famiglie ESP32. Scrivi uno script, lo carichi e vedi il risultato subito, senza compilare. Per imparare e per verificare un'idea in un pomeriggio è ottimo.
I limiti: è più lento e consuma più memoria del C, ha una gestione automatica della memoria che può introdurre pause, e non è adatto a requisiti di tempo reale stretti o a dispositivi con pochissime risorse. Esistono anche firmware basati su Lua con la stessa logica: comodi per partire, raramente la scelta finale per un prodotto.
"Il linguaggio Arduino" non esiste
È l'equivoco più diffuso. Chi inizia con Arduino pensa di imparare un linguaggio chiamato Arduino. In realtà gli sketch Arduino sono C++, compilati da un normale compilatore C++, con una libreria che semplifica le operazioni comuni e un ambiente che nasconde alcuni passaggi.
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
}
setup() e loop() sono funzioni C++. pinMode e digitalWrite sono funzioni della libreria Arduino che, dietro le quinte, scrivono nei registri del chip. Il main c'è, solo che l'ambiente lo scrive per te e chiama le tue due funzioni.
La conseguenza è positiva: quello che impari con Arduino è C++ vero, e puoi usarlo oltre Arduino. Il limite è che la libreria nasconde proprio le parti che l'embedded professionale richiede di conoscere: registri, interrupt, consumi, temporizzazioni. Prima o poi dovrai scendere di un livello.
Tabella riassuntiva
| Linguaggio | Quando sceglierlo | Limite principale |
|---|---|---|
| C | Quasi sempre: firmware, driver, lavoro professionale | Memoria gestita a mano, errori silenziosi |
| C++ (sottoinsieme) | Progetti più grandi che beneficiano di strutture e astrazioni | Serve disciplina per evitare le parti costose |
| Rust | Progetti nuovi, chip ben supportati, sicurezza prioritaria | Ecosistema embedded ancora giovane |
| Zig | Sperimentazione, integrazione con codice C | Linguaggio non ancora stabile |
| Assembly | Avvio, interrupt, poche routine critiche | Legato all'architettura, lento da scrivere |
| MicroPython | Prototipi, didattica, progetti hobbistici | Lento, pesante, inadatto al tempo reale |
| Arduino (C++) | Primi passi, prototipi rapidi | La libreria nasconde l'hardware |
| Qualunque (su Raspberry Pi) | Progetti con Linux, rete, interfacce | Non è un microcontrollore: consumi e avvio diversi |
Errori comuni
Scegliere il linguaggio prima dell'hardware. Nell'embedded il chip decide cosa è supportato. Controlla prima quali strumenti esistono per la scheda che hai in mente.
Usare un microcontrollore dove serviva un Raspberry Pi, o viceversa. Se ti servono rete complessa e interfaccia web, un microcontrollore ti farà soffrire. Se ti serve leggere un sensore a batteria per mesi, un computer Linux è sprecato.
Portare le abitudini del desktop. Allocare memoria a piacere, usare librerie enormi, stampare log ovunque: su pochi kilobyte non funziona.
Credere di sapere l'embedded perché si usa Arduino. È un ottimo inizio, ma la libreria nasconde il funzionamento reale del chip.
Adottare un linguaggio giovane per un prodotto da mantenere per anni senza valutare supporto, strumenti e persone disponibili.
In sintesi
Nell'embedded il linguaggio lo scelgono i vincoli: pochi kilobyte, niente sistema operativo o un RTOS, consumi e tempo reale. Vince chi ti lascia controllare memoria e tempi senza costi nascosti.
Il C è lo standard di fatto, con il C++ usato in forma ridotta per i progetti più strutturati. Rust è l'alternativa seria in crescita, Zig è da seguire, l'Assembly resta per avvio e parti critiche, MicroPython per prototipi e didattica.
Arduino non è un linguaggio: è C++ con una libreria semplificata. E un Raspberry Pi non è un microcontrollore, ma un computer Linux dove puoi usare qualunque linguaggio.
Se inizi, parti dal C con una scheda economica. Accendi un LED, leggi un pulsante, poi prova a farlo scrivendo direttamente nei registri: capirai più in una settimana che in mesi di librerie.
Per costruire basi solide di programmazione prima di scendere verso l'hardware trovi i percorsi nei corsi.
Contenuto redatto con l'assistenza di strumenti di intelligenza artificiale e rivisto dalla redazione di Codegrind, che ne è responsabile. Hai trovato un errore? Scrivici.