Gleitender Mittelwert für Batteriemessung mit ESP32 über OpenPLC realisieren

Korniman

Level-1
Beiträge
3
Reaktionspunkte
0
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo und guten Abend, ich bin ganz neu hier, habe vor 20 Jahren mal Elektroniker gelernt, aber habe seit dem mit SPS nichts mehr zu tun gehabt.
Seit ein paar Jahren bastle ich wieder mit Elektronik, z.B. mit Raspberry Pi.

Für ein Radio mit Li-Po, dass ich mir baue, habe ich mir in den Kopf gesetzt die Steuerung über einen ESP32 Mikrocontroler abzuwickeln.
Das funktioniert auch ganz prima mit dem OpenPLC Editor und Modbus. OpenPLC arbeitet mit IEC Standard.
Taster drücken, hochfahren, runterfahren. LED an und aus, - Alles gut.
Nun hänge ich seit Tagen an der Überwachung der Batterie und komme mit meinem Hobby nicht weiter.
Im OpenPLC Forum gibt es dazu bislang keine Hilfe: OpenPLC Forums-Eintrag

Die Aufgabe ist eigentlich simpel. Der ESP32 soll über einen Widerstandsteiler den Bereich von 12-18V überwachen und bei Bedarf den Raspi sanft runterfahren.
Das 3,3V Signal lesen 3 ADC des ESP32 gleichzeitig ein, jeweils mit einem UINT Wert von max 65535. Den Wert mittel ich dann (Summe der Signal durch Anzahl der Signale) mit KOP um die Schwankungen der Eingänge auszugleichen. Das Signal ist damit schon wesentlich ruhiger aber noch nicht brauchbar und das Mitteln über die Zeit ist notwendig.
Ich brauche keine wirklich genauen Spannungswerte, eine Stelle hinter dem Komma z.B. 2,5V reicht völlig aus. Ich würde das Signal zusätzlich gerne über den Zeitraum mitteln.
Hier im Forum und auch im OpenPLC Forum habe ich mich etwas angelesen, Median brauche ich glaube ich eher nicht, da es keine großen Peaks gibt sondern eher ein Rauschen. Das währe dann ein digitaler Filter mit einem gleitenden Mittelwert.

Mit OpenPLC kann man sich der Oscat-PLC Daten bedienen da der Editor selber so einen Funktionsblock nicht hat.
Oscat hat 2 AVG Bausteine und beide funktionieren nicht :)

Der erste Baustein heißt "FILTER_MAV_W" und jeder der beiden Bausteine nutzt eine zusätzliche Funktion "INC1" genannt.
Mittlerweile verstehe ich auch ein ganz bisschen was ich mache und kann auch grob lesen was das ST macht, aber hier komme ich einfach nicht weiter und habe zu wenig Ahnung.


Wenn ich den analogen Eingang von einem niedrigen Wert langsam auf einen hohen Wert ändere und das mit einem analogen Ausgang vergleiche, der den Filter umgeht, hat der Ausgang des Filters ein völlig anderes Rampenverhalten, was man auf den Bildern oben sehen kann.
Wenn der reale Eingang etwas über 40368 ist, hat das Filter bereits den Maximalwert erreicht und fängt wieder bei 0 an und geht dann wieder schneller hoch als der reale analog Eingang.
Nach einigen Recherchen habe ich herausgefunden, dass dieser Funktionsblock von Oscat im Laufe der Jahre einige Probleme gehabt hat.
Mit der variable N kann ich die Sample einstellen. z.B. 20 Werte. Das ändert aber nichts an dem Fehlverhalten des Bausteins.

Wenn ich z.B. 32 sample nehme und die alle einen max. UINT Wert von 65535 haben, dann bin ich bei 2097120 das ist zu viel für WORD mit 16Bit, das müsste dann DWORT und UDINT sein, aber da weiß ich dann auch schon nicht weiter mit der ST Programmierung. Vielleicht liegt der Fehler auch ganz woanders.

Könnt Ihr mir bitte helfen, mir ist es mittlerweile auch ganz egal ob ich diesen Baustein nehme oder ihr was parat habt das ich testen kann. Den anderen OSCAT Baustein führe ich unten noch zur Vollständigkeit auf, aber der hat sofort richtig stark runter und hoch geschwankt obwohl er für DWORD ist.

Liebe Grüße und einen schönen Abend
Korniman

1707344274097.png


1707344322412.png


1707343679409.png

Code:
N := MIN(N, UINT#32);


(* startup initialisation *)
IF NOT init OR rst OR N = UINT#0 THEN
    init := TRUE;
    tmp := UINT_TO_INT(N) - 1;
    FOR i := 1 TO tmp DO
        buffer[i] := X;
    END_FOR;
    sum := UINT_TO_UDINT(WORD_TO_UINT(Y) * N);
    Y := X;
ELSE
    tmp := UINT_TO_INT(N);
    i := INC1(i, tmp);
    sum := sum + WORD_TO_UDINT(X) - WORD_TO_UDINT(buffer[i]);
    Y := UDINT_TO_WORD(sum / UINT_TO_UDINT(N));
    buffer[i] := X;
END_IF;


1707345771127.png



Das hier ist der INC1 Baustein:

1707343814553.png


Und das hier der Filter_DW für DWORD und UDINT der sofort stark schwankt

1707344693907.png
 
Mit UnSigned-Werten würde ich hier gar nicht arbeiten.
Versuch es doch mal so :
Code:
Ausgabe := (99 * alter_Wert + neuer_Wert) / 100 ;
alter_Wert := Ausgabe ;
 
Zuviel Werbung?
-> Hier kostenlos registrieren
@Larry Laffer wollte mich noch zurückmelden.
Vielen lieben Dank für deine Hilfe. Wusste echt nicht was Unsigned und Signed bedeutet und musste mich im Internet auch schlau machen. Das war dann ein sehr gutes Stichwort und daran lag es dann auch.
Ich habe deinen Vorschlag mit ChatGPT und viel interessantem Lernen nachgebaut. (ChatGPT lügt ja bei jedem dritten Satz, aber assistiert ganz gut, so möchte ich es mal vorsichtig ausdrücken). Hier das funktionierende Ergebnis:

1707495638206.png

C-ähnlich:
(* Berechnung des neuen Mittelwerts *)
(* Konvertierung von UnsignedValue (UINT) zu SignedValue (DINT) für die Berechnung *)
SmoothedValue := DINT_TO_UINT((99 * PreviousValue + UINT_TO_DINT(InputValue)) / 100);


(* Speichern des Vorherigen Werts *)
(* Konvertierung von SignedValue (DINT) zu UnsignedValue (UINT) für den Ausgang *)
PreviousValue := UINT_TO_DINT(SmoothedValue);

Der kleine Wehrmutstropfen bei dem Filter oben ist für mich das das Ergebnisse erst hochfährt, da am Anfang noch keine Daten da sind, bis es nach 100 Durchläufen sehr gute Ergebnisse liefert.
lg Korniman
 
Nachdem das dann funktioniert hat, hat es mich gepackt und ich habe auch den Filter mit gewichtetem Mittelwert der die Ergebnisse in ein Array schreibt umsetzen können. Für die Suche: "Weighted Moving Average Filter"

1707496359198.png

C-ähnlich:
(* Conversion from UINT to DINT for weighted average calculation *)
(* Calculation of weighted average *)
Sum := Sum - UINT_TO_DINT(PreviousValue[Index]) + UINT_TO_DINT(InputValue);


(* Saving the new value in the array *)
PreviousValue[Index] := InputValue;


(* Calculation of the weighted average *)
SmoothedValue := DINT_TO_UINT(Sum / UINT_TO_DINT(SamplesQty));


(* Incrementing the index for the next value *)
IF Index < SamplesQty THEN
    Index := Index + 1;
ELSE
    Index := 1;
END_IF;
 
Naja ... als Optimierung wäre ja denkbar, dass du erst mitzählst wieviele Werte du schon erfasst hast und den Teiler entsprechend anpasst und erst bei >= 100 mit dem Teiler rechnest.
Außerdem könntest du auch mit einem kleinenren Teiler/Multiplikator arbeiten - einfach mal "ein bißchen" rumprobieren - das mit den 100 war jetzt ein Schuß aus der Hüfte ...
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Unabhängig von dieser eher banalen Anwendung ist es oft notwendig, die zeitlichen Parameter eines solchen Filters im laufenden Betrieb zu variieren. Dafür hat sich bei uns eine Kombination aus Timer und einem recht großen Array bewährt. Als Parameter benötigt man dann eine Taktlänge und die tatsächlich zu nutzende Arraygröße.
Bei einem Neustart oder einer Veränderung der genutzten Arraygröße wird außerdem einmalig der gesamte genutzte Bereich mit dem ersten brauchbaren Aktualwert beschrieben. So kommt es zu Beginn zwar evtl. zu einer gewissen Welligkeit, aber nicht zu komplett falschen Werten.
 
Für die Spannungsüberwachung einer Batterie sollte keine Glättung notwendig sein. Ganz kurze Einbrüche bei Belastung könntest du mit einem Timer überbrücken.

Für andere Anwendungen kannst du den Filter von Larry verwenden und wie folgt erweitern. (Startwert von alter_Wert = -1)

Code:
If alter_Wert = -1 Then
    alter_Wert := neuer_Wert;
EndIF
Ausgabe := (99 * alter_Wert + neuer_Wert) / 100 ;
alter_Wert := Ausgabe ;
 
Zurück
Oben