In this tutorial we will be making PONG Game with ARIES v3 board. I am sure you played this game on your mobile phone, let try it on LED Matrix. Here we will be using 8×8 LED Matrix to display the game, 4-Digit display to display the score and Joystick to control the game.

Prerequisites

Hardware Required

  • ARIES v3.0 Board
  • MAX7219 Dot Let Matrix Module
  • 4-Digit Display
  • Analog Joystick
  • Jumper Wires

Note: All of the following boards can be used for this project

ARIES v3.0 Board

The ARIES v3.0 is a fully indigenous and a “Made in India” product to get started with basic microprocessor programming and embedded systems. This board is built upon a RISC-V ISA compliant VEGA Processor with easy-to-use hardware and software. For more details about ARIES v3.0 boards please refer to the ARIES development boards and Ecosystem.

MAX7219 Dot Led Matrix Module

The MAX7219/MAX7221 are compact, serial input/output common-cathode display drivers that interface microprocessors (µPs) to 7-segment numeric LED displays of up to 8 digits, bar-graph displays, or 64 individual LEDs.

MAX7219

4-Digit Display

7 Segment LED displays are used in many applications as front panel number indicators. The most common applications are calculators, microwave ovens, electronic lab equipment like function generators and frequency counters. A 7-segment LED display consists of 7 LEDs arranged in such a way that it can display numbers from 0 to 9. The arrangement of LEDs in the display can be either common anode or common cathode. In this project, a 4 – digit 7 – segment LED display is used to display numbers using Arduino.

4-DigitDisplay

Analog Joystick

The Analog Joystick is similar to two potentiometers connected together, one for the vertical movement (Y-axis) and other for the horizontal movement (X-axis). The joystick also comes with a select switch. It can be very handy for retro gaming, robot control or RC cars. In this project we are just going to use its X-axis.

Joystick

Libraries Required

Download the MD_MAX72XX and Grove 4-digit Display Library from Tools >> Manage Libraries…

Circuit Diagram

Now let’s connect the 8×8 LED Matrix, HC-05 Bluetooth module and 4-Digit display with the ARIES v3 board.

Connections

8X8 LED MATRIXARIES v3.0
VCC3.3V
GNDGND
DINMOSI-0
CSGPIO-10
CLKSCLK-0
connection pins of 8X8 LED Matrix & ARIES v3

Analog JoystickARIES v3.0
+5V3.3V
GNDGND
VRxA0
connection pins of Analog Joystick & ARIES v3

4-Digit DisplayARIES v3.0
VCC3.3V
GNDGND
DIOGPIO-0
CLKGPIO-1
connection pins of 4-Digit Display & ARIES v3

Arduino Code

Once we are done with the connections, we are ready to take a look at the Arduino code.

/*
    Joystick Controlled Pong Game on Dot Matrix
    by VEGA Processors, www.vegaprocessors.in

    Library Required: 1) MD_MAX72xx [https://github.com/MajicDesigns/MD_MAX72XX]
                      2) Grove_4Digital_Display by Seeed-Studio [https://github.com/Seeed-Studio/Grove_4Digital_Display]
*/


#include <MD_MAX72xx.h>
#include <SPI.h>
#include <TM1637.h>

SPIClass SPI(0);  // selecting SPI port-0

#define SPEED_FROM_ANALOG 0   // optional to use analog input for speed control
#define DEBUG 0   // Enable or disable (default) debugging output
#define CLK 1     //CLK of TM1637 is connected to GPIO-1 pin of Aries Board
#define DIO 0     //DI0 of TM1637 is connected to GPIO-0 pin of Aries Board

#if DEBUG
#define PRINT(s, v)   { Serial.print(F(s)); Serial.print(v); }      // Print a string followed by a value (decimal)
#define PRINTX(s, v)  { Serial.print(F(s)); Serial.print(v, HEX); } // Print a string followed by a value (hex)
#define PRINTS(s)     { Serial.print(F(s)); }                       // Print a string
#else
#define PRINT(s, v)   // Print a string followed by a value (decimal)
#define PRINTX(s, v)  // Print a string followed by a value (hex)
#define PRINTS(s)     // Print a string
#endif

TM1637 tm(CLK,DIO);

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 1 // Define the number of devices we have in the chain and the hardware interface
#define CS_PIN    10  // connect CS pin to GPIO-10
/* NOTE: You can use any GPIO pin */

MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

#if SPEED_FROM_ANALOG
const uint8_t SPEED_POT = A1;
#endif

// --------------------
// Constant parameters
//
const uint32_t TEXT_MOVE_DELAY = 100;   // in milliseconds
const uint32_t BAT_MOVE_DELAY = 50;     // in milliseconds
const uint32_t BALL_MOVE_DELAY = 100;   // in milliseconds
const uint32_t END_GAME_DELAY = 2000;   // in milliseconds

