아두이노 우노 R4 조이스틱

이 가이드에서는 Arduino UNO R4와 함께 조이스틱을 사용하는 방법을 배웁니다. 우리는 다음을 다룰 것입니다:

아두이노 우노 R4 및 조이스틱

준비물

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

조이스틱에 대하여

게임 컨트롤러, 장난감 컨트롤러 또는 굴착기 컨트롤러와 같은 대형 기계에서 사용되는 조이스틱을 본 적이 있을 것입니다.

조이스틱에는 정사각형 배열로 배치된 두 개의 포텐셔미터와 하나의 푸시 버튼이 있습니다. 다음과 같은 출력을 제공합니다:

  • 수평 위치(X-좌표)에 대한 아날로그 값(0에서 1023)
  • 수직 위치(Y-좌표)에 대한 아날로그 값(0에서 1023)
  • 버튼의 디지털 상태(HIGH 또는 LOW)

두 개의 아날로그 값을 결합하면 2차원 좌표가 형성됩니다. 조이스틱이 움직이지 않을 때 이 좌표들은 중심에 위치합니다. 테스트 코드를 실행하여 이러한 좌표의 실제 방향을 쉽게 파악할 수 있으며, 이는 다음 섹션에서 다룰 예정입니다.

일부 애플리케이션은 세 가지 출력 모두를 사용할 수 있는 반면, 다른 애플리케이션은 일부만 사용할 수 있습니다.

핀아웃

조이스틱은 다섯 개의 핀을 사용합니다.

  • GND 핀: GND(0V)에 연결
  • VCC 핀: VCC(5V)에 연결
  • VRX 핀: 가로 위치(X좌표)에 대한 아날로그 값을 제공합니다.
  • VRY 핀: 세로 위치(Y좌표)에 대한 아날로그 값을 제공합니다.
  • SW 핀: 조이스틱의 푸시버튼에서 나옵니다. 일반적으로 열려 있습니다. 풀업 저항기를 사용하면 누르지 않았을 때 이 핀이 HIGH가 되고, 눌렀을 때 LOW가 됩니다.
조이스틱 핀아웃

작동 방식

  • 좌/우 움직임
    • 조이스틱을 좌우로 움직이면 VRX 핀의 전기 신호(전압)가 변경됩니다.
    • 왼쪽으로 움직이면 전압이 0볼트로 낮아집니다.
    • 오른쪽으로 움직이면 전압이 5볼트로 증가합니다.
    • 아두이노는 이 전압을 읽고 0에서 1023 사이의 숫자로 변환합니다. 따라서 0V는 0에 해당하고, 5V는 1023에 해당합니다.
  • 위/아래 움직임:
    • 마찬가지로, 조이스틱을 위아래로 움직이면 VRY 핀의 전압이 변경됩니다.
    • 위로 움직이면 전압이 0볼트로 낮아집니다.
    • 아래로 움직이면 전압이 5볼트로 증가합니다.
    • 아두이노는 이 전압도 읽고 VRX 핀과 마찬가지로 0에서 1023 사이의 숫자로 변환합니다.
  • 결합된 움직임:
    • 조이스틱을 어떤 방향으로 움직이든지(단순히 좌/우 또는 위/아래뿐만 아니라) 각 축에 따라 VRX 및 VRY 핀의 전압이 변경됩니다.
  • 조이스틱 누르기:
    • 조이스틱을 아래로 누르면(버튼을 누르는 것과 같이) 조이스틱 내부의 내장 버튼이 작동됩니다.
    • SW 핀에 풀업 저항을 연결하면 눌렀을 때 이 핀의 전압이 5V에서 0V로 변경됩니다.
    • 아두이노는 이 변화를 디지털 신호로 인식하며, 누르지 않았을 때는 HIGH (5V), 눌렀을 때는 LOW (0V)로 표시됩니다.

    요약하면, 조이스틱을 움직이면 VRX 및 VRY의 전압이 변경되며, 이는 Arduino가 0과 1023 사이의 숫자로 읽습니다. 조이스틱을 누르면 SW 핀의 전압이 변경되며, 이는 Arduino가 HIGH 또는 LOW로 읽습니다.

선연결

아두이노 우노 R4 조이스틱 배선도

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

모듈 전선과 굵은 전선은 암 헤더에 잘 고정되지 않는 경우가 많습니다. Arduino UNO R4에 Proto Screw Terminal Block Shield 또는 Screw Shield V2를 추가하기만 하면 됩니다. 아래 다이어그램은 Screw Shield V2를 사용한 같은 배선을 보여 줍니다.

아두이노 우노 R4 조이스틱 배선도 (screw shield v2)

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

Arduino Uno R4와 기타 부품에 전원을 공급하는 가장 효과적인 방법을 확인하시려면, 아래 링크를 참조하세요: 아두이노 우노 R4 전원 공급 방법.

조이스틱 프로그래밍 방법

조이스틱은 두 개의 구성 요소로 이루어져 있습니다: 아날로그 부분(X 및 Y 축)과 디지털 부분(푸시 버튼).

  • X축 및 Y축의 값을 읽으려면 아날로그 입력 핀에서 analogRead() 함수를 사용하면 됩니다.
int xValue = analogRead(A1); int yValue = analogRead(A0);
  • 디지털 부품 중 푸시버튼 부분: 그냥 버튼입니다. 쉽고 효과적인 방법은 ezButton 라이브러리를 사용하는 것입니다. 이 라이브러리는 버튼의 안정성을 관리하고 버튼을 안정적으로 유지하기 위한 내부 시스템도 활성화합니다. 아두이노 우노 R4 - 버튼에서 더 많은 정보를 배울 수 있습니다. 이 튜토리얼의 다음 부분에서는 코드 사용 방법을 보여드리겠습니다.

아날로그 핀에서 값을 얻은 후에는 그 값을 관리하기 쉬운 값으로 변경해야 할 수도 있습니다. 다음 섹션에서는 이에 대한 예제 코드를 제공합니다.

아두이노 UNO R4 코드

다음은 Arduino UNO R4의 예제 코드입니다:

  • 예제 코드: 조이스틱에서 아날로그 값을 가져오기
  • 예제 코드: 아날로그 값을 가져오고 조이스틱의 버튼 상태 확인
  • 예제 코드: 아날로그 값을 명령으로 변경: MOVE_LEFT, MOVE_RIGHT, MOVE_UP, MOVE_DOWN
  • 예제 코드: 아날로그 값을 각도로 변환하여 두 개의 서보 모터 제어 (팬-틸트 카메라처럼)

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

/* * 이 아두이노 우노 R4 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 우노 R4 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino-uno-r4/arduino-uno-r4-joystick */ #define VRX_PIN A1 // The Arduino UNO R4 pin connected to VRX pin #define VRY_PIN A0 // The Arduino UNO R4 pin connected to VRY pin int xValue = 0; // To store value of the X axis int yValue = 0; // To store value of the Y axis void setup() { Serial.begin(9600) ; } void loop() { // read analog X and Y analog values xValue = analogRead(VRX_PIN); yValue = analogRead(VRY_PIN); // print data to Serial Monitor on Arduino IDE Serial.print("x = "); Serial.print(xValue); Serial.print(", y = "); Serial.println(yValue); delay(200); }

자세한 사용 방법

다음 지침을 단계별로 따르십시오:

  • Arduino Uno R4 WiFi/Minima를 처음 사용하는 경우, 아두이노 우노 R4 - 소프트웨어 설치 튜토리얼을 참조하십시오.
  • 주어진 다이어그램에 따라 조이스틱을 Arduino UNO R4에 연결하십시오.
  • USB 케이블을 사용하여 Arduino Uno R4 보드를 컴퓨터에 연결하십시오.
  • 컴퓨터에서 Arduino IDE를 실행하십시오.
  • 적절한 Arduino Uno R4 보드(예: Arduino Uno R4 WiFi) 및 COM 포트를 선택하십시오.
  • 위의 코드를 복사하여 Arduino IDE에서 엽니다.
  • Arduino IDE에서 Upload 버튼을 클릭하여 코드를 Arduino UNO R4에 업로드하십시오.
  • 조이스틱의 엄지스틱을 끝까지 밀고, 원형으로(시계 방향 또는 반시계 방향) 돌리십시오.
  • 시리얼 모니터에서 결과를 확인하십시오.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
Arduino Uno R4 WiFi
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'Arduino Uno R4 WiFi' on 'COM15')
New Line
9600 baud
Ln 11, Col 1
Arduino Uno R4 WiFi on COM15
2
  • 조이스틱을 돌릴 때, 시리얼 모니터를 주시하세요. X 값이 0이면 이 위치를 왼쪽으로 기억하고, 반대는 오른쪽입니다. Y 값이 0이면 이 위치를 위쪽으로 기억하고, 반대는 아래쪽입니다.

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

