Skip to the content.

Torna all’indice generazione tempi >Versione in Python

SCHEDULATORE DI COMPITI BASATO SUL POLLING DEL TEMPO CORRENTE

Una categoria di schedulatori di compiti che, a differenza di quelli basati sulla delay(), possiede la peculiare proprietà di non bloccare l’esecuzione degli atri task del loop() si può ottenere realizzando, all’interno del loop(), il polling periodico della funzione millis().

Il polling della funzione millis() serve a stabilire in quale ciclo di loop il tempo corrente assume il valore di tempo limite (timeout) oltre il quale eseguire un certo compito.

E’ buona norma evitare, all’interno dei task, l’esecuzione frequente di operazioni lente quando queste non sono strettamente necessarie, in modo da lasciare spazio ad altre operazioni, quali ad esempio gestione degli eventi di input, che richiedono una velocità elevata per essere usufruite in modo più interattivo.

In particolare, all’interno di un task, andrebbe accuratamente evitata l’introduzione di ritardi prolungati tramite delay(). Possono esserci per breve tempo (qualche mSec) ma, in ogni caso, non dovrebbero mai far parte della maniera (algoritmo) con cui la logica di comando pianifica i suoi eventi.

Utilizzando la tecnica della schedulazione esplicita dei task nel loop(), la realizzazione di un algoritmo non dovrebbe essere pensata in maniera sequenziale (o lineare) ma, piuttosto, come una successione di eventi a cui corrispondono dei compiti (task) che comunicano con le periferiche di input/output ed, eventualmente, dialogano tra loro in qualche modo (ad es. variabili globali, buffer, ecc.).

Momento del campionamento

Il polling (o campionamento periodico) della millis() può essere fatto:

Precisione del timer SW

I timer SW basati sul polling possono essere realizzati in due forme:

Tipi di schedulazione

Una volta stabilito ad ogni loop() il valore del tempo corrente, ci sono molti modi per determininare da esso i tempi dei vari task. Ognuna di queste tecniche, in realtà, è adoperabile a prescindere dal campionamento della millis(), tutte richiedono soltanto che lo schedulatore venga chiamato al tempo usato come riferimento per la generazione di tutti gli altri tempi. Questo tempo è detto tempo base.

Il tempo base è un tempo fisso comune a tutti i task, la loro periodicità deve quindi essere un multiplo intero del tempo base.

Gli schedulatori di questo tipo sono periodici e si possono realizzare sostanzialmente in due modi:

alt text

SCHEDULATORE DI COMPITI BASATO SUL CONTEGGIO DEI TICK

Di seguito è riportato un esempio di schedulatore che pianifica nel tempo l’esecuzione periodica di una serie di compiti (task) da programmare nel futuro con cadenza diversa.

Il tempo base è la base dei tempi di tutte le schedulazioni, viene calcolato periodicamente all’accadere di un evento (superamento di una soglia di tempo) che viene detto tick (analogia con il metronomo per la musica).

La soglia di tempo corrisponde al timeout di un timer SW realizzato all’interno del loop() mediante il polling della funzione millis(). Ad ogni loop(), viene campionato il valore del tempo attuale per vedere quando esso raggiunge un tempo futuro prestabilito. Quando ciò accade viene calcolato il nuovo tick.

Le schedulazioni nel futuro dei diversi task vengono calcolate in corrispondenza di ogni timeout conteggiando un certo multiplo intero del tempo base, ne segue che il tempo base dovrebbe essere calcolato come il massimo comune divisore (MCD) di tutti i tempi futuri che devono essere generati.

Il conteggio dei multipli interi del tempo base è tenuto da un contatore circolare (step) che deve essere ruotato dopo aver effettuato un numero di conteggi pari al minimo comune multiplo di tutti i tempi in gioco, cioè i tempi di attivazione dei task.

Se ci sono pulsanti da gestire insieme ad altri task il tempo base può essere impostato tra 50 e 200 mSec in maniera da poterlo utilizzare per effettuare un polling degli ingressi immune dal fenomeno dei rimbalzi (antibounce SW). Per una descrizione dettagliata dell’uso della millis() per generare i time tick si rimanda a tecniche di schedulazione).

#define tbase  1000  // periodo base in milliseconds
#define nstep  1000  // numero di fasi massimo di un periodo generico
unsigned long precm = 0;
unsigned long step = 0;
byte led1 = 13;
byte led2 = 12;

void setup()
{
	pinMode(led1, OUTPUT);
    	pinMode(led2, OUTPUT);
}

