D1Mini Interrupt Routine zählt nicht hoch.

OP #7635477
Lesenswert?

Hallo, ich möchte mit einem D1Mini S0 Stromzähler auslesen und die Werte in eine Datenbank ablegen.

Es sollen 4 Zähler ausgelesen werden. Im Array counter[4] werden die Impulse addiert. Nach 28 Sekunden wird der Zählerstand zur Datenbank übertragen. Es genügt hier ein Wertebereich von 0 bis 255.

// Zuordnung der GPIOs #define Kanal1 14 // D5 #define Kanal2 12 // D6 #define Kanal3 13 // D7 #define Kanal4 5 // D1

byte counter[4]; // 0 bis 255 byte count_wert[4];

//Die ISR darf keine Parameter annehmen und zurückgeben! ICACHE_RAM_ATTR void interruptRoutine1(){ interruptRoutine(0); }

Aus der Interrupt Routine rufe ich die eigentliche Zähler - Funtion auf. Die Interrupts werden ausgelöst. In der Form des nachfolgenden Codes steht counter[btKanalNr] immer auf 0. Auch wenn ich die Zeile "counter[btKanalNr] = 0;" auskommentiere tut sich nichts.

Füge ich am Ende der Funktion ein "counter[btKanalNr]++;" so wird tatsächlich um 1 hochgezählt. Das Verückte dabei ist jedoch, es bleibt dann dabei bei 1! Und wenn ich 2 mal direkt hintereinander inkrementiere dann bleibt es bei 2.

void interruptRoutine(int btKanalNr) { pulsIstZeit[btKanalNr] = millis(); if (pulsIstZeit[btKanalNr] - pulsStartIst[btKanalNr] >= endPrellZeit) { pulsStartIst[btKanalNr] = pulsIstZeit[btKanalNr]; if (counter[btKanalNr] = 255) { counter[btKanalNr] = 0; } else { counter[btKanalNr]++; } } }

void setup() {

pinMode(Kanal1, INPUT_PULLUP); .....

// ---- Starte Interrupts ---------- attachInterrupt(digitalPinToInterrupt(Kanal1), interruptRoutine1, FALLING); .....

}

Ist counter[btKanalNr] mal z.B. auf 2 gesetzt worden, so wird der Wert auch tatsächlich der Versand - Funktion übergeben. Weitere Impulse werden aber nicht hinzuaddiert. Es bleibt bei 2.

Wie komme ich da weiter? mfg Klaus

OP #7636008
Lesenswert?

Hallo Andreas, ich wollte einen Vergleich durchführen und habe eine Zuweisung codiert.

1
 if (counter[btKanalNr] = 255) {

Was das Programm dann wirklich macht kann ich nicht so recht nachvollziehen. Jedenfalls war das Verhalten verwirrend.

Mit der korrekten Schreibweise läuft das Programm einwandfrei.

1
 if (counter[btKanalNr] == 255) {

Dein Tipp die Parameter zu tauschen ist zumindest für Anfänger sehr hilfreich weil der Compiler dann eher einen Schreibfehler entdeckt.

1
 if (255 == counter[btKanalNr]) {

Die Ardurino IDE ist eben nicht mit Visual Studio von Microsoft zu vergleichen.

Nochmals, vielen Dank. mfg Klaus

#7636617
Lesenswert?

Was das Programm dann wirklich macht kann ich nicht so recht nachvollziehen. Jedenfalls war das Verhalten verwirrend.

Du könntest das auch einfach ausprobieren, eine Dummy if() Anweisung mit solch einer Zuweisung statt eines Vergleichs z.B. in der loop() einbauen und mit printf() Debug alles ausgeben was dich interessiert.

Einen weiteren Fallstrick sehe ich allerdings in deiner Interrupt-Service-Routine noch. Beim den ESPs mit externem seriellen Flash müssen die ISR mit der Compiler Directive IRAM_ATTR (obsolet=ICACHE_RAM_ATTR) versehen sein, damit der zugehörige Code eben nicht wie üblich aus dem Flash abläuft, sondern vorher ins RAM gecached wird und von dort abläuft (siehe auch Doku: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/spi_flash/spi_flash_concurrency.html#iram-safe-interrupt-handlers ). Für die IRS von oben stimmt das, aber es ist auch zwingend notwendig für Funktionen die innerhalb der IRS aufgerufen werden. In deinem Fall also auch für interruptRoutine(). Desweiteren findet sich in der Doku, dass die "globalen" Variablen auf welche die IRS zugreift per DRAM_ATTR Direktive abgesichert werden sollen.

OP #7636736
Lesenswert?

Hallo Andreas, ich verwende nicht einen ESP32 sondern einen D1 Mini - ESP8266. Anbei zeige ich Dir noch mal die betroffenen aktuellen Sourcen.

1
unsigned long pulsStartIst[4];
2
unsigned long pulsIstZeit[4];
3
const byte endPrellZeit = 100;
4

5
//Die ISR darf keine Parameter annehmen und zurückgeben!
6
ICACHE_RAM_ATTR void interruptRoutine1() {
7
  interruptRoutine(0);
8
}
9
ICACHE_RAM_ATTR void interruptRoutine2() {
10
  interruptRoutine(1);
11
}
12
ICACHE_RAM_ATTR void interruptRoutine3() {
13
  interruptRoutine(2);
14
}
15
ICACHE_RAM_ATTR void interruptRoutine4() {
16
  interruptRoutine(3);
17
}
18

19
void interruptRoutine(int btKanalNr) {
20
  pulsIstZeit[btKanalNr] = millis();
21

22
  if (pulsStartIst[btKanalNr] > 0) {
23
    if (pulsIstZeit[btKanalNr] - pulsStartIst[btKanalNr] >= endPrellZeit) {
24
       pulsStartIst[btKanalNr] = pulsIstZeit[btKanalNr];
25
      if (counter[btKanalNr] == 255) {
26
        counter[btKanalNr] = 0;
27
      } else {
28
        counter[btKanalNr]++;
29
      } 
30
    } else {
31
      //Serial.print("Geprellt.");
32
      //Serial.print("\n");
33
    }
34
  } else {
35
    pulsStartIst[btKanalNr] = pulsIstZeit[btKanalNr];    
36
  }
37
}
38
void setup() {
39
....
40
 //Vaiablen initialisieren.
41
  for (byte n = 0; n <= 3; n++) {
42
    counter[n] = 0;
43
    count_wert[n] = 0;
44
    pulsStartIst[n] = 0;
45
    pulsIstZeit[n] = 0;
46
  }
47

48
 pinMode(Kanal1, INPUT_PULLUP);
49
  pinMode(Kanal2, INPUT_PULLUP);
50
  pinMode(Kanal3, INPUT_PULLUP);
51
  pinMode(Kanal4, INPUT_PULLUP);
52

53
  // ---------------------- Starte Interrupts ---------------------------------------------
54
  attachInterrupt(digitalPinToInterrupt(Kanal1), interruptRoutine1, FALLING);
55
  attachInterrupt(digitalPinToInterrupt(Kanal2), interruptRoutine2, FALLING);
56
  attachInterrupt(digitalPinToInterrupt(Kanal3), interruptRoutine3, FALLING);
57
  attachInterrupt(digitalPinToInterrupt(Kanal4), interruptRoutine4, FALLING);

Also ICACHE_RAM_ATTR ist demnach schon obsolet ...

1
ICACHE_RAM_ATTR void interruptRoutine1() {
2
  interruptRoutine(0);
3
}

... und ist durch IRAM_ATTR zu ersetzen.

1
IRAM_ATTR void interruptRoutine1() {
2
  interruptRoutine(0);
3
}

Ferner muß ich noch die Funktion interruptRoutine mit IRAM_ATTR aufrufen.

1
IRAM_ATTR void interruptRoutine(int btKanalNr) {
2
  pulsIstZeit[btKanalNr] = millis();
3

4
  if (pulsStartIst[btKanalNr] > 0) {
5
    if (pulsIstZeit[btKanalNr] - pulsStartIst[btKanalNr] >= endPrellZeit) {
6
       pulsStartIst[btKanalNr] = pulsIstZeit[btKanalNr];
7
      if (counter[btKanalNr] == 255) {
8
        counter[btKanalNr] = 0;
9
      } else {
10
        counter[btKanalNr]++;
11
      } 
12
    } else {
13
      //Serial.print("Geprellt.");
14
      //Serial.print("\n");
15
    }
16
  } else {
17
    pulsStartIst[btKanalNr] = pulsIstZeit[btKanalNr];    
18
  }
19
}

Andreas B. schrieb:

Desweiteren findet sich in der Doku, dass die "globalen" Variablen auf welche die IRS zugreift per DRAM_ATTR Direktive abgesichert werden sollen.

1
DRAM_ATTR unsigned long pulsStartIst[4];
2
DRAM_ATTR unsigned long pulsIstZeit[4];

Ich nehme mal an das die Konstante nicht mit DRAM_ATTR zu deklarieren ist.

1
const byte endPrellZeit = 100;

Ich nutze als Quelle gerne https://randomnerdtutorials.com . Dort wird DRAM_ATTR wohl nirgendwo eingesetzt. Das heißt natürlich auch wiederum nichts.

Bitte prüfe mal. mfg Klaus

#7637235
Lesenswert?

Bitte prüfe mal.

Hhmm, ich hab die Schaltung grad nicht da und auch keine Ardunino IDE installiert. (ich verwende für ESPs VisualStudioCode + Platformio Extension)

Aber Du könntest doch am lebenden Objekt selber testen ob der Code ohne Warnungen durch den Compiler/Linker läuft und ob das Programm dann genau tut was Du erwartest.

Wie gesagt, die Info bezüglich DRAM seht so in den Espressif Doku. Ich kann mir vorstellen, dass die FW auch ohne DRAM Directive funktioniert und du gar nix merkst. Aber es ist eben auch wahrscheinlich, das die SW sproadisch Quatsch macht und du nicht weißt warum.

Noch ein kleiner Hinweis, was du evtl. verbessern könntest. Die millis() Routine liefert einen unsigned long zurück, d.h. nach ca. 50 Tage gibt es einen Überlauf. Durch Umstellen der Entprellung kannst du das Problem vermeiden. Einfach mal nach overflow+millis() googlen oder hier z.B. eine Erklärung: https://www.norwegiancreations.com/2018/10/arduino-tutorial-avoiding-the-overflow-issue-when-using-millis-and-micros/

OP #7637462
Lesenswert?

Andreas B. schrieb:

Noch ein kleiner Hinweis, was du evtl. verbessern könntest. Die millis() Routine liefert einen unsigned long zurück, d.h. nach ca. 50 Tage gibt es einen Überlauf. Durch Umstellen der Entprellung kannst du das Problem vermeiden. Einfach mal nach overflow+millis() googlen oder hier z.B. eine Erklärung: https://www.norwegiancreations.com/2018/10/arduino-tutorial-avoiding-the-overflow-issue-when-using-millis-and-micros/

Der Überlauf wird tatsächlich vorkommen. Der Prozessor soll solange laufen wie Strom vorhanden ist und selbst dann hängt er noch mit anderen Verbrauchern an einer USV dran.

In der Tat ist es ja so, daß der D1Mini selber nur rollierend Werte von 0 bis 255 sendet. Der S0 Stromzähler liefert 1000 Impulse pro 1 kWh. Das sind bei 1 kW = 0,277 Impulse/s. Mein Programm für die Einarbeitung in die Datenbank liest schon seit über 10 Jahren einen anderen S0 Stromzähler aus. Diesen Überlauf habe ich schon behandelt.

Bei D1Mini muß ich mir jedoch noch ein neues Konzept einfallen lassen. Gut das Du mich nochmal auf diese Problematiken hingewiesen hast, denn ich war schon sehr zufrieden.

Ich liebe Teamwork und hoffe Dir auch mal so helfen zu können. mfg Klaus

OP #7637477
Lesenswert?

Hallo Andreas,

1
DRAM_ATTR  byte counter[4];  // 0 bis 255
2
DRAM_ATTR  byte count_wert[4];

Erzeugt eine Fehlermeldung: Compilation error: 'DRAM_ATTR' does not name a type; did you mean 'IRAM_ATTR'?

1
void IRAM_ATTR gpio_isr_handler(void* arg)
2
{
3
  DRAM_ATTR  byte counter[4];  // 0 bis 255
4
  DRAM_ATTR  byte count_wert[4];
5
}

Erzeugt diese Fehlermeldung: Compilation error: 'DRAM_ATTR' was not declared in this scope; did you mean 'IRAM_ATTR'?

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/memory-types.html#how-to-place-code-in-iram

Im oben angegeben Link wird an anderer Stelle "esp_attr.h" eingebunden.

1
#include <ESP8266WiFi.h>
2
#include <ESP8266WiFiMulti.h>
3
#include <ESP8266HTTPClient.h>
4
#include <WiFiClient.h>
5
#include "esp_attr.h"

Fehlermeldung: Compilation error: esp_attr.h: No such file or directory

Wenn es das ist was mir fehlt, wie/wo binde ich das File ein? mfg Klaus

#7637492
Lesenswert?

ich befürchte, dass das ESP8266_RTOS_SDK nicht in den Arduino Libs enthalten ist. Daher fehlt das Include File natürlich. Dann hab ich für den ESP8266 leider auch keine gute Lösung. Sorry für den falschen Hinweis, dann wird es wohl auch ohne gehen, so wie in anderen Beispielen, die du im Netz gefunden hast.

OP #7637684
Lesenswert?

Andreas B. schrieb:

ich befürchte, dass das ESP8266_RTOS_SDK nicht in den Arduino Libs enthalten ist. Daher fehlt das Include File natürlich. Dann hab ich für den ESP8266 leider auch keine gute Lösung. Sorry für den falschen Hinweis, dann wird es wohl auch ohne gehen, so wie in anderen Beispielen, die du im Netz gefunden hast.

Ja, man müßte wohl mit dem SDK und RTOS arbeiten. Das geht mir zu weit. Aber es funktionierte ja schon zuvor zufriedenstellend. Besten Dank. mfg Klaus

OP #7637703
Lesenswert?

Pit S. schrieb:

Variablen, die in einem ISR-kontext verändert werden (z.B. deine counter) müssen 'volatile' gekennzeichnet werden, damit der Compiler nicht weg-optimiert.

Ich habe mir die Referenz von 'volatile' angeschaut.

https://www.arduino.cc/reference/en/language/variables/variable-scope-qualifiers/volatile/

1
Declaring a variable volatile is a directive to the compiler. The compiler is software which translates your C/C++ code into the machine code, which are the real instructions for the Atmega chip in the Arduino.
2

3
Specifically, it directs the compiler to load the variable from RAM and not from a storage register, which is a temporary memory location where program variables are stored and manipulated. Under certain conditions, the value for a variable stored in registers can be inaccurate.
4

5
A variable should be declared volatile whenever its value can be changed by something beyond the control of the code section in which it appears, such as a concurrently executing thread. In the Arduino, the only place that this is likely to occur is in sections of code associated with interrupts, called an interrupt service routine.

Ich verstehe das so, daß 'volatile' so etwas wie 'DRAM_ATTR' bewirkt. Zumindest wird das Programm dadurch sicherer. mfg Klaus

#7637796
Lesenswert?

Klaus R. schrieb:

Ich habe mir die Referenz von 'volatile' angeschaut.

...

Ich verstehe das so, daß 'volatile' so etwas wie 'DRAM_ATTR' bewirkt.

Nein. Der Unterschied ist zum einen, daß volatile ein Schlüsselwort ist, das jeder C/C++ Compiler verstehen muß. DRAM_ATTR ist hingegen ein Makro, dessen Bedeutung nicht vom Standard festgelegt wird. Es sieht aus wie eine Festlegung der Speicherklasse.

Google (https://www.google.com/search?&q=DRAM_ATTR) verrät, daß es sich bei DRAM_ATTR um ein Attribut zur Steuerung der Lokalisierung der Variable im DRAM handelt.

Zumindest wird das Programm dadurch sicherer.

Auch nicht. volatile verbietet dem Compiler bloß einige Optimierungen. Spezifisch die Optimierung, daß der Compiler davon ausgehen darf, daß die Variable nur innerhalb des betrachteten Programmcodes verändert werden kann. Deswegen muß er die Variable jedes Mal wieder vom Speicher lesen. Auch wenn er bereits eine Kopie in einem Register hat.

Einfach alle Variablen als volatile zu deklarieren, macht das Programm nicht sicherer, sondern nur langsamer.

#7637895
Lesenswert?

Axel S. schrieb:

Deswegen muß er die Variable jedes Mal wieder vom Speicher lesen. Auch wenn er bereits eine Kopie in einem Register hat.

Um es präziser zu sagen: Der Compiler muss für die Variable einen Platz im Arbeitsspeicher (egal, ob Flash, RAM oder Register) fest reservieren. Und: Der Compiler muss dafür sorgen, dass der aktuelle Wert bei jedem Zugriff darauf von diesem festen Ort gelesen bzw. dorthin geschrieben wird.

Mit Registern hat das nichts zu tun. Theoretisch könnte der Compiler auch ein Register dafür fest reservieren; praktisch tut er das nur bei Prozessoren mit extrem vielen Registern. Wichtig ist in jedem Fall nur die oben erwähnte Zugriffstaktik, egal ob Register oder Arbeitsspeicher:

volatile int aa; Foo() { int bb; bb = aa; if (bb == aa) { x = 99; } }

Ohne 'volatile' wäre es dem Compiler erlaubt, die 'if'-Bedingung wegzuoptimieren, und das würde schieflaufen, falls eine Interruptroutine den Wert von 'aa' nach der Zuweisung und vor dem Auswerten der if-Bedingung verändern würde. Ob bzw. wann so ein Interrupt kommt, kann man aber nicht vorhersagen. Das Beispiel ist natürlich nicht sinnvoll, aber es zeigt, dass ein Wegoptimieren mancher Teile ungeahnte Folgen haben kann.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren