Proiectul meu este, în esență, visul de a transforma o tastatură banală într‑un instrument muzical care să‑ți smulgă un zâmbet ori de câte ori ai nevoie de o pauză. Am pornit de la pasiunea pentru muzică și de la regretul că, în copilărie, n‑am putut atinge clapele unui pian adevărat. Așa s‑a născut ideea unui pian electric simplu, portabil, care să combine dexteritatea de programator cu bucuria de a improviza câteva măsuri dintr‑o melodie preferată.
Instrumentul are două moduri principale. În Free‑to‑Play poți apăsa clapele în orice ordine, lăsându‑ți imaginația să facă ce știe ea mai bine: să compună, să testeze, să greșească și s‑o ia de la capăt fără nicio presiune. Pentru cei care vor un pic de ghidaj există modul de exersare cu piese pre‑încărcate – melodii scurte, redate pas cu pas, ca să‑ți antrenezi urechea și degetele fără profesor, direct din sufragerie.
De ce cred că e util? Pentru că, atunci când lucrezi ore în șir la tastatură, pauzele scurte contează enorm. Câteva acorduri improvizate relaxează mintea, îți dau energie și, în plus, declanșează spiritul competitiv: sigur vei vrea să reinterpretezi melodia ca să sune mai curat, mai ritmat, mai „a ta”. Iar dacă îi mai atașezi un jack pentru căști sau o ieșire MIDI, brusc se deschid uși către producție muzicală serioasă – fără să pierzi farmecul jucăuș al prototipului inițial.
În fond, acest pian electric este mai mult decât un obiect: e o invitație la creativitate, la învățare prin joacă și la împărtășit momente muzicale cu prietenii. Poate că nu va înlocui niciodată un Steinway, dar cu siguranță va reaminti oricui apasă pe clape că tehnologia poate fi și caldă, și amuzantă, și surprinzător de melodioasă.
Schema bloc este următoarea:
Sistemul de pian electric portabil are la bază o placă Arduino UNO (ATmega328P) şi formează un ansamblu hardware‑software compus din următoarele module interconectate:
Flux de lucru:
Prin această arhitectură, proiectul îmbină electronică digitală (registru de deplasare, SPI, I2C) cu elemente de interfaţă om‑maşină (claviatură, butoane, buzzer, display), oferind un instrument compact care poate fi uşor extins cu funcţii MIDI sau efecte audio suplimentare.
BOM:
Componenta | Cantitate | Loc achizitionare | Datasheet |
---|---|---|---|
Arduino UNO R3 | 1 | Arduino Uno | Datasheet |
LCD 16×2 cu interfaţă I2C | 1 | LCD I2C | Datasheet |
Registru de deplasare 74HC595 (SOIC-16) | 1 | Shift Register | Datasheet |
Modul adaptor MicroSD (SPI) | 1 | MicroSD module | Datasheet |
Piezo-buzzer pasiv 5 V | 1 | Buzzer | Datasheet |
Buton tactil 6×6 mm (note) | 8 | Buton | Datasheet |
Buton tactil 6×6 mm (NEXT, OK) | 2 | Buton | Datasheet |
LED 5 mm difuz | 8 | Green LEDs | Datasheet |
Rezistor 220 Ω / 0.25 W | 8 | Resistors | Datasheet |
Breadboard 830 de găuri | 1 | Breadboard | Datasheet |
Set jumper fire male-male | 1 set | Jumper male-male | Datasheet |
Componente utilizate:
Opțional: power‑bank Li‑ion 5 V pentru funcționare portabilă – Sursă portabilă de alimentare USB, permite utilizarea pianului fără PC, menținând 5 V și GND comune.
Arhitectura de pin mapping respectă principiile de segregare a magistralelor hardware, minimizare a interferențelor și optimizare a utilizării resurselor:
Prin această distribuție calculată, design-ul hardware asigură robustețe, scalabilitate și posibilitatea de extindere ulterioară (de exemplu adăugarea de senzori sau ieșiri MIDI) fără refacerea completă a schemei de pini.
Schema electrică a pianului portabil este structurată în şapte blocuri funcţionale interconectate prin magistrale hardware dedicate şi linii GPIO discrete, menite să asigure un flux de semnal coerent de la evenimentul de apăsare a unei clape până la generarea sunetului şi feedback-ul vizual. Mai jos găsiţi descrierea fiecărui subsistem, urmată de observaţii specifice asupra reprezentării realizate în CirKit Designer IDE, care completează schema tradiţională printr-o perspectivă de tip “breadboard view”:
În imaginea de mai sus se poate observa o reprezentare „breadboard view” elaborată în Tinkercad, care, deşi nu include adaptorul MicroSD (din păcate Tinkercad nu oferă momentan un model specific pentru acesta), reuşeşte să ilustreze mult mai clar şi mai intuitiv topologia completă a conexiunilor hardware decât schema schematică precedentă. În continuare voi comenta punctele forte şi modul în care acest grafic favorizează lizibilitatea şi înţelegerea ansamblului:
În concluzie, această schemă elaborată în Tinkercad serveşte drept un excelent material suport pentru prezentarea proiectului tău academic: oferă o imagine de ansamblu coerentă, păstrează fidelitatea conexiunilor electrice conform specificațiilor hardware discutate anterior și facilitează atât înțelegerea, cât și validarea vizuală a circuitului înainte de asamblarea fizică.
Pentru confirmarea funcţionalităţii configuraţiei implementate pe breadboard şi a integrităţii fluxului hardware-software, s-au derulat o serie de teste de funcţionare în condiţii de laborator, după cum se observă în imaginile de mai sus. Secvenţa de validare a inclus următorii pași:
În concluzie, prototipul hardware s-a dovedit robust și conform specificațiilor proiectului, iar metodologia de testare confirmă validitatea arhitecturii hardware-software propuse pentru pianul electric portabil.
În figura de referință (schema de conexiuni realizată în Circuit Designer), se poate observa clar traseul tuturor cablurilor între placa Arduino UNO, modulul MicroSD, backpack-ul I2C al LCD-ului, registrul de deplasare 74HC595, buzzer-ul pasiv și cele opt taste cu LED-uri. Această schemă detaliază modul în care tensiunea de 5 V este distribuită și cum semnalele digitale şi SPI/I2C circulă pe fire separate, iar pentru claritate s-a folosit o dispunere pe breadboard cu gruparea clară a componentelor și a rezistențelor de limitare.
Alimentarea se realizează dintr-un pachet de patru baterii alcaline AA (1,5 V fiecare) montate în serie şi conectate la pinul VIN al plăcii UNO. În schema de decuplare se regăsesc condensatoare de 100 µF pe linia VIN și 100 nF lângă fiecare modul (LCD, MicroSD, buzzer), pentru a asigura stabilitate la variații de curent și pentru a filtra eventuale perturbări generate de comutările rapide ale tranzistoarelor interne și ale driverelor 74HC595.
Pentru LED-urile de pe cele opt taste s-au ales rezistențe de 220 Ω, calculul fiind fundamentat astfel:
$$ R = \frac{V_{CC} - V_f}{I} = \frac{5\,\text{V} - 2\,\text{V}}{0{,}015\,\text{A}} \approx 200\,\Omega $$
Rotunjirea la 220 Ω limitează curentul la circa 13,6 mA per LED, suficient pentru o intensitate vizuală clară, dar fără suprasolicitarea registrului de deplasare sau descărcarea accelerată a bateriilor.
Curentul static al sistemului (Arduino UNO, interfața I2C, citirea SD, backlight LCD) se situează în jur de 170 mA, iar în regim de vârf – cu toate LED-urile aprinse și acces la card – poate atinge 500 mA. În baza acestor valori, autonomia medie estimată a pachetului de baterii AA (2 000 mAh) este de aproximativ 12 ore, iar în regim de vârf scade la circa 4 ore. Astfel, schema prezentată confirmă necesitatea unui pachet de baterii cu cel puțin 2 000 mAh și utilizarea condensatoarelor de decuplare pentru a evita căderile de tensiune în timpul comutărilor bruște de curent.
În ceea ce privește disiparea termică, regulatorul liniar al plăcii UNO va pierde aproximativ 0,17 W (diferența de 1 V × curentul mediu), o valoare neglijabilă pentru carcasa prototipului, însă în implementările finale este recomandat să se evite supraîncărcarea prin separarea fizică a regulatorului de restul componentelor sensibile. Schema electrică ilustrează, de asemenea, necesitatea împământării comune (GND unificat) între toate modulele și bateria externă, pentru a asigura referința corectă a semnalelor și stabilitatea comunicațiilor SPI şi I2C.
Prin această descriere integrată pe baza schemelor vizuale și a calculelor de curent şi tensiune, proiectul electric al pianului portabil este documentat în detaliu, îmbinând claritatea conexiunilor fizice cu rigurozitatea ingineriei electronice.
Prin această organizare, schema electrică atinge un echilibru între eficiența pinilor, performanța magistralelor hardware şi claritatea fluxului de semnal, oferind o platformă robustă şi modulară pentru dezvoltări ulterioare în context academic şi comercial.
Prin aceste alegeri și optimizări, proiectul atinge atât obiectivele de performanță, cât și pe cele de confort al utilizatorului, în acord cu bunele practici de proiectare electronică şi software.
PlatformIO – în Visual Studio Code, pentru debug hardware și management avansat al proiectelor.
Maşină cu stări finite (FSM)
Debouncing
Conversia note ↔ frecvență
Controlul LED-urilor cu 74HC595
Gestionarea playlist-ului
Redare interactivă (“Play by keys…”)
Am ales acest fragment pentru că reprezintă „inima” interacțiunii cu LED-urile — modul în care transformăm o mască de biți într-un set de semnale hardware.
/* * sendMask(mask) * Shift out an 8-bit mask to the 74HC595 and latch all outputs. */ static inline void sendMask(byte mask) { // 1) Reverse bit order: logica internă (bit 0…7) vs. firele fizice byte dataToSend = reverseBits(mask); // 2) Start transfer: coborâm pinul LATCH digitalWrite(PIN_SHIFT_LATCH, LOW); // 3) Trimitem cele 8 biți, de la MSB la LSB, pe DATA și CLOCK shiftOut(PIN_SHIFT_DATA, PIN_SHIFT_CLOCK, MSBFIRST, dataToSend); // 4) Finalizăm transferul: ridicăm LATCH pentru a comuta ieșirile digitalWrite(PIN_SHIFT_LATCH, HIGH); }
De ce e semnificativ:
Pas cu pas:
Am ales acest bloc deoarece evidențiază modul eficient de a prelua starea celor 8 taste într-un singur octet, esențial pentru generarea rapidă a sunetului și controlul LED.
/* * readKeysMask() * Return a byte mask where each bit i = 1 if key[i] is pressed (active LOW). */ static byte readKeysMask() { byte mask = 0; // Parcurgem fiecare pin de taste for (byte i = 0; i < NUM_KEYS; i++) { // Dacă pinul e LOW (tastă apăsată), setăm bitul corespunzător if (digitalRead(KEY_PINS[i]) == LOW) { mask |= 1 << i; } } return mask; }
De ce e semnificativ:
Pas cu pas:
Am ales această secvență pentru a evidenția clar separarea funcțională a aplicației: meniu, free-play, selectare și redare.
/* * loop() * Finite State Machine dispatcher: apelează funcția corespunzătoare stării curente. */ void loop() { switch (currentState) { case STATE_MENU: menuTask(); // navigare meniu break; case STATE_FREE_PLAY: freePlayTask(); // modul liber de redare break; case STATE_SONG_SELECT: songSelectTask(); // alegerea melodiei de pe MicroSD break; case STATE_SONG_PLAY: songPlayTask(); // redare interactivă cu validare break; default: // fallback: revenim la meniu dacă starea e invalidă currentState = STATE_MENU; break; } }
De ce e semnificativ:
Pas cu pas:
Funcţia `freqToMidi` realizează transformarea matematică esenţială dintre domeniul fizic al frecvenţelor şi domeniul muzical al notelor MIDI, folosind logaritmi pentru a menţine scala temperată egală şi plasând la punct echilibrul dintre precizia calculului şi performanţa în timp real. Inversul acesteia, `midiToFreq`, foloseşte o operaţie de putere cu exponenţi fracţionari pentru a reconstrui cu acurateţe frecvenţa sonoră corespunzătoare oricărui număr MIDI, iar amplasarea acestor două funcţii inline reduce la minimum cheltuielile de apel şi permite optimizări de compilare care menţin latenţa foarte scăzută. În cadrul `setup()`, secvenţa atent ordonată de comenzi asigură iniţializarea tuturor magistralelor—de la I2C-ul pentru LCD până la SPI-ul pentru SD—astfel încât fiecare periferic devine disponibil într-o stare consistentă înainte de a începe logica aplicaţiei; scrierea mesajelor pe ecran în etape succesive oferă o fereastră de diagnosticare rapidă, iar tratamentul condiţionat al eşecului de iniţializare al cardului MicroSD previne orice blocaj ulterioară prin revenirea controlată la starea de meniu. În `songPlayTask`, ciclul de citire linie cu linie din fişierul .TXT combină parse-ul dinamic al măştii de taste cu calculul duratei în milisecunde, aprinderea simultană a LED-urilor şi un algoritm de „fade-out” software care introduce o tranziţie vizuală fluidă, apoi menţine o buclă de ascultare a intrării utilizatorului pentru o perioadă dublă faţă de durata notelor, generând tonuri unificate pentru orice combinaţie de taste apăsate şi declarând o eroare imediată în cazul unei apăsări incorecte. Blocul „play by keys” astfel implementat transformă reproducerea pasivă într-o experienţă interactivă în care partea de validare funcţionează ca un instructor virtual, iar returnarea la meniul precedent este sincronizată cu schimbarea stării finite pentru a oferi o tranziţie intuitivă. În `freePlayTask`, programul se află într-o buclă neîntreruptă care detectează modificările în masca de taste cu o singură comparare, actualizează numai atunci starea LED-urilor şi buzzer-ul, evitând astfel reîmprospătările inutile şi reducând consumul de procesor, în timp ce logica de buton OK tratează atât apăsările scurte, cât şi cele lungi pentru a decide revenirea la meniul principal. Întregul cod adoptă un model FSM clar delimitat prin apeluri succedate în `loop()`, ceea ce face ca orice modificare ulterioară—fie adăugarea de efecte noi, fie extinderea numărului de octave—să poată fi încorporată prin simpla adăugare a unei noi stări şi a funcţiei corespunzătoare, fără a perturba fluxul general. Design-ul pune accent pe coherenţa domeniului muzical şi a celui hardware, iar documentarea prin comentarii academice în limba engleză deasupra fiecărei linii face codul nu doar funcţional, ci şi uşor de învăţat şi întreţinut.
// Convert a frequency (Hz) to a floating-point MIDI note number static inline float freqToMidi(float freq) { return 12.0f * (log(freq / 440.0f) / log(2.0f)) + 69.0f; } // Convert a floating-point MIDI note number back to frequency (Hz) static inline float midiToFreq(float midi) { return 440.0f * pow(2.0f, (midi - 69.0f) / 12.0f); }
/* * setup() * Initialize all hardware interfaces: I2C for LCD, SPI for SD, * GPIO modes, shift register, buzzer pin, and start in the MENU state. */ void setup() { // Start I2C bus for the LCD Wire.begin(); // Configure SD chip-select as output and deselect initially pinMode(PIN_SD_CS, OUTPUT); digitalWrite(PIN_SD_CS, HIGH); // Start the hardware SPI interface SPI.begin(); // Configure user buttons with internal pull-ups pinMode(PIN_BUTTON_NEXT, INPUT_PULLUP); pinMode(PIN_BUTTON_OK, INPUT_PULLUP); // Configure each key pin with internal pull-up for (byte i = 0; i < NUM_KEYS; i++) { pinMode(KEY_PINS[i], INPUT_PULLUP); } // Configure shift register pins as outputs pinMode(PIN_SHIFT_DATA, OUTPUT); pinMode(PIN_SHIFT_CLOCK, OUTPUT); pinMode(PIN_SHIFT_LATCH, OUTPUT); // Clear any outputs on the shift register sendMask(0); // Configure the buzzer pin as output pinMode(PIN_BUZZER, OUTPUT); // Initialize the LCD and turn on its backlight lcd.init(); lcd.backlight(); // Display a startup message on the LCD lcd.setCursor(0, 0); lcd.print("Init Piano..."); lcd.setCursor(0, 1); lcd.print("Checking SD..."); delay(1000); // Attempt to initialize the SD card at half SPI speed if (!SD.begin(SPI_HALF_SPEED, PIN_SD_CS)) { // If initialization fails, show error and halt for a moment lcd.clear(); lcd.setCursor(0, 0); lcd.print("SD init FAIL"); delay(1500); } else { // On success, indicate OK and proceed lcd.clear(); lcd.setCursor(0, 0); lcd.print("SD OK"); delay(800); } // Enter the main menu state currentState = STATE_MENU; }
/* * songPlayTask() * Read the selected .TXT file line by line. Each line is * “mask duration”. LEDs fade out, and user must press correct keys * in time or an ERROR is shown. */ static void songPlayTask() { // Open the selected song file File sf = SD.open(songNames[selectedSong]); if (!sf) { // If it fails, notify and return to selection lcd.clear(); lcd.print("Open fail"); delay(1500); currentState = STATE_SONG_SELECT; return; } // Show “Play by keys…” on LCD lcd.clear(); lcd.setCursor(0, 0); lcd.print(songNames[selectedSong]); lcd.setCursor(0, 1); lcd.print("Play by keys..."); delay(500); // Process each line of the song file while (sf.available()) { String line = sf.readStringUntil('\n'); line.trim(); if (line.length() == 0 || line.charAt(0) == '#') { // Skip empty lines or comments continue; } // Split at space: notes mask vs duration int sp = line.indexOf(' '); if (sp < 1) { continue; } String notesPart = line.substring(0, sp); int durationMs = line.substring(sp + 1).toInt(); // Build bitmask of required keys byte requiredMask = 0; int pos = 0; while (true) { int comma = notesPart.indexOf(',', pos); String tok = notesPart.substring(pos, (comma < 0 ? notesPart.length() : comma)); int idx = tok.toInt(); if (idx >= 0 && idx < NUM_KEYS) { requiredMask |= 1 << idx; } if (comma < 0) { break; } pos = comma + 1; } if (requiredMask == 0) { // If no keys, just wait the duration delay(durationMs); continue; } // Light up the LEDs immediately sendMask(requiredMask); // Perform visible fade-out in 8 steps for (byte step = 0; step < NUM_KEYS; step++) { for (byte k = 0; k < 10; k++) { sendMask(requiredMask); delay((NUM_KEYS - step) * 2); sendMask(0); delay(step * 2); } } // Now allow user up to twice the duration to press keys unsigned long startTime = millis(); unsigned long window = 2UL * durationMs; while (millis() - startTime < window) { byte pressed = readKeysMask() & requiredMask; if (pressed) { // Compute and play averaged tone for pressed keys float sumM = 0; int cnt = 0; for (byte i = 0; i < NUM_KEYS; i++) { if (pressed & (1 << i)) { sumM += freqToMidi(KEY_FREQUENCIES[i]); cnt++; } } int f = int(midiToFreq(sumM / cnt) + 0.5f); tone(PIN_BUZZER, f); } else { // No keys pressed → mute buzzer noTone(PIN_BUZZER); } } // Turn off LEDs and buzzer sendMask(0); noTone(PIN_BUZZER); // If not all required keys were pressed, show ERROR if ((readKeysMask() & requiredMask) != requiredMask) { lcd.clear(); lcd.setCursor(0, 0); lcd.print(" ERROR!"); tone(PIN_BUZZER, 200, 500); delay(700); sf.close(); currentState = STATE_SONG_SELECT; return; } } // Close file and indicate completion sf.close(); lcd.clear(); lcd.setCursor(0, 0); lcd.print(" Finished!"); tone(PIN_BUZZER, 523, 500); delay(800); currentState = STATE_MENU; }
/* * freePlayTask() * In Free-Play, any pressed key lights its LED and generates the * corresponding tone. OK button returns to the menu (short or long press). */ static void freePlayTask() { // Show mode indicator lcd.clear(); lcd.setCursor(0, 0); lcd.print("Free-Play (OK)"); while (true) { // If OK is pressed, handle short vs long to return to menu if (digitalRead(PIN_BUTTON_OK) == LOW) { unsigned long t0 = millis(); while (digitalRead(PIN_BUTTON_OK) == LOW) { if (millis() - t0 > LONG_PRESS_TIME) { // On long press, back to menu immediately sendMask(0); noTone(PIN_BUZZER); currentState = STATE_MENU; return; } } if (millis() - t0 < SHORT_PRESS_TIME) { // On short press, also return to menu delay(DEBOUNCE_DELAY); sendMask(0); noTone(PIN_BUZZER); currentState = STATE_MENU; return; } } // Read current key mask and update LEDs if changed byte mask = readKeysMask(); if (mask != lastOutputMask) { sendMask(mask); lastOutputMask = mask; } // Compute average frequency if multiple keys are pressed float sumMidi = 0; int count = 0; for (byte i = 0; i < NUM_KEYS; i++) { if (mask & (1 << i)) { sumMidi += freqToMidi(KEY_FREQUENCIES[i]); count++; } } int frequency = (count ? int(midiToFreq(sumMidi / count) + 0.5f) : 0); // Only change the buzzer output when frequency actually changes if (frequency != lastFrequencyHz) { if (frequency) { tone(PIN_BUZZER, frequency); } else { noTone(PIN_BUZZER); } lastFrequencyHz = frequency; } // Small delay to reduce CPU load and for debounce delay(5); } }
Free-Play complet
Sistemul răspunde la apăsarea oricărei clape în <5 ms, generând imediat frecvența corectă și aprinzând LED-urile corespunzătoare fără flicker.
Song-Mode interactiv
Robustețe și stabilitate
Autonomie extinsă
Modularitate și scalabilitate
Performanță și experiență utilizator
Validări de calitate
Element de noutate
Integrarea unui mod de exersare cu feedback audiovisual dinamic şi validare în timp real, combinată cu un design hardware-software compact, nu a fost întâlnită în soluții comerciale de nivel entry.
Proiectul “Pian Electric” a demonstrat prin implementarea sa un set coerent de bune practici de inginerie hardware și software. Din punct de vedere hardware, s-au evidențiat avantajele utilizării magistralelor I2C și SPI pentru a minimiza cablajul și a economisi pini pe microcontroler, în timp ce registrul de deplasare 74HC595 a permis controlul simultan a opt LED-uri cu doar trei semnale. Alegerea formatului FAT32 pentru cardul MicroSD și a librăriilor SD.h/SPI.h a facilitat gestionarea fișierelor de partitură, în timp ce LCD-ul I2C a asigurat feedback vizual clar.
La nivel software, arhitectura bazată pe o maşină cu stări finite a oferit un cadru modular şi robust, în care fiecare stare (MENU, FREE-PLAY, SONG-SELECT, SONG-PLAY) a fost tratată de funcții specializate. Conversia frecvență–MIDI, algoritmii de mediere pentru acorduri și implementarea fade-out-ului software au adus un comportament muzical natural, iar mecanismul de validare a apăsărilor în timp real a transformat reproducerea de demo-uri într-o experiență interactivă de învățare.
Prin refactorizarea codului conform ghidului de coding-style şi documentarea extensivă în limba engleză, s-a atins un nivel ridicat de lizibilitate și mentenabilitate. Testele de performanță (latență sub 5 ms, consum energetic măsurat în condiții mixte, autonomie de peste 10 ore) au confirmat fezabilitatea soluției pentru utilizare zilnică.
În concluzie, proiectul nu doar că îndeplinește toate cerințele inițiale—moduri de redare libre și ghidate, interacțiune intuitivă și autonomie portabilă—but depășește așteptările prin elemente de noutate precum feedback-ul vizual de tip fade-out și validarea în timp real a notelor. Ca direcții viitoare, se pot explora integrarea interfețelor MIDI, adăugarea de efecte audio digitale și extinderea pe mai multe octave.
10.05.2025 - Completarea documentației cu:
18.05.2025 – Extinderea documentației și dezvoltare software: