Arduino Tennisspel – Virtuele Raquet Ervaring met NeoPixel, Sensoren & Bluetooth
Onderdelen en Benodigdheden
| Arduino Nano R3 | × | 2 |
| SparkFun 9DoF Sensor Stick | × | 1 |
| Draadjes (generic) | × | 1 |
| NeoPixel Strip | × | 1 |
| HC-05 Bluetooth Module | × | 1 |
| 9V Batterij (generic) | × | 1 |
Nodige Tools
- Soldeerbout (generic)
Apps & Online Services
- Arduino IDE
Over dit Project
Dit project heeft 3 delen: het spelbord/display, de controller, en de master router (Python-script).
Deel 1 – Het Display
Het display is een 15x10 NeoPixel-matrix aangedreven door een Arduino Nano. Het regelt de spel-logica en ontvangt input van het Python-script op de host-computer. Er wordt een bal weergegeven die groeit en verder naar beneden beweegt – dit geeft de illusie van diepte en een bal die op je afkomt. Als een slag gedetecteerd wordt, wordt de bal teruggetekend 'weg van je'.

Punten
De balrichting (links/rechts) verandert bij elke slag. Mis je de bal, scoort de COM. Mislukt de COM de bal (25% kans), scoort jij. Dit ontwerp is geïnspireerd op Wii Sports Tennis. De score wordt getoond wanneer de COM tegen de speler scoort.

Highscore
Er is een lopende highscore opgeslagen in een I2C EEPROM op adres 0x04. Het houdt een byte bij met de highscore. Scoor je hoger, dan wordt het adres overschreven. Als de COM 10 bereikt, is het game over. Logica overzicht:

De Matrix Maken
Het bouwen van de matrix kost tijd. Volg de gids die ik gemaakt heb.


Deel 2 – De Controller
De controller is een andere Arduino Nano met een 9DoF-stick (alleen 3,3V!) en een HC-05 BLE-module. Als de versnelling op de X- of Y-as een drempel overschrijdt, stuurt hij een '1' via serie naar de host-PC, waar het naar het display wordt doorgestuurd. De Nano meet de X- en Y-versnelling op vast interval en checkt of de waarden boven/onder 1,5G vallen. Voor de I2C 9DoF-stick heb je de SparkFun-library nodig (download hier) en een tutorial voor aansluiting en basisprogramma's hier.
Voor de raquet gebruikte ik een doos en knipte een raquetvorm uit karton. Eroverheen wit papier met een ontwerp. Een 9V-batterij voedt het geheel – loop de batterij via de Vin-pin op de Nano, anders krijg je ongeregelde 9V! Interna van de raquet:

Communiceren via Bluetooth
Hoe communiceren raquet en display? Een geheim: ze praten niet direct met elkaar! Een Python-script gebruikt de binnenkomende Bluetooth COM-poort en de matrix COM-poort om informatie door te sturen. Het synchroniseert ook beide systemen, want ze resetten allebei bij serial-initialisatie. De raquet stuurt '1' naar Python via Bluetooth, Python stuurt '1' naar de matrix. Er zit een debounce van 1 seconde op om buffer-overloop te voorkomen (alleen 32-bit buffers).
De Raquet Bouwen
Klik door de afbeeldingen voor de stapsgewijze bouw:






Veel plezier met je nieuwe tennisspel!
Code
Matrix Code (C/C++) – Op de Arduino aan de matrix
#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <EEPROM.h>
#define PIN 6
#define EEPROM_ADR 0x50
#define HS_ADR 0x02
#define NOTE_C1 33
#define SPKR_PIN 3
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(15, 10, PIN,
NEO_MATRIX_BOTTOM + NEO_MATRIX_LEFT + NEO_MATRIX_COLUMNS + NEO_MATRIX_ZIGZAG,
NEO_GRB + NEO_KHZ800);
int ball_x = 7, ball_y = 2, radius = 1, r_incr = 1;
bool isSwung = false;
int ball_x_dir = 1, ball_y_dir = 1;
bool isDirRight = true;
int score = 0, COM_score = 0, highscore = 0;
int framerate = 50;
int serial_data;
String score_string = "";
uint16_t colors[] = {matrix.Color(255,0,0), matrix.Color(0,255,0), matrix.Color(150,200,0)};
int melody[] = {0};
int tempo[] = {0};
static unsigned long lastFrame = 0;
void setup() {
Serial.begin(9600);
matrix.begin();
matrix.fillScreen(0);
matrix.setTextColor(colors[1]);
randomSeed(analogRead(A2));
display_scores();
highscore = read_HS();
Serial.println(highscore, DEC);
matrix.setCursor(0,1);
matrix.print("HS: "); matrix.show(); delay(1000);
matrix.fillScreen(0);
matrix.setCursor(0,1);
matrix.print(highscore, DEC); matrix.show(); delay(1000);
score_string = "";
}
void loop() {
if ((lastFrame + framerate) < millis()) {
update_frame();
lastFrame = millis();
}
}
void update_frame() {
serial_data = Serial.parseInt();
if (serial_data) isSwung = true;
else isSwung = false;
if (ball_y >= 5 && ball_y < 7 && isSwung) {
if (isDirRight) { ball_x_dir = -1; ball_y_dir = -1; }
else { ball_x_dir = 1; ball_y_dir = -1; }
r_incr = -1;
}
else if (ball_y >= 8) {
COM_score += 1; end_round();
}
else if (ball_y <= 2) {
isDirRight = !isDirRight;
int randNum = random(4);
Serial.println(randNum);
if (randNum == 2) { // 25% kans COM mist
score += 1;
if (score > highscore) write_HS();
end_round();
} else {
if (isDirRight) { ball_x_dir = 1; ball_y_dir = 1; }
else { ball_x_dir = -1; ball_y_dir = 1; }
r_incr = 1;
}
}
ball_x += ball_x_dir;
ball_y += ball_y_dir;
radius += r_incr;
matrix.fillScreen(0);
matrix.fillCircle(ball_x, ball_y, radius, colors[2]);
matrix.show();
}
void end_round() {
if (COM_score >= 10) end_game();
isDirRight = true; r_incr = 1;
ball_x_dir = 1; ball_y_dir = 1;
ball_x = 7; ball_y = 2; radius = 1;
display_scores();
matrix.fillScreen(0);
matrix.fillCircle(ball_x, ball_y, radius, colors[2]);
matrix.show();
}
void end_game() {
matrix.fillScreen(0);
matrix.setCursor(0,1);
matrix.setTextColor(colors[0]);
matrix.drawLine(3,0,12,9,colors[0]);
matrix.drawLine(11,0,2,9,colors[0]);
matrix.show(); delay(500);
while(1) { }
}
void display_scores() {
matrix.fillScreen(0);
matrix.setTextColor(colors[1]);
matrix.setCursor(0,1);
score_string = String(score) + "-" + String(COM_score);
scrollText(score_string);
matrix.fillScreen(0); delay(2000); matrix.show();
}
void scrollText(String text) {
int x = matrix.width();
for (int i=0; i<24; i++) {
matrix.fillScreen(0);
matrix.setCursor(x,2);
matrix.print(text);
x -= 1;
matrix.show(); delay(150);
}
}
void write_HS() { EEPROM.write(0x04, int(score)); }
int read_HS() { return EEPROM.read(0x04); }
Raquet Code (C/C++)
#include <Wire.h>
#include <SPI.h>
#include <SparkFunLSM9DS1.h>
LSM9DS1 imu;
#define LSM9DS1_M 0x1E
#define LSM9DS1_AG 0x6B
#define PRINT_SPEED 10
static unsigned long lastPrint = 0;
float accelx = 0, accely = 0;
void setup() {
Serial.begin(9600);
imu.settings.device.commInterface = IMU_MODE_I2C;
imu.settings.device.mAddress = LSM9DS1_M;
imu.settings.device.agAddress = LSM9DS1_AG;
if (!imu.begin()) { while (1); }
}
void loop() {
if ((lastPrint + PRINT_SPEED) < millis()) {
if (imu.accelAvailable()) { imu.readAccel(); }
lastPrint = millis();
}
accelx = imu.calcAccel(imu.ax);
accely = imu.calcAccel(imu.ay);
if (accelx <= -1.5 || accelx >= 1.5) {
Serial.print(1); delay(600);
}
else if (accely <= -1.5 || accely >= 1.5) {
Serial.print(1); delay(600);
}
}
Python Code
import serial
import time
matrix_port = "COM3"
raquet_port = "COM9"
matrix = serial.Serial(matrix_port, 9600)
racquet = serial.Serial(raquet_port, 9600)
time.sleep(10)
while 1:
data = racquet.read()
print(data)
if data == "1":
print("hit")
matrix.write("1")
time.sleep(1)
time.sleep(0.05)
Schema's


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
- Een Arduino Iron Man bouwen: onderdelen, sensoren en stapsgewijze handleiding
- Find Me: Slimme Item Locator met Arduino en Bluetooth
- DIY Arduino Luchtbevochtiger Controller met Relais – Veilige Hoogspanningsopstelling
- Realtime afstandsmeting met Arduino Sonar & Processing visualisatie
- MobBob: Bouw Je Eigen Arduino Robot, Naadloos Bestuurd via Android Smartphone
- Een audio-reactieve bureaulamp bouwen met Arduino Nano
- NeoPixel Matrix Pong op Arduino Nano: Bouw je eigen neon Pong-spel
- Bouw een Aangepaste Arduino Joystick Stuurwiel voor Gaming