Hackster is hosting Hackster Holidays, Finale: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Tuesday!Stream Hackster Holidays, Finale on Tuesday!
John Bradnam
Published © GPL3+

Matrix Game Console

A generic 32k Flash, 2k RAM Game Console with an 8x8 Red/Green LED matrix, Speaker, X-Pad and 3 support buttons.

IntermediateFull instructions provided12 hours620
Matrix Game Console

Things used in this project

Hardware components

ATTINY3216
Microchip ATTINY3216
×1
STP16CPS05 16 Channel Constant Current LED driver
×1
74HC138 3 to 8 line decoder/driver
×1
LM1117-5 5V Regulator
×1
60mmx60mm Red/Green LED Common Anode Matrix
×1
AO3401 P-Channel MOSFET
×8
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
6mm Shafts
×7
8mm x 8mm On/Off push switch
With button cap
×1
Buzzer
Buzzer
×1
Passive Components
8 x 1k 0805 resistor, 8 x 100R 0805 resistor, 1 x 2K7 0805 resistor, 3 x 0.1uF 0805 capacitor, 1 x 10uF 206 ceramic capacitor, 1 x 220uF/10V tantalum capacitor
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

Files for 3D printing

Schematics

Schematic

PCB

Eagle Files

Schematic and PCB in Eagle format

Code

AsteroidsV1.ino

C/C++
/**************************************************************************
 Asteroids V1
 Based on MAX72XX LED matrix display asteroids game by Joshua Wener

 Author: John Bradnam (jbrad2089@gmail.com)
 
 2021-06-28
  Create program for ATtiny3216
  
 --------------------------------------------------------------------------
 Setup:
 --------------------------------------------------------------------------
  CPU:  ATtiny3216
  millis()/micros() Timer: "TCD0 (1-series only, default there)"
  Display: 8x8 Red/Green Matrix

 --------------------------------------------------------------------------
 Arduino IDE:
 --------------------------------------------------------------------------
  BOARD: 20pin tinyAVR 0/1/2 Series
  Chip: ATtiny3216
  Clock Speed: 20MHz
  Programmer: jtag2updi (megaTinyCore)

  ATtiny3216 Pins mapped to Ardunio Pins
                            _____
                    VDD   1|*    |20  GND
   (nSS)  (AIN4) PA4  0~  2|     |19  16~ PA3 (AIN3)(SCK)(EXTCLK)
          (AIN5) PA5  1~  3|     |18  15  PA2 (AIN2)(MISO)
   (DAC)  (AIN6) PA6  2   4|     |17  14  PA1 (AIN1)(MOSI)
          (AIN7) PA7  3   5|     |16  17  PA0 (AIN0/nRESET/UPDI)
          (AIN8) PB5  4   6|     |15  13  PC3
          (AIN9) PB4  5   7|     |14  12  PC2
   (RXD) (TOSC1) PB3  6   8|     |13  11~ PC1 (PWM only on 1-series)
   (TXD) (TOSC2) PB2  7~  9|     |12  10~ PC0 (PWM only on 1-series)
   (SDA) (AIN10) PB1  8~ 10|_____|11   9~ PB0 (AIN11)(SCL)
 

  PA0 to PA7, PB0 to PB5, PC0 to PC3 can be analog or digital
  PWM on D0, D1, D7, D8, D9, D10, D11, D16
  
 **************************************************************************/

//Display functions to drive RED/GREEN matrix
#include "Display.h"

#define SW_BA 9        //PB0
#define SW_BB 10       //PC0
#define SW_BC 11       //PC1
#define SW_LF 3        //PA7
#define SW_RG 2        //PA6
#define SW_UP 1        //PA5
#define SW_DN 0        //PA4

#define SPEAKER 12     //PC2

#define COLOR_SHIP srn::C_RED
#define COLOR_ALIEN srn::C_GRN
#define COLOR_EXPLODE srn::C_ORG
#define COLOR_ERASE srn::C_BLK

// buttons
volatile unsigned long buttonPressed;
#define BUTTON_DELAY 150    //Debounce timeout

// score
int score;                  //Current score
bool switchText;            //Used to switch between game name and score
volatile bool gameOver;     //Status of game

// "rythm" of the game, in milliseconds
unsigned int tick;
int8_t tickCounter = 1;

// timestamp
unsigned long now;

// display
int ship;                           //Initial ship position
int columns[] = {0,0,0,0,0,0,0,0};  //8 columns, every int is row 1-8.
int randomInt;

//-------------------------------------------------------------------------
//Initialise Hardware

void setup()
{
  //Setup screen
  srn::setup();
  srn::clearDisplay();     //Clear the primary buffer
  srn::refresh();          //Transfer to display buffer

  //Setup sound
  pinMode(SPEAKER,OUTPUT);

  gameOver = true;
  randomSeed(analogRead(15)); // better random numbers

  pinMode(SW_LF, INPUT_PULLUP);
  pinMode(SW_RG, INPUT_PULLUP);
  pinMode(SW_BA, INPUT_PULLUP);

  /* attach button press to interrupts */
  attachInterrupt(digitalPinToInterrupt(SW_LF), left, FALLING);
  attachInterrupt(digitalPinToInterrupt(SW_RG), right, FALLING);
  
  delay(500);
  srn::scrollDelay = 0;     //Force refresh of scrolling text
}

//-------------------------------------------------------------------------
// handle left button press

void left()
{
  if (!gameOver && (millis() - buttonPressed > BUTTON_DELAY)) // handle switch contact bounce
  {
    ship = ((ship + 8) - 1) & 0x07;
    srn::clearDisplay();
    buttonPressed = millis();
  }
}

//-------------------------------------------------------------------------
//  handle right button press

void right()
{
  if (!gameOver && (millis() - buttonPressed > BUTTON_DELAY)) // handle switch contact bounce
  {
    ship = (ship + 1) & 0x07;
    srn::clearDisplay();
    buttonPressed = millis();
  }
}

//-------------------------------------------------------------------------
//  initialise a game

void setupGame()
{
  score = 0;
  tick = 300;
  tickCounter = 1;
  ship = 3;
  for(int i=0; i < 8; i++)
  {
    columns[i] = 0;
  }
  now = millis();
  buttonPressed = millis();
  gameOver = false;
  
  srn::scrollDelay = 0;     //Stop scrolling text
  srn::clearDisplay();
  srn::refresh();
}

//-------------------------------------------------------------------------
// Handle interactions
void loop()
{
  if (!gameOver)
  {
    if (millis() - now > tick)  //do every tick
    {
      // score is: how many ticks you survived
      score++;
    
      now = millis();
      if (tickCounter == 0) { //every 4th tick
  
         // make game faster over time
         tick = tick/1.02;
  
        // randomly choose column
        randomInt = random(0, 8);
  
        // if no asteroid exists in column, create one in row 1.
        if(columns[randomInt] == 0)
        {  
          columns[randomInt] = 1;
        }
      }
      tickCounter = (tickCounter + 1) & 0x03;
  
      // do for every column
      for (int i = 0; i<8; i++){
        
        if (columns[i] == 10) // delete asteroids when out of display
        {
          columns[i] = 0;
        }
        else if (columns[i] != 0) // make asteroids fall down
        {
          columns[i]++;
        }
      }
  
      srn::clearDisplay();
    }
  
  
    /* write to display */
    // ship
    srn::setPixel(7, ship, COLOR_SHIP);
  
    // asteroids
    for (int i = 0; i<8; i++)
    {
      if (columns[i] > 0)
      {
        srn::setPixel(columns[i]-2, i, COLOR_ALIEN);
        srn::setPixel(columns[i]-3, i, COLOR_ALIEN);
      }
    }
  
    srn::refresh();
  
    // detect collision of ship with asteroid
    if (columns[ship] == 10 or columns[ship] == 9)
    {
      srn::clearDisplay();
  
      // animate explosion
      for(int i = 0; i<4; i++){
        srn::setPixel(7, ship+i, COLOR_EXPLODE);
        srn::setPixel(7, ship-i, COLOR_EXPLODE);
        srn::setPixel(7-i, ship+i, COLOR_EXPLODE);
        srn::setPixel(7-i, ship-i, COLOR_EXPLODE);
        srn::setPixel(7-1.5*i, ship, COLOR_EXPLODE);
        srn::refresh();
  
        // explosion sound
        unsigned long time = millis();
        int randomSound=1000;
        while(millis() - time <= 250)  {  
          randomSound--;
          tone(SPEAKER, random(randomSound, 1000));   // change the parameters of random() for different sound
        }
  
        srn::clearDisplay();
        noTone(SPEAKER);
      }
  
      delay(500);
      switchText = true;
      gameOver = true;
    }
  }

  if (gameOver)
  {
    //wait for button A to be pressed
    if (digitalRead(SW_BA) == LOW)
    {
      setupGame();            
    }
    else if (srn::scrollDelay == 0)
    {
      if (score > 0 && switchText)
      {
        srn::drawString("SCORE:" + String(score), COLOR_ALIEN);
      }
      else
      {
        srn::drawString("ASTERIODS", COLOR_SHIP);
      }
      switchText = !switchText;
    }
  }
}

