Guitar Tuner

Student: Pădurariu Teofil - 334CC

Introducere

Acest proiect presupune implementarea unui device prin care orice muzician își poate acorda cu ușurință instrumentul muzical. Sunetul va fi captat cu ajutorul unui microfon. Tunerul va detecta frecvența predominantă, o va compara cu valoarea de referință pentru frecvența notei dorite și pe display se va afișa rezultatul în timp real.

Este un proiect util deoarece permite muzicienilor să își acordeze rapid și precis instrumentele, îmbunătățind calitatea sunetului acestora. Utilizarea tehnologiei Arduino face dispozitivul portabil și ușor de utilizat, aducând beneficii considerabile în practica muzicală zilnică.

Descriere generală

Sunetul corzii este captat printr-un microfon preamplificat. Semnalul electric analogic generat va fi trimis mai departe către ADC care va transforma semnalul primit, în semnal digital. Folosind algoritmul Fast Fourier Transform, semnalul va fi descopus în frecvențe (armonice). Tuner-ul va selecta frecvența cu amplitudinea cea mai mare și o va compara cu una dintre frecvențele de referință ale notelor „ideale” (cu frecvența celei mai apropiate note „ideale”). Displayul 16×2 va afișa rezultatul în timp real: dacă frecvența corzii este mai mică decât cea de referință sau dacă frecvența corzii este mai mare decât aceasta.

Schema bloc

Hardware Design

Componentele folosite:

Arduino Nano Board: Placa pentru dezvoltare aleasă. Procesează semnalul primit de la microfon și afișează rezultatul pe display-ul LCD

LCD 1602: Display-ul pe care se va afișa rezultatul procesării semnalului și informații despre modul de tuning curent

I2C LCD module: Modulul care permite comunicarea Arduino Nano cu LCD-ul prin protocolul I2C

MAX4466: Microfonul preamplificat necesar captării sunetului

Un RGB LED Module: Necesar indicării stării tunerului.

Pinii utilizați:

A0: Pentru semnalul de intrare venit de la microfon. Pinul corespunde unui ADC intern, convertor de care avem nevoie pentru a putea transforma semnalul analogic, în semnal digital.

A4 și A5: Sunt SDA-ul, respectiv SCL-ul, care permit comunicarea cu LED-ul.

D2, D3, D4: Vor seta culoarea LED-ului.

5V pin: Pentru alimentarea componentelor externe.

GND: Masa la care se conectează toate componentele.

Schema electrică

Principiul de funcționare: Pe pinul A0 vine semnalul analogic captat de microfon, acesta fiind convertit în semnal digital de către ADC-ul intern. Semnalul este procesat și se trimite către display (prin I2C → pinii A4 și A5 == SDA și SCL) frame-ul corespunzător stării curente a tunerului. LED-ul va afișa starea tunerului: nu primește semnal îndeajuns de puternic ca sa acordeze (BLUE), out of tune (RED), in tune (GREEN). Culoarea este dată de prin pinii D2 (pinul B al LED-ului), D3 (pinul G al LED-ului) și D4 (pinul R al LED-ului).

Implementarea hardware

16 mai 2024

23 mai 2024

Implementare Software

Este un proiect software-heavy.

INPUTUL ȘI PROCESAREA SA

Core-ul tunerului este Arduino Nano. Pe pinul A0 vor veni datele de la microfonul cu ajutorul căruia se captează semnalul analogic generat de vibrația corzii de chitară. Pentru a putea prelucra semnalul de intrare, este necesară eșantionarea acestuia de către ADC.

Procesarea semnalului presupune descompunerea sa din amplitudine(timp) în amplitudine(frecvență), găsirea frecvenței de magnitudine maximală și compararea acesteia cu frecvența de referință specifică celei mai apropiate note de aceasta. Este necesar acest proces datorită faptului că semnalul generat de chitara este aperiodic. Conține o multitudine de armonice, care se însumează și formează întregul semnal (matematic, semnalul poate fi gandit ca o însumare de funcții sinus/cosinus). La nivel software, această descompunere poate fi realizată prin aplicarea transformării Fourier pe semnalul de intrare, implementată prin algoritmul Fast Fourier Transform (FFT). index_nota

BIBLIOTECI EXTERNE

Pentru implementarea tuner-ului, am folosit 2 biblioteci externe:

Inițial m-am gândit sa implementez singur măcar una din aceste biblioteci, însă sunt greu de implementat de la 0. Le-am ales pe acestea și datorită popularității: au fost folosite de mulți, deci oferă siguranța că sunt stable.

IMPLEMENTAREA PROPRIU-ZISĂ

Pe SRAM-ul microcontroller-ului sunt stocate 2 buffere cu care va lucra algoritmul FFT: vReal și vImag. Inițial 128 de sample-uri generate de ADC se vor stoca în vReal. Pentru o conversie se va aloca un timp de așteptare, care este egal cu perioada eșantionării: 1/SAMPLING_FREQUENCY, pentru stabilitate. SAMPLING_FREQUENCY-ul este rata de eșantionare a semnalului și va da și frecvența maximă pe care o va recunoaște algoritmul FFT, conform teoremei de eșantionare a lui Nyquist, care spune că pentru a detecta frecvențe până în frecvența f, trebuie ca rata de eșantionare să fie cel puțin dublă: SAMPLING_FREQUENCY = 2 * f. Pentru tuner am ales să merg până în 1000Hz, îndeajuns pentru majoritatea instrumentelor cu coardă.

Cod citire date:

for (int i = 0; i < SAMPLES; i++) {
	microseconds = micros();

	vReal[i] = static_cast<float>(get_sample(PC0)) - 512.0;
	vImag[i] = 0;
		
	while (micros() < (microseconds + SAMPLING_PERIOD)) {}
}

Menționare: am configurat ADC-ul pe registri : vezi labul de ADC.

Cum am mai spus, este nevoie de 2 buffere: vReal și vImag. Motivul este acela că FFT lucrează pe numere complexe.

Odată stocate sample-urile în bufferul vReal, se va aplica un window datelor (FFT_WIN_TYP_HANN: mai specific semnalelor care reprezintă vibrații). FFT consideră semnalul pe care îl primește ca fiind o perioadă a unui semnal continuu, periodic. Pentru a obține date mult mai accurate se aplică această fereastră și se va evita astfel și fenomenul de „spectral leakage”. Mai multe detalii aici: click. Mai apoi se va aplica algoritmul propriu-zis și se vor calcula și amplitudinile fiecăror frecvențe și se va căuta frecvența maximală.

Cod FFT:

FFT.windowing(vReal, SAMPLES, FFT_WIN_TYP_HANN, FFT_FORWARD);
FFT.compute(vReal, vImag, SAMPLES, FFT_FORWARD);
FFT.complexToMagnitude(vReal, vImag, SAMPLES);

float peak;
float magnitude;

FFT.majorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY, &peak, &magnitude);
peak -= get_fft_error(peak);

Pentru generarea tuturor frecvențelor de referință am pornit de la nota A4, ce are frecvența f0 = 440.0Hz. Toate celelalte frecvențe se vor afla pornind de la aceasta:

f = f0 * 2 ^ ((NOTE_INDEX - A4_INDEX) / 12)

Am generat 64 de note, începând la A0 și mergând până la ultima notă sub 1000Hz. Pentru aflarea indexului cele mai apropiate note de frecvența maximală detectată, am scos din ecuația precedentă pe index_notă. Astfel am obținut formula:

int closest = round(12 * log(peak / A4_FREQ) / log(2.0)) + A4_INDEX;
// pentru aflarea frecvenței de la indexul closest
freq[closest]

Detecția deviației:

Avem 4 cazuri:

  • Dacă frecvența se află în intervalul (ref - REF_RNG, ref + REF_RNG), unde REF_RNG este o eroare (1 setata in software), atunci coarda este acordata
  • Dacă frecvența detectată este mai mică decât frecvența de referință, numărul de segmente care ilustrează cu cât este mai jos aceasta față de cealaltă va fi:
float step = (ref - lower) / 14.0;
int16_t seg_num = (uint16_t)((ref - peak) / step);
if (seg_num < 7) seg_num++;
  • Dacă frecvența detectată este mai mare decât frecvența de referință, numărul de segmente care ilustrează cu cât este mai jos aceasta față de cealaltă va fi:
float step = (higher - ref) / 14.0;
uint16_t seg_num = (uint16_t)((peak - ref) / step);
if (seg_num < 7) seg_num++;
  • Dacă amplitudinea nu este îndeajuns de mare încât tunerul să o considere input, atunci LCD-ul va afișa ”Play some notes”.

LCD-ul este folosit pentru afișarea rezultatului procesării, alături de LED-ul RGB ce indică starea curentă a tuner-ului. Arduino Nano comunică prin protocolul I2C cu LCD-ul (vezi labul de I2C). Am creat 7 caractere custom pentru segmente, care au fost încărcare la inițializarea tuner-ului în memoria controllerului LCD-ului, la adresele 0x0-0x6. Biblioteca permite, prin apelarea unor funcții simple, afișarea caracterelor pe display.

Let's see some code:

LCD.init(); // Initializare I2C
LCD.backlight(); // Pornire backlight
LCD.setCursor(0, 0); // Setare cursor in poziția de scriere (linia 0, caracter 0)
LCD.print("Guitar Tuner v1"); // Afișare string pe ecran
LCD.clear(); // Ștergere caractere de pe ecran
LCD.setCursor(0, 0);
LCD.write(0x1); // Afișarea caracterului de la poziția 0x1 în memoria controllerului LCD-ului.

Pentru schimbarea culorii LED-ului am folosit labul de GPIO.

CALIBRARE + OPTIMIZARE COD

Pentru că nu am avut hardware-ul necesar să măresc buffer sizeul pentru rezultate mai accurate, am făcut o funcție care întoarce erorile specifice pentru anumite range-uri de frecvențe (le-am determinat experimental):

float get_fft_error(float f) {
	if (f >= freq[0] && f < 200.0) {
		return 2.0;
	} else if (f >= 200.0 && f < 300.0) {
		return 4.0;
	} else if (f >= 300.0 && f < 400.0) {
		return 6.0;
	} else if (f >= 300.0 && f < 400.0) {
		return 8.0;
	} else {
		return 10.0;
	}
}

Download

Bibliografie

pm/prj2024/alucaci/teofil.padurariu.txt · Last modified: 2024/05/23 00:54 by teofil.padurariu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0