Fényorgona (helyett spektrumanalizátor)

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
  1. 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!
  2. 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

4 thoughts on “Fényorgona (helyett spektrumanalizátor)”

  1. 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…

  2. Bár még tesztelgetem az új IIR szűrőt, azért magát a kódrészletét megosztom:

    	ldi	temp3,133 ; 6kHz   beállítjuk az együtthatót (Vigyázz, temp3 regisztert vermelni kell!)
    
    	movw	stempL, filter1L ; vesszük az y[n-1] elõzõ kimeneti értéket
    	sub	stempL, sampleL  ; kivonjuk belõle x[n]-t, elõállt y[n-1]-x[n]
    	sbc	stempH, sampleH
    	mul	stempL, temp3    ; együtthatóval való (tört)szorzás (alsó bájtrész)
    	sub	filter1L,R1      ; alsó bájtrész kivonása egy bájt lejjebb tolás mellett
    	brcc	PC+2             ; ha történt átvitel, akkor azt átvezetjük a magas bártra
    		dec	filter1H
    	mulsu	stempH, temp3    ; felsõ bájtrész elõjeles szorzása
    	sub	filter1L,R0      ; felsõ bájtrész kivonása
    	sbc	filter1H,R1

    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.

  3. 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

  4. 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

Vélemény, hozzászólás?