Proiect PM 2007


Acordor muzical în scară egal temperată

Salutare

Azi o sa vă povestesc despre cum se face un frecvenţmetru audio care ştie să zică şi nota muzicală cea mai apropiată de frecvenţa măsurată. Ce-l face oarecum interesant e că poate primi cam orice semnal scos de un instrument muzical când îl acordezi, adică nu e nevoie să ai o sinusoidă pură, nici măcar nu trebuie ca frecvenţele componentelor semnalului să fie în relaţie armonică. Dispozitivul va măsura cea mai mică frecvenţă de o amplitudine notabilă, ceea ce e plauzibil să aştepţi de la un instrument muzical ca definind nota cântată. Componentele de frecvenţe mai mari ar defini timbrul. Dacă acestea nu sunt în relaţie armonică cu prima (să zicem, raport de numere întregi mici), cum se întâmplă de exemplu la instrumentele cu coardă când amplitudinea de oscilaţie nu e "suficient de mică", forma de undă nu va fi constantă de la o perioadă la alta (se poate vedea în timp real pe osciloscop cum se modifică, fie şi pe osciloscopul virtual al winampului pe fişierele de pe situl [C1]). Alte motive pentru asta sunt viteza de atenuare dependentă de frecvenţă şi variaţia frecvenţelor modurilor superioare de oscilaţie cu amplitudinea (în principal cu amplitudinea modului fundamental care domină). Ideea este că avem de-a face cu un mediu neliniar: frecvenţa de rezonanţă e dată de viteza undelor transversale, care e dată de tensiunea în coardă care creşte cu elongaţia transversală (care e o măsură a cât de tare se trage de coardă). Tot pe [C1] găsiţi nişte reprezentări în timp şi spectrograme ale semnalului generat de o coardă de chitară electrică. Observaţi din fişierele audio cum timbrul variază în timp după ciupirea corzii. Când acordezi o chitară în principiu vrei să te afli în ultima parte a fişierului când forma de undă s-a oarecum stabilizat. (şi da, zic asta şi pentru că simplifică oarecum proiectul :p )

Teorie

Ideea imediată care mi-a venit când stăteam şi discutam cu Cosmin în ED218 pe marginea întrebării "se poate face un acordor de chitară cu un AVR?" a fost să fac un FFT, dat fiind faptul că, după cum v-aţi prins din reprezentările în domeniul timp de pe [C1], abordarea numărării trecerilor prin 0 e proastă. Pentru că dă o frecvenţă mai mare. Ce face FFT în termeni simpli e să numere trecerile prin 0 pentru toate componentele sinusoidale ale semnalului, numai că în loc de abordarea brute-force de a număra efectiv, face o înmulţire cu sinusoide de diferite frecvenţe, de fapt cu exponenţiale complexe periodice. Am găsit repede răspunsul la întrebarea "se poate face aşa ceva într-un AVR?" . Se poate face pe un număr mic de eşantioane, nu pentru că n-aş avea putere de calcul (am mult mai multă decât primele PCuri) ci pentru că nu am memorie. Proiectul trebuia făcut cu un ATmega16 care are doar 1K de RAM. Presupunând că aş face un FFT in-place cu o rată de eşantionare de 2048Hz şi aş fi folosit toată memoria (cam improbabil că aş fi reuşit să ţin toate variabilele în registre, dar deloc imposibil) aş obţine 1024 de puncte în domeniul timp pe care le convertesc în 1024 de puncte în domeniul frecvenţă deci voi avea o rezoluţie de 2Hz şi voi putea măsura o frecvenţă maximă de vreo 7-800Hz, şi în plus am nevoie de un filtru analogic foarte abrupt sau de un filtru trece-jos cu decimare în software (folosind doar registrele). Nu merge. Nici dacă aş fi folosit eşantioane pe 4 biţi în loc de 8 n-aş obţine un câştig prea mare. Ar fi mers cu o memorie externă de 64kB şi un microcontroler ATmega162 dar m-aş fi complicat prea mult. Am analizat nişte proiecte similare găsite prin gugăleală (cuvânt existent în dicţionar).

  • [A1] Digital Guitar Tuner, Jesper’s AVR Pages
    proiect simplu, foarte "brute force", amplifică puternic intrarea şi o introduce in uC pe un pin digital (nu se oboseşte cu conversie A/D -> oarecare avantaj: cost mic). Forma de undă este rezonabil de digitală (şi intrările în AVR au un mic histerezis deci vom presupune că e ok). I se măsoară durata dintre fronturi folosind un timer ce rulează 173kHz. Rezoluţia este dată practic de frecvenţa de numărare a timerului şi este posibil crescută prin medierea a 32 de măsurători.

Următoarele sunt proiecte software şi folosesc placa de sunet a unui PC pentru a eşantiona semnalul de la instrument. Următoarele 3 proiecte folosesc metoda FFT şi ilustrează ce am discutat mai sus - e nevoie de memorie multă.

  • [A2] Guitar Tuner
    foloseşte o frecvenţă de eşantionare de 22050 Hz şi o fereastră de 16384 eşantioane.
  • [A3] gTune
    în modul default eşantionează la 32768 Hz şi o fereastră de 0.5 secunde (minimul este de 0.1s) deci tot 16384 eşantioane.
  • [A4] NAT Spectrum Analyzer and Guitar Tuner
    mare parte din sourceforge.net era picat, n-am apucat deci să analizez codul, dar foloseşte FFT după toate probabilităţile

Interesant e că toate consideră ca relevantă frecvenţa corespunzând celui mai înalt vârf în FFT, şi nu cea mai mică frecvenţă cu amplitudine mai mare ca un procent oarecare din vârful respectiv. Proiectul meu s-a inspirat până la urmă din:

  • [A5] Guitune
    foloseşte o metodă similară cu [A1], dar mult mai rafinată: implementează în software un trigger schmitt (comparator cu histerezis), adică transformă semnalul într-unul dreptunghiular bazându-se pe trecerile sale prin 2 praguri ce sunt diferite funcţie de valoarea ieşirii. By default ele sunt setate la 60% din amplitudinea semnalului.

Practic în loc să numere trecerile prin 0, numără vârfurile. N-am reuşit să demonstrez matematic propoziţia că frecvenţa fundamentală (care mă interesează pe mine) egal 1/(durata dintre 2 vârfuri), dar pare destul de intuitiv, dacă vârfurile sunt maxime sau minime "globale". Se poate întâmpla să am 2 vârfuri alăturate de amplitudini similare şi deci să-mi fie greu să determin care e "principal", dar folosind metoda cu histerezis nu pot număra un al 2lea vârf pozitiv până nu am numărat un vârf negativ după primul. Astfel acopăr marea majoritate a formelor de undă generate de instrumentele muzicale. Pe [A5] se găsesc nişte poze convingătoare. Ca să mă conving şi mai bine, am simulat nişte cazuri cu fundamentala de 100Hz şi primele 2 armonice, cu diverse faze şi amplitudini. Am desenat cu mâna pragurile pentru trigger şi semnalul digital rezultat. Sus e reprezentată componenta fundamentală.

<< amplitudini 1V, 0.7V, 0.5V, în fază

În ultima poză (în care n-am mai reprezentat şi forma de undă digitală) se observă un fenomen interesant: am 2 componente de 200, respectiv 300 Hz. Frecvenţa fundamentală este tot de 100 Hz (cel mai mare divizor comun) chiar dacă această componentă nu există în spectrogramă.

În particular, dacă aş avea 2 frecvenţe de 499 şi 501 Hz voi obţine nişte bătăi (frecvenţă de 500Hz modulată în amplitudine la 1Hz). Fundamentala este de 1Hz chiar dacă "nu există". Muzical vorbind aceasta nu are vreun sens, nota este dată de frecvenţa de 500Hz şi pentru aceasta trebuie făcută cumva diferenţa între frecvenţe audibile şi simple variaţii de amplitudine. Aceasta se face ajustând pragurile comparatorului funcţie de valorile maxime respectiv minime ale semnalului trecute printr-un filtru trece jos cu o constantă de timp potrivit aleasă (de exemplu 100ms). Circuitul electronic ar fi un detector de vârf cu diodă pentru identificarea vârfului şi R-C pentru constanta de timp, dar va fi implementat mai ieftin în software: la fiecare 100ms updatez 2 variabile reprezentând max, min şi le fac 0 pe tempmax şi tempmin, pe cele din urmă updatându-le la fiecare eşantion.

Fenomenul despre care vorbesc e ilustrat în următoarele poze (care exemplifică şi ce se întâmplă când cineva decide să implementeze un osciloscop virtual mult prea realist. Bad idea. De-aia nu se mai fabrică osciloscoape analogice.)

În poza din dreapta se observă ieşirea detectorului de vârf în partea de sus. Trebuia să se observe ceva identic în partea de jos. E clar că dacă nu aş fi implementat acest element sau aş fi pierdut vârfuri, sau aş fi numărat prea multe. Oricum detectorul de amplitudine (de vârf) este esenţial pentru că la multe instrumente amplitudinea semnalului audio scade în timp (ceva gen exp(-t/tau), normal, se pierde energie). Excepţie ar face cântatul, când poţi pompa continuu energie în oscilator, deşi se poate face acelaşi lucru şi cu o coardă de vioară dacă ai talent. Totuşi trebuie să existe un prag proporţional cu amplitudinea semnalului şi această amplitudine trebuie detectată şi variaţia ei lentă nu trebuie interpretată ca ceea ce este matematic, adică o lăţire a liniilor spectrale. (Există oricum o întreagă discuţie legată de faptul că spectrograma capturează doar o bucată finită de semnal). Cu de la mine putere am definit "lent" ca sub 10Hz, o octavă sub limita minimă audio.

Nu numai că semnalul variază ca amplitudine în timp, dar diferite instrumente / combinaţii instrument-microfon vor da amplitudini foarte variate. Şi bineînţeles că se poate cânta mai tare sau mai încet. Am dorit astfel să pot măsura orice semnal cu amplitudinea între 20mV şi 2V. Apare aici o problemă foarte mare, anume convertorul A-D nu are decât 10 biţi rezoluţie şi pentru o precizie rezonabilă aş vrea ca amplitudinea ce intră în ADC să fie cât mai apropiată de Full Scale Range adică 2,5V minus căderea de tensiune pe cablul şi conectorii USB prin care alimentez montajul minus alte toleranţe. Soluţia imediată este un AGC (reglaj automat al amplificării). Am un amplificator cu câştig variabil inclus în modulul ADC al microcontrolerului dar nu mă satisface, pentru că are doar 3 trepte şi o impedanţă de intrare prea mică. Aşa că am proiectat un AGC extern comandat digital cu 8 trepte. E realizat din 2 etaje identice cu amplificator operaţional, câştigul în curent continuu e fix, iar în alternativ este setat prin cuplarea la masă a unor rezistenţe ponderate.

Am vrut să alimentez tot montajul la 5V de pe USB deci am avut nevoie de un AO care să poată lucra single-supply. Orice AO în particular poate lucra single-supply dar unele pot avea intrări şi/sau ieşiri rail-rail sau pot avea domeniul de intrare extins (de la V+ minus 1V şi ceva, până la V- minus 0.3V (sub masă)). LM358 [B2] ar fi un exemplu ieftin din ultima categorie, dar stă cam prost cu impedanţa de intrare şi ieşirea nu merge până la V+, doar până la masă. MCP602 pe de altă parte are ieşire rail-rail şi domeniu de intrare între -0.3 şi 3.8V când este alimentat la 5V. Ambele conţin 2 AOuri pe acelaşi cip, ceea ce e just pentru aplicaţia de faţă.

Am nevoie de o tensiune plasată la jumătatea domeniului de intrare, faţă de care să centrez semnalul de intrare cu un condensator. Această tensiune ar fi de 1.75V şi se realizează cu 2 divizoare rezistive. Acestea se alimentează de la o tensiune de circa 5V filtrată cu un circuit RC pentru a nu injecta zgomotul de pe alimentare pe intrare. Tot de aici se alimentează şi un eventual microfon cu condensator folosit pe intrare, prin conectarea unui jumper.

