Arduino - Joystick

Neste tutorial, vamos aprender a usar o Joystick com o Arduino. Em detalhes, vamos aprender:

Pinagem do Joystick

Sobre o Sensor Joystick

Você provavelmente já viu um Joystick em algum lugar, como em um controle de videogame, um controle de brinquedo ou até em uma grande máquina real, como o controle de uma escavadeira.

O joystick é composto por dois potenciômetros posicionados em ângulo reto entre si e um botão de pressão. Por isso, ele fornece as seguintes saídas:

  • Um valor analógico (de 0 a 1023) correspondente à posição horizontal (chamada de coordenada X)
  • Um valor analógico (de 0 a 1023) correspondente à posição vertical (chamada de coordenada Y)
  • Um valor digital de um botão de pressão (HIGH ou LOW)

A combinação dos dois valores analógicos pode criar coordenadas 2D, cujo centro são os valores quando o joystick está na posição de repouso. A direção real das coordenadas pode ser identificada facilmente quando você executar um código de teste (na próxima parte).

Algumas aplicações podem usar as três saídas, outras podem usar apenas algumas delas.

Pinagem

Um Joystick tem 5 pinos:

  • Pino GND: precisa ser conectado ao GND (0V)
  • Pino VCC: precisa ser conectado ao VCC (5V)
  • Pino VRX: fornece um valor analógico correspondente à posição horizontal (chamada de coordenada X).
  • Pino VRY: fornece um valor analógico correspondente à posição vertical (chamada de coordenada Y).
  • Pino SW: é a saída do botão de pressão dentro do joystick. Ele é normalmente aberto. Se usarmos um resistor de pull-up neste pino, o pino SW ficará em HIGH quando não estiver pressionado e em LOW quando estiver pressionado.
Pinagem do Joystick

Como Funciona

  • Quando você empurra a alavanca do joystick para a esquerda/direita, a tensão no pino VRX muda. A faixa de tensão vai de 0 a 5V (0 à esquerda e 5v à direita). O valor da tensão é proporcional à posição da alavanca ⇒ O valor lido no pino analógico do Arduino vai de 0 a 1023
  • Quando você empurra a alavanca do joystick para cima/baixo, a tensão no pino VRY muda. A faixa de tensão vai de 0 a 5V (0 em cima e 5v embaixo). O valor da tensão é proporcional à posição da alavanca ⇒ O valor lido no pino analógico do Arduino vai de 0 a 1023
  • Quando você empurra a alavanca do joystick em qualquer direção, a tensão nos pinos VRX e VRY muda proporcionalmente à projeção da posição em cada eixo
  • Quando você pressiona a alavanca do joystick de cima para baixo, o botão de pressão dentro do joystick é fechado. Se usarmos um resistor de pull-up no pino SW, a saída do pino SW mudará de 5V para 0V ⇒ O valor lido no pino digital do Arduino muda de HIGH para LOW

Diagrama de Ligação

Diagrama de Ligação do Arduino com Joystick

This image is created using Fritzing. Click to enlarge image

Como Programar o Joystick

O joystick tem duas partes: analógica (eixos X, Y) e digital (botão de pressão)

  • Para as partes analógicas (eixos X, Y), basta ler o valor do pino de entrada analógica usando a função analogRead().
int xValue = analogRead(A1); int yValue = analogRead(A0);
  • Para a parte digital (botão de pressão): ela é um botão. A forma mais simples e prática é usar a biblioteca ezButton. Esta biblioteca oferece debounce para botões e também ativa um resistor de pull-up interno. Você pode ver mais sobre botões no tutorial Arduino - Botão. O código será apresentado na próxima seção deste tutorial.

Depois de ler os valores dos pinos analógicos, talvez seja necessário convertê-los em alguns valores controláveis. A próxima parte vai fornecer os códigos de exemplo para isso.

Código Arduino

Esta seção vai fornecer os seguintes códigos de exemplo para Arduino:

  • Código de exemplo: lê os valores analógicos do joystick
  • Código de exemplo: lê os valores analógicos e o estado do botão do joystick
  • Código de exemplo: converte o valor analógico em comandos MOVE_LEFT, MOVE_RIGHT, MOVE_UP, MOVE_DOWN
  • Código de exemplo: converte os valores analógicos em ângulos para controlar dois servo motores (por exemplo, em uma câmera pan-tilt)

