MILESTONE 1 - TEMA PROIECT
Acest proiect constă într-o implementare a jocului Snake clasic pe platforma Arduino Uno R3. O caracteristică importantă este controlul vitezei șarpelui prin intermediul unui potențiometru, iar scorul obținut de jucător este vizualizat pe un display LCD.
MILESTONE 2 - HARDWARE
Componente: LCD 12864 Albastru ST7920, Modul potentiometru rotativ RV09, 4 x Buton Mini 6x6x5 4 pini.
Am finalizat conexiunile componentelor, configurarea pinilor butoanelor, citirea valorilor potențiometrului și funcționarea display-ului LCD. Partea hardware este completă, urmând să mă concentrez pe implementarea software-ului.
Butoane:
Cele 4 butoane sunt conectate la pinii 2, 3, 4 și 9. Am ales acești pini deoarece:
În acest mod, avem rutine separate (sau mecanisme de identificare) pentru fiecare buton, și nu trebuie să verificăm ce buton a fost apăsat în mod constant în bucla principală a programului. Un pin al fiecărui buton este conectat la ground, iar celălalt este conectat la pinul de intrare respectiv al plăcii. De asemenea, butoanele folosesc rezistențele de pull-up interne ale Arduino.
Potentiometru:
Potențiometrul este conectat astfel: GND la masă, Vcc la 5V, iar ieșirea (OUT) la pinul analogic A0 al Arduino. Valoarea citită de pe potențiometru va fi utilizată pentru a controla viteza șarpelui
LCD-ul:
Pinii LCD-ului ST7920 128×64 sunt conectati de placa Arduino in felul urmator:
LCD-ul ST7920 comunică cu placa de dezvoltare Arduino prin protocolul SPI.
Bill of Materials:
Diagrama Bloc a proiectului:
Circuit:
Schema electrică:
Poză:
MILESTONE 3 - SOFTWARE
Stadiul proiectului
Proiectul este complet, toate funcțiile menționate fiind implementate. Șarpele poate fi controlat, crește în lungime, viteza este ajustabilă, iar scorul curent și recordul sesiunii de joc sunt înregistrate.
Biblioteci
Am utilizat biblioteca u8g2, care a simplificat semnificativ accesul la funcțiile LCD-ului, permițând afișarea facilă de text și forme grafice.
Elemente originale
Un aspect distinctiv al proiectului nostru este funcționalitatea de schimbare a vitezei. Aceasta nu am întâlnit-o în alte versiuni ale jocului 'Snake' pe care le-am experimentat.
Concepte aplicate
Dintre conceptele asimilate la laborator, am aplicat întreruperile, generate de apăsarea butoanelor pentru a modifica direcția șarpelui. De asemenea, am utilizat SPI pentru comunicarea dintre placă și LCD, iar ADC-ul pentru a citi valorile analogice de la potențiometru.
Sumarul scheletului de cod
În funcția principală a programului (loop), capul șarpelui își actualizează coordonatele conform direcției sale curente. Imediat după această actualizare a poziției, sistemul efectuează o serie de verificări cruciale. În primul rând, dacă șarpele atinge un punct de scor, lungimea sa este extinsă și scorul din joc este incrementat. Apoi, se verifică dacă player-ul s-a lovit de o parte a șarpelui sau de un perete, caz în care jocul se termină (GAME OVER); scorul este resetat, iar dacă acesta este mai mare decât recordul curent al sesiunii, recordul este actualizat. La finalul buclei, un delay controlat de 100ms + map(valoare_ADC) asigură că viteza șarpelui rămâne reglabilă.
Un scurt demo al jocului
Calibrarea elementelor de senzoristica
Am utilizat funcția map() pentru a converti valorile citite de la ADC într-un interval de milisecunde ajustabil.
#include <U8g2lib.h> #include <avr/io.h> #include <avr/interrupt.h> #include <Arduino.h> const int buttonRight = 2; const int buttonDown = 3; const int buttonUp = 4; const int buttonLeft = 9; const int analogPin = A0;
#define LEFT 1 #define DOWN 2 #define UP 3 #define RIGHT 4
#define CS_PIN 10 #define RST_PIN 8
U8G2_ST7920_128X64_F_HW_SPI u8g2(U8G2_R0, CS_PIN, RST_PIN);
volatile int direction = 0; int analogValue = 0; int delay_ms = 0;
int dotX; int dotY; int newHeadX; int newHeadY; const int dotRadius = 4; const int moveStep = 4; const int gameWidth = 96; #define MAX_SNAKE_LENGTH 50 int snakeX[MAX_SNAKE_LENGTH]; int snakeY[MAX_SNAKE_LENGTH]; int snakeLength = 1;
int scoreX; int scoreY; int score = 0; int record = 0;
int manhattanDistance(int x1, int y1, int x2, int y2) { return abs(x1 - x2) + abs(y1 - y2); }
int isCloseToSnake(int x, int y) { for (int i = 0; i < snakeLength; i++) { if (manhattanDistance(x, y, snakeX[i], snakeY[i]) < 4) { return 1; } } return 0; }
void generateScorePoint() { int newX, newY; do { newX = random(dotRadius, gameWidth - dotRadius); newY = random(dotRadius, u8g2.getHeight() - dotRadius); newX -= newX % moveStep; newY -= newY % moveStep; } while (isCloseToSnake(newX, newY));
scoreX = newX; scoreY = newY; }
void resetGame() { dotX = gameWidth / 2 - (gameWidth / 2) % moveStep; dotY = u8g2.getHeight() / 2 - (u8g2.getHeight() / 2) % moveStep; direction = 0; score = 0; snakeLength = 1; generateScorePoint(); }
void showGameOver() { u8g2.clearBuffer(); u8g2.setFont(u8g2_font_ncenB14_tr); const char* gameOverText = "GAME OVER"; int textWidth = u8g2.getStrWidth(gameOverText); int textX = (u8g2.getWidth() - textWidth) / 2; int textY = u8g2.getHeight() / 2; u8g2.drawStr(textX, textY, gameOverText); u8g2.sendBuffer(); delay(3000); if (score > record) { record = score; u8g2.clearBuffer(); u8g2.setFont(u8g2_font_ncenB14_tr); const char* highScoreText = "RECORD"; textWidth = u8g2.getStrWidth(highScoreText); textX = (u8g2.getWidth() - textWidth) / 2; int textY = u8g2.getHeight() / 2; u8g2.drawStr(textX, textY, highScoreText); u8g2.sendBuffer(); delay(3000); } }
int readADC(uint8_t channel) { ADMUX = (0 << REFS1) | (1 << REFS0) | (channel & 0x07); ADCSRA |= (1 << ADSC); while (ADCSRA & (1 << ADSC)); return ADCW; }
void SPI_MasterInit(void) { DDRB |= (1 << DDB3) | (1 << DDB5) | (1 << DDB2); SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0); }
void setup() { DDRD &= ~((1 << PIND2) | (1 << PIND3) | (1 << PIND4)); PORTD |= (1 << PIND2) | (1 << PIND3) | (1 << PIND4); DDRB &= ~(1 << PINB1); PORTB |= (1 << PINB1); UBRR0H = (uint8_t)(51 & 0xF00); UBRR0L = (uint8_t)(51 & 0x0FF); UCSR0B = (1 << RXEN0) | (1 << TXEN0); UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
ADMUX = (0 << REFS1) | (1 << REFS0) | (0 << ADLAR); ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
randomSeed(readADC(1));
EICRA |= (1 << ISC11) | (1 << ISC01); EIMSK |= (1 << INT1) | (1 << INT0); PCICR |= (1 << PCIE2) | (1 << PCIE0); PCMSK0 |= (1 << PCINT1); PCMSK2 |= (1 << PCINT20); sei(); Serial.begin(9600);
SPI_MasterInit();
u8g2.begin();
resetGame(); }
ISR(INT0_vect) { if (direction != LEFT) direction = RIGHT; delayMicroseconds(20000); }
ISR(INT1_vect) { if (direction != UP) direction = DOWN; delayMicroseconds(20000); }
ISR(PCINT2_vect) { if (!((PIND >> PIND4) & 1)) { if (direction != DOWN) direction = UP; } delayMicroseconds(20000); }
ISR(PCINT0_vect) { if (!((PINB >> PINB1) & 1)) { if (direction != RIGHT) direction = LEFT; } delayMicroseconds(20000); }
void loop() { u8g2.clearBuffer(); analogValue = readADC(0); delay_ms = map(analogValue, 0, 1023, 0, 200); switch (direction) { case LEFT: dotX -= moveStep; break; case RIGHT: dotX += moveStep; break; case UP: dotY -= moveStep; break; case DOWN: dotY += moveStep; break; }
if (dotX < 0 || dotX >= gameWidth || dotY < 0 || dotY >= u8g2.getHeight()) { showGameOver(); resetGame(); }
newHeadX = dotX; newHeadY = dotY; if (dotX == scoreX && dotY == scoreY) { score++; snakeLength++; if (snakeLength > MAX_SNAKE_LENGTH) { snakeLength = MAX_SNAKE_LENGTH; } generateScorePoint(); }
for (int i = 1; i < snakeLength; i++) { if (snakeX[i] == dotX && snakeY[i] == dotY) { showGameOver(); resetGame(); } } for (int i = snakeLength - 1; i > 0; i--) { snakeX[i] = snakeX[i - 1]; snakeY[i] = snakeY[i - 1]; } snakeX[0] = newHeadX; snakeY[0] = newHeadY;
u8g2.setDrawColor(1); for (int i = 0; i < snakeLength; i++) { u8g2.drawBox(snakeX[i], snakeY[i], dotRadius, dotRadius); } u8g2.drawBox(scoreX, scoreY, dotRadius, dotRadius); u8g2.drawVLine(gameWidth, 0, u8g2.getHeight()); u8g2.setFont(u8g2_font_6x10_tf); char scoreTextDisplay[10]; sprintf(scoreTextDisplay, "Scor"); u8g2.drawStr(gameWidth + (u8g2.getWidth() - gameWidth - u8g2.getStrWidth(scoreTextDisplay)) / 2, 10, scoreTextDisplay); u8g2.setFont(u8g2_font_7x13_tf); char scoreString[5]; sprintf(scoreString, "%d", score); u8g2.drawStr(gameWidth + (u8g2.getWidth() - gameWidth - u8g2.getStrWidth(scoreString)) / 2, 25, scoreString); u8g2.drawHLine(gameWidth, u8g2.getHeight() / 2, u8g2.getWidth() - gameWidth); u8g2.setFont(u8g2_font_6x10_tf); char recordText[10]; sprintf(recordText, "Best"); u8g2.drawStr(gameWidth + (u8g2.getWidth() - gameWidth - u8g2.getStrWidth(recordText)) / 2, u8g2.getHeight() / 2 + 11, recordText); u8g2.setFont(u8g2_font_7x13_tf); char recordString[5]; sprintf(recordString, "%d", record); u8g2.drawStr(gameWidth + (u8g2.getWidth() - gameWidth - u8g2.getStrWidth(recordString)) / 2, u8g2.getHeight() / 2 + 26, recordString); u8g2.sendBuffer(); Serial.println(delay_ms); delay(100 + delay_ms); }