leOS - un semplice OS per schedulare piccoli task

Per default, avr-gcc compila tutte le routine di reset "atomiche", quindi non possono essere interrotte

In questo caso non hai bisogno del leOS2, ma del watchdog puro.
Attivi il watchdog, se non resetti il suo contatore il watchdog ti resetta il micro.
Si può fare con il leOS1, puoi impostare il WatchDog in modo indipendente ad esempio per tempi lunghi (2/4/8 secondi) e poi metti un task che resetta il WDT un po' prima del reset.
Siccome il WDT è indipendente, se il suo contatore va in overflow esso resetta il micro indipendentemente da cosa il micro sta facendo (anche se è freezato in un loop infinito).

Questo sketch fa quel che dico e che mi pare stia dicendo tu:

#include <avr/wdt.h>
#include "leOS.h"

leOS myOS; 
byte led1Status = 0;
const byte LED1 = 13;

//program setup
void setup() {
    wdt_disable();
    myOS.begin(); //initialize the scheduler
    pinMode(LED1, OUTPUT);
    
    //add the tasks at the scheduler
    myOS.addTask(flashLed1, 150);
    myOS.addTask(resetWdt, 3500);
    myOS.addTask(freeze, 3000);
    wdt_enable(WDTO_4S);
}


//main loop
void loop() {
}


//first task
void flashLed1() {
    led1Status^=1;
    digitalWrite(LED1, led1Status);
}


//second task
void resetWdt() {
    wdt_reset();
}


void freeze() {
    while(1) {}
}

Creo 3 task.
Il primo fa lampeggiare un LED, il secondo resetta il WDT ed il terzo freeza il micro.
Siccome il freeze arriva prima del reset del contatore del WDT, il WDT resetta il micro senza problemi.
Puoi verificare commentando la riga dove aggiungo il task che blocca tutto e vedere come il LED lampeggi senza interruzioni.

non ero tatno interessato al nuovo leoS,ma alle tue conoscenze nel campo :)..se imposto un wd con reset,questo mi resetta il micro anche se sono in un'istruzione atomica e fin qua son d'accordo...se però il wd è impostato sia x reset che x lanciare interrupt,cosa capita se il micro era freezato in un'istruzione atomica??mi fa cmq il reset,o aspetta l'esecuzione dell'interrupt invocato?

Allora, la situazione è ingarbugliata ed ho studiato 1 oretta per capirci qualcosa :sweat_smile:

Prendi questo codice e caricatelo sull'Arduino, mettendo 3 led sui pin D7, D8 e D9:

#include <avr/wdt.h>
#include "leOS.h"
leOS myOS;

const byte LED1 = 7;
byte statusLed1 = 1;
const byte LED2 = 8;
const byte LED3 = 9;

void setup() {
    //per sicurezza, SEMPRE disattivare il WDT appena avviato lo sketch
    MCUSR = 0;
    wdt_disable();
    myOS.begin();
    pinMode(LED1, OUTPUT);
    pinMode(LED2, OUTPUT);
    pinMode(LED3, OUTPUT);
    //avviso che sono partito
    for (byte i=0; i<2; i++) {
        digitalWrite(LED3, HIGH);
        delay(100);
        digitalWrite(LED3, LOW);
        delay(100);
    }
    myOS.addTask(resetWdt, 3500); //resetto il WDT
    myOS.addTask(blinkLed1, 250); //lampeggio un LED
    myOS.addTask(freezeMcu, 4500); //congelo l'MCU
    setWDT(); //imposto il WDT
}

void loop() {}

void resetWdt() {
    wdt_reset();
}

void blinkLed1() {
    digitalWrite(LED1, statusLed1);
    statusLed1 ^=1;
}

void freezeMcu() {
    //inverti i commenti sulle 2 righe sottostanti
    //while(1) {};
    myOS.pauseTask(resetWdt);
}

void setWDT() {
    //fermo il WDT
    MCUSR = 0; 
    wdt_disable();
   //disabilito gli interrupt 
    SREG &= ~(1<<SREG_I);
    WDTCSR |= ((1<<WDCE) | (1<<WDE));
    WDTCSR = ((1<<WDE) | (1<<WDP3) | (1<<WDIE));
    SREG |= (1<<SREG_I); 
}

