Fényorgonának indult, spektrumanalizátor lett…
A videó már nem az alapverziót tartalmazza!
Előre szólok, nem egy készre csiszolt, utánépíthető ketyeréről szól ez a cikk, hanem sokkal inkább csak tanulásra és kísérletezésre szolgál! Különösen azoknak lehet hasznos, akik egyszerű és gyors IIR szűrők témakörben keresgélnek infót.
Korábbi verzió kicsi oszlopokkal. A dupázás csak optikai tuning, a mégés nem sztereó!
Már amikor elkezdtem összeállítani első AVR-es rendelésemet, tudtam, hogy IIR szűrőt is fogok programozni. Kezdésnek, próbálgatásnak ez a feladat tökéletesnek tűnt, feltételeztem, hogy egy egyszerű, valós idejű 3 sávos hangfrekvenciás sávbontást, majd azt követő csúcsdetektálást elbír egy mikrovezérlő számítási sebessége.Így hát kértem is a csomagba 3db 5mm-es szép színes LED-et, mondván ez helyettesíti majd a 3 izzót. Ezek a készülékek -és házi megépítésük- főleg a 80-as években volt népszerű, nem is nagyon voltak olyan házibulik, ahová valaki ne vitt volna egy ilyen ketyerét. A lényeg, hogy a hangfrekvenciás jelet rá kellett kapcsolni (kis- vagy nagyjelű szinten, bár volt amelyik beépített mikrofonról működött) ezt a jelet mély-közép-magas sávokra bontotta (pl. 300Hz és 3kHz-en) és 3db színes 220V-os izzót vezérelt ki a három sávban. (pl. mély a pirosat, közép a sárgát, magas a kéket vagy zöldet) Igazából egy ilyen készüléket ma is sokkal egyszerűbb lenne analóg szűrőkkel megvalósítani, de itt most nem a tényleges hasznos használat a lényeg, hanem csak a tanulás, kísérletezés, különösen az IIR szűrők egyszerű programozásának kipróbálása. És hogy végül miért nem fényorgona lett? Nos, az ATmega8 mikrovezérlőmnek az előző projektben (Hangfrekis teljesítményanalizátor) készült egy lyukacsos próbanyák, amin ki van alakítva az analóg bemenet, egy LCD modul meghajtása, és egy 10.7MHz-es rezonátor, amiből kényelmes mintavételi frekvenciát lehet leosztani, valamint a programozójelek is kábelen rávezethetőek. Azonban nem akartam erre a panelre még LED-eket és ellenállásokat is forrasztani, így hát úgy döntöttem, inkább amolyan spektrumanalizátor féleséget csinálok, legalább az LCD egyedi karakterek, grafikák terén is próbálok valamit. Végül egy 5 sávos spektrumanalizátor lett, amiben amúgy csak három sáv van ténylegesen szűrve, két közbenső oszlopot átlagszámítással hoz létre. Megjegyzem amúgy, hogy bármikor át lehet alakítani a szoftvert az eredeti célra, kivenni belőle az LCD vezérlést és beleírni a három LED kivezérlését, ami lehet adott komparátorszinten történő kigyújtás, vagy PWM vezérlés. Azt is megjegyezném, hogy ez a feladat valamelyik olcsóbb kis 8 lábú ATTiny-vel is megvalósítható lenne, talán lesz is ilyen konkrét terv. Érdekes amúgy, hogy az FFT alapú spektrumanalizátorokhoz képest mintha valahogy simább, szebb, analógosabb, valósághűbb működése lenne. Ez talán annak tudható be, hogy az IIR szűrők valósan valós idejűek, és nulla késésük van, míg az FFT spektrumanalizátorok csak kvázi valós idejűek: Mérnek, aztán dolgoznak, majd kijeleznek. Megint mérnek egyet, dolgoznak, kijeleznek stb. Így nem folytonos a mérés, hanem csak szakaszosan mérnek bele a jelbe, kihagyva hosszú részeket. Tervezem amúgy a közeljövőben FFT alapú spektrumanalizátor programozását is, és az is Assemblyben lesz! (Bár egyelőre csak a DFT algoritmusát ismerem, az FFT-nek nekem is utána kell néznem, de kevés mintán a DFT is nagyon jól megy.)
Csapjunk akkor a lecsóba, lássuk a programot részekre bontva:
.def filter1L = R2 ; filter1 16 bites reg.
.def filter1H = R3
.def filter2L = R4 ; filter2 16 bites reg.
.def filter2H = R5
.def peakW = R9
.def peakT = R10
.def peakM = R11
.def peakB = R12
.def temp = R16
.def temp2 = R17
.def temp3 = R18
.def ciklus = R19
.def sampleL = R20
.def sampleH = R21
.def timerL = R24
.def timerH = R25
.equ timerValue = int(10700000 / 16 / 13 / 15); int(cpuclk / adcprescaler / (adcclk/Fs) / LCD_frissítés )
A teljes forráskódot a cikk végén letölthetővé teszem, a magyarázat viszont részekre bontva könnyebb. Az LCD kijelzés definícióit és inicializálását most kihagyom, csak az marad, ami a szűrök és az egyéb funkciókhoz kell. Van tehát két 16 bites regiszterünk filter1 és filter2. Ezek két aluláteresztő szűrőt képeznek, egyik kb 2.4kHz, másik 260Hz-en vág. A szűrők az egytárolós PT1, vagy ha úgy jobban tetszik a sima elsőrendű RC aluláteresztő szűrőt utánozzák. A magas sáv jelét úgy kapjuk, hogy a szűretlen jelből kivonjuk a 2.4kHz-es szűrő jelét, a középsáv előállításához a 2.4kHz-es szűrő jeléből vonjuk ki a 260Hz-es szűrő jelét, míg a mély sávot maga a 260Hz-es szűrő adja. Tehát az IIR szűrőnek csak a két fent említett low-pass filtert kell megvalósítani, a többi összegző/kivonó műveletekkel könnyen számítható. (Igazából aluláteresztő szűrővel az összes többi szűrőtípust előállíthatjuk egyszerű összegző/kivonó műveletekkel) A peakW, peakT, peakM, peakB regiszterek a csúcsértékek, úgy mint, wideband (teljes sáv vagy szűretlen), treble (magas), midrange (közép), bass (mély). A sampleH és sampleL az ADC-ből beolvasott minta magas és alacsony bájtja. Látjuk, hogy van 3 temp általános munkaregiszterünk, és egy, ami ciklusszámlálónak van fenntartva. Szerencsére a kódban minden változót regiszterben tudunk tartani, és ki se merítjük az összesen 32db-os készletet. A timerH, timerL egy 16 bites időzítő, mely egy kezdeti értékről számol visszafelé, és amikor eléri a nullát, akkor lefuthat egyszer a főprogram. Ezt a mintavételező megszakítási rutin kezeli; minden lefutáskor csökkenti egyel, majd amikor ez a számláló eléri a nullát, bekapcsolja a státuszregiszter T jelzőbitét, ezzel üzen a főprogramnak, hogy lefuthat egy ciklust, és újraindítja az időzítőt. Ennek konstans értéke a timerValue, és a fenti beállításban 1/15 sec azaz 15Hz, ez lesz a kijelzőre kiiratás frissítési frekvenciája. Tehát a működés alapja, hogy folyamatosan fut a mintavételezés (free running módban megy az ADC) a fenti 10.7MHz-es órajel és cpuclk/16 adc órajel mellett kb 51.44kHz-en mintavételez. Ezen a frekvencián működik a valós idejű IIR szűrés és a csúcsdetektálás is. Eközben a főprogram 15Hz gyakorisággal fut le, és a csúcsértékek szerint megjeleníti a kijelzőn az oszlopokat.
ADC inicializálása
; ADC konverter és megszakításkezelés
sbi ADMUX, REFS0 ; -5V ADC referencia
sbi ADMUX, ADLAR ; 10 bitet felfelé eltolás
cbi ADCSRA, ADPS0 ; \
cbi ADCSRA, ADPS1 ; -leosztás 16 (8MHz/500k/FS=38.46k - 10.7MHz/668.75k/FS=51.44k)
sbi ADCSRA, ADPS2 ; /
sbi ADCSRA, ADIE ; ADC megszakítás engedély
sbi ADCSRA, ADFR ; auto trigger enable
sbi ADCSRA, ADEN ; ADC be
Az ADC-t a fenti kód szerint állítjuk be. +5V lesz az ADC referencia-feszültsége, de vegyünk figyelembe, hogy a bemeneten csatolókondi és munkaponti feszültségosztó van, ami +2.5V-ra eltolja a nulla pontot, a bemenőjel tehát ±2.5V csúcsban. A hangkártyák 2Veff kimenetet tudnak, ami ±2.82V csúcsban, vagyis még túl is tudja vezérelni az ADC fokozatot, erre figyelni kell! A 10 bites eredményt felfelé illesztjük az ADLAR bit szerint, fontos azonban tudni, hogy ebben az overclock üzemmódban az ADC kb 8 biten pontos csak, a két legkisebb helyiértékű bit már nem beszámítható (bár itt a 9-ik még valószínűleg pontos). Az ADC órajel a CPU órajel 16-oda, ez 10.7MHz órajelen 668.75kHz ADCclk és 51.44kHz Fsample. Ha nincs külső 10.7MHz kristály, akkor a belső 8MHz-ről is működni fog, 500kHz ADCclk és 38.48kHz Fsample mellett. Ekkor a szűrők törésponti frekvenciái is kicsit lejjebb kerülnek (1.76kHz és 194Hz, de ezen lehet változtatni). Bekapcsoljuk még az ADC megszakításkérést, és free running-ra állítjuk az ADC-t. Az ADEN bekapcsolás után még nem indul el magától a mintavételezés, az első indítást kézileg kell kiadni, a többit már önmaga indítja. Mivel azonban az itt most nem részletezendő LCD rutinjai is bekerültek, így az LCD inicializálás megelőzi az ADC indítását, ugyanis a gyakori megszakítás meghamisítaná az késleltető algoritmusokat, és ez megnehezíti az amúgy is kényes LCD init folyamatát.
; ADC mintavételezés elindítása
sei ; megszakítás be
sbi ADCSRA, ADSC ; elso konverzió kézi elindítása
Az ADC indítása tehát a fenti kód szerint történik az LCD init után, először engedélyezzük az általános megszakításkezelést, utána kiadunk egy konvertálás indítása parancsot az ADC-nek. Fontos a sorrend, eleinte ugyanis sehogy nem akart működni a free-running mód nálam!
; Néhány kezdõérték beállítása
clr peakW
clr peakB
clr peakM
clr peakT
clr filter1L
clr filter1H
clr filter2L
clr filter2H
ldi timerL,low(timerValue)
ldi timerH,high(timerValue)
Szükséges néhány kritikus regiszter nullázása, és itt állítjuk be a 15Hz-es kijelzési frissítés időzítő kezdőértékét is. A következőkben nézzük az ADC megszakítási rutint:
.org ADCCaddr
in sampleL,SREG
push sampleL
; minta kiolvasása az ADC-bõl
in sampleL, ADCL
in sampleH, ADCH
subi sampleH, 128 ; signed int
asr sampleH ; így biztosan nem csordul sehol (egy felsõ tartalék bit)
ror sampleL
Nincs ugróutasítás, közvetlenül vektorcímen kezdődik az egész rutin, ezzel hajszálnyit ugyan, de spórolunk az órajellel. Fontos is ez, ugyanis a fenti esetben 208 óraciklus áll rendelkezésre két mintavételezés között, és ebbe biztonságosan bele kell férni úgy, hogy azért a főprogram is elégséges CPU időhöz jusson. (A legutolsó esetben amúgy 70-valahány clk volt a szimuláció szerint a legrosszabb, azaz a feltételes elágazásokat leghosszabban bejáró eset, vagyis bőven van számítási kapacitás!) A rutinban első lépéseként mentjük a státuszregisztert, ennek elmulasztása zavart okozhat a főprogram működésében. Ehhez ideigenesen a sampleL regisztert használjuk fel. Óvatosan kell a regiszterekhez nyúlni egy megszakítási rutinban, lehet használni őket, de menteni és visszaállítni kell minden olyan regisztert, amit a megszakított program is használ! Ezután beolvassuk az ADC-ből a mintavételezett eredmény alsó és felső bájtját, majd a felső bájtból kivonunk 128-at. (Lényegében a teljes 16 bites értékből 0x8000-át, csak az alsó bájtrész ilyenkor nem változik) Ezzel a pozitív egész eltolt nulla szintű értéket előjeles egésszé konvertáljuk. Egy jobbra tolással még lefelezzünk, ami azért kell, mert a szűrőkben túlcsordulás jöhet létre, ezért egy bitnyi helyet fenn kell tartani felfelé. Egyrészt ne feledjük, hogy egyes szűrőkben akár kisebb túllövések is megjelenhetnek, de a szűrő matematikai konstrukciójából mindig látjuk, hogy ez mennyire veszélyes, hol keletkezhet túlcsordulás a műveletek során. Másrészt előjeles szám miatt kicsit kényes lenne a túlcsordulás lekezelése a későbbi műveletben történne, mert ilyenkor az sem mindegy, hogy milyen irányba csordult ki, ilyenkor nem elég csak visszarotálni a carry-t! Ezzel az óvintézkedéssel viszont van hova csordulnia a bitnek, így biztos lesz a működés is. A minta amúgy is csak 10 vagy inkább csak 9 értékes bites, ami 16 biten van ábrázolva, hely van. Fontos, hogy a rotáció az asr majd ror utasításokkal történjen az imént említett előjeles érték miatt! (az asr az lsr-hez hasonló, de előjeltartó jobbra tolás)
; filter1 lowpass ; y[n] = y[n-1]-a0*(y[n-1]-x[n]) egyenlet alapján
movw R0,filter1L ; vesszük az y[n-1] elõzõ kimenõjelet
sub R0,sampleL ; elõállítjuk az y[n-1]-x[n] tagot
sbc R1,sampleH
asr R1 ; osztás az IIR a1 együttható szerint
ror R0
asr R1
ror R0
sub filter1L,R0 ; kivonjuk y[n-1]-bõl, ezzel a szûrés kész (a továbbiakban y[n]-ként tekintjük
sbc filter1H,R1
Következik a filter1 2.4kHz-es szűrőjének kódja. Mielőtt azonban az amúgy tényleg nem bonyolult programkódot elemeznénk, nézzük meg magának a szűrőnek a matematikai működését! Ez a szűrő a következő egyenlet szerint működik:
y[n] = a0 · x[n] + b1 · y[n-1]
Ez a legegyszerűbb rekurzív szűrő, ráadásul pontosan úgy működik, mint az analóg RC aluláteresztő szűrő, hajszál pontosan ugyanazt a karakterisztikát produkálja. A képletben y[n] az, amit éppen számolunk, ez az n-edik (mostani) kimeneti érték. x[n] lesz az n-edik bemenő érték, y[n-1] pedig az előző azaz n-1-edik szűrőkimenet. Vegyük észre, hogy programozás szempontjából, amikor az aktuális y-t számoljuk, akkor valójában éppen az előző értéke van még benne, így a szögletes zárójelek akár el is hagyhatók:
y = a0 · x + b1 · y
Ez így most amolyan A=A+1 eset, aki programoz, az érti… Térjünk rá az a0 és b1 un. szűrőegyütthatókra. Ezek a konstansok szabják meg, hogy milyen törésponttal vág a szűrő, mekkora lesz az eredő erősítése, és hogy egyáltalán stabil-e. Egyrészt, a két együttható összege mindig 1 kell hogy legyen, ez a feltétele az egységnyi erősítéstartásnak, valamint mindkét együtthatóra igaz, hogy a 0-1 közé kell, hogy essen az értéke, de úgy, hogy ezen szélső értékeket már nem vehetik fel. Az első szabály értelmében az együtthatók egymásba rendezhetők: pl. a0 = 1 – b1 vagy b1 = 1 – a0, így megoldható, hogy csak az egyikőjük szerepeljen a képletben. Maga az alapegyenlet is rendezhető úgy, hogy assembly programozás szempontjából egy kedvezőbb alakot öltsön:
y = y - a0 · ( y - x )
Az a0 ebben a képletben továbbra is 0-1 tartományba esik, ami egész számmal nem írható le. A reciproka nem tört, de ha azt választjuk, akkor azzal osztani kell. Ha az a0-t a elkezdjük felezgetni, vagy a reciprokát duplázgatni, akkor majdnem oktávokat ugrunk lefelé a törésponti frekvenciában. Talán ebből már érezzük, hogy a bonyolult osztás helyett egyszerű bit tolásokat fogunk alkalmazni. A fenti képlet algoritmusa tehát a következő lesz: Vesszük az előző y értéket és átmásoljuk egy munkaregiszterbe. Kivonjuk ebből a munkaregiszterből az x bemenőjelet. (ezzel előállt az y – x) Eltoljuk a munkaregisztert jobbra annyiszor, ahányszor (az együttható kettes alapú logaritmusa szerint) kell. (előállt az a0 · ( y – x )) Fontos, hogy lefelé legyen annyi bitnyi hely a munkaregiszterben, ahány shiftet végrehajtunk, a legkisebb helyiérték ne csorduljon ki! Ha ez a feltétel nem teljesül, akkor a szűrő a nagyon kis bemenőjelekre érzéketlen lesz. (nem ad rá kimenőjelet) A szükséges munkaregiszter hosszúság tehát balra +1 bit, jobbra pedig annyi, amennyit rotálni kell. Végül kivonjuk az így kapott részeredményt a szűrő filter1 regiszteréből, és ezzel a szűrés kész is, a filter1 innentől az új n-edik értéken van. A filter2 260Hz-es szűrő ugyanígy működik, csak több a rotálás benne. Továbbá vegyük észre, hogy még mindig az előjeles jobbra tolást használjuk, azaz a magas bájton az előjeltartó asr, az alacsonyon az átvitelkezeléshez szükségesen pedig a ror-t.
; filter2 lowpass ; y[n] = y[n-1]-a0*(y[n-1]-x[n]) egyenlet alapján
movw R0,filter2L ; vesszük az y[n-1] elõzõ kimenõjelet
sub R0,sampleL ; elõállítjuk az y[n-1]-x[n] tagot
sbc R1,sampleH
asr R1 ; osztás az IIR a1 együttható szerint
ror R0
asr R1
ror R0
asr R1
ror R0
asr R1
ror R0
asr R1
ror R0
sub filter2L,R0 ; kivonjuk y[n-1]-bõl, ezzel a szûrés kész (a továbbiakban y[n]-ként tekintjük
sbc filter2H,R1
A digitális szűrők (FIR, IIR) amúgy megérne egy nagyobb misét, sőt egyes szerzők 6-8 centi vastag könyvet is meg tudtak tölteni vele, szóval ez a kis szösszenet most erre volt elég. Később talán írok valami átfogóbbat, konyhanyelven digszűrőkből, mert szerencsére ez a téma tényleg jól elmagyarázható egyszerű konyhanyelven is, Z-transzformáció és a többi matematikai absztrakció nélkül. A kód további részében a két mondjuk úgy nyers szűrt jelből állítjuk elő azt a hármat, amire konkrétan szükségünk van. Először a magas tartomány előállítása történik meg, amit úgy kapunk, hogy a szűretlen jelből kivonjuk a filter1 jelét. Ez egy mov és egy sub művelet (csak a felső 8 biten dolgozunk, ide most ennyi is elég)
; magas tartomány elõállítása és csúcsdetektálása
; a magas sávú jel elõállításához ki kell vonni a teljes sávú jelbõl a filter1 jelét
mov sampleL,sampleH
sub sampleL,filter1H ; magas tartomány elõállítása
brmi PC + 4 ; csúcsdetektálás
cp peakT,sampleL
brcc PC + 2
mov peakT,sampleL
Mivel a sampleL regisztert ezután már úgyse használjuk, ezért megint befogjuk, ebbe kerül a részeredmény. A brmi feltételes ugrása a kivonással kapott eredmény előjelétől függően átugorja a következő 4 kódsort, ha az eredmény negatív. Csak pozitív és nulla esetben fut az IF ág, amiben újabb vizsgálattal a csúcsérték detektálás történik. Ez lényegében egy szoftveres egyutas egyenirányító. Ha az új minta nagyobb, mint a korábbi csúcs, akkor peakT frissül. Tehát lényegében adott idő után a peakT begyűjti a pozitív maximumot a jelből, a felejtésről pedig majd a főprogram gondoskodik. A közép sáv előállítása annyiban különbözik az előbbitől, hogy a magasabb töréspontú filter1-ből vonjuk ki az alacsonyabb töréspontú filter2 jelét, ezzel sáváteresztőt kapunk a két fc között.
; közép tartomány elõállítása és csúcsdetektálása
; a közép sávú jel elõállításához ki kell vonni a filter1 jelbõl a filter2 jelét
mov sampleL,filter1H
sub sampleL,filter2H ; közép tartomány elõállítása
brmi PC + 4 ; csúcsdetektálás
cp peakM,sampleL
brcc PC + 2
mov peakM,sampleL
A mély-tartománynál csupán teszteltetni kell a filter2-t, hogy az őt követő feltételes elágazás dolgozhasson, a többi a már ismert módon zajlik:
; mély tartomány és csúcsdetektálása
; a filter2 jele maga a mély sávú jel
tst filter2H
brmi PC + 4 ; mélytart. csúcsdetektálás
cp peakB,filter2H
brcc PC + 2
mov peakB,filter2H
Teljes sávú jelnél pedig teszteltetjük a sampleH bemenőjel magas bájtját, és minden ugyanaz:
; teljes sávú jel csúcsdetektálása (csak pozitív félhullámra)
tst sampleH
brmi PC + 4 ; pozitív minta?
cp peakW,sampleH
brcc PC + 2 ; és nagyobb, mint a korábbi csúcs?
mov peakW,sampleH
A mintavételező megszakítási rutin végén kezeljük az időzítőt: dekrementáljuk a 16 bites értéket, majd ha elérte a nullát, akkor újraindítjuk az időzítést és jelezést adunk a főprogramnak a T jelzőbiten keresztül. Fontos, hogy a státusz regiszter helyreállítása törölné a T bitet, ezért először az SREG-et kell visszaállítani a veremből, és csak utána tudjuk a T-t magasra állítani!
; timer
sbiw timerL,1
brne sampler_skip_1
ldi timerL,low(timerValue) ; idõzítõ reset
ldi timerH,high(timerValue)
pop sampleL
out SREG,sampleL
set ; jelezzük a fõprogramnak, hogy dolgozhat
reti
sampler_skip_1:
pop sampleL
out SREG,sampleL
reti
A következőkben jöjjön maga a főprogram. Első körben tehát meg kell várni, a megszakítási rutin jelzését, ezt a brtc önmagára ugró feltételes elágazás végzi. Ez a sor addig pörög egymagában, míg a T jelzőbit magasra nem vált. (Ugye nem felejtettük el, hogy a mintavételező megszakításból megy és másodpercenként mintegy 51-ezerszer lefut, tehát ez a brtc utasítás valójában nagyon sokszor megszakításra kerül, ezért ez nem egy végtelen ciklus!) Ha megérkezett a várva várt jel, akkor mindjárt töröljük is T bitet a clt utasítással. A brtc utasítás előtt van egy kontroll LED kigyújtó sor, ill. mögötte egy lekapcsoló. Ez a LED az égető MISO lába, és a MISO LED-et villogtatja meg, ha fut a program. Ezzel ellenőrizhető, hogy az írást követő RESET jel után lefutott, sőt működik a kód a mikrovezérlőben. Ilyen debug megoldásokat nyugodtan lehet ám alkalmazni bármilyen projektben!
; *** Ciklikus fõprogram ***
main:
sbi portB,portB4 ; kontroll LED be
brtc PC ; Várakozás az idõzítésre
clt ; T üzenetbit törlése
cbi portB,portB4 ; kontroll LED ki
A soron következő programrész parancsot küld az LCD-nek egyedi karakter bitminta küldésre. (CGRAM írásra) Az oszlopkijelzést fordítva valósítottam meg: nem előre definiált karaktereket használok, hanem kiteszek 6 fix egyedi karaktert (5 sáv, plusz a szélére egyet ami a szűretlen jelet méri), és futás közben azok bitmintáját változtatom. (Ezzel a fordított megoldással amúgy különféle animációkat is lehet készíteni) Az első oszlop karakterkódja a 1, a másodiké a 2 stb. Ezek az LCD inicializálás részében előre ki lettek íratva, csak az LCD kezelés részeket most elhagytam a kódból. (LCD kezelésről kimerítő részletességgel találsz anyagot a neten, többek között pl. Király Tibor weblapján, vagy a PIC-kwic oldalon is)
lcdCmd
write $48 ; váltás egyedi karakter bitkép írásra
lcdChr
Következik a 6 oszlop újrarajzolás, elsőként a mély csatorna, melynek jelét bemásoljuk temp2 tegiszterbe és meghívjuk a scale (ld. később) szubrutint. A mély-közép sáv nem valós, a mély és a közép sáv átlaga adja. (egy kis csalás, hogy finomabbnak tűnjön a felbontás) Az add és a ror együtt átlagszámítást valósít meg, a kicsordult bit a ror hatására visszakerül a regiszterbe. A többi csatorna gondolom magától értetődő, sorban jön a közép, a kvázi közép-magas, a magas, és a végén a szűretlen jel:
mov temp2,peakB ; mély csatorna csúcsjelének oszlopa
rcall scale
add temp2,peakM ; mély-közép kvázioszlop (átlagszámítással)
ror temp2
rcall scale
mov temp2,peakM ; közép csatorna oszlopa
rcall scale
add temp2,peakT ; közép-magas kvázioszlop (átlagszámítással)
ror temp2
rcall scale
mov temp2,peakT ; magas csatorna oszlopa
rcall scale
mov temp2,peakW ; szûretlen csatorna oszlopa
rcall scale
Ezután töröljük a peak regisztereket a következő érték befogadására. A törlés helyett lsr logikai (Carry mentes) shiftet is alkalmazhatunk, ezzel ciklusonként felezzük, így kicsit finomabb a visszahullás. Azonban az LCD kijelző optikai válaszideje így is nagyon lassú, simán cls-el törölve is az, szellemképesen képes csak követni a gyors változásokat. LED-kijelző esetén lenne értelme szép lassan lehulló módban működtetni az oszlopokat. Végül pedig visszaugrunk a main főciklus elejére, és jöhet a következő ciklus kijelzési ciklus:
; csúcsértékek ejtése (azonnali vagy lassan hulló)
clr peakT ; clr azonnali ejtés, lsr lassan hulló
clr peakM
clr peakB
clr peakW
rjmp main
Már csak maga a scale oszloprajzoló szubrutin ismertetése maradt hátra:
; oszlopkijelzés szubrutinja
scale:
ldi ciklus,8
ldi temp,0
ldi ZL,low(data1*2)
ldi ZH,high(data1*2)
loop_1:
lpm temp3,Z+
cp temp2,temp3
brcs PC + 2
ldi temp,0b00011111
rcall lcdWrite
dec ciklus
brne loop_1
ret
data1:
; kijelzési komprátorszintek
.db 48,43,38,34,27,21,15,9
A lényeg, hogy a 8 léptékhez (ugye ennyi pixelsor van egy karakterhelyen) egy ciklusban végigmegyünk, mindegyikhez beolvasunk a programmemóriából egy komparálási értéket, amivel összehasonlítjuk, és ha átléptük a küszöbszintet, akkor nulla helyett a továbbiakban vonalakat kezdünk kiküldeni a karakter bitképébe.
Végül megtekinthetjük egyben a teljes kódot, amihez tartozik egy makrókat magába foglaló include fájl is (utóbbi az LCD kezelés és a delay függvényekre íródtak):
.include "m8def.inc"
.include "macros.asm"
; LCD vezérlés portok
.equ lcdPort4 = portD
.equ lcdPort5 = portD
.equ lcdPort6 = portD
.equ lcdPort7 = portB
.equ lcdPortE = portB
.equ lcdPortRS = portB
.equ lcdDdr4 = ddrD
.equ lcdDdr5 = ddrD
.equ lcdDdr6 = ddrD
.equ lcdDdr7 = ddrB
.equ lcdDdrE = ddrB
.equ lcdDdrRS = ddrB
.equ lcd4 = portD5 ; 11 láb - LCD egység csatlakozásai a uC-hez
.equ lcd5 = portD6 ; 12 láb LCD modul D0-D3 adatlábak és az
.equ lcd6 = portD7 ; 13 láb R/W láb GND-hez van kötve!
.equ lcd7 = portB0 ; 14 láb
.equ lcdE = portB1 ; 15 láb
.equ lcdRS = portB2 ; 16 láb
.def filter1L = R2 ; filter1 16 bites reg.
.def filter1H = R3
.def filter2L = R4 ; filter2 16 bites reg.
.def filter2H = R5
.def peakW = R9
.def peakT = R10
.def peakM = R11
.def peakB = R12
.def temp = R16
.def temp2 = R17
.def temp3 = R18
.def ciklus = R19
.def sampleL = R20 ; FIGYELEM, ez javítva lett időközben! (ld. lábjegyzet)
.def sampleH = R21
.def timerL = R24
.def timerH = R25
.equ timerValue = int(10700000 / 16 / 13 / 15); int(cpuclk / adcprescaler / (adcclk/Fs) / LCD_frissítés )
;IIR szûrõ törésponti frekvenciák a shiftelés függvényében:
;
;shift oszt rel.Ti rel.Fc Fc[Hz]@51.44kHz(Q=10.7MHz)
;========================================================================
;1 2 1,4426950409 0,1103178001 5675,0022154648
;2 4 3,4760594968 0,0457860239 2355,3387279084
;3 8 7,4888756894 0,0212521812 1093,2612441215
;4 16 15,4946221632 0,0102716247 528,3960762023
;5 32 31,4973543196 0,0050529623 259,9360400309
;6 64 63,4986876423 0,0025064289 128,9364844735
;7 128 127,4993464025 0,0012482805 64,2144276366
;8* 256 255,4996738418 0,0006229164 32,0442583357
;------------------------------------------------------------------------
;*7-nél több shift esetén kimegyünk a 16 bites munkatartományból, ami
;hibás mûködéshez vezet! Megoldható, ha növeljük a bitmélységet.
.org 0x0
rjmp initializer
; sampler - mintavételezõ 51,44kHz-en
.org ADCCaddr
in sampleL,SREG
push sampleL
; minta kiolvasása az ADC-bõl
in sampleL, ADCL
in sampleH, ADCH
subi sampleH, 128 ; signed int
asr sampleH ; így biztosan nem csordul sehol (egy felsõ tartalék bit)
ror sampleL
; filter1 lowpass ; y[n] = y[n-1]-a0*(y[n-1]-x[n]) egyenlet alapján
movw R0,filter1L ; vesszük az y[n-1] elõzõ kimenõjelet
sub R0,sampleL ; elõállítjuk az y[n-1]-x[n] tagot
sbc R1,sampleH
asr R1 ; osztás az IIR a1 együttható szerint
ror R0
asr R1
ror R0
sub filter1L,R0 ; kivonjuk y[n-1]-bõl, ezzel a szûrés kész (a továbbiakban y[n]-ként tekintjük
sbc filter1H,R1
; filter2 lowpass ; y[n] = y[n-1]-a0*(y[n-1]-x[n]) egyenlet alapján
movw R0,filter2L ; vesszük az y[n-1] elõzõ kimenõjelet
sub R0,sampleL ; elõállítjuk az y[n-1]-x[n] tagot
sbc R1,sampleH
asr R1 ; osztás az IIR a1 együttható szerint
ror R0
asr R1
ror R0
asr R1
ror R0
asr R1
ror R0
asr R1
ror R0
sub filter2L,R0 ; kivonjuk y[n-1]-bõl, ezzel a szûrés kész (a továbbiakban y[n]-ként tekintjük
sbc filter2H,R1
; magas tartomány elõállítása és csúcsdetektálása
; a magas sávú jel elõállításához ki kell vonni a teljes sávú jelbõl a filter1 jelét
mov sampleL,sampleH
sub sampleL,filter1H ; magas tartomány elõállítása
brmi PC + 4 ; csúcsdetektálás
cp peakT,sampleL
brcc PC + 2
mov peakT,sampleL
; közép tartomány elõállítása és csúcsdetektálása
; a közép sávú jel elõállításához ki kell vonni a filter1 jelbõl a filter2 jelét
mov sampleL,filter1H
sub sampleL,filter2H ; közép tartomány elõállítása
brmi PC + 4 ; csúcsdetektálás
cp peakM,sampleL
brcc PC + 2
mov peakM,sampleL
; mély tartomány és csúcsdetektálása
; a filter2 jele maga a mély sávú jel
tst filter2H
brmi PC + 4 ; mélytart. csúcsdetektálás
cp peakB,filter2H
brcc PC + 2
mov peakB,filter2H
; teljes sávú jel csúcsdetektálása (csak pozitív félhullámra)
tst sampleH
brmi PC + 4 ; pozitív minta?
cp peakW,sampleH
brcc PC + 2 ; és nagyobb, mint a korábbi csúcs?
mov peakW,sampleH
; timer
sbiw timerL,1
brne sampler_skip_1
ldi timerL,low(timerValue) ; idõzítõ reset
ldi timerH,high(timerValue)
pop sampleL
out SREG,sampleL
set ; jelezzük a fõprogramnak, hogy dolgozhat
reti
sampler_skip_1:
pop sampleL
out SREG,sampleL
reti
; *** LCD-re írás 4 bites módban adat/parancs és késlelteto sub-ok ***
lcdWrite:
; felso 4 bit
lcd0 7
lcd0 6
lcd0 5
lcd0 4
sbrc temp,7
lcd1 7
sbrc temp,6
lcd1 6
sbrc temp,5
lcd1 5
sbrc temp,4
lcd1 4
rcall writeE
; alsó 4 bit
lcd0 7
lcd0 6
lcd0 5
lcd0 4
sbrc temp,3
lcd1 7
sbrc temp,2
lcd1 6
sbrc temp,1
lcd1 5
sbrc temp,0
lcd1 4
rcall writeE
; ráfut a 100us késleltetore, RET onnan!
; *** fix 100us késlelteto szub ***
delay100us:
delay 100
ret
; *** E kiküldés szub ***
writeE:
lcd1 E
rcall delay30us ; késleltetot hívja
lcd0 E
; késleltetore ráfut és annak RET-je a kilépés
; *** fix 30us késlelteto szub ***
delay30us:
delay 30
ret
; *** fix 200us késlelteto szub ***
delay200us:
delay 200
ret
; *** fix 10ms késlelteto szub ***
delay10ms:
push temp
push temp2
ldi temp2,6 ; elso beállított kör utáni teljes körök száma
ldi temp,255-136 ; elso kör ennyit megy
out TCNT0,temp ; számláló beállítás
loop_d10ms_1:
ldi temp,1<<TOV0 ; overflow bit törlése
out TIFR,temp ;
in temp,TIFR ; számláló lejárat ellenorzése
sbrs temp,TOV0 ; lejárt, kilépés
rjmp PC-2 ; késleltetés folytatása
dec temp2
brne loop_d10ms_1 ; még 6 teljes kört
pop temp2
pop temp
ret
initializer:
; verem
ldi temp, low(RAMEND)
out SPL, temp
ldi temp, high(RAMEND)
out SPH, temp
; idozíto elindítása
ldi temp,3 ; osztás: clk/64
out TCCR0,temp
; ADC konverter és megszakításkezelés
sbi ADMUX, REFS0 ; -5V ADC referencia
sbi ADMUX, ADLAR ; 10 bitet felfelé eltolás
cbi ADCSRA, ADPS0 ; \
cbi ADCSRA, ADPS1 ; -leosztás 16 (8MHz/500k/FS=38.46k - 10.7MHz/668.75k/FS=51.44k)
sbi ADCSRA, ADPS2 ; /
sbi ADCSRA, ADIE ; ADC megszakítás engedély
sbi ADCSRA, ADFR ; auto trigger enable
sbi ADCSRA, ADEN ; ADC be
; LCD kijelzo inicializálása
sbi lcdDdr7,lcd7
sbi lcdDdr6,lcd6
sbi lcdDdr5,lcd5
sbi lcdDdr4,lcd4
sbi lcdDdrE,lcdE
sbi lcdDdrRS,lcdRS
lcd0 E
lcd0 RS
; itt még 8 bites módban fogad parancsokat az LCD
lcd0 7 ; function reset parancs bebitelése
lcd0 6
lcd1 5
lcd1 4
lcdSendE ; elso reset kiküldés
rcall delay10ms
lcdSendE ; második reset kiküldés
rcall delay200us
lcdSendE ; harmadik reset kiküldés
rcall delay200us
lcd0 4 ; D4=0 -> 4 bites módra váltunk
lcdSendE ; negyedik reset kiküldés 4 bit móddal
rcall delay100us
; innentol 4 bites módban kell küldeni a parancsokat
write 0b00101000 ; func set
write 0b00001000 ; Off
write 0b00000001 ; Clear
rcall delay10ms
write 0b00000110 ; Entry Mode
write 0b00001100 ; On
write 0b10000000 ; pos $0
lcdChr
; ADC mintavételezés elindítása
sei ; megszakítás be
sbi ADCSRA, ADSC ; elso konverzió kézi elindítása
sbi ddrB,portB4 ; kontroll (debug) LED
; Animált karakterek kiiratása
lcdCmd
write $80+$40 +5 ; kurzor pozícionálás
lcdChr
write 1 ; spektrumjelek oszlopainak megfelelõ karakterek
write 2 ; fordítva csináljuk, fixen kitesszük az egyedi karaktereket,
write 3 ; és a bitmintát változtatjuk menet közben
write 4
write 5
write ' ' ; tovább pozícionálás
rcall lcdWrite
rcall lcdWrite
rcall lcdWrite
rcall lcdWrite
write 6 ; szûretlen jel oszlopa
; Néhány kezdõérték beállítása
clr peakW
clr peakB
clr peakM
clr peakT
clr filter1L
clr filter1H
clr filter2L
clr filter2H
ldi timerL,low(timerValue)
ldi timerH,high(timerValue)
; *** Ciklikus fõprogram ***
main:
sbi portB,portB4 ; kontroll LED be
brtc PC ; Várakozás az idõzítésre
clt ; T üzenetbit törlése
cbi portB,portB4 ; kontroll LED ki
lcdCmd
write $48 ; váltás egyedi karakter bitkép írásra
lcdChr
mov temp2,peakB ; mély csatorna csúcsjelének oszlopa
rcall scale
add temp2,peakM ; mély-közép kvázioszlop (átlagszámítással)
ror temp2
rcall scale
mov temp2,peakM ; közép csatorna oszlopa
rcall scale
add temp2,peakT ; közép-magas kvázioszlop (átlagszámítással)
ror temp2
rcall scale
mov temp2,peakT ; magas csatorna oszlopa
rcall scale
mov temp2,peakW ; szûretlen csatorna oszlopa
rcall scale
; csúcsértékek ejtése (azonnali vagy lassan hulló)
clr peakT ; clr azonnali ejtés, lsr lassan hulló
clr peakM
clr peakB
clr peakW
rjmp main
; oszlopkijelzés szubrutinja
scale:
ldi ciklus,8
ldi temp,0
ldi ZL,low(data1*2)
ldi ZH,high(data1*2)
loop_1:
lpm temp3,Z+
cp temp2,temp3
brcs PC + 2
ldi temp,0b00011111
rcall lcdWrite
dec ciklus
brne loop_1
ret
data1:
; kijelzési komprátorszintek
.db 48,43,38,34,27,21,15,9
És a makró: (Megjegyzem amúgy, hogy a makró eredetileg másik projekthez készült, van benne itt nem használt dolog is!)
; saját makrók
; késleltetések:
; fcpu=8Mhz esetén Tbase 8us
; fcpu=10,7Mhz esetén Tbase 5,98us~6us
;***************************************
; Idõzítés beállítása CPU órajelhez
; -------------------------------------
.equ Tbase = 6 ; 10,7MHz eset
.equ Fcpu10 = 107 ; Fcpu[MHz] tízszerese
;***************************************
; K[us] késleltetás 1500us-ig (ADC indítás utánra, TIMER0 számlálóval)
; A makró szubrutinban és kódfolyamba beszúrva is elõfordulhat,
.macro delay
.if ( @0 < 19 ) ; ezek a rövid delay-ek a hagyományos megoldást követik
push temp
ldi temp,int(@0*Fcpu10/30-4+0.999)
dec temp
brne PC-1
pop temp
.else ; IRQ miatt többször/sokszor megszakításra kerülõ delay-ek, timrt0 hw countert használunk
push temp
ldi temp,255-int(@0/Tbase+0.999)
out TCNT0,temp ; számláló beállítás
ldi temp,1<<TOV0 ; overflow bit törlése
out TIFR,temp ;
in temp,TIFR ; számláló lejárat ellenõrzése
sbrs temp,TOV0 ; lejárt, kilépés
rjmp PC-2 ; késleltetés folytatása
pop temp
.endif
.endm
; LCD adott portját (7,6,5,4,E,RS) 0-ba állítása
.macro lcd0
cbi lcdPort@0,lcd@0
.endm
; LCD adott portját (7,6,5,4,E,RS) 1-be állítása
.macro lcd1
sbi lcdPort@0,lcd@0
.endm
; LCD-be kiiratás közvetlen értékadással temp-en keresztül
.macro write
ldi temp, @0
rcall lcdWrite
.endm
; LCD-be kiiratás adott regiszterbol (temp-en, de vermeli)
.macro writer
mov temp,@0
rcall lcdWrite
.endm
; LCD E kiküldés
.macro lcdSendE
rcall writeE
.endm
; üzemmód váltás parancs/szöveg
.macro lcdSendCmd ; parancs kiküldés (temp nincs vermelve)
cbi lcdPortRS,lcdRS
ldi temp,@0
rcall lcdWrite
sbi lcdPortRS,lcdRS
.endm
.macro lcdCmd ; karakteres módba váltás
cbi lcdPortRS,lcdRS
.endm
.macro lcdChr ; parancsmódba váltás
sbi lcdPortRS,lcdRS
.endm
Mellékletek
Összecsomagolva ez az egész: fenyorg.zip
ATmega8A-PU biztosítékbitek (külső 10.7MHz kerámiarezonátorral):
IIR együttható számolótábla: IIR_LP_szamolo.ods
IIR együtthatóinak számításának képlete:
ahol:
b1, a0: szűrőegyütthatók Ts, fs: mintavételi periódusidő vagy frekvencia τ: időállandó (ha ez alapján számolunk) fc: törésponti frekvencia (ha ez alapján számolunk)
Analóg bemeneti fokozat kialakítása ADC0 bemeneten:
Az R1 bemeneti soros ellenállás jelen esetben egy 100W-os végfokozat nagyjelű kimenetéhez illesztene (ez igazából a korábbi HF terhelésanalizátor projektem örökölt kapcsolása), ezért ezt célszerűen lehet kisebbre venni, vagy el is lehet hagyni a túlfesz védődiódákkal együtt, ha pl. hangkártya kimenetre kötnénk a készüléket.
Lábjegyzet
Adós vagyok a fenti IIR aluláteresztő szűrő fc méretező képletével. Egyszer már telefirkáltam emiatt pár A4-es lapot, de most csak a kész kalkulátort találtam meg excelben. A forráskódban viszont táblázatosan ki van írva, hogy az egyes rotációk milyen fc-t adnak ki.Pótolva!Lehet törtrésszel szorozni, tehát a szűrő valószínűleg a mul vagy inkább a mulsu szorzásművelettel is megvalósítható lenne, de ezt még nem próbáltam (ami késik nem múlik…)ez is megvan
Először is BUG-ot javítottam a kódban, bár a működésre nem volt kihatással. A mintavételező sampleL, sampleH regisztereknél az alsó és felső bájt fel volt cserélve a definícióknál, tehát az R20 volt a H és az R21 az L. Ennek valódi következménye csak akkor lenne, ha a regiszterpárt 16 bites műveletben is használnánk, de ilyen a kódban (ezzel a regiszterpárral legalábbis) nem volt. (ezért is nem tűnt fel a hiba a teszteléskor)
Másfelől, dolgozom a rotálás helyett tört szorzással működő variánson, és van már eredmény (hamarosan talán publikálom is). Az alacsonyabb töréspontú szűréseknél (ahol sokat kellett rotálni) igen határozott a sebességjavulás, de ami fontosabb, hogy most finoman lehet megadni a töréspontokat. Próbaképp növeltem a szűrők számát is, és most tényleg 5 sávban működik. Szimulációban 120 órajel alatt fut le a mintavételező – IIR szűrő – csúcsdetektor IRQ rutin, vagyis lehetne még fokozni az élvezetet…
Bár még tesztelgetem az új IIR szűrőt, azért magát a kódrészletét megosztom:
A tört szorzás lényege, hogy a szorzás eredményéül kapott 16 bites számnak csak a felső bájtját vesszük (az alsó törtrész). A szorzó bájt 0-1 intervallumba eső szorzást valósít meg, ahol 0-nak nem meglepő módon a 0, 1-nek a 256 egész érték feleltethető meg. (ez már ügye nem fér be a szorzó számábrázolásába, de ez már nem is tört) Ha pl 0.5-el akarunk szorozni, akkor a 256 felével, azaz 128-al kell a szorzást végezni, és az eredmény magas bájtja pont fele lesz az eredeti értéknek, vagy pl 0.25-nél 64-el szorzunk stb. Úgy is felfoghatjuk a működést, mintha szoroznánk (0…255)/256-al, mivel azzal, hogy a magas bájtot vesszük eredménynek, lényegében 256-al osztunk.
A számoló táblázatot is frissítem, az új változat a törtrészes szorzónak megfelelő együtthatókat is számolja. Ebből nekünk az a0 kell majd. Még egy kicsit finomítom a törésponti frekvenciákat, és az egészet új verziónévvel feltöltöm.
A jelenlegi változat át lett ültetve ATmega88PA-PU mikrovezérlőre, de bármikor vissza lehet térni az ATmega8A-ra. Egyszerűsítettem is a felálláson, most nem kell külső 10.7MHz-es kerámiarezonátor, a belső óragenerátorról megy 8MHz-en (ehhez a biztosítékbiteknél a 8-al való osztást ki kell kapcsolni). Ezzel a mintavételi frekvencia 51.44kHz-ről leesett 38.46kHz-re, így egy kis aliasing veszély is bejött a képbe, de egy ilyen látványketyerénél nem olyan vészes ez. Szintén bármikor át lehet számolni az IIR szűrőket és időzítéseket az előző 10.7MHz-es órára, vagy bármilyen magasabb órajelre. A szűrők számát növeltem, most 6 aluláteresztő szűrő dolgozik a mikrovezérlőben, amiből 7 sáv jele számítható ki, és lineáris interpolációval 13 oszlopot rajzol. A különféle időzítésekkel még lehet játszadozni, pl. az eddigi 15Hz kijelzés frissítési frekit egy huszárvágással 50Hz-re emeltem, és mindjárt szebb lett az oszlopok tánca. A visszahullás időzítővel is érdemes játszadozni. Ami viszont konkrét hátrány, hogy az elsőrendű szűrés miatt lapos az oldalmeredekség (6dB/oktáv), így egy FFT alapú sávbontáshoz képest itt eléggé áthallásosak a sávok, főleg ebben a 7 sávos felbontásban. De igazából szép lett a működés, valamilyen szinten ez is egy simítást ad, nem önálló életet élve össze-vissza ugrálnak. (ne feledjük, hogy ez analóg szűrőt utánzó digit szűrő) Elvileg lehetne növelni a szűrők rendjét, és mondjuk 12dB/oktáv oldalmeredekséget beállítani.
Az új változat mintavételező-IIR-csúcsdetektor megszakítási rutinjának futásideje legfeljebb 179 órajel. Az felső határ ugye 208 lenne, de ekkor nem maradna prociidő a főprogramnak, így ez a cirka 180 most kb a felső határ. A hirtelen megugrás oka nem csak a plusz 2 szűrő megjelenése, hanem a regiszterkiosztást is újra kellett gondolni, néhány regiszter osztott használatba került a főprog és a megszakítási rutin között, így a megszakítási rutin elején és végén elég sok lett a veremművelet. Igazából ezzel a mutatvánnyal kb itt értük el a racionális határt. Bár egy órajel kétszerezéssel meg lehetne duplázni a megszakítási rutin alatti órajelek számát, de a regiszterekből is kifogytunk, masszívan el kellene kezdeni a memóriahasználatot, és ennyit már szerintem nem ér az ügy, akkor már inkább egy FFT alapú változatra célszerű váltani. (Ellel a mikrovezérlővel 20MHz órajel mellett 32-es ADCclk osztással 416 órajel jutna egy mintavételi ciklusra és 48077Hz lenne a mintavételi freki)
A mostani verziót nem listázom be részletesen,csak a csomagolt fájlokat linkelem be: IIR_spektrum_m88a.zip
Sajnos amióta újra dolgozom, nagyon kevés időm és főleg energiám van a hobbijaimra, és az oldalak frissítésére-szerkesztésére is. A 24/48-as munkarend nagyon kimerítő, egyszerűen kiszívja az ember agyát. Nem haladok a hangsugárzós terhelésmérővel sem, pedig az már majdnem célegyesben van. Viszont most volt egy pici energiám, így kicsit mókoltam ezzel az IIR szűrős spektrumanalizátor készülékkel. A legfontosabb, hogy a pici oszlopok helyett most olyan nagy, két egymás feletti karakterhely nagyságú oszlopokkal működik ez a program is, mint a DFT spektrumanalizátor. Az órajel maradt a 8MHz belső óragenerátor, viszont felszabadítottam még három regisztert és kapott egy újabb filtert, így most 7 LP filter segítségével 8 valódi sávban mér, méghozzá a következő frekvenciákon: 47Hz, 100Hz, 220Hz, 470Hz, 1kHz, 2.2Hz, 4.7kHz, 10kHz. Az értékekben pici kerekítések vannak, ezeket a számokat írhatnánk ki a spektrumvonalak alá. Azonban a kijelzőre most is interpolációt használva rajzolja fel a spektrumot, vagyis minden spektrumvonal közé egy fiktív (a két érték átlagát tartalmazó) oszlop került, így a kijelzés összesen 15 oszlopban történik. Sajnos a szűrők meredeksége még mindig csak 6dB oktávonként, így a sávok eléggé áthallásosak, kissé hullámos, simított a teteje. Ami ebben számomra az érdekes, hogy bár szerintem inkább 12db/oktávos meredekségű szűrők kellenének legalább, mégis sok neten fellelt, mások által megépített analóg szűrős kapcsolás is 6dB/oktávos szűrőkkel készül, sokszor 10 sávban, oktávos felbontásban. Hát elvileg azoktól ez se marad el, de mégis egyszerűbb, egy-chip konstrukció. LED-soros kijelzővel amúgy még szebb és folytonosabb lenne (az LCD kijelzőnek kicsit gyenge a válaszideje, bár a videók se igazán mutatják meg, hogy milyen valójában, sokat ront az élményen a videóminőség is) A plusz egy szűrőnek amúgy pont a kijelzési időzítő átalakításával sikerült 3 regisztert találni/felszabadítani; most hardveres időzítőt használva 120Hz-re jön ki a frissítés. (A kijelző saját belső frissítése kb 60-80Hz amúgy)
Ez a verzió most megosztásra kerül, ATmega88PA konrtoller kell alá, és 8MHz belső órára kell beállítani, így a megépítés is egyszerűbb lehet, ha valaki kedvet kapna a kipróbálásához.
Bár az előző hozzászólás is azzal végződött, hogy nincs értelme tovább reszelni ezt a kódot, de azért ez a kis mod mégis elfért még rajta. Ellenben a továbblépést most már nem is annyira a DFT/FFT megoldásban látom, ott ugyanis a lineáris frekvencialéptékezés nagyon megnehezíti a dolgot, ill lerontja az élményt. Jobb ötletem van! Fourier transzformációhoz hasonlóan működő, azaz szinuszos/koszinuszos harmonikusokkal történő szorzáson alapuló, de mégis szín tiszta logaritmikus léptékezésű, változó ablakhosszal operáló és állandó Q jóságú, a teljes HF sávszélességben használható megoldásra van ötletem! A számításidénye szerény, de különösen akkor nagyon kevés, ha pontosan oktávonként követik egymást az oszlopok. A transzformáció röptében történik, azaz bár a változó ablakhossz miatt az alacsony frekvenciákhoz nagy puffer kellene, de valójában semmit nem kell pufferelni, egyetlen bejövő mintát sem. A cél ezzel az ötlettel egy oktávos felbontású 10-12 valódi sávos (tehát nem optikai tuningból interpolációval sokszorozó) megoldás megvalósítása. Azonban az, hogy mikor, arra sajnos most nem tudok még választ adni, ráadásul olyan nagy együtthatós táblát kell csinálni bele (20-24kByte, ha azt akarjuk, hogy tényleg gyors legyen), hogy csak a 32kB-os flash memóriás pl. m328P chipbe lehetne belegyömöszölni, tehát új eszközt is be kell szerezni hozzá (kivéve, ha az Arduino Nano-t fogom be a célra, persze asm-ben kódolva). Ugyanakkor még nem látni előre azt se, hogy számításigényben mennyire fog beleférni egy ATmega képességeibe, de első blikkre nem tűnik megoldhatatlannak vele, különösen 16-20MHz órával 416 órajel/mintavét esetén. (Ha 208 órajelen összehoztam 8 IIR sávszűrőt, akkor 416-ban muszáj összejönnie annak a megoldásnak is mondjuk 10 sávban!)
Letöltés csomagolva: fenyorg_2_m88a_8band.tar.gz