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
1 | if (counter[btKanalNr] = 255) {
|
sollte wohl
1 | if (counter[btKanalNr]==255) {
|
heißen. Ich schreibe es gerne so:
1 | if (255 == counter[btKanalNr]) {
|
Denn dann findet der Compiler den Fehler, falls man fälschlicherweise
nur ein einzelnes "=" schreibt.
Hallo Andreas,
das könntes es sein. Ich denke da einfach noch in VB.NET. Ich melde mich
später.
mfg Klaus
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
>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.
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
Klaus R. schrieb:
> Anbei zeige ich Dir noch mal die betroffenen aktuellen Sourcen.
.... und als altes, erfahrenes Mitglied hier im Forum
berücksichtigen wir auch immer brav die Hinweise zum Posten
von Sourcecode, gelle?
1 | Wichtige Regeln - erst lesen, dann posten!
| 2 | Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang
|
Wastl schrieb:
> gelle?
Aber natürlich. Aber so lang ist der Auszug doch nicht.😉
mfg Klaus.
>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/
Variablen, die in einem ISR-kontext verändert werden (z.B. deine
counter) müssen 'volatile' gekennzeichnet werden, damit der Compiler
nicht weg-optimiert.
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.
Danke, daran hatte ich gar nicht mehr gedacht.
mfg Klaus
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
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
Hallo Klaus,
versuche mal Krokodilmaul statt Gänsefüßchen wie bei den anderen
includes darüber.
Um esp_attr.h
Gruß Sven
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.
Andreas B. schrieb:
> ESP8266_RTOS_SDK
Sven K. schrieb:
> Hallo Klaus,
>
> versuche mal Krokodilmaul statt Gänsefüßchen wie bei den anderen
> includes darüber.
>
> Um esp_attr.h
>
> Gruß Sven
Dein Tipp genügt manchmal, hier aber nicht. Der Header ist wirklich
nicht greifbar.
mfg Klaus.
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
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
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.
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.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
|