Salve a tutti,
sto lavorando con FreeRTOS su ESP32 per avere la possibilità di gestire più task contemporaneamente, e documentandomi per altri motivi sul qualificatore volatile ho scoperto che era storicamente venduto come un palliativo per il passaggio di dati in una programmazione multi-thread, la mia domanda è: resta corretto qualificare una variabile (anche complessa tipo strutture e/o classi) come volatile se deve essere utilizzata su più task, o esistono modi migliori per poter gestire questo genere di necessità?
Anche perché nel caso una struttura contenga degli overload ad operatori che possono non essere volatile la chiamata da un'istanza volatile genera errore di compilazione!
Grazie a chi mi risponderà
Ho provato a compilare il codice che hai messo, ma è tutto un po' "confuso" e ci sono molte dichiarazioni mancanti. Comunque non credo che il problema sia il qualificatore volatile perché in questo modo non stai passando la variabile al task, ma la usi come globale. Nella funzione xTaskCreate(), dove si aspetta il puntatore ai parametri , tu hai messo NULL.
Ad essere sincero non credo di aver capito esattamente cosa vuoi ottenere, ma credo che il modo migliore per gestire gli accessi concorrenziali di più task alle variabili, sia usare il meccanismo delle code o dei semafori piuttosto che arrestare e riavviare task o affidarsi al qualificatore volatile.
Ho semplificato il codice che hai postato per renderlo un po' più chiaro.
Questo sketch, per quanto inutile fa quello che è previsto e siccome nel task metti in pausa il loop() principale, qualificare come volatile la variabile valori secondo me è del tutto ridondante oltre che non accettato dalla funzione xTaskCreate() che prevede un void * per passare al task dei parametri.
Se riesco questa sera provo a fare anche un esempio usando le code QueueHandle_t un semaforo (più adeguato all'esempio specifico).
// In C++ non è più necessario usare il typedef con le struct
struct DummySum {
bool doSum;
int var1;
int var2;
int result;
int sum(int a, int b) {
return a + b;
}
} ;
extern TaskHandle_t loopTaskHandle;
TaskHandle_t TaskRoutineHandle;
DummySum valori;
void setup() {
Serial.begin(115200);
xTaskCreate(
TaskRoutine, /* Task function. */
"MYTASK_T", /* name of task. ONLY FOR HUMANS*/
10000, /* Stack size of task */
&valori, /* parameter of the task */
1, /* priority of the task */
&TaskRoutineHandle /* Task handle to keep track of created task */
);
}
void TaskRoutine(void * pvParameters) {
while (1) {
// Casting esplicito del puntatore ai parametri
// al tipo "user defined" DummySum
DummySum* val = (DummySum*) pvParameters;
// val è un puntatore alla variabile valori
if (val->doSum) {
vTaskSuspend(loopTaskHandle);
val->var1 = random(10);
val->var2 = random(10);
val->result = val->sum(val->var1, val->var2);
val->doSum = false;
}
vTaskResume(loopTaskHandle);
yield();
vTaskDelay(10);
}
vTaskDelete(TaskRoutineHandle);
}
void loop() {
static uint32_t sumTime;
if (millis() - sumTime > 5000) {
sumTime = millis();
valori.doSum = true;
Serial.print("Ho eseguito la somma ");
Serial.print(valori.var1);
Serial.print(" + ");
Serial.print(valori.var2);
Serial.print(" = ");
Serial.println(valori.result);
}
}
Eccolo!
Due task che accedono in modo concorrenziale e random alla stessa struct (TaskRoutine1 ha precedenza su TaskRoutine2 perché ha priorità maggiore).
// In C++ non è più necessario usare il typedef con le struct
struct DummySum {
int var1;
int var2;
int result;
bool sumDone;
String lastTask;
int sum(int a, int b) {
return a + b;
}
} ;
TaskHandle_t Task1Handle, Task2Handle;
SemaphoreHandle_t xMutex;
DummySum valori;
void setup() {
/* create Mutex */
// Il semaforo di tipo Mutex è una specie di token:
// chi ce l'ha può eseguire azioni e poi lo restituisce
xMutex = xSemaphoreCreateMutex();
Serial.begin(115200);
xTaskCreate(
TaskRoutine1, /* Task function. */
"MYTASK2", /* name of task. ONLY FOR HUMANS*/
10000, /* Stack size of task */
&valori, /* parameter of the task */
3, /* priority of the task */
&Task1Handle /* Task handle to keep track of created task */
);
xTaskCreate(
TaskRoutine2, /* Task function. */
"MYTASK2", /* name of task. ONLY FOR HUMANS*/
10000, /* Stack size of task */
&valori, /* parameter of the task */
2, /* priority of the task */
&Task2Handle /* Task handle to keep track of created task */
);
}
void TaskRoutine1(void * pvParameters) {
while (1) {
// Casting esplicito del puntatore ai parametri al tipo "user defined" DummySum
DummySum* val = (DummySum*) pvParameters;
// Prendi il token
xSemaphoreTake( xMutex, portMAX_DELAY );
val->var1 = random(10);
val->var2 = random(10);
val->result = val->sum(val->var1, val->var2);
val->lastTask = "MYTASK1";
val->sumDone = true;
// Restituisci il token
xSemaphoreGive( xMutex );
delay(random(10) * 1000);
}
vTaskDelete(Task1Handle);
}
void TaskRoutine2(void * pvParameters) {
while (1) {
DummySum* val = (DummySum*) pvParameters;
xSemaphoreTake( xMutex, portMAX_DELAY );
val->var1 = random(10);
val->var2 = random(10);
val->result = val->sum(val->var1, val->var2);
val->lastTask = "MYTASK2";
val->sumDone = true;
xSemaphoreGive( xMutex );
delay(random(10) * 1000);
}
vTaskDelete(Task2Handle);
}
void loop() {
if ( valori.sumDone) {
valori.sumDone = false;
Serial.print("Il task ");
Serial.print(valori.lastTask);
Serial.print(" ha eseguito la somma ");
Serial.print(valori.var1);
Serial.print(" + ");
Serial.print(valori.var2);
Serial.print(" = ");
Serial.println(valori.result);
}
}
questo è dovuto al fatto che ho adattato il codice per il forum, e anche qua mi tocca approfondire la cosa;
Ho un sistema che tramite circuiteria elettronica (super cap, diodo) mi rileva su un pin un fronte di discesa quando l'alimentatore toglie tensione, il secondo task, ne tiene controllato lo stato e, se identifica un fronte di discesa, disabilita il loopTask, entra in PowerSave (ho tolto tutto nell'esempio sopra), e deve procedere a salvare i dati correnti nella EEPROM, però io vorrei che prima leggesse quello che è contenuto, lo confrontasse con quello che vuole salvare, e se è diverso lo salvi.
La questione dei due task che lavorano sulla stessa variabile è direi fittizio, perché l'uno esclude l'altro, e il vTaskSuspand é voluto per implementazione, non per problemi relativi al volatile o alla condivisione di variabili.
Il problema avviene quando uso l'operatore == della quale ho fatto l'overload. Ovviamente l'operatore si aspetta come this un valore non volatile, e perciò il compilatore va in errore.
Stiamo monopolizzando il forum, ma almeno non si parla sempre di delay() e di millis()
Ok, ora mi è più chiaro lo scopo.
Allora secondo me la cosa migliore da fare è usare un semaforo binario associato all'interrupt Binary semaphores for FreeRTOS oppure una "task notification".
Un po' come il secondo esempio, solo che il semaforo lo sblocchi a seguito di un interrupt.
In pratica, il task che deve salvare il contentuo dei dati in EEPROM lo metti in pausa con un xSemaphoreTake(), quando c'è l'interrupt il semaforo viene sbloccato ed il task fa quel che deve.
L'immagine animata che c'è in fondo alla pagina del link rende in modo molto intuitivo il principio di funzionamento.
La verifica dei dati presenti in EEPROM non è necessaria perché le istruzioni put() e write() già lo prevedono di base e scrivono solo se diversi.
ecco... ora non sto capendo più....
quindi riesco a bloccare il loopTask e a far partire il powerFailTask collegandolo ad un interrupt sul pin?
Quindi:
// In C++ non è più necessario usare il typedef con le struct
struct DummySum {
int var1;
int var2;
int result;
bool sumDone;
String lastTask;
int sum(int a, int b) {
return a + b;
}
} ;
TaskHandle_t Task1Handle;
SemaphoreHandle_t xMutex;
DummySum valori;
void giveSemaphore(){
xSemaphoreGive( xMutex );
}
void setup() {
/* create Mutex */
// Il semaforo di tipo Mutex è una specie di token:
// chi ce l'ha può eseguire azioni e poi lo restituisce
xMutex = xSemaphoreCreateMutex();
Serial.begin(115200);
xTaskCreate(
TaskRoutine1, /* Task function. */
"MYTASK1", /* name of task. ONLY FOR HUMANS*/
10000, /* Stack size of task */
NULL, /* parameter of the task */
3, /* priority of the task */
&Task1Handle /* Task handle to keep track of created task */
);
attachInterrupt(digitalPinToInterrupt(15), giveSemaphore, FALLING);
}
void TaskRoutine1(void * pvParameters) {
while (1) {
// Prendi il token
xSemaphoreTake( xMutex, portMAX_DELAY );
val->var1 = random(10);
val->var2 = random(10);
val->result = val->sum(val->var1, val->var2);
val->lastTask = "MYTASK1";
val->sumDone = true;
delay(random(10) * 1000);
}
vTaskDelete(Task1Handle);
}
void loop() {
if ( valori.sumDone) {
valori.sumDone = false;
Serial.print("Il task ");
Serial.print(valori.lastTask);
Serial.print(" ha eseguito la somma ");
Serial.print(valori.var1);
Serial.print(" + ");
Serial.print(valori.var2);
Serial.print(" = ");
Serial.println(valori.result);
}
}
Premesso che non vedo quale sia l'utilità di bloccare il looptask, la risposta è si perché con ESP32 la funzione loop() viene gestita all'interno di un task RTOS allo stesso modo di come viene gestita il tuo powerFailTask quindi puoi usare lo stesso meccanismo del semaforo oppure forzare la sospensione come nel primo esempio.
#define Btn1_GPIO 8
#define LED1_GPIO 9
bool toggle;
extern TaskHandle_t loopTaskHandle;
TaskHandle_t powerFailTaskHandle;
SemaphoreHandle_t xToken;
// Interrupt Service Routine associata all'ingresso
void IRAM_ATTR Ext_INT1_ISR()
{
/* Is it time for vATask() to run? */
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* Is it time for vATask() to run! */
xSemaphoreGiveFromISR(xToken, &xHigherPriorityTaskWoken);
/* Exit from ISR (ESP-IDF) */
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
void powerFailTask (void * pvParameters) {
while (1) {
// Questo task rimane in attesa fino a quando non prende il token
xSemaphoreTake(xToken, portMAX_DELAY );
Serial.println("Salvo i valori nella EEPROM");
/* Secondo me non è necessario, ma se volessi comunque farlo cosi funziona */
// Sospendo il loopTask per 10 secondi
vTaskSuspend(loopTaskHandle);
Serial.println("loopTask sospeso per 10 secondi, il led smette di lampeggiare");
delay(10000);
// Faccio ripartire il loopTask
vTaskResume(loopTaskHandle);
Serial.println("loopTask riavviato, il led riprende a lampeggiare");
}
vTaskDelete(powerFailTaskHandle);
}
void setup()
{
Serial.begin(115200);
Serial.println("START");
pinMode(LED1_GPIO, OUTPUT);
pinMode(Btn1_GPIO, INPUT_PULLUP);
// Associazione input - ISR
attachInterrupt(Btn1_GPIO, Ext_INT1_ISR, FALLING);
// Creazione del semaforo binario
xToken = xSemaphoreCreateBinary();
// Avvio del task powerFailTask
xTaskCreate(powerFailTask,"powerFailTask", 8000, NULL, 3, &powerFailTaskHandle);
}
void loop()
{
// Faccio lampeggiare un led giusto per vedere quando il loopTask viene eseguito
static uint32_t blinkTime;
if (millis() - blinkTime > 333) {
blinkTime = millis();
toggle = !toggle;
digitalWrite(LED1_GPIO, toggle);
}
}