void loop()
{
	// polling della millis() alla ricerca del tempo in cui scade ogni periodo
	unsigned long currTime = millis();
	if(currTime-precm >= (unsigned long) tbase){ 	//se è passato un periodo tbase dal precedente periodo
		precm = currTime;  			//preparo il tic successivo azzerando il conteggio del tempo ad adesso
		step = (step + 1) % nstep; 		// conteggio circolare (arriva al massimo a nstep-1)

		// task 1
		if(!(step%2)){  // schedulo eventi al multiplo del periodo (2 sec = 2 periodi)
			digitalWrite(led1,!digitalRead(led1)); 	// stato alto: led blink
		}
		// task 2
		if(!(step%3)){  // schedulo eventi al multiplo del periodo (3 sec = 3 periodi)
			digitalWrite(led2,!digitalRead(led2)); 	// stato alto: led blink
		}
		// il codice eseguito al tempo del metronomo (esattamente un periodo) va quì
	}
	// il codice eseguito al tempo massimo della CPU va qui
}

Di seguito il link della simulazione online con Tinkercad su Arduino: https://www.tinkercad.com/embed/b7Fj3hCPAwi?editbtn=1

Di seguito il link della simulazione online con Wowki su esp32: https://wokwi.com/projects/348709453878526548

Si noti che:

SCHEDULATORE DI COMPITI BASATO SU TIMER PERIODICI

Uno schedulatore di compiti (task) si può realizzare anche utilizzando più timers basati sul polling della funzione millis().

byte led1 = 13;
byte led2 = 12;
unsigned long period[2];
unsigned long precs[2];

void setup()
{
	pinMode(led1, OUTPUT);
  	pinMode(led2, OUTPUT);
	precs[0]=0;
	precs[1]=0;
	period[0] = 500;
	period[1] = 1000;
}

void loop()
{
	unsigned long current_millis = millis();
	// task 1
	if ((current_millis - precs[0]) >= period[0]) {
		precs[0] += period[0]; 
        	digitalWrite(led1,!digitalRead(led1)); 	// stato alto: led blink
	}	
	// task 2
	if ((current_millis - precs[1]) >= period[1]) {
		precs[1] += period[1]; 
        	digitalWrite(led2,!digitalRead(led2)); 	// stato alto: led blink
	}
	// il codice eseguito al tempo massimo della CPU va qui
}

Si noti che:

Di seguito il link della simulazione online con Tinkercad su Arduino: https://www.tinkercad.com/embed/fcbmLkC10ms?editbtn=1

Di seguito il link della simulazione online con Wowki su esp32: https://wokwi.com/projects/351933794966569551

Recupero dei tick persi in un task lento

In questo caso il ritardo di un task maggiore di un tempo base è compensato. Infatti se dopo un tempo t pari a x multipli di tbase lo scedulatore ricampiona il tempo con la funzione millis(), allora la condizione if ((current_millis - precs[0]) >= period[0]) sarà valutata alla massima velocità del clock e sarà vera per x volte, ciò causerà un rapido recupero di precs[0] fino a che la diferenza non sarà minore di tbase. Da questo momento in poi i tick procederanno senza ritardo fino allo scatto della condizione dei vari task.

Riordinamento dei task

Se il primo task veloce è affetto da ritardi casuali può accadere che questi possono ritardare anche i task che occorrono a seguire nello stesso loop(). Questo accade se più task con periodicità diversa occorrono nello stesso tempo (tick).

Se più task con periodicità diversa occorrono nello stesso tempo (tick), conviene dare priorità maggiore a quelli con periodicità più lunga perchè un eventuale ritardo di un task veloce determinerebbe un errore di tempo che coinvolgerebbe solo il primo task breve a seguire (rimanendo confinato nel tick corrente) e non avrebbe effetto sui tick lenti (di periodicità più grande che agiscono su più tick) dato che questi sarebbero sempre serviti prima. In altre parole, si cerca, in questo modo, di limitare l’effetto di eventuali ritardi di un task sul minor numero possibile di tick consecutivi.

Ricampionamento ad ogni task

Un’alternativa praticabile in questa categoria di schedulatori potrebbe essere ricampionare il tempo corrente sia ad ogni loop() e, all’interno di questo, tra ogni task. Il ricampionamente avrebbe due obiettivi:

Una soluzione basata sul ricampionamento tra un task e l’altro potrebbe farsi così:

byte led1 = 13;
byte led2 = 12;
unsigned long period[2];
unsigned long precs[2];
unsigned long prevMillis = 0;

void setup()
{
	randomSeed(analogRead(0));
	Serial.begin(115200); 
	pinMode(led1, OUTPUT);
  	pinMode(led2, OUTPUT);
	precs[0]=0;
	precs[1]=0;
	period[0] = 500;
	period[1] = 2000;
}

void loop()
{
	// task 1
	unsigned long current_millis = millis();
	if ((current_millis - precs[0]) >= period[0]) {
		precs[0] = current_millis; 
		unsigned randomDelay = random(1, 200);
		Serial.print("delay: ");Serial.println(randomDelay);
		delay(randomDelay);
    		digitalWrite(led1,!digitalRead(led1)); 	// stato alto: led blink
	}	
	// task 2
	current_millis = millis();
	if ((current_millis - precs[1]) >= period[1]) {
		precs[1] =  current_millis; 
      		unsigned long now = millis();
		unsigned long diff = now-prevMillis;
		//diff = diff%50;
		Serial.print("ontwosec: ");Serial.println(diff);
		digitalWrite(led2,!digitalRead(led2)); 	// stato alto: led blink
		prevMillis = now;
	}
	// il codice eseguito al tempo massimo della CPU va qui
	delay(1);
}

Di seguito il link della simulazione online con Wowki su esp32: https://wokwi.com/projects/352929647651521537

SCHEDULATORE DI COMPITI BASATO SU FILTRAGGIO DEL TEMPO BASE

E’ possibile realizzare uno schedulatore di più task che agiscono con periodicità diverse in tempi diversi a partire da un timer che esegue periodicamente un task comune si sincronizzazione che agisce su un tempo base comune sottomultiplo del tempo di tutti gli altri task. In altre parole, il periodo di ciascun task è un multiplo intero del periodo base di sincronizzazione comune:

byte led1 = 13;
byte led2 = 12;
unsigned long period[2];
unsigned long precs[2];
unsigned long precm;
unsigned long tbase;

void setup()
{
	pinMode(led1, OUTPUT);
  	pinMode(led2, OUTPUT);
	period[0] = 500;
	period[1] = 1000;
	precm = 0;
	tbase = 500;
	// inizializzazione dei task
	for(int i=0; i<2; i++){
		precs[i] = precm -period[i];
	}
}

void loop()
{
	if(millis()-precm >= tbase){ 	
		precm += tbase;  			
		// task 1
		if ((precm - precs[0]) >= period[0]) {
			precs[0] += period[0]; 
			digitalWrite(led1,!digitalRead(led1)); 	// stato alto: led blink
		}	
		// task 2
		if ((precm - precs[1]) >= period[1]) {
			precs[1] += period[1]; 
			digitalWrite(led2,!digitalRead(led2)); 	// stato alto: led blink
		}
	}
	// il codice eseguito al tempo massimo della CPU va qui
	delay(1);
}

Di seguito il link della simulazione online con Wowki su esp32: https://wokwi.com/projects/352691213474403329

Tempo assoluto

La generazione del tempo assoluto di riferimento medianteprecm += tbase è basata su considerazioni riportate in Generazione di tempi assoluti

Inizializzazione dei task

Per ottenere la partenza sincronizzata di tutti i task al primo tick del sistema bisogna prima inizializzare il tempo trascorso (precs[i]) di ogni task al valore del suo intervallo di esecuzione (period[i]):

//Inizializzazione dei task
for(int i=0; i<2; i++){
	precs[i] = precm -period[i];
}

Recupero dei tick persi in un task lento

In questo caso il ritardo di un task maggiore di un tempo base è compensato. Infatti se dopo un tempo t pari a x multipli di tbase lo scedulatore ricampiona il tempo con la funzione millis(), allora la condizione if(millis()-precm >= tbase sarà valutata alla massima velocità del clock e sarà vera per x volte, ciò causerà un rapido recupero di precm fino a che la diferenza non sarà minore di tbase. Da questo momento in poi i tick procederanno senza ritardo fino allo scatto della condizione dei vari task.

Riordinamento dei task

Se il primo task veloce è affetto da ritardi casuali può accadere che questi possono ritardare anche i task che occorrono a seguire nello stesso loop(). Questo accade se più task con periodicità diversa occorrono nello stesso tempo (tick).

Se più task con periodicità diversa occorrono nello stesso tempo (tick), conviene dare priorità maggiore a quelli con periodicità più lunga perchè un eventuale ritardo di un task veloce determinerebbe un errore di tempo che coinvolgerebbe solo il primo task breve a seguire (rimanendo confinato nel tick corrente) e non avrebbe effetto sui tick lenti (di periodicità più grande che agiscono su più tick) dato che questi sarebbero sempre serviti prima. In altre parole, si cerca, in questo modo, di limitare l’effetto di eventuali ritardi di un task sul minor numero possibile di tick consecutivi.

In questo caso non è possibile ricampionare i task in maniera indipendente l’uno dall’altro perchè quelli con tempo uguale devono avvenire nello stesso tick.

Una soluzione in base a quanto descritto sopra potrebbe essere:

byte led1 = 13;
byte led2 = 12;
unsigned long period[2];
unsigned long precs[2];
unsigned long precm;
unsigned long tbase;
unsigned long prevMillis = 0;

void setup()
{
	randomSeed(analogRead(0));
	Serial.begin(115200); 
	pinMode(led1, OUTPUT);
	pinMode(led2, OUTPUT);
	period[0] = 500;
	period[1] = 2000;
	precm = 0;
	tbase = 50;
	//Inizializzazione dei task
	for(int i=0; i<2; i++){
		precs[i] = precm -period[i];
	}
}

void loop()
{
	unsigned  long current_millis = millis();
	if(current_millis-precm >= tbase){ 	
		precm += tbase;  		

		// task 2
		if ((precm - precs[1]) >= period[1]) {
			precs[1] +=  period[1]; 
			unsigned long now = millis();
			unsigned long diff = now-prevMillis;
			//diff = diff%50;
			Serial.print("ontwosec: ");Serial.println(diff);
			digitalWrite(led2,!digitalRead(led2)); 	// stato alto: led blink
			prevMillis = now;
		}	
		// task 1
		if ((precm - precs[0]) >= period[0]) {
			precs[0] +=  period[0]; 
			unsigned randomDelay = random(1, 200);
			Serial.print("delay: ");Serial.println(randomDelay);
			delay(randomDelay);
			digitalWrite(led1,!digitalRead(led1)); 	// stato alto: led blink
		}	
	}
	// il codice eseguito al tempo massimo della CPU va qui
	delay(1);
}

Di seguito il link della simulazione online con Wowki su esp32: https://wokwi.com/projects/353186425425809409

SCHEDULATORE DI COMPITI SU BASE DEI TEMPI GENERICA

E’ possibile realizzare uno schedulatore di task che non necessita di alcuna funzione di misura del tempo (delay() o millis()). L’unico prerequisito è che avvenga l’invocazione periodica di una funzione ad un certo tempo base comune a tutti i task, calcolato col massimo comune divisore (M.C.D. o G.C.D) di tutti i tempi dei singoli task. Un contatore di tempi base determina, per ogni task, il momento buono in cui questo deve essere eseguito resettando il proprio contatore subito dopo.

La base dei tempi comune può essere realizzata mediante qualunque tecnica di generazione di tempi periodici, ma la particolarità dell’assenza di misure di tempo corrente (millis()) rendono la realizzazione particolarmente adatta per essere adoperata su una base dei tempi generata mediante interrupt.

Si ricorda che in C e in C++ le variabili globali a comune tra ISR e loop() principale e suscettibili di modifiche da parte di entrambi andrebbero dichiarate tutte con il qualificatore volatile.

La versione originale più completa dello schedulatore insieme ad una dettagliata discussione teorica si trova in: https://www.ics.uci.edu/~givargis/pubs/C50.pdf e in https://www.cs.ucr.edu/~vahid/rios/.

#include <Ticker.h>

Ticker periodicTicker1;
byte led1 = 13;
byte led2 = 12;
unsigned long period[2];
volatile unsigned long elapsedTime[2];
volatile bool processingRdyTasks;
unsigned long tbase;

void setup()
{
	pinMode(led1, OUTPUT);
	pinMode(led2, OUTPUT);
	Serial.begin(115200); 
	periodicTicker1.attach_ms(500, scheduleAll);
	elapsedTime[0] = 0;
	elapsedTime[1] = 0;
	period[0] = 500;
	period[1] = 1000;
	tbase = 500;
	// task time init
	processingRdyTasks = false;
	for(int i=0; i<2; i++){
		elapsedTime[i] = period[i];
	}
}

void scheduleAll(){
	if(processingRdyTasks){ 	
		Serial.println("Timer ticked before task processing done");
	}else{  	
		processingRdyTasks = true;		
		// task 1
		if (elapsedTime[0] >= period[0]) {
			digitalWrite(led1,!digitalRead(led1)); 	// stato alto: led blink
			elapsedTime[0] = 0;
		}
		elapsedTime[0] += tbase;
		// task 2
		if (elapsedTime[1] >= period[1]) {
			digitalWrite(led2,!digitalRead(led2)); 	// stato alto: led blink
			elapsedTime[1] = 0;
		}
		elapsedTime[1] += tbase;
		processingRdyTasks = false;
	}
}

void loop()
{
	delay(10);
	// il codice eseguito al tempo massimo della CPU va qui
}

Il flag processingRdyTasks servirebbe ad evitare l’interruzione della ISR sopra un’altra ISR dovuta ad un eventuale ritardo di completamento di un task precedente. Questa circostanza nei microcontrollori come Arduino o ESP32 in genere non accade perchè le interruzioni annidate sono di base (default) disabilitate.

Anche in questo caso non è possibile ricampionare i task in maniera indipendente l’uno dall’altro perchè quelli con tempo uguale devono avvenire nello stesso tick.

Inizializzazione dei task

Per ottenere la partenza sincronizzata di tutti i task al primo tick del sistema bisogna prima inizializzare il tempo trascorso (elapsed) di ogni task al valore del suo intervallo di esecuzione (periodo):

for(int i=0; i<2; i++){
	elapsedTime[i] = period[i];
}

Di seguito il link della simulazione online con Wowki su esp32: https://wokwi.com/projects/352766239477208065

Recupero dei tick persi in un task lento

In questo caso il ritardo di un task maggiore di un tempo base non potrebbe essere compensato dato che da un lato la funzione scheduleAll() non è interrompibile per cui ritardo di un task ritarda anche il task successivo, dall’altro elapsedTime[0] += tbase viene incrementata ad ogni tick sempre una sola volta. Due scenari possibili:

Nello ESP32 sembra che sia effettivo il secondo scenario per cui il repero dei tick persi in un task lento è sempre possibile.

Riordinamento dei task

Se il primo task veloce è affetto da ritardi casuali può accadere che questi possono ritardare anche i task che occorrono a seguire nello stesso loop(). Questo accade se più task con periodicità diversa occorrono nello stesso tempo (tick).

Se più task con periodicità diversa occorrono nello stesso tempo (tick), conviene dare priorità maggiore a quelli con periodicità più lunga perchè un eventuale ritardo di un task veloce determinerebbe un errore di tempo che coinvolgerebbe solo il primo task breve a seguire (rimanendo confinato nel tick corrente) e non avrebbe effetto sui tick lenti (di periodicità più grande che agiscono su più tick) dato che questi sarebbero sempre serviti prima. In altre parole, si cerca, in questo modo, di limitare l’effetto di eventuali ritardi di un task sul minor numero possibile di tick consecutivi.

Una soluzione parziale in base a quanto descritto sopra potrebbe essere:

byte led1 = 13;
byte led2 = 12;
unsigned long period[2];
unsigned long precs[2];
unsigned long elapsedTime[2];
unsigned long precm;
unsigned long precm2;
unsigned long tbase, k;
unsigned long prevMillis = 0;
bool shift = false;

void setup()
{
	randomSeed(analogRead(0));
	Serial.begin(115200); 
	pinMode(led1, OUTPUT);
  	pinMode(led2, OUTPUT);
	precs[0] = 0;
	precs[1] = 0;
	period[0] = 500;
	period[1] = 2000;
	elapsedTime[0] = 0;
	elapsedTime[1] = 0;
	precm = 0;
	precm2 = 0;
	tbase = 50;
}

void loop()
{
	unsigned long current_millis = millis();
	if(current_millis - precm >= tbase){ 		
		precm += tbase;
		// task 2
		if (elapsedTime[1] >= period[1]) {
			unsigned long now = millis();
			unsigned long diff = now-prevMillis;
			//diff = diff%50;
			Serial.print("ontwosec: ");Serial.println(diff);
			digitalWrite(led2,!digitalRead(led2)); 	// stato alto: led blink
			prevMillis = now;
			elapsedTime[1] = 0;
		}
		elapsedTime[1] += tbase;
		// task 1
		if (elapsedTime[0] >= period[0]) {
			unsigned randomDelay = random(1, 200);
			Serial.print("delay: ");Serial.println(randomDelay);
			delay(randomDelay);
			digitalWrite(led1,!digitalRead(led1)); 	// stato alto: led blink
			elapsedTime[0] = 0;
			shift = true;
		}	
		elapsedTime[0] += tbase;		
	}
	// il codice eseguito al tempo massimo della CPU va qui
	delay(1);
}

Per compensare i ritardi di un task, potrebbe essere sfruttata la proprietà di prerilascio forzato di un task che possiedono gli interrupt. Se i tick sono interrupt based allora essi accadono sempre e comunque nel tempo esatto a loro deputato anche in presenza di un task che ritarda la sua esecuzione. Questo perchè un tick che occorresse ripetutamente su uno stesso task molto lento causa comunque la sua interruzione e l’esecuzione di una ISR che, pur non potendo servire un nuovo task, può far partire un algoritmo che compensi il conteggio dei tick dei task successivi a quello che si prolunga troppo in maniera da farli accadere al tempo giusto.

Anche se task di tick successivi non si interferiscono, rimane sempre l’influenza che un task lento a terminarsi può avere sui task del suo stesso tick che potrebbe essere ridotta grazie all’espediente del riordinamento per tempi decrescenti dei task.

Di seguito il link della simulazione online con Wowki su esp32: https://wokwi.com/projects/353034389606720513

#include <Ticker.h>
// Inspired from https://www.cs.ucr.edu/~vahid/rios/
Ticker periodicTicker1;
int led1 = 13;
int led2 = 12;
int pulsante=27;
byte precval;
byte stato= LOW;
unsigned long period[2];
unsigned long elapsedTime[2];
void (*tickFct[2])(void); 
volatile bool timerFlag;
unsigned long tbase;
uint8_t tasknum = 2;
unsigned long prevMillis = 0;

void periodicBlink500() {
  	unsigned randomDelay = random(1, 200);
	Serial.print("delay: ");Serial.println(randomDelay);
	delay(randomDelay);
	digitalWrite(led1,!digitalRead(led1)); 	// stato alto: led blink
}

void periodicBlink2000(){
	unsigned long now = millis();
	unsigned long diff = now-prevMillis;
	//diff = diff%50;
	Serial.print("ontwosec: ");Serial.println(diff);
	digitalWrite(led2,!digitalRead(led2)); 	// stato alto: led blink
	prevMillis = now;
}

void setup(){
	randomSeed(analogRead(0));
	pinMode(led1, OUTPUT);
	pinMode(led2, OUTPUT);
	Serial.begin(115200); 
	periodicTicker1.attach_ms(50, timerISR);
	elapsedTime[0] = 0;
	elapsedTime[1] = 0;
	period[0] = 2000;
	period[1] = 500;
	tickFct[0] = periodicBlink2000;
	tickFct[1] = periodicBlink500;
	tbase = 50;
	// task time init
	timerFlag = false;
	for(int i=0; i<tasknum; i++){
		elapsedTime[i] = period[i];
	}
}

void timerISR(void) {
   if (timerFlag) {
	//Serial.println("Timer ticked before task processing done");
	for(int i=0; i<tasknum; i++){
		elapsedTime[i] += tbase;
	}
   }
   else {
      timerFlag = true;
   }
   return;
}

void scheduleAll(){		
	for(int i=0; i<tasknum; i++){
		if(elapsedTime[i] >= period[i]) {
			(*tickFct[i])();
			//Serial.print("Ticked: ");Serial.println(elapsedTime[i]);
			elapsedTime[i] = 0;
		}
		elapsedTime[i] += tbase;
	}
}

void loop()
{
	if(timerFlag){
		scheduleAll();
		timerFlag = false;
	}
	delay(1);
	// il codice eseguito al tempo massimo della CPU va qui
}

Il flag timerFlag serve a:

La versione originale più completa dello schedulatore insieme ad una dettagliata discussione teorica si trova in: https://www.ics.uci.edu/~givargis/pubs/C50.pdf e in https://www.cs.ucr.edu/~vahid/rios/.

Di seguito il link della simulazione online con Wowki su esp32: https://wokwi.com/projects/352976196236642305

Versione alternativa di schedulatore generico

Basato su https://github.com/marcelloromani/Arduino-SimpleTimer/tree/master/SimpleTimer e su Generazione di tempi assoluti

#include <Ticker.h>

Ticker periodicTicker1;
byte led1 = 13;
byte led2 = 12;
volatile unsigned long  precm = 0;
unsigned long  tbase1 = 500;
volatile unsigned long precs[]= {0, 0};
unsigned long period1[] = {1500, 6000};
int leds1[] = {led1, led2};

void periodicBlink(int led) {
  digitalWrite(led, !digitalRead(led));
}

void scheduleAll(int *leds){
	precm += tbase1;
	// task 1
	if ((precm - precs[0]) >= period1[0]) {
		precs[0] += period1[0]; 
    		periodicBlink(leds[0]);
	}	
	// task 2
	if ((precm - precs[1]) >= period1[1]) {
		precs[1] += period1[1]; 
    		periodicBlink(leds[1]);
	}
}

void setup(){
	pinMode(led1, OUTPUT);
	pinMode(led2, OUTPUT);
	Serial.begin(115200); 
	periodicTicker1.attach_ms(500, scheduleAll, leds1);
	// task time init
	for(int i=0; i<2; i++){
		precs[i] = precm -period1[i];
	}
}

void loop()
{
	delay(10);
	// il codice eseguito al tempo massimo della CPU va qui
}

Sui dettagli relativi alla generazione del tempo assoluto, sul recupero dei tick persi in un task lento, sul riordinamento dei task valgono le stesse considerazioni già fatte sopra per lo

Simulazione su Arduino con Wowki: https://wokwi.com/projects/371852832413769729

Schedulatore generico realizzato con funzione get()

Schedulatore realizzato utilizzando una variante DiffTimer2 del timer della libreria urutils.h in cui, ad ogni chiamata della funzione get(), viene incrementato il tempo corrente di ciascun timer (elapsed) di una quantità fissa pari al tempo base. Il momento dell’incremento è contestuale a quello della chiamata a get() che, quindi, deve avvenire esattamente ogni tempo base. Il tempo base è il M.C.D. dei tempi in gioco nei vari task.

Il tempo base viene generato utilizzando il timer DiffTimer1 che realizza, mediante il polling della sua funzione get() il polling della funzione millis() che restituisce il tempo corrente del sistema.

/*
Alla pressione del pulsante si attiva o disattiva il lampeggo di un led, mentre un
altro led lampeggia indisturbato.
*/
#include "urutils.h"
int led = 13;
int led2 = 14;
byte pulsante =12;
byte precval, val;
unsigned long tbase = 50;
DiffTimer tmrdeb;
DiffTimer2 tmrblink1, tmrblink2;
 
void setup() {
  Serial.begin(115200);
  pinMode(led, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(pulsante, INPUT);
  precval=LOW;
  tmrdeb.start();
  tmrblink2.start();
}

// loop principale
void loop() {
  if(tmrdeb.get() > tbase){  	
    tmrdeb.reset();   
    //task_toggle
    val = digitalRead(pulsante);		
    if(precval==LOW && val==HIGH){ 		//rivelatore di fronte di salita
      tmrblink1.toggle();		
    }
    precval=val;	
    //task_blink1
    if (tmrblink1.get(tbase) > 500) {
      digitalWrite(led, !digitalRead(led));
      tmrblink1.reset();
    } 
    //task_blink2
    if (tmrblink2.get(tbase) > 1000) {
      digitalWrite(led2, !digitalRead(led2));
      tmrblink2.reset();
    } 
  }
  delay(10);
}

Di seguito il link della simulazione online con ESP32 su Wokwi: https://wokwi.com/projects/388359604541585409

ESEMPI

/*
Realizzzare un programma che fa blinkare un led per 5 sec poi lo fa stare sempre spento per un altri 5 sec, 
poi lo fa blinkare di nuovo per altri 5 sec e così via.
*/
#define tbase  500 // periodo base in milliseconds
#define nstep  1000  // numero di fasi massimo di un periodo generico
unsigned long precm = 0;
unsigned long step = 0;
byte led1 = 12;
bool stato;

void setup()
{
	pinMode(led1, OUTPUT);
	stato = true;
}

void loop()
{
	// polling della millis() alla ricerca del tempo in cui scade ogni periodo
	if((millis()-precm) >= (unsigned long) tbase){ 	//se è passato un periodo tbase dal precedente periodo
		precm = millis();  		//preparo il tic successivo azzerando il conteggio del tempo ad adesso
		
		step = (step + 1) % nstep; 	// conteggio circolare (arriva al massimo a nstep-1)

		// task 1
		if(!(step%1)){  // schedulo eventi al multiplo del periodo (2 sec = 2 periodi)
			if(stato)
				digitalWrite(led1,!d.igitalRead(led1)); 	// stato alto: led blink
			else
				digitalWrite(led1,LOW);
		}
		// task 2
		if(!(step%10)){  // schedulo eventi al multiplo del periodo (3 sec = 3 periodi)
			stato = !stato;
		}
		// il codice eseguito al tempo del metronomo (esattamente un periodo) va quì
	}
	// il codice eseguito al tempo massimo della CPU va qui
}

Di seguito il link della simulazione online con Tinkercad su Arduino: https://www.tinkercad.com/embed/0vP4WlJGycZ?editbtn=1

Una libreria molto precisa basata su timer assoluti è https://github.com/marcelloromani/Arduino-SimpleTimer/tree/master/SimpleTimer.

L’esempio di seguito è basato su una libreria esterna che realizza uno schedulatore ad eventi basato su time ticks (https://github.com/sebastianomelita/time-tick-scheduler).

I blink sono comandati dallo schedulatore esterno ma sono abilitati e disabilitati valutando una variabile di conteggio ausiliaria nella funzione epochScheduler() che realizza in pratica un timer che attiva o disattiva gli eventi periodici dello schedulatore.

#include "Scheduler2.h"
int led1 = 13;
int led2 = 12;
int count = 0;

Scheduler scheduler;

void onMaxSpeedEvents();
void onHalfSecondEvents();
void onSecondEvents();
void epochScheduler();
 
void periodicBlink(int led) {
  Serial.print("printing periodic blink led ");
  Serial.println(led);

  digitalWrite(led, !digitalRead(led));
}

void epochScheduler(){
	if(count < 10){
		Serial.print("Doing stuff... ");
		Serial.println(count);
	}else if(count == 10){
		Serial.println("Disable all... ");
		scheduler.disableEvent(1,1000);
		scheduler.disableEvent(1,500);
		digitalWrite(led1, LOW);
		digitalWrite(led2, LOW);
		Serial.println("Ending timers...");
	}else if(count < 20){
		Serial.print("Frized... ");
		Serial.println(count);
	}else if(count >= 20){
		Serial.print("Enable all... ");
		Serial.println(count);
		scheduler.enableEvent(1,1000);
		scheduler.enableEvent(1,500);
		Serial.println("Starting timers...");
		count = -1;
	}
  count++;
}

void onHalfSecondEvents(){
	periodicBlink(led1);
}
void onSecondEvents(){
	periodicBlink(led2);
}
 
void setup() {
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  Serial.begin(115200); 
	Serial.println(F("Scheduler init"));
	Serial.print(F("Time base: "));
	Serial.println(scheduler.getTimebase());
	Serial.print(F("Nsteps: "));
	Serial.println(scheduler.getNsteps());
	//scheduler.addEvent(onMaxSpeedEvents, 1, ON0MSEC);
	scheduler.addPeriodicEvent(onHalfSecondEvents, 1, 500);
	scheduler.addPeriodicEvent(onSecondEvents, 1, 1000);
	scheduler.addPeriodicEvent(epochScheduler, 2, 1000);
}
 
void loop() {
	scheduler.scheduleAll();
	delay(10);
}

Di seguito il link della simulazione online con Tinkercad su Arduino: https://wokwi.com/projects/351319080732459608

Stesso esempio precedente in cui tutti gli eventi periodici sono realizzati con lo schedulatore fornito dalla libreria, compresi gli eventi che abilitano e disabilitano parte delle funzioni di schedulazione tramite i comandi enableEvent() e disableEvent().

#include "Scheduler2.h"
int led1 = 13;
int led2 = 12;

Scheduler scheduler;
int count = 0;

void onMaxSpeedEvents();
void onHalfSecondEvents();
void onSecondEvents();
void epochScheduler();
void epoch10sec();
void epoch20sec();
 
void periodicBlink(int led) {
  Serial.print("printing periodic blink led ");
  Serial.println(led);

  digitalWrite(led, !digitalRead(led));
}

void epoch10secDisable(){
	scheduler.disableEvent(1,1000);
	scheduler.disableEvent(1,500);
	scheduler.enableEvent(1,10000);
	scheduler.disableEvent(2,10000);
	digitalWrite(led1, LOW);
	digitalWrite(led2, LOW);
	Serial.println("Ending timers...");
	Serial.print("Frized... ");
}

void epoch10secEnable(){
	Serial.print("Enable all... ");
	Serial.println(count);
	scheduler.enableEvent(1,1000);
	scheduler.enableEvent(1,500);
	scheduler.disableEvent(1,10000);
	scheduler.enableEvent(2,10000);
	Serial.println("Starting timers...");
}

void onHalfSecondEvents(){
	periodicBlink(led1);
}
void onSecondEvents(){
	periodicBlink(led2);
}
 
void setup() {
	pinMode(led1, OUTPUT);
	pinMode(led2, OUTPUT);
	Serial.begin(115200); 
	//scheduler.addEvent(onMaxSpeedEvents, 1, 0);
	scheduler.addPeriodicEvent(onHalfSecondEvents, 1, 500);
	scheduler.addPeriodicEvent(epoch10secEnable, 1, 10000, false);
	scheduler.addPeriodicEvent(epoch10secDisable, 2, 10000);
	scheduler.addPeriodicEvent(onSecondEvents, 1, 1000);
	Serial.println(F("Scheduler init"));
	Serial.print(F("Time base: "));
	Serial.println(scheduler.getTimebase());
	//scheduler.addPeriodicEvent(onSecondEvents, 1, 0);
	//scheduler.deletePeriodicEvent(1, 0);
}
 
void loop() {
	scheduler.scheduleAll();
	delay(10);
}

Di seguito il link della simulazione online con Tinkercad su Arduino: https://wokwi.com/projects/352057010320512001

In questo esempio si utilizza un unico timer HW come base dei tempi per uno schedulatore SW che gestisce la tempistica di due task:

Le operazioni benchè semplici vengono considerate come prototipi di task più complessi e magari soggetti a ritardi considerevoli. In questa circostanza la loro esecuzione all’interno di una ISR è sconsigliata per cui essi vengono eseguiti nel loop() principale su segnalazione di un flag asserito dentro la ISR del timer.

#include <Ticker.h>
// Inspired from https://www.cs.ucr.edu/~vahid/rios/
Ticker periodicTicker1;
int led1 = 13;
int led2 = 12;
int pulsante=27;
byte precval;
byte stato= LOW;
unsigned long period[2];
unsigned long elapsedTime[2];
void (*tickFct[2])(void); 
volatile bool timerFlag;
unsigned long tbase;
uint8_t tasknum = 2;

void periodicBlink500() {
  digitalWrite(led1, !digitalRead(led1));
}

void on50msEvents(){
	byte val = digitalRead(pulsante);		//pulsante collegato in pulldown
	//val = digitalRead(!pulsante);	//pulsante collegato in pullup
	if(precval==LOW && val==HIGH){ 	//rivelatore di fronte di salita
		stato = !stato; 							//impostazione dello stato del toggle	
		//Serial.println(stato);
		digitalWrite(led2, stato);
	}
	precval=val;	
}

void setup(){
	pinMode(led1, OUTPUT);
	pinMode(led2, OUTPUT);
	Serial.begin(115200); 
	periodicTicker1.attach_ms(50, timerISR);
	elapsedTime[0] = 0;
	elapsedTime[1] = 0;
	period[0] = 50;
	period[1] = 500;
	tickFct[0] = on50msEvents;
	tickFct[1] = periodicBlink500;
	tbase = 50;
	// task time init
	timerFlag = false;
	for(int i=0; i<tasknum; i++){
		elapsedTime[i] = period[i];
	}
}

void timerISR(void) {
   if (timerFlag) {
      Serial.println("Timer ticked before task processing done");
   }
   else {
      timerFlag = true;
   }
   return;
}

void scheduleAll(){		
	for(int i=0; i<tasknum; i++){
		if(elapsedTime[i] >= period[i]) {
			(*tickFct[i])();
			elapsedTime[i] = 0;
		}
		elapsedTime[i] += tbase;
	}
}

void loop()
{
	if(timerFlag){
		scheduleAll();
		timerFlag = false;
	}
	delay(1);
	// il codice eseguito al tempo massimo della CPU va qui
}

Di seguito il link della simulazione online con ESP32 su Wokwi: https://wokwi.com/projects/352790112505422849

Sitografia:

Torna all’indice generazione tempi>Versione in Python