Arduino VU Meter

25 07 2014

Pratęsiu audio signalų valdymo su Arduono ciklą ir aprašysiu kaip per Arduino stebėti garso signalo stiprumą. Tai būtų elementarus VU meter.

Techninė įranga

Apžvelgsiu trumpai technines garso signalo nuskaitymo galimybes. Jo yra ne viena ir ne kelios, tad visko nepaminėsiu.

Pirmasis būdas būtų pasinaudoti specialią mikroschemą MSGEQ7 su kuria galima stebėti visus 7 garso diapazonus. Kiekvienas diapazonas nuskaitomas atskiru analogRead() metodu prieš tai paprasčiausiai perjungiant rėžimą per STROBE elektrodu. Kelių diapazonų nuskaitymas yra geras dalykas ir turint tokią informaciją galime LCD ekrane sugeneruoti neblogą vizualizacija. Tačiau mano atveju tokio blatno VU man nereikia.

Pats paprasčiausias būdas būtų dar nestiprinta Audio signalą iškarto nusiskaityti su analogRead(). Šiuo atveju nuskaitytume tik abstraktu audio stiprumą, kurio man pakaks. Rasite daug pavyzdžių internete kur audio signalas pajungiamas tiesiai prie Arduino analoginio įėjimo. Taip daryti kiek pavojinga, nes audio signalas kartasi gali būti nenuspėjamas, o mes rizikuoti sudeginti ATMEGA328P juk nenorime. Taigi galime kiek įmanoma stipriau pasaugoti jį panaudojus kelis paprastus elektronikos komponentus:

z

Šiuo atveju mes ne tik su susilpniname tekančią link Arduino srovę, bet ir įtampa ribojame GND-VCC ribose. Kaip žinia analoginiai audio signalai yra kintamas tarp neigiamo ir teigiamo, tad dviejų varžų(22k) pagalbą audio signalą paverčiame vien tik teigiamu, centruotu ties 5V viduriu. Dabar, kuomet audio šaltinis man sius nuli aš nusiskaityti teoriškai turėčiau 2,5V, ko esant garsui analoginiame signale matysime mažėjančias ir didėjančias reikšmes, bet vidurio taškas bus visada ten pat. Dabar saugiai galime nuskaityti audio signalą. Tiesa mikrofonui toks variantas netinkamas, jam prieš tai reikia signalą dar ir sustiprinti su priešstrintuviu. Mano atveju audio signalas yra jau pastiprintas tad pakako breadboard’e susidėti reikiamus komponentus:

IMG_0441

Programavimas

Pasidalinsiu dvejomis realizavimo versijomis. Abiem variantais 1602 LCD ekrane vaizduosiu stereo VU stulpelių pagrindu. Tam pirmiausia sukūriau kelis papildomus simbolius, kurie leis spausdinti ne 16 segmentu vienoje eilutėje, o visus 80:

byte r1[8] = {
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b10000
};
byte r2[8] = {
0b11000,
0b11000,
0b11000,
0b11000,
0b11000,
0b11000,
0b11000,
0b11000
};
byte r3[8] = {
0b11100,
0b11100,
0b11100,
0b11100,
0b11100,
0b11100,
0b11100,
0b11100
};
byte r4[8] = {
0b11110,
0b11110,
0b11110,
0b11110,
0b11110,
0b11110,
0b11110,
0b11110
};
byte l1[8] = {
0b00001,
0b00001,
0b00001,
0b00001,
0b00001,
0b00001,
0b00001,
0b00001
};
byte l2[8] = {
0b00011,
0b00011,
0b00011,
0b00011,
0b00011,
0b00011,
0b00011,
0b00011
};
byte l3[8] = {
0b00111,
0b00111,
0b00111,
0b00111,
0b00111,
0b00111,
0b00111,
0b00111
};
byte l4[8] = {
0b01111,
0b01111,
0b01111,
0b01111,
0b01111,
0b01111,
0b01111,
0b01111
};

lcd.createChar(0, l1);
lcd.createChar(1, l2);
lcd.createChar(2, l3);
lcd.createChar(3, l4);
lcd.createChar(4, r1);
lcd.createChar(5, r2);
lcd.createChar(6, r3);
lcd.createChar(7, r4);

Dabar turime susikurti kelis kintamuosius kurie bus naudojami kairiojo ir dešiniojo signalų saugojimui:

int lMeterMin=1023;
int lMeterMax=0;
int lMeterVal=0;
int rMeterMin=1023;
int rMeterMax=0;
int rMeterVal=0;

Min ir Max reikšmes naudosime tam kad ekrane bent jau kažkoks veiksmas matytųsi. Mūsų analoginės reikšmės gali kisti 100 tašų skirtumu, tad išnaudoti likusios 900 taškų erdvės būtų nesamanė.

Dabar laikas nusiskaityti reikšmes:

lMeterVal = analogRead(LVUPIN);
rMeterVal = analogRead(RVUPIN);
if (lMeterVal > lMeterMax)
lMeterMax = lMeterVal;
if (lMeterVal < lMeterMin)
lMeterMin = lMeterVal;
if (rMeterVal > rMeterMax)
rMeterMax = rMeterVal;
if (rMeterVal < rMeterMin)
rMeterMin = rMeterVal;

Vėliau pamatysime kad reikalingas kiek kitoks Min ir Max pozicijų skaičiavimas, bet dabar pasiliekame ties dabartine versija, o vėliau išbandysime kitą.

Tuo metu galime skaičiuoti dabartines LCD ekrano reikšmes iš spausdinti stulpelius ekrane:

int left = map(lMeterVal, lMeterMin, lMeterMax, 0, 40);
int right = map(rMeterVal, rMeterMin, rMeterMax, 0, 40);
lcd.setCursor(0,1);
byte p = 1;
for (p = 8; p-(8-left/5)>0; p--)
{
lcd.setCursor(p-1,1);
lcd.print(char(255));
}
if (left%5!=0)
{
lcd.setCursor(p-1,1);
lcd.write(byte((left%5)-1));
p--;
}
for (p; p>=1; p--)
{
lcd.setCursor(p-1,1);
lcd.print(char(254));
}
for (p = 9; p<=(8+right/5); p++)
{
lcd.setCursor(p-1,1);
lcd.print(char(255));
}
if (right%5!=0)
{
lcd.setCursor(p-1,1);
lcd.write(byte((right%5)+3));
p++;
}
for (p; p<=16; p++)
{
lcd.setCursor(p-1,1);
lcd.print(char(254));
}

Kaip matote spausdinimas vyksta iš kairės į dešine, pereinant visus 16 segmentų. Kadangi segmentai perpiešiame be ekrano ištrinimo prieš tai, vadinasi gauname nemirksinti galutini variantą. Dabar rezultatas toks:

IMG_0439

Iš centro į kairę piešiamas kairės pusės signalas, o iš centro į dešinė – dešinysis. Trumpas video:

Kaip matome vistiek neišnaudojamas pilnai visas stulpelių plotis, veiksmas vyksta tik keliuose segmentuose, o ir pats vizualizavimas menkai sutampa su garsu :D. Ir neveltui, nes dabar mes skaičiuojame viska blogai. Tam truputi perdariau Min ir Max reikšmių skaičiavimą pritaikant slenkanti žingsnį. Nusprendžiau kaupti kažkokį kiekį duomenų ir kiekviena karta piešiant stulpelius Min ir Max reikšmes skaičiuoti iš trumpo garso stebėjimo periodo. Tokiu būdu nusiskaitęs tam tikro periodo audio signalo Min ir Max reikšmes iš jų galėsiu spręsti kiek audio signalas stipriai kinta/vibruoja. Tam naujai apsirašiau papildomai reikalingus kintamuosius:

const int samples = 100;
 unsigned lData[samples];
 unsigned rData[samples];
 unsigned lDataMin = 10000;
 unsigned lDataMax = 0;
 unsigned rDataMin = 10000;
 unsigned rDataMax = 0;

Sename variante buvę kintamieji beja yra globalus ir jie išlieka visos programos metu, o šie bus naudojami tik nuskaitant is apdorojant audio signalą, t.y. jų reikšmės laikinos.

Dabar atliksime kitokius veiksmus nuskaitant signalą:

for(int i=0; i<samples; i++)
 {
 lData[i]=analogRead(LVUPIN);
 rData[i]=analogRead(RVUPIN);
 } 

Dabar turėdami trumpo laiko duomenis galime pradeti skaičiuoti ką ten turime:

//processing bar data for left channel
for(int i=0; i<samples; i++)
{
if(lData[i]<lDataMin) lDataMin=lData[i];
if(lData[i]>lDataMax) lDataMax=lData[i];
}
int left=unsigned(lDataMax-lDataMin)-20;
if (left<0) left=0;
if (lMeterMax<left) lMeterMax=left;
left = map(left, 0, lMeterMax, 0, 40);
//processing bar data for right channel
for(int i=0; i<samples; i++)
{
if(rData[i]<rDataMin) rDataMin=rData[i];
if(rData[i]>rDataMax) rDataMax=rData[i];
}
int right=unsigned(rDataMax-rDataMin)-20;
if (right<0) right=0;
if (rMeterMax<right) rMeterMax=right;
right = map(right, 0, rMeterMax, 0, 40);

Viskas paprasčiau nei atrodo. Mes tiesiog sukauptuose duomenyse randame Min ir Max reikšmes, iš jų apskaičiuojame pokyti ir šį pokytį naudojame piešiant grafikus. Prie pokyčio skaičiavimo matyti kad dar papildomai atimame 20 vienetų. To reikia tam kad dirbtinai sukeltume garso jautrumą. Kitaip stulpelių reikšmės pastoviai bus vienoje vietoje, kuri dažniausiai buna ties pabaiga, o vaizdelis bus ne koks. Geriausiai tai matosi filmuotoje medžiagoje. Štai kuomet atimame 20:

Ir dabar atimame 50:

Paskutinis variantas man mielesnis, tad ji ir paliksiu. Paprastai atimamą reikšmę reikia žiūrėtis individualiai, nes audio signalai visada skirsis savo charakteristikomis. MP3 grotuvas duos vienokio stiprumo signalą, o koks CD grotuvas visai kitokį.

Galimas programos dar vienas patobulinimas. Galima lData ir rData masyvus pasidaryti globalius ir juos pildyti vis naujesne informacija. Tokiu atveju kiekvieno stulpelio atnaujinimo metu mes turėsime nuskaityti ne 100×2 analoginių reikšmių, o tik dvi. Klausimas tik kaip tai veiktu, nes per tą laiką kai duomenys nenuskaitomi audio signalas kinta ir galimai prarasime Min ir Max teisingas reikšmes. Tokiu atveju nuskaitymą reikia perkelti į laikrodžio kontroliuojama atskirą procesą, o atskirai piešiant skaičiuoti tik Min ir Max iš to ką turime sukaupta. Vienaip ar kitaip, šios idėjos neplėtosiu.

Apibendrinimas

Jei nereikia žinoti audio signalo charakteristikų, o pakanka audio signalo pokyčių nuskaitymo, tai mano aprašytas nuskaitymo būdas yra tikrai geras. Technine dalimi mažai ką tobulinti ir galime, bet programuodami galime signalą nusiskaityti kaip mes norime. Mano parodytas būdas yra tik vienas iš tokių.

 


Veiksmai

Information

Parašykite komentarą

Įveskite savo duomenis žemiau arba prisijunkite per socialinį tinklą:

WordPress.com Logo

Jūs komentuojate naudodamiesi savo WordPress.com paskyra. Atsijungti / Keisti )

Twitter picture

Jūs komentuojate naudodamiesi savo Twitter paskyra. Atsijungti / Keisti )

Facebook photo

Jūs komentuojate naudodamiesi savo Facebook paskyra. Atsijungti / Keisti )

Google+ photo

Jūs komentuojate naudodamiesi savo Google+ paskyra. Atsijungti / Keisti )

Connecting to %s




%d bloggers like this: