Frage:
IIR-Filter auf einer 16-Bit-MCU (PIC24F)
rasgo
2011-04-03 20:31:11 UTC
view on stackexchange narkive permalink

Ich versuche bisher ohne Erfolg, ein einfaches IIR-Filter erster Ordnung auf einer MCU (PIC24FJ32GA002) zu implementieren. Das Filter ist ein DC-Tracking-Filter (Tiefpassfilter), dessen Zweck darin besteht, die DC-Komponente eines 1,5-Hz-Signals zu verfolgen. Die Differenzgleichung wurde einer TI-Anwendungsnotiz entnommen:

y (n) = K x (n) + y (n-1) (1-K)
mit K = 1/2 ^ 8

Ich habe ein MATLAB-Skript erstellt, um es zu testen, und es funktioniert gut in der Simulation. Verwendeter Code:

  K = 1/2 ^ 8b = Ka = [1 - (1-K)] Fs = 200; // AbtastfrequenzTs = 1 / Fs; Nx = 5000; // Anzahl der Proben nT = Ts * (0: Nx-1); fin = 1,5; // Signalfrequenzrandn ('Zustand', Summe (100 * Takt)); Rauschen = Randn (1, Nx); Rauschen = Rauschmittelwert (Rauschen); xin = 200 + 9 * (cos (2 * pi * fin *) nT)); xin = xin + Rauschen; out = Filter (b, a, xin);  

Simulation Frequence response

Ich kann es jedoch nicht Implementieren Sie es auf einem PIC24F-Mikrocontroller. Ich stelle die Koeffizienten im Q15 (1.15) -Format dar, speichere sie in kurzen Variablen und verwende einen langen für Multiplikationen. Hier ist es der Code:

  kurz xn; kurz y; kurz b0 = 128, a1 = 32640; // Q15long aux1, aux2; // (...) while (1) {xn = readADC (adc_ch); aux1 = ((lang) b0 * xn) << 1; aux2 = ((lang) a1 * y) << 1; y = ((aux1 + aux2) >> 16); delay_ms (5);}  

Long Cast wird verwendet, um das Signal so zu erweitern, dass die Multiplikationsoperation korrekt ausgeführt wird. Nach jeder Multiplikation verschiebe ich ein Bit nach links, um das erweiterte Signalbit zu entfernen. Beim Summieren verschiebe ich 16 Bit nach rechts, um y im Q15-Format zu erhalten.

Ich debugge die MCU mit Pickit2 und dem Fenster "View-> Watch" (MPLAB IDE 8.53) und teste den Filter mit einem DC-Signal (I. Ändern Sie das Gleichstromsignal mit einem Potentiometer, um verschiedene Werte zu testen. Der ADC hat eine Auflösung von 10 Bit und die MCU wird mit 3,3 V geliefert. Einige Ergebnisse:

1 V -> xn = 312 (richtig), yn = 226 (falsch)
1,5 V -> xn = 470 (richtig), yn = 228 (völlig falsch)

Was mache ich falsch? Irgendwelche Vorschläge zur Implementierung dieses IIR-Filters auf einer 16-Bit-MCU?

Vielen Dank im Voraus :)

Sechs antworten:
user3624
2011-04-03 22:32:26 UTC
view on stackexchange narkive permalink

Ich bin nicht sehr weit in Ihr Filterdesign eingetaucht, aber nur der Quellcode zu betrachten, bringt ein paar Dinge auf den Punkt. Zum Beispiel diese Zeilen:

  aux1 = ((lang) b0 * xn) << 1; aux2 = ((lang) a1 * y) << 1; y = ((aux1 + aux2) >> 16);  

Das erste Problem, das ich sehe, ist das ((lange) b0 * xn). Ich bin auf Compiler gestoßen, die dies falsch kompilieren würden als ((long) (b0 * xn)), was völlig falsch ist. Um auf der sicheren Seite zu sein, würde ich dies als (((lang) b0) * ((lang) xn)) schreiben. Dies ist zwar eine paranoide Programmierung, aber ...

Wenn Sie als Nächstes "<<1" ausführen, ist dies NICHT dasselbe wie "* 2". Für die meisten Dinge ist es nah, aber nicht für DSP. Dies hängt damit zusammen, wie die MCU / DSP mit Überlaufbedingungen und Zeichenerweiterungen usw. umgeht. Aber selbst wenn es als * 2 funktioniert hat, entfernen Sie ein Bit Auflösung, das Sie nicht entfernen müssen. Wenn Sie wirklich eine * 2 machen müssen, dann machen Sie eine * 2 und lassen Sie den Compiler herausfinden, ob er stattdessen eine <<1 ersetzen könnte.

