Simulating a Bouncy Ball on the Digital Arduino LED Matrix

by Amanojaku23 in Circuits > Arduino

9 Views, 1 Favorites, 0 Comments

Simulating a Bouncy Ball on the Digital Arduino LED Matrix

arduino ball

We’ll build an interactive bouncy ball simulation using the Arduino UNO R4 WiFi and an analog joystick. You’ll code a 12 x 8 LED matrix display to simulate velocity, collision, and damping. You'll be able to fling a digital ball across the screen and shift gravity down, left, up, or right by clicking.

Supplies

Untitled design (19).png
  1. Arduino UNO R4 WiFi (The LED's are built in so it can play animation)
  2. Analog Joystick Module
  3. 5x Jumper Wires (Female-to-Male)
  4. USB-C Cable

Wiring

Untitled design (20).png
Untitled design (21).png

Joystick to Arduino Wiring

To connect the analog joystick to the Arduino, we need to wire the positive power, ground, the two directional control pins (X and Y axes), and the push-button switch. The joystick translates physical movement and button clicks into electrical signals that the Arduino interprets to control the ball's velocity and toggle gravity.

These are the corresponding pins from the joystick to the Arduino.

GND (Brown)

Arduino GND

Return path for the electric current. Establishes a shared ground reference.

+5V (Red)

Arduino 5V

Provides a constant 5-Volt stream of electricity to power the joystick.

VRx (Yellow)

Arduino A0

Analog output for horizontal movement.

VRy (Green)

Arduino A1

Analog output for vertical movement.

SW (Blue)

Arduino Pin 2

Digital output for the push-button switch.


You can power the project with a computer via USB-C cable.

Coding

To make the animation, we need to create individual frames that tell the Arduino which LED lights to have off and which to have turned off. We build and display these frames quickly, which simulates a ball bouncing around. The program constantly tracks its position, speed, and the pull of gravity. Inside the main loop, the Arduino reads your joystick movements, pulls the ball downward to simulate gravity, and watches for the edges of the screen. When the ball hits a wall, the code flips its direction and applies a "damping" factor so the ball loses a bit of energy.

I uploaded this code using the PlatformIO extension in VS Code, but you can also use the Arduino IDE. If you are using the official IDE, you can just copy and paste the code below straight into a blank sketch and upload it directly to your Arduino.

Constants and Setup()

#include "Arduino_LED_Matrix.h" // Import the library with the tools to let you control the LED matrix

ArduinoLEDMatrix matrix; // This matrix object lets you controll the matrix

// Pin Definitions. These match up with the Arduino and the Joystick wiring
const int VRX_PIN = A0; // Joystick X-axis
const int VRY_PIN = A1; // Joystick Y-axis
const int SW_PIN = 2; // Joystick Button

// Physics Constants
const float GRAVITY = 0.15; // Acceleration added at all times
const float BOUNCE_DAMPING = 0.75; // Energy kept after a bounce (slows after hitting the wall)
const float FLICK_STRENGTH = 0.003; // How much joystick affects velocity
const int JOYSTICK_DEADZONE = 50; // Avoids stick-drift by ignoring small stick tilts.

// The LED matrix is 12 pixels wide and 8 pixels high
// Use a 2d array to represent every pixel on the Arduino, 1 means on, 0 means off.
uint8_t frame[8][12] = {0};

// Ball Position, Velocity, and Gravity
float posX = 5.0;
float posY = 0.0;
float velX = 0.0;
float velY = 0.0;
float gravX = 0.0;
float gravY = GRAVITY;

int gravityMode = 0; // 0: Down, 1: Left, 2: Up, 3: Right
bool lastButtonState = HIGH;

// The setup() function only occurs once when the program starts.

void setup() {
Serial.begin(115200);
matrix.begin();
pinMode(SW_PIN, INPUT_PULLUP);
}

Loop()

// The loop function runs continously while the program runs.

void loop() {
// These two read the position of the joystick.
int xRaw = analogRead(VRX_PIN);
int yRaw = analogRead(VRY_PIN);


// Check for button press to rotate gravity
bool currentButtonState = digitalRead(SW_PIN);
if (lastButtonState == HIGH && currentButtonState == LOW) {
// This increases gravityMode by one, and cycles between 0,1,2,3. The gravity turns 90 degrees every press.
gravityMode = (gravityMode + 1) % 4;

switch (gravityMode) {
case 0: // Down
gravX = 0;
gravY = GRAVITY;
break;
case 1: // Left
gravX = -GRAVITY;
gravY = 0;
break;
case 2: // Up
gravX = 0;
gravY = -GRAVITY;
break;
case 3: // Right
gravX = GRAVITY;
gravY = 0;
break;
}
}

lastButtonState = currentButtonState;

// Make the joystick inputs match the matrix, so pixels on the left are negative while only pixels on the right are positive.
// Originally, a resting joystick would have the value (512,512). Now it's (0,0).
float dx = xRaw - 512;
float dy = yRaw - 512;

// Change velocity, only if the joystick is flicked hard enough
if (abs(dx) > JOYSTICK_DEADZONE) velX += dx * FLICK_STRENGTH;
if (abs(dy) > JOYSTICK_DEADZONE) velY += dy * FLICK_STRENGTH;

// Update position and gravity
velX += gravX;
velY += gravY;
posX += velX;
posY += velY;

// This is where kinetic energy is lost after collision.
// Collision that tracks the top left corner of the 2x2 ball.
// Horizontal bounds: 12 wide. 'x' can be 0 to 10
// The top left corner can touch the left wall, but not the right
if (posX < 0) {
posX = 0;
velX = -velX * BOUNCE_DAMPING;
} else if (posX > 10) {
posX = 10;
velX = -velX * BOUNCE_DAMPING;
}

// Vertical bounds: 8 high. Top-left 'y' can be 0 to 6.
if (posY < 0) {
posY = 0;
velY = -velY * BOUNCE_DAMPING;
} else if (posY > 6) {
posY = 6;
velY = -velY * BOUNCE_DAMPING;
}

// Rendering (We drew pixels on the frame of the matrix, and now we have to display it)
// Cleaning and displaying different frames quickly makes the ball look like it's moving

memset(frame, 0, sizeof(frame)); // Cleans the last frame

// If the calculations ended in a decimal number like 1.2
// We can't display them on a matrix unless we round them.
int ix = (int)posX;
int iy = (int)posY;

// Draw the 2x2 ball pixels
frame[iy][ix] = 1;
frame[iy][ix + 1] = 1;
frame[iy + 1][ix] = 1;
frame[iy + 1][ix + 1] = 1;

matrix.renderBitmap(frame, 8, 12); // This updates the screen with the next frame.
delay(30); // Wait 30 milliseconds, so the ball isn't too fast.
}

Entire File

#include "Arduino_LED_Matrix.h" // Import the library with the tools to let you control the LED matrix

ArduinoLEDMatrix matrix; // This matrix object lets you controll the matrix

// Pin Definitions. These match up with the Arduino and the Joystick wiring
const int VRX_PIN = A0; // Joystick X-axis
const int VRY_PIN = A1; // Joystick Y-axis
const int SW_PIN = 2; // Joystick Button

// Physics Constants
const float GRAVITY = 0.15; // Acceleration added at all times
const float BOUNCE_DAMPING = 0.75; // Energy kept after a bounce (slows after hitting the wall)
const float FLICK_STRENGTH = 0.003; // How much joystick affects velocity
const int JOYSTICK_DEADZONE = 50; // Avoids stick-drift by ignoring small stick tilts.

// The LED matrix is 12 pixels wide and 8 pixels high
// Use a 2d array to represent every pixel on the Arduino, 1 means on, 0 means off.
uint8_t frame[8][12] = {0};

// Ball Position, Velocity, and Gravity
float posX = 5.0;
float posY = 0.0;
float velX = 0.0;
float velY = 0.0;
float gravX = 0.0;
float gravY = GRAVITY;

int gravityMode = 0; // 0: Down, 1: Left, 2: Up, 3: Right
bool lastButtonState = HIGH;

// The setup() function only occurs once when the program starts.

void setup() {
Serial.begin(115200);
matrix.begin();
pinMode(SW_PIN, INPUT_PULLUP);
}
// The loop function runs continously while the program runs.

void loop() {
// These two read the position of the joystick.
int xRaw = analogRead(VRX_PIN);
int yRaw = analogRead(VRY_PIN);


// Check for button press to rotate gravity
bool currentButtonState = digitalRead(SW_PIN);
if (lastButtonState == HIGH && currentButtonState == LOW) {
// This increases gravityMode by one, and cycles between 0,1,2,3. The gravity turns 90 degrees every press.
gravityMode = (gravityMode + 1) % 4;

switch (gravityMode) {
case 0: // Down
gravX = 0;
gravY = GRAVITY;
break;
case 1: // Left
gravX = -GRAVITY;
gravY = 0;
break;
case 2: // Up
gravX = 0;
gravY = -GRAVITY;
break;
case 3: // Right
gravX = GRAVITY;
gravY = 0;
break;
}
}

lastButtonState = currentButtonState;

// Make the joystick inputs match the matrix, so pixels on the left are negative while only pixels on the right are positive.
// Originally, a resting joystick would have the value (512,512). Now it's (0,0).
float dx = xRaw - 512;
float dy = yRaw - 512;

// Change velocity, only if the joystick is flicked hard enough
if (abs(dx) > JOYSTICK_DEADZONE) velX += dx * FLICK_STRENGTH;
if (abs(dy) > JOYSTICK_DEADZONE) velY += dy * FLICK_STRENGTH;

// Update position and gravity
velX += gravX;
velY += gravY;
posX += velX;
posY += velY;

// Collision that tracks the top left corner of the 2x2 ball.
// Horizontal bounds: 12 wide. 'x' can be 0 to 10
// The top left corner can touch the left wall, but not the right
if (posX < 0) {
posX = 0;
velX = -velX * BOUNCE_DAMPING;
} else if (posX > 10) {
posX = 10;
velX = -velX * BOUNCE_DAMPING;
}

// Vertical bounds: 8 high. Top-left 'y' can be 0 to 6.
if (posY < 0) {
posY = 0;
velY = -velY * BOUNCE_DAMPING;
} else if (posY > 6) {
posY = 6;
velY = -velY * BOUNCE_DAMPING;
}

// Rendering (We drew pixels on the frame of the matrix, and now we have to display it)
// Cleaning and displaying different frames quickly makes the ball look like it's moving

memset(frame, 0, sizeof(frame)); // Cleans the last frame

// If the calculations ended in a decimal number like 1.2
// We can't display them on a matrix unless we round them.
int ix = (int)posX;
int iy = (int)posY;

// Draw the 2x2 ball pixels
frame[iy][ix] = 1;
frame[iy][ix + 1] = 1;
frame[iy + 1][ix] = 1;
frame[iy + 1][ix + 1] = 1;

matrix.renderBitmap(frame, 8, 12); // This updates the screen with the next frame.
delay(30); // Wait 30 milliseconds, so the ball isn't too fast.
}


Modifying

Modifying

Gravity - You can change the GRAVITY variable to something much lower like .01 to significantly reduce the ball's gravity, like it's on the moon.


Damping - You can change the BOUNCE_DAMPING variable to something higher like .9 so the bouncy ball gets much bouncier and loses less energy hitting the walls.


You could also add on to the code by allowing the analog stick to change these variables while the program runs. You could make the frames load quicker, add more balls, or make bigger balls.