Despre chitară se spune că este un instrument fascinant, dar pe cât de interesantă este, pe atât de ușor se dezacordează. Puțini sunt cei care pot reacorda “după ureche”, motiv pentru care mulți apelează la un acordor. Inspirată de această nevoie, am venit cu ideea acestui proiect după ce mi-am achiziționat o chitară în speranța de a învăța să cânt. În acest proces, am descoperit și am fost impresionată de modul în care funcționează un tuner.
Principiul de funcționare al acordorului meu este următorul:
Când sunt acordate corect, corzile au următoarele frecvențe:
Pentru a oferi feedback vizual suplimentar, voi folosi leduri, pe care le voi aprinde astfel:
Laboratorul 0: GPIO → folosit pentru aprinderea și stingerea ledurilor;
Laboratorul 2: Întreruperi → folosit pentru realizarea întreruperilor pe butoane;
Laboratorul 4: ADC → pentru citirea frecvenței transmise de microfon;
Laboratorul 6: I2C → pentru afișarea datelor pe ecranul LCD-ului;
#include "arduinoFFT.h" #include "LiquidCrystal_I2C.h" // Canalul ADC pentru citirea semnalelor audio de la microfon #define CHANNEL A0 // SNUmarul de esantioane pentru FFT const uint16_t samples = 128; // Frecventa de esantinare const double samplingFrequency = 2000; // Perioada de esantionare in ms unsigned int sampling_period_us; // microsecunde unsigned long microseconds; // Threshold-ul pentru detectarea frecventei unsigned int threshold = 200; // Intervalul de deviatie acceptabil in jurul frecventei tinta const float frequencyRange = 40.0; // Dimensiunea pasului pentru luminarea LED-urilor const float frequencyIncrement = 5.0; // Partea reala pentru stocarea inputului FFT double vReal[samples]; // Partea imaginara pentru stocarea inputului FFT double vImag[samples]; // Interfata I2C pentru displayul LCD-ului LiquidCrystal_I2C lcd(0x3F, 16, 2); // Pinii GPIO pentru ON/OFF (Intrerupere) const int onOffButtonPin = 2; // Pinii GPIO pentru ciclu (Intrerupere) const int cycleButtonPin = 3; // Starea tunerului (pornit/oprit) volatile bool isOn = false; // Indexul pentru frecventa tinta curenta volatile int targetFrequencyIndex = 0; // Lista frecventelor tinta pentru fiecare nota in parte int targetFrequencies[] = { 82, 110, 147, 196, 247, 330 }; // Obiectul FFT creat ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, samples, samplingFrequency);
Mai jos voi explica fiecare funcție în parte, precum și funcționalitățile acestora:
void setup() { // Perioada de esantionare in microsecunde sampling_period_us = round(1000000 * (1.0 / samplingFrequency)); // Comunicarea seriala penrtu debugging Serial.begin(115200); while (!Serial); Serial.println("Ready"); lcd.begin(16, 2); lcd.init(); lcd.backlight(); lcd.clear(); // Configurarea pinilor GPIO pentru butoane pinMode(onOffButtonPin, INPUT_PULLUP); pinMode(cycleButtonPin, INPUT_PULLUP); // Intreruperi pentru cele 2 butoane pe front crescator attachInterrupt(digitalPinToInterrupt(onOffButtonPin), onOffButtonISR, RISING); attachInterrupt(digitalPinToInterrupt(cycleButtonPin), cycleButtonISR, RISING); }
void loop() { if (isOn) { // Tuner pornit // Citeste semnalele audio sample(); delay(50); } else { // Stinge toate ledurile conectate clearLEDs(); lcd.clear(); lcd.setCursor(0, 0); // Afiseaza mesajul Waiting OFF pentru a semnala ca tunerul e pornit lcd.print("Waiting OFF"); delay(50); } }
void clearLEDs() { // Itereaza prin toti pinii de la 5 la 13 for (int pin = 5; pin <= 13; pin++) { digitalWrite(pin, LOW); } }
void sample() { // Initializeaza timpul de esantionare microseconds = micros(); // Citirea esantioanelor audio for (int i = 0; i < samples; i++) { // Cititrrea valorii ADC la microfon vReal[i] = analogRead(CHANNEL); vImag[i] = 0; // Pana cand perioada de esantionare e completa while (micros() - microseconds < sampling_period_us) {} microseconds += sampling_period_us; } // Efectuarea FFT FFT.windowing(FFTWindow::Hamming, FFTDirection::Forward); FFT.compute(FFTDirection::Forward); // Convertirea valorilor complexe la magnitudine FFT.complexToMagnitude(); // Filtrarea sunetului (frecvente neimportante) for (uint16_t i = 0; i < (samples >> 1); i++) { if (vReal[i] < threshold || (vReal[i] > targetFrequencies[targetFrequencyIndex] - frequencyRange && vReal[i] < targetFrequencies[targetFrequencyIndex] + frequencyRange)) { vReal[i] = 0.0; } } // Frecventa principala double x = FFT.majorPeak(); if (x > 20 && x < 980) { Serial.println(x, 6); // Actualizarea LED-urilor in functie de frecevnta mapFrequencyToLEDs(x); // Actualizarea LCD-ului dupa frecventa displayFrequency(targetFrequencies[targetFrequencyIndex]); } else { Serial.println("Waiting"); } }
void mapFrequencyToLEDs(double dominantFrequency) { // Opreste toate ledurile clearLEDs(); // Deviatia frecventei in functie de target float deviation = dominantFrequency + frequencyRange - targetFrequencies[targetFrequencyIndex]; // Index led ce trebuie aprins int ledIndex = round(deviation / frequencyIncrement) - 4; Serial.println("Deviation: "); Serial.print(deviation); Serial.print(", ledIndex: "); Serial.print(ledIndex); ledIndex = constrain(ledIndex, 0, 8); Serial.print(", constrained ledIndex: "); Serial.print(ledIndex); Serial.print("\n\n"); // Determinarea pinului GPIO corespunzator LED-ului int pin = ledIndex + 5; // Aprinderea ledului corespunzator digitalWrite(pin, HIGH); }
void displayFrequency(double frequency) { // Sterge afisajul de pe LCD lcd.clear(); // Seteaza cursorul la pozitia (0, 0) lcd.setCursor(0, 0); lcd.print("Freq: "); // Frecventa cu 2 zecimale lcd.print(frequency, 2); lcd.print(" Hz"); lcd.setCursor(0, 1); lcd.print("Note: "); // Nota muzicala corespunzatoare frecventei switch (static_cast<int>(frequency)) { case 82: lcd.print("E2"); break; case 110: lcd.print("A2"); break; case 147: lcd.print("D3"); break; case 196: lcd.print("G3"); break; case 247: lcd.print("B3"); break; case 330: lcd.print("E4"); break; default: lcd.print("Unknown"); break; } }
void onOffButtonISR() { static unsigned long lastInterruptTimeOnOff = 0; unsigned long interruptTime = millis(); // Daca a trecut suficient timp de la ultima intrerupere if (interruptTime - lastInterruptTimeOnOff > 400) { // Inverseaza starea tunerului isOn = !isOn; } // Actualizeaza timpul ultimei intreruperi lastInterruptTimeOnOff = interruptTime; }
void cycleButtonISR() { static unsigned long lastInterruptTimeCycle = 0; unsigned long interruptTime = millis(); // Daca a trecut suficient timp de la ultima intrerupere if (interruptTime - lastInterruptTimeCycle > 400) { // Ciclu prin frecventele tinta targetFrequencyIndex = (targetFrequencyIndex + 1) % 6; } // Actualizeaza timpul ultimei intreruperi lastInterruptTimeCycle = interruptTime; }
În urma proiectului, am obținut rezultate bune în urma testării cu un generator de frecvențe de note muzicale (am generat unde sinusoidale corespunzătoare fiecărei note muzicale).
Mai jos, am realizat câteva videoclipuri pentru a demonstra funcționalitatea completă și corectă a acorodrului:
Trecerea prin notele muzicale - implementare cu întreruperi - Prima dată tunerul e în starea de OFF, iar primul buton apăsat îl trece în starea de ON. Cât timp este identificată o frecvență validă care nu este zgomot, utilizatorul poate trece prin notele disponibile prin apăsarea butonului de cilare.
Acorodul meu a identificat pentru fiecare caz în parte momentul când nota este corectă, iar indicațiile oferite de LED-uri au fost corecte și consistente.
Deși acorodul funcționează bine în general, am observat că pentru amplitudini foarte mici, sistemul are unele dificultăți în identificarea corectă a frecvenței.
Acordorul digital s-a dovedit a fi un succes, deomonstrând o funcționalitate corectă și completă. Utlizarea unui generator de frecvențe de note muzicale a confirmat capacitatea acordorului de a recunoaște corect frecvențele și de a oferi indicații precise pentru acordaj, ceea ce înseamnă că am realizat corect eliminarea zgomotului, precum și FFT-ul aplicat semnalului audio.
Deși există anumite limitări pentru amplitudini foarte joase, acest acordor poate fi utlizat cu încredere pentru acordarea diferitelor instrumente muzicale.
Mă bucur că am avut ocazia să realizez acest proect, deoarece am reușit să îmbin pasiunea mea pentru tehnologie cu cea pentru muzică. Procesul de proiectare și dezvoltare m-a făcut să înțeleg mai bine cum pot utiliza diferite componente electronice pentru a crea în final un produs util și eficient.
Bibliografie
https://forum.arduino.cc/t/i-want-to-create-a-guitar-tuner-steps-to-take-parts-to-use/686392
https://www.youtube.com/watch?v=CvqHkXeXN3M&list=PL2orsk2zoELnX3RqZVVvgOhDJaFO8XCjQ
https://www.youtube.com/watch?v=Sj142ZOLEhM&list=PL2orsk2zoELnX3RqZVVvgOhDJaFO8XCjQ&index=2
https://www.youtube.com/watch?v=0nmDqKCMh3Q&list=PL2orsk2zoELnX3RqZVVvgOhDJaFO8XCjQ&index=3
https://docs.arduino.cc/built-in-examples/basics/Blink/
https://docs.arduino.cc/built-in-examples/digital/Button/
https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
https://www.allaboutcircuits.com/technical-articles/using-interrupts-on-arduino/
https://www.arduino.cc/reference/en/libraries/arduinofft/
https://docs.arduino.cc/learn/electronics/lcd-displays/
https://www.circuitbasics.com/how-to-use-microphones-on-the-arduino/