Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pm:prj2025:ajipa:andrei.radu2102 [2025/05/21 14:10]
andrei.radu2102 [Exemplu de Secvențe de Cod]
pm:prj2025:ajipa:andrei.radu2102 [2025/05/21 15:44] (current)
andrei.radu2102 [Exemplu de Secvențe de Cod]
Line 313: Line 313:
  
 ===== Exemplu de Secvențe de Cod ===== ===== Exemplu de Secvențe de Cod =====
 +=== 1. Controlul registrului de deplasare 74HC595 (sendMask) ===
  
 +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.
  
 +<code cpp> ​
 +/* * 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);
 +
 +</​code>​
  
 +De ce e semnificativ:​
 +
 +  - reversBits(mask) – demonstrează o soluție software pentru adaptarea ordinii logice la cablaj.
 +  - digitalWrite(…LOW/​HIGH) – ilustrează principiul de „strobing” al registrului,​ esențial pentru a preveni flicker‐ul LED-urilor.
 +  - shiftOut(...) – o funcție standard care simplifică mult codul manual de toggling al pinilor.
 +
 +Pas cu pas:
 +
 +  * Întâi realiniem bitii la modul cum sunt conectați fizic.
 +  * Coborâm LATCH înainte de a trimite datele, pentru a nu afișa valori intermediare.
 +  * Trimitem octetul bit cu bit, sincronizat de ceas (CLOCK).
 +  * Ridicăm LATCH, moment în care toate ieșirile se actualizează simultan.
 +
 +=== 2. Citirea tuturor tastelor cu un singur apel (readKeysMask) ===
 +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.
 +
 +<code cpp> ​
 +/* * 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; 
 +
 +</​code>​
 +
 +De ce e semnificativ:​
 +
 +  - Loop‐ul compact arată cum putem extinde ușor la mai multe taste fără cod repetitiv.
 +  - Active LOW cu INPUT_PULLUP reduce necesarul de componente externe (nu folosim rezistoare de pull-down).
 +  - Masca de biți permite operații bitwise ulterioare foarte rapide pentru logică și afișare.
 +
 +Pas cu pas:
 +
 +  * Inițializăm masca cu zero (nicio tastă).
 +  * Iterăm pinii definiți în KEY_PINS[].
 +  * Dacă o tastă e apăsată, setăm bitul i în mask.
 +  * La final, întoarcem întregul octet, reprezentând simultan starea celor 8 taste.
 +
 +=== 3. Mașina cu stări finite în loop() ===
 +
 +Am ales această secvență pentru a evidenția clar separarea funcțională a aplicației:​ meniu, free-play, selectare și redare.
 +
 +<code cpp> ​
 +/* * 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; ​
 +    } 
 +
 +</​code>​
 +
 +De ce e semnificativ:​
 +
 +  - Clarity și Extensibilitate:​ adăugarea unui nou mod sau stare se reduce la o nouă intrare în enum și la o nouă funcție.
 +  - Separare de responsabilități:​ fiecare *Task() gestionează un singur scop, facilitând testarea și mentenanța.
 +  - Robustete: fallback-ul default previne blocaje în caz de stare neașteptată.
 +
 +Pas cu pas:
 +
 +  * switch pe currentState distrbuie controlul către funcții dedicate.
 +  * Fiecare ramură tratează intrările și ieșirile pentru starea respectivă.
 +  * Dacă starea nu există, revenim în siguranță la meniu.
 +
 +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.
 +
 +
 +<code cpp>
 +// 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);
 +}
 +</​code>​
 +
 +<code cpp>
 +/*
 + * 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;
 +}
 +</​code>​
 +
 +<code cpp>
 +/*
 + * 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;
 +}
 +</​code>​
 +
 +<code cpp>
 +/*
 + * 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);
 +    }
 +}
 +</​code>​
 +
 +<​html>​
 +<iframe width="​560"​ height="​315"​
 +        src="​https://​www.youtube.com//​embed/​p7ay5NSMkBY"​
 +        frameborder="​0"​
 +        allow="​autoplay;​ encrypted-media"​
 +        allowfullscreen>​
 +</​iframe>​
 +</​html>​
  
 ===== Rezultate Obţinute ===== ===== Rezultate Obţinute =====
Line 366: Line 763:
  
 Î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. Î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.
-===== Download ===== 
  
-<note warning> 
-O arhivă (sau mai multe dacă este cazul) cu fişierele obţinute în urma realizării proiectului:​ surse, scheme, etc. Un fişier README, un ChangeLog, un script de compilare şi copiere automată pe uC crează întotdeauna o impresie bună ;-). 
- 
-Fişierele se încarcă pe wiki folosind facilitatea **Add Images or other files**. Namespace-ul în care se încarcă fişierele este de tipul **:​pm:​prj20??:​c?​** sau **:​pm:​prj20??:​c?:​nume_student** (dacă este cazul). **Exemplu:​** Dumitru Alin, 331CC -> **:​pm:​prj2009:​cc:​dumitru_alin**. 
-</​note>​ 
  
 ===== Jurnal ===== ===== Jurnal =====
Line 394: Line 785:
  
   - [[https://​github.com/​andreiradu2102/​Pian-electric|Proiect GitHub: Pian-electric]]   - [[https://​github.com/​andreiradu2102/​Pian-electric|Proiect GitHub: Pian-electric]]
 +  - [[https://​www.youtube.com/​watch?​v=p7ay5NSMkBY|Link video YouTube]]
   - [[https://​docs.arduino.cc/​resources/​datasheets/​A000066-datasheet.pdf|Arduino Uno R3 Datasheet]]   - [[https://​docs.arduino.cc/​resources/​datasheets/​A000066-datasheet.pdf|Arduino Uno R3 Datasheet]]
   - [[https://​pdf.datasheet.live/​datasheets-1/​kingbright/​L-53GD-B.pdf|LED verde 5 mm (Kingbright L-53GD) Datasheet]] ​   - [[https://​pdf.datasheet.live/​datasheets-1/​kingbright/​L-53GD-B.pdf|LED verde 5 mm (Kingbright L-53GD) Datasheet]] ​
pm/prj2025/ajipa/andrei.radu2102.1747825828.txt.gz · Last modified: 2025/05/21 14:10 by andrei.radu2102
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