Lê os valores analógicos do joystick

/* * Este código de Arduino foi desenvolvido por pt.newbiely.com * Este código de Arduino é disponibilizado ao público sem nenhuma restrição. * Para tutoriais completos e diagramas de ligação, visite: * https://pt.newbiely.com/tutorials/arduino/arduino-joystick */ #define VRX_PIN A1 // Arduino pin connected to VRX pin #define VRY_PIN A0 // Arduino 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); }

Passos Rápidos

  • Copie o código acima e abra-o na Arduino IDE
  • Clique no botão Upload na Arduino IDE para carregar o código no Arduino
  • Empurre a alavanca do joystick ao máximo até o limite e depois gire-a em círculo (no sentido horário ou anti-horário)
  • Veja o resultado no Serial Monitor.
∞
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
  • Enquanto gira a alavanca do joystick, continue observando o Serial Monitor
    • Se o valor X for 0, marque ou memorize a posição atual como esquerda ⇒ a direção oposta é a direita
    • Se o valor Y for 0, marque ou memorize a posição atual como cima ⇒ a direção oposta é baixo

    Lê os valores analógicos e o estado do botão de um joystick

    /* * Este código de Arduino foi desenvolvido por pt.newbiely.com * Este código de Arduino é disponibilizado ao público sem nenhuma restrição. * Para tutoriais completos e diagramas de ligação, visite: * https://pt.newbiely.com/tutorials/arduino/arduino-joystick */ #include <ezButton.h> #define VRX_PIN A1 // Arduino pin connected to VRX pin #define VRY_PIN A0 // Arduino pin connected to VRY pin #define SW_PIN 2 // Arduino 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); }

    Passos Rápidos

    • Vá até o ícone Libraries na barra lateral esquerda da Arduino IDE.
    • Pesquise “ezButton” e encontre a biblioteca de botão da ArduinoGetStarted.com
    • Clique no botão Install para instalar a biblioteca ezButton.
    Biblioteca de botão do Arduino
    • Copie o código acima e abra-o na Arduino IDE
    • Clique no botão Upload na Arduino IDE para carregar o código no Arduino
    • Empurre a alavanca do joystick para a esquerda/direita/cima/baixo
    • Pressione a alavanca do joystick de cima para baixo
    • Veja o resultado no Serial Monitor.
    ∞
    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

    Converte o valor analógico em comandos MOVE LEFT/RIGHT/UP/DOWN

    /* * Este código de Arduino foi desenvolvido por pt.newbiely.com * Este código de Arduino é disponibilizado ao público sem nenhuma restrição. * Para tutoriais completos e diagramas de ligação, visite: * https://pt.newbiely.com/tutorials/arduino/arduino-joystick */ #define VRX_PIN A1 // Arduino pin connected to VRX pin #define VRY_PIN A0 // Arduino 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 } }

    Passos Rápidos

    • Copie o código acima e abra-o na Arduino IDE
    • Clique no botão Upload na Arduino IDE para carregar o código no Arduino
    • Empurre a alavanca do joystick para a esquerda/direita/cima/baixo ou em qualquer direção
    • Veja o resultado no Serial Monitor.
    ∞
    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

    ※ Nota:

    Em um dado momento, pode não haver nenhum comando, haver um comando ou dois comandos (por exemplo, UP e LEFT ao mesmo tempo)

    Converte os valores analógicos em ângulos para controlar dois servo motores

    Os detalhes são apresentados no tutorial Arduino - Joystick controla Servo Motor

Vídeo

A demonstração em vídeo abaixo usa o código abaixo. Observe que o vídeo mostra o Arduino Uno R4, mas funciona da mesma forma para o Arduino Uno R3:

/* * Este código de Arduino foi desenvolvido por pt.newbiely.com * Este código de Arduino é disponibilizado ao público sem nenhuma restrição. * Para tutoriais completos e diagramas de ligação, visite: * https://pt.newbiely.com/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); }

Referências de Funções

Tutoriais Relacionados

※ NOSSAS MENSAGENS

  • Fique à vontade para compartilhar o link deste tutorial. Porém, por favor, não use nosso conteúdo em outros sites. Investimos muito esforço e tempo para criar este conteúdo, por favor respeite nosso trabalho!