mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik IIR-Filter mit verschwindendem Fehler


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Walter T. (nicolas)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hallo zusammen,

ich will ein IIR-Filter erster Ordnung implementieren. Randbedingung 
ist, dass die Implementierung in Integer erfolgt, und dass das Ziel ist, 
dass sich der Rundungsfehler nicht akkumuliert, sondern sowohl der 
Fehler bei der Impulsantwort als auch bei der Sprungantwort dauerhaft 
verschwindet.

Oder anders gesagt: Die Differenz zwischen der Summe der Eingangswerte 
und Ausgangswerte wächst nicht an.

Jetzt habe ich allerdings gerade einen Knoten im Kopf, was die 
Formulierung des Fehlerterms angeht (siehe Anhang). Kann mir jemand 
einen Tipp geben?

Autor: Analogmann (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Was soll der Fehlerterm bringen? Willst du das Ergebnis am Ende 
irgendwann einmal korrigieren? Der IIR soll doch sicher ein permanentes 
Ergebnis ausgeben.

Autor: Wolfgang (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Analogmann schrieb:
> Was soll der Fehlerterm bringen? Willst du das Ergebnis am Ende
> irgendwann einmal korrigieren?

Solange der Erwartungswert für den Fehlertherm Null ist, braucht man 
nichts zu korrigieren und auch nicht bis zu irgendeinem Ende warten.

Autor: Walter T. (nicolas)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wolfgang schrieb:
> Solange der Erwartungswert für den Fehlertherm Null ist, braucht man
> nichts zu korrigieren

Mal sehen: Ohne Fehlerterm ist bei einem Einheitssprung als Eingang 
u(t<0) = 0, u(t>=0) = 1  und alpha = 0.9 ist die Summe über alle u(k) 
unendlich. Die Summe über alle y(k) ist Null. Also wächst der Fehler y-u 
ohne Fehlerterm gegen minus unendlich. Um einen Fehlerterm komme ich 
also nicht herum. Ich weiß nur gerade nicht, wie ich das richtig 
herleite.

Autor: c-heater (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
int filtIn = 0;
long filtLong = 0;
int  filtOut = 0;


//Filter erster Ordnung:
filtLong += (filtIn-filtOut);
filtOut = filtLong / 128; // Für Filterzeit: tau= 128 * Taskzeit


Autor: c-heater (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nachtrag: filtLong ist deshalb "long", weil die Werte um die Faktor 
Filterzeit, hier also 128 größer sind als die Ein-und Ausgangswerte

Autor: K. S. (the_yrr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Mal sehen: Ohne Fehlerterm ist bei einem Einheitssprung als Eingang
> u(t<0) = 0, u(t>=0) = 1  und alpha = 0.9 ist die Summe über alle u(k)
> unendlich. Die Summe über alle y(k) ist Null.

dann musst du (würde ich so sehen) die u(k) auch skalieren. alpha 
skalierst du ja mit 2^16. bei Int ist der kleinste Schritt halt 1, 
probier das ganze mal mit 2^8 oder so als Sprung. Wenn du bei Float den 
Sprung klein genug machst verschwindet der auch wieder durch 
Rundungsfehler. niemand würde erwarten dass ein Filter mit Float einen 
Sprung von 0.0000...x verarbeiten kann.

selbst wenn deine Fehlerkorrektur funktioniert, ist u(k) = y(k + zeit 
bis fehlerkorrektur greift). damit gewinnst du nichts, du müsstest u 
(und damit y) anders quantisieren bzw. auch hoch skalieren.

Autor: Walter T. (nicolas)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
c-heater schrieb:
> [...]

Ja, so sah mein zweiter Ansatz auch aus. Der Nachteil an dieser 
Formulierung besteht darin, dass der Fehler für Summe(y(t)) immer noch 
maximal 128-1 (bei mir 20-1) ist. Irgendwie habe ich naiv erwartet, dass 
der Fehler auf Plusminus 1 zu bekommen ist.

P.S.: Hoppla....bei den "exponnentiellen" Filtern (IIR 1. Ordnung) sind 
mir die "n" ein wenig verruscht.

: Bearbeitet durch User
Autor: c-heater (Gast)
Datum:
Angehängte Dateien:

Bewertung
1 lesenswert
nicht lesenswert
Hallo,
nein, da entsteht kein Fehler. Das Filter läuft nach einer gewissen Zeit 
auf genau den Eingangswert.
Hier als Bild und Tabelle das Beispiel mit Eingangswerten 0,100,50,71,19 
und Filterzeit 20. Wobei in C sich Filterzeiten 16,32,64 anbieten, weil 
keine Division erforderlich ist.

Autor: c-heater (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Bei
filtOut += (filtIn - filtOut) / 128;

hättest Du allerdings das von Dir beschriebene Problem. Nicht aber, wenn 
Du die 2-zeilige Version von oben nimmst. Der Preis dafür ist allerdings 
eine zusätzliche Variable, die möglicherweise einen breiteren Datentyp 
erfordert.

Autor: Walter T. (nicolas)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
c-heater schrieb:
> Das Filter läuft nach einer gewissen Zeit
> auf genau den Eingangswert.

Das stimmt. Allerdings stimmen die Zwischenwerte nicht. Bei einem 
linearen Filter müsste bei den folgenden Varianten das gleiche Ergebnis 
herauskommen:
deltaU = input();
u += deltaU;
y0 += filterExp(deltaU, &Status0);
y1  = filterExp(u, &Status1);

Bei den in double implementierten Filtern ist das auch so. Aber beim 
Integer-Filter ist noch der Wurm drin.

Autor: K. S. (the_yrr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Bei den in double implementierten Filtern ist das auch so. Aber beim
> Integer-Filter ist noch der Wurm drin.

wenn alpha >0.5 ist kommt der Integer Filter immer nur auf +-1 an den 
Sollwert, sowohl von oben als auch von unten.

es ist halt nicht einfach und/oder gut bei einem Filter, der mit 
Bruchteilen des Eingangswertes arbeitet, die kleinste Quantisierung (bei 
Integer ist das halt 1) als signifikante Stelle zu benutzen.

in float folgt auf die 1 die 1.0000001192092896 als nächste mögliche 
Zahl, berechne mal den Sprung in einem Filter nur in Float, da kommt 
auch nur Müll raus (da du hier die Zwischenberechnung mit 2^16 
hochskalierst, müsste man dann fairerweise die Berechnung in Double 
machen und dann nur u(k) und y(k) wieder nach float konvertieren, ändert 
aber nichts daran, dass der Filter bei alpha>0.5 nicht springen wird => 
Fehler wird unendlich)

nimm bei deinem ersten Ansatz statt u(k) mal u(k)*2^8, speicher y(k) so 
wie es aus der Formel kommt für den nächsten Schritt (für y(k-1)), und 
nimm dann nur für die Ausgabe y(k)/2^8. die 2^8 kannst du relativ 
beliebig variieren. dann funktioniert der Filter problemlos ohne 
jeglichen Fehler.

: Bearbeitet durch User
Autor: Jürgen S. (engineer) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die einfachste Möglichkeit, das Problem zu umgehen, ist nach wie vor, 
den Eingangswert des Filters zu verrauschen. Der maximale Abstand zum 
Real-Wert ist dann maximal temporär 1 digit. Mache ich seit X Jahren in 
all meinen Synthies und vielen Integer-basierten Rechnungen im FPGA so.

: Bearbeitet durch User
Autor: Walter T. (nicolas)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jürgen S. schrieb:
> ist nach wie vor, den Eingangswert des Filters zu verrauschen.

Gehört habe ich davon schon, aber mich noch nie damit beschäftigt. 
Kennst Du dazu empfehlenswerte Literatur?

Autor: c-heater (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Das stimmt. Allerdings stimmen die Zwischenwerte nicht.
Aber so: Jetzt wird auch der Rest berücksichtigt, der bei der 
Integer-Division ansonsten verloren geht. Ist allerdings die Frage, ob 
es nicht einfacher ist, einfach alle Eingangswerte um TAU größer zu 
skalieren.
#define TAU 128
int filtIn = 0;
long filtLong = 0;
int  filtOut = 0;
inr filtRest = 0;


//Filter erster Ordnung:
filtLong += (filtIn-filtOut);
filtOut = (filtLong+filtRest) / TAU ;
filtRest = (filtLong+filtRest ) - filtOut * TAU ;

Autor: Walter T. (nicolas)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
c-heater schrieb:
> Aber so

Erst einmal: Danke. Das funktioniert tatsächlich so. Egal, ob das Filter 
inkrementell oder auf den Wert angewendet wird, stimmt jetzt das 
Ergebnis.

Jetzt muß ich mir noch einmal in Ruhe anschauen, worin genau der 
Unteschied zu meinem Ansatz am Montag besteht. Da habe ich die Summe aus 
filtRest und filtLong gespeichert.

Fun fact: Das Filter ist zwar ein Tiefpass, aber nur im Rahmen der 
Integer-Auflösung.

: Bearbeitet durch User

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.