아두이노 조이스틱

이 튜토리얼에서는 아두이노와 조이스틱을 사용하는 방법을 배울 예정입니다. 자세히는 다음을 배울 것입니다:

조이스틱 사용법 핀아웃

준비물

1×아두이노 우노 R3 쿠팡 | 아마존
1×USB 2.0 타입 A-to-B 케이블 (USB-A PC용) 쿠팡 | 아마존
1×USB 2.0 타입 C-to-B 케이블 (USB-C PC용) 아마존
1×조이스틱 (Joystick) 쿠팡 | 아마존
1×점퍼케이블 쿠팡 | 아마존
1×(추천) 아두이노 우노용 스크루 터미널 블록 쉴드 쿠팡 | 아마존
1×(추천) Sensors/Servo Expansion Shield for Arduino Uno 쿠팡 | 아마존
1×(추천) 아두이노 우노용 브레드보드 쉴드 쿠팡 | 아마존
1×(추천) 아두이노 우노용 케이스 쿠팡 | 아마존
1×(추천) 아두이노 우노용 프로토타이핑 베이스 플레이트 & 브레드보드 키트 아마존
공개: 이 포스팅 에 제공된 일부 링크는 아마존 제휴 링크입니다. 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

조이스틱 센서에 관하여

당신은 아마 조이스틱을 게임 컨트롤러, 장난감 컨트롤러 또는 실제 대형 기계 예를 들어 굴착기 컨트롤러와 같은 곳에서 본 적이 있을 것입니다.

조이스틱은 서로 직각을 이루는 두 개의 포텐쇼미터와 하나의 푸시 버튼으로 구성되어 있습니다. 따라서, 다음과 같은 출력을 제공합니다:

  • 수평 위치(‘X-좌표’라고 함)에 해당하는 아날로그 값(0에서 1023까지)
  • 수직 위치(‘Y-좌표’라고 함)에 해당하는 아날로그 값(0에서 1023까지)
  • 푸시버튼의 디지털 값(HIGH 또는 LOW)

두 아날로그 값의 조합은 조이스틱이 휴식 상태일 때 중심이 값인 2차원 좌표를 만들 수 있습니다. 좌표의 실제 방향은 다음 부분에서 테스트 코드를 실행할 때 간단히 식별할 수 있습니다.

어떤 애플리케이션은 세 가지 출력 모두를 사용할 수 있고, 어떤 애플리케이션은 세 가지 출력 중 일부를 사용할 수 있습니다.

핀배열

조이스틱은 5개의 핀을 가지고 있습니다:

  • GND 핀: GND(0V)에 연결해야 합니다.
  • VCC 핀: VCC(5V)에 연결해야 합니다.
  • VRX 핀: 수평 위치(이것을 X-좌표라고 함)에 해당하는 아날로그 값을 출력합니다.
  • VRY 핀: 수직 위치(이것을 Y-좌표라고 함)에 해당하는 아날로그 값을 출력합니다.
  • SW 핀: 조이스틱 내부의 푸시버튼에서 나오는 출력입니다. 보통 개방 상태입니다. 이 핀에 풀업 저항을 사용하면, SW 핀은 눌리지 않았을 때 HIGH가 되고, 눌렸을 때는 LOW가 됩니다.
조이스틱 사용법 핀아웃

작동 방식

  • 조이스틱의 엄지를 왼쪽/오른쪽으로 밀면 VRX 핀의 전압이 변화합니다. 전압 범위는 0에서 5V까지입니다 (왼쪽에서 0, 오른쪽에서 5V). 전압 값은 엄지의 위치에 비례합니다 ⇒ 아두이노의 아날로그 핀에서 읽는 값은 0에서 1023까지입니다.
  • 조이스틱의 엄지를 위/아래로 밀면 VRY 핀의 전압이 변화합니다. 전압 범위는 0에서 5V까지입니다 (위에서 0, 아래에서 5V). 전압 값은 엄지의 위치에 비례합니다 ⇒ 아두이노의 아날로그 핀에서 읽는 값은 0에서 1023까지입니다.
  • 조이스틱의 엄지를 어느 방향으로든 밀면 VRX와 VRY 핀의 전압이 각 축에 대한 위치의 투영에 비례하여 변화합니다.
  • 조이스틱의 엄지를 위에서 아래로 밀면 조이스틱 내부의 푸시버튼이 닫힙니다. SW 핀에 풀업 저항을 사용하면 SW 핀에서의 출력이 5V에서 0V로 변경됩니다 ⇒ 아두이노의 디지털 핀에서 읽는 값이 HIGH에서 LOW로 변경됩니다.

선연결

아두이노 조이스틱 연결 배선도

이 이미지는 Fritzing을 사용하여 만들어졌습니다. 이미지를 확대하려면 클릭하세요.

조이스틱 프로그래밍 방법

조이스틱은 두 부분으로 구성됩니다: 아날로그(가로축, 세로축)와 디지털(버튼 누름)

아날로그 부분(X, Y 축)의 경우, analogRead() 함수를 사용하여 아날로그 입력 핀에서 값을 읽기만 하면 됩니다.

int xValue = analogRead(A0); int yValue = analogRead(A1);

디지털 부분(푸시버튼)에 대하여: 이것은 버튼입니다. 가장 간단하고 편리한 방법은 ezButton 라이브러리를 사용하는 것입니다. 이 라이브러리는 버튼의 디바운스를 지원하며 내부 풀업 저항을 활성화할 수 있습니다. 버튼에 대해 더 알아보려면 아두이노 - 버튼을 참고하십시오. 이 튜토리얼의 다음 세션에서 코드가 소개될 예정입니다.

아날로그 핀에서 값을 읽은 후, 우리는 그것들을 어떤 조절 가능한 값으로 변환해야 할 수도 있습니다. 다음 파트에서는 이를 위한 예제 코드를 제공할 것입니다.

아두이노 코드

이 섹션은 다음 아두이노 예제 코드를 제공할 것입니다:

  • 예제 코드: 조이스틱에서 아날로그 값을 읽음
  • 예제 코드: 조이스틱에서 아날로그 값과 버튼 상태를 읽음
  • 예제 코드: 아날로그 값을 MOVE_LEFT, MOVE_RIGHT, MOVE_UP, MOVE_DOWN 명령어로 변환함
  • 예제 코드: 아날로그 값을 각도로 변환하여 두 개의 서보 모터를 제어함 (예: 팬-틸트 카메라)

조이스틱에서 아날로그 값을 읽습니다.

/* * 이 아두이노 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-joystick */ #define VRX_PIN A0 // Arduino 핀이 VRX 핀에 연결됨 #define VRY_PIN A1 // Arduino 핀이 VRY 핀에 연결됨 int xValue = 0; // X 축의 값을 저장하기 위함 int yValue = 0; // Y 축의 값을 저장하기 위함 void setup() { Serial.begin(9600) ; } void loop() { // 아날로그 X 및 Y 아날로그 값을 읽음 xValue = analogRead(VRX_PIN); yValue = analogRead(VRY_PIN); // 아두이노 IDE의 시리얼 모니터에 데이터 출력 Serial.print("x = "); Serial.print(xValue); Serial.print(", y = "); Serial.println(yValue); delay(200); }

사용 방법

  • 위의 코드를 복사하여 아두이노 IDE로 열기
  • 아두이노 IDE에서 Upload 버튼을 클릭하여 코드를 아두이노에 업로드하기
  • 조이스틱의 엄지를 최대한 끝까지 밀고, 그 다음에 시계 방향이나 반시계 방향으로 회전하기
  • 결과를 시리얼 모니터에서 확인하기
Newbiely | Arduino IDE 2.3.8
──
File
Edit
Sketch
Tools
Help
Arduino Uno
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'Arduino Uno' on 'COM15')
New Line
9600 baud
Ln 11, Col 1
Arduino Uno on COM15
2

조이스틱의 손가락을 돌리면서 시리얼 모니터를 계속해서 지켜보세요.

  • X 값이 0이면, 현재 위치를 왼쪽으로 표시하거나 기억하세요 ⇒ 반대 방향은 오른쪽입니다.
  • Y 값이 0이면, 현재 위치를 위로 표시하거나 기억하세요 ⇒ 반대 방향은 아래입니다.

조이스틱에서 아날로그 값을 읽고 버튼 상태를 읽습니다.

/* * 이 아두이노 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-joystick */ #include <ezButton.h> #define VRX_PIN A0 // Arduino 핀이 VRX 핀에 연결됨 #define VRY_PIN A1 // Arduino 핀이 VRY 핀에 연결됨 #define SW_PIN 2 // Arduino 핀이 SW 핀에 연결됨 ezButton button(SW_PIN); int xValue = 0; // X 축의 값을 저장하기 위함 int yValue = 0; // Y 축의 값을 저장하기 위함 int bValue = 0; // 버튼의 값을 저장하기 위함 void setup() { Serial.begin(9600) ; button.setDebounceTime(50); // 50밀리초로 디바운스 시간 설정 } void loop() { button.loop(); // MUST call the loop() function first // 아날로그 X와 Y 아날로그 값을 읽기 xValue = analogRead(VRX_PIN); yValue = analogRead(VRY_PIN); // 버튼 값 읽기 bValue = button.getState(); if (button.isPressed()) { Serial.println("The button is pressed"); // TODO 여기서 무언가 수행하기 } if (button.isReleased()) { Serial.println("The button is released"); // TODO 여기서 무언가 수행하기 } // 아두이노 IDE의 시리얼 모니터에 데이터 출력 Serial.print("x = "); Serial.print(xValue); Serial.print(", y = "); Serial.print(yValue); Serial.print(" : button = "); Serial.println(bValue); }

사용 방법

  • Arduino IDE의 왼쪽 바에서 Libraries 아이콘으로 이동하세요.
  • “ezButton”을 검색한 다음, ArduinoGetStarted.com에서 버튼 라이브러리 찾아보세요.
  • EzButton 라이브러리를 설치하려면 Install 버튼을 클릭하세요.
아두이노 버튼 라이브러리
  • 위의 코드를 복사하고 아두이노 IDE로 열기
  • 아두이노 IDE에서 Upload 버튼을 클릭하여 코드를 아두이노에 업로드하기
  • 조이스틱의 엄지를 왼쪽/오른쪽/위/아래로 밀기
  • 조이스틱의 엄지를 위에서 아래로 밀기
  • 시리얼 모니터에서 결과 보기
Newbiely | Arduino IDE 2.3.8
──
File
Edit
Sketch
Tools
Help
Arduino Uno
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'Arduino Uno' on 'COM15')
New Line
9600 baud
Ln 11, Col 1
Arduino Uno on COM15
2

아날로그 값을 왼쪽/오른쪽/위/아래로 이동하는 명령어로 변환합니다

/* * 이 아두이노 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-joystick */ #define VRX_PIN A0 // Arduino 핀이 VRX 핀에 연결됨 #define VRY_PIN A1 // Arduino 핀이 VRY 핀에 연결됨 #define LEFT_THRESHOLD 400 #define RIGHT_THRESHOLD 800 #define UP_THRESHOLD 400 #define DOWN_THRESHOLD 800 #define COMMAND_NO 0x00 #define COMMAND_LEFT 0x01 #define COMMAND_RIGHT 0x02 #define COMMAND_UP 0x04 #define COMMAND_DOWN 0x08 int xValue = 0 ; // X 축의 값을 저장하기 위함 int yValue = 0 ; // Y 축의 값을 저장하기 위함 int command = COMMAND_NO; void setup() { Serial.begin(9600) ; } void loop() { // 아날로그 X 및 Y 아날로그 값을 읽음 xValue = analogRead(VRX_PIN); yValue = analogRead(VRY_PIN); // 아날로그 값을 명령으로 변환 // 명령 초기화 command = COMMAND_NO; // 좌/우 명령 확인 if (xValue < LEFT_THRESHOLD) command = command | COMMAND_LEFT; else if (xValue > RIGHT_THRESHOLD) command = command | COMMAND_RIGHT; // 상/하 명령 확인 if (yValue < UP_THRESHOLD) command = command | COMMAND_UP; else if (yValue > DOWN_THRESHOLD) command = command | COMMAND_DOWN; // 주의: 한 번에 명령이 없거나, 하나의 명령이 있거나, 두 개의 명령이 있을 수 있음 // 명령을 시리얼로 출력하고 명령 처리 if (command & COMMAND_LEFT) { Serial.println("COMMAND LEFT"); // 할 일을 여기에 추가 } if (command & COMMAND_RIGHT) { Serial.println("COMMAND RIGHT"); // 할 일을 여기에 추가 } if (command & COMMAND_UP) { Serial.println("COMMAND UP"); // 할 일을 여기에 추가 } if (command & COMMAND_DOWN) { Serial.println("COMMAND DOWN"); // 할 일을 여기에 추가 } }

