Digitalfilter mit ATmega

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Auch mit 8-Bit-Mikrocontrollern der ATmega-Reihe ist es möglich, einfache digitale Signalverarbeitungen auszuführen. Mittels kostenloser Mathematiksoftware ist die Berechnung eigener Filter möglich.

Viele von uns haben schon analoge Filterschaltungen aufgebaut, als passive LC-Filter oder aktive Filter mit Operationsverstärkern. Dank kostenloser Berechnungssoftware, wie z. B. AADE, ist die Auswahl der Schaltung und Berechnung der Bauteilwerte kein Problem mehr. Die gleichen Filter in digitaler Technik zu erstellen gilt dagegen immer noch als kompliziert, obwohl sie in professionellen Schaltungen die Analogtechnik weitgehend verdrängt haben. Ihr großer Vorteil ist der fehlende Abgleich. Dank Quarzoszillator wird ein einmal berechneter Frequenzgang ohne Präzisionsbauteile reproduzierbar eingehalten.

Die Software ist im Grunde sehr einfach aufgebaut, besteht nur aus Schiebeoperationen, Additionen und einigen Multiplikationen. Die ATmega-Controller enthalten alle dazu nötigen Funktionen, einen A/D-Wandler, einen 8-Bit Multiplizierer und mehrere D/A-Wandler durch Pulsbreitenmodulation. Am Ausgang muß nur noch ein einfacher Tiefpaß die PWM-Frequenz wegfiltern. Die Programmbeispiele wurden auf Grundlage der Applikationsschrift AVR223 mit AVRStudio entwickelt.

Am Ende der Programme befindet sich eine Koeffiziententabelle, deren Werte die Filtereigenschaften bestimmen. Um eigene Filter entwickeln zu können sollen dazu die Grundbegriffe sehr kurz erläutert und dann einfache „Kochrezepte“ vorgestellt werden.

Ein IIR-Filter 6.Ordnung

Als erstes wird ein Filter berechnet, das nur durch Änderung der Koeffizienten als Tiefpaß, Hochpaß, Bandpaß oder Bandsperre wirkt. Im Beispielprogramm soll es ein 1750 Hz Sperrfilter werden, das laute Ruftöne beim Zuhören auf Amateur-Relaisfunkstellen unterdrücken kann. Dieser Filtertyp heißt „IIR“, infinite impulse response, weil er nach Anregung mit einem Impuls und schlecht gewählten Einstellungen zum Oszillator wird. Der verwendete Aufbau aus drei hintereinandergeschalteten Teilfiltern zweiter Ordnung ist als gutmütig bekannt. Diese kaskadierten Teilfilter nennt man „SOS“ second order sections oder auch biquad sections. Der Aufbau mit einem einzigen Filter 6. Ordnung, wie er in der AVR223 von 2001 noch beschrieben ist, schwingt dagegen sehr zuverlässig.

Software zur Koeffizientenberechnung

Es gibt einige spezialisierte Programme, die aber nur eine kleine Anzahl von Digitalfilterkonfigurationen kennen, z. B. FilterFree. Wesentlich universeller sind umfangreiche Programmpakete für numerische Mathematik. Neben dem kommerziellen Matlab das auch in einer eingeschränkten Studentenversion angeboten wird, und zu dem zahlreiche Literatur existiert, z. B. ISBN 9783486584271 , gibt es auch zwei kostenlose Nachahmer: Octave und Scilab. Spezialitäten der digitalen Signalverarbeitung bilden nur einen kleinen Teil des Gesamtpakets, und die Hilfetexte dazu sind sehr knapp oder auch mal ein Jahrzehnt alt. Zum Glück sind beide Programme dem Vorbild so nahe, daß man vieles der Literatur zu Matlab entnehmen kann. Die eigentliche Koeffizientenberechnung besteht meistens aus nur wenigen Textzeilen. Man gibt wie beim analogen Filter die gewünschten Durchlaß- und Sperrfrequenzen, die Filtercharakteristik und Ordnung ein. Auf eine grafische Benutzeroberfläche muß man allerdings verzichten. Immerhin können der Frequenzgang und andere Kurven grafisch ausgegeben werden.

Transformationen und komplexe Zahlen

Die mathematischen Grundbegriffe der Digitalfilterberechnung sollen hier nur stichwortartig erwähnt werden:

Von der Fourier-Transformation hat man schon mal gehört, ihre Bedeutung ist noch einigermaßen begreifbar. Von einem periodischen Signal x(t) wie es das Oszilloskop zeigt, also im Zeitbereich, gelangt man über sie zur spektralen Darstellung x(f) im Frequenzbereich auf dem Spektrumanalysator. Das ist aber nur die halbe Information, eigentlich gehört zu jeder Harmonischen außer der angezeigten Amplitude auch noch ihre Phasenlage. x(f) ist also eine zweidimensionale Variable, Amplitude (Betrag) und Phase (Winkel) sind polare Koordinaten. Wenn wir zu kartesischen Koordinaten übergehen, und die x- Achse als reelle, die y-Achse als imaginäre Achse bezeichnen, kommen wir zur komplexen Zahlendarstellung. Für die Berechnung analoger Filter ist die Laplace-Transformation zuständig, für digitale Filter die nahe verwandte z-Transformation. Im ersten Fall wird x(t) in ein X(s) transformiert, in unserem Fall, wie der Name schon sagt in ein X(z). Zur besseren Unterscheidung wird das X groß geschrieben. Auch hier rechnen wir mit komplexen Zahlen, wir befinden uns nach der Transformation in der komplexen z-Ebene.

Übertragungsfunktion und Koeffizienten

Das Digitalfilter ist für uns zunächst ein schwarzer Kasten, in den ein Signal x(t) hineinläuft und als gefiltertes Signal y(t) wieder herauskommt. Die z-transformierten Signale heißen X(z) und Y(z). Der Quotient Y(z)/X(z) = H(z) wird Übertragungsfunktion, engl. transfer function genannt. Für bestimmte Werte von z wird der Zähler oder Nenner der Übertragungsfunktion Null, diese heißen Nullstellen und Pole der Funktion. Diese Werte der komplexen Zahl z können im Pol/Nullstellendiagramm als Punkte in der z-Ebene dargestellt werden, das hilft beispielsweise bei der Untersuchung der Stabilität des Filters gegen Schwingneigung.

Die genannten Mathematikprogramme berechnen uns aus den gewünschten Filterdaten die Übertragungsfunktion. Im einfachsten Fall können wir daraus die Zahlenwerte direkt in die Filtersoftware als Koeffiziententabelle eintragen. Die Aufteilung in Teilfilter macht die Berechnung etwas umständlicher. Die einzelnen Übertragungsfunktionen werden multipliziert, also Y(z)/X(z) = H1(z)*H2(z)*H3(z). Wir müssen also H(z) in geeignete Faktoren zerlegen, auch dabei hilft uns die Mathematiksoftware.

Octave und Scilab

Am besten installieren wir gleich beide Softwarepakete. Beide bieten unterschiedliche Lösungswege zur Berechnung der gewünschten SOS-Teilfilter. In Scilab werden Frequenzen als Bruchteile der Abtastfrequenz, engl. sample rate des Analog-Digitalwandlers angegeben. Der AD-Wandler im ATmega wird hier mit einer Taktfrequenz von 1/64 des Quarzoszillators betrieben und braucht pro Wandlung 13 Takte. Mit einem 15 MHz – Quarz ergibt sich also ein Abtastfrequenz von 15MHz/(64*13) = 18029 Hz. Octave rechnet mit Bruchteilen der halben Abtastfrequenz, auch Nyquist-Frequenz genannt. Die Filterordnung bezieht sich in beiden Programmen auf Tief- und Hochpassfilter. Für Bandpässe und -sperren muß die Hälfte angegeben werden, hier also 3 statt 6.

Beginnen wir mit Scilab. Das kurze Programm in Bild 1 speichern wir als Textdatei z. B. unter „Sperrfilter1750Hz.sci“ ab, dann lädt es durch Anklicken direkt in den bunten Scilab-Editor und wird mit „Ausführen – in Scilab laden“ gestartet. Ein neues Fenster mit der Frequenzgangkurve öffnet sich und im Scilab-Hauptfenster stehen die Übertragungsfunktionen der drei Teilfilter, sowie die Gesamtverstärkung gain. Die Filterordnung kann nicht direkt vorgegeben werden, wir müssen die vier Grenzfrequenzen und die beiden Dämpfungswerte variieren bis genau drei SOS-Teilfilter herauskommen. In Octave werden nur zwei Grenzfrequenzen verlangt, dafür können wir die Filterordnung vorgeben. Wieder wird eine Textdatei ausgeführt und liefert Übertragungsfunktionen für drei Teilfilter.

Einen Schönheitsfehler haben die berechneten Funktionen noch. In der Regelungstechnik und auch in Scilab/Octave wird mit positiven Exponenten von z gerechnet, die Literatur zur digitalen Signalverarbeitung benutzt dagegen negative. Wenn wir aber Zähler und Nenner jeder Teil-Übertragungsfunktion durch z2 teilen, ändert sie sich nicht und wir haben die Formel wie sie für digitale Filter üblich ist.

Jetzt können wir die Koeffizienten direkt aus den Übertragungsfunktionen ablesen. Wir müssen sie noch skalieren, also mit einer geeigneten Zweierpotenz multiplizieren und zu einer ganzen Zahl auf/abrunden. Sie sollen hier zu einer 16 Bit-Zahl (Festkommadarstellung mit Vorzeichenbit) werden, also alle Koeffizienten im Bereich -32768...+32767 liegen.

Frequenzgang maßgeschneidert

Mit der Funktion yulewalk können wir auch kompliziertere Frequenzgangkurven erreichen. In Octave fehlt sie, drei ähnlich lautende Funktionen haben nichts mit Filtern zu tun. Die gewünschte Funktion wird mit Geradenstücken zwischen beliebigen Kurvenpunkten vorgegeben. Als Beispiel nehmen wir einen Bandpass für SSTV slow scan television, der 1974 in der "CQ-DL" vorgestellt wurde. Synchronimpuls (1200 Hz) und Bildinformation (1500-2300 Hz) sollen durchgelassen, Störungen außerhalb dieser Bereiche unterdrückt werden. Nach längerem Ausprobieren verschiedener Vorgabewerte und Reduktion der Abtastfrequenz auf die Hälfte ergibt sich eine gute Übereinstimmung mit dem Frequenzgang des Originals. Jetzt haben wir eine Übertragungsfunktion 6.Ordnung, die wieder in SOS-Teilfilter zerlegt werden muß. Diese Funktion tf2sos transfer function to second order sections fehlt in Scilab, deshalb übertragen wir die Zahlen aus Scilab nach Octave und erhalten so schließlich unsere Koeffizienten.

Es gibt noch weitere Verfahren, um "Wunschfrequenzgänge" zu berechnen:
prony in Matlab ( in Scilab nicht vorhanden) hier im Forum erwähnt, und
FDLS (frequency domain least square) von Greg Berchin: Matlab-File und PDFs dazu, Diskussion auf fpgacentral.com Grundlagenartikel "Precise Filter Design" Auch als Kapitel 7 in ISBN 9781118316917 (2012) oder ISBN 9780470131572 (2007) ab Seite 59 zu finden

Von der Übertragungsfunktion zum Programm

Unsere SOS-Teilfilter haben jetzt (je nach Vorzeichen der Exponenten) diese Übertragungsfunktion:

[math]\displaystyle{ H(z)=\frac{b_{2}\cdot z^{-2}+b_{1}\cdot z^{-1}+b_{0}}{a_{2}\cdot z^{-2}+a_{1}*z^{-1}+1} = {\frac{b_{2}+b_{1}\cdot z+b_{0}\cdot z^{2}}{a_{2}+a_{1}\cdot z+z^{2}}} }[/math]

Jetzt wird von z-Bereich wieder in den Zeitbereich rücktransformiert. Diese Funktion y(t)= f(x(t))und der dazugehörige Programmablauf wird üblicherweise in Form eines Blockdiagramms dargestellt:

IIR Block.png

Wir sehen drei Blocktypen: ein Verzögerung um einen Sampletakt Δt, Addition mehrere Summanden, und Multiplikationen mit Konstanten, die aus der Übertragungsfunktion abgelesen werden. Spezielle Signalprozessoren enthalten übrigens für die beiden letzteren einen sogenannten MAC multiply-accumulate Befehl. Im Atmega-Programm wird es in einem Assembler-Macro namens SMAC32 durchgeführt.

