multithread

Buonasera a tutti. Ho creato un prototipo con Arduino uno + GSM Shield. Il prototipo esegue nel LOOP la lettura di eventuali messaggi in arrivo e calcola la temperatura della stanza. La procedura di calcolo della temperatura mi permette di impostare la temperatura desiderata nella stanza, la confronta con la rilevata e decide se accendere o spegnere il riscaldamento. La regolazione della temperatura la faccio mediante trimmer. Ho la necessità di leggere immediatamente il nuovo valore del trimmer senza aspettare il ciclo che dura qualche secondo. Non posso usare interrupt in quanto funzionano solo sulle porte digitali e inoltre il pin 2 e 3 è già occupato dalla gsm shield. mi chiedevo se c'era modo di fare due thread in modo da leggere il trimmer con il primo e calcolare la temperatura con il secondo. Così facendo l' aggiornamento del valore del trimmer dovrebbe essere simultaneo con il calcolo della temperatura. Stavo valutando di collegare un arduino nano che mi faccia parte dell'operazione, per esempio il calcolo della temperatura.

Prima di tutto, nella sezione in lingua Inglese si può scrivere SOLO in Inglese ... quindi, per favore, la prossima volta presta più attenzione, poi, essendo questo il tuo primo post, ti chiedo cortesemente di presentarti QUI (spiegando bene quali conoscenze hai di elettronica e di programmazione) e di leggere con attenzione il REGOLAMENTO ... Grazie.

Guglielmo

NON sei su un PC dove c'è un sistema operativo, ci sono più "core", c'è un multitasking e una gestione multithread ... qui sei su una piccola MCU dove devi fare tutto tu da codice.

Si trovano delle librerie che aiutano a fare degli scheduler, ma in ogni caso quando gira una cosa NON ne gira un altra. Esempi sono la "looper" e la "leOS2" ...

Guglielmo

Ciao, come @gpb01 ti ha fatto notare, Arduino è un Microcontroller Single-Core che non può eseguire multitasking/multithreading.

Hai due possibili "soluzioni":

  • Usare un multitasking "fittizio", ovvero sia creare dei "loop 2,3,4..." che si alternino tra loro durante i richiami dei delay in maniera talmente rapida da dare l'impressione di lavorare in parallelo (in realtà si sta solo passando da una funzione ad un'altra in un tempo rapidissimo).
    Sul forum (usa la funzione cerca!) ci sono moltissimi thread sull'argomento (specialmente perchè molte persone sopravvalutano il potenziale di questa piattaforma, o lo interpretano male).

  • rtOS, ovvero "Real Time Operative System"; si tratta di cambiamenti radicali che vengono apportati allo stile di scrittura poichè coinvolgono funzioni particolari che permettono una gestione ancora migliore dello "scheduler", cioè colui che si occupa di "smistare" le funzioni del tuo programma. E' come la prima soluzione, ma migliore.

@c0rsa1r: NON esiste una versione di "rtOS" per Arduino UNO ... esistono quelle librerie che ho indicato nel mio post ...

Ricorda che sei su una piccola MCU a 8 bit che ha solo 32KB di flash e 2KB di SRAM ... un po' pochini ... :wink:

Guglielmo

@gbp01 ti sei dimenticato del porting scheduler ufficiale! funziona su tutte le schede Arduino con Arm o Avr

State dando suggerimenti per alevare i sintomi ma non la causa.
Il problema lo vedo nel loop() che dura qualche secondo.

Per primo é strano che un ciclo duri cosí a lungo (eccetto un uso di delay())
per secondo non vedo la problematica se una temperatura e il valore di riferimento vengano letti non frequentemente.

stefano_8805 facci cedere lo sketch.

Ciao Uwe

vbextreme:
@gbp01 ti sei dimenticato del porting scheduler ufficiale! funziona su tutte le schede Arduino con Arm o Avr

Si, vero, in ogni caso è un'altro affare come leOS2, leOS, looper, ecc ... uno scheduler ... e non un RTOS multithread :grin:

Guglielmo

@gbp01 no! veramente il mio è l'unico vero scheduler tra quelli proposti!
Gli altri emulano lo scheduler, il mio ha un vero context switch che tocca punte di 11us per il cambio di task. Sto cercando di aggiornalo per renderlo ancora piu veloce.
Quindi con il mio hai un vero multitask! guardati gli esempi, penso troverai interessante il ServoWithoutTimer....

@gbp01, non volevo dirtelo ma lo scheduler è il kernel e un RTOS è un kernel :wink:

@VB ... so benissimo cosa è un kernel :slight_smile: ... qui si parlava di capacità di multi-thread e multi-tasking ...
... non ho esaminato a fondo il tuo ... appena ho un po' d tempo ci do un'occhiata ...

Guglielmo

Buongiorno a tutti e grazie per gli spunti. Mi ero già informato su google su eventuali soluzioni da poter adottare. Ho intitolato il post impropriamente multithread in quanto ero già a conoscenza che arduino non è un multi core di conseguenza non è possibile gestire più processi contemporaneamente, motivo per cui ho ipotizzato di aggiungere un arduino nano per dividere le operazioni.
Il problema che ho è dovuto al fatto che quando "giro" il trimmer e quindi cambia la temperatura da raggiungere, l'aggiornamento del nuovo valore sul display ci mette qualche secondo a visualizzarsi, invece mi piacerebbe fosse immediato. Verificando con la funzione millis() i tempi noto che il ciclo di calcolo temperatura e lettura trimmer è un po' lento (qualche secondo). L'unico delay presente dura 1000ms che mi server per dare tempo al sensore di temperature di leggere 100 volte la temperatura per poi mediarla e aumentare la precisione. Se preferite vi posto lo sketch completo.

Facci vedere lo sketch.
Ciao Uwe

ecco qui lo sketch completo. Allego anche il file.

// includo le librerie
#include <GSM.h>
#include <EEPROMex.h>
#include <LiquidCrystal.h>
//Inizializzo i pin dell' LCD
LiquidCrystal lcd(4,5,17,16,15,14);
//Dichiaro le costanti dei PIN\
const	int	GateReleControlPin	=	9;
const	int	RiscReleControlPin	=	10;
const	int	LCDLightPin			=	6; 
const	int	LCDOnOffPin			=	8;
const	int	RegTempManualPin	=	11;

const	int TempSensorPin	=	A4;
const	int TempRegPin		=	A5;
//dichiaro le variabili globali
String		Riscaldamento	=	"";
float		temperature;
float		TempMem,TempNew		=	0;

char		_bufferTempImp[5]	=	"";
char		_bufferTempRil[5]	=	"";
char		_bufferTempNew[5]	=	"";
//Stringa di memorizzazione SMS in ingresso
String message	=	"";
//Stringhe di memorizzazione parametri SMS
String	commandID,	deviceID,	param;
//Array di memorizzazione del numero di telefono
char	senderNumber[20]; 

// initialize the library instances
GSM		gsmAccess;
GSM_SMS	sms;

void setup() 
{
	//Inizializzo LCD Vcc PIN
	pinMode(LCDLightPin, OUTPUT);
	digitalWrite(LCDLightPin,HIGH);
	lcd.begin(16,2);
	//Inizializzo GSM Shield
	GSMinitialization();
	//inizializzo la seriale
	Serial.begin(9600);
	writeLCD("Inizializzazione","PIN",1000);
	// inizilizzo i pin
	pinMode(GateReleControlPin, OUTPUT);
	pinMode(RiscReleControlPin, OUTPUT);
	pinMode(LCDOnOffPin, INPUT);
	pinMode(RegTempManualPin, INPUT);
	digitalWrite(GateReleControlPin,HIGH);
	digitalWrite(RiscReleControlPin,HIGH);
	//Attivo confronto con AREF in modo da migliorare 
	analogReference(EXTERNAL);
	//Leggo la temperatura memorizzata in EEPROM	
	TempMem	=	EEPROM.readDouble(0);	
}

void loop() 
{
	//Verifico se ci sono messaggi da leggere 
	if (sms.available())
		smsProcess();
	//Calcolo temperatura		
	calcTemp();
	//on off display alla pressione di un pulsante
	if (digitalRead(LCDOnOffPin) == HIGH)
	{
		 if (digitalRead(LCDLightPin) == HIGH) 
			digitalWrite(LCDLightPin,LOW);
		else
			digitalWrite(LCDLightPin,HIGH); 
	}
}

float calcTemp()
{
	//inizializzo variabili
	int val_Adc = 0;
	temperature	=	0;
	//eseguo un ciclo
	for(byte Ciclo = 0; Ciclo<100; Ciclo++)
	{
		//acquisisco il valore e lo sommo alla variabile
		val_Adc += analogRead(TempSensorPin);
		//questo ritardo serve per dare il tempo all’ ADC di eseguire correttamente la prossima acquisizione
		delay(10);
	}
	//eseguo la media dei 100 valori letti
	val_Adc /= 100;
	//calcolo la temperatura in °C
	temperature =(((val_Adc * 0.0032)-0.5)/0.01); 
	
	if (digitalRead(RegTempManualPin) == HIGH)
	{
		//Serial.println("Impostazione manuale temperatura.");
		TempNew		=	((analogRead(TempRegPin)*0.010)+15);
		dtostrf(TempNew, 2, 1, _bufferTempNew);
		TempNew = atof (_bufferTempNew);
		if (TempNew != TempMem)
		{
			EEPROM.writeDouble(0,TempNew);
			TempMem 	=	EEPROM.readDouble(0); 
		}
	}
	else
	{
		Serial.println("Impostazione temperatura via SMS.");
		TempMem 	=	EEPROM.readDouble(0);
	}
	
	if ((temperature < TempMem) or (analogRead(TempRegPin)>1010))
	{
		Riscaldamento = "ON";
		digitalWrite(RiscReleControlPin,LOW);
	}
	if ((temperature > TempMem) or (analogRead(TempRegPin)<10))
	{	
		Riscaldamento = "OFF";
		digitalWrite(RiscReleControlPin,HIGH);
	} 
	//arrotondo i double per tenere un solo decimale
	dtostrf(temperature, 2, 1, _bufferTempRil);
	dtostrf(TempMem, 2, 1, _bufferTempImp);
	
	
	writeLCD("T Ril   Imp  Ctr" ," "+String(_bufferTempRil)+" "+String(_bufferTempImp)+" "+Riscaldamento,0);
	return temperature;
	
}

void GSMinitialization()
{
	writeLCD("Inizializzazione","GSM",0);
	//Stato connessione
	boolean notConnected = true;
	//Avvio connessione GSM
	while(notConnected)
	{
		if(gsmAccess.begin() == GSM_READY)
		{
			notConnected = false;
		}
		else
		{
		writeLCD("GSM non","connesso",2000);
		Serial.println("GSM non connesso");
		delay(1000);
		}
	}
	writeLCD("GSM","Inizializzato",0);
	Serial.println("GSM Inizializzato");
}

void smsProcess()
{
	char c;
	Serial.println("Message received from:");
	// Get remote number
	sms.remoteNumber(senderNumber, 20);
	Serial.println(senderNumber);
	// An example of message disposal    
	// Any messages starting with # should be discarded
	// usually anonymous messages starts with #
	if(sms.peek()=='#')
	{
		writeLCD("Discarded SMS","",2000);
		//Serial.println("Discarded SMS");
		sms.flush();
	}
	// Read message bytes and print them
	while(c = sms.read())
		message += c;
		// cancella message ricevuto 
	sms.flush();
	writeLCD("MESSAGE DELETED","",2000);
	Serial.println("MESSAGE DELETED");
	
	//Se ci sono messaggi inizio ad elaborarli
	if(message != "") 
	{
		//modifico il messaggio e lo scrivo tutto minuscolo
		message.toLowerCase();
		writeLCD(message,"",2000);
		Serial.println(message);
		int i=0; 
		int j=0; 
		//cerco il carattere di fine device
		while(message.charAt(i) != '*') i++;
		//cerco carattere di fine comando
		while(message.charAt(j) != '+') j++;

		deviceID 	= 	message.substring(0,i );
		commandID	= 	message.substring(i+1,j);
		param		=	message.substring(j+1,message.length());
		execCommand(deviceID,commandID,param);
		// pulisco il messaggio processato
		message = "";
	}
}

