ESP32 mehrfacher Interrupt Toucheingabe

Hallo Forum,

es soll bspw. Lautstärke oder Helligkeit in Stufen geregelt werden (im Folgenden der "Wert").
Aufs Wesentliche reduzierter Code: Toucheingaben an zwei Touch-Pins werden mittels Interrupt verarbeitet. Einer erhöht den Wert, einer verringert den Wert.
Ein Timer (nicht delay) sorgt dafür, dass jeweils die Toucheingabe für 500ms blockiert ist (ansonsten reagiert der Interrupt bei einer Berührung mehrfach).

Problem: Berühre ich einen der Touchpins (T3, angeschlossener Draht), funktioniert es wie es soll. Bei jeder Berührung, bzw. alle 500ms wird der Wert erhöht. Berühre ich nun den anderen (T0), wird zuerst der Interrupt des Pins T0 ausgeführt und direkt darauf der für T3 ausgeführt (wieso?). Berühre ich wieder T0 wird der Wert erwartungsgemäß verringert. Berühre ich wieder T3, wird wieder zuerst der Interrupt für T0 ausgeführt, gefolgt von T3. Das hat zur Folge, dass bei der ersten Berührung des jeweils anderen Kontaktes, der Wert verringert und erhöht wird (quasi gleich bleibt).

Ausgabe des Seriellen Monitors, ich habe etwas kommentiert...

Ready...
  // T3 wird berührt, Interrupt-Routine für T3 wird ausgeführt.
T3 Interrupt activated  // Serial.Print innerhalb Interrupt-Funktion, setzt außerdem Interrupt-Flag T3 auf true.
T3 processed in loop: 1 // Textausgabe innerhalb Loop() (durch Abfrage Interrupt-Flag T3). Wert um 1 erhöht.
  // T3 wird berührt, Interrupt-Routine für T3 wird ausgeführt.
T3 Interrupt activated  // Gleich wie eben...
T3 processed in loop: 2 // Gleich wie eben...
  // T0 wird berührt, Interrupt-Routine für T0 wird ausgeführt.
T0 Interrupt activated  // Serial.Print innerhalb Interrupt-Funktion, setzt außerdem Interrupt-Flag T0 auf true.
T3 Interrupt activated  // ######## WESHALB WIRD HIER DIE INTERRUPT-ROUTINE FÜR T3 AUSGEFÜHRT? ########
T0 processed in loop: 1 // Textausgabe innerhalb Loop() (durch Abfrage Interrupt-Flag T0). Wert um 1 verringert.
T3 processed in loop: 2 // ######## Durch T3 Interrupt wird Wert um 1 erhöht ########
  // T3 wird berührt, aber zuerst die Interruptroutine für T0 ausgeführt...
T0 Interrupt activated  // Dasselbe Verhalten folgt immer dann, wenn zuvor der andere Touch-Pin berührt wurde.
T3 Interrupt activated
T0 processed in loop: 1
T3 processed in loop: 2
  // T0 wird berührt
T0 Interrupt activated
T3 Interrupt activated
T0 processed in loop: 1
T3 processed in loop: 2
  // T0 wird berührt
T0 Interrupt activated
T0 processed in loop: 0
  // T0 wird berührt  --> Folgt eine Berührung desselben Touchpins, läuft es wie es sollte.
T0 Interrupt activated
T0 processed in loop: -1

Code:

const uint8_t threshold = 60;
bool touchedT0 = false; // Touch 0 ist GPIO4
bool touchedT3 = false; // Touch 0 ist GPI15

const long touchDelay = 500; //ms
uint8_t brightnessLED = 0;
int testWert = 0;

volatile unsigned long sinceLastTouchT0 = 0;
volatile unsigned long sinceLastTouchT3 = 0;

void T0wasActivated()
{
  if (millis() - sinceLastTouchT0 < touchDelay) return;
  sinceLastTouchT0 = millis();
  Serial.println("T0 Interrupt activated");
  touchedT0 = true;
}

void T3wasActivated() 
{
  if (millis() - sinceLastTouchT3 < touchDelay) return;
  sinceLastTouchT3 = millis();
  Serial.println("T3 Interrupt activated");
  touchedT3 = true;
}

void setup() {
  Serial.begin(115200);
  touchAttachInterrupt(T0, T0wasActivated, threshold);
  touchAttachInterrupt(T3, T3wasActivated, threshold);  
  delay(1000);
  Serial.println("Ready...");
}

void loop() 
{
  if (touchedT0) 
  {
    touchedT0 = false;
    Serial.print("T0 processed in loop: "); Serial.println(testWert=testWert-1);
  }

  if (touchedT3) 
  {
    touchedT3 = false;
    Serial.print("T3 processed in loop: "); Serial.println(testWert=testWert+1);
  }
}

Grüße René

