This is an old revision of the document!
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:
Schema bloc:
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
| 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 |
| 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:
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
| 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 |
| 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 |
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; } } }
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 } }
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 }
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); } }
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 }
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); } }
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);
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; }
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).
Proiectul functioneaza conform specificatiilor extinse:
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:
Lectii invatate: