Erkennung S0-Schnittstellen-Impuls

Guuten Tach, Jung´s!
Euch geht´s noch allen gut? Alles im grünen Bereich und reichlich Erfahrungen
mit dem Arduino gesammelt?
Rabenauge, was macht Deine GPS-Karre? Findet sie jetzt den kürzesten und
genausten Weg nach Hause?

Bin ja lange nicht mehr dabei gewesen und jetzt verzweifel ich an etwas
leichten. Bei mir will der Groschen nicht fallen...

Ich habe hier so meine Probleme mit dem erkennen eines S0-Impuls am Mega 2560.
Ich habe nachfolgenden Sketch, der die Impulse von 8-S0-WSZ empfängt.
Das funktioniert bis jetzt recht zuverlässig und genau...

/** Hilfsprogramm zur Auswertung der Zähler
* Maximal 8 SO-Zähler können angeschlossen werden, die mit folgendem Protokoll
* an die serielle Schnittstelle ausgegeben werden:
*
* byte0  byte1     byte2     byte3      ...
* A-H    0-9       0-9       0-9        n
* Kanal  Millis                         Newline
*
* Die Millis sind dabei die Millisekunden zwischen den letzten beiden Low-High-Flanken
* auf dem angegebenen Kanal.
*/
const byte counterPins[8] = { 2,3,4,5,6,7,8,9 };
unsigned long millisBetween[8];
unsigned long lastMillis[8];
byte lastState[8];

void setup() {
  for (byte i = 0; i < sizeof(counterPins); i++) {
     pinMode(counterPins[i], INPUT);
     digitalWrite(counterPins[i], LOW);
     millisBetween[i] = 0;
     lastMillis[i] = 0;
     lastState[i] = 0;
  }
  Serial.begin(9600);
}

void loop() {
  unsigned char bitMaskToSend = 0;
  unsigned long time = millis();
  for (byte i = 0; i < 8; i++) {
     byte val = digitalRead(counterPins[i]);
     if (val == HIGH && lastState[i] == LOW) {
       millisBetween[i] = time-lastMillis[i];
       lastMillis[i] = time;
       bitSet(bitMaskToSend, i);
     }
     lastState[i] = val;
  }
  for (byte i = 0; i < 8; i++) {
     unsigned long dataToWrite = millisBetween[i];
     if (bitRead(bitMaskToSend,i)) {
       Serial.print((char)('A'+i));
       Serial.println(dataToWrite);
     }
  }
}

An den S0+ habe ich 5v anliegen, S0- geht über einen 4,5k gegen Masse und auf
die Pin´s. Das funktioniert.

Nun habe ich:

const byte counterPins[8] = { 2,3,4,5,6,7,8,9 };

in

const byte counterPins[1] = {30};

geändert- und den Rest dazu, das funktioniert auch.

Die Impulse werden zuverlässig empfangen. Die Länge des Impuls beträgt 30ms.
Bei 5v kommt da nicht mehr viel an, aber es wird zuverlässig erkannt.

Jetzt mein Problem.
Wie kann ich Pin 30 mit einer einfachen "if-Abfrage" abfragen:

If (pin30 == xx)
{
mache was
}

Frage ich den Pin mit "if" ab, dann werden die Impulse nicht erkannt. Selbst
wenn der Sketch nur aus der Abfrage besteht.
Warum funktioniert das mit dem Sketch, aber nicht mit einer "if-Abfrage"?
Ich kapier´s nicht?
Wie kann ich das denn mit "if" erschlagen? Das muss doch möglich sein.
Gruß und Dank
Andreas

Quelle des Ganzen:
https://blog.elektrowolle.de/2011/07/26/s0-messwerterfassung-von-stromzahlern/

Das sollte so aussehen:

If (digitalRead(pin30) == xx)
{
mache was
}

Wie kann ich Pin 30 mit einer einfachen "if-Abfrage" abfragen:

if(digitalRead(counterPins[0]) == HIGH)

Sowas? Oder mit dem Index einer for-Schleife. Wenn du nur einen Pin hast, kannst du die Arrays auch weglassen

Das ist übrigens überflüssig:

millisBetween[i] = 0;
lastMillis[i] = 0;
lastState[i] = 0;

Globale Variable werden automatisch mit 0 initalisiert

Außerdem kann man die globalen Variablen auch so machen dass sie automatisch skalieren:

const byte counterPins[] = { 2,3,4,5,6,7,8,9 };
unsigned long millisBetween[sizeof(counterPins)];
unsigned long lastMillis[sizeof(counterPins)];
byte lastState[sizeof(counterPins)];

EDIT:
Die 8 in der letzten for-Schleife sollte man auch durch sizeof() ersetzen:

for (byte i = 0; i < 8; i++)

Dann werden nicht verwendete Kanäle nicht gesendet

Hallo,
wie man die Pin´s abfragt sollte ich eigentlich wissen- so wie Ihr es hier
beschrieben habt.
Nachdem ich die Verdrahtung neu aufgebaut habe, funktioniert das auch. Weiß der
Teufel, was dort vorher nicht gestimmt hat.

Die Verbesserungen von Serenifly sind ja nett gemeint, aber ich glaube nicht,
das der Sketch es schafft jeden Impuls zu empfangen.

Wenn wir einmal von 230V ausgehen und 16A max. Last, dann sind wir bei
3680P/Wh.
Bei meinem Zähler wären das dann 7360 Impulse/Std, macht 2 Impulse/Sec.
Das sind dann alle 500ms/Impuls.
Jetzt haben wir aber 8 Zähler, bleibt für einen Zähler eine Verarbeitungszeit von 63ms.
Der Sketch benötigt für einen Zähler 17ms. Bleibt ein Rest von 46ms- das muß
dann reichen für Berechnung und Darstellung auf einem Display.

Läßt man nun noch eine Uhr für die Zeit und eine SD für den Zählerstand
mitlaufen, dann wird die Kiste verdammt eng- glaube ich.
Gruß und Dank
Andreas

dann wird die Kiste verdammt eng- glaube ich.

Dann kannste deinen AVR noch reichlich in den Schlaf schicken.....

Da wird nix eng!
Es sei denn du baust blockierenden Code.

wie man die Pin´s abfragt sollte ich eigentlich wissen- so wie Ihr es hier
beschrieben habt.

if(digitalRead(counterPins[0]) == HIGH)

Ich verstehe nicht, warum man einen bool Wert mit HIGH vergleichen wollte...
Ein Beschäftigungsmaßnahmen für arbeitslose AVRs?

if(digitalRead(counterPins[0]))

SkobyMobil:
Jetzt mein Problem.
Wie kann ich Pin 30 mit einer einfachen "if-Abfrage" abfragen:

If (pin30 == xx)
{
mache was
}

Frage ich den Pin mit "if" ab, dann werden die Impulse nicht erkannt.

Dein Problem scheint zu sein, dass Du bisher die einfache Schalterauswertung noch nicht verstanden hast, denn der S0-Ausgang ist nichts anderes als ein Schalter.

Und ob Du die Abfrage nun "Schalter wird gedrückt" oder "Impuls wird erkannt" nennst, spielt für die Software keinen Unterschied.

Erkennen mußt Du dabei nicht den aktuellen "Zustand" des Schalters, sondern Du musst die "Zustandsänderung" erkennen, also wenn der Pegel wechselt.

In dem von Dir verlinkten Codebeispiel finde diese Erkennung der Zustandsänderung ("State change detection") hier statt:

 byte val = digitalRead(counterPins[i]);
  if (val == HIGH && lastState[i] == LOW) 
  {
    // hier hat sich der Zustand geändert und der Impuls ist erkannt worden 
  }

Und wenn Du die Zustandsänderung erkannt hast, mußt Du den letzten ausgelesenen Zustand in einer Variablen merken, das geschieht danach mit diesem Code:

lastState[i] = val;

combie:
Ich verstehe nicht, warum man einen bool Wert mit HIGH vergleichen wollte...
Ein Beschäftigungsmaßnahmen für arbeitslose AVRs?

if(digitalRead(counterPins[0]))

:confused:

Du hast noch nie Assembler programmiert, oder? Da musst ein Vergleich irgendeiner Art stattfinden. Anders geht es nicht. Ob man da == HIGH/LOW, oder in anderen Fällen true/false schreibt hat mehr damit zu tun wie viel man tippen will, oder ob man es lesbarer findet.

Außerdem liefert digitalRead() einen int!

Mich wundert dass der Code nicht absolut identisch ist, aber der Unterschied ist minimal.

if (digitalRead(2) == HIGH)
{ ... }

wird zu:

ldi r24, 0x02
call 0x180 ; 0x180 <digitalRead>
sbiw r24, 0x01 ; 1
brne .+12     ; 0x12c <loop+0x16>
if (digitalRead(2))
{ ... }

wird zu:

ldi r24, 0x02
call 0x180 ; 0x180 <digitalRead>
or r24, r25
breq .+12     ; 0x12c <loop+0x16>

sbiw (subtract immediate from word) dauert 2 Zyklen, da der Befehl einen 16-Bit Operanden hat. Or dauert nur eine Zyklus.

Glückwunsch. Du hast einen Takt gespart.

Hallo,
da haben mich die "richtigen" erwischt- jurs und Serenifly!

Bei euch muss man immer höllisch aufpassen was man macht, mit jedem Tip auf
der Tastatur verschwindet die vertraute Sicherheit…

ich verstehe das schon- glaube ich…
Mit dem Beispiel-Sketch wurden Impulse erkannt.

Mit meiner einfachen Abfrage wurde kein Impuls erkannt-
mein Messgerät erkannte aber einen Impuls am Pin.

Es ging nur erst einmal darum, diesen Impuls zu erkennen- nicht darum, wie er
später zu verwerten ist.

"S0-Ausgang ist nichts anderes als ein Schalter"
Im weitesten Sinne gebe ich Dir da recht, die ImpulsLänge beträgt 30ms.

Ich weiß noch nicht, wie genau es ist. Ich bin mir noch nicht recht im klaren
darüber, ob die ImpulsLänge (30ms) mit in die Rechnung einbezogen werden
muss.
Der S0-Impuls wird von einem geeichten Zähler übertragen. Nach knapp 3 Tagen
zeigte dieser Zähler bei ca. 15,47P/Wh einen Stand von 930Wh an.
Der Arduino einen von 924Wh. Das könnten im Jahr 730Wh sein.

Woher diese "Ungenauigkeit" kommt weiß ich noch nicht. Das könnten die millis()
sein, Rundungsfehler oder eben eine falsche Impulsberechnung.
Gruß und Dank
Andeas

SkobyMobil:
Nach knapp 3 Tagen
zeigte dieser Zähler bei ca. 15,47P/Wh einen Stand von 930Wh an.
Der Arduino einen von 924Wh. Das könnten im Jahr 730Wh sein.

Was bedeutet die Einheit "P/Wh", was ist das?

Und was ist das für ein Zähler?
Wenn das mal angenommen ein Zähler ist, der 1000 Impulse pro kWh liefert, also 1 Impuls pro Wh, dann ist der Unterschied zwischen 930 Wh und 924 Wh ein Unterschied von 6 Impulsen, die nicht gezählt wurden.

Die Frage ist: Was könnte in Deinem Code verhindern, dass diese 6 Impulse gezählt worden sind?
Hast Du delay() Aufrufe in Deinem Programm drin?
Hast Du irgendwelche Aktionen, die länger als eine handvoll Millisekunden dauern, so dass ein 30 Millisekunden langer Impuls manchmal verpaßt werden könnte?

Das ist schwierig zu sagen, ohne den VOLLSTÄNDIGEN Code des Programms zu sehen.

Im übrigen ist Dein Programm von Dir ausdrücklich dafür vorgesehen, dass es um so fehlerhafter und ungenauer arbeitet, je größer die Zahlen werden. Und irgendwo zwischen 16 Mio. und 17 Mio. Wattstunden, also ca. zwischen 16000 und 17000 kWh, die Du mit Deinem Programm zählst, wird der Zähler komplett stehenbleiben und gar nicht mehr weiterlaufen. Der Grund liegt darin, dass Du Impulse, also ganze Zahlen, in float-Gleitkommavariablen zählst, die nur eine sehr begrenzte Auflösung und Genauigkeit haben.

Hallo,
P/Wh, P ist die Leistung in Wh.
Der Zähler gibt per kWh 2000 Impulse raus.
Eltako-Zähler-Stand 930, Arduino-Zähler-Stand 1848, Mess-Zeitraum 59:10Std.

"dass Du Impulse, also ganze Zahlen, in float-Gleitkommavariablen zählst"

int 5 bleibt doch auch float 5.0 immer?
float 5.0*0.5 bleibt doch 2.5, nach .5 kommt doch nichts mehr?
Was ist da also ungenau, oder wie geht´s genauer?

Ich nehme ja die gemessene Zeit auf.
116809ms bei 15.41Wh

Wenn jetzt ein Impuls nicht erfast wird, dann müßten die 116809ms sich doch
ca. verdoppeln?
Wenn ich die 116808ms mit einer min/max Variable festhalte, dann könnte man
doch erkennen ob ein Impuls fehlt?

Jetzt habe ich z.B. 117744ms bei 15.29Wh

Ich könnte doch max auf 116808 legen, wenn max sich erhöht ist maxneu 117744
Wenn max > 200000, z.B.
dann
ZlImp++

So könnte man doch einen fehlenden Impuls erwischen?
Ich habe Dir den ganzen Sketch einmal dran gehängt.
Gruß und Dank
Andreas

SkobyMobil:
P/Wh, P ist die Leistung in Wh.

Eine Leistung P wird allerdings in Watt [W] gemessen, und nicht in [Wh] wie eine Arbeit.

SkobyMobil:
Der Zähler gibt per kWh 2000 Impulse raus.
Eltako-Zähler-Stand 930, Arduino-Zähler-Stand 1848, Mess-Zeitraum 59:10Std.

Machst Du da einen Vergleich desselben Zählers, indem Du

  • einmal die Differenz des angezeigten Zählerstands Anfang/Ende ermittelst und
  • einmal Impulse mit Arduino zählst und daraus die die gezählte Arbeit ausrechnest?

Oder vergleichst Du zwei verschiedene Zähler miteinander?

Wieso ist überhaupt die Summe der Impulsdauern notwendig? Ich dachte, 2000 Impulse pro kWh, dann muss ich doch nur die steigenden Flanken zählen, um den Stromverbrauch zu haben. Und Impulse sind immer ganz und nie negativ, also unsigned long.
Aus dem Zeitabstand der steigenden Flanke kann man mehr oder weniger genau auf den momentan fliesenden Strom schliessen.

So zumindest habe ich das verstanden.

Hallo,
den Speicher des Eltako stelle ich auf "0"
Dann starte ich den Arduino.

Der Eltako ist nach 59:10h auf 930 gesprungen.
Zur gleichen Zeit hatte der Arduino 1848 Impulse gezählt.
Bis zum nächsten Impuls wären es max 01:56min.
1848 Impulse sind (bei 2000Imp/kWh) 0,924kWh

Es besteht also ein Unterschied von 6W oder 12 Impulsen.
Die Wirkleistung hole ich mir über die gemessene Zeit zwischen 2 Impulsen.

Zeit 115530ms
(3600000/115530)*0.5=15.58W

Für den Stand der Zähler (Eltako 0.930kWh zu Arduino 0.924kWh) werden nur die
Impulse des Arduino gezählt.

Mit der Zeit, die zwischen 2 Impulsen vergeht, läßt sich die momentare
Wirkleistung errechnen. Und die ist ziemlich genau. S.O.
Ich weiß nur noch nicht, ob die Impulslänge von 30ms mit in die Rechnung
gehört. Es wird ja die Zeit zwischen 2 LOW-Flanken gemessen.

" Und Impulse sind immer ganz und nie negativ"

2000Imp/kWh = 1Imp/0.5W

Gruß und Dank
Andreas

was gibt denn der Hersteller für Genauigkeit an?

Hallo,
Genauigskeitklasse für ±1% B
S0 nach DIN EN62053-31
Gruß und Spaß
Andreas

Na ja, 12 zu 1848 sind ca. 0,6%
weil zwischen Impulsen und Display wird es ja auch diese Ungenauigkeiten geben

Hi Leute, kann ich hier auch nochmal nachhaken?

ich habe mir nen Wechselstromzähler DRS155D von E-Tech besorgt, 1000 Imp/kWh

Habe ein Tutorial nachgemacht und den Code eingegeben.

#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

#include <StopWatch.h>

StopWatch MySW;
StopWatch SWarray[5];

void setup() {

pinMode(6, INPUT);
lcd.begin(16, 2);
lcd.print("LADEN...");
delay(500);
lcd.clear();
lcd.print("WARTE AUF IMPULS");
lcd.setCursor(0, 1);
lcd.print("<<<<<<<<>>>>>>>>");
Serial.beginn (9600);

SWarray[0].start();

}

void loop() {

if(digitalRead(6) ==0){

MySW.stop();
zeit = MySW.elapsed();
MySW.reset();
MySW.start();
imp = imp + 1;

delay(100);
while(digitalRead(6) == 0) {
delay(100);
}

kwh = imp / 1000;
lcd.clear();
lcd.print("KW");
dtostrf(kwh, 1, 3, tmp1);
lcd.setCursor(3, 0);
lcd.print(tmp1);

lcd.setCursor(0, 1);
lcd.print("EU");
lcd.setCursor(3, 1);
euro = kwh * Preis;
dtostrf(euro, 1, 4, tmp2);
Serial.print(tmp2);

lcd.setCursor(12, 0);
watt = 3600 / (zeit / 1000);

if (int(watt) > 9999){
lcd.print("OVER");
}

else {
lcd.print(int(watt));
}
lcd.setCursor(12, 1);
lcd.print("WATT");

}

}

Nun hängt bei mir eine 60 Watt Lampe zum Test:

und beim Serial.print von kwh und watt erhalte ich

kwh 0.00
watt inf
kwh 0.00
watt 318.22
kwh 0.00
watt 123.65
kwh 0.00
watt 216.67
kwh 0.00
watt 246.34
kwh 0.01
watt 192.36
kwh 0.01
watt 235.08

die kwh steigen dann ca. jede Minute um 0.02 und die watt eiern auch kurios herum 234, 453, ...

Bei ner 60 Watt Lampe sollte doch eigentlich bei kWh nach einer Stunde 0.06 stehen und 60 Watt bei der Liveanzeige.

Was mache ich den hier falsch?

Hallo,
ich weiß nicht, was Du falsch gemacht hast, aber die "delay()" haben da nichts
zu suchen…
Den Zähler richtig angeschlossen?
S0+ auf 5V
S0- über Widerstand gegen Massen, und weiter auf DigiPin

Irgendwie zu viel Gemurkstes, weiß nicht…

Kanst ja einmal probieren:
Das:
"PWh=(3600000/ZeitWatt)*0.5;"
müßtest Du vielleicht angleichen...
Gruß und Spaß
Andreas
P.S. das hier ist ein ArduinoForum- KEIN- FischertechnikForum

#include <LiquidCrystal.h>

const int SOA = 30;// S0-

//******************************     Für S0-Zähler     *************************
unsigned long StartMillis;
unsigned long MessMillis;
unsigned long EndeMillis;
unsigned long ImpulsZeit;
int val; 
byte lastState;
float KWh;
float ZlKWh=-1;
float ZeitWatt;
float PWh;
unsigned long maxMessMillis;
static boolean SER = true;
//******************************************************************************

LiquidCrystal lcd(12, 11, 5, 4, 7, 2);
void setup()  
{
pinMode(SOA, INPUT);
digitalWrite(SOA, LOW);
}

 void loop()
 
