This shows you the differences between two versions of the page.
pm:prj2024:ddosaru:marius.cristea1811 [2024/05/27 01:38] marius.cristea1811 [Hardware Design] |
pm:prj2024:ddosaru:marius.cristea1811 [2024/05/27 02:10] (current) marius.cristea1811 [Software Design] |
||
---|---|---|---|
Line 52: | Line 52: | ||
Schema cablaj: \\ | Schema cablaj: \\ | ||
- | {{:pm:prj2024:ddosaru:1_schcab.jpg?200|}} | + | {{:pm:prj2024:ddosaru:1_schcab.png?200|}} \\ |
Schema electrica: \\ | Schema electrica: \\ | ||
{{:pm:prj2024:ddosaru:1_schel.png?200|}} \\ | {{:pm:prj2024:ddosaru:1_schel.png?200|}} \\ | ||
Poza cablaj: \\ | Poza cablaj: \\ | ||
{{:pm:prj2024:ddosaru:1-zacablaj.jpg?200|}} \\ | {{:pm:prj2024:ddosaru:1-zacablaj.jpg?200|}} \\ | ||
- | Poza proiect: \\ | ||
- | {{:pm:prj2024:ddosaru:1-zalook.jpg?200|}} | ||
- | Tin sa mentionez ca in schema cablajului nu este acelasi amplificator audio pe care il folosesc pentru ca nu am gasit schema exacta a componentei, dar este unul similar. In schema electrica am realizat manual pentru amplificatorul audio desenul.\\ | + | Tin sa mentionez ca in schema cablajului nu este acelasi amplificator audio pe care il folosesc pentru ca nu am gasit schema exacta a componentei, dar este unul similar.\\ |
===== Conexiuni pini ===== | ===== Conexiuni pini ===== | ||
pinuri digitale: \\ | pinuri digitale: \\ | ||
- | SCK - pin 13 pentru microSD card reader si LCD (la comun) \\ | + | SCK - pin 13 pentru microSD card reader si LCD \\ |
- | MOSI - pin 11 pentru microSD card reader si LCD (la comun) \\ | + | MOSI - pin 11 pentru microSD card reader si LCD \\ |
pin 4 - CS microSD card reader \\ | pin 4 - CS microSD card reader \\ | ||
pin 8 - RES LCD \\ | pin 8 - RES LCD \\ | ||
pin 9 - DC LCD \\ | pin 9 - DC LCD \\ | ||
+ | pin 9 - Speaker (pe arduino nano) \\ | ||
pin 10 - CS LCD \\ | pin 10 - CS LCD \\ | ||
pinuri analog: \\ | pinuri analog: \\ | ||
- | pin 2: input pentru speaker conectat la IN - amplificator \\ | + | pin A0,A1,A2,A3,A4: pinii pentru butoane \\ |
- | pin 0,1,3,4,5: pinii pentru butoane \\ | + | |
Line 84: | Line 82: | ||
<note tip> | <note tip> | ||
- | Descrierea codului aplicaţiei (firmware): | + | |
- | * mediu de dezvoltare (if any) (e.g. AVR Studio, CodeVisionAVR) | + | * mediu de dezvoltare: Arduino IDE \\ |
- | * librării şi surse 3rd-party (e.g. Procyon AVRlib) | + | * librarii: Adafruit_GFX; Adafruit_ST7735; TMRpcm |
- | * algoritmi şi structuri pe care plănuiţi să le implementaţi | + | |
- | * (etapa 3) surse şi funcţii implementate | + | |
</note> | </note> | ||
- | ===== Rezultate Obţinute ===== | + | Codul pentru joc: \\ |
+ | <file PM_project> | ||
- | <note tip> | + | #include <Adafruit_GFX.h> |
- | Care au fost rezultatele obţinute în urma realizării proiectului vostru. | + | #include <Adafruit_ST7735.h> |
- | </note> | + | #include <SPI.h> |
- | ===== Concluzii ===== | + | // Definirea pinilor pentru LCD |
+ | #define TFT_CS 10 | ||
+ | #define TFT_RST 8 | ||
+ | #define TFT_DC 9 | ||
- | ===== Download ===== | + | // Definirea pinilor pentru butoane |
+ | #define BUTTON_UP A0 | ||
+ | #define BUTTON_DOWN A4 | ||
+ | #define BUTTON_LEFT A1 | ||
+ | #define BUTTON_RIGHT A2 | ||
+ | #define BUTTON_SELECT A3 | ||
- | <note warning> | + | // Crearea unui obiect Adafruit_ST7735 pentru ecranul LCD |
- | 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ă ;-). | + | Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); |
- | 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**. | + | // Dimensiuni grilă |
- | </note> | + | #define GRID_SIZE 3 |
+ | #define CELL_SIZE 30 | ||
- | ===== Jurnal ===== | + | // Debounce |
+ | unsigned long lastDebounceTime = 0; | ||
+ | unsigned long debounceDelay = 200; | ||
- | <note tip> | + | // Starea jocului |
- | Puteți avea și o secțiune de jurnal în care să poată urmări asistentul de proiect progresul proiectului. | + | char board[GRID_SIZE][GRID_SIZE]; |
- | </note> | + | int currentPlayer = 0; // 0 pentru X, 1 pentru O |
+ | int currentRow = 0, currentCol = 0; | ||
+ | |||
+ | // Structuri pentru a ține evidența mutărilor fiecărui jucător | ||
+ | struct Move { | ||
+ | int row; | ||
+ | int col; | ||
+ | }; | ||
+ | |||
+ | Move movesX[3]; | ||
+ | Move movesO[3]; | ||
+ | int moveCountX = 0; | ||
+ | int moveCountO = 0; | ||
+ | |||
+ | // Calcularea coordonatelor pentru centrare | ||
+ | int gridStartX = (tft.width() - (GRID_SIZE * CELL_SIZE)) / 2; | ||
+ | int gridStartY = (tft.height() - (GRID_SIZE * CELL_SIZE) - 40) / 2; | ||
+ | |||
+ | bool gameWon = false; | ||
+ | bool alreadyDisplayed = false; | ||
+ | unsigned long winTime; | ||
+ | |||
+ | void setup() { | ||
+ | Serial.begin(9600); | ||
+ | |||
+ | // Inițializarea LCD-ului | ||
+ | tft.initR(INITR_BLACKTAB); | ||
+ | tft.fillScreen(ST77XX_BLACK); // Curățarea ecranului și umplerea cu negru | ||
+ | |||
+ | // Inițializarea pinilor pentru butoane | ||
+ | pinMode(BUTTON_UP, INPUT_PULLUP); | ||
+ | pinMode(BUTTON_DOWN, INPUT_PULLUP); | ||
+ | pinMode(BUTTON_LEFT, INPUT_PULLUP); | ||
+ | pinMode(BUTTON_RIGHT, INPUT_PULLUP); | ||
+ | pinMode(BUTTON_SELECT, INPUT_PULLUP); | ||
+ | |||
+ | // Inițializarea tablei de joc | ||
+ | for (int i = 0; i < GRID_SIZE; i++) { | ||
+ | for (int j = 0; j < GRID_SIZE; j++) { | ||
+ | board[i][j] = ' '; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Desenarea grilei | ||
+ | drawGrid(); | ||
+ | highlightCell(currentRow, currentCol, true); | ||
+ | } | ||
+ | |||
+ | |||
+ | void loop() { | ||
+ | if (gameWon) { | ||
+ | if (millis() - winTime > 10000) { // Așteaptă 10 secunde înainte de resetare | ||
+ | resetBoard(); | ||
+ | gameWon = false; | ||
+ | alreadyDisplayed = false; | ||
+ | tft.fillScreen(ST77XX_BLACK); | ||
+ | drawGrid(); | ||
+ | } else { | ||
+ | if (!alreadyDisplayed) { | ||
+ | char winner; | ||
+ | if (currentPlayer == 0) { | ||
+ | winner = 'X'; | ||
+ | } else { | ||
+ | winner = 'O'; | ||
+ | } | ||
+ | displayWinner(winner); | ||
+ | alreadyDisplayed = true; | ||
+ | currentPlayer = 0; | ||
+ | } | ||
+ | } | ||
+ | return; | ||
+ | } else { | ||
+ | readButtons(); | ||
+ | // Evidențiere celula curentă | ||
+ | highlightCell(currentRow, currentCol, true); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void readButtons() { | ||
+ | if (millis() - lastDebounceTime > debounceDelay) { | ||
+ | if (digitalRead(BUTTON_UP) == LOW) { | ||
+ | lastDebounceTime = millis(); | ||
+ | moveCursor(0, -1); // Muta sus | ||
+ | } | ||
+ | |||
+ | if (digitalRead(BUTTON_DOWN) == LOW) { | ||
+ | lastDebounceTime = millis(); | ||
+ | moveCursor(0, 1); // Muta jos | ||
+ | } | ||
+ | |||
+ | if (digitalRead(BUTTON_LEFT) == LOW) { | ||
+ | lastDebounceTime = millis(); | ||
+ | moveCursor(-1, 0); // Muta stânga | ||
+ | } | ||
+ | |||
+ | if (digitalRead(BUTTON_RIGHT) == LOW) { | ||
+ | lastDebounceTime = millis(); | ||
+ | moveCursor(1, 0); // Muta dreapta | ||
+ | } | ||
+ | |||
+ | if (digitalRead(BUTTON_SELECT) == LOW) { | ||
+ | lastDebounceTime = millis(); | ||
+ | placeMark(); // Selecteaza miscare | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void drawGrid() { | ||
+ | for (int i = 1; i < GRID_SIZE; i++) { | ||
+ | tft.drawLine(gridStartX + CELL_SIZE * i, gridStartY, gridStartX + CELL_SIZE * i, gridStartY + GRID_SIZE * CELL_SIZE, ST77XX_WHITE); | ||
+ | tft.drawLine(gridStartX, gridStartY + CELL_SIZE * i, gridStartX + GRID_SIZE * CELL_SIZE, gridStartY + CELL_SIZE * i, ST77XX_WHITE); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void highlightCell(int row, int col, bool highlight) { | ||
+ | // Curățăm celula anterioară (dacă există) | ||
+ | for (int i = 0; i < GRID_SIZE; i++) { | ||
+ | for (int j = 0; j < GRID_SIZE; j++) { | ||
+ | if (i == row && j == col) continue; | ||
+ | int x = gridStartX + j * CELL_SIZE; | ||
+ | int y = gridStartY + i * CELL_SIZE; | ||
+ | tft.drawRect(x, y, CELL_SIZE, CELL_SIZE, ST77XX_WHITE); | ||
+ | if (board[i][j] != ' ') { | ||
+ | drawMark(i, j, board[i][j]); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Evidențiem celula curentă | ||
+ | int x = gridStartX + col * CELL_SIZE; | ||
+ | int y = gridStartY + row * CELL_SIZE; | ||
+ | tft.drawRect(x, y, CELL_SIZE, CELL_SIZE, highlight ? ST77XX_BLUE : ST77XX_WHITE); | ||
+ | // Redesenăm conținutul celulei după evidențiere pentru a nu o pierde | ||
+ | if (board[row][col] != ' ') { | ||
+ | drawMark(row, col, board[row][col]); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void moveCursor(int dx, int dy) { | ||
+ | currentCol = (currentCol + dx + GRID_SIZE) % GRID_SIZE; | ||
+ | currentRow = (currentRow + dy + GRID_SIZE) % GRID_SIZE; | ||
+ | } | ||
+ | |||
+ | void placeMark() { | ||
+ | if (board[currentRow][currentCol] == ' ') { | ||
+ | char mark = currentPlayer == 0 ? 'X' : 'O'; | ||
+ | board[currentRow][currentCol] = mark; | ||
+ | drawMark(currentRow, currentCol, mark); | ||
+ | |||
+ | // Adaugă mutarea la istoricul mutărilor jucătorului curent | ||
+ | if (currentPlayer == 0) { | ||
+ | if (moveCountX >= 3) { | ||
+ | Move oldestMove = movesX[moveCountX % 3]; | ||
+ | board[oldestMove.row][oldestMove.col] = ' '; | ||
+ | drawMark(oldestMove.row, oldestMove.col, ' '); | ||
+ | } | ||
+ | movesX[moveCountX % 3] = {currentRow, currentCol}; | ||
+ | moveCountX++; | ||
+ | } else { | ||
+ | if (moveCountO >= 3) { | ||
+ | Move oldestMove = movesO[moveCountO % 3]; | ||
+ | board[oldestMove.row][oldestMove.col] = ' '; | ||
+ | drawMark(oldestMove.row, oldestMove.col, ' '); | ||
+ | } | ||
+ | movesO[moveCountO % 3] = {currentRow, currentCol}; | ||
+ | moveCountO++; | ||
+ | } | ||
+ | |||
+ | // Verifică dacă jucătorul curent a câștigat | ||
+ | if (checkWin(currentRow, currentCol, mark)) { | ||
+ | winTime = millis(); // Setează timpul de câștig | ||
+ | gameWon = true; // Setează starea de câștig | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | // Schimbă jucătorul | ||
+ | currentPlayer = 1 - currentPlayer; | ||
+ | displayCurrentPlayer(); | ||
+ | |||
+ | } | ||
+ | } | ||
+ | |||
+ | void drawMark(int row, int col, char mark) { | ||
+ | int x = gridStartX + col * CELL_SIZE + CELL_SIZE / 2 - 5; | ||
+ | int y = gridStartY + row * CELL_SIZE + CELL_SIZE / 2 - 8; | ||
+ | tft.setCursor(x, y); | ||
+ | tft.setTextColor(ST77XX_WHITE); | ||
+ | tft.setTextSize(2); | ||
+ | if (mark == ' ') { | ||
+ | // Curățăm marca | ||
+ | tft.fillRect(gridStartX + col * CELL_SIZE + 1, gridStartY + row * CELL_SIZE + 1, CELL_SIZE - 2, CELL_SIZE - 2, ST77XX_BLACK); | ||
+ | } else { | ||
+ | tft.print(mark); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | bool checkWin(int row, int col, char mark) { | ||
+ | // Verifică rândul | ||
+ | if (board[row][0] == mark && board[row][1] == mark && board[row][2] == mark) return true; | ||
+ | // Verifică coloana | ||
+ | if (board[0][col] == mark && board[1][col] == mark && board[2][col] == mark) return true; | ||
+ | // Verifică diagonalele | ||
+ | if (board[0][0] == mark && board[1][1] == mark && board[2][2] == mark) return true; | ||
+ | if (board[0][2] == mark && board[1][1] == mark && board[2][0] == mark) return true; | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | void displayWinner(char winner) { | ||
+ | tft.fillScreen(ST77XX_BLACK); | ||
+ | |||
+ | // Definim variabilele pentru a stoca limitele textului | ||
+ | int16_t x1, y1; | ||
+ | uint16_t w, h; | ||
+ | |||
+ | // Creăm textul complet care va fi afișat | ||
+ | String text = "Player "; | ||
+ | text += winner; | ||
+ | text += " won!"; | ||
+ | |||
+ | // Setăm dimensiunea textului | ||
+ | tft.setTextSize(1); | ||
+ | |||
+ | // Obținem limitele textului | ||
+ | tft.getTextBounds(text.c_str(), 0, 0, &x1, &y1, &w, &h); | ||
+ | |||
+ | // Calculăm pozițiile cursorului pentru a centra textul | ||
+ | int16_t centeredX = (tft.width() - w) / 2; | ||
+ | int16_t centeredY = (tft.height() - h) / 2; | ||
+ | |||
+ | // Setăm culoarea textului | ||
+ | tft.setTextColor(ST77XX_WHITE); | ||
+ | |||
+ | // Setăm cursorul la poziția calculată | ||
+ | tft.setCursor(centeredX, centeredY); | ||
+ | |||
+ | // Afișăm textul | ||
+ | tft.print(text); | ||
+ | |||
+ | // Coordonatele centrului smiley face-ului | ||
+ | int16_t smileyX = tft.width() / 2; | ||
+ | int16_t smileyY = centeredY / 2; // Poziționăm smiley face-ul deasupra textului | ||
+ | |||
+ | // Dimensiunea smiley face-ului | ||
+ | int16_t radius = 20; | ||
+ | |||
+ | // Desenam smiley face-ul | ||
+ | tft.drawCircle(smileyX, smileyY, radius, ST77XX_WHITE); | ||
+ | tft.fillCircle(smileyX - radius / 3, smileyY - radius / 3, 2, ST77XX_WHITE); | ||
+ | tft.fillCircle(smileyX + radius / 3, smileyY - radius / 3, 2, ST77XX_WHITE); | ||
+ | drawArc(smileyX, smileyY, radius - 5, 0, 180, ST77XX_WHITE); | ||
+ | } | ||
+ | |||
+ | void drawArc(int16_t x, int16_t y, int16_t radius, int16_t startAngle, int16_t endAngle, uint16_t color) { | ||
+ | for (int16_t angle = startAngle; angle <= endAngle; angle++) { | ||
+ | float rad = angle * 3.14 / 180; | ||
+ | int16_t x1 = x + cos(rad) * radius; | ||
+ | int16_t y1 = y + sin(rad) * radius; | ||
+ | tft.drawPixel(x1, y1, color); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void resetBoard() { | ||
+ | for (int i = 0; i < GRID_SIZE; i++) { | ||
+ | for (int j = 0; j < GRID_SIZE; j++) { | ||
+ | board[i][j] = ' '; | ||
+ | } | ||
+ | } | ||
+ | moveCountX = 0; | ||
+ | moveCountO = 0; | ||
+ | drawGrid(); | ||
+ | highlightCell(currentRow, currentCol, true); | ||
+ | } | ||
+ | |||
+ | void displayCurrentPlayer() { | ||
+ | // Șterge zona jucătorului curent | ||
+ | tft.fillRect(0, GRID_SIZE * CELL_SIZE + gridStartY, tft.width(), 20, ST77XX_BLACK); | ||
+ | |||
+ | // Definim variabilele pentru a stoca limitele textului | ||
+ | int16_t x1, y1; | ||
+ | uint16_t w, h; | ||
+ | |||
+ | // Creăm textul complet care va fi afișat | ||
+ | String text = "Player: "; | ||
+ | text += (currentPlayer == 0) ? 'X' : 'O'; | ||
+ | |||
+ | // Setăm dimensiunea textului | ||
+ | tft.setTextSize(1); | ||
+ | |||
+ | // Obținem limitele textului | ||
+ | tft.getTextBounds(text.c_str(), 0, 0, &x1, &y1, &w, &h); | ||
+ | |||
+ | // Calculăm pozițiile cursorului pentru a centra textul | ||
+ | int16_t centeredX = (tft.width() - w) / 2; | ||
+ | int16_t centeredY = gridStartY + GRID_SIZE * CELL_SIZE + 5; | ||
+ | |||
+ | // Setăm cursorul la poziția calculată | ||
+ | tft.setCursor(centeredX, centeredY); | ||
+ | |||
+ | // Setăm culoarea textului | ||
+ | tft.setTextColor(ST77XX_WHITE); | ||
+ | |||
+ | // Afișăm textul | ||
+ | tft.print(text); | ||
+ | } | ||
+ | |||
+ | </file> | ||
+ | |||
+ | Codul pentru muzica: \\ | ||
+ | <file music> | ||
+ | #include <SPI.h> | ||
+ | #include <SD.h> | ||
+ | #include <TMRpcm.h> | ||
+ | |||
+ | #define CS_SD 4 | ||
+ | #define Speaker 9 | ||
+ | TMRpcm audio; | ||
+ | |||
+ | void setup() { | ||
+ | Serial.begin(9600); | ||
+ | |||
+ | if(!SD.begin(CS_SD)) { | ||
+ | Serial.println("A esuat initializarea cardului SD"); | ||
+ | while(1); | ||
+ | } | ||
+ | |||
+ | audio.CSPin = CS_SD; | ||
+ | audio.speakerPin = Speaker; | ||
+ | audio.setVolume(6); | ||
+ | |||
+ | } | ||
+ | |||
+ | void loop() { | ||
+ | if (!audio.isPlaying()) { | ||
+ | audio.play("powerup.wav"); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | </file> | ||
+ | ===== Rezultate Obţinute ===== | ||
+ | |||
+ | |||
+ | {{:pm:prj2024:ddosaru:1-zalook.jpg?200|}} | ||
+ | |||
+ | Acesta este rezultatul final al proiectului. Am facut cutia astfel incat sa se poata deschida pentru a se vedea cablajul. LCD-ul si butoanele se pot inlatura sau baga in cutie. | ||
+ | <html> | ||
+ | <iframe width="700" height="435" src="https://youtube.com/embed/kd_nhWqsvPM?feature=share" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> | ||
+ | </html> |