Bouw een interactief LCD-spel met Arduino UNO
Onderdelen en benodigdheden
| Onderdeel | Aantal |
|---|---|
| Arduino UNO | 1 |
| Adafruit RGB Backlight LCD - 16x2 | 1 |
| Breadboard (generic) | 1 |
| Weerstand 220 ohm | 1 |
| SparkFun Drukknopschakelaar 12mm | 1 |
| Jumper draden (generic) | 1 |
| USB-A naar B Kabel | 1 |
Apps en online diensten
- Arduino IDE
Over dit project

De benodigde onderdelen zijn in de afbeelding hierboven weergegeven. Begin met de Arduino zonder stroom. Steek de USB-kabel NOG NIET in. Dat gebeurt in een latere stap wanneer het tijd is om te programmeren en het spel uit te proberen.
Verbindingen
Gebruik een lange draad om het 5V-signaal op de Arduino te verbinden met het uiterst linkerpunt van de rode rij bovenaan het breadboard.
Gebruik een lange draad om het GND-signaal te verbinden met het uiterst linkerpunt van de zwarte (of blauwe op sommige breadboards) rij bovenaan het breadboard.
De LCD-module (Liquid Crystal Display) heeft een 16-pins mannelijke header aan de onderkant. Steek deze in het breadboard zoals in de afbeelding getoond. Alle elektronische signalen die de LCD voeden en besturen gaan door deze header.
Deze pennen zijn (van links naar rechts):
- GND - stroom grond signaal
- VCC - positief stroomsignaal
- V0 - contrast aanpassen
- RS - register selecteren
- R/W - lezen/schrijven selecteren
- E - bewerkingsinschakeling signaal
- DB0 - databit 0 (hier niet gebruikt)
- DB1 - databit 1 (hier niet gebruikt)
- DB2 - databit 2 (hier niet gebruikt)
- DB3 - databit 3 (hier niet gebruikt)
- DB4 - databit 4
- DB5 - databit 5
- DB6 - databit 6
- DB7 - databit 7
- LED+ - achtergrondverlichting LED positief
- LED- - achtergrondverlichting LED negatief
Met korte draden, verbind GND en LED- (pennen 1 en 16) met de zwarte rij bovenaan.
Op dezelfde manier, verbind VCC (pen 2) met de rode rij bovenaan met een korte draad.
Buig de draad van de 220 Ω weerstand (rode-rode-bruine kleurbanden) en verbind deze tussen LED+ en de rode rij bovenaan het breadboard.
Gebruik langere draden voor de overige verbindingen:
- Verbind DB7 met Arduino pin 3
- Verbind DB6 met Arduino pin 4
- Verbind DB5 met Arduino pin 5
- Verbind DB4 met Arduino pin 6
- Verbind E met Arduino pin 9
- Verbind R/W met Arduino pin 10 (of met zwarte rij bovenaan breadboard)
- Verbind RS met Arduino pin 11
- Verbind V0 met Arduino pin 12 (of met zwarte rij bovenaan breadboard)
Steek de drukknop ergens links van het LCD-scherm, de kanaal in het midden van het breadboard overspannend (zie afbeelding hierboven). Verbind een van de bovenste twee pennen van de knop met de zwarte rij bovenaan het breadboard met een korte draad. Verbind de andere pen bovenaan de knop met pin 2 van de Arduino.