{

StartMillis = millis();

val = digitalRead(SOA);
if (val == HIGH && lastState == LOW)

{
MessMillis = StartMillis-EndeMillis;
EndeMillis = StartMillis;
ZlKWh++;
KWh=(ZlKWh/1000)*0.5;
lcd.setCursor(3, 2);
lcd.print(KWh,3);



 if(SER)
 {
 Serial.println(MessMillis);
  SER = false;
 }

}
 
else
{
  SER = true;
}  
lastState = val;
ZeitWatt=MessMillis;
PWh=(3600000/ZeitWatt)*0.5;
    
{ 
 lcd.setCursor(2, 1);
lcd.print(MessMillis);
lcd.print("   "); 


  



if ((PWh >=0) && (PWh <=9.94))
{
lcd.setCursor(2, 3);
lcd.print(" ");
lcd.print(PWh,2);
}
if ((PWh >=9.95) && (PWh <=99.94))
{
lcd.setCursor(1, 3);
lcd.print(" ");
lcd.print(PWh,2);
}
if ((PWh >=99.95) && (PWh <=999.94))
{
lcd.setCursor(0, 3);
lcd.print(" ");
lcd.print(PWh,2);
}
if ((PWh >=999.95) && (PWh <=9999.94))
{
lcd.setCursor(0, 3);
lcd.print(PWh,2);
}

}

}

mayrst:
Was mache ich den hier falsch?

Wenn bei Dir ein Programm läuft und etwas falsches anzeigt, dann bestimmt nicht das von Dir gepostete Programm. Das läßt sich ohne eine Deklaration etlicher Variablen, die darin verwendet werden, nämlich nicht einmal kompilieren.

Außerdem würde ich gerne mal das genaue Schaltbild sehen, wie Du Arduino und S0-Schnittstelle verbunden hast und den PullUp-Widerstand verwendest. Wenn Du keinen PullUp-Widerstand in der Schaltung hast, würdest Du im Programm auch den falschen pinMode setzen und das Programm kann alleine deshalb nicht korrekt funktionieren, weil der Code nicht zur angeschlossenen Hardware passt.

Abgesehen davon programmierst Du da zum Zählen von Zeiten und Impulsen einen völligen Irrsinn:

 if(digitalRead(6) ==0){

    MySW.stop();
    zeit = MySW.elapsed();
    MySW.reset();
    MySW.start();
    imp = imp + 1;

    delay(100);
    while(digitalRead(6) == 0) {
      delay(100);
    }

Du zählst Impulse, indem Du eine Stoppuhr anhältst und die Zeit abliest, dann die Stoppuhr resettest und wieder startest? Und dann wird mit delay() das laufende Programm noch mindestens einmal, eventuell aber auch mehrmals für eine Zehntelsekunde blockiert, bevor es weiterläuft? Bist Du sicher, dass die mit der Stoppuhr gemessene Zeit dabei noch irgendwas mit der Zeit zwischen zwei Impulsen zu tun hat?

Mein Tipp: Schau Dir mal das Arduino-Programmierbeispiel "StateChangeDetection" an, und verwende statt Deiner Stoppuhr-Library mit der wild gestoppten, abgelesenen, resetteten und wieder gestarteten Stoppuhr den Stand des "millis()" Zeitzählers zur Ermittlung der Zeiten!

Also mache ein vernünftiges Programm statt so eines Kuriosums!
Und prüfe, ob Deine Hardwareschaltung (PullUp-Widerstand) zum pinMode im Programm passt!

Hi SkobyMobil
ich habe es mal mit deinem Script versucht, leider auch ohne Erfolg,

sollte es oben 3 und nicht 30 heißen? hast du dich da vertippt?
Als input wähle ich also Digital 3 als Input?

Zum Thema wie schließe ich das ganze an:

S0+ auf Arduino 5V
S0- über 4,7 kOhm auf Pin D3

habe auf folgender Seite gelesen dass der Arduino interne Widerstände zum schalten hat und dass man den Analog In auch nutzen kann.

http://www.msxfaq.de/verschiedenes/bastelbude/arduinos0.htm