/* * 이 아두이노 우노 R4 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 우노 R4 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino-uno-r4/arduino-uno-r4-joystick */ #include <ezButton.h> #define VRX_PIN A1 // The Arduino UNO R4 pin connected to VRX pin #define VRY_PIN A0 // The Arduino UNO R4 pin connected to VRY pin #define SW_PIN 2 // The Arduino UNO R4 pin connected to SW pin ezButton button(SW_PIN); int xValue = 0; // To store value of the X axis int yValue = 0; // To store value of the Y axis int bValue = 0; // To store value of the button void setup() { Serial.begin(9600) ; button.setDebounceTime(50); // set debounce time to 50 milliseconds } void loop() { button.loop(); // MUST call the loop() function first // read analog X and Y analog values xValue = analogRead(VRX_PIN); yValue = analogRead(VRY_PIN); // Read the button value bValue = button.getState(); if (button.isPressed()) { Serial.println("The button is pressed"); // TODO do something here } if (button.isReleased()) { Serial.println("The button is released"); // TODO do something here } // print data to Serial Monitor on Arduino 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 버튼을 누르십시오.
아두이노 우노 R4 버튼 라이브러리
  • 코드를 복사하여 Arduino IDE에서 엽니다.
  • Arduino IDE에서 Upload 버튼을 클릭하여 코드를 Arduino UNO R4에 업로드합니다.
  • 조이스틱 엄지패드를 왼쪽, 오른쪽, 위쪽 또는 아래쪽으로 이동합니다.
  • 조이스틱의 상단을 누릅니다.
  • 시리얼 모니터에서 결과를 확인합니다.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
Arduino Uno R4 WiFi
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'Arduino Uno R4 WiFi' on 'COM15')
New Line
9600 baud
Ln 11, Col 1
Arduino Uno R4 WiFi on COM15
2

아날로그 값을 이동 명령(왼쪽/오른쪽/위/아래)으로 변환합니다.

/* * 이 아두이노 우노 R4 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 우노 R4 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino-uno-r4/arduino-uno-r4-joystick */ #define VRX_PIN A1 // The Arduino UNO R4 pin connected to VRX pin #define VRY_PIN A0 // The Arduino UNO R4 pin connected to VRY pin #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 ; // To store value of the X axis int yValue = 0 ; // To store value of the Y axis int command = COMMAND_NO; void setup() { Serial.begin(9600) ; } void loop() { // read analog X and Y analog values xValue = analogRead(VRX_PIN); yValue = analogRead(VRY_PIN); // converts the analog value to commands // reset commands command = COMMAND_NO; // check left/right commands if (xValue < LEFT_THRESHOLD) command = command | COMMAND_LEFT; else if (xValue > RIGHT_THRESHOLD) command = command | COMMAND_RIGHT; // check up/down commands if (yValue < UP_THRESHOLD) command = command | COMMAND_UP; else if (yValue > DOWN_THRESHOLD) command = command | COMMAND_DOWN; // NOTE: AT A TIME, THERE MAY BE NO COMMAND, ONE COMMAND OR TWO COMMANDS // print command to serial and process command if (command & COMMAND_LEFT) { Serial.println("COMMAND LEFT"); // TODO: add your task here } if (command & COMMAND_RIGHT) { Serial.println("COMMAND RIGHT"); // TODO: add your task here } if (command & COMMAND_UP) { Serial.println("COMMAND UP"); // TODO: add your task here } if (command & COMMAND_DOWN) { Serial.println("COMMAND DOWN"); // TODO: add your task here } }

자세한 사용 방법

  • 위의 코드를 복사하여 Arduino IDE로 엽니다.
  • Arduino IDE에서 Upload 버튼을 클릭하여 코드를 Arduino UNO R4에 업로드합니다.
  • 조이스틱 엄지손가락을 왼쪽, 오른쪽, 위, 아래 또는 다른 방향으로 움직입니다.
  • 시리얼 모니터에서 결과를 확인합니다.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
Arduino Uno R4 WiFi
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'Arduino Uno R4 WiFi' on 'COM15')
New Line
9600 baud
Ln 11, Col 1
Arduino Uno R4 WiFi on COM15
2

※ 주의:

때때로 명령이 전혀 없거나 하나의 명령, 또는 동시에 두 개의 명령이 있을 수 있습니다(예: 위와 왼쪽 함께).

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

아두이노 우노 R4 - 조이스틱 - 서보 모터 튜토리얼에서 정보를 찾을 수 있습니다.

동영상

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

아래 동영상 데모는 아래 코드를 사용합니다:

/* * 이 아두이노 우노 R4 코드는 newbiely.kr 에서 개발되었습니다 * 이 아두이노 우노 R4 코드는 어떠한 제한 없이 공개 사용을 위해 제공됩니다. * 상세한 지침 및 연결도에 대해서는 다음을 방문하세요: * https://newbiely.kr/tutorials/arduino-uno-r4/arduino-uno-r4-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); }