Hi, mit ein wenig Hilfe habe ich die Lösung gefunden. So kompakt der code in der Interrupt-Funktion auch ist, er ist nicht kompakt genug. Bei einem Interrupt sollte möglichst nichts passieren, außer ein Flag zu setzen.
Der richtige Weg ist, die Zeitspanne seit dem letzten Touch mit einem delay-Wert innerhalb der loop()-Funktion zu vergleichen.

Im Code unten wird der Interrupt bei jedem Touch ausgeführt (Flag wird auf true gesetzt) Dieses Flag wird in der loop() abgefragt. Ist es true, wird es als erstes wieder false gesetzt (dass der Touch gedrückt wurde, wurde schließlich erkannt).
Erst dann findet in einer ausgelagerten Funktion die Abfrage statt, ob seit dem letzten Touch genügend Zeit vergangen ist: if (touchDelayComp(sinceLastTouchT0)). Falls die Funktion false zurück liefert, ist die if-Abfrage beendet und es geschieht nichts. Liefert sie jedoch true zurück, wird in diesem Fall der Wert "Value" modifiziert. Der Zeitpunkt der "erfolgreichen" Toucheingabe wird für den nächsten Vergleich gespeichert: sinceLastTouchT0 = millis();.

#include <Arduino.h>

const uint8_t threshold = 60;
bool touchedT0 = false; // Touch 0 ist GPIO4
bool touchedT3 = false; // Touch 0 ist GPI15

const long touchDelay = 350; //ms
int value = 0;

volatile unsigned long sinceLastTouchT0 = 0;
volatile unsigned long sinceLastTouchT3 = 0;

bool touchDelayComp(unsigned long);

void IRAM_ATTR T0wasActivated() { touchedT0 = true; }
void IRAM_ATTR T3wasActivated() { touchedT3 = true; }

void setup() {
  Serial.begin(115200);
  delay(1000);
  touchAttachInterrupt(T0, T0wasActivated, threshold);
  touchAttachInterrupt(T3, T3wasActivated, threshold);
  Serial.println("Ready...");
}

void loop() 
{
  if (touchedT0) 
  {
    touchedT0 = false;
    if (touchDelayComp(sinceLastTouchT0))
    {
      value = value - 1;
      Serial.print("T0: "); Serial.println(value);
      sinceLastTouchT0 = millis();
    }
  }

  if (touchedT3) 
  {
    touchedT3 = false;
    if (touchDelayComp(sinceLastTouchT3))
    {
      value = value + 1;
      Serial.print("T3: "); Serial.println(value);
      sinceLastTouchT3 = millis();
    }
  }
}

bool touchDelayComp(unsigned long lastTouch)
{
  if (millis() - lastTouch < touchDelay) return false;
  return true;
}

So kompakt der code in der Interrupt-Funktion auch ist, er ist nicht kompakt genug

Kompakt ist eventuell nicht der richtige Ansatz.
Aber insbesondere dein Serial.print ist verboten, weil es eine Wartefunktion enthält.

Mal abgesehen davon, dass der Ansatz "Manuelle Interaktion per Interrupt" generell auf ein falsches Interrupt-Verständnis hindeutet.

Hallo Michael,

die Interrupt Funktionen sind im letzten Beispiel frei von Serial.prints. Das Verhalten des Codes auf einem ESP32 ändert sich für den Nutzer allerdings nicht, ob die "verbotenen" Serial.prints enthalten sind oder nicht.
Abgesehen davon sind sie nur im Code enthalten, um das Verhalten zu untersuchen und zu sehen was passiert. Im produktiven Einsatz, in einem Programm, das nicht nur +1 und -1 rechnet, werden sie nicht enthalten bzw. nicht aktiv sein.

Ich denke es ist nicht zielführend, einem die Worte im Mund herumzudrehen. Wer sich ansatzweise mit dem Thema Interrupt beschäftigt, sollte wissen wie er es einzusetzen hat. Und wenn eine spürbare Verzögerung des Programmablaufs nicht stört und nicht gerade ein lebenserhaltendes Gerät daran hängt, dann wird es demjenigen auch genügen.
Wer mal ein paar Zeilen programmiert hat, wird wissen, dass wenige Zeichen, gerade bei der Nutzung von Bibliotheken, nicht bedeuten, dass die Abläufe auf dem Controller nicht sehr umfangreich sein können.

Die einzige Funktion, die hier zum Interrupt führt, ist die Nutzereingabe. Ganz offensichtlich ist das ein erwünschtes und typisches Einsatzgebiet für eine solche Systematik.

Der Thread sollte Leuten helfen, die sich auf demselben Weg befinden und evtl. teilweise falschen Empfehlungen im Netz folgen. Wenn du die Welt mit deinem Wissen erleuchten möchtest, dann sage den Leuten wie es geht - und nicht wie es nicht geht. Poste deine Quellen oder beteilige dich konstruktiv.

Grüße René

1 Like