Qualificatore volatile

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à

Alan

Ti consiglio la lettura di QUESTO articolo che spiega bene i vari casi di utilizzo ...

Guglielmo

Ciao Guglielmo,
come al solito sempre pronto... Grazie.

Nell'articolo che mi hai consigliato è spiegato molto bene quando e come si usa il qualificatore volatile; il mio problema nasce però in questo caso:

//main.h
typedef enum {tabellone, orologio} Mode;
typedef enum {stop, run} Stato;

typedef struct Valori {
  byte val[9];
  Stato stato;
  Mode mode;
  bool modeImpostata;
  Valori();
  void print(bool whitConf = false)const;
  void println(bool whitConf = false)const;
  bool operator==(const Valori &val2);
} Valori;

//main.cpp
Valori::Valori() {
  for (byte i = 0; i < 9; i++) {
    val[9] = 0;
  }
  stato = stop;
  mode = tabellone;
  modeImpostata = false;
}

void Valori::print(bool whitConf)const {
  for (byte i = 0; i < 8; i++) {
    Serial.print(val[i]); Serial.print(".");
  }
  Serial.print(val[8]);
  if (whitConf) {
    String str = "\tStato:";
    switch (stato) {
      case stop:
        str += "stop\t";
        break;
      case run:
        str += "run\t";
        break;
    }
    str += "Modalita': ";
    switch (mode) {
      case tabellone:
        str += "tabellone\t";
        break;
      case orologio:
        str += "orologio\t";
        break;
    }
    str += "Mode Impostata: ";
    str += modeImpostata;
    Serial.print(str);
  }
}
void Valori::println(bool whitConf)const {
  print(whitConf);
  Serial.println();
}
bool Valori::operator==(const Valori &val2) {
  for (byte i = 0; i < 9; i++) {
    if (val[i] != val2.val[i]) {
      return false;
    }
  }
  if (stato != val2.stato) {
    return false;
  }
  if (mode != val2.mode) {
    return false;
  }
  if (modeImpostata != val2.modeImpostata) {
    return false;
  }
  return true;
}
//skatch.ino
extern taskHandle_t loopTask;
volatile Valori valori;

void setup(){
    xTaskCreate(
    TaskRoutine,        /* Task function. */
    "MYTASK_T",        /* name of task. ONLY FOR HUMANS*/
    10000,                   /* Stack size of task */
    NULL,                    /* parameter of the task */
    1,                           /* priority of the task */
    &task1Handle_t);    /* Task handle to keep track of created task */
}

void TaskRoutine(void * pvParameters) {
  while (1) {
    state = digitalRead(15);
    if (!state && state0 != state) {
      time_s = millis();
      vTaskSuspend(loopTaskHandle);  
      Valori temp;
      EEPROM.get(0,temp);
      if(!(temp == valori){  //ERRORE
         EEPROM.put(0,valori);
         EEPROM.comit();
    } else if (state && state0 != state) {
        vTaskResume(loopTaskHandle);
    }
    state0 = state;
    vTaskDelay(10);
  }
  vTaskDelete(task1Handle_t);
}

void loop(){
  valori.println();
  delay(100);
}

Errore:

passing 'volatile Valori' as 'this' argument discards qualifiers [-fpermissive]

In questo caso come dovrei fare?
Devo ri-definire un altro operatore == con il qualificatore volatile?

Alan

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);
  }

}

Ciao, come al solito ti ringrazio....

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.

Spero di essere stato più chiaro ora...

Alan

Stiamo monopolizzando il forum, ma almeno non si parla sempre di delay() e di millis() :joy: :rofl: :upside_down_face:

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.

hahahahahh.....

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);
  }

}

ma poi, il loop come faccio a metterlo in pausa?

Alan

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.

extern TaskHandle_t loopTaskHandle;
vTaskSuspend(loopTaskHandle);
vTaskResume(loopTaskHandle);

Ad esempio, ho appena testato questo sketch:

#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);
  }
}