사용 방법

  • 위의 코드를 복사하고 Arduino IDE로 열기
  • Arduino IDE에서 Upload 버튼을 클릭하여 코드를 Arduino에 업로드하십시오
  • 조이스틱의 썸을 왼쪽/오른쪽/위/아래 또는 어느 방향으로든 밀어 넣으십시오
  • 시리얼 모니터에서 결과를 확인하십시오.
Newbiely | Arduino IDE 2.3.8
──
File
Edit
Sketch
Tools
Help
Arduino Uno
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'Arduino Uno' on 'COM15')
New Line
9600 baud
Ln 11, Col 1
Arduino Uno on COM15
2

※ 주의:

한 시점에는 명령이 없거나, 하나의 명령 또는 두 개의 명령(예: 동시에 UP과 LEFT)이 있을 수 있습니다.

아날로그 값을 각도로 변환하여 두 개의 서보 모터를 제어합니다.

상세내용은 아두이노 - 조이스틱 - 서보 모터 튜토리얼에서 확인할 수 있습니다.

동영상

비디오 제작은 시간이 많이 걸리는 작업입니다. 비디오 튜토리얼이 학습에 도움이 되었다면, YouTube 채널 을 구독하여 알려 주시기 바랍니다. 비디오에 대한 높은 수요가 있다면, 비디오를 만들기 위해 노력하겠습니다.

아래 동영상 데모는 아래 코드를 사용합니다. 동영상은 Arduino Uno R4를 보여주지만 Arduino Uno R3에서도 동일하게 작동합니다:

/* * 이 아두이노 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino/arduino-joystick */ #include "Arduino_LED_Matrix.h" // Pin definitions #define PIN_VRX A1 #define PIN_VRY A0 #define PIN_SW 2 #define PIN_BUZZER 8 // Matrix display dimensions #define MATRIX_WIDTH 12 #define MATRIX_HEIGHT 8 #define MAX_SNAKE_LENGTH 96 // Snake movement direction enum Direction { UP, DOWN, LEFT, RIGHT }; // Game state machine enum GameState { START_BOOT, // Initial boot screen scrolling "DIYables" PLAYING, // Active gameplay GAME_OVER // Game over score display }; GameState currentState = START_BOOT; ArduinoLEDMatrix matrix; uint8_t frame[8][12]; // Snake properties int snakeX[MAX_SNAKE_LENGTH]; int snakeY[MAX_SNAKE_LENGTH]; int snakeLength = 3; int score = 0; Direction dir = RIGHT; Direction nextDir = RIGHT; // Food coordinates int foodX = 0; int foodY = 0; // Movement timing unsigned long lastMoveTime = 0; int moveInterval = 250; // Text scrolling timing int startTextX = 12; int scoreTextX = 12; unsigned long lastScrollTime = 0; const int SCROLL_INTERVAL = 75; // Physics particle structure for 3s fall effect struct Particle { float x; float y; float vx; float vy; bool active; }; // Function prototypes void resetGame(); void handleInput(); void updateGameLogic(); void renderGame(); void spawnFood(); void renderText(String text, int xOffset); uint8_t getCharCol(char c, int col); void playShootingStarEffect(); void playEatSound(); void playStartSound(); bool isButtonPressed(); void clearFrame(); void setup() { pinMode(PIN_SW, INPUT_PULLUP); pinMode(PIN_BUZZER, OUTPUT); matrix.begin(); randomSeed(analogRead(A2)); } void loop() { switch (currentState) { case START_BOOT: // Show "DIYables" only on initial power-up if (millis() - lastScrollTime >= SCROLL_INTERVAL) { lastScrollTime = millis(); renderText("DIYables", startTextX); startTextX--; if (startTextX < -36) { startTextX = 12; } } if (isButtonPressed()) { resetGame(); playStartSound(); currentState = PLAYING; } break; case PLAYING: handleInput(); updateGameLogic(); renderGame(); break; case GAME_OVER: // Display score ("Score: X") if (millis() - lastScrollTime >= SCROLL_INTERVAL) { lastScrollTime = millis(); String scoreStr = "Score: " + String(score); renderText(scoreStr, scoreTextX); scoreTextX--; int strLen = scoreStr.length() * 4; if (scoreTextX < -strLen) { scoreTextX = 12; } } // Press button to restart game directly if (isButtonPressed()) { resetGame(); playStartSound(); currentState = PLAYING; } break; } } // Read Joystick axes independently for accurate 4-way directional control void handleInput() { int xVal = analogRead(PIN_VRX); int yVal = analogRead(PIN_VRY); int dx = xVal - 512; int dy = yVal - 512; // Prioritize axis with stronger deflection if (abs(dx) > abs(dy)) { if (dx < -200 && dir != RIGHT) nextDir = LEFT; else if (dx > 200 && dir != LEFT) nextDir = RIGHT; } else { if (dy < -200 && dir != DOWN) nextDir = UP; else if (dy > 200 && dir != UP) nextDir = DOWN; } } // 3.0-second shooting star fall effect void playShootingStarEffect() { const int MAX_PARTICLES = 45; Particle particles[MAX_PARTICLES]; int pCount = 0; // Convert active LEDs (snake body and food) into free-falling particles for (int r = 0; r < MATRIX_HEIGHT; r++) { for (int c = 0; c < MATRIX_WIDTH; c++) { if (frame[r][c] == 1 && pCount < 25) { particles[pCount].x = c; particles[pCount].y = r; particles[pCount].vx = (float)random(-15, 15) / 100.0; particles[pCount].vy = (float)random(-20, 5) / 100.0; particles[pCount].active = true; pCount++; } } } unsigned long startTime = millis(); unsigned long lastSpawn = 0; // 3-second physics simulation loop while (millis() - startTime < 3000) { clearFrame(); unsigned long elapsedTime = millis() - startTime; // Spawn additional falling star trails from top during the first 2.2 seconds if (elapsedTime < 2200 && millis() - lastSpawn > 80 && pCount < MAX_PARTICLES) { lastSpawn = millis(); particles[pCount].x = random(0, MATRIX_WIDTH); particles[pCount].y = -random(1, 4); particles[pCount].vx = (float)random(-5, 5) / 100.0; particles[pCount].vy = (float)random(10, 30) / 100.0; particles[pCount].active = true; pCount++; } // Update particle positions for (int i = 0; i < pCount; i++) { if (!particles[i].active) continue; particles[i].x += particles[i].vx; particles[i].y += particles[i].vy; particles[i].vy += 0.04; // Gravity acceleration int px = (int)round(particles[i].x); int py = (int)round(particles[i].y); // Draw particle if (py >= 0 && py < MATRIX_HEIGHT && px >= 0 && px < MATRIX_WIDTH) { frame[py][px] = 1; } // Draw particle trail if falling rapidly int tailY = py - 1; if (tailY >= 0 && tailY < MATRIX_HEIGHT && px >= 0 && px < MATRIX_WIDTH && particles[i].vy > 0.3) { frame[tailY][px] = 1; } // Deactivate particle once below screen if (py >= MATRIX_HEIGHT + 3) { particles[i].active = false; } } matrix.renderBitmap(frame, 8, 12); // Decreasing pitch sound from 1200Hz to 100Hz synchronized over 3 seconds int soundFreq = 1200 - (int)((elapsedTime / 3000.0) * 1100); if (soundFreq > 100) { tone(PIN_BUZZER, soundFreq, 30); } delay(35); } noTone(PIN_BUZZER); clearFrame(); matrix.renderBitmap(frame, 8, 12); } // 3x5 bitmap font data uint8_t getCharCol(char c, int col) { switch (c) { // "DIYables" characters case 'D': return (col==0)?0x1F:(col==1)?0x11:0x0E; case 'I': return (col==0)?0x11:(col==1)?0x1F:0x11; case 'Y': return (col==0)?0x03:(col==1)?0x1C:0x03; case 'a': return (col==0)?0x0C:(col==1)?0x12:0x1E; case 'b': return (col==0)?0x1F:(col==1)?0x14:0x08; case 'l': return (col==0)?0x11:(col==1)?0x1F:0x10; case 'e': return (col==0)?0x0C:(col==1)?0x16:0x14; case 's': return (col==0)?0x14:(col==1)?0x16:0x0A; // "Score:" characters case 'S': return (col==0)?0x12:(col==1)?0x15:0x09; case 'c': return (col==0)?0x0C:(col==1)?0x12:0x12; case 'o': return (col==0)?0x0C:(col==1)?0x12:0x0C; case 'r': return (col==0)?0x1E:(col==1)?0x04:0x02; case ':': return (col==0)?0x00:(col==1)?0x0A:0x00; case ' ': return 0x00; // Digits 0 - 9 case '0': return (col==0)?0x0E:(col==1)?0x11:0x0E; case '1': return (col==0)?0x12:(col==1)?0x1F:0x10; case '2': return (col==0)?0x19:(col==1)?0x15:0x12; case '3': return (col==0)?0x11:(col==1)?0x15:0x0A; case '4': return (col==0)?0x07:(col==1)?0x04:0x1F; case '5': return (col==0)?0x17:(col==1)?0x15:0x09; case '6': return (col==0)?0x0E:(col==1)?0x15:0x09; case '7': return (col==0)?0x19:(col==1)?0x05:0x03; case '8': return (col==0)?0x0A:(col==1)?0x15:0x0A; case '9': return (col==0)?0x12:(col==1)?0x15:0x0E; default: return 0x00; } } // Render string to LED matrix frame void renderText(String text, int xOffset) { clearFrame(); int curX = xOffset; int topY = 1; for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); for (int col = 0; col < 3; col++) { int screenX = curX + col; if (screenX >= 0 && screenX < MATRIX_WIDTH) { uint8_t colData = getCharCol(ch, col); for (int row = 0; row < 5; row++) { if ((colData >> row) & 1) { frame[topY + row][screenX] = 1; } } } } curX += 4; } matrix.renderBitmap(frame, 8, 12); } // Snake game logic update void updateGameLogic() { if (millis() - lastMoveTime < moveInterval) return; lastMoveTime = millis(); dir = nextDir; int newHeadX = snakeX[0]; int newHeadY = snakeY[0]; switch (dir) { case UP: newHeadY--; break; case DOWN: newHeadY++; break; case LEFT: newHeadX--; break; case RIGHT: newHeadX++; break; } // Wall collision -> Game Over if (newHeadX < 0 || newHeadX >= MATRIX_WIDTH || newHeadY < 0 || newHeadY >= MATRIX_HEIGHT) { playShootingStarEffect(); scoreTextX = 12; currentState = GAME_OVER; return; } // Self-collision -> Game Over for (int i = 0; i < snakeLength; i++) { if (snakeX[i] == newHeadX && snakeY[i] == newHeadY) { playShootingStarEffect(); scoreTextX = 12; currentState = GAME_OVER; return; } } bool ateFood = (newHeadX == foodX && newHeadY == foodY); if (ateFood) { snakeLength++; score++; playEatSound(); if (moveInterval > 100) { moveInterval -= 5; } } for (int i = snakeLength - 1; i > 0; i--) { snakeX[i] = snakeX[i - 1]; snakeY[i] = snakeY[i - 1]; } snakeX[0] = newHeadX; snakeY[0] = newHeadY; if (ateFood) { spawnFood(); } } void renderGame() { clearFrame(); frame[foodY][foodX] = 1; for (int i = 0; i < snakeLength; i++) { frame[snakeY[i]][snakeX[i]] = 1; } matrix.renderBitmap(frame, 8, 12); } void spawnFood() { bool onSnake; do { onSnake = false; foodX = random(0, MATRIX_WIDTH); foodY = random(0, MATRIX_HEIGHT); for (int i = 0; i < snakeLength; i++) { if (snakeX[i] == foodX && snakeY[i] == foodY) { onSnake = true; break; } } } while (onSnake); } void resetGame() { snakeLength = 3; score = 0; dir = RIGHT; nextDir = RIGHT; moveInterval = 250; snakeX[0] = 4; snakeY[0] = 3; snakeX[1] = 3; snakeY[1] = 3; snakeX[2] = 2; snakeY[2] = 3; spawnFood(); } bool isButtonPressed() { static bool lastState = HIGH; bool state = digitalRead(PIN_SW); if (lastState == HIGH && state == LOW) { delay(50); lastState = state; return true; } lastState = state; return false; } void clearFrame() { memset(frame, 0, sizeof(frame)); } void playEatSound() { tone(PIN_BUZZER, 1200, 60); } void playStartSound() { tone(PIN_BUZZER, 440, 80); delay(90); tone(PIN_BUZZER, 554, 80); delay(90); tone(PIN_BUZZER, 659, 120); delay(120); }