const uint8_t BAT_SIZE = 3;             // in pixels, odd number looks best

char *welcome;
bool messageComplete;
static int score;
int finalScore;

// ========== General Variables ===========
//
uint32_t prevTime = 0;    // used for remembering the mills() value
uint32_t prevBatTime = 0; // used for bat timing

// ========== Control routines ===========
//

uint8_t scrollDataSource(uint8_t dev, MD_MAX72XX::transformType_t t)
// Callback function for data that is required for scrolling into the display
{
  static char* p;
  static enum { INIT, LOAD_CHAR, SHOW_CHAR, BETWEEN_CHAR } state = INIT;
  static uint8_t  curLen, showLen;
  static uint8_t  cBuf[15];
  uint8_t colData = 0;    // blank column is the default

  // finite state machine to control what we do on the callback
  switch(state)
  {
    case INIT:   // Load the new message
      p = welcome;
      messageComplete = false;
      state = LOAD_CHAR;
      break;

    case LOAD_CHAR: // Load the next character from the font table
      showLen = mx.getChar(*p++, sizeof(cBuf)/sizeof(cBuf[0]), cBuf);
      curLen = 0;
      state = SHOW_CHAR;

      // !! deliberately fall through to next state to start displaying

    case SHOW_CHAR: // display the next part of the character
      colData = cBuf[curLen++];
      if (curLen == showLen)
      {
        if (*p == '\0')    // end of message!
        {
          messageComplete = true;
          state = INIT;
        }
        else  // more to come
        {
          showLen = 1;
          curLen = 0;
          state = BETWEEN_CHAR;
        }
      }
      break;

    case BETWEEN_CHAR: // display inter-character spacing (blank columns)
      colData = 0;
      curLen++;
      if (curLen == showLen)
        state = LOAD_CHAR;
      break;

    default:
      state = LOAD_CHAR;
  }

  return(colData);
}

void scrollText(void)
{
  // Is it time to scroll the text?
  if (millis() - prevTime >= TEXT_MOVE_DELAY)
  {
    mx.transform(MD_MAX72XX::TSL);  // scroll along - the callback will load all the data
    prevTime = millis();      // starting point for next time
  }
}

void resetDisplay(void)
{
  mx.control(MD_MAX72XX::INTENSITY, MAX_INTENSITY/2);
  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
  mx.clear();
}

inline bool swL(void) { return(analogRead(0)>1000); }
inline bool swR(void) { return(analogRead(0)<400); }
#if SPEED_FROM_ANALOG
inline uint32_t speed(void) { return(map(analogRead(SPEED_POT),0, 1640, 0, 300)); }
#else
inline uint32_t speed(void) { return(BALL_MOVE_DELAY); }
#endif

void drawBat(int8_t x, int8_t y, bool bOn = true)
{
  for (uint8_t i=0; i<BAT_SIZE; i++)
    mx.setPoint(y, x + i, bOn);
}

void drawBall(int8_t x, int8_t y, bool bOn = true)
{
  mx.setPoint(y, x, bOn);
}

void displayNumber(int num){   
    tm.display(3, num % 10);   
    tm.display(2, num / 10 % 10);   
    tm.display(1, num / 100 % 10);   
    tm.display(0, num / 1000 % 10);
}

void setup(void)
{
  mx.begin();
  tm.init();

  //set brightness; 0-7
  tm.set(7);
  displayNumber(0000);
  
#if SPEED_FROM_ANALOG
  pinMode(SPEED_POT, INPUT);
#endif

#if DEBUG
  Serial.begin(115200);
#endif
  PRINTS("\n[MD_MAX72XX Simple Pong]");
}

void loop(void)
{
  static enum:uint8_t { INIT, WELCOME, PLAY_INIT, WAIT_START, PLAY, END , FINAL_SCORE } state = INIT;
  
  static int8_t ballX, ballY;
  static int8_t batX;
  const int8_t batY = ROW_SIZE - 1;

  static int8_t deltaX, deltaY;   // initialisesd in FSM

  switch (state)
  {
  case INIT:
    PRINTS("\n>>INIT");
    welcome = (char*)"** PONG **";
    resetDisplay();
    mx.setShiftDataInCallback(scrollDataSource);
    prevTime = 0;
    state = WELCOME;
    break;

  case WELCOME:
    PRINTS("\n>>WELCOME");
    scrollText();
    if (messageComplete) state = PLAY_INIT;
    break;

  case PLAY_INIT:
    PRINTS("\n>>PLAY_INIT");
    displayNumber(0000);
    digitalWrite(24, HIGH);
    digitalWrite(22, LOW);
    mx.setShiftDataInCallback(nullptr);
    state = WAIT_START;
    mx.clear();
    batX = (COL_SIZE - BAT_SIZE) / 2;
    ballX = batX + (BAT_SIZE / 2);
    ballY = batY - 1;
    deltaY = -1;            // always heading up at the start
    deltaX = 0;             // initialized in the direction of first bat movement
    drawBat(batX, batY);
    drawBall(ballX, ballY);
    break;

  case WAIT_START:
    //PRINTS("\n>>WAIT_START");
    if (swL()) deltaX = 1;
    if (swR()) deltaX = -1;
    if (deltaX != 0)
    {
      prevTime = prevBatTime = millis();
      state = PLAY;
    }
    break;

  case PLAY:
    // === Move the bat if time has expired
    if (millis() - prevBatTime >= BAT_MOVE_DELAY)
    {
      if (swL())  // left switch move
      {
        PRINTS("\n>>PLAY - move bat L");
        drawBat(batX, batY, false);
        batX++;
        if (batX + BAT_SIZE >= COL_SIZE) batX = COL_SIZE - BAT_SIZE;
        drawBat(batX, batY);
      }

      if (swR())  // right switch move
      {
        PRINTS("\n>>PLAY - move bat R");
        drawBat(batX, batY, false);
        batX--;
        if (batX < 0) batX = 0;
        drawBat(batX, batY);
      }

      prevBatTime = millis();       // set up for next time;
    }

    // === Move the ball if its time to do so
    if (millis() - prevTime >= speed())
    {
      PRINTS("\n>>PLAY - ");

      drawBall(ballX, ballY, false);

      // new ball positions
      ballX += deltaX;
      ballY += deltaY;

      // check for edge collisions
      if (ballX >= COL_SIZE - 1 || ballX <= 0)   // side bounce
      {
        PRINTS("side bounce");
        deltaX *= -1;
      }
      if (ballY <= 0)
      {
        PRINTS("top bounce");
        deltaY *= -1;  // top bounce
      }

      //=== Check for side bounce/bat collision
      if (ballY == batY - 1 && deltaY == 1)  // just above the bat and travelling towards it
      {
        score += 1;
        displayNumber(score);
        PRINT("check bat x=", batX); PRINTS(" - ");
        if ((ballX >= batX) && (ballX <= batX + BAT_SIZE - 1)) // over the bat - just bounce vertically
        {
          deltaY = -1;
          PRINT("bounce off dy=", deltaY);
        }
        else if ((ballX == batX - 1) || ballX == batX + BAT_SIZE) // hit corner of bat - also bounce horizontal
        {
          deltaY = -1;
          if (ballX != COL_SIZE-1 && ballX != 0)    // edge effects elimination
            deltaX *= -1;
          PRINT("hit corner dx=", deltaX);
          PRINT(" dy=", deltaY);
        }
      }

      drawBall(ballX, ballY);

      // check if end of game
      if (ballY == batY)
      {
        PRINTS("\n>>PLAY - past bat! -> end of game");
        state = END;
        finalScore = score;
        score = 0;
      }

      prevTime = millis();
    }
    break;

  case END:
      delay(20);
      PRINTS("\n>>END");
      sprintf(welcome, "GAME OVER  ");
      digitalWrite(22, HIGH);
      digitalWrite(24, LOW);
      mx.setShiftDataInCallback(scrollDataSource);
      prevTime = 0;
      scrollText();
      if (messageComplete) state = FINAL_SCORE;
      break;

  case FINAL_SCORE:
      delay(20);
      Serial.print("SCORE = ");
      Serial.println(finalScore);
      sprintf(welcome, " SCORE : %d    ", finalScore);
      mx.setShiftDataInCallback(scrollDataSource);
      prevTime = 0;
      scrollText();
      if (messageComplete) state = PLAY_INIT;
      break;
    
   default:
     PRINT("\n>>UNHANDLED !!! ", state);
     state = INIT;
     break;
  }
  delay(50);
}

Procedure

Let’s power up the boards. Make sure the Laptop/Desktop/PC which we’re using should be preinstalled with Arduino IDE and VEGA ARIES boards of latest version.

  • Now, Open the Arduino IDE
  • Create a new Sketch File -> New
  • Copy-paste the above Arduino code to it.
  • Make sure you have selected ARIES v3 Board from Tools -> Board -> VEGA Processor: ARIES Boards -> ARIES v3
  • Select Programmer as VEGA XMODEM from Tools -> Programmer -> VEGA XMODEM
  • Also select appropriate port, Tools -> Port -> COM* (ARIES Board)
  • Now, Upload the code in the ARIES v3 board.

Output

Once the code is uploaded, game name will display on 8X8 LED Matrix i.e., PONG. After that bat and ball will display on screen. To start the game, move the joystick to the left or right.

Live score will display on 4-Digit display.

Leave a Reply

Your email address will not be published. Required fields are marked *