Simon Says - Joc de memorie cu LEDs, LCD si Buzzer

Introducere

Proiectul reprezinta o implementare extinsa a jocului clasic Simon Says pe placa ATmega328P Xplained Mini. Jocul afiseaza secvente de lumini prin 4 LED-uri colorate, iar jucatorul trebuie sa reproduca secventa in ordinea corecta, apasand butoanele corespunzatoare. Cu fiecare nivel reusit, secventa creste in lungime, iar viteza de afisare creste progresiv.

Proiectul integreaza:

  • LCD 16×2 cu modul I2C pentru afisarea scorului, nivelului, countdown-ului si mesajelor de stare
  • Animatii LCD cu 8 caractere custom (inima, skull, trofeu, note muzicale, progress bar)
  • Selectare dificultate la start — Easy / Medium / Hard, cu viteze si timpi diferiti
  • Dificultate adaptiva bidiectionala — viteza creste la succese si scade la greseli
  • Countdown vizibil pe LCD in timpul in care jucatorul replica secventa
  • Feedback sonor prin buzzer activ, cu note diferite per culoare
  • Melodie “We Are the Champions” la baterea recordului, cu LED-uri sincronizate
  • Celebrare record in timp real — fara sa intrerupa jocul
  • EEPROM persistent pentru salvarea celui mai bun scor intre resetari

Schema bloc:

Descriere Generala

Logica jocului

  1. La pornire, LCD afiseaza animatie de intro cu scroll si cel mai bun scor din EEPROM
  2. LED-urile executa o animatie de bun-venit (wave in ambele directii)
  3. Jucatorul selecteaza dificultatea: Easy (B1), Medium (B2), Hard (B3)
  4. La apasarea oricarui buton, jocul incepe
  5. Microcontrolerul genereaza o secventa aleatoare si o afiseaza prin LED-uri cu sunet specific fiecarei culori
  6. Jucatorul reproduce secventa; pe LCD se afiseaza countdown-ul de timp ramas
  7. La nivel corect: animatie inima care bate, scor actualizat, dificultate crescuta
  8. Daca scorul depaseste recordul: celebrare imediata cu trofeu pe LCD si melodie, jocul continua
  9. La raspuns gresit sau timeout: skull animat, Game Over, scor afisat, selectie dificultate din nou
  10. Recordul persista in EEPROM intre resetari si deconectari

Masina de stari

IDLE (ecran intro + animatie)
    │ apasare orice buton
    ▼
SELECT_DIFFICULTY (Easy / Medium / Hard)
    │ apasare B1 / B2 / B3
    ▼
SHOW_SEQUENCE (afisare secventa LED-uri cu sunet)
    │ secventa terminata
    ▼
WAIT_INPUT (asteapta input jucator, countdown vizibil pe LCD)
    ├─ corect, scor > record → RECORD (trofeu + melodie) → SHOW_SEQUENCE
    ├─ corect, scor <= record → CORRECT (animatie inima) → SHOW_SEQUENCE
    └─ gresit / timeout → GAME_OVER (skull animat + salvare scor) → SELECT_DIFFICULTY

Hardware Design

Lista de componente

Componenta Model/Valoare Cantitate Rol in proiect
Microcontroller ATmega328P Xplained Mini 1 Unitate centrala de control
Display LCD 16×2 cu modul I2C (PCF8574) 1 Afisare scor, nivel, countdown, animatii
LED rosu 5mm, 2V, 20mA 1 Culoarea 1 a jocului
LED verde 5mm, 2.1V, 20mA 1 Culoarea 2 a jocului
LED galben 5mm, 2V, 20mA 1 Culoarea 3 a jocului
LED albastru 5mm, 3.2V, 20mA 1 Culoarea 4 a jocului
Rezistenta 220Ω 4 Protectie LED-uri (limiteaza curentul la ~15mA)
Butoane tactile 6x6mm, 4 pini 4 Input jucator
Buzzer activ 5V 1 Feedback sonor, note diferite per culoare
Breadboard 830 puncte 1 Prototipare circuit
Fire jumper M-M ~25 Conexiuni intre componente

Pinii folositi

Componenta Pin ATmega Pin Arduino Motivatie alegere
LED rosu PD2 D2 GPIO simplu, fara functii speciale
LED verde PD3 D3 GPIO simplu, fara functii speciale
LED galben PD4 D4 GPIO simplu, fara functii speciale
LED albastru PD5 D5 GPIO simplu, fara functii speciale
Buton rosu PD6 D6 GPIO cu pull-up intern disponibil
Buton verde PD7 D7 GPIO cu pull-up intern disponibil
Buton galben PB0 D8 GPIO cu pull-up intern disponibil
Buton albastru PB1 D9 GPIO cu pull-up intern disponibil
Buzzer activ PB2 D10 Compatibil cu functia tone()
LCD SDA PC4 A4 Pin hardware TWI/I2C al ATmega328P
LCD SCL PC5 A5 Pin hardware TWI/I2C al ATmega328P

Motivatia alegerii pinilor:

  • PD0/PD1 au fost evitati — sunt RX/TX, folositi de comunicatia seriala USB cu mEDBG; conectarea LED-urilor pe acesti pini blocheaza upload-ul de cod
  • PC4/PC5 sunt pinii hardware TWI (I2C) ai ATmega328P, oferind comunicatie stabila cu LCD-ul prin protocol hardware, nu software bitbang
  • Butoanele folosesc INPUT_PULLUP intern — elimina necesitatea rezistentelor externe de pull-down, simplificand circuitul si reducand numarul de componente

Schema electrica

LED-uri (x4, conectare identica):

Pin GPIO (D2-D5) ── Rezistenta 220Ω ── Anod LED (+, picior lung)
                                        Catod LED (-, picior scurt) ── GND

Butoane (x4, cu pull-up intern, fara rezistente externe):

Pin GPIO (D6-D9) ── Pin 1 buton (o latura)
GND ─────────────── Pin 2 buton (latura opusa, de cealalta parte a canalului central)
Pull-up intern activat prin INPUT_PULLUP → citeste LOW la apasare, HIGH in repaus

Buzzer activ:

D10 ── Pin + buzzer (marcat cu +)
GND ── Pin - buzzer (marcat cu -)

LCD I2C:

A4 (SDA/PC4) ── SDA modul I2C
A5 (SCL/PC5) ── SCL modul I2C
5V ──────────── VCC modul I2C
GND ─────────── GND modul I2C
Potentiometru albastru pe modul = reglaj contrast

5211176995717324748.jpg

Software Design

Mediu de dezvoltare

  • Arduino IDE cu framework Arduino
  • Limbaj: C++ cu API Arduino
  • Upload prin USB (mEDBG integrat pe placa Xplained Mini)
  • Serial Monitor la 9600 baud pentru debugging si logging

Biblioteci folosite

Biblioteca Sursa Motivatie
Wire.h Built-in Arduino Comunicatie I2C hardware cu LCD
LiquidCrystal_I2C.h Frank de Brabander Abstractizare comenzi LCD: initializare, cursor, print, caractere custom
EEPROM.h Built-in Arduino Acces la memoria EEPROM pentru persistenta high score

Elementul de noutate

  • Selectare dificultate la start: jucatorul alege Easy / Medium / Hard inainte de fiecare joc, cu viteze si timpi de replica diferiti; selectia reapare dupa fiecare Game Over
  • Dificultate adaptiva bidiectionala: viteza creste la succese (−20ms/nivel) si scade la greseli (+30ms), cu limite 150ms–600ms, mentinand jocul accesibil indiferent de nivel
  • Celebrare record in timp real: daca scorul depaseste recordul in timpul jocului (nu doar la Game Over), se afiseaza trofeu pe LCD, se canta melodia si se salveaza in EEPROM — jocul continua fara intrerupere
  • Animatii LCD cu caractere custom: 8 simboluri definite ca matrici 5×8 pixeli (inima, skull, trofeu, note muzicale, sageata, progress bar) folosite in animatii procedurale
  • Melodie la record: primele 15 note din “We Are the Champions” cantate pe buzzer, cu LED-urile clipind sincronizat cu fiecare nota
  • Countdown vizibil: in timpul replicii jucatorului, LCD afiseaza secundele ramase, actualizate in timp real

Functionalitati din laborator utilizate

Functionalitate Utilizare in proiect
GPIO output Control LED-uri pe D2-D5 pentru afisarea secventei si animatii
GPIO input cu pull-up Citire butoane fara rezistente externe: INPUT_PULLUP pe D6-D9
Timer (tone()) Generare frecvente audio pentru buzzer — intern foloseste Timer2
TWI/I2C (Wire.h) Comunicatie cu modulul LCD prin 2 fire (SDA/SCL)
EEPROM Stocare persistenta a high score-ului la adresa 0 cu EEPROM.put/get
Debounce software Eliminarea bouncing-ului mecanic: asteptare release + delay 50ms

Scheletul proiectului si portiuni de cod relevante

Selectare dificultate

La pornire si dupa fiecare Game Over, jucatorul selecteaza dificultatea prin primele 3 butoane. LED-urile se aprind ca indicatori vizuali in timp ce asteapta selectia:

Dificultate Buton Viteza initiala Timp replica
Easy B1 (rosu) 600ms per LED 12 secunde
Medium B2 (verde) 500ms per LED 10 secunde
Hard B3 (galben) 300ms per LED 7 secunde
void selecteazaDificultate() {
  lcd.write(CHAR_NOTE);    lcd.print("E  ");   // Easy
  lcd.write(CHAR_SAGEATA); lcd.print("M  ");   // Medium
  lcd.write(CHAR_SKULL);   lcd.print("H");     // Hard
 
  // Aprinde LED-urile ca indicatori vizuali
  for (int i = 0; i < 3; i++) digitalWrite(ledPins[i], HIGH);
 
  while (!ales) {
    if (digitalRead(btnPins[0]) == LOW) { viteza=600; timpReplica=12000; ales=true; }
    if (digitalRead(btnPins[1]) == LOW) { viteza=500; timpReplica=10000; ales=true; }
    if (digitalRead(btnPins[2]) == LOW) { viteza=300; timpReplica=7000;  ales=true; }
  }
}

Dificultate adaptiva bidiectionala

Dupa fiecare nivel, viteza si timpul de replica se ajusteaza automat. Limitele explicite previn ca jocul sa devina imposibil sau prea lent:

void ajusteazaDificultate(bool corect) {
  if (corect) {
    viteza      = max(150, viteza - 20);       // LED-urile se aprind mai repede
    timpReplica = max(5000, timpReplica - 300); // mai putin timp de replica
  } else {
    viteza      = min(600, viteza + 30);        // LED-urile se aprind mai lent
    timpReplica = min(12000, timpReplica + 500);// mai mult timp de replica
  }
}

Countdown vizibil pe LCD

In bucla de asteptare a butoanelor, LCD-ul este actualizat continuu cu secundele ramase. Functia returneaza −1 la timeout, tratata ca greseala:

int waitForButton() {
  unsigned long start = millis();
  while (millis() - start < (unsigned long)timpReplica) {
    int secRamase = (timpReplica - (millis() - start)) / 1000;
    lcd.setCursor(9, 1);
    lcd.print("T:"); lcd.print(secRamase); lcd.print("s  ");
 
    for (int i = 0; i < 4; i++) {
      if (digitalRead(btnPins[i]) == LOW) {
        lightUp(i, 300);
        while (digitalRead(btnPins[i]) == LOW); // asteapta release
        delay(50);                               // debounce
        return i;
      }
    }
  }
  return -1; // timeout
}

Animatii LCD cu caractere custom

LCD-ul 16×2 suporta pana la 8 caractere custom definite ca matrici de pixeli 5×8. Proiectul defineste 8 simboluri inregistrate la initializare cu lcd.createChar():

Index Simbol Utilizat in
0 Inima plina Nivel corect, record
1 Inima goala Animatie batai inima
2 Skull Game Over
3 Note muzicale Intro, selectie Easy
4 Sageata Nivel curent, selectie Medium
5 Trofeu Record, castig
6 Progres plin Progress bar
7 Progres gol Progress bar

Exemplu definitie si animatie inima care bate:

byte inima[]      = {0b00000,0b01010,0b11111,0b11111,0b11111,0b01110,0b00100,0b00000};
byte inimaGoala[] = {0b00000,0b01010,0b10101,0b10001,0b10001,0b01010,0b00100,0b00000};
 
void animatieInima() {
  for (int i = 0; i < 3; i++) {   // 3 batai
    lcd.setCursor(15, 0);
    lcd.write(CHAR_INIMA);          // inima plina
    tone(buzzer, 800); delay(80); noTone(buzzer);
    delay(100);
    lcd.setCursor(15, 0);
    lcd.write(CHAR_INIMA_GOALA);    // inima goala
    delay(200);
  }
  lcd.setCursor(15, 0);
  lcd.write(CHAR_INIMA);
}

Progress bar pe randul 2 al LCD-ului, care arata progresul prin cele 20 de nivele:

void afiseazaProgressBar() {
  lcd.setCursor(0, 1);
  int pasi = map(level, 1, MAXLEVEL, 0, 16); // 1-20 nivele → 0-16 caractere
  for (int i = 0; i < 16; i++) {
    if (i < pasi) lcd.write(CHAR_PROG_FULL);
    else          lcd.write(CHAR_PROG_EMPTY);
  }
}

Celebrare record in timp real

Spre deosebire de implementarile clasice (care verifica recordul doar la Game Over), aceasta implementare verifica dupa fiecare nivel corect. La depasirea recordului, se afiseaza trofeu, se salveaza in EEPROM si se canta melodia — apoi jocul continua:

// In loop(), dupa completarea unui nivel:
scor += level * 10;
ajusteazaDificultate(true);
 
if (scor > highScore) {
  celebreazaRecord();   // trofeu + melodie + salveaza EEPROM
} else {
  animatieInima();      // animatie normala nivel corect
}
 
level++;
if (level > MAXLEVEL) win();
else playSequence();    // jocul continua normal
 
void celebreazaRecord() {
  lcd.write(CHAR_TROPHY); lcd.print(" NOU RECORD! "); lcd.write(CHAR_TROPHY);
  highScore = scor;
  EEPROM.put(0, highScore);  // persistenta intre resetari
  cantaMelodie();             // We Are the Champions
}

Melodie la record

La baterea recordului, buzzerul canta primele 15 note din “We Are the Champions”. LED-urile clipesc sincronizat cu melodia (cate un LED diferit per nota, in cicluri de 4):

const int melodieNote[]   = { 392,392,349,392,0,523,494,440,392,
                               659,659,659,587,523,392 };
const int melodieDurata[] = { 200,200,200,400,200,400,200,200,600,
                               200,200,200,200,200,600 };
 
void cantaMelodie() {
  for (int i = 0; i < melodieLen; i++) {
    if (melodieNote[i] == 0) { noTone(buzzer); }   // pauza intre note
    else {
      tone(buzzer, melodieNote[i]);
      digitalWrite(ledPins[i % 4], HIGH);           // LED sincronizat cu nota
    }
    delay(melodieDurata[i]);
    noTone(buzzer);
    digitalWrite(ledPins[i % 4], LOW);
    delay(30);
  }
}

Persistenta EEPROM

Scorul maxim este salvat la adresa 0 din EEPROM. Validarea la citire protejeaza impotriva datelor corupte sau a primei porniri (cand EEPROM contine valori nedefinite):

// La pornire — citire cu validare:
EEPROM.get(0, highScore);
if (highScore < 0 || highScore > 9999) highScore = 0;
 
// La baterea recordului — scriere imediata:
EEPROM.put(0, highScore);

Debounce butoane

Butoanele mecanice genereaza bouncing la apasare si eliberare. Debounce-ul software combina doua tehnici complementare:

if (digitalRead(btnPins[i]) == LOW) {
  lightUp(i, 300);                          // feedback vizual imediat
  while (digitalRead(btnPins[i]) == LOW);   // asteapta release complet
  delay(50);                                // elimina bouncing la eliberare
  return i;
}

Validare functionare

Validarea s-a realizat in mai multe etape:

1. Testare LED-uri individual:

// Aprinde fiecare LED pe rand cu delay, verifica orientarea si conexiunile
void loop() {
  for (int i = 0; i < 4; i++) {
    digitalWrite(ledPins[i], HIGH); delay(500);
    digitalWrite(ledPins[i], LOW);  delay(200);
  }
}

2. Testare butoane cu Serial Monitor:

// Afiseaza butonul apasat — a identificat problema de inversare GND/semnal
void loop() {
  for (int i = 0; i < 4; i++) {
    if (digitalRead(btnPins[i]) == LOW) {
      Serial.print("Buton "); Serial.print(i+1); Serial.println(" apasat!");
      delay(300);
    }
  }
}

3. Validare prin Serial Monitor in joc: La fiecare eveniment se afiseaza mesaje la 9600 baud:

=== SIMON SAYS ===
Dificultate: MEDIUM
------------------
NIVEL 3 | Viteza: 460ms
Secventa:
  1. Rosu
  2. Verde
  3. Albastru
Repeta secventa!
  Apasat: Rosu -> Corect! (1/3)
  Apasat: Verde -> Corect! (2/3)
  Apasat: Albastru -> Corect! (3/3)
Nivel completat! Scor: 60
*** NOU HIGH SCORE! ***
Viteza noua: 440 | Timp replica: 9400

4. Testare EEPROM: Dupa game over cu scor nou, s-a verificat ca la resetare hardware scorul persista (citit cu EEPROM.get la pornire si afisat pe LCD).

Optimizari realizate

  • Debounce software: asteptare release + delay 50ms, eliminand bouncing-ul mecanic fara timere hardware
  • randomSeed(analogRead(A0)): pinul A0 neconectat citeste zgomot analogic, asigurand secvente cu adevarat aleatorii la fiecare pornire
  • INPUT_PULLUP: elimina rezistentele externe de pull-down pentru butoane, simplificand circuitul
  • Validare EEPROM la citire: protejeaza impotriva valorilor corupte sau nedefinite la prima pornire
  • Celebrare record inline: verificarea si salvarea recordului se face dupa fiecare nivel, nu doar la Game Over, fara a intrerupe fluxul jocului

Rezultate Obtinute

Proiectul functioneaza conform specificatiilor extinse:

  • Selectarea dificultaii este functionala cu feedback LED si LCD, reapare dupa fiecare Game Over
  • Secventele sunt generate aleator si afisate corect pe LED-uri cu sunet specific per culoare
  • Butoanele sunt detectate corect cu debounce, fara false pozitive sau omisiuni
  • LCD afiseaza nivel, scor, countdown timp ramas si animatii in timp real
  • Dificultatea se ajusteaza automat dupa fiecare nivel in ambele directii
  • Recordul se salveaza in EEPROM si persista intre resetari hardware si deconectari
  • Celebrarea recordului se declanseaza imediat, fara sa intrerupa jocul
  • Melodia “We Are the Champions” se canta corect cu LED-urile sincronizate pe note
  • Progress bar-ul reflecta fidel progresul prin cele 20 de nivele

Concluzii

Proiectul Simon Says a fost implementat cu succes pe placa ATmega328P Xplained Mini, depasind specificatiile de baza prin adaugarea mai multor functionalitati care imbunatatesc experienta de joc si complexitatea tehnica.

Principalele provocari intampinate:

  • Compatibilitatea librariei LiquidCrystal_I2C cu toolchain-ul MiniCore — rezolvata prin implementare manuala Wire.h
  • Conflicte de pini intre RX/TX si LED-uri — rezolvate prin relocarea LED-urilor pe PD4-PD7
  • Calibrarea debounce-ului butoanelor pentru raspuns fluid
  • High score blocat — valoare corupta in EEPROM dintr-o sesiune anterioara; rezolvat prin validare la citire si reset manual

Lectii invatate:

  • Documentarea pinilor inainte de cablare evita conflicte greu de depanat
  • Testarea incrementala (LED, buton, LCD, buzzer separat) economiseste timp considerabil
  • INPUT_PULLUP simplifica semnificativ circuitul butoanelor
  • Verificarea recordului in timp real (nu doar la Game Over) ofera o experienta de joc mai fluida

Bibliografie

pm/prj2026/alexandru.jipa2803/valentina.ceban.txt · Last modified: 2026/05/24 20:26 by valentina.ceban
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