Literatur

  • Der wichtigste Artikel zur Signalverarbeitung mit Scilab ist Signal.pdf (1,2MByte) von 1998. Die dazugehörigen Programme sind etwas schwerer zu finden, hier habe ich sie daher zu SignalFiles.pdf (980kB) zusammengefasst.
  • Bücher zu Scilab gibt es nur wenige, und diese gehen nicht auf das Thema Signalverarbeitung ein.
  • Für Januar 2010 angekündigt: ISBN 3527407243 oder 9783527407248 Signals and Systems Using SCILAB, Vorabinfo: kurzer Verlagstext PDF Seite 33 und bei Google-books.
  • ISBN 9781584502814 (2004) Digital signal processing fundamentals mit einem sehr kleinen Anhang "Scilab Tutorial", sehr oberflächlich
  • ISBN 9780817640095 (1999) Engineering and scientific computing with Scilab mit einem Kapitel zu "Signal Processing", für den Einstieg in Scilab sehr zu empfehlen. Auch als unveränderter Nachdruck von 2007 noch neu erhältlich.

Scilab-Berechnung mit eqiir

<c> // Stopband-Filter für 1750 Hz, Samplerate =18029Hz // Eckfrequenzen 1400Hz, 1700Hz, 1800 Hz, 2100Hz: omega=[2*%pi*(1400/18029),2*%pi*(1700/18029),2*%pi*(1800/18029),2*%pi*(2100/18029)]; // Maximale Durchgangswelligkeit 0,5 dB deltapass = (1-10**(-0.5/20)) ; // minimale Sperrband-Dämpfung 50 dB deltastop = 10**(-50/20); // IIR-Filter berechnen, "stopband", "elliptic": [sos,gain,zeroes,poles] = eqiir('sb','el',omega,deltapass,deltastop); // Second order sections anzeigen: sos gain //Frequenzgang berechnen und plotten: zaehlerprodukt = prod(sos(2)); nennerprodukt = prod(sos(3)); frequenzgang = gain*abs(freq(zaehlerprodukt,nennerprodukt,exp(%i*(0:0.01:%pi)))); // frequenzgang = gain*abs(freq(zaehlerprodukt,nennerprodukt,exp(%i*(0:0.01:%pi)))); n = prod(size(frequenzgang)); //plot(20*log(frequenzgang(2:n))/log(10),(n*18029/(200*%pi))); plot(20*log(frequenzgang (2:n))/log(10))

(Ergebnis:)

                           2                             2                           2
         1 - 1.6219938z + z            1 - 1.6396794z + z           1 - 1.656616z + z

sos = -------------------------- * ------------------------ * -------------------------

                               2                             2                            2
     0.9383702 - 1.5100165z + z     0.8466245 - 1.513936z + z    0.9485511 - 1.663563z + z

gain = 0.8683583 </c>

Messung des 1750 Hz-Sperrfilters

Gemessen mit "Visual Analyzer":

1750HzNotch.png

Frequenzgang-Planung mit Yulewalk

Oben das Programm, weitgehend aus dem Hilfetext entnommen, in der Mitte der berechnete Frequenzgang - Messung liegt noch keine vor -, unten rechts die Vorgabe mit Geradenstücken, links die Übertragungsfunktion 6.Ordnung.

Massgeschneiderter Frequenzgang mit yulewalk

Die Koeffizienten heißen im Zähler von links nach rechts b6...b0 , im Nenner a6...a0, wobei a0 immer =1 ist. Hier die Zerlegung in Teilfilter mit Octave: <c> B=[0.1211431 0.0379961 -0.0272326 -0.0361440 -0.0041574 -0.0010921 -0.0894734 ]; A=[1 -1.7641816 2.7032943 -2.3805502 1.8419165 -0.8344276 0.2958684]; [sos,g] = tf2sos(B,A) // // octave.exe:2> B=[0.1211431 0.0379961 -0.0272326 -0.0361440 -0.0041574 -0.0010921 -0.0894734 ]; // octave.exe:3> A=[1 -1.7641816 2.7032943 -2.3805502 1.8419165 -0.8344276 0.2958684]; // octave.exe:4> [sos,g] = tf2sos(B,A) // sos = // // 1.0000000 1.1503168 1.0001280 1.0000000 0.0596667 0.7154071 // 1.0000000 -0.8390889 0.7389315 1.0000000 -0.7453174 0.7120574 // 1.0000000 0.0024186 -0.9993911 1.0000000 -1.0785309 0.5808050 // // g = 0.12114 </c> In drei Zeilen untereinander die drei Teilfilter-Koeffizienten, von links nach rechts b0, b1, b2, a0 (wieder=1), a1 und a2.

Quelltext des ATmega48-Programms

basierend auf AVR223, zerlegt in 3 SOS-Teilfilter, mit Überlaufbegrenzung <avrasm>

  • Digital filter with ATmega48P 6th-order IIR, AVR223 application note *
  • 15 MHz xtal, input ADC0, PWM-output OCR1a/b, test output PD0 *
  • Christoph Kessler 2008 db1uq_at_t-online_dot_de *

.nolist .include "m48pdef.inc" .list

register 0-15 (no "immediate"-operations possible)

.def MltLo = r0 ; multiplier result .def MltHi = r1 ; multiplier result .def Zero = r2 ; Zero register (used to add carry flag) .def SavSrg = r3 ; save status register during interrupt routine .def reg04 = r4 ; free .def reg05 = r5 ; free .def reg06 = r6 ; free .def reg07 = r7 ; free .def reg08 = r8 ; free .def reg09 = r9 ; free .def reg10 = r10 ; free .def reg11 = r11 ; free .def reg12 = r12 ; free .def accu0 = r13 ; Accumulator - 32bit for Interrupt routine .def accu1 = r14 ; Accumulator - 32bit for Interrupt routine .def accu2 = r15 ; Accumulator - 32bit for Interrupt routine

register 16-23 ("immediate"-operations possible)

.def accu3 = r16 ; Accumulator - 32bit for Interrupt routine .def reg17 = r17 ; free .def reg18 = r18 ; free .def reg19 = r19 ; free .def datal = r20 ; Data samples mul register .def datah = r21 ; for Interrupt routine .def coefl = r22 ; Filter coefficient mul register .def coefh = r23 ; for Interrupt routine

  • register 24-31 (16Bit-registers)

.def Tmp1 = r24 ; general temporary register .def Tmp2 = r25 ; general temporary register

XL = r26 ; free
XH = r27 ; free
YL = r28 ; used by Interrupt routine
YH = r29 ; used by Interrupt routine
ZL = r30 ; init routine only, free for main program
ZH = r31 ; init routine only, free for main program
constants
1st SOS filter

.equ x11 = 0 .equ x21 = 2 .equ y11 = 4 .equ y21 = 6

2nd SOS filter

.equ x12 = 8 .equ x22 = 10 .equ y12 = 12 .equ y22 = 14

3rd SOS filter

.equ x13 = 16 .equ x23 = 18 .equ y13 = 20 .equ y23 = 22

1st SOS filter

.equ b01 = 24 .equ b11 = 26 .equ b21 = 28 .equ a11 = 30 .equ a21 = 32

2nd SOS filter

.equ b02 = 34 .equ b12 = 36 .equ b22 = 38 .equ a12 = 40 .equ a22 = 42

3rd SOS filter

.equ b03 = 44 .equ b13 = 46 .equ b23 = 48 .equ a13 = 50 .equ a23 = 52

  • Macros

.MACRO load_node ldd datal,Y+@0 ; Load low byte of node ldd datah,Y+@0+1 ; Load high byte of node .ENDMACRO .MACRO update_node std Y+@0,datal ; Update low byte of node to prepare next filter run std Y+@0+1,datah ; Update high byte of node to prepare next filter run .ENDMACRO .MACRO load_coef ldd coefl,Y+@0 ; Load low byte of node ldd coefh,Y+@0+1 ; Load high byte of node .ENDMACRO .MACRO mul_move_32 muls coefh,datah ; Signed multiply, coefficient high byte and data high byte mov accu2,MltLo ; Copy result word into accumulator byte 2:3 mov accu3,MltHi ; Copy result word into accumulator byte 2:3 mul coefl,datal ; Unsigned multiply, coefficient low byte and data low byte mov accu0,MltLo ; Copy result word into accumulator byte 2:3 mov accu1,MltHi ; Copy result word into accumulator byte 2:3 mulsu coefh,datal ; Signed-unsigned multiply, coefficient high byte and data low byte sbc accu3,Zero ; Sign extention add accu1,MltLo ; Add low byte of result to accumulator byte 1 adc accu2,MltHi ; Add with carry high byte of result to accumulator byte 2 adc accu3,Zero ; Add carry to accumulator byte 3 mulsu datah,coefl ; Signed-unsigned multiply, data high byte and coefficient low byte sbc accu3,Zero ; Sign extention add accu1,MltLo ; Add low byte of result to accumulator byte 1 adc accu2,MltHi ; Add with carry high byte of result to accumulator byte 2 adc accu3,Zero ; Add carry to accumulator byte 3 .ENDMACRO .MACRO smac32 muls coefh,datah ; Signed multiply, coefficient high byte and data high byte add accu2,MltLo ; Add low byte of result to accumulator byte 2 adc accu3,MltHi ; Add with carry high byte of result to accumulator byte 3 mul coefl,datal ; Unsigned multiply, coefficient low byte and data low byte add accu0,MltLo ; Add low byte of result to accumulator byte 0 adc accu1,MltHi ; Add with carry high byte of result to accumulator byte 1 adc accu2,Zero ; Add carry to accumulator byte 2 adc accu3,Zero ; Add carry to accumulator byte 3 mulsu coefh,datal ; Signed-unsigned multiply, coefficient high byte and data low byte sbc accu3,Zero ; Sign extention add accu1,MltLo ; Add low byte of result to accumulator byte 1 adc accu2,MltHi ; Add with carry high byte of result to accumulator byte 2 adc accu3,Zero ; Add carry to accumulator byte 3 mulsu datah,coefl ; Signed-unsigned multiply, data high byte and coefficient low byte sbc accu3,Zero ; Sign extention add accu1,MltLo ; Add low byte of result to accumulator byte 1 adc accu2,MltHi ; Add with carry high byte of result to accumulator byte 2 adc accu3,Zero ; Add carry to accumulator byte 3

ret

.ENDMACRO .LISTMAC

reset/interrupt-vectors

.cseg rjmp Initial ; after RESET to main program reti ; External Interrupt Request 0 reti ; External Interrupt Request 1

reti ; External Interrupt Request 2 mega644

reti ; Pin Change Interrupt Request 0 reti ; Pin Change Interrupt Request 1 reti ; Pin Change Interrupt Request 2

reti ; Pin Change Interrupt Request 3 mega644

reti ; Watchdog Time-out Interrupt reti ; Timer/Counter2 Compare Match A reti ; Timer/Counter2 Compare Match B reti ; Timer/Counter2 Overflow reti ; Timer/Counter1 Capture Event reti ; Timer/Counter1 Compare Match A reti ; Timer/Counter1 Compare Match B reti ; Timer/Counter1 Overflow reti ; Timer/Counter0 Compare Match A reti ; Timer/Counter0 Compare Match B reti ; Timer/Counter0 Overflow reti ; SPI Serial Transfer Complete reti ; USART0, Rx Complete reti ; USART0 Data register Empty reti ; USART0, Tx Complete

reti ; Analog Comparator mega644

rjmp ADCint ; ADC Conversion Complete reti ; EEPROM Ready reti ; 2-wire Serial Interface reti ; Store Program Memory Read

after reset
  • initialise stack,ports

Initial: ldi Tmp1,Low(RAMEND) out SPL,Tmp1 ; ldi Tmp1,High(RAMEND) out SPH,Tmp1 ; stack initialised clr Zero ; always Zero ldi Tmp1,$FF ; out PortB,Tmp1 ; PortB = % 1111 1111 out PortC,Zero ; PortC = % 0000 0000 out PortD,Tmp1 ; PortD = % 1111 1111 out DDRB,Tmp1 ; PortB direction = % 1111 1111 out DDRC,Zero ; PortC direction = % 0000 0000 out DDRD,Tmp1 ; PortD direction = % 1111 1111 ldi Tmp1,$A1 ; pos. outputs, fast PWM 8Bit sts TCCR1A,Tmp1 ; ldi Tmp1,$09 ; fast PWM 8Bit, clk/1 (no prescaling) sts TCCR1B,Tmp1 ; sts OCR1AH,Zero ; sts OCR1AL,Zero ; sts OCR1BH,Zero ; sts OCR1BL,Zero ; ldi Tmp1,$40 ; Ref=Vcc, right-adj, ADC0=input sts ADMUX,Tmp1 ;

ldi Tmp1,$EE ; Start,Auto, enable Int, Clk/64

ldi Tmp1,$EF ; Start,Auto, enable Int, Clk/128 sts ADCSRA,Tmp1 ; ldi Tmp1,$00 ; free running sts ADCSRB,Tmp1 ; ldi Tmp1,$01 ; disable ADC0 dig.inp (optional) sts DIDR0,Tmp1 ; ldi ZL,low(CoTblF<<1) ; move filter coefficents ldi ZH,high(CoTblF<<1) ; from program-flash ldi YL,low(CoTblS) ; to SRAM ldi YH,high(CoTblS) ; ldi Tmp1,30 ; count 15 * 16 Bit coefficients TbLoop: lpm Tmp2,Z+ ; fetch 1 byte from coefficient table st Y+,Tmp2 ; copy coefficient to Sram, increment Y dec Tmp1 brne TbLoop ldi YL,low(DatTbl) ; load SRAM-Pointer for Interrupt routine ldi YH,high(DatTbl) ; sei ; enable interrupts

Main program

Endless: rjmp Endless ;

ADC-Interrupt routine
  • compute filter and update PWM output

ADCint: sbi PortD,0 ; just to measure INT-Time in SavSrg,SREG ; save status register

  • 1st SOS filter

load_coef b21 ; b21*x1[t-2] load_node x21 ; mul_move_32 ; load_coef b11 ; b11*x1[t-1] load_node x11 ; update_node x21 ; smac32 ; load_coef b01 ; b01*x1[t] lds datal,ADCL ; Load new sample lds datah,ADCH ; into data multiply register dec datah ; convert 10 bit unsigned $03FF -> $01FF dec datah ; to 16 Bit signed $0000 -> $FE00

lsl datal rol datah lsl datal rol datah lsl datal rol datah lsl datal rol datah lsl datal rol datah lsl datal rol datah

update_node x11 ; smac32 ; load_coef a21 ; a21*y1[t-2] load_node y21 ; smac32 ; load_coef a11 ; a11*y1[t-1] load_node y11 ; update_node y21 ; smac32 ; cpi accu3,$20 ; accu < $20000000 ? brlo NoLimit1 ; cpi accu3,-$20 ; accu >= -$20000000 ? brsh NoLimit1 ; clr accu2 ; accu2 = $00 cpi accu3,0 ; brge PosLim1 ; ldi accu3,$80 ; accu3 = $80 rjmp Limit1 ; PosLim1: ldi accu3,$7F ; accu3 = $7F com accu2 ; accu2 = $FF rjmp Limit1 ; NoLimit1: lsl accu1 ; Scaling to gain factor 2^15 rol accu2 ; rol accu3 ; lsl accu1 ; Scaling to gain factor 2^16 rol accu2 ; rol accu3 ; Limit1: std Y+y11,accu2 ; Updating y1[t-1] node to prepare next filter run std Y+y11+1,accu3 ; and save it for following SOS filter

  • 2nd SOS filter

load_coef b22 ; b22*x2[t-2] load_node x22 ; mul_move_32 ; load_coef b12 ; b12*x2[t-1] load_node x12 ; update_node x22 ; smac32 ; load_coef b02 ; b02*x2[t] load_node y11 ; result of 1st SOS update_node x12 ; smac32 ; load_coef a22 ; a22*y2[t-2] load_node y22 ; smac32 ; load_coef a12 ; a12*y2[t-1] load_node y12 ; update_node y22 ; smac32 ; cpi accu3,$20 ; accu < $20000000 ? brlo NoLimit2 ; cpi accu3,-$20 ; accu >= -$20000000 ? brsh NoLimit2 ; clr accu2 ; accu2 = $00 cpi accu3,0 ; brge PosLim2 ; ldi accu3,$80 ; accu3 = $80 rjmp Limit2 ; PosLim2: ldi accu3,$7F ; accu3 = $7F com accu2 ; accu2 = $FF rjmp Limit2 ; NoLimit2: lsl accu1 ; Scaling to gain factor 2^15 rol accu2 ; rol accu3 ; lsl accu1 ; Scaling to gain factor 2^16 rol accu2 ; rol accu3 ; Limit2: std Y+y12,accu2 ; Updating y2[t-1] node to prepare next filter run std Y+y12+1,accu3 ; and save it for following SOS filter

  • 3rd SOS filter

load_coef b23 ; b23*x3[t-2] load_node x23 ; mul_move_32 ; load_coef b13 ; b13*x3[t-1] load_node x13 ; update_node x23 ; smac32 ; load_coef b03 ; b03*x3[t] load_node y12 ; result of 2nd SOS update_node x13 ; smac32 ; load_coef a23 ; a23*y3[t-2] load_node y23 ; smac32 ; load_coef a13 ; a13*y3[t-1] load_node y13 ; update_node y23 ; smac32 ; cpi accu3,$20 ; accu < $20000000 ? brlo NoLimit3 ; cpi accu3,-$20 ; accu >= -$20000000 ? brsh NoLimit3 ; clr accu2 ; accu2 = $00 cpi accu3,0 ; brge PosLim3 ; ldi accu3,$80 ; accu3 = $80 rjmp Limit3 ; PosLim3: ldi accu3,$7F ; accu3 = $7F com accu2 ; accu2 = $FF rjmp Limit3 ; NoLimit3: lsl accu1 ; Scaling to gain factor 2^15 rol accu2 ; rol accu3 ; lsl accu1 ; Scaling to gain factor 2^16 rol accu2 ; rol accu3 ; Limit3: std Y+y13,accu2 ; Updating y3[t-1] node to prepare next filter run std Y+y13+1,accu3 ;

  • PWM - D/A-output

subi accu3,$80 ; signed to unsigned $8000 ->$0000, $7FFF ->$FFFF sts OCR1AH,Zero ; sts OCR1AL,Tmp1 ; fast PWM 8 bit input signal to OCR1A sts OCR1BH,Zero ; sts OCR1BL,accu3 ; fast PWM 8 bit result to OCR1B cbi PortD,0 ; just to measure INT-Time out SREG,SavSrg ; restore status register reti ;

  • coefficient table in flash memory

CoTblF: /*

1750 Hz Sperrfilter

.dw 16384 ; b01 *16384 ( 1 ) .dw -26575 ; b11 *16384 (-1,6219938) .dw 16384 ; b21 *16384 ( 1 ) .dw 24740 ; -a11 *16384 ( 1,5100165) .dw -15374 ; -a21 *16384 (-0,9383702)

.dw 16384 ; b02 *16384 ( 1 ) .dw -26865 ; b12 *16384 (-1,6396794) .dw 16384 ; b22 *16384 ( 1 ) .dw 24804 ; -a12 *16384 ( 1,5139300) .dw -13871 ; -a22 *16384 (-0,8466245)

.dw 16384 ; b03 *16384 ( 1 ) .dw -27142 ; b13 *16384 (-1,6566160) .dw 16384 ; b23 *16384 ( 1 ) .dw 27256 ; -a13 *16384 ( 1,6635630) .dw -15541 ; -a23 *16384 (-0,9485511) */

SSTV-Bandpass nach DJ6HP 015

.dw 16384 ; b01 *16384 ( 1 ) .dw 18847 ; b11 *16384 ( 1.1503168) .dw 16386 ; b21 *16384 ( 1.0001280) .dw -978 ; -a11 *16384 (-0.0596667) .dw -11721 ; -a21 *16384 (-0.7154071)

.dw 16384 ; b02 *16384 ( 1 ) .dw -13748 ; b12 *16384 (-0.8390889) .dw 12107 ; b22 *16384 ( 0.7389315) .dw 12211 ; -a12 *16384 ( 0.7453174 .dw -11666 ; -a22 *16384 (-0.7120574)

.dw 16384 ; b03 *16384 ( 1 ) .dw 40 ; b13 *16384 ( 0.0024186) .dw -16374 ; b23 *16384 (-0.9993911) .dw 17671 ; -a13 *16384 ( 1.0785309) .dw -9516 ; -a23 *16384 (-0.5808050)

  • data memory in SRAM

.dseg .org $0100 DatTbl: .dw $0000 ; x11 .dw $0000 ; x21 .dw $0000 ; y11 .dw $0000 ; y21

.dw $0000 ; x12 .dw $0000 ; x22 .dw $0000 ; y12 .dw $0000 ; y22

.dw $0000 ; x13 .dw $0000 ; x23 .dw $0000 ; y13 .dw $0000 ; y23

  • coefficient table in SRAM

CoTblS: .dw $0000 ; b01 *16384 .dw $0000 ; b11 *16384 .dw $0000 ; b21 *16384 .dw $0000 ; -a11 *16384 .dw $0000 ; -a21 *16384

.dw $0000 ; b02 *16384 .dw $0000 ; b12 *16384 .dw $0000 ; b22 *16384 .dw $0000 ; -a12 *16384 .dw $0000 ; -a22 *16384

.dw $0000 ; b03 *16384 .dw $0000 ; b13 *16384 .dw $0000 ; b23 *16384 .dw $0000 ; -a13 *16384 .dw $0000 ; -a23 *16384

</avrasm>