looper - un semplice schedulatore senza timer/interrupt

legacy, ti comunico che sei rientrato nella mia lista dei cattivi :stuck_out_tongue_closed_eyes:

Ciao Leo, tramite looper è possibile lanciare una istanza OneTime per effettuare l'accensione e lo spegnimento di un pin con un intervallo settabile?

Mi spiego: avviene un evento metto il pin HIGH passa un tot di tempo metto il pin LOW

questo solo una volta e per ogni evento. Lo so che posso usare la millis con variabili di supporto, ma mi si incasina lo sketch. oppure è meglio la Leos?

Un task di tipo onetime viene eliminato dallo scheduler una volta eseguito. Quindi pare fatto apposta per il tuo caso.

Se la tempistica non è critica, puoi usare anche il looper invece che il leOS.

Ok. Provo. :roll_eyes:

leo72: In allegato trovate looper2, è una versione modificata di looper che dovrebbe sistemare il bug dell'overflow di milis mediante l'uso di un contatore di overflow a 8 bit, creando in pratica una variabile a 40 bit, che dovrebbe quindi soffrire del problema dell'overflow dopo 34 anni. Ho modificato anche lo scheduler affinché usi questa nuova gestione a 40 bit. Siccome non ho potuto provare per 49,7 giorni BlinkWithoutMillis, rilascio il codice così com'è :sweat_smile: :sweat_smile:

Leo rileggendo questo punto mi chiedevo: ma la situazione overflow millis e' stata risolta da te/lesto con il discorso dell'articolo del tuo blog ? A questo punto non puoi usare quel metodo ed eliminare questa variabile da 40bit ?

Sì, vero. Ma quel post era di agosto, sai quant'acqua è passata sotto ai ponti... mica me lo ricordavo neanche :stuck_out_tongue_closed_eyes: Oggi provvedo a sistemare anche il looper. Grazie della segnalazione ;)

sara' a tutti gli effetti il Delay2.0 spero il team si accorga della grande necessita' di una funzione di ritardo non bloccante integrata nel core

Nuovo looper 1.0!

Fantastico, che devo dire di pi√Ļ? :stuck_out_tongue_closed_eyes:
Ecco un piccolo riassunto delle novità:

  1. ridotto il codice compilato rispetto al vecchio looper2 (usando struct e ripulendo il codice)
  2. mantenuta la sintassi dei metodi del looper2 (quindi addJob, removeJob ecc…) per poter usare il looper abbinato al leOS
  3. nuovo metodo myDelay! Adesso è possibile introdurre nel codice principale un’attesa mentre lo scheduler continua a lanciare i job in sospeso!! Allegato alla libreria c’è un esempio che mostra proprio questa possibilità: un job fa lampeggiare un led ogni 100 ms mentre nel loop principale un altro led viene fatto lampeggiare con un intervallo di 1000 ms gestito proprio con myDelay. Come si può vedere, il led che lampeggia con il minor tempo di intervallo non risente del delay di 1000 ms, mentre i precedenti looper e looper2 soffrivano di questo problema.

@leo72 +1

e complimenti! Pighixxx

Grazie pighi. Tornando ai punti, il 3 √® quello pi√Ļ interessante. L'esempio che ho inserito exnovo √® questo:

//include the scheduler
#include "looper.h"

looper myScheduler; //create a new istance of the class looper

//variabiles to control the LEDs
byte led1Status = 0;
byte led2Status = 0;
const byte LED1 = 7;
const byte LED2 = 8;

//program setup
void setup() {
    pinMode(LED1, OUTPUT);
    pinMode(LED2, OUTPUT);
    
    //add the tasks at the scheduler
    myScheduler.addJob(flashLed1, 100);
}


//main loop
void loop() {
    digitalWrite(LED2, led2Status);
    led2Status ^= 1;    
    myScheduler.myDelay(1000);
    myScheduler.scheduler();
}


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

Ciò che mi preme evidenziare è :

myScheduler.myDelay(1000);

Questa istruzione mette il looper in un ciclo in cui per 1000 milisecondi altro non fa che controllare lo scheduler per lanciare i job eventualmente da eseguire. Il job flashLed1 deve essere eseguito ogni 100 ms, e così viene fatto, nonostante il loop si fermi per 1000 ms. Ciò si evidenzia dal fatto che il secondo led lampeggia proprio con la frequenza di 1 secondo.

instancabile leo! posso chiederti come funziona il myDelay? in realtà è una sotto-dalay di 1ms che poi cha il ckeck, oppure fai un delay+delayMisslis fino al primo job schedulato, esegui, e così via fino a fine delay?

lesto:
instancabile leo! posso chiederti come funziona il myDelay? in realtà è una sotto-dalay di 1ms che poi cha il ckeck, oppure fai un delay+delayMisslis fino al primo job schedulato, esegui, e così via fino a fine delay?

E’ una scemenza immane :sweat_smile:

void looper::myDelay(unsigned long myPause) {
unsigned long _tMillis = millis();
  while (millis() - _tMillis < myPause) {
    scheduler();
  }
}

In pratica è un while basato su millis() che dura per il tempo passato come argomento e dentro a cui chiamo ripetutamente lo scheduler :wink:

ahahaha, ah ragione astro, sono proprio un UCAS :grin:

Tengo a precisare che la scemenza immane non era quello che avevi detto tu ma la soluzione che avevo adottato io ;) ;)

bhe alla luce dei fatti la mia √® una scemenza.. √® valida per ambienti multithread, dove sprecare CPU vuol dire rubare risorse ad altri processi/thread, ma sull'arduino la tua soluzione √® pi√Ļ che ottima :grin:

leo ma a questo punto possiamo dire di avere inventato il ‚Äúmio‚ÄĚ Delay2.0 ?

cioe’ posso usare in autonomia myDelay, sostituendolo ai delay normali di arduino, senza creare nessun AddJob ?

cioe’ questo funziona e non e’ bloccante ?

BLINK 2.0 
(idea Testato - creazione Leo :-) )

void loop() {
  digitalWrite(13, HIGH);  
  myScheduler.myDelay(1000);
  myScheduler.scheduler();
  digitalWrite(13, LOW);    
  myScheduler.myDelay(1000);
  myScheduler.scheduler();
}

funziona e puoi eliminare le due righe myScheduler.scheduler();

Testato: leo ma a questo punto possiamo dire di avere inventato il "mio" Delay2.0 ?

Guarda che l'ho chiamato myDelay, e non testatoDelay :P

cioe' posso usare in autonomia myDelay, sostituendolo ai delay normali di arduino, senza creare nessun AddJob ?

Lo puoi fare ma a questo punto è inutile la lib.

cioe' questo funziona e non e' bloccante ?

BLINK 2.0 
(idea Testato - creazione Leo :-) )

void loop() {  digitalWrite(13, HIGH);    myScheduler.myDelay(1000);  myScheduler.scheduler();  digitalWrite(13, LOW);      myScheduler.myDelay(1000);  myScheduler.scheduler(); }

Funziona ma non va bene ed il myDelay è bloccante per il codice così come il delay. Non è bloccante per i job inseriti nello scheduler. Ma se non usi nessun job nello scheduler, a che ti serve myDelay?

Ti spiego perché il tuo codice non va bene. Questo andrebbe bene:

BLINK 2.0 
(idea Testato - creazione Leo :-) )

void loop() {
  digitalWrite(13, HIGH);  
  myScheduler.myDelay(1000);
  digitalWrite(13, LOW);    
  myScheduler.myDelay(1000);
}

Non so se hai visto il codice di myDelay che ho postato un paio di messaggi sopra per capire come funziona ma in pratica si tratta di un ciclo while che chiama costantemente lo scheduler finché non sono trascorsi i ms passati come parametro. Se il myDelay è l'ultima istruzione del loop, puoi evitare di chiamare lo scheduler perché l'ultima cosa fatta da myDelay è appunto l'invocazione di esso.

Ma a questo punto il codice qui sopra non va bene lo stesso perché nello scheduler non c'è nulla. Che lo fai a fare uno sketch del genere? :P Il codice giusto per avere un Blink non bloccante è invertire le cose e mettere il codice del lampeggio in un job e lasciare tutto il resto nel loop. Ad esempio, questo fa il blink mentre ad intervalli di 1 secondo stampa un valore sulla seriale:

#include "looper.h"
looper myScheduler;

byte led1Status = 0;
const byte LED1 = 13;
int counter = 0;

void setup() {
    pinMode(LED1, OUTPUT);
    Serial.begin(19200);
    myScheduler.addJob(flashLed1, 100);
}

void loop() {
    Serial.println(++counter);
    myScheduler.myDelay(1000);
}

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

Questo è un esempio concreto del delay 2.0, che non ferma l'esecuzione dei job in background ma funziona invece come delay classico per il codice principale.

Se nemmeno lesto ha capito che mydelay è bloccante figurati se lo capivo io.

Ma quindi il delay2.0, mio e basta come diritti di autore, intedo come semplive comando da mettere nel loop, non si può tecnicamente fare ? O è difficile farlo ?

Il myDelay ha un senso se ragioni in termini di RTOS o pseudotali. Gli RTOS sono basati su un multitasking di tipo senza prelazione o cooperativo (cooperativo) o con prelazione (preemptive). Questi ultimi sono quelli che dopo un certo periodo di tempo congelano lo stato di un task indipendentemente dalla volontà di quest'ultimo di essere bloccato. Quelli di tipo cooperativo passano il controllo al SO in modo volontario. In questo modo lo scheduler può passare il controllo della CPU ad un altro task. Sull'Arduino DUE è stato implementato una specie di RTOS cooperativo in cui il codice della delay è stato rivisto ed ora fa un po' ciò che fa myDelay. Durante l'esecuzione del delay la CPU non è bloccata in un ciclo inutile ma salta da un task all'altro.

myDelay cerca di fare il verso a questo comportamento, con tutti i limiti di uno scheduler non gestito da interrupt né capace di salvare lo stato della CPU e dei suoi registri per congelare un task. Semplicemente fintanto che bisogna stare fermi ad aspettare viene comunque eseguito lo scheduler.

Rammentiamoci che delay è un'istruzione valida ed utile in alcuni casi. Se metto un delay significa che voglio bloccare il codice per un certo lasso di tempo. Magari, però, potrei voler bloccare solo una determinata parte di codice. I job, appunto, potrei volerli continuare ad eseguire. Quindi myDelay fa proprio questo: ferma il codice nel punto in cui è stata messa ma permette di continuare a fare altro.

Può risultare utile. Pensa ad esempio all'attesa dell'inizializzazione di qualcosa. Pensa ad esempio, per riallacciarsi al problema della Ethernet shield, al delay(300) che c'è in Ethernet.begin. Se ci metto myDelay(300) la libreria Etherner attende che il W5100 abbia fatto la sua inizializzazione ma nel contempo posso continuare ad eseguire altri compiti.