Display.h

C/C++
/*
Namespace: Display
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: 8x8 Red/Green Matrix display routines
*/
#pragma once
#include <SPI.h>// SPI Library used to clock data out to the shift registers

namespace srn
{
  
#define LATCH_PIN 13   //PC3 can use any pin you want to latch the shift registers
#define BLANK_PIN 8    //PB1 same, can use any pin you want for this, just make sure you pull up via a 1k to 5V
#define DATA_PIN 14    //PA1 used by SPI, must be pin 11
#define CLOCK_PIN 16   //PA3 used by SPI, must be 13
#define ANODE_A 7      //PB2 74138 A Input
#define ANODE_B 6      //PB3 74138 B Input
#define ANODE_C 5      //PB4 74138 C Input

#define BLANK_PORT    PORTB
#define BLANK_BM      PIN1_bm
#define LATCH_PORT    PORTC
#define LATCH_BM      PIN3_bm

#define ROWS 8            //Number of rows of LEDs
#define LEDS_PER_ROW 16   //Number of leds on each row
#define BYTES_PER_ROW 2   //Number of bytes required to hold one bit per LED in each row

enum CMODE { C_BLACK, C_RED, C_GRN, C_ORG };

//Bit buffer for matrix
byte ledStates[ROWS][BYTES_PER_ROW];  //Store state of each LED (either off or on)
byte ledNext[ROWS][BYTES_PER_ROW];    //Double buffer for fast updates
int8_t activeRow = 0;                 //this increments through the anode levels

#define SCROLL_SPEED 30               //Speed at which text is scrolled
String scrollText;                    //Used to store scrolling text
volatile int8_t scrollDelay;          //Used to store scroll delay
volatile int8_t scrollCharPos;        //Current character position in text being scrolled
volatile int8_t scrollCharCol;        //Next column in character to display
volatile int8_t scrollOffScreen;      //Extra columns required to scroll last character off screen
volatile CMODE scrollColor;           //String color

//Font
#define SPACE 16
const uint8_t font5x7 [43][5] PROGMEM = {
  {0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0 
  {0x00, 0x42, 0x7F, 0x40, 0x00}, // 1 
  {0x42, 0x61, 0x51, 0x49, 0x46}, // 2 
  {0x21, 0x41, 0x45, 0x4B, 0x31}, // 3 
  {0x18, 0x14, 0x12, 0x7F, 0x10}, // 4 
  {0x27, 0x45, 0x45, 0x45, 0x39}, // 5 
  {0x3C, 0x4A, 0x49, 0x49, 0x30}, // 6 
  {0x01, 0x71, 0x09, 0x05, 0x03}, // 7 
  {0x36, 0x49, 0x49, 0x49, 0x36}, // 8 
  {0x06, 0x49, 0x49, 0x29, 0x1E}, // 9 
  {0x00, 0x36, 0x36, 0x00, 0x00}, // : 
  {0x00, 0x56, 0x36, 0x00, 0x00}, // ; 
  {0x08, 0x14, 0x22, 0x41, 0x00}, // < 
  {0x14, 0x14, 0x14, 0x14, 0x14}, // = 
  {0x00, 0x41, 0x22, 0x14, 0x08}, // > 
  {0x02, 0x01, 0x51, 0x09, 0x06}, // ? 
  {0x00, 0x00, 0x00, 0x00, 0x00}, // Space 
  {0x7C, 0x12, 0x11, 0x12, 0x7C}, // A 
  {0x7F, 0x49, 0x49, 0x49, 0x36}, // B 
  {0x3E, 0x41, 0x41, 0x41, 0x22}, // C 
  {0x7F, 0x41, 0x41, 0x22, 0x1C}, // D 
  {0x7F, 0x49, 0x49, 0x49, 0x41}, // E 
  {0x7F, 0x09, 0x09, 0x09, 0x01}, // F 
  {0x3E, 0x41, 0x49, 0x49, 0x7A}, // G 
  {0x7F, 0x08, 0x08, 0x08, 0x7F}, // H 
  {0x00, 0x41, 0x7F, 0x41, 0x00}, // I 
  {0x20, 0x40, 0x41, 0x3F, 0x01}, // J 
  {0x7F, 0x08, 0x14, 0x22, 0x41}, // K 
  {0x7F, 0x40, 0x40, 0x40, 0x40}, // L 
  {0x7F, 0x02, 0x0C, 0x02, 0x7F}, // M 
  {0x7F, 0x04, 0x08, 0x10, 0x7F}, // N 
  {0x3E, 0x41, 0x41, 0x41, 0x3E}, // O 
  {0x7F, 0x09, 0x09, 0x09, 0x06}, // P 
  {0x3E, 0x41, 0x51, 0x21, 0x5E}, // Q 
  {0x7F, 0x09, 0x19, 0x29, 0x46}, // R 
  {0x46, 0x49, 0x49, 0x49, 0x31}, // S 
  {0x01, 0x01, 0x7F, 0x01, 0x01}, // T 
  {0x3F, 0x40, 0x40, 0x40, 0x3F}, // U 
  {0x1F, 0x20, 0x40, 0x20, 0x1F}, // V 
  {0x3F, 0x40, 0x38, 0x40, 0x3F}, // W 
  {0x63, 0x14, 0x08, 0x14, 0x63}, // X 
  {0x07, 0x08, 0x70, 0x08, 0x07}, // Y 
  {0x61, 0x51, 0x49, 0x45, 0x43}  // Z 
};

//-------------------------------------------------------------------------
//Forward references
void setup();
void refresh();
void drawString(String s, CMODE color);
void scrollTextLeft();
void drawCharacter(int8_t x, char ch, CMODE color);
void setPixel(int8_t r, int8_t c, CMODE color);
CMODE getPixel(int8_t r, int8_t c);
int8_t mapColumn(int8_t c, bool g);
int8_t mapColumnRed(int8_t c);
int8_t mapColumnGrn(int8_t c);
void setBitInArray(int8_t r, int8_t c, bool on);
bool getBitInArray(int8_t r, int8_t c);
void clearDisplay();

//-------------------------------------------------------------------------
//Initialise Hardware

void setup()
{
  SPI.setBitOrder(MSBFIRST);//Most Significant Bit First
  SPI.setDataMode(SPI_MODE0);// Mode 0 Rising edge of data, keep clock low
  SPI.setClockDivider(SPI_CLOCK_DIV2);//Run the data in at 16MHz/2 - 8MHz

  noInterrupts();// kill interrupts until everybody is set up
  activeRow = 0;

  //Set up display refresh timer
  //CLK_PER = 3.3MHz (303nS)
  TCB1.CCMP = 49152;   //Refresh value for display (67Hz)
  TCB1.INTCTRL = TCB_CAPT_bm;
  TCB1.CTRLA = TCB_ENABLE_bm;

  //finally set up the Outputs
  pinMode(LATCH_PIN, OUTPUT);//Latch
  pinMode(DATA_PIN, OUTPUT);//MOSI DATA
  pinMode(CLOCK_PIN, OUTPUT);//SPI Clock
  //Setup pins to 3 to 8 channel multiplexer
  pinMode(ANODE_A, OUTPUT);
  pinMode(ANODE_B, OUTPUT);
  pinMode(ANODE_C, OUTPUT);

  //pinMode(BLANK_PIN, OUTPUT);//Output Enable  important to do this last, so LEDs do not flash on boot up
  SPI.begin();//start up the SPI library
  interrupts();//let the show begin, this lets the multiplexing start
}

//-------------------------------------------------------------------------
//Timer B Interrupt handler interrupt each mS - output segments
ISR(TCB1_INT_vect)
{

  BLANK_PORT.OUTSET = BLANK_BM; //The first thing we do is turn all of the LEDs OFF, by writing a 1 to the blank pin
  
  //Turn on all columns
  for (int shift_out = 0; shift_out < BYTES_PER_ROW; shift_out++)
  {
    SPI.transfer(ledStates[activeRow][shift_out]);
  }

  //Enable row that we just outputed the column data for
  digitalWrite(ANODE_A, (activeRow & 0x01) ? LOW : HIGH);
  digitalWrite(ANODE_B, (activeRow & 0x02) ? LOW : HIGH);
  digitalWrite(ANODE_C, (activeRow & 0x04) ? LOW : HIGH);

  LATCH_PORT.OUTSET = LATCH_BM; //Latch pin HIGH
  LATCH_PORT.OUTCLR = LATCH_BM; //Latch pin LOW
  BLANK_PORT.OUTCLR = BLANK_BM; //Blank pin LOW to turn on the LEDs with the new data

  activeRow = (activeRow + 1) % ROWS;   //increment the active row
  
  BLANK_PORT.DIRSET = BLANK_BM; //Make BLANK pin an OUTPUT

  //Handle scrolling of text
  if (scrollDelay != 0)
  {
    scrollDelay--;
    if (scrollDelay == 0)
    {
      scrollDelay = SCROLL_SPEED;
      scrollTextLeft();
    }
  }

  //Clear interrupt flag
  TCB1.INTFLAGS |= TCB_CAPT_bm; //clear the interrupt flag(to reset TCB1.CNT)
}

//---------------------------------------------------------------
//Transfers the working buffer to the display buffer
void refresh()
{
  memcpy(ledStates, ledNext, ROWS * BYTES_PER_ROW);
}

//---------------------------------------------------------------
//Draw string
// s = String to display
// col = color to show
void drawString(String s, CMODE color)
{
  if (scrollDelay == 0)
  {
    scrollText = s;
    scrollColor = color;
    scrollCharPos = 0;
    scrollCharCol = 0;
    scrollOffScreen = 0;
    scrollDelay = SCROLL_SPEED;     //Starts scrolling
  }
}

//---------------------------------------------------------------
//Scroll text left
void scrollTextLeft()
{
  uint8_t bits;
  uint8_t mask;

  //Scroll screen buffer left
  for (int8_t c = 0; c < 8; c++)
  {
    for (int8_t r = 0; r < 8; r++)
    {
      setPixel(r, c, (c == 7) ? C_BLACK : getPixel(r, c + 1));
    }
  }

  //Sixth character column is blank for letter spacing
  if (scrollOffScreen == 0 && scrollCharCol < 5)
  {
    char ch = scrollText[scrollCharPos];
    if (ch >= 48 && ch <= 90)
    {
      //Get bits in the next column and output to buffer
      bits = pgm_read_byte(&font5x7[ch-48][scrollCharCol]);
      mask = 0x40;
      for(int8_t r = 0; r < 7; r++)
      {
        if (bits & mask)
        {
          setPixel(7 - r, 7, scrollColor);
        }
        mask = mask >> 1;
      }
    }
  }

  if (scrollOffScreen > 0)
  {
    scrollOffScreen--;
    if (scrollOffScreen == 0)
    {
      //Stop scrolling
      scrollDelay = 0;
    }
  }
  else
  {
    scrollCharCol++;
    if (scrollCharCol == 6)
    {
      scrollCharCol = 0;
      scrollCharPos++;
      if (scrollCharPos == scrollText.length())
      {
        //All text has been outputted, just wait until it is scrolled of the screen
        scrollOffScreen = 8;
      }
    }
  }
  refresh();
}

//---------------------------------------------------------------
//Draw character
// x = column (0-7) 0 being farest left hand and 7 being farest right
// ch = ASCII character
// col = color to show
void drawCharacter(int8_t x, char ch, CMODE color)
{
  uint8_t bits;
  uint8_t mask;
  if (ch >= 48 && ch <= 90)
  {
    ch = ch - 48;
    for(int c = 0; c < 6; c++)
    {
      int8_t pos = c + x;
      if (pos >= 0 && pos < 8)
      {
        if (c == 5)
        {
          //Blank column for character spacing
          for(int r = 0; r < 7; r++)
          {
            setPixel(r, pos, C_BLACK);
          }
        }
        else
        {
          bits = pgm_read_byte(&font5x7[ch][c]);
          mask = 0x40;
          for(int r = 0; r < 7; r++)
          {
            if (bits & mask)
            {
              setPixel(7 - r, pos, color);
            }
            mask = mask >> 1;
          }
        }
      }
    }
  }
}

//---------------------------------------------------------------
//Sets the bit in the 6 byte array that corresponds to the physical column and row
// r = row (0 - top row to 7 - bottom row)
// c = column (0 to 7)
// color = color to set pixel
// on = true to switch bit on, false to switch bit off
void setPixel(int8_t r, int8_t c, CMODE color)
{
  if (r >= 0 && r < 8 && c >= 0 && c < 8)
  {
    switch (color)
    {
      case C_BLACK:
        setBitInArray(r, mapColumnRed(c), false);
        setBitInArray(r, mapColumnGrn(c), false);
        break;
            
      case C_RED:
        setBitInArray(r, mapColumnRed(c), true);
        setBitInArray(r, mapColumnGrn(c), false);
        break;
      
      case C_GRN:
        setBitInArray(r, mapColumnRed(c), false);
        setBitInArray(r, mapColumnGrn(c), true);
        break;
      
      case C_ORG:
        setBitInArray(r, mapColumnRed(c), true);
        setBitInArray(r, mapColumnGrn(c), true);
        break;
    }
  }
}

//---------------------------------------------------------------
//Get the color of the bit that corresponds to the physical column and row
// r = row (0 - top row to 7 - bottom row)
// c = column (0 to 7)
// Returns color of pixel (BLACK, RED, GREEN, ORANGE)
CMODE getPixel(int8_t r, int8_t c)
{
  bool red = getBitInArray(r, mapColumnRed(c));
  bool grn = getBitInArray(r, mapColumnGrn(c));
  if (red && grn)
  {
    return C_ORG;
  }
  else if (red)
  {
    return C_RED;
  }
  else if (grn)
  {
    return C_GRN;
  }
  return C_BLACK;
}

//---------------------------------------------------------------
//Maps matrix, column and color to LED column number
// c = column (0-7) 0 being farest left hand and 7 being farest right
// g = green - true for green, false for red
// returns physical column (0 to 47)
int8_t mapColumn(int8_t c, bool g)
{
  return ((c < 4) ? (8 + (c << 1) + ((g) ? 0 : 1)) : (8 - ((c - 3) << 1) + ((g) ? 1 : 0)));
}

//---------------------------------------------------------------
//Maps matrix and red column to LED column number
// c = column (0-7) 0 being farest left hand and 7 being farest right
// returns physical column (0 to 47)
int8_t mapColumnRed(int8_t c)
{
  return ((c < 4) ? (9 + (c << 1)) : (8 - ((c - 3) << 1)));
}

//---------------------------------------------------------------
//Maps matrix and green column and color to LED column number
// c = column (0-7) 0 being farest left hand and 7 being farest right
// returns physical column (0 to 47)
int8_t mapColumnGrn(int8_t c)
{
  return ((c < 4) ? (8 + (c << 1)) : (9 - ((c - 3) << 1)));
}

//---------------------------------------------------------------
//Sets the bit in the 6 byte array that corresponds to the physical column and row
// r = row (0 - top row to 7 - bottom row)
// c = column (0 to 15)
// on = true to switch bit on, false to switch bit off
void setBitInArray(int8_t r, int8_t c, bool on)
{
  uint8_t by = c >> 3;
  uint8_t bi = c - (by << 3);
  //Serial.println("r:" + String(7 - r) + ", c:" + String(c) + ", by:" + String(by) + ", bi:" + String(bi));
  if (on)
  {
    ledNext[7 - r][by] |= (1 << bi);
  }
  else
  {
    ledNext[7 - r][by] &= ~(1 << bi);
  }
}

//---------------------------------------------------------------
//Gets the bit that corresponds to the physical column and row
// r = row (0 - top row to 7 - bottom row)
// c = column (0 to 15)
// returns true if bit on
bool getBitInArray(int8_t r, int8_t c)
{
  uint8_t by = c >> 3;
  uint8_t bi = c - (by << 3);
  return (ledNext[7 - r][by] & (1 << bi));
}

//---------------------------------------------------------------
//Clears the working buffer
void clearDisplay()
{
  //Clear out ledStates array
  for (int r = 0; r < ROWS; r++)
  {
    for (int c = 0; c < BYTES_PER_ROW; c++)
    {
      ledNext[r][c] = 0;
    }
  }
}

} //namespace

Credits

John Bradnam
147 projects • 181 followers
Thanks to Joshua Weßner.

Comments