Pentru a centra ieşirea pe 2.5 V se foloseşte reacţia negativă în curent continuu. Scăpam de o rezistenţă dacă foloseam un AO cu domeniu de intrare care să includă V+ (de exemplu LMC6482), dar n-am găsit la un preţ suficient de mic. Reacţia negativă în curent alternativ este variată prin conectarea la masă a 3 rezistenţe cu ajutorul unor tranzistoare. Ca să nu polarizez invers tranzistoarele folosesc 2 rezistenţe de 2M spre 1.75V. Câştigul cumulat al celor 2 etaje se poate seta în 8 trepte între aproximativ 2 şi 250. Impedanţa de intrare este de circa 600 kOhmi.

Se observă un filtru RC pentru eliminarea interferenţelor radio la intrare.

M-am gândit să conectez şi un filtru trece-jos variabil pentru a uşura procesarea semnalului în soft, deşi am concluzionat ulterior că nu merită costul adăugat. Am simulat filtre RC cu un singur pol dar nu am observat vreun beneficiu evident. Ideal ar fi fost un filtru cu capacităţi comutate care să dea o atenuare mai abruptă, dar nu am găsit un astfel de circuit integrat. Cine e interesat de astfel de filtre poate vedea de exemplu datasheetul de la LMF100 [B1]. Sunt foarte tari pentru că frecvenţa lor caracteristică este dată de un clock, şi se pot realiza foarte bine în circuite integrate având performanţe bune (mulţi poli = tăiere abruptă). Ideea era să realizez întâi un FFT de precizie mică şi apoi să comand filtrul trece-jos, păstrând doar frecvenţa de interes, pe care o măsor cu precizie mai bună prin metoda descrisă.

Oricum este nevoie de un filtru anti-alias, m-am limitat la un circuit R-C simplu, ar fi trebuit probabil ceva mai complicat (RLC cu 2 celule), dar mă bazez pe ideea că nu va fi prea multă energie la frecvenţe mari în semnal, mai ales că eşantionez la 14.4kHz, deci probleme ar fi peste 7kHz, iar note măsor până la 4kHz.

Schema:

