leOS - un semplice OS per schedulare piccoli task

d407336:
È una buona idea far fare tutto il lavoro a delle funzioni richiamate con leOS e lasciare il loop() vuoto se non serve eseguire in continuazione il codice o è meglio lasciare la parte principale in loop() e mettere un delay alla fine?

I task dovrebbero essere compiti semplici, che occupano poco tempo CPU, in modo da non rallentare l'esecuzione delle altre funzioni basate su interrupt. Se il tuo caso ricade in questo esempio, puoi anche inserire tutto in task e lasciare il loop vuoto. Se hai un compito che invece è molto gravoso, conviene inserirlo nel loop (e qui puoi rallentarne l'esecuzione con un delay).

Testato:
a proposito Leo, complimenti per il lavoro pubblicato sull'overflow di millis,

Grazie :wink:

potresti chiedere di modificare il blinkwithoutdelay aggiungendo la tua idea per superare i famosi 50giorni
sarebbe comunque molto bello avere il tuo nome in uno sketch che viene inserito ufficialmente negli esempi dell'IDE

Non credo che lo faranno mai :stuck_out_tongue_closed_eyes:

p.s. ma oltre all'articlo sul tuo blog c'e' hai aperto anche una discussione sul forum ?

No.

PaoloP:
Leo, hai visto questa discussione? --> http://arduino.cc/pipermail/developers_arduino.cc/2012-October/007211.html

Oggi ho studiato un po' la cosa, ed è una funzione molto più complessa della mia. Essa sfrutta un modulo presente nel SAM3X, il SysTick, un contatore indipendente dai timer del micro a cui hanno agganciato un gestore di task. Ogni task ha un suo tempo computazionale, ed il tutto è gestito dallo scheduler proprio tramite i tick, anche il delay segue lo stesso principio: usano un task che genera i millis, che poi leggono per i ritardi. Insomma, una cosina più raffinata che ovviamente non è replicabile sui Mega perché appunto utilizzano dell'HW dedicato.

PS:
in teoria si potrebbe agganciare il leOS ad un INT esterno o ad un PCINT, ma in questo modo si perderebbe un pin.

Mi sa che tra un po' per il DUE uscirà una sorta di ArduinOS ex-novo... :wink:

tuxduino:
Mi sa che tra un po' per il DUE uscirà una sorta di ArduinOS ex-novo... :wink:

Non credo, penso che non sia questo il desiderio del team di Arduino. Loro sono per le cose semplici, ed un RTOS nel vero senso della parola non è una cosa tanto facile da gestire per un utente di medio livello. E poi non penso che passerà molto tempo prima che progetti quali FreeRTOS o ChibiOS siano resi compatibili con il SAM3X della DUE.

In effetti la mia era più che altro una battuta. Penso che tu abbia ragione.

non penso che passerà molto tempo prima che progetti quali FreeRTOS o ChibiOS siano resi compatibili con il SAM3X della DUE[/quote

Il Chibi l'avevo provato sulla 2009, mi è sembrato interessante anche se non ho approfondito. Credo che la DUE diventerà una piattaforma molto interessante per lo studio dei sistemi operativi grazie alla maggiore potenza della cpu.

tuxduino:
Il Chibi l'avevo provato sulla 2009, mi è sembrato interessante anche se non ho approfondito. Credo che la DUE diventerà una piattaforma molto interessante per lo studio dei sistemi operativi grazie alla maggiore potenza della cpu.

Il problema della DUE è che non è abbastanza potente per poter farci girare un vero SO, e gli RTOS embedded girano tranquillamente anche su chippini insignificanti come i Tiny con 2 kB di Flash, vedi il FemtoOS. Insomma, nulla di nuovo.

Ah, ok. Pensavo che con un chip molto più potente si potesse fare chissà cosa nel campo rtos rispetto a UNO... :cold_sweat:

Diciamo che la gestione ne risulta avvantaggiata per diversi motivi: maggior clock, maggior memoria, 32 bit di potenza, sistema di tick gestito via HW, DMA per un accesso diretto e veloce alla memoria ecc...
Basta vedere appunto la libreria Scheduler creata ad oc per la DUE in men che non si dica

leo72:

d407336:
È una buona idea far fare tutto il lavoro a delle funzioni richiamate con leOS e lasciare il loop() vuoto se non serve eseguire in continuazione il codice o è meglio lasciare la parte principale in loop() e mettere un delay alla fine?

I task dovrebbero essere compiti semplici, che occupano poco tempo CPU, in modo da non rallentare l'esecuzione delle altre funzioni basate su interrupt. Se il tuo caso ricade in questo esempio, puoi anche inserire tutto in task e lasciare il loop vuoto. Se hai un compito che invece è molto gravoso, conviene inserirlo nel loop (e qui puoi rallentarne l'esecuzione con un delay).

Usare un analogRead() e poi stampare un valore su un display con LiquidCrystal non dovrebbe essere troppo gravoso?

e' gravoso :slight_smile:

l'analog read è gravosa (0.2millisecondi circa), stampare su LCD ancora di più.

0.2millisecondi vuol dire che dopo 5000 letture il tuo timer della millis() sballa di 1 secondo

Non tanto il fatto di stamparci sopra, se comunica in i2c sono 100Kbit/s, o 400Kbit/s se modifichi le libreria e il modulo supporta la fastI2c, però il vero problema è che l'esse umano non vede più velociemente di circa 20/50millisecondi, se poi parliamo di leggere arriviamo al secondo e rotto :slight_smile:

nel tuo caso sarebbe da attivare a livello di registri l'interrupt per il completamento della lettura analogica. All'interrupt del leos fai partire la lettura analogica (che lavora in parallelo, quindi non "spreca" cpu), al completamento viene lanciato un interrupt che mette il valore letto in una variaile volatile.

poi un altro task nel leos ogni secondo prende la variabile volatile e la stampa sull'LCD.

Il tempo impiegato dell'I2C, ma ancor meglio dalla SPI (fino a 5Mbit/s se ricordo bene, ma dipende dal modulo), è inifluente. Quello della seriale invece influisce, ma la Serial lavora per interrupt quindi in pratica lo sbattimento di cui sopra è già fatto, devi solo fare attenzione a non riempire il buffer seriale più in fretta di quanto esso non riesca a svuotasi

Potrebbe usare il looper, schedula i compiti ma non ha un sistema bloccante degli interrupt come il leOS.

Grosse novità in arrivo.... :wink:

lesto:
l'analog read è gravosa (0.2millisecondi circa), stampare su LCD ancora di più.

0.2millisecondi vuol dire che dopo 5000 letture il tuo timer della millis() sballa di 1 secondo

Non tanto il fatto di stamparci sopra, se comunica in i2c sono 100Kbit/s, o 400Kbit/s se modifichi le libreria e il modulo supporta la fastI2c, però il vero problema è che l'esse umano non vede più velociemente di circa 20/50millisecondi, se poi parliamo di leggere arriviamo al secondo e rotto :slight_smile:

nel tuo caso sarebbe da attivare a livello di registri l'interrupt per il completamento della lettura analogica. All'interrupt del leos fai partire la lettura analogica (che lavora in parallelo, quindi non "spreca" cpu), al completamento viene lanciato un interrupt che mette il valore letto in una variaile volatile.

poi un altro task nel leos ogni secondo prende la variabile volatile e la stampa sull'LCD.

Il tempo impiegato dell'I2C, ma ancor meglio dalla SPI (fino a 5Mbit/s se ricordo bene, ma dipende dal modulo), è inifluente. Quello della seriale invece influisce, ma la Serial lavora per interrupt quindi in pratica lo sbattimento di cui sopra è già fatto, devi solo fare attenzione a non riempire il buffer seriale più in fretta di quanto esso non riesca a svuotasi

grazie lesto, ma se io metto il task con un intervallo di 1 secondo il display verrà aggiornato ogni secondo (e il valore da scrivere potrebbe rimanere uguale), quindi non penso ci siano problemi di aggiornamento troppo rapido da non riuscire a leggere.
il problema che tu dici è che se per scrivere sul display serve un certo tempo, la successiva ripetizione del task non verrà eseguita esattamente dopo l'intervallo dichiarato?

non ho usato looper perchè mi serve che il task venga eseguito ogni secondo, altrimenti dovrei fare un calcolo diverso tenendo conto del tempo passato dall'inizio del task al successivo inizio con millis(), ma con leos è meno complicato.
a proposito, se metto un intervallo di 1 secondo il task successivo inizierà 1 secondo dopo l'inizio del precedente o dopo un secondo dalla fine del precedente?

potrei fare calcolare il valore al task senza stamparlo sul display ma solo salvandolo in una variabile come ha detto lesto, ma poi anziche fare un altro task per scrivere sul display, mettere in loop() la scrittura sul display leggendo la variabile e alla fine di loop() mettere un delay?

Dopo aver letto ieri di come funziona lo scheduler sull'Arduino DUE, che è basato su uno specifico timer interno al micro (sysTick), stavo pensando se c'era modo di poter replicare la cosa in qualche modo anche sull'Arduino UNO/Leonardo e sugli altri microcontrollori che normalmente uso.

Alla fine è nato leOS2!
Che cos'ha di differente il leOS2 rispetto al leOS? Una cosa tanto semplice quanto "ganza". Esso usa il WatchDog Timer (WDT) al posto di un timer del microcontrollore!

Che cos'è il WDT? E' un timer utilizzato come contatore dal WatchDog, il circuito che normalmente viene utilizzato per resettare il microcontrollore nel caso il codice si blocchi durante l'esecuzione in loop infiniti oppure in eterna attesa di dati che non arrivano mai. Se il tempo prefissato come "campanello d'allarme" termina prima che il contatore sia stato resettato, il WatchDog viene attivato ed esso resetta il micro. Per far ciò il contatore viene incrementato da un oscillatore interno separato a 128 kHz tramite un prescaler che divide la frequenza in modo da avere diversi tempi di reset, da 16 ms in su.

Il WDT può essere utilizzato sia per resettare il microcontrollore sia per generare un semplice resetinterrupt. Quest'ultimo caso è quello che ci interessa: creando una ISR (Interrupt Service Routine) che intercetta il reset, possiamo gestire lo scheduler dei processi da eseguire in automatico senza utilizzare timer del microcontrollore, evitando quindi che il nostro sketch vada in conflitto con altre librerie che anch'esse usano lo stesso timer.

Il prezzo da pagare è la risoluzione degli intervalli: essi devono essere di 16 ms o multipli di esso per via del fatto che un "tick" non può essere più corto di 16 ms:
Fosc/Prescalermax -> 128.000 Hz / 2.048 = 62,5 Hz
1/62,5 = 0,016 s -> 16 ms
I metodi addTask() e modifyTask del leOS sono stati rivisti in modo che essi accettino il nuovo parametro in input. Se non si vuole fare a mano la conversione fra ms e tick (basta una semplice divisione per 16, comunque), si può usare il nuovo metodo convertMs(), che trasforma appunto i ms in tick. Ecco un esempio:

myOS.addTask(funzione, myOS.convertMs(1000));

Questa riga aggiunge la funzione "funzione" allo scheduler impostando un intervallo di 62 tick (1000/16=62,5->62).

In aggiunta a tutti i metodi del leOS, ho introdotto anche il metodo reset(). Siccome il WatchDog è spesso utilizzato per resettare un microcontrollore, se si vanno ad usare i metodi standard della libreria WDT.h per gestire il WatchDog si altera il funzionamento del leOS2 sia prima che dopo il reset. Per questo motivo, basta richiamare:

myOS.reset();

in qualunque punto del codice per resettare il microcontrollore senza

Atmel ha strutturato i suoi micro in modo che il circuito del WatchDog ed i registri che lo gestiscono siano uguali per quasi tutti i microcontrollori Atmega ed Attiny per cui la libreria può, in teoria, funzionare sulla maggior parte dei chip supportati dall'IDE e dai core disponibili senza uso di #define varie (esempio: Atmega88/168/328, Atmega644/1284, Atmega2560, Attiny84, Attiny85 ecc...). L'unico chip supportato dall'IDE che non è supportato dalla libreria è l'Atmega8 perché questo microcontrollore non ha la possibilità di sollevare un interrupt all'overflow del contatore del WatchDog ma solo un segnale di reset.

Ribadisco un concetto, chiarendolo meglio.
Il leOS2 non è preciso come il leOS né può gestire intervalli al millisecondo. La risoluzione è 16 ms, quindi 16 ms, 32 ms, 128 ms, 256 ms, 1024 ms sono valori precisi. 500 ms sono arrotondati a 496 (500/16=31,25->31 e 31*16=496).
Però il vantaggio è quello di poter ora utilizzare tutte le librerie che usano lo stesso timer impegnato dal precedente leOS.

Attendo repliche, consigli, segnalazioni di bug, suggerimenti e critiche :wink:

watchdog_timer_block.png

leOS2-2.0.2.zip (22.8 KB)

d407336:
a proposito, se metto un intervallo di 1 secondo il task successivo inizierà 1 secondo dopo l'inizio del precedente o dopo un secondo dalla fine del precedente?

1 secondo dopo la fine del precedente.
Sia il contatore su cui è basato lo scheduler che lo scheduler stesso sono contenuti nella ISR di gestione dell'interrupt sull'overflow del timer per cui quando viene eseguito un task il contatore non viene incrementato.

bellissima l'idea del watchdog, ma così non si resetta anche il codice del loop? oppure intercettando la ISR fai in modo che non si resetta?

lesto:
bellissima l'idea del watchdog, ma così non si resetta anche il codice del loop? oppure intercettando la ISR fai in modo che non si resetta?

Leggi qui:

Comunque se non resetti il wdt non viene resettato "il codice del loop", ma l'intero micro. E' come il pulsante di reset quando winsozz si pianta... :wink:

lesto:
bellissima l'idea del watchdog, ma così non si resetta anche il codice del loop? oppure intercettando la ISR fai in modo che non si resetta?

Colpa di un mio refuso :wink:

Il WDT può essere utilizzato sia per resettare il microcontrollore sia per generare un semplice reset interrupt. Quest'ultimo caso è quello che ci interessa: creando una ISR (Internet Service Routine) che intercetta il reset, possiamo gestire lo scheduler dei processi da eseguire in automatico senza utilizzare timer del microcontrollore, evitando quindi che il nostro sketch vada in conflitto con altre librerie che anch'esse usano lo stesso timer.

Si può attivare un reset, un interrupt od entrambi. Se attivo solo un interrupt, intercettando la ISR posso svolgere i miei compiti. Nella ISR ho messo proprio il contatore dei tick e lo schedulatore che gestisce i task. Purtroppo si ha una risoluzione di soli 16 ms ma, come vantaggio, non si impegnano timer aggiuntivi.
Inoltre il codice (se non si usa la funzione convertMs) risulta anche più snello di quello generato usando il leOS versione 1. Il che è un altro vantaggio.

A titolo informativo, ho parzialmente utilizzato la libreria Avr WDT.h: parzialmente perché essa parte dal presupposto di impostare il WDT per generare sempre e solo un reset, quindi per ora il suo utilizzo si limita allo sfruttamento di alcune macro per la gestione dei bit del registro di configurazione del WDT, mentre il setup del WDT lo faccio a mano. Prevedo col tempo di eliminarla completamente: l'ho usata per ora perché il leOS2 l'ho terminato praticamente stanotte e gli ultimi test li ho condotti stamani, prima di pubblicarla.