Die >> 16 ist ebenfalls problematisch. Ich weiß nicht, ob es eine logische oder arithmetische Verschiebung geben wird. Sie möchten eine arithmetische Verschiebung. Arithmetische Verschiebungen behandeln das Vorzeichenbit korrekt, wobei eine logische Verschiebung Nullen für die neuen Bits einfügt. Außerdem können Sie Auflösungsbits sparen, indem Sie <<1 entfernen und >> 16 in >> 15 ändern. Nun, und all dies in normale Multiplikationen und Divisionen ändern.

Hier ist also der Code, den ich verwenden würde:

  aux1 = ((long) b0) * ((long) ) xn); aux2 = ((lang) a1) * ((lang) y); y = (kurz) ((aux1 + aux2) / 32768);  

Nun, ich ziehe an behaupten Sie nicht, dass dies Ihr Problem lösen wird. Es kann oder kann nicht, aber es verbessert Ihren Code.

Es hat sich nicht gelöst, aber es hat geholfen, den Code und die "Tricks" des Compilers besser zu verstehen. Das Problem war mit den Koeffizienten. Es sollte a1 * xn und b0 * y sein. Ein wirklich lahmer Fehler eheh Danke für deine Hilfe!
+1: Technisch gesehen definiert der C-Standard nicht, ob >> eine arithmetische oder logische Verschiebung ist ... oder zumindest haben mir die Leute von Microchip dies vor einigen Jahren gesagt, als ich berichtete, dass ich Probleme bei der Verwendung von >> für Power-of habe -2-Teilungen wegen fehlender Zeichenerweiterungen.
markrages
2011-04-04 00:25:36 UTC
view on stackexchange narkive permalink

Ich schlage vor, Sie ersetzen "short" durch "int16_t" und "long" durch "int32_t". Speichern Sie "kurz", "int", "lang" usw. für Stellen, an denen die Größe nicht wirklich wichtig ist, z. B. Schleifenindizes.

Dann funktioniert der Algorithmus genauso, wenn Sie auf Ihrem Desktop kompilieren Computer. Das Debuggen auf dem Desktop wird viel einfacher. Wenn es auf dem Desktop zufriedenstellend funktioniert, verschieben Sie es auf den Mikroprozessor. Um dies zu vereinfachen, faktorisieren Sie die knifflige Routine in eine eigene Datei. Möglicherweise möchten Sie eine einfache main () - Funktion für den Desktop schreiben, die die Routine ausführt und Null (für Erfolg) oder ungleich Null (für Fehler) beendet.

Integrieren Sie dann das Erstellen und Ausführen des Tests in den Build System für Ihr Projekt. Jedes Entwicklungssystem, das eine Überlegung wert ist, lässt Sie dies tun, aber Sie müssen möglicherweise das Handbuch lesen. Ich mag GNU Make.

Noch besser ist, dass Sie diese Typedefs nicht selbst definieren müssen, wenn Ihr System über eine verfügt: http://pubs.opengroup.org/onlinepubs/007904975/basedefs/stdint.h.html
Rocketmagnet
2012-04-21 01:49:58 UTC
view on stackexchange narkive permalink

David Kessner erwähnte die >> 16 als nicht zuverlässig, was wahr ist. Das hat mich schon einmal gebissen, daher habe ich jetzt immer eine statische Zusicherung in meinem Code, die überprüft, ob >> so funktioniert, wie ich es erwartet habe.

Er schlug stattdessen eine tatsächliche Teilung vor. Aber hier ist, wie man eine richtige Arithmetik >> 16 macht, wenn sich herausstellt, dass Ihre nicht ist.

  union {int16_t i16s [2]; uint16_t i16u [2]; int32_t i32s; uint32_t i32u;} union32; union32 x; x.i32s = -809041920; // jetzt durch 65536x.i16u [0] = x.i16u [1] teilen; // Die Verschiebung erfolgt auf einmal. // Jetzt machen wir die Vorzeichenerweiterung (x.i16u [0] & 0x8000) // War das eine negative Zahl? x.i16u [1] = 0xFFFF; // Stellen Sie dann sicher, dass das Endergebnis immer noch negativ ist. X.i16u [1] = 0x0000; // Andernfalls stellen Sie sicher, dass das Endergebnis immer noch positiv ist. // Jetzt x.i32s = -12345  

Überprüfen Sie, ob Ihr Compiler netten Code für (x.i16u [0] & 0x8000) generiert. . Microchip-Compiler nutzen in solchen Fällen nicht immer die BTFSC-Anweisungen des PIC. Besonders die 8-Bit-Compiler. Manchmal bin ich gezwungen, dies in asm zu schreiben, wenn ich die Leistung wirklich brauche.

Das Verschieben auf diese Weise ist auf PICs, die jeweils nur ein Bit verschieben können, viel schneller.

+1 für die Verwendung von intNN_t und uintNN_t typedefs, auch der Union-Ansatz, die ich beide in meinen Systemen verwende.
@JasonS - Danke. Der Gewerkschaftsansatz unterscheidet eingebettete Entwickler von PC-Entwicklern. Die PC-Leute scheinen keine Ahnung zu haben, wie ganze Zahlen funktionieren.
Leon Heller
2011-04-03 21:10:12 UTC
view on stackexchange narkive permalink

Die Verwendung eines dsPIC anstelle des PIC24 kann die Arbeit erheblich vereinfachen (sie sind Pin-kompatibel). Der dsPIC-Compiler wird mit einer DSP-Bibliothek geliefert, die IIR-Filter enthält, und die Hardware unterstützt Festkomma-Arithmetik direkt.

Ich muss diese MCU (PIC24F) verwenden müssen, da sie in einer tragbaren Anwendung mit geringem Stromverbrauch verwendet werden soll und dsPIC diese Anforderung nicht erfüllt. Der Filter ist einfach, es ist ein Filter 1. Ordnung. Ich nehme an, mein Problem liegt in der Konvertierung des Doppel-> Festkomma-Formats. Wenn ich jedoch den Code lese, denke ich, dass ich das Richtige tue, aber es funktioniert nicht ... Ich frage mich, warum.
"Wird in einer tragbaren Low-Power-Anwendung verwendet und dsPIC erfüllt diese Anforderung nicht." <- Diese Art von pauschaler Aussage löst Alarmglocken in meinem Kopf aus. Der Stromverbrauch ist anwendungsabhängig, und Hardware-Unterstützung bedeutet häufig einen geringeren Gesamtstromverbrauch. Bei Anwendungen mit geringem Stromverbrauch bleibt der Prozessor die meiste Zeit eingeschlafen. Daher ist die Reduzierung der Einschaltdauer der Weg, um den Stromverbrauch zu senken ...
ist das ein fester Punkt oder ein Gleitkomma? Floating wäre ein Plus, aber Sie benötigen keinen DSP, um einen Bi-Quad-Filter zu implementieren. Sie benötigen viel mehr eine Filterdesign-Software, um die Parameter zu finden.
Kellenjb
2011-04-04 02:35:57 UTC
view on stackexchange narkive permalink

Es sieht so aus, als würden Sie y nie einen Wert geben, bevor Sie ihn verwenden. Sie verwenden y in der Berechnung für aux2, was korrekt ist, da es sich um das gefilterte vorherige Beispiel handelt, aber Sie haben keine Ahnung, wie es in dieser Situation beginnen wird.

Ich würde vorschlagen, dass Sie einen ADC verwenden Probe, bevor Sie Ihre while (1) -Schleife betreten. Weisen Sie diesen Wert y zu, verzögern Sie 5 ms und geben Sie dann Ihre Schleife ein. Auf diese Weise kann Ihr Filter normal funktionieren.

Ich würde auch empfehlen, dass Sie Ihre Shorts so ändern, dass sie längere Variablen sind. Wenn Ihnen nicht der Speicherplatz ausgeht, haben Sie viel einfacheren Code, wenn Sie nicht in jeder Schleife cast eingeben müssen.

davidcary
2011-04-29 06:33:01 UTC
view on stackexchange narkive permalink

Viele frühe MCUs haben nicht einmal einen Multiplikationsbefehl, geschweige denn eine Division. Der IIR-Filter mit dem magischen Wert mit K = 1/2 ^ 8 wurde speziell für solche MCUs entwickelt.

  // Filter implementiert // y (n) = K * x (n) + y (n-1) * (1-K) // mit K = 1/2 ^ 8 .// basierend auf Ideen von // http : //techref.massmind.org/techref/microchip/math/filter.htm// WARNUNG: ungetesteter Code.// Verwendet keine Multiplikationen oder Divisionen.// Schließlich konvergiert y zu einer Art "Durchschnitt" der x-Werte. kurzer Durchschnitt; // globale Variable // (...) while (1) {short xn = readADC (adc_ch); // hoffe, dass wir die Subtraktion nicht überlaufen - // - vielleicht hier eine Sättigungsarithmetik verwenden? kurz aux1 = xn - Durchschnitt; kurz aux2 = aux1 / 256; // Der Compiler sollte eine arithmetische Verschiebung verwenden, // oder vielleicht einen nicht ausgerichteten Byte-Lesevorgang, keinen tatsächlichen Divisionsdurchschnitt + = aux2; delay_ms (5);}  


Diese Fragen und Antworten wurden automatisch aus der englischen Sprache übersetzt.Der ursprüngliche Inhalt ist auf stackexchange verfügbar. Wir danken ihm für die cc by-sa 2.0-Lizenz, unter der er vertrieben wird.
Loading...