Ca software, am folosit gcc ca să compilez şi este foarte respectabil suportul pentru floating-point, de care nu m-am ferit să beneficiez. Unii memorează o tabelă cu note şi caută binar în ea, eu pur şi simplu am logaritmat frecvenţa ca să calculez nota. E o soluţie bună, pentru că sistemul merge, şi pot folosi un controler cu memorie puţină => cost mai mic într-un produs comercial. Tabela ocupă foarte multă memorie dacă ar fi să memorez toate notele, iar dacă nu, oricum trebuie să fac calcule suplimentare. (Notă: scara muzicală egal temperată presupune o scară exponenţială împărţită în octave a câte 12 note fiecare incluzând diezurile. Indexând cel mai jos do (C0) cu 0, avem frecvenţă(notă) = 440Hz * 2^((notă-57)/12), unde nota 57 este A4 (la) de 440Hz.

O problemă este durata mare necesară pentru calculul frecvenţei şi afişarea pe LCD, care nu se puteau face în timp real cu o frecvenţă mare de eşantionare. Aşa că am renunţat la ideea de a rula un timer şi de a mă sincroniza cu el, cum face de exemplu [A1]. Cunoscând frecvenţa de eşantionare (ADCul merge în free running mode cu o conversie la 13 cicluri, ceas f_cpu/64) pot calcula frecvenţa semnalului numărând eşantioanele şi comutaţiile comparatorului soft. Rezoluţia e dată practic de cât de mult timp stau să număr (8192 eşantioane -> eroare relativă de 0.01%). Eşantioanele sunt trecute într-o coadă din întreruperea de ADC conversion complete, şi preluate din coadă în bucla principală. Updatarea frecvenţei şi LCDului, operaţie costisitoare, se realizează mai rar de 2 ori pe secundă, şi nu afectează funcţionarea sistemului, pentru că eşantioanele se acumulează în coadă (254 de poziţii sunt suficiente la un ceas de 12MHz). Am folosit ceas de 12MHz pentru că voiam iniţial să implementez USB software, dar m-am răzgândit. Oricum, cu cât e ceasul mai lent, cu atât mai bine. Excepţie - dacă ajunge să funcţioneze la 8MHz nu vă gândiţi să folosiţi oscilatorul intern!! e imprecis. Dacă ajungea precizie de 1% foloseam FFT.

Mai multe detalii despre implementare găsiţi sub formă de comentarii în cod.

Practică

Proiectul e compus dintr-o placă de bază ("mini-placă-de-dezvoltare"), o placă de extensie şi un modul LCD, toate separabile.

Placa de bază poate primi un microcontroler ATmega8535, 16, 32, 324, 644 etc. (pin-compatibile).
Are un conector USB ce ar permite implementarea unui device low-speed, un port serial RS-232 (doar TXD şi RXD) şi 2 porturi de programare: unul serial compatibil cu cablurile din ED218 şi PonyProg, şi unul tot serial dar cu 5 pini conectabili la portul paralel cu doar 3 rezistenţe (şi respectivele opţionale) care permite o programare sensibil mai rapidă cu uisp sau avrdude. 

Programatorul ar fi o copie de DT-006 ca şi conectare la portul paralel, pinoutul pe placă e altul. Programarea e automatizată în Makefile (make flash), care trebuie editat funcţie de mediul de dezvoltare folosit. Pentru WinAvr trebuie găsit un uisp dintr-o distribuţie mai veche sau rescris Makefile-ul pentru avrdude. Se specifică portul paralel (de exemplu 0x378). Pe GNU/Linux trebuie instalate toolurile separat (avr-binutils, avr-gcc, uisp) şi încărcat modulul ppdev care permite accesarea portului paralel (/dev/parport0).

Placa de extensie conţine amplificatorul cu câştig reglabil şi un soclu pentru modulul LCD (16x1). Valorile rezistenţelor aşa cum le-am dat eu pot fi recalculate (ca să dea un răspuns mai liniar al amplificării funcţie de codul PA[7:5]).

Realizarea plăcilor s-a făcut prin metoda transfer toner. Deşi implică un oarecare efort, asigură prin funcţiile de ERC, DRC şi LVS ale softului de proiectat PCB-uri o oarecare corectitudine. Nu e neapărat vorba că ar arăta mai sexy, deşi este şi acesta un punct. Deşi am văzut proiecte mai aspectuoase şi erau pe plăci cu găurele.

Metoda implică tipărirea la imprimanta laser a unei imagini oglindite a circuitului imprimat, transferul termic al tonerului de pe hârtie pe suportul placat cu cupru, folosind un fier de călcat (atenţie, frige), dizolvarea hârtiei în apă şi îndepărtarea prin frecare cu degetul, corodarea cuprului neacoperit de toner cu clorură ferică (atenţie, produs toxic), găurirea cu bormaşina (din nou, atenţie, pericol accidente), acoperirea cuprului cu fludor (atenţie letconul frige).

În imagini:

proiecare PCB

tipărire

(poza al'dată)

transfer termic

(poza al'dată)

dizolvare, îndepărtare hârtie

pregătire pentru corodare: mâner din scoci
pentru a preveni atingerea clorurii cu mâna

corodare. placa pluteşte.
soluţia trebuie să fie cât mai caldă.

după

şi după găurire

spate

faţă

Arhive

Scheme, plăci (Eagle)
Cod
Documentaţie preliminară ("etapa 2")

Referinţe

A. Proiecte similare

1. Digital Guitar Tuner, Jesper’s AVR Pages
http://www.myplace.nu/avr/gtuner/

2. Guitar Tuner
http://developer.berlios.de/projects/guitar-tuner/

3. gTune
http://www.geocities.com/SiliconValley/Lakes/4189/gtune/index.html

4. NAT Spectrum Analyzer and Guitar Tuner
http://sourceforge.net/projects/natspectrum/

5. Guitune
http://www.geocities.com/SiliconValley/Hardware/2684/kguitune_page.html

B. Datasheets, Application Notes

1. Filtru LMF100
http://www.national.com/pf/LM/LMF100.html

2. Amplif operaţional LM358
http://www.national.com/pf/LM/LM358.html

3. Amplif operaţional MCP601/2/3/4
http://ww1.microchip.com/downloads/en/DeviceDoc/21314f.pdf

C. Altele

1. Semnal de chitară electrică, domeniu timp şi frecventă
http://physics.mtsu.edu/~wmr/four_5a.htm

2. Filtru variabil controlat în tensiune
http://www.uni-bonn.de/~uzs159/vcf4007.html