Gestione del'interrupt software

Buonasera, sono un eterno principiante, nel senso che ogni volta che scopro qualcosa di nuovo diventa un chiodo fisso.
Ho da poco scoperto gli Esp32 e i vari sensori tra cui il D18B20 per testare la temperatura.
fatto il primo sketch con il ciclo loop leggo le temperature tranquillamente.
Poi però l'utilizzo di "loop" mi andava stretto, in quanto utilizzando delay tra una lettura e l'altra non potevo gestire altre operazioni con intervalli diversi.
Cerco e scopro gli interrupt software, su cui la maggior parte della documentazione è riferita a versioni precedenti.
Dopo ulteriori ricerche ho trovato qualcosa sulla nuova versione e facendo qualche prova con WOKWI funziona perfettamente.
La realtà ha evidenziato risultati più deludenti.
Ad ogni ciclo mi viene restituito un errore credo su indirizzi di memoria non gestibili.

Interrupt ...28:13:6b:58:00:00:00:b9 -> 20.31°
Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled.

Core 1 register dump:
PC : 0x40083f5c PS : 0x00050d31 A0 : 0x800d20c7 A1 : 0x3ffbf8ec
A2 : 0x00000040 A3 : 0x00018040 A4 : 0x000637ff A5 : 0x3ffbf8cc
A6 : 0x00000000 A7 : 0xb33fffff A8 : 0x800d4610 A9 : 0x4008aac2
A10 : 0x00000000 A11 : 0x7da15afe A12 : 0x800838d8 A13 : 0x3ffbf8ac
A14 : 0x3ffc1fe8 A15 : 0x400854e4 SAR : 0x00000019 EXCCAUSE: 0x0000001c
EXCVADDR: 0x800d20d3 LBEG : 0x400866b5 LEND : 0x400866c5 LCOUNT : 0xffffffff

Backtrace: 0x40083f59:0x3ffbf8ec |<-CORRUPTED

su questo non riesco a trovare soluzioni.
vi allego il codice, se qualcuno potesse aiutarmi

#include <OneWire.h>
#include <DallasTemperature.h>

// GPIO where the DS18B20 is connected to
const int oneWireBus = 4; 

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(oneWireBus);

// Pass our oneWire reference to Dallas Temperature sensor 
DallasTemperature sensors(&oneWire);
DeviceAddress mac;
struct rdTemp
{
  String sndMac;
  float temp ;
};

hw_timer_t *timer = NULL;
rdTemp x; 
void ARDUINO_ISR_ATTR onTimer()
{
  Serial.print("Interrupt ...");
  rdTemp dati = readTemp();
  Serial.print(dati.sndMac);
  Serial.print(" -> ");
  Serial.print(dati.temp);
  Serial.print("°");
 Serial.println();
}


rdTemp readTemp()
{
  sensors.requestTemperatures(); 
  float temperatureC = sensors.getTempCByIndex(0);
  String wMac = decodMac() ; 
  rdTemp x ={ wMac, temperatureC};
  return x;
}

String decodMac()
{
    String wMac = "" ;
    String sep  = "" ;
    for (uint8_t ii = 0; ii < 8; ii++) 
    {
      String mc = "00" + String(mac[ii], HEX) ;  
      int l = mc.length() ;
      mc = mc.substring(l -2,l); 
      wMac += sep + mc ;
      sep = ":" ;
    }
    return wMac ;
}

void setup()
{
  // put your setup code here, to run once:
  Serial.begin(115200);
  
  sensors.begin();
  sensors.getAddress(mac, 0);
  decodMac(); 

  delay(100);
  timer = timerBegin(1000000);
  timerAttachInterrupt(timer, onTimer);
  timerAlarm(timer, 5000000, true, 0);
}



void loop()
{

}


:warning:
Ti segnalo che, nella sezione in lingua Inglese, si può scrivere SOLO in Inglese ... quindi, per favore, la prossima volta presta più attenzione in quale sezione metti i tuoi post; questa volta esso è stato spostato, da un moderatore della sezione di lingua Inglese, nella sezione di lingua Italiana ... la prossima volta potrebbe venire direttamente eliminato.
Grazie.

A quanto detto da UKHeliBob aggiungo ...

... cortesemente, come prima cosa, leggi attentamente il REGOLAMENTO della sezione Italiana del forum, (... e, per evitare future possibili discussioni/incomprensioni, prestando sempre molta attenzione al punto 15), dopo di che, come da suddetto regolamento (punto 16.7), fai la tua presentazione NELL'APPOSITA DISCUSSIONE (... quello che vedi in blu è un link, fai click su di esso per raggiungere la discussione) spiegando bene quali esperienze hai in elettronica e programmazione, affinché noi possiamo conoscere la tua esperienza ed esprimerci con termini adeguati.

Grazie,

Guglielmo

P.S.: Ti ricordo che, purtroppo, fino a quando non sarà fatta la presentazione nell’apposita discussione, nel rispetto del succitato regolamento nessuno ti risponderà (eventuali risposte o tuoi ulteriori post, verrebbero temporaneamente nascosti), quindi ti consiglio di farla al più presto. :wink:

P.P.S.: Evitate di utilizzare la traduzione automatica fatta dal browser ... vi impedisce di capire la lingua della sezione dove andate a scrivere ...

Invece di incasinarti subito la vita utilizzando cose che richiedono più esperienza, impara a NON usare la funzione delay() che è bloccante ed studiati invece come si usa la funzione millis(), prima QUI, poi QUI e QUI e QUI e tutti gli articoli che sono in QUESTA pagina ... vedrai che poi ti sarà tutto più chiaro come fare ... :wink:

Guglielmo

Sisi usando una libreria che usa gli interrupt (Wire.h) all'interno di una Interrupt Service Routine associata quindi ad un'altro interrupt.

Non possono che venirne fuori solo guai...

Ho capito che mi sto incasinando, ma se non lo faccio ora che sono agli inizi, quando lo faccio?
gestire millis() come suggerisce Guglielmo significa utilizzare gli IF che considero il male assoluto dei codici.
Ho letto qualcosa riguardo alla definizione "volatile", che credo possa centrare, ma non trovo più l'articolo.
Potrebbe essere una strada?

Guarda caso hai indovinato tutto ciò che riguarda gli interrupt, tutto il resto è la causa del problema.

return wMac ;

Non restituire una variabile locale. Il tipo String è una classe C++, con il suo costruttore, costruttore di copia, assegnamento e distruttore. Il distruttore viene chiamato automaticamente quando la funzione termina e questo è buono perché libera la memoria allocata (a run-time) da String, diversamente si verificherebbe un "memory leak".

  rdTemp x ={ wMac, temperatureC};
  return x;
}

La parola chiave struct è presente sia in C che in C++, ma in C++ è equivalente ad una classe.
Uno ci si aspetta che venga fatta una copia dell'oggetto locale (x) per essere restituito così come avviene con i tipi built-in (int, uint, ecc), ma non accade.

void decodMac(String &wMac)
{
    
    String sep  = "" ;
    for (uint8_t ii = 0; ii < 8; ii++) 
      {
        String mc = "00" + String(mac[ii], HEX) ;  
         int l = mc.length() ;
         mc = mc.substring(l -2,l); 
         wMac += sep + mc ;   // usa il riferimento passato come argomento wMac
         sep = ":" ;
    }
 }

la chiamata sarebbe simile a:

String wMac;
decodeMac(wMac);
// qui puoi direttamente stampare wMac
// ma occhio che essendo per forza di cose dentro una funzione quando questa 
// termina viene chiamato il distruttore su wMac per liberare la memoria.

Stesso ragionamento per la funzione readTemp().

Per il discorso loop() visto che la scheda è una esp32 hai tanti modi differenti aggirare il problema,
uno è usare FreeRTOS già incluso, che ti permette programmazione concorrente, con tutti i problemi che ne derivano (Serve polso e lucidità, non è banale).

Mentre millis() e i timer hardware per piccoli/medi programmi è la soluzione più usata, l'unico problema è che devi disintossicarti :smiley: da delay().

Ti lascio un link ad un articolo dove si descrive il programmino per realizzare un termostato, dove
viene usato millis().

Ciao.

Wire.h come I2c?
Oppure OneWire?

Cioè OneWire e Dallas ecc usano interrupt su ESP?

PS: di ESP conosco poco e male.

Ciao.

Ehhhh ???

E questa dove l'hai letta ????

Guglielmo

Non proprio a dire il vero, però si tratta di operazioni che vengono comunque eseguite con una certa criticità e non son sicuro che questo blocco di codice venga eseguito correttamente all'interno di una ISR.

Secondo me la strada maestra in questi casi è FreeRTOS per quanto possa apparire poco user friendly e richieda impegno.
Anche gestire tutto temporizzando con millis() va altrettanto bene, tanto l'ESP32 riesce a fare questo ed altri 20 task simili senza problemi tutti all'interno di loop()

L'approccio del timer hardware invece non mi sembra "buono" in questo caso... sarebbe come usare uno scheduler (il timer) all'interno di uno scheduler già esistente (FreeRTOS).

:open_mouth: :open_mouth: :open_mouth:

Questo equivale più o meno ad affermare che le vocali sono il male assoluto delle lingue parlate...

1 Like

Riguardo agli IF, mi viene il mal di stomaco ogni volta che devo scontrarmi con roba del genere.

	For Each strVarianti In cfgVar
		arrVarianti = split(strVarianti,"|")		
		result = True
		For Each variante In split(arrVarianti(0),",")
			codVar = split(variante,"=")(0)
			rispVar = split(variante,"=")(1)
			If instr(codVar, ":") > 0 Then		
				amb.reg.var.letv split(codVar,":")(0),rispVar
				rispCalc = UCase(amb.reg.gzvar.valuta("$(" & codVar & ")"))
				if inStr(rispVar, "{") > 0 Then
					rispVar = replace(rispVar,"{","")
					rispVar = replace(rispVar,"}","")
					rispVar = ";" & rispVar & ";"
					if inStr(rispVar, ";" & rispCalc & ";") = 0 Then
						result = False
					end if
				else						
					If uCase(rispCalc) <> uCase(rispVar) Then
						result = False
					End If
				end if
			Else
				if instr(rispVar, "{") > 0 Then
					rispVar = replace(rispVar,"{","")
					rispVar = replace(rispVar,"}","")
					resValVar = false
					for each valVar in split(rispVar,";")
						fakeVal = uCase(codVar) & "=" & uCase(valVar)
						if instr(strArg, ";" & uCase(fakeVal) & ";") > 0 Then
							resValVar = true
						end if
					next
					
					if not resValVar then
						result = False
					end if

				else				
					If inStr(uCase(strArg), ";" & uCase(variante) & ";") = 0 Then
						result = False
					End If

				End if
			End If				
		Next
		
		If result Then
			checkVarianti = arrVarianti(1)
		End If
	Next

Grazie per la collaborazione, stasera approfondisco.
per il momento ho da sbattermi con gli IF

A me invece viene ogni volta che vedo un listato Visual Basic :rofl:

Comunque scherzi a parte, il problema di base quando si è agli inizi è che non si elaborano gli algoritmi in modo "modulare" separando il codice in "funzioni" le quali aiutano a suddividere un programma complesso in parti più piccole e gestibili (moduli) migliorando l'organizzazione, la leggibilità e la manutenzione del codice.

Ma non mi dire, anche io provo nausea con annidamenti di profondità simile e non c'è linguaggio che tenga. Quando incontro codice simile, alle volte mi chiedo quanto tempo ci ha messo per farlo funzionare? Io di sicuro non riesco a farlo funzionare se lo devo scrivere così annidato, ma considera che c'è di peggio, cioè quando l'annidamento si estende per diverse pagine. Io con i miei due neuroni non c'è la posso fare. :smiley:

Quindi scompongo il problema in pezzi e affronto un pezzo per volta, testo il pezzo di codice nella funzione e se si comporta come previsto passo all'altro pezzo, in sostanza tendo a fare dei puzzle, come sintetizzato da @cotestatnt .

Si, ok, ad intuito dovrebbe funzionare, male perché la ISR si prende più tempo del necessario a causa delle temporizzazioni bloccanti per il loop(). Sempre ad intuito IRAM_ATTR serve a spostare la ISR da flash alla ram oltre a marcare la funzione, puoi confermare?
Ma purtroppo l'intuito non basta, serve provare e verificare cosa accade nel dettaglio e che ESP-32 e la sua SDK sono complesse e non basta qualche ora per venirne a capo.

Dalla doc ufficiale su IRAM_ATTR:
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/memory-types.html#how-to-place-code-in-iram

Ciao.

Buonasera, ho fatto centinaia di prove e sinceramente mi sta venendo il dubbio che ci sia qualcosa che non va nella libreria "DallasTemperature".

Ho riscritto lo sketch

#include <OneWire.h>
#include <DallasTemperature.h>

// GPIO where the DS18B20 is connected to
const int oneWireBus = 4; 

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(oneWireBus);