void execCommand(String device,String command, String param)
{
	commandID.trim();
	deviceID.trim();
	String rispostaSMS = "";

	//controllo il device da gestire
	if(deviceID == "cancello")
    {
		Serial.println(deviceID);
		//controllo che comando eseguire
		if(commandID == "apri") 
		{
		//sendFeedbackSMS(senderNumber, deviceID, "in apertura");  // send the feedback to the sender
		digitalWrite(GateReleControlPin,LOW);
        delay(100);
		digitalWrite(GateReleControlPin,HIGH);
		writeLCD("Cancello in","apertura",2000);
		Serial.println("cancello in apertura");
		}
    }
	 // controllo il device da interrogare
	 if(deviceID == "termometro")
    {
		Serial.println(deviceID);
		//controllo il comando da eseguire
		if(commandID == "read") 
		{
			Serial.println("calcolo temperatura");
			rispostaSMS = "Temperatura rilevata "+ String(calcTemp())+" temperatura impostata "+ EEPROM.readDouble(0);
			sendFeedbackSMS(senderNumber, rispostaSMS ,"");
		}
		if(commandID == "set") 
		{
			Serial.println("imposta temperatura");
			EEPROM.writeDouble(0, param.toFloat());
			Serial.print(EEPROM.readDouble(0));
			TempMem	=	param.toFloat();	
		}
    }
}

// this function, given a phone number and two strings representing the device and 
// its state send a feedback text message to the phone number
void sendFeedbackSMS(char remoteNum[], String devString, String devState) {
  String txtMsg = devString + " " + devState;
  Serial.println(txtMsg);
  Serial.println(remoteNum);
  sms.beginSMS(remoteNum);
  sms.print(txtMsg);
  sms.endSMS(); 
}



void writeLCD(String riga1,String riga2,int ritardo)
{
	lcd.setCursor(0,0);
	lcd.print("                ");
	lcd.setCursor(0,1);
	lcd.print("                ");
	lcd.setCursor(0,0);
	lcd.print(riga1);
	lcd.setCursor(0,1);
	lcd.print(riga2);
	if (ritardo > 0)
	{
		delay(ritardo);
		lcd.setCursor(0,0);
		lcd.print("                ");
		lcd.setCursor(0,1);
		lcd.print("                ");
	}
}

SMS_3.ino (6.82 KB)

Premetto che è OT, ma non è certo vero che per fare il multithreading e/o il multitasking serva più di un core :).

Fine OT.

SukkoPera:
non è certo vero che per fare il multithreading e/o il multitasking serva più di un core :).

Sicuramente NO ... dato che nei bei tempi passati si faceva pure con il i80386 ... :smiley: :smiley: :smiley: ... però se ce li hai riesci ad avere un certo parallelismo che, ovviamente, in altro modo non è possibile :wink:

Guglielmo

bhe questo sarebbe un bel programmino da poter eseguire lo scheduler...
@stefano_8805 hai guardato il link che ho postato?

Ora lo guardo e provo ad applicarlo. Poi vi faccio sapere se ho risolto. Lo scopo fondamentale è simulare qualcosa che mi dia il risultato del multi task. Intanto grazie.

Ho provato ad usare lo scheduler, ma sicuramente sbaglio qualcosa in quanto esegue solo il loop2. Ho provato persino a togliere provvisoriamente una parte di codice per vedere se funzionava. Vi allego lo sketch nuovo. Leggendo in giro sembrerebbe funzioni solo su arduino due mentre io ho arduino uno.
Vi ringrazio fin da ora per le risposte.

// includo le librerie
#include <GSM.h>
#include <EEPROMex.h>
#include <LiquidCrystal.h>
#include <Scheduler.h>
//Inizializzo i pin dell' LCD
LiquidCrystal lcd(4,5,17,16,15,14);
//Dichiaro le costanti dei PIN\
const	int	GateReleControlPin	=	9;
const	int	RiscReleControlPin	=	10;
const	int	LCDLightPin			=	6; 
const	int	LCDOnOffPin			=	8;
const	int	RegTempManualPin	=	11;

const	int TempSensorPin	=	A4;
const	int TempRegPin		=	A5;
//dichiaro le variabili globali
String		Riscaldamento	=	"";
float		temperature;
float		TempMem,TempNew		=	0;

char		_bufferTempImp[5]	=	"";
char		_bufferTempRil[5]	=	"";
char		_bufferTempNew[5]	=	"";
//Stringa di memorizzazione SMS in ingresso
String message	=	"";
//Stringhe di memorizzazione parametri SMS
String	commandID,	deviceID,	param;
//Array di memorizzazione del numero di telefono
char	senderNumber[20]; 

// initialize the library instances
GSM		gsmAccess;
GSM_SMS	sms;

void setup() 
{
	//Inizializzo LCD Vcc PIN
	pinMode(LCDLightPin, OUTPUT);
	digitalWrite(LCDLightPin,HIGH);
	lcd.begin(16,2);

	//inizializzo la seriale
	Serial.begin(9600);
	writeLCD("Inizializzazione","PIN",1000);
	// inizilizzo i pin
	pinMode(9, OUTPUT);
	pinMode(RiscReleControlPin, OUTPUT);
	pinMode(LCDOnOffPin, INPUT);
	pinMode(RegTempManualPin, INPUT);
	digitalWrite(9,HIGH);
	digitalWrite(RiscReleControlPin,HIGH);
	//Attivo confronto con AREF in modo da migliorare 
	analogReference(EXTERNAL);
	//Leggo la temperatura memorizzata in EEPROM	
	TempMem	=	EEPROM.readDouble(0);	
	Scheduler.startLoop(loop2);
	Scheduler.startLoop(loop3);
}

void loop() 
{
	//Calcolo temperatura		
	//calcTemp();
	//on off display alla pressione di un pulsante
	if (digitalRead(LCDOnOffPin) == HIGH)
	{
		 if (digitalRead(LCDLightPin) == HIGH) 
			digitalWrite(LCDLightPin,LOW);
		else
			digitalWrite(LCDLightPin,HIGH); 
	}
	delay(3000);
}

void loop3()
{
	
	//inizializzo variabili
	int val_Adc = 0;
	temperature	=	0;
	//eseguo un ciclo
	for(byte Ciclo = 0; Ciclo<100; Ciclo++)
	{
		//acquisisco il valore e lo sommo alla variabile
		val_Adc += analogRead(TempSensorPin);
		//questo ritardo serve per dare il tempo all’ ADC di eseguire correttamente la prossima acquisizione
		delay(10);
	}
	//eseguo la media dei 100 valori letti
	val_Adc /= 100;
	//calcolo la temperatura in °C
	temperature =(((val_Adc * 0.0032)-0.5)/0.01); 
	
	
	
	if ((temperature < TempMem) or (analogRead(TempRegPin)>1010))
	{
		Riscaldamento = "ON";
		digitalWrite(RiscReleControlPin,LOW);
	}
	if ((temperature > TempMem) or (analogRead(TempRegPin)<10))
	{	
		Riscaldamento = "OFF";
		digitalWrite(RiscReleControlPin,HIGH);
	} 
	//arrotondo i double per tenere un solo decimale
	dtostrf(temperature, 2, 1, _bufferTempRil);
	dtostrf(TempMem, 2, 1, _bufferTempImp);
	
	
	writeLCD("T Ril   Imp  Ctr" ," "+String(_bufferTempRil)+" "+String(_bufferTempImp)+" "+Riscaldamento,0);

	yield();
}


void writeLCD(String riga1,String riga2,int ritardo)
{
	lcd.setCursor(0,0);
	lcd.print("                ");
	lcd.setCursor(0,1);
	lcd.print("                ");
	lcd.setCursor(0,0);
	lcd.print(riga1);
	lcd.setCursor(0,1);
	lcd.print(riga2);
	if (ritardo > 0)
	{
		delay(ritardo);
		lcd.setCursor(0,0);
		lcd.print("                ");
		lcd.setCursor(0,1);
		lcd.print("                ");
	}
}

void loop2()
{
	if (digitalRead(RegTempManualPin) == HIGH)
	{
		Serial.println("Impostazione manuale temperatura.");
		TempNew		=	((analogRead(TempRegPin)*0.010)+15);
		dtostrf(TempNew, 2, 1, _bufferTempNew);
		TempNew = atof (_bufferTempNew);
		if (TempNew != TempMem)
		{
			EEPROM.writeDouble(0,TempNew);
			TempMem 	=	EEPROM.readDouble(0); 
		}
	}
	else
	{
		Serial.println("Impostazione temperatura via SMS.");
		TempMem 	=	EEPROM.readDouble(0);
	}
	delay(3000);
}

se hai scaricato lo scheduler dal mio repository funziona anche sulla Uno.

più tardi guardo il tuo codice.
prova a mettere un Serial.print nel setup e dentro ogni loop così vedi meglio cosa accade