Zpracování signálu se zvukovou kartou - DirectSound DLL interface
Posledních pár let se pracovně zabývám vývojem digitálních vzorkovacích aparatur pro účely primární a sekundární metrologie elektrických veličin.
V uplynulých zhruba 15ti letech probíhal/probíhá celkem intenzivní výzkum s cílem digitalizovat a především automatizovat analogové měřící aparatury, které prozatím v mnoha oblastech poskytují nejlepší nejistoty měření.
Např. se v poslední době objevila celá řada algoritmů pro harmonickou analýzu signálů, takže se začaly vyvíjet digitální vzorkovací wattmetry, fázoměry, zkresloměry, digitální impedanční můstky a celá řada aparatur pro měření různých exotičtějších parametrů jako třeba flicker. Tyto systémy už dnes dosahují nejistot měření srovnatelných s původními analogovými sestavami. 1. Volba zvukové karty Volba karty byla celkem snadná záležitost. Potřeboval jsem něco externího s USB, aby se to dalo snadno galvanicky oddělit. To se vždycky hodí kvůli zemním smyčkám. Dál by to mělo umět 24bitů a především to musí používat tzv. "asynchronní režim". Tj. karta si řídí vzorkovací kmitočet sama podle interních hodin a PC jen doplňuje/vyčítá data z FIFO bufferů. Čili přesně, jako jsem to kdysi zkoušel s I2S. Toto je jediný režim, kdy je zajištěno synchronní vzorkování ADC a DAC. Existují ještě další režimy, kdy PC sype data vlastním tempem a karta nějak pochybně mění vzorkování nebo provádí resampling, ale to se samozřejmě použít nedá. Popravdě nevím, proč takovou hovadinu vůbec někdo vymyslel. Snad jen aby se vyhnul nějakým patentům, protože složitěji už by to asi fakt nešlo a o kvalitě zvuku ani nemluvě. Po troše googlení jsem zjistil, že jedna z mála recenzovaných karet za ještě rozumnou cenu je Creative X-FI HD USB. Od pohledu to dělal někdo, kdo o tom něco ví. Na DPS je oddělená analogová a digitální zem, dají se sehnat katalogové listy od DAC i ADC a dokonce tam je i zemní svorka. :-) To je tak trochu indikace, že to dělal někdo, kdo asi i ví, jak to použít. No a na bonus to není karta inzerovaná jako audiofilní/gamer produkt, takže je v celkem normálně hranatě vypadající pixle a né v nějaké hypermoderní předesignované kastli jako třeba model X7. 2. První testy - waveIn/waveOut
Jako první jsem zkusil pro ovládání staré dobré in/out, tedy waveIn()/waveOut() samozřejmě (co jiného, že...). Bohužel už prostý poslech generované čisté sinusovky ukázal, že je něco hodně špatně.
Bylo zřetelně slyšet, že se po začátku přehrávání několikrát skokově mění amplituda i kmitočet.
Tak jsem trochu googlil a zjistil jsem, že od Windoze Vista M$ konečně zahodil prehistorický audio stack a nahradil ho něčím odpovídajícím 21 století. Palec hore za to, protože např. způsob směšování streamů s různým formátem byla katastrofa.
Ale bohužel někdo něco podělal ve wrapper funkcích, které mají zajišťovat zpětnou kompatibilitu pro waveIn/waveOut rozraní. Sice to jakože něco dělá, ale na
zpracování signálu se to už opravdu použít nedá. Různě po fórech na tento jev nadávalo už dost lidí a našel jsem i řadu "osvedčených" řešení, ale jak se asi dá čekat, nic nefunguje.
Takže jsem se tedy rozhodl tyto 20+ let staré API opustit a použít něco novějšího. Kdyby někoho zajímaly detaily o novém audio stacku, tak je velmi pěkně vysvětlen zde: 3. Další pokusy - DirectSound
Nějak se mi nechtělo jít přímo ke zdroji a použít přímo nové WASAPI. Z dokumentace jsem zrovna moudrý nebyl a nějaké uspokojivé příklady jsem tehdy také nenašel.
Takže jsem si řekl, že zkusím další nejjednodušší řešení a to je DirectSound. To má tu výhodu, že by to mělo fungovat i v XP.
Sice jsem byl trochu skeptický, protože po zkušenostech s DirectDraw jsem očekával, že to budu řešit pěkně dlouho, ale ukázalo se to podezřele snadné.
Na rozdíl od waveIn/WaveOut zcela odpadá ta známá onanie s několika buffery, které bylo třeba připravit, plnit, rotovat a zase "odpřipravit". DirectSound má proti tomu jen jeden kruhový buffer a uživatel prostě jen periodicky zapisuje (přehrávání) nebo čte data (záznam), aby nedošlo k přetečení (záznam) nebo podtečení (přehrávání). Toť vše. Docela se to blíží low level programování na MCU. No vlastně kdysi prý DirectSound umožňoval vrtat přímo do primárního bufferu, tj. do paměti karty, ale to už je samozřejmě pasé, protože dnes je DirectSound jen wrapper pro WASAPI, jestli jsem to dobře pochopil.
4. DirectSound DLL interface Po prověření DS API funkcí jsem se rozhodl k tomu napsat nějakou knihovnu funkcí pro snadnější použití. Mé požadavky na funkce byly následující:
První dva požadavky jsou snadné. Na to už knihovny existují. Bohužel časové značky jsou zřejmě pro většinu programátorů neznámá věc, takže mě psaná vlastní knihovny stejně neminulo. Časové značky jsou velmi užitečný nástroj např. pro měření více signálů v časovém multiplexu. Problém je ilustrován na obr. 4.1.
Dva signály, A a B, jsou generovány dvěma střídavými zdroji. Oba jsou postupně měřeny společným ADC pomocí přepínače (multiplexeru). Je vidět, že oba signály mají sice stejnou fázi, ale vzhledem k tomu, že začátky vzorkování nejsou nijak synchronizovány se signály A a B, mají zaznamenané signály zjevně jinou fázi. Navíc při opakování měření bude tento fázový posuv jiný, protože interval mezi t2 a t4 je díky granularitě časovače systému a zpožděním různých API náhodný. Je tedy zřejmé, že pro určení fázového posuvu mezi signály je potřeba znát časový interval mezi t1 a t4. Měřit ho pomocí systémového časovače je nesmysl, protože časovač má mizerné rozlišení a navíc není nijak svázán s časovou základnou zvukové karty. Jedno z možných řešení je prostě vzorkovat kontinuálně od t1 do t5 a pak z té nudle vybrat dvě okna A a B. Vzhledem k tomu, že je mezi nimi známý počet vzorků a je známá i vzorkovací perioda TS, lze spočítat časový posuv a provést korekci podle:
kde fx je kmitočet měřeného signálu a ΦA a ΦB jsou fáze signálů z oken A a B získané např. pomocí DFT. Toto řešení ovšem není příliš praktické pro složitější měřící sekvence, protože to znamená zaznamenat a uložit celkem značné množství dat, z čehož většina je často ustalování relé.
To je jeden z důvodů, proč každý seriózní digitizér podporuje časové značky. Tj. současně se zaznamenaným průběhem vrací relativní čas prvního vzorku počítaný od nějaké společné události, např. resetu. Některé modely dokonce umožňují i absolutní časové značky odvozené od GPS, ale to už je jiná story.
Výhodné na tomto řešení je, že časové značky jsou odvozené od stejných hodin, jako vzorkování, takže se dá říct, že jsou, vzhledem k zaznamenanému signálu, absolutně přesné. Tedy až na jitter.
4.1. Knihovna DSDLL
Celá knihovna DSDLL (hodně kreativní název...) je řešená jako klasická DLL. Obsahuje funkce pro výčet zařízení, přehrávání, záznam, konfiguraci a asynchronní zjišťování stavu. Až na jednu výjimku používá jen typy int32, uint32, double a pointery, takže ji lze snadno použít v LabVIEW. A když to jde v LV, tak to musí jít snad všude.
Původně jsem to chtěl napsat ve starém dobrém Borland BDS2006 Turbo C++, ale nějak jsem zjistil, že to do Windoze 7 nedostanu, protože instalace vyžaduje nějakou prehistorickou verzi .NET, která jaksi pro Windoze 7 neňa.
Řešit to ve virtuálu se mi nechtělo, takže jsem zkusil M$ Visual Studio 2013.
Překvapivě to docela funguje a sžít se tím ani nebylo nijak zvlášť složité.
No a na rozdíl od RAD Studia, jakožto nástupce Borlandu, je to free.
4.1.1 Licence Vzhledem k tomu, že se nejedná o nějakou převratnou věc, rozhodl jsem se knihovnu uvolnit pod GNU Lesser GPL, která je poněkud méně restriktivní, než klasická GNU GPL. Mimo jiné umožňuje linkovat DLL i do komerčních produktů. Demonstrační utilitky jsou uvolněny pod ještě méně restriktivní licencí WTFPL. To je sice tak trochu trolling, ale jinak se mi líbí ta jednoduchost. :-) 4.1.2 - Základní použití Popis jednotlivých funkcí je přímo ve zdrojácích i hlavičkových souborech a použití by mělo být jasné z demonstračních prográmků, takže zde jen pár základních informací formou příkladů pro C/C++. 4.1.2.1 - Výčet DirectSound zařízení Následující kód umožňuje jednoduchý výčet dostupných DS zařízení. Nejdřív se naplní seznamy vstupních a výstupních zařízení voláním sb_enum(). Následně se pomocí sb_enum_get_device() získá GUID zvoleného zařízení. Pak lze seznamy zase smáznout voláním sb_enum_clear_list(). Získané GUID se pak použijí k samotnému otevření zařízení pomocí sb_open(). // initialize empty lists of input/output devices TSDev dsolist = {0,0,NULL}; TSDev dsilist = {0,0,NULL}; // fill the lists with DirectSound devices sb_enum(&dsolist,&dsilist,NULL,NULL); // obtain the GUID of selected input/output devices, id = 0 is default device GUID outguid; GUID inpguid; sb_enum_get_device(&dsolist,output_device_id,&outguid,NULL,0); sb_enum_get_device(&dsilist,output_device_id,&inpguid,NULL,0); // clear the lists sb_enum_clear_list(&dsolist); sb_enum_clear_list(&dsilist); // now the DirectSound can be initialized by calling sb_open() with the GUIDs sb_open(..., &inpguid, &outguid, ...); 4.1.2.2 - Otevírání a zavírání DirectSound zařízení
DSDLL obsahuje jen jednu společnou funkci pro otevření vstupních a výstupních zařízení.
V první řadě je třeba zvolit formát přehrávání a záznamu "fmt".
Vzorkovací kmitočet je společný pro záznam i přehrávání. Počty kanálů se mohou lišit, ale musí být platné, tj. minimálně 1 i když vstup/výstup není použit.
sb_open() skousne i NULL parametry místo GUID. V tom případě prostě vstupní/výstupní zařízení nebude použito.
Pokud je zadáno prázdné GUID (samé nuly), otevře se defaultní vstupní/výstupní zařízení. Upozorňuji, že to není standardní chování DirectSound!
To jsem tam přidal dodatečně, protože defaultní zařízení ve výčtu z nějakého důvodu nemá přiřazeno GUID. // create a wave format descriptor TSBfmt fmt = {sampling_rate,output_channels,input_channels,wave_format_id}; // make&clean DirectSound structure TSBhndl ds; ZeroMemory((void*)&ds,sizeof(TSBhndl)); // create DirectSound object (both input/output devices) sb_open(&ds,&inpguid,&outguid,&fmt); Vstupní i výstupní zařízení lze zavřít voláním sb_close(). To zároveň ukončí případný záznam nebo přehrávání. // close DirectSound devices sb_close(&ds); 4.1.2.3 - Přehrávání záznamu Pokud je výstupní zařízení otevřeno, lze zahájit přehrávání pomocí sb_output_write(). Pole "data" je dvourozměrné pole s prvky podle formátu ve "TSBfmt" struktuře použité při sb_open(). Kanály jsou proloženy, tj. 1. levý, 1. pravý, 2. levý, 2. pravý,... a používá se pochopitelně little-endian. Přehrávání lze spustit buďto rovnou pomocí sb_output_write(...,1/2) nebo zpožděně pomocí sb_output_play(). To lze použít i opakovaně, pokud výstupní buffer stále existuje. Obě funkce jsou neblokující, tj. vrací ihned. // writing data and starting playback immediately (loop_mode > 1) sb_output_write(&ds,(void*)data,samples_count,loop_mode); // writing data but not starting playback yet sb_output_write(&ds,(void*)data,samples_count,0); // doing some stuff ... for instance setting the volume and panning sb_output_set_volume(&ds,0,0); // starting the playback (only if the data were written before) sb_output_play(&ds,loop_mode); Přehrávání lze ukončit voláním sb_output_stop(). Stav přehrávání, resp. výstupního zařízení obecně, lze vyčíst pomocí sb_output_status(). Příklad použití při čekání na dokončení přehrávání je v následujícím kódu. while(1){ // get playback status DWORD count; int stat; int err = sb_output_status(&ds,&stat,NULL,&count,NULL,NULL); // leave if playback is done or no output data or something f.cked up if(err || !count || !stat) break; else Sleep(50); } 4.1.2.4 - Záznam Pokud bylo otevřeno vstupní zařízení, může být zahájen záznam daného počtu vzorků pomocí sb_capture_wave(). Tato funkce je na rozdíl od sb_output_write() synchronní, tj. nevrátí, dokud není záznam dokončen. Příklad záznamu s formátem "float" (DSDLLF_IEEE32) je ukázán v následujícím kódu. // allocate buffer for the captured data float *buf = (float*)malloc(sample_count*input_channels*sizeof(float)); // capture the waveform, return actual samples count 'read' and the 'time_stamp' double time_stamp; DWORD read; sb_capture_wave(&ds,(void*)buf,sample_count,&read,&time_stamp); Alternativně může být potřebná velikost bufferu získána pomocí sb_capture_get_size(). I když je funkce sb_capture_wave() synchronní, může být přerušena voláním sb_capture_wave_abort() a lze také vyčítat stav záznamu sb_capture_wave_get_status(). Tyto funkce je ale pochopitelně třeba volat z jiného vlákna, než sb_capture_wave(). Je to trochu netradiční řešení, obvykle to bývá opačně, ale vzhledem k tomu, že tyto funkce používám výhradně v GUI, které stejně vždy mám v jiném vlákně, tak to nepředstavuje problém. 4.1.2.5 - Ostatní DSDLL má ještě řadu dalších funkcí pro konfiguraci, výpis chyb apod. Tyto funkce jsou popsány ve zdrojáku, takže není třeba je zde rozebírat. Jedinou podstatnou věcí je možnost logování pro účely ladění. Pokud je ve složce s "dsdll.dll" nalezen soubor "dsdll.ini" a je v něm následující: [DEBUG] ;enable logging (0-none, 1-basic function, 2-include status checking functions) log = 1
pak bude po přilinkování vytvořen soubor "debug.log" ve stejné složce. Do toho by se měly ukládat všechny volání jednotlivých funkcí knihovny. [CAPTURE] ;default capture thread priority (see SetThreadPriority() API for values, default: +1 - above normal) thread_priority = +1 ;default capture thread idle time [ms] (typically 10 ms, max 500 ms) thread_idle_time = 10 Ty ovlivňují časování záznamového vlákna. Zvyšování priority vlákna by teoreticky mohlo mít účinek při těžce vytíženém CPU nebo u jednojádrových CPU. Prodlužování idle periody vlákna, když zrovna neprobíhá záznam by mohlo snížit zatížení CPU u nějaké starší šunky, ale nelze ho zvýšit nad 500 ms, protože velikost záznamového bufferu je jen cca 1 s. 4.2 - C/C++ příklady Pro snažší pochopení jsem připravil pár příkladů v C/C++. Zdrojáky i binárky jsou součástí projektu knihovny. 4.2.1 - Základní přehrávání
Toto demo provede výčet DS zařízení, otevře defaultní výstup a spustí přehrávání sinusovky.
4.2.2 - Replay demo
Toto demo provede výčet DS zařízení. Otevře defaultní vstup a výstup, zaznamená pár vteřin a pak tento záznam opakuje ve smyčce až do stisku klávesy.
4.2.3 - Časové značky Toto demo je jednoduchý příklad použití časových značek. Line-out zvukovky je zapojen do line-in. Výstupní zařízení generuje kontinuálně sinusovku. Frekvence signálu a počet vzorků smyčky je nastaven tak, aby signál byl koherentní. Vstupní zařízení opakovaně zaznamenává signál z line-in. Spouštění vzorkování je záměrně pseudonáhodné. Počet zaznamenávaných vzorků je opět nastaven tak, aby zaznamenaný průběh (délka okna) byl koherentní s generovaným signálem. Díky tomu lze amplitudu a fázi zaznamenaného průběhu snadno spočítat pomocí DFT. Vypočtená fáze Φx takto zaznamenaného signálu je pochopitelně náhodná (viz zde), takže je korigována právě pomocí časových značek:
kde fX je frekvence signálu v Hz a tS je časová značka vrácená sb_capture_wave().
Pokud všechno v pořádku, měla by vypočtená fáze být téměř neměnná. Bude samozřejmě vykazovat pomalý drift daný teplotní závislostí analogové části karty a nějaký ten šum.
Případné rychlé změny nebo skoky svědčí o tom, že karta buďto nevzorkuje synchronně (zmíněné adaptivní vzorkování) nebo někde v DSDLL přetékají/podtékají buffery, což by mohlo být způsobené nevhodným nastavením časování záznamového vlákna.
Následující grafy ukazují příklad měření s tímto prográmkem s Creative X-FI HD USB. Po nějakých 80ti minutách se fáze změnila o méně, než 1.5 µrad a amplituda pod 4 ppm, což není špatný výsledek na zvukovku za 1800 KČ. V laborce s klimatizací by to nejspíše bylo ještě lepší.
4.3 - Příklady pro GNU Octave/Matlab
Osobně celkem často používám pro zpracování signálu a dat prostředí GNU Octave.
Je to téměř 100 % kompatibilní s Matlabem a po letech vývoje se to dokonce už i dá rozumně použít ve Windoze.
Problém je, že volat z GNU Octave přímo DLL je trochu oříšek. Z toho důvodu jsem vyrobil jednoduchou utilitku "dsrec.exe".
4.3.1 - Synchronní vzorkovací fázoměr
Toto jednoduché demo je základní implementace vzorkovacího fázoměru pro koherentní signály.
Na výstupu generuje sinusovky s daným fázovým posuvem o zadaném kmitočtu a amplitudě, které jsou spočítány tak, aby bylo možně generovat signál ve smyčce a byl koherentní.
Průběhy zaznamenané vstupním zařízením jsou analyzovány pomocí FFT a z rozdílu fází je vypočten fázový posuv.
Vzhledem k tomu, že se používá primitivní FFT, tak tento fázoměr lze použít výhradně na měření fáze generovaných signálů, resp. je lze prohnat nějakým analogovým obvodem, ale nelze měřit fáze externích signálů, protože nebudou koherentní.
4.3.2 - Jednoduchý měřič vstupní impedance zvukové karty Toto jednoduché demo umožňuje měřit vstupní impedanci kanálu zvukovky s použitím známé externí impedance. Výstup zvukovky generuje sinus na zvolených kmitočtech. Jeden vstupní kanál je zapojen přímo na výstup a slouží k měření referenčního napětí U1. Druhý vstup je zapojen na výstup přes referenční impedanci Zref a je na něm měřeno napětí U2. Zref tvoří spolu se vstupní impedancí Zi napěťový dělič, takže lze ze známých vstupních a výstupních napětí U1 a U2 a referenční impedance Zref vypočítat vstupní impedanci:
Je to vlastně jednoduchý impedanční můstek, jen měřená impedance je přímo vstup zvukovky.
Aby měření mělo nějaký smysl, přidal jsem do skriptu i funkci na korekci mezikanálových chyb karty, bez kterých zejména vedlejší složka impedance, v tomto případě ekvivalentní paralelní kapacita, bude mít velmi mizernou přesnost.
Skript tedy uživatele vyzve k zapojení obou vstupních kanálů přímo do výstupu karty. Provede se série měření mezikanálových chyb fáze a amplitudy, výsledek se uloží do souboru a ten je pak při samotném měření použit jako gain/phase korekce.
4.3.3 - Měření přeslechu zvukové karty Update 2.9.2016: V několika aplikacích jsem se pokoušel použít zvukovou kartu Creative X-FI HD USB jako ADC a DAC. Vzhledem k tomu, že běžně měřím s nejistotami v řádu ppm (0.0001 %) a jednotek µrad, je nezbytné korigovat všechny možné rušivé vlivy. Jeden z těch základních zdrojů chyb je samozřejmě přeslech mezi vstupními kanály a také přeslech z výstupu na vstup. Zní to ovšem jednodušeji, než jaké to ve skutečnosti je. Problém parazitních vazeb v rámci dvoukanálové zvukovky je ilustrován na následujícím obrázku.
Je zřejmé, že možných vazeb je uvnitř zvukové karty minimálně osm. Na to, aby je bylo možné nějak rozumně korigovat je potřeba je změřit a to jednotlivě. Pokud je použit jen jeden výstupní kanál karty, což v mých aplikací je zatím vždy, tak se situace trochu zjednoduší, zůstanou už jen čtyři přenosy. Ty ale musí vzájemně být odděleny. Např. pro měření přenosu C34 nelze jednoduše generovat sinus na "line-out R", připojit ho do "line-in L" a měřit parazitní napětí U1R na "line-in R", protože na něm bude superponováno také napětí přes vazbu C24 přímo z výstupu "line-out R". Jediná cesta tedy zřejmě je buďto použít jiný zdroj napětí, který ale nebude koherentní, což značně zkomplikuje zpracování signálu, nebo nejdřív nechat oba vstupy nezapojené, změřit zbytkové napětí U0R na "line-in R" indukované přes vazbu C24 a pak ho odečíst při vlastním měření vazby C34. To má ovšem drobný háček. Všechny napětí a přenosy musí být měřené jako komplexní čísla, tj. nejen amplituda, ale i fáze. A pokud při měření U0R neznám fázi referenčního signálu, jakože ji neznám, protože ho kvůli přeslechům nemohu připojit na žádný vstupní kanál, tak lze změřit jen amplitudu. Jediné řešení tedy zřejmě je použití časového multiplexu a externího multiplexeru (odpojovače) mezi výstupem a analyzovaným vstupem. K tomuto účelu jsem spíchl přípravek podle nesledujícího schématu.
Aby to k něčemu bylo, musí odpojovač zajistit izolaci lepší, než nějakých -140 dB. Proto jsem použil dvě vhodně zapojené relé za sebou.
Jsou asi 2cm daleko a druhé relé je odděleno kovovou přepážkou, protože jinak to přes plastové pouzdro chytalo kapacitní vazbu.
Změřený parazitní přenos přes rozpojený multiplexer je menší, než -150 dB na 20 kHz. Relé jsou navíc zapojeny tak, aby při rozpojené cestě byl výstup zvukovky zatížen zhruba stejnou impedancí, jako v průchozím stavu.
K tomu slouží substituční impedance RL a CL.
Ovládací cívky jsou jedním koncem zapojeny proti signálové zemi a jsou blokovány kondíky a vzájemně ještě odděleny RCR členem, jinak to má tendenci chytat vazbu přes cívky.
Konkrétně jsem použil bistabilní relé ZETTLER AZ850P1-5, které se i přes nízkou cenu celkem osvědčily.
Abych nemusel tahat nějaké ovládací signály z COM portu nebo USB a psát kvůli tomu ještě další program, je multiplexer ovládán pomocí nevyužitého kanálu zvukovky. Ten generuje kladné a záporné pulzy.
Libovolný J-FET operák s rail-to-rail výstupem funguje jako schmittův KO, tranzistory ho proudově posílí a kondík C3 už generuje samotné ovládací pulzy pro cívky relé.
Hodnoty součástek jsou nastavené tak, že to s 1 ms pulzy spolehlivě spíná při line-out výstupu Creative X-FI HD USB ještě s poloviční amplitudou.
S uvedeným OZ je klidová spotřeba cca 40 µA, ale dalo by se to poladit i na pár µA. To je velká výhoda bistabilních relé.
Jeden takový multiplexer mám postavený a klidová spotřeba je jen cca 0.5µA. To už pak není ani potřeba odpojovat baterku a s nějakou lithiovou 9V to vydrží klidně 5 let provozu. :-)
Pro určení zbylého přenosu C43 je třeba prohodit vstupní kanály a měření zopakovat. Obdobně pro určení vazeb z levého výstupu, pokud je třeba. Následující dva grafy ukazují měření jednotlivých koeficientů v závislosti na kmitočtu. Jak je patrné, měření s referenčním signálem na obou line-in kanálech ukazuje zhruba identické přeslechy. Je celkem zvláštní, že přenos výstup-vstup má optimum okolo 1 kHz a na obě strany přeslech roste. Přeslech mezi vstupními kanály je asi až do 3 kHz nějakých -94 dB, pak roste zřejmě vlivem kapacitní vazby. To je celkem dost vysoký přeslech a je tedy zřejmé, že měřit cokoliv seriózně oběma kanály současně bez korekcí v podstatě nelze a celkem to i vysvětluje, chyby pokusného fázoměru.
Protože mi tvar přeslechu výstup-vstup přišel poněkud podezřelý, zkusil jsem měření zopakovat při zvýšené zátěži výstupu. Použil jsem k tomu odpory 1 kΩ a 100 Ω. Následující dva grafy ukazují, jak se přeslechy změnily.
Přeslech na nízkých kmitočtech se celkem brutálně zvýšil a na vysokých se dost podezřele zdeformoval. Nemám náladu provádět reverzní inženýrství karty, abych zjistil příčinu, ale plyne z toho závěr, že není rozumné výstupy příliš zatěžovat. Je zřejmě vhodné oddělit výstup nějakým sledovačem s J-FET operákem. Kapacitní zátěž kabelem sice zůstane, ale činná složka tak vzroste minimálně do řádu MΩ. 4.4 - LabVIEW knihovna Aktualizace 22.4.2017: Konečně jsem měl náladu trochu poladit a otestovat svoji DSDLL knihovnu pro LV. Jedná se v zásadě jen o wrapper DLL knihovny napsaný tak, aby se tím daly snadno nahradit ovladače libovolného profesionálního digitizéru. Knihovna obsahuje také pár demo aplikací napsaných tak, aby nevyžadovaly žádné knihovny, což je trochu problém, protože základní LV neobsahuje ani FFT.
Knihovna je napsané v LV 2013 a je dostupná pod licencí GNU Lesser General Public License. Je rozdělená do několika skupin funkcí: Inicializace, řízení, přehrávání a záznam. Jedinou změnou proti volání z C/C++ je, že se musí inicializovat handle DSDLL. To slouží k nalinkování DLL knihovny. Standardně je očekávaná ve složce s binárkou, ale lze zadat konkrétní cestu. Navíc byly přidány vstupně/výstupní funkce pracující s formátem 'double', které si samy převedou data na potřebný formát. Zbytek je identický přímému volání DLL.
4.4.1 - Základní demo Je to nejjednodušší použití knihovny. Umožňuje nahrát vzorek a ten následně přehrávat ve smyčce. Mimo to asynchronně zjišťuje pozice pointerů v bufferech.
4.4.2 - Měření frekvenčního přenosu Toto demo je určeno k měření frekvenčního přenosu dvoubranu pomocí bílého šumu. Na line-out je generován bílý šum. Ten je přiveden na vstup dvoubranu. Na levém a pravém kanálu line-in je nahráváno vstupní a výstupní napětí uL,R(t). Z těch jsou následně vypočteny spektra UL,R(f). Komplexní přenos je vypočten z jejich poměru: G(f) = UR(f)/UL(f). Výsledek je filtrován a zobrazen jako Bode plot a v Nyquistově rovině. Na screenshotu na obr. 4.20 je měřena pásmová zádrž (TT-filtr).
4.4.3 - Měření stability Poslední demo je ekvivalentní C/C++ demu pro měření stability. Na line-out je generován sinus, na line-in je nahráván. Pomocí FFT je vypočtena amplituda a fáze, pomocí časových značek je korigována fáze, takže když to funguje, měla by být fáze i amplituda konstantní jen s pomalými fluktuacemi díky teplotní nestabilitě analogové části zvukové karty.
5 - Nějaké ty měření Zde je/bude pár příkladů měření s Creative X-FI HD USB. 5.1 - Nekoherentní vzorkovací fázoměr Jen tak ze zvědavosti jsem zkusil napsat jednoduchý vzorkovací fázoměr pro sinusové signály. Vzhledem tomu, že generátor Clarke-Hess 5000, který jsem použil ke generování referenčního fázového posuvu nelze nijak synchronizovat se vzorkovací kartou, jsou zaznamenané průběhy pochopitelně nekoherentní a nelze tudíž použít primitivní DFT. Pro tyto účely existuje celá řada algoritmů, ale asi nejjednodušší je prosté proložení navzorkovaného průběhu víceparametrovým modelem harmonické funkce. Může to znít trochu komplikovaně, ale naštěstí v GNU Octave je pro účely prokládání nelineárních funkcí funkce leasqr() z balíku "optim". Tato funkce dokáže proložit snad cokoliv. Konkrétně je výpočet fáze zaznamenaného průběhu proveden následovně: % four parameter model of the harmonic function (amplitude, phase, frequency and dc offset) F = inline(' th(1).*sin(2*pi*th(2)*t + th(3)) + th(4) ','t','th'); % initial guess of the captured waveform parameters amplitude = 0.5*(max(waveform) - min(waveform)); offset = 0.5*(max(waveform) + min(waveform)); phase = 0; frequency = must_be_known_at_least_approximately; % vector of initial parameters th = [amplitude frequency phase offset]; % fit the 'waveform' which is function of 'time' by the function 'F' [..., par] = leasqr(time, waveform, F, th, ...) % calculate complex signal amplitude U = par(1)*exp(j*par(3)); % extract the phase measured_phase = arg(U); Tento primitivní skript je použit pro výpočet fází obou signálů a ty jsou pak odečteny. Vypadá to sice dost jednoduše, ale při dobrém počátečním odhadu parametrů to snad vždy konverguje. V příkladu nijak neřeším počáteční odhad fáze, což často vede k inverzi amplitudy, ale s tím si poradí předposlední řádek. Pro prověření funkce jsem vstupy karty zapojil přímo na generátor CH5000. Měření bylo provedeno jen pro několik kmitočtů a napětí 1 Vrms na obou kanálech. Pochopitelně před samotným měřením jsem spojil oba kanály na jeden výstup generátoru a provedl odečet zbytkového fázového posuvu mezi kanály karty. Tím pak korigují vlastní měření. Výsledek tohoto experimentu je uveden v tabulce 4.1.
Dle očekávání jsou výsledky celkem uspokojivé. Odchylky fáze jsou max v řádu m°. Nejistota měření je dána především specifikacemi CH5000. Jeho kalibrační data mají sice nejistotu podstatně nižší, ale už jsou přes rok stará, takže nelze odhadnout, jestli odchylky způsobuje CH5000 nebo fázoměr. Každopádně to není špatný výsledek a bez problému předčí komerční přístroje v řádu desítek kKČ. Kdyby se k tomu dodělaly korekce na přeslech mezi kanály, tak by to mohlo mít přesnost ještě vyšší. Update 5.9.2016: Nějak jsem neodolal a zopakoval jsem totéž měření s korekcemi na mezikanálové přeslechy. Celá korekce spočívá v odečtení parazitních napětí na obou kanálech indukovaných přes přenosy C34 z levého do pravého line-in vstupu a C43 z pravého do levého vstupu: % U1 and U2 are the fitted complex voltages U from the fitting script % fix the voltages by subtracting the crosstalk voltages U1c = U1 - U2*C43; U2c = U2 - U1*C34; % calculate the phase difference phase_shift = arg(U1c/U2c); Podle očekávání se chyby dost snížily především na vysokých kmitočtech, kde byly přeslechy nejhorší. Nové výsledky měření jsou uvedeny v tab. 4.2. Efekt korekcí by byl ještě vyšší, kdyby byl měřen jiný poměr napětí, než 1:1.
(c) 2017, Stanislav Mašláň - All rights reserved.
|