Code
LCD_Game.ino
#include <LiquidCrystal.h>
#define PIN_BUTTON 2
#define PIN_AUTOPLAY 1
#define PIN_READWRITE 10
#define PIN_CONTRAST 12
#define SPRITE_RUN1 1
#define SPRITE_RUN2 2
#define SPRITE_JUMP 3
#define SPRITE_JUMP_UPPER '.' // Gebruik het '.' teken voor het hoofd
#define SPRITE_JUMP_LOWER 4
#define SPRITE_TERRAIN_EMPTY ' ' // Gebruik het ' ' teken
#define SPRITE_TERRAIN_SOLID 5
#define SPRITE_TERRAIN_SOLID_RIGHT 6
#define SPRITE_TERRAIN_SOLID_LEFT 7
#define HERO_HORIZONTAL_POSITION 1 // Horizontale positie van held op scherm
#define TERRAIN_WIDTH 16
#define TERRAIN_EMPTY 0
#define TERRAIN_LOWER_BLOCK 1
#define TERRAIN_UPPER_BLOCK 2
#define HERO_POSITION_OFF 0 // Held is onzichtbaar
#define HERO_POSITION_RUN_LOWER_1 1 // Held rent op onderste rij (pose 1)
#define HERO_POSITION_RUN_LOWER_2 2 // (pose 2)
#define HERO_POSITION_JUMP_1 3 // Start een sprong
#define HERO_POSITION_JUMP_2 4 // Halfweg omhoog
#define HERO_POSITION_JUMP_3 5 // Sprong is op bovenste rij
#define HERO_POSITION_JUMP_4 6 // Sprong is op bovenste rij
#define HERO_POSITION_JUMP_5 7 // Sprong is op bovenste rij
#define HERO_POSITION_JUMP_6 8 // Sprong is op bovenste rij
#define HERO_POSITION_JUMP_7 9 // Halfweg omlaag
#define HERO_POSITION_JUMP_8 10 // bijna landen
#define HERO_POSITION_RUN_UPPER_1 11 // Held rent op bovenste rij (pose 1)
#define HERO_POSITION_RUN_UPPER_2 12 // (pose 2)
LiquidCrystal lcd(11, 9, 6, 5, 4, 3);
static char terrainUpper[TERRAIN_WIDTH + 1];
static char terrainLower[TERRAIN_WIDTH + 1];
static bool buttonPushed = false;
void initializeGraphics(){
static byte graphics[] = {
// Renpositie 1
B01100, B01100, B00000, B01110, B11100, B01100, B11010, B10011,
// Renpositie 2
B01100, B01100, B00000, B01100, B01100, B01100, B01100, B01110,
// Sprong
B01100, B01100, B00000, B11110, B01101, B11111, B10000, B00000,
// Sprong lager
B11110, B01101, B11111, B10000, B00000, B00000, B00000, B00000,
// Grond
B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111,
// Grond rechts
B00011, B00011, B00011, B00011, B00011, B00011, B00011, B00011,
// Grond links
B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11000,
};
int i;
// Karakter 0 overslaan, dit stelt lcd.print() in staat om snel meerdere tekens te tekenen
for (i = 0; i < 7; ++i) {
lcd.createChar(i + 1, &graphics[i * 8]);
}
for (i = 0; i < TERRAIN_WIDTH; ++i) {
terrainUpper[i] = SPRITE_TERRAIN_EMPTY;
terrainLower[i] = SPRITE_TERRAIN_EMPTY;
}
}
// Schuif het terrein naar links in half-teken stappen
void advanceTerrain(char* terrain, byte newTerrain){
for (int i = 0; i < TERRAIN_WIDTH; ++i) {
char current = terrain[i];
char next = (i == TERRAIN_WIDTH-1) ? newTerrain : terrain[i+1];
switch (current){
case SPRITE_TERRAIN_EMPTY:
terrain[i] = (next == SPRITE_TERRAIN_SOLID) ? SPRITE_TERRAIN_SOLID_RIGHT : SPRITE_TERRAIN_EMPTY;
break;
case SPRITE_TERRAIN_SOLID:
terrain[i] = (next == SPRITE_TERRAIN_EMPTY) ? SPRITE_TERRAIN_SOLID_LEFT : SPRITE_TERRAIN_SOLID;
break;
case SPRITE_TERRAIN_SOLID_RIGHT:
terrain[i] = SPRITE_TERRAIN_SOLID;
break;
case SPRITE_TERRAIN_SOLID_LEFT:
terrain[i] = SPRITE_TERRAIN_EMPTY;
break;
}
}
}
bool drawHero(byte position, char* terrainUpper, char* terrainLower, unsigned int score) {
bool collide = false;
char upperSave = terrainUpper[HERO_HORIZONTAL_POSITION];
char lowerSave = terrainLower[HERO_HORIZONTAL_POSITION];
byte upper, lower;
switch (position) {
case HERO_POSITION_OFF:
upper = lower = SPRITE_TERRAIN_EMPTY;
break;
case HERO_POSITION_RUN_LOWER_1:
upper = SPRITE_TERRAIN_EMPTY; lower = SPRITE_RUN1;
break;
case HERO_POSITION_RUN_LOWER_2:
upper = SPRITE_TERRAIN_EMPTY; lower = SPRITE_RUN2;
break;
case HERO_POSITION_JUMP_1:
case HERO_POSITION_JUMP_8:
upper = SPRITE_TERRAIN_EMPTY; lower = SPRITE_JUMP;
break;
case HERO_POSITION_JUMP_2:
case HERO_POSITION_JUMP_7:
upper = SPRITE_JUMP_UPPER; lower = SPRITE_JUMP_LOWER;
break;
case HERO_POSITION_JUMP_3:
case HERO_POSITION_JUMP_4:
case HERO_POSITION_JUMP_5:
case HERO_POSITION_JUMP_6:
upper = SPRITE_JUMP; lower = SPRITE_TERRAIN_EMPTY;
break;
case HERO_POSITION_RUN_UPPER_1:
upper = SPRITE_RUN1; lower = SPRITE_TERRAIN_EMPTY;
break;
case HERO_POSITION_RUN_UPPER_2:
upper = SPRITE_RUN2; lower = SPRITE_TERRAIN_EMPTY;
break;
}
if (upper != ' ') {
terrainUpper[HERO_HORIZONTAL_POSITION] = upper;
collide = (upperSave == SPRITE_TERRAIN_EMPTY) ? false : true;
}
if (lower != ' ') {
terrainLower[HERO_HORIZONTAL_POSITION] = lower;
collide |= (lowerSave == SPRITE_TERRAIN_EMPTY) ? false : true;
}
byte digits = (score > 9999) ? 5 : (score > 999) ? 4 : (score > 99) ? 3 : (score > 9) ? 2 : 1;
// Teken de scene
terrainUpper[TERRAIN_WIDTH] = '\0';
terrainLower[TERRAIN_WIDTH] = '\0';
char temp = terrainUpper[16-digits];
terrainUpper[16-digits] = '\0';
lcd.setCursor(0,0);
lcd.print(terrainUpper);
terrainUpper[16-digits] = temp;
lcd.setCursor(0,1);
lcd.print(terrainLower);
lcd.setCursor(16 - digits,0);
lcd.print(score);
terrainUpper[HERO_HORIZONTAL_POSITION] = upperSave;
terrainLower[HERO_HORIZONTAL_POSITION] = lowerSave;
return collide;
}
// Afhandelen van knopindruk als interrupt
void buttonPush() {
buttonPushed = true;
}
void setup(){
pinMode(PIN_READWRITE, OUTPUT);
digitalWrite(PIN_READWRITE, LOW);
pinMode(PIN_CONTRAST, OUTPUT);
digitalWrite(PIN_CONTRAST, LOW);
pinMode(PIN_BUTTON, INPUT);
digitalWrite(PIN_BUTTON, HIGH);
pinMode(PIN_AUTOPLAY, OUTPUT);
digitalWrite(PIN_AUTOPLAY, HIGH);
// Digitale pin 2 wordt afgebroken op interrupt 0
attachInterrupt(0/*PIN_BUTTON*/, buttonPush, FALLING);
initializeGraphics();
lcd.begin(16, 2);
}
void loop(){
static byte heroPos = HERO_POSITION_RUN_LOWER_1;
static byte newTerrainType = TERRAIN_EMPTY;
static byte newTerrainDuration = 1;
static bool playing = false;
static bool blink = false;
static unsigned int distance = 0;
if (!playing) {
drawHero((blink) ? HERO_POSITION_OFF : heroPos, terrainUpper, terrainLower, distance >> 3);
if (blink) {
lcd.setCursor(0,0);
lcd.print("Press Start");
}
delay(250);
blink = !blink;
if (buttonPushed) {
initializeGraphics();
heroPos = HERO_POSITION_RUN_LOWER_1;
playing = true;
buttonPushed = false;
distance = 0;
}
return;
}
// Schuif het terrein naar links
advanceTerrain(terrainLower, newTerrainType == TERRAIN_LOWER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY);
advanceTerrain(terrainUpper, newTerrainType == TERRAIN_UPPER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY);
// Maak nieuw terrein dat rechts binnenkomt
if (--newTerrainDuration == 0) {
if (newTerrainType == TERRAIN_EMPTY) {
newTerrainType = (random(3) == 0) ? TERRAIN_UPPER_BLOCK : TERRAIN_LOWER_BLOCK;
newTerrainDuration = 2 + random(10);
} else {
newTerrainType = TERRAIN_EMPTY;
newTerrainDuration = 10 + random(10);
}
}
if (buttonPushed) {
if (heroPos <= HERO_POSITION_RUN_LOWER_2) heroPos = HERO_POSITION_JUMP_1;
buttonPushed = false;
}
if (drawHero(heroPos, terrainUpper, terrainLower, distance >> 3)) {
playing = false; // De held botste tegen iets. Jammer.
} else {
if (heroPos == HERO_POSITION_RUN_LOWER_2 || heroPos == HERO_POSITION_JUMP_8) {
heroPos = HERO_POSITION_RUN_LOWER_1;
} else if ((heroPos >= HERO_POSITION_JUMP_3 && heroPos <= HERO_POSITION_JUMP_5) && terrainLower[HERO_HORIZONTAL_POSITION] != SPRITE_TERRAIN_EMPTY) {
heroPos = HERO_POSITION_RUN_UPPER_1;
} else if (heroPos >= HERO_POSITION_RUN_UPPER_1 && terrainLower[HERO_HORIZONTAL_POSITION] == SPRITE_TERRAIN_EMPTY) {
heroPos = HERO_POSITION_JUMP_5;
} else if (heroPos == HERO_POSITION_RUN_UPPER_2) {
heroPos = HERO_POSITION_RUN_UPPER_1;
} else {
++heroPos;
}
++distance;
digitalWrite(PIN_AUTOPLAY, terrainLower[HERO_HORIZONTAL_POSITION + 2] == SPRITE_TERRAIN_EMPTY ? HIGH : LOW);
}
delay(100);
}
Schema's
Geen document.
Productieproces
- TinyML Taaldetector met Edge Impulse op Arduino Nano 33 BLE Sense
- Bouw een real-time gyroscoop-spel met Arduino Nano & MPU-6050 sensor
- Arduino Digitale Dobbelsteen: Bouw Je Eigen LCD-willekeurige Getalgenerator
- Bouw Je Eigen 37-LED Roulette-spel met Arduino Nano
- DIY Arduino USB Gaming Controller – Bouw Je Eigen High-Performance Gamepad
- Interactieve Joystick Game met Arduino en LED Feedback
- Find Me: Slimme Item Locator met Arduino en Bluetooth
- Pixel Chaser: Interactieve één-tik LED-spel met Arduino Nano
- Zelfbouw Arduino Nano Oscilloscoop (10 Hz – 50 kHz) met 128×64 LCD
- Een audio-reactieve bureaulamp bouwen met Arduino Nano
- NeoPixel Matrix Pong op Arduino Nano: Bouw je eigen neon Pong-spel