Jeu vidéo / Embarqué

LED Arcade

Un Pong maison sur matrice LED, joué sur enceinte de voiture

ArduinoC/C++LED MatrixEmbarquéAudioÉquipe

L'idée

Je voulais un Pong sans PC et sans écran classique : un Arduino, une bande LED adressable repliée en matrice pour afficher, et une vieille enceinte de voiture branchée sur une broche pour le son. Tout le code est sur la carte, qui démarre toute seule une fois alimentée.

Pong se prête à l’exercice : deux raquettes, une balle, des collisions simples, une logique qui tient dans la mémoire d’un Arduino sans forcer.

On était trois sur le projet. J’ai pris tout le code Arduino, en particulier le point le plus tendu côté timing : le rendu de l’image et la sortie audio tournent sur le même processeur.

Le matériel

On est partis sur du low-tech dès le départ : pas de Raspberry Pi, pas de carte son ni d’écran, seulement ce qui se branche directement sur un Arduino.

La matrice LED, l'Arduino et le portable sur la table de travail
  • un Arduino pour le cerveau, avec tout le code en C/C++ embarqué
  • une bande LED adressable repliée en matrice : c’est l’écran, où s’affichent la balle, les raquettes et le score
  • une vieille enceinte de voiture pour l’audio, pilotée par un signal carré sur une broche

Mono-thread et interruptions

C’est ce qui m’a pris le plus de temps, à cause du conflit de timing entre la bande LED et l’audio. L’Arduino est mono-thread : tout ce qui doit tourner en parallèle de la boucle principale passe par une interruption matérielle.

Le rafraîchissement de l’image reste simple. Dans la boucle principale, j’accumule le delta time dans une variable, et un if déclenche le refresh quand le seuil est atteint, avant de remettre la variable à zéro. Il n’y a pas besoin d’interruption pour ça.

L’audio est différent. Chaque note est un signal carré à une fréquence précise sur la broche de l’enceinte. Pour que cette fréquence reste stable, une interruption matérielle inverse l’état de la broche à intervalles réguliers, quoi que fasse la boucle.

Le problème vient du protocole d’envoi des données aux bandes LED adressables, très sensible au timing. Si une interruption tombe au milieu de la trame, le signal est corrompu et les LEDs partent en sucette. Je désactive donc les interruptions pendant le refresh, je pousse les pixels, puis je réarme l’interruption audio sur la note suivante de la séquence.

  • la musique du jeu est découpée en notes, chacune jouée entre deux refresh
  • le tempo dépend donc du framerate du jeu
  • il n’y a pas de désynchro, puisque le son redémarre juste après chaque refresh

Le rendu sonore

Le son n’a pas la richesse d’une vraie carte son, mais c’était voulu : un grain de signal carré qui colle à l’esthétique LED, et un beep à chaque rebond de la balle qui passe juste.

Video game / Embedded

LED Arcade

A homemade Pong on an LED matrix, played through a car speaker

ArduinoC/C++LED MatrixEmbarquéAudioÉquipe

The idea

I wanted a Pong with no PC and no regular screen: an Arduino, an addressable LED strip folded into a matrix to display the game, and an old car speaker wired to a pin for the sound. All the code is on the board, which boots by itself once powered.

Pong suits this: two paddles, a ball, simple collisions, logic that fits in an Arduino’s memory without strain.

We were three on the project. I took all the Arduino code, especially the tightest point for timing: image rendering and audio output run on the same processor.

The hardware

We went low-tech from the start: no Raspberry Pi, no sound card or display, only what plugs straight into an Arduino.

The LED matrix, the Arduino and the laptop on the workbench
  • an Arduino as the brain, with all the code in embedded C/C++
  • an addressable LED strip folded into a matrix: it is the screen, showing the ball, the paddles and the score
  • an old car speaker for audio, driven by a square wave on a pin

Single thread and interrupts

This took me the most time, because of the timing conflict between the LED strip and the audio. The Arduino is single-threaded: anything that has to run alongside the main loop goes through a hardware interrupt.

The frame refresh stays simple. In the main loop, I accumulate the delta time in a variable, and an if triggers the refresh when the threshold is reached, then resets the variable. No interrupt is needed for that.

Audio is different. Each note is a square wave at a precise frequency on the speaker pin. To keep that frequency stable, a hardware interrupt toggles the pin at regular intervals, whatever the loop is doing.

The problem comes from the protocol that sends data to addressable LED strips, which is very sensitive to timing. If an interrupt lands in the middle of the data stream, the signal is corrupted and the LEDs go haywire. So I disable interrupts during the refresh, push the pixels, then re-arm the audio interrupt on the next note of the sequence.

  • the game’s music is cut into notes, each played between two refreshes
  • the tempo therefore depends on the game’s framerate
  • there is no desync, since the sound restarts right after each refresh

The sound

The sound doesn’t have the richness of a real sound card, but that was intended: a square-wave grain that fits the LED look, and a beep on every ball bounce that lands right.