// Pass our oneWire reference to Dallas Temperature sensor 
DallasTemperature sensors(&oneWire);

struct rdTemp
{
  String sndMac;
  float temp ;
};


hw_timer_t *timer = NULL;
void ARDUINO_ISR_ATTR onTimer()
{
  Serial.print("Interrupt ...");
  rdTemp dati = readTemp();

  Serial.print(dati.sndMac);
  Serial.print(" -> ");
  Serial.print(dati.temp);
  Serial.print("°");
  Serial.println();
}


rdTemp readTemp()
{
 float temperatureC = 0 ;
  DeviceAddress mac ; 
  sensors.getAddress(mac, 0);
  sensors.requestTemperatures(); 
  // temperatureC = sensors.getTempCByIndex(0);


  String wMac         = decodMac(mac); 
  rdTemp x            ={ wMac, temperatureC };
  return x;
}

String decodMac(DeviceAddress baAddr)
{
    String wMac = "" ;
    String sep  = "" ;
    for (uint8_t ii = 0; ii < 8; ii++) 
    {
      String mc = "00" + String(baAddr[ii], HEX) ;  
      int l = mc.length() ;
      mc = mc.substring(l -2,l); 
      wMac += sep + mc ;
      sep = ":" ;
    }
    return wMac ;
}

void setup()
{
  Serial.begin(115200);
  sensors.begin();
  delay(100);

  timer = timerBegin(1000000);
  timerAttachInterrupt(timer, onTimer);
  timerAlarm(timer, 5000000, true, 0);

}

void loop()
 {

}

nella funzione readTemp

leggo il MAC della sonda collegata, che funziona ripetutamente,
come anche la riga successiva
con requestTemperatures()
Mentre
// temperatureC = sensors.getTempCByIndex(0);

che ho commentato, funziona solo la prima volta.

Oltre questo ho scritto un successivo sketch dove mio malgrado ho abbandonato l'idea degli interrupt e ho utilizzato gli "IF" visto che "switch" è ben diverso da quello di PHP con cui ho più affiatamento.

Qui ho gestito comunque chiamate ad intervalli diversi.
Mi dite cosa ne pensate se ci sono alternative più performanti?

#include <OneWire.h>
#include <DallasTemperature.h>

// GPIO where the DS18B20 is connected to
const int         oneWireBus = 4; 
OneWire           oneWire(oneWireBus);
DallasTemperature sensors(&oneWire);

    struct rdTemp
    {
      String  sndMac;
      float   temp ;
    };

    unsigned  long mllsTime;
    unsigned  long bitTime     = 0 ;
    unsigned  long intervall   = 1 ;
    unsigned  long lastTime1   = 0 ; 
    unsigned  long lastTime2   = 0 ; 

    void setup() 
    {
      Serial.begin(9600);
      sensors.begin();
    }

    void loop() 
    {
      mllsTime =  millis() ;
      bitTime  =  int((mllsTime) / (intervall * 1000)) ; 
      
      if(int(bitTime / 1) * 1  != lastTime1)
        {
          lastTime1   = int(bitTime / 1) * 1 ;     // per comatibilità con gli altri blocchi
          Serial.println("interval 1 : "); 
          rdTemp dati = readTemp();
          outputTemp(dati);  

        }
          
      if( int(bitTime / 2) * 2  != lastTime2 )
        {
          lastTime2   = int(bitTime / 2) * 2 ; 
          Serial.println("interval 2 : "); 
        }
    }

    rdTemp readTemp()
    {
      DeviceAddress mac; 
      sensors.getAddress(mac, 0);
      sensors.requestTemperatures(); 
      float temperatureC  = sensors.getTempC(mac);
      String wMac         = decodMac(mac); 
      rdTemp x            = {wMac, temperatureC};
      return x;
    }

    String decodMac(DeviceAddress baAddr)
    {
      String wMac = "" ;
      String sep  = "" ;
      for (uint8_t ii = 0; ii < 8; ii++) 
      {
        String mc = "00" + String(baAddr[ii], HEX) ;  
        int l = mc.length() ;
        mc = mc.substring(l -2,l); 
        wMac += sep + mc ;
        sep = ":" ;
      }
      return wMac ;
    }

    void outputTemp(rdTemp dati)
    {
      Serial.print(dati.sndMac);
      Serial.print(" -> ");
      Serial.print(dati.temp);
      Serial.print("°");
      Serial.println();
    }

