Hallo zusammen, befasse mich gerade ein wenig mit digitaler Signalverarbeitung. Ich habe gelesen, dass für Audioverarbeitung oft FIR-Filter in Lattice Struktur eingesetzt werden. Meine Frage lautet nun: was hat die Lattice-Struktur gegenüber einem 'normalen' FIR-Filter für einen Vorteil? Warum eignet sie sich angeblich besonders gut für Audio? Sind die Reflexionskoeffizienten irgendwie weniger anfällig auf Quantisierungsfehler (Runden aufgrund von Fixpoint-Implementierung)? Wenn ich eine Audiosignalverarbeitung implementieren möchte und dazu ein solches Lattice-Filter verwende, wie implementiere ich das dann SW-Mässig auf meinem DSP/uC ? Oder würde man zur Software-Implementierung einfach ein gewöhnliches FIR nehmen? Dieselbe Frage stelle ich mir auch bei IIR-Filtern: dort wird immer gesagt, dass man die üblicherweise in eine SOS-Form und einen Gain zerlegt. Wird bei der Software-Implementierung tatsächlich dann auch mit einzelnen SOS-Filtern gerechnet und am Schluss kommt noch der Gain hinzu, oder wie soll ich mir das vorstellen? Den Sinn dieser verschiedenen Filtertopologien sehe ich schon ein, wenn man das auf einem FPGA realisieren würde. Aber wenn ich es in SW auf einem DSP mache habe ich fast das Gefühl, der Aufwand, 5 SOS-Filter nacheinander zu berechnen, ist grösser, als wenn ich ein einziges Filter 10. Ordnung rechne. Und dazu habe ich dann auch mehr Rundungsprobleme. Beim Lattice denke ich mir das ähnlich - da ist doch ein simples FIR einfacher, als wenn ich mir die Mühe mache, in SW einen solchen Lattice zu beschreiben! oder täusche ich mich da? Gruss!
Bert schrieb: > Aber wenn ich es in SW auf > einem DSP mache habe ich fast das Gefühl, der Aufwand, 5 SOS-Filter > nacheinander zu berechnen, ist grösser, als wenn ich ein einziges Filter > 10. Ordnung rechne. Und dazu habe ich dann auch mehr Rundungsprobleme. Bei einem Filter 10. Ordnung müssen die Filterkoeffizienten relativ genau eingehalten werden, damit das Filter die gewünschte Eigenschaften hat. Mit 5 einzelnen Filtern 2. Ordnung hat man eine größere Toleranz gegenüber Rundungsfehlern bei der Berechnung bzw. Quantisierung der Filterkoeffizienten. Speziell bei IIR-Filtern hoher Ordnung können durch die Rückkopplung auch sehr kleine Fehler eine große Wirkung haben.
Hallo Johannes ja, das Problem ist mir bekannt. Darum teilt man ein grösseres Filter ja auch in SOS-Teilfilter auf. Sind die Teilfilter für sich stabil, dann ist es auch das aus der Serieschaltung resultierende Gesamtfilter. Und für ein Filter 2. Ordnung lässt es sich noch verhältnismässig einfach und schnell feststellen, ob es stabil ist. Aber wie gesagt, wie realisiere ich dies in C auf einem DSP / Mikrocontroller? Dass man in einem FPGA einfach 5 Filter 2. Ordnung baue, um mein IIR-Filter 10.Ordnung zu erhalten, ist natürlich klar, und ausser ein paar zusätzlichen LEs oder Slices bringt das keinen Zusatzaufwand. Aber wie sieht es auf dem Mikrocontroller aus? Dort kann ich ja nicht die 5 Filter parallel rechnen. Macht man es etwa so (Pseudocode):
1 | y1[k] = b01*u[k] + b11*u[k-1] + b12*u[k-2] - a11*y1[k-1] - a21*y1[k-2]; |
2 | |
3 | y2[k] = b02*y1[k] + b12*y1[k-1] + b22*y1[k-2] - a12*y2[k-1] - a22*y2[k-2]; |
4 | ... |
man rechnet also einfach mit ein "paar" Zwischenresultaten? ist das so nicht super-duper ineffizient?
Bert schrieb: > man rechnet also einfach mit ein "paar" Zwischenresultaten? ist das so > nicht super-duper ineffizient? Es ist schon etwas mehr Rechenaufwand, aber so extrem ist das dann auch nicht: Ein Filter 2. Ordnung hat 5 Multiplikationen, wenn man 5 solcher Filter durchrechnet, sind das 25 Multiplikationen. Ein Filter 10. Ordnung hat 21 Multiplikationen, also 4 weniger als mit den einzelnen Filtern. Bei der Anzahl der Additionen ist es ähnlich. Die Frage bei so etwas ist nicht primär die Effizienz, sondern erst mal ist wichtig, dass es überhaupt funktioniert. Wenn ein Filter 10. Ordnung etwas effizienter ist als 5 Filter 2. Ordnung, aber durch die Quantisierungsfehler instabvil ist, hat man ja auch nichts gewonnen.
Hallo Johannes, ah okay, man macht es also wirklich genauso, wie man auch das Blockschaltbild zeichnet - indem effektiv mehrere Filter 2. Ordnung hintereinander kommen. Danke für die Auskunft. Hast du soetwas schon einmal so realisiert? Zum Lattice-Filter kannst du auch etwas sagen? Gruss & Dank
Hallo, Nachtrag zu meinem vorherigen Post: wäre folgendes eine adäquate Implementierung? u ist der Puffer für die alten Eingangswerte. Der ist einfach. Dann, y ist ein zweidimensionales Array, dessen erste Dimension die Sektion angibt, und die zweite Dimension sind dann einfach die alten Werte.
1 | static float sos_iir(float in) |
2 | {
|
3 | static float u[3] = { 0 }; |
4 | static float y[5][3] = { 0 }; |
5 | u[0] = in; |
6 | |
7 | |
8 | |
9 | u[2] = u[1]; u[1] = u[0]; |
10 | y[0][2] = y[0][1]; y[0][1] = y[0][0]; |
11 | y[0][0] = b[0][0]*u[0] + |
12 | b[0][1]*u[1] + |
13 | b[0][2]*u[2] - |
14 | a[0][1]*y[0][1] - |
15 | a[0][2]*y[0][2]; |
16 | |
17 | for(i = 1; i < 5; i++) |
18 | {
|
19 | y[i][2] = y[i][1]; y[i][1] = y[i][0]; |
20 | y[i][0] = b[i][0]*y[i-1][0] + |
21 | b[i][1]*y[i-1][1] + |
22 | b[i][2]*y[i-1][2] - |
23 | a[i][1]*y[i][1] - |
24 | a[i][2]*y[i][2]; |
25 | }
|
26 | |
27 | return y[4][0]; |
28 | }
|
Bert schrieb: > Nachtrag zu meinem vorherigen Post: wäre folgendes eine adäquate > Implementierung? Ich denke schon, dass es so funktioniert. Es gibt bei so etwas natürlich immer unterschiedliche Lösungswege. Die Frage ist, welche Eigenschaften dir wichtig sind bzw. auf was man den Code optimieren möchte (Code-Größe, RAM-Verbrauch, Brechnungsdauer). Man kann es z.B. auch so machen, dass man eine Funktion schreibt, die einen Filter 2. Ordnung durchrechnet, die Speicher-Arrays werden als Funktions-Prameter übergeben. Diese Funktion kann man dann 5 mal hintereinander aufrufen und übergibt jeweils den passenden Pointer. Dadurch sollte die Code-Größe kleiner werden. Wenn man die Filter in der "Direkt-Form 2" (http://de.wikipedia.org/wiki/Filter_mit_unendlicher_Impulsantwort#Direkt-Form_2_.28DF2.29) implementiert, wird weniger RAM benötigt. Ein wichtiger Punkt bei digitalen Filtern ist das Zahlenformat. Du hast in deinem Beispiel Fließkomma-Zahlen verwendet; das ist auf einem Controller ohne Fließkomma-Einheit sehr ungünstig; da verwenet man normalerweise Festkomma-Zahlen. Auf einem DSP gibt es meistens spezielle MAC-Befehle (Multiply and accuulate), die man aber nicht so einfach aus C-Code heraus aufrufen kann. Hier muss man dann entweder den Filter in Assembler programmieren oder eine für diese CPU optimierte Bibliothek verwenden. Du siehst, es ist also nicht so einfach, "den besten" Algotithmus auszuwählen, das hängt von sehr vielen Randbedinungen ab.
Hallo, vielen Dank für deine Ausführungen. Das Filter ansich soll später auf einem MSP430 laufen. Aber da ich dazu die Hardware noch nicht habe, werde ich zunächst das Cortex M4 Discoveryboard benutzen. Der dort verbaute Microcontroller hat auch eine FPU, sodass ich getrost Fliesskomma verwenden kann. Natürlich muss ich es dann für den MSP in ein gescheites Zahlenformat ummünzen, das war mir klar. Noch eine Frage: In Matlab verwende ich den Befehl tf2sos, um mein Filter zu bestimmen: http://www.mathworks.ch/ch/help/signal/ref/tf2sos.html wie der Algorithmus funktioniert, ist mir klar, und wenn ich es von Hand durchrechne, komme ich auch auf das selbe Ergebnis wie Matlab. Das scheint also zu funktionieren. SOS ist eine Matrix mit 6 Spalten (b0 b1 b2 1 a1 a2) und 5 Zeilen (bei dem Filter 10. Ordnung). G ist der Gain, welcher natürlich Skalar ist. Wenn ich in Matlab allerdings mti dem FDATOOL ein Filter baue, dann bekomme ich als Resultat die SOS Matrix und einen Gain-*Vektor* - der Gain wird da auf die einzelnen Sektionen aufgeteilt, aber nicht jede Sektion hat gleich viel Gain. Was hat das für einen Sinn? Muss ich das auch machen oder kann ich einfach einen Gesamtgain verwenden?
Die Gainproblematik resultiert aus den unterschiedlichen Gains, der filter-Koeffs, man spart sich da einfach Zwischenskalierungen auf 1. Einen anderen Grund sehe ich da nicht. Bei Audio musst Du bedenken, dass dort oft mit 16Bit und mehr an Genaugkeit gearbeitet werden soll. Ein Filter auf 90dB glatt zu bekommen ist aufwändig. Anders herum sind Audio-Apss oft billig realisiert, der Kosten wegen. Ich würde also auch davon ausgehen, dass die Filter, die Du dort vorfindest, eine Kompromiss sind.
Bert schrieb: > Muss ich das > auch machen oder kann ich einfach einen Gesamtgain verwenden? Bei Fließkomma-Zahlen ist es vermutlich egal, wie du es machst. Bei Festkomma-Zahlen sollte man drauf achten, dass die Zahlenbereiche möglichst optimal ausgenutzt werden, da kann es schon sinnvoll sein, den Gain geschickt aufzuteilen.
Okay. Also dann wäre es bei Fixpoint ggf. besser, den Gain aufzuteilen. Ich würde das dann so machen: Für jedes Teilfilter wird der DC-Gain ausgerechnet. Und den Gesamtgain teile ich dann so auf jedes Teilfilter auf, dass bei jedem Teilfilter (DC Gain) * (aufgeteilter Gain) maximal wird, ja? würdest du auch so machen? Ich habe übrigens jetzt gerade meine Implementierung des genannten Filters 10. Ordnung in C implementiert. Und mal laufen lassen. Es handelt sich um den Code, den ich weiter oben gepostet habe. Man kann direkt die SOS-Matrix von Matlab in meinen Code rein kopieren, die Filterlänge angeben, und fertig, das ergebnis (Impuls- und Sprungantwort) stimmt exakt mit Matlab überein. (Jetzt ist es in float implementiert, der Cortex M4 dürfte damit gar kein Problem haben.) Den Code kann vielleicht sonst noch jemand brauchen, darum habe ich ihn angehangen! Jetzt befasse ich mich noch mit dem Lattice Filter. Ich habe dazu mal etwas geschrieben, allerdings habe ich bei dem Code einen Denkfehler gemacht, er funktioniert nicht. Ich meine, es sollte beim Lattice Filter möglich sein, den Filter Output zu berechnen, ohne dass ich die Samples rumkopieren muss. Beim FIR muss ich ja immer etwas in der Art u[5] = u[4]; u[4] = u[3]; u[3] = u[2] usw. schreiben; ich würde mal meinen, beim Lattice liesse sich das umgehen, was natürlich interessant wäre. Aber so oder so, bekomme ich es nicht gebacken :-) Vorerst würde ich mich mit einem Lattice FIR begnügen: http://de.wikipedia.org/wiki/Latticefilter Vielleicht kommt ja noch Karl Heinz Buchegger hier vorbei und kann mir verraten, wo mein Denkfehler liegt :-)
1 | #include <stdio.h> |
2 | |
3 | |
4 | #define L 6
|
5 | static const float K[L] = |
6 | {
|
7 | |
8 | 100.0000e-003, 464.6465e-003, 846.8445e-003, -972.9879e-003, 23.0590e+000, 965.1336e-003, |
9 | };
|
10 | |
11 | /* delay elements */
|
12 | static float f[L]; |
13 | static float g[L]; |
14 | |
15 | |
16 | static float u_old = 0.0f; |
17 | |
18 | static float lattice_fir(float u) |
19 | {
|
20 | int i; |
21 | |
22 | /* special case: 1st stage */
|
23 | f[0] = u + K[0]*u_old; |
24 | g[0] = u_old + K[0]*u; |
25 | |
26 | u_old = u; |
27 | |
28 | /* handle all other stages */
|
29 | for(i = 1; i < L; i++) |
30 | {
|
31 | f[i] = f[i-1] + K[i]*g[i-1]; |
32 | }
|
33 | for(i=L-1; i>0; i--) |
34 | {
|
35 | g[i] = g[i-1] + K[i]*f[i-1]; |
36 | }
|
37 | |
38 | /* filter output */
|
39 | return f[L-1]; |
40 | }
|
41 | |
42 | |
43 | int main(void) |
44 | {
|
45 | float inp = 1.0f; |
46 | int i; |
47 | FILE* prot = fopen("x.txt", "w"); |
48 | |
49 | for(i = 0; i < 100; i++) |
50 | {
|
51 | fprintf(prot, "%15f %15f\n", inp, lattice_fir(inp)); |
52 | |
53 | inp = 0; |
54 | }
|
55 | |
56 | fclose(prot); |
57 | |
58 | return 0; |
59 | }
|
Bert schrieb: > Vorerst würde ich mich mit einem Lattice FIR begnügen: > http://de.wikipedia.org/wiki/Latticefilter Ich sehe jetzt irgendwie nicht, was dein Code mit der Filter-Struktur bei Wikipedia gemeinsam hat... Du berechnest U_old, für was brauchst du das? Und was sind die Speicher f und g? Es gibt bei Wikipedia pro Filterstufe nur einen Speicher (z^-1). Dafür braucht man zwei Koeffizienten pro Filterstufe Alpha und Alpha'; bei dir gibt es nur ein Koeffizienten-Array K.
Hallo Johannes, ja, ich habe hier im Buch gelesen: Proakis, J. G. and Manolakis, D. G. Digital Signal Processing -- Principles, Algorithms and Applications Pearson International dass beim Lattice die Koeffizienten alpha und alpha' immer gleich sind. Anscheinend ist eben gerade das der Witz des Lattice; da die Zahlen, die man an den Summationspunkten dann addiert, aufgrund von alpha=alpha' immer in der selben Grössenordnung sind, ist das ganze numerisch etwas besser. (Steht im Buch, lasse ich mal so im Raum stehen, kann ich nicht so genau nachvollziehen...) Ich muss da nochmal drüber schauen, wie man das Filter denn realisieren könnte. Oder hast du eine Idee. Gruss!
Hallo, so nun habe ich auch den Lattice noch geschafft. Ist eigentlich einfach :-) Hier der Code, falls es jemand gebrauchen kann. Das Lattice lässt sich tatsächlich effizienter implementieren, denn man muss nie die alten Messwerte in dem Array herumschieben. Ich denke, das ist mit ein Grund, warum Lattice in den Lehrbüchern als so toll angepriesen wird.
1 | #define L 4
|
2 | static const float K[] = {0.1, 0.1, 0.1, 0.1}; /* koeffizienten in matlab berechnen mit tf2latc */ |
3 | |
4 | static float g[L]; |
5 | |
6 | static void lattice_fir(float input, float* f_out, float* g_out) |
7 | {
|
8 | float f[L]; |
9 | int i; |
10 | |
11 | f[0] = input + K[0]*g[0]; |
12 | for(i = 1; i < L; i++) |
13 | {
|
14 | f[i] = f[i-1] + K[i]*g[i]; |
15 | }
|
16 | |
17 | *fout = f[L-1]; |
18 | *gout = g[L-1] + K[L-1]*f[L-2]; |
19 | |
20 | for(i = L-1; i > 1; i--) |
21 | {
|
22 | g[i] = g[i-1] + K[i]*f[i-2]; |
23 | }
|
24 | |
25 | g[1] = g[0] + K[0]*input; |
26 | g[0] = input; |
27 | }
|
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.