Un task scheduler per Arduino
Come promesso, è ora di tornare sulla gestione del multithreading in Arduino.
Data la limitatezza delle risorse disponibili, sarà necessario mettere alcuni "paletti" ben fissi, ma questo non ci impedirà di avere libertà sufficiente per gli usi più svariati.
L'errore comune che si fa è l'uso di delay()
. È assolutamente da evitare: il codice va scritto in modo che sia composto di "blocchi" da eseguire nel minor tempo possibile e tutti i ritardi vanno "accumulati" al di fuori dei blocchi di codice.
In questo modo, ciò che per un thread è "pausa", per un altro è "esecuzione". E la difficoltà è più apparente che reale: una volta abituati a pensare nel modo giusto, risulta poi difficile tornare al vecchio metodo, anche se solo per uno sketch di poche decine di righe.
Un esempio è questa libreria.
Abbastanza ben strutturata, ma passa come parametro il "now" per ben due volte, quando potrebbe essere gestito con una variabile privata "volatile static const uint32 &" inizializzata nel costruttore della classe (un modo "elegante" per usare una variabile globale senza usarla )...
Abbastanza "brutti" il polling e la lista statica, ma dati i constraints (soprattutto la poca RAM) non ci vedo alternative altrettanto efficienti. Per evitare il polling sarebbe necessaria una coda con priorità in cui i vari task in stato "ready" si inseriscono, ma questa dovrebbe essere comunque tenuta in RAM e scandita. Un buon compromesso potrebbe essere un vettore di uint32 che segnali per ogni task quando sarà il prossimo stato di ready. Scandendo il vettore non servirebbe una costosa chiamata a funzione per verificarlo, ma si "sacrificano" almeno 4 byte di preziosa RAM per ogni thread. C'è da dire che molto probabilmente la classe che implementa il thread deve comunque tenerne traccia, e quindi tanto vale "centralizzare" la cosa (anche perché gestirla correttamente può non essere banale, soprattutto il caso "esegui non appena possibile anche nello stesso millisecondo" che dovrebbe solo rimettere il task in coda senza far ripartire la scansione).
Un'altra cosa che non mi piace troppo è che non si esce mai dallo scheduler, e questo contrasta un tantino con la filosofia di Arduino. Inoltre, mettendo il codice di Taskscheduler:run() privato del while(1)
all'interno della run()
dello sketch, renderebbe il sistema ancora più versatile.
[Edit]: Magari l'ho imparato un po' in ritardo, ma ci sono anche dei veri e propri kernele realtime: ChibiOS/RT, FreeRTOS e BeRTOS.
Mentre possono essere leggermente overkill per un Uno o un Leonardo, sono quasi necessari per poter sfruttare bene Due.