I linguaggi che conosci sono ben diversi da C++.
Stai usando variabili locali che cessano di esistere quando la funzione termina.

float temperatureC
rdTemp x

Sono variabili che esistono solo dentro la funzione readTemp().
Dichiarale globali, ad esempio oneWireBus è una variabile globale accessibile da ogni funzione.

Ciao.

Ciao Maurotec
perdonami, ma le variabili per come le ho strutturate funzionano.
il valori memorizzati nella struct rdTemp vengono restituiti regolarmente.
Ciò che fa saltare la procedura è la chiamata alla libreria dallasTemterature getTempCByIndex(0), che stranamente funziona solo al primo ciclo.
In ogni caso grazie per avermi incuriosito con "&" in un commento, ho imparato l'uso dei puntatori.

La libreria in questione è una delle più utilizzate da lungo tempo... ti assicuro che non c'è nulla che non va.

Secondo me piuttosto stai facendo un po' dei tentativi "a caso" senza avere ben chiaro quello che stai facendo.

Ad esempio, a me sfugge la logica che c'è dietro a questo codice...
Se è necessario solo per temporizzare le letture, mi sembra eccessivamente complesso.

Adesso hai aggiunto una struct, quindi prevedi la gestione di più sensori, oppure è solo una tra le "centinaia" di prove che hai fatto?

Secondo me devi fare qualche passo indietro, guardare con calma e attenzione gli esempi inclusi nella libreria per farti un'idea di cosa e come è possibile farlo e partire da li per sviluppare il tuo progetto.

la struct era già nel primo sketch.
In effetti l'idea è quella di gestire n sonde, per poi trasmettere le variazioni di temperatura ad un server (evviva linux/php) da cui estrarre un registro con le temperature giornaliere.
il MAC formattato da decodMac() serve da chiave di ricerca frigorifero.
Sempre nell'idea (non è ancora un progetto definito), vorrei aggiungere il controllo dell'apertura porta.
Le diverse temporizzazioni mi servirebbero per verificare le temperature ogni bitTime
e la verifica connessione ogni 2 o più bitTime.
Inoltre voglio anche gestire Blynk per le notifiche.

Il codice da te evidenziato, sono d'accordo che non abbia senso, ma come scritto nel commento

      lastTime1   = int(bitTime / 1) * 1 ;     // per compatibilità con gli altri blocchi

serviva solo a mantenere la compatibilità con gli altri blocchi di calcolo degli intervalli.

Se hai di meglio da consigliarmi ben venga (possibilmente risparmiando sugli IF).

Ottima idea quella della struct, ma messa cosi "da sola" è inutile e non va bene per il tuo obiettivo: devi definire un tipo dati custom (ovvero la tua struct) e poi gestire n istanze di questo tipo dati.
Stai usando un microcontrollore che ti permetterebbe di usare senza problemi vettori dinamici, map, list etc etc, ma per ora è opportuno rimanere su cose più basilari e quindi direi di usare un array di struct allocato staticamente (quindi il numero di sensori deve essere noto a priori).

Usare una stringa come chiave di ricerca, a prescindere dal linguaggio utilizzato, non è una buona idea. I sensori Dallas DS18B20 sono identificati grazie ad un id univoco rappresentato da un intero a 64 bit (attenzione, non si tratta di un MAC address) il quale sarebbe il tipo di chiave perfetto in un sistema multi-sensore.

La libreria usa però il tipo dati custom typedef uint8_t DeviceAddress[8] ovvero un array di 8 byte, per ragioni di compatibilità perché in molte piattaforme non esiste l'intero a 64 bit.
In realtà, se i sensori rimangono sempre nella stessa posizione, si può semplicemente usare l'indice del bus che rimane costante ed è assegnato in funzione della posizione del device all'interno del bus OneWire durante il setup.

Per quanto riguarda la temporizzazione, come scritto anche altrove, io uso sempre un template del genere:

  // Variabile locale di tipo static cosi non interferisco con eventuali altre  variabili globali 
  // con lo stesso nome e posso mantenere un naming semplice e chiaro
  static uint32_t printTime; /
  if (millis() - printTime > 1000)
  {
    printTime = millis();
    etc etc
  } 

Un possibile esempio pratico di quello che intendo (ci sono altri mille modi possibili):

P.S.
Il simulatore online ha qualche problema con i gradi centigradi.