ISR(WDT_vect){
    digitalWrite(LED2, HIGH);
    delayMicroseconds(50000);
    digitalWrite(LED2, LOW);
    //WDTCSR |= (1<<WDIE); //decommenta e commenta
}

Questo codice attiva 3 task: un lampeggio di un LED, un reset del WDT ed un freeze.
Se freezo il micro con un ciclo infinito in un task, sia che reimposti il bit WDIE (quello che attiva l'interrupt sul WDT) sia che non lo faccia, il micro freezato non esegue l'ISR del WDT ma si resetta al timeout del watchdog.

Se invece blocco il reset del WDT, quindi non un freeze, se ho il bit WDIE reimpostato nella ISR, il micro esegue la ISR del WDT ma non resetta nulla. Se invece disattivo la reimpostazione del bit WDIE, accade una cosa buffa:

  1. al timeout preimpostato, viene eseguita la ISR ma non viene resettato il micro
  2. al successivo timeout NON viene eseguita la ISR ma viene subito resettato il micro.

Quest'ultima modalità, sul datasheet, è riportata come voluta ad esempio per far salvare la configurazione del microcontrollore se una routine ha freezato la CPU: eseguendo la ISR si può salvare dei dati prima del riavvio.

guardiamo se ho capito bene:
-se il micro è freezato,allora il wd scatena solo il reset(anche se era nella modalità interrupt+reset)
-altrimenti se non è freezato,e se ho impostato il wd per lanciare interrupt+reset,al primo watchdog viene invocato l'interrupt..se poi voglio resettarlo,invoco un altro watchdog..

m_ri:
guardiamo se ho capito bene:
-se il micro è freezato,allora il wd scatena solo il reset(anche se era nella modalità interrupt+reset)

Esattamente, perché una ISR non può interrompere un'altra ISR (a meno di non modificare le librerie Avr).

-altrimenti se non è freezato,e se ho impostato il wd per lanciare interrupt+reset,al primo watchdog viene invocato l'interrupt..se poi voglio resettarlo,invoco un altro watchdog..

Non esattamente. Il timer del WatchDog scorre sempre. La prima volta lui manda un segnale di interrupt: se il micro è freezato, ovviamente non può essere intercettato (caso precedente), altrimenti sì e quindi si può eseguire un compito. Poi il timer del WatchDog riparte, e la seconda volta spedisce un reset, e questo ovviamente funziona sempre :wink:

Con interrupt+reset attivi, la sequenza è:

  1. interrupt
  2. reset
    Se si reimposta nella ISR il bit WDIE, al prossimo timeout del contatore del WDT il micro non resetta ma riesegue prima l'interrupt. Solo se il bit WDIE non viene reimpostato, al successivo timeout il WDT resetta il micro.

Secondo me è una caratteristica mooolto interessante, perché dà modo appunto di usare il watchdog normalmente ma anche di resettarlo se qualcosa lo pianta. Anzi, nella prox versione del leOS2 la implemento :wink:

EDIT:
no, perché il WDT nel leOS2 è impostato per girare su un tempo di 16 ms. Può capitare che un task possa durare più di 16 ms, ed avrei il reset al successivo timeout del WDT.

Leo, quindi nella modalità ISR+RESET è la ISR stessa a decidere se al "prossimo giro"(*) il micro verrà resettato o se verrà lanciata di nuovo la ISR, giusto ?

Se è così, allora sarebbe possibile usare la ISR per salvare informazioni di stato in caso di freeze anche se tale salvataggio comportasse un tempo "lungo" (ad esempio salvare su eeprom una serie di variabili o un array). Tipo:

wdt_isr():
if l'operazione di salvataggio non è ancora stata avviata
lancia l'operazione di salvataggio dati
reimposta il bit WDIE
else
if il salvataggio dati NON è completo
reimposta il bit WDIE
else
// il salvataggio è completo, non facciamo nulla e lasciamo scadere il wdt provocando il reset del micro
endif
endif

(*) cioè al prossimo scadere del wdt

no leo, vorrei analizzare meglio la situazione.

Se il watchdog lancia una funzione, che retta il watchdog e poi fa qualcosa. se dura più del prossimo interrupt watchdog, che non si resetta, mi aspetto che la funzione venga lanciata di nuovo, bloccando temporaneamente le prima esecuzione. Ovviamente se ci sono variabili globali (e se usi librerie tipo Serial, Wire, etc..) in uso nasce un casino della madonna, perchè potresti usare valori temporanei.
In oltre ogni richiamo è una chiamata a funzione in stack, il che può causare uno stack overflow. Notare che comunque la seconda chiama riazzera il watchdog, quindi nessun problema di restet

il problema sussiste se invece il secondo interrupt viene ignorato perchè la ISR è già in esecuzione. In tal caso hai ragione a eliminare il reset, ma sinceramente cercherei una soluzione migliore, per esempio forzare il richiamo della ISR, ma una variabile ti avvisa se c'è qualche funzione pendente in esecuzione. In tal caso resetti il timer ma NON lanci l'esecuzione del nuovo codice. Potresti usare un puntatore a funzione al posto della variabile, se è NULL allora fai partire la funzione richiesta, se non è null e la funzione schedulata è differente da quella in esecuzione (e in modalità DEBUG), allora lanci un alert per avvisare l'utente del ritardi di esecuzione del task a causa della sua funzione precedente troppo pesante, o perchè ha schedulato due task per avvenire assieme.

Seguendo questo principio della modalità DEBUG, puoi avvisare l'utente quando stai effettivamente lanciando una sua funzione, calcolando anche l'errore rispetto all'orario impostato.

bhe direi che per ora ti ho rotto le balle abbastanza :grin:

una funzione, che retta il watchdog e poi fa qualcosa

Pensavo fossimo in un forum per famiglie :stuck_out_tongue:

Il contatore WDT è indipendente e non può essere neanche manipolato dall'esterno. Non è come i contatori dei timer che puoi modificargli il valore.
Quello lì o lo resetti prima che vada in timeout oppure lo disabiliti.

Quindi, facciamo un caso concreto: ho un task che impiega normalmente 5 ms, mettiamo che elabori qualcosa o aspetti dei dati da un sensore.
Come detto, il WDT è impostato a 16 ms, per avere una risoluzione accettabile. Se lo imposto in modalità interrupt+reset, la prima volta chiama la ISR e resetta il flag WDIE; la seconda volta, se il flag WDIE è ancora a 0, resetta il micro.
Normalmente il reset non accade perché in fondo alla ISR lo schedulatore reimposta ad 1 il bit WDIE.

Mettiamo ora che il sensore sia difettoso e "muoia". Il task che lo legge resta in attesa di una risposta che non arriva più. La prima volta viene sollevato l'interrupt ma questo non viene eseguito perché il controllo della CPU ce l'ha ancora il task bloccato. Dopo altri 16 ms il WDT va nuovamente in timeout e questa volta resetta il micro perché nessuno ha rimesso a posto il bit WDIE.

allora faccimao così:

Normalmente il reset non accade perché in fondo alla ISR lo schedulatore reimposta ad 1 il bit WDIE.

bene, il WDIE viene impostato ad 1 ALL'INIZIO della ISR, mi aspetto che ora la nostra ISR possa essere richiamata anche se non ha completato la sua esecuzione giusto? facciamo che imposta ANCHE una variabile da -1 a 1 se ha lanciato una funzione. Al temine della ISR la variabile torna ad essere -.
se la variabile non è -1 la viene incrementata di 1e non viene lanciata nessun'altra funzione; in pratica stai contando quanti cicli di WDT la tua funzione sta impiegando ad essere eseguita.

ora l'utente ha l'opzione per impostare quanti cicli di WDT una funzione può durare: se il valore è 0 vuol dire infinito, qualsiasi altro valore positivo vuol dire che raggiunto quel valore, il WDIE NON viene più azzerato, il che scatenerà un reset al prossimo giro.

Siamo d'accordo?

Sull'uso della variabile possiamo anche essere d'accordo ma ti ho spiegato che il WDT è indipendente. Se ho un qualcosa che mi blocca per più di 32 ms gli interrupt, mettiamo anche un codice dell'utente con un bel cli() all'inizio, il WDT va 2 volte in timeout e non c'è cristi: il micro viene resettato. Questo perché la ISR del watchdog non può essere chiamata, essendo una routine di interrupt ed essendo gli interrupt bloccati.

ma non puoi mettere come primissima istruzione una sei(), e quindi attivare la RI-chiamata dell'interrupt?

edit: al posto di sei() puoi usare:

"ISR_NOBLOCK(WDT_vect)" al posto di "ISR(WDT_vect)"

da avr-libc: <avr/interrupt.h>: Interrupts

lesto:
ma non puoi mettere come primissima istruzione una sei(), e quindi attivare la RI-chiamata dell'interrupt?

Potrei richiamare la ISR sempicemente mettendo ad 1 il bit WDIF, che segnala al micro quando è stato chiamato un interrupt dal WDT. Però il punto è un altro, se ho la CPU "insabbiata" in un codice atomico, l'ISR del watchdog non sarà mai eseguita, ma il WDT continuerà a contare per cui si arriverà al reset del micro anche se ciò non è voluto.

Spero poi di aver capito cosa intendevi dirmi :sweat_smile:

sì, ma permetti che un codice atomico che dura 16ms è errato di partenza, e infatti col mio sistema rendi il codice dalla ISR del watchdog NON atomica.
Se invece sei in un altro interrput (vedi timer, pwm, etc) allora DOVREBBE essere settato un flag che indica che è stat richiesto l'interrupt WDT che sarà eseguito appena possibile.

quindi, salvo che l'user non crei un suo interrupt bloccante che dura più di 16ms, non avrai problemi con la tua libreria.

se poi di default metti il reset a 0 (no reset), allora l'user deve fare un interrupt pessimo E tirarsi la zappa sul piede

lesto:
sì, ma permetti che un codice atomico che dura 16ms è errato di partenza

Su questo siamo d'accordo.
Ma il problema non siamo tanto né io né te, ma gli utenti che usano questa cosa.
Se leggi nelle varie pagine, vedrai come di errori derivanti dal fatto di non sapere come funziona il core di Arduino e le sue funzioni, quindi il rischio di ritrovarsi un task freezato c'è.

e infatti col mio sistema rendi il codice dalla ISR del watchdog NON atomica.

Lo vedo ora, non avevo notato la modifica.

Se invece sei in un altro interrput (vedi timer, pwm, etc) allora DOVREBBE essere settato un flag che indica che è stat richiesto l'interrupt WDT che sarà eseguito appena possibile.

quindi, salvo che l'user non crei un suo interrupt bloccante che dura più di 16ms, non avrai problemi con la tua libreria.

se poi di default metti il reset a 0 (no reset), allora l'user deve fare un interrupt pessimo E tirarsi la zappa sul piede

Vedo di rileggermi con calma tutto.

Mi son riletto con calma tutto... si può fare.
Rendendo lo schedulatore non atomico, anche i task che esso gestisce lo diventano. Quando il WDT va in timeout chiama nuovamente la corrispondente ISR. Questa decrementa il conta-timeout e controlla se non siamo arrivati a zero, in caso non rimette ad 1 il flag de WDIE per cui al successivo timeout il watchdog resetta bellamente il micro.
Se siamo invece ancora nella fase "normale", controlla se lo scheduler sta eseguendo un task oppure è libero. Se lo sta eseguendo, esce. Se è libero, ne lancia uno.

Dicevi così?

esatto. la sintesi non è il mio forte, a quanto pare :grin:

Perfect. Oggi butto giù qualche riga di codice e faccio i test.

Non funziona.
Rileggendo la documentazione che mi hai linkato, mi sembra di capire che comunque si possono verificare condizioni anomale, come ISR nidificate che possono saturare lo stack. Difatti facendo eseguire un paio di task insieme, di cui uno freezante, ad un certo punto i led che ho messo di debug lampeggiano stranamente, poi il tutto si resetta e riparte per bene, per poi riandare in tilt di lì a poco.

il problema degli stack lo hai solo quando si lanciano troppi interrupt senza che i precedenti siano completi.
Per questo devi fare attenzione a lanciare il task solo una volta, e in tutti gli altri casi lasciare il prima possibile l'interrupt libero.

che codice hai usato?