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+

Red/Green/Blue Disco Tile

A 16x16 Red/Green/Blue matrix display driven by shift registers, MOSFETs and a Arduino Pro Micro (old school electronics).

IntermediateFull instructions provided12 hours740

Things used in this project

Hardware components

Arduino Pro Mini 328 - 5V/16MHz
SparkFun Arduino Pro Mini 328 - 5V/16MHz
Any variant will do - I used a Arduino Pro Mini but you can use a UNO or Nano
×1
Red/Green/Blue 8x8 LED matrix
60mm x 60mm Common Anode
×1
DM13A 16 Bit Shift Register
×3
74HC154 4 to 16 line decoder/driver
0.3in narrow version
×1
AO3401 P-Channel MOS-FET
×16
Passive Components
3 x 2K7 0805 resistors, 16 x 100R 0805 resistors, 4 x 0.1uF 0805 SMD capacitors
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Schematic

PCB

Magenta, Cyan and Green lines are not part of the PCB. They are manually wired to the board.

Eagle files

Schematic & PCB in Eagle fomat

Code

Dance_Floor_Tile_RGB.ino

C/C++
/*
The 16x16 Red-Green-Blue Dance Floor Tile

by John Bradnam

*/

#include <SPI.h>// SPI Library used to clock data out to the shift registers
#include <math.h>

#define LATCH_PIN 2    //can use any pin you want to latch the shift registers (YEL)
#define BLANK_PIN 4    // same, can use any pin you want for this, just make sure you pull up via a 1k to 5V (GRN)
#define DATA_PIN 11    // used by SPI, must be pin 11 (BRN)
#define CLOCK_PIN 13   // used by SPI, must be 13 (ORG)
#define ANODE_D 5      //74154 D Input (BLU)
#define ANODE_C 6      //74154 C Input (PUR)
#define ANODE_B 7      //74154 B Input (GRY)
#define ANODE_A 8      //74154 A Input (WHI)

#define TILE_SIZE 16			        //Number of columns or rows in Tile
#define TILE_MAX (TILE_SIZE - 1)                //Max tile index
#define LEDS_PER_ROW TILE_SIZE                  //Number of LEDS per row
#define COLOR_ARRAY_SIZE ((TILE_SIZE * TILE_SIZE) / 8)

//***variables***variables***variables***variables***variables***variables***variables***variables
//These variables are used by multiplexing and Bit Angle Modulation Code
//This is how the brightness for every LED is stored,  
//Each LED only needs a 'bit' to know if it should be ON or OFF, so 32 Bytes gives you 256 bits= 256 LEDs
//Since we are modulating the LEDs, using 4 bit resolution, each color has 4 arrays containing 256 bits each
byte red0[COLOR_ARRAY_SIZE], red1[COLOR_ARRAY_SIZE], red2[COLOR_ARRAY_SIZE], red3[COLOR_ARRAY_SIZE];
byte blue0[COLOR_ARRAY_SIZE], blue1[COLOR_ARRAY_SIZE], blue2[COLOR_ARRAY_SIZE], blue3[COLOR_ARRAY_SIZE];
byte green0[COLOR_ARRAY_SIZE], green1[COLOR_ARRAY_SIZE], green2[COLOR_ARRAY_SIZE], green3[COLOR_ARRAY_SIZE];

/*
byte cellMap[TILE_SIZE * TILE_SIZE] = {
  0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
  0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
  0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,
  0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,
  0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,
  0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,
  0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,
  0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,
  0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x8E,0x0F,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
  0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
  0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
  0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
  0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
  0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
  0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
  0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF
};
*/

int cathodeRow=0;//keeps track of which row we are shifting data to
int anodeRow=0;//this increments through the anode rows
int BAM_Bit, BAM_Counter=0; // Bit Angle Modulation variables to keep track of things
int animation = 0; //Keeps track of animation in main loop

//****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup
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

  //Serial.begin(115200);// if you need it?
  noInterrupts();// kill interrupts until everybody is set up

  //We use Timer 1 to refresh the cube
  TCCR1A = B00000000;//Register A all 0's since we're not toggling any pins
  TCCR1B = B00001011;//bit 3 set to place in CTC mode, will call an interrupt on a counter match
  //bits 0 and 1 are set to divide the clock by 64, so 16MHz/64=250kHz
  TIMSK1 = B00000010;//bit 1 set to call the interrupt on an OCR1A match
  OCR1A=15; // you can play with this, but I set it to 30, which means:
  //our clock runs at 250kHz, which is 1/250kHz = 4us
  //with OCR1A set to 30, this means the interrupt will be called every (30+1)x4us=124us, 
  // which gives a multiplex frequency of about 8kHz

  //finally set up the Outputs
  pinMode(LATCH_PIN, OUTPUT);//Latch
  pinMode(DATA_PIN, OUTPUT);//MOSI DATA
  pinMode(CLOCK_PIN, OUTPUT);//SPI Clock
  pinMode(ANODE_A, OUTPUT);//74154 A Input
  pinMode(ANODE_B, OUTPUT);//74154 B Input
  pinMode(ANODE_C, OUTPUT);//74154 C Input
  pinMode(ANODE_D, OUTPUT);//74154 D Input
  digitalWrite(ANODE_A, LOW);
  digitalWrite(ANODE_B, LOW);
  digitalWrite(ANODE_C, LOW);
  digitalWrite(ANODE_D, LOW);
  
  //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
  
  Serial.begin(9600);           // set up Serial library at 9600 bps
  Serial.println("16x16 Matrix");
  randomSeed(micros());
}

//***start loop***start loop***start loop***start loop***start loop***start loop***start loop***start loop***start loop
void loop()
{
  //Each animation located in a sub routine
  // To control an LED, you simply:
  // LED(row you want 0-TILE_MAX,  column you want 0-TILE_MAX, red brighness 0-15, green brighness 0-15, blue brighness 0-15);

  //test_leds();
  clean();
  animation = animation + 1;
  switch (animation) {
    case 1: drawParticles(10); break;
    case 2: drawSpiral(10); break;
    case 3: drawHatch(15); break;
    case 4: playGameOfLife(30); break;
    case 5: playSnake(60); break;
    case 6: randomColor(10); break;
    case 7: squareInOut(20); break;
    case 8: spiralOut(20); break;
    case 99: animation = 0; break;
  }
}

//****LED Routine****LED Routine****LED Routine****LED Routine
void LED(int row, int column, byte red, byte green, byte blue)
{ 
  //This is where it all starts
  //This routine is how LEDs are updated, with the inputs for the LED location and its R G and B brightness levels

  // First, check and make sure nothing went beyond the limits, just clamp things at either 0 or 7 for location, and 0 or 15 for brightness
  row = constrain(row, 0, TILE_MAX);
  column = constrain(column, 0, TILE_MAX);
  red = constrain(red, 0, 15);
  green = constrain(green, 0, 15);
  blue = constrain(blue, 0, 15);
  
  // Get the LED number 0 - 511
  //int wholebyte = cellMap[row * TILE_SIZE + column];
  if ((row > 7) && (column < 8)) {
    column = column + 8;
    row = row - 8;
  }
  else if ((row < 8) && (column > 7)) {
    column = column - 8;
    row = row + 8;
  }
  int wholebyte = row * TILE_SIZE + column;
  
  // Get the index into the array. Each indexed location holds one byte or 8 bits;
  int whichbyte = int(wholebyte / 8);
  int whichbit = (wholebyte & 7);
  //swap high low byte
  whichbyte = (whichbyte ^ 1);
  
  //This will all make sense in a sec

  //This is 4 bit color resolution, so each color contains x4 64 byte arrays, explanation below:
  bitWrite(red0[whichbyte], whichbit, bitRead(red, 0));
  bitWrite(red1[whichbyte], whichbit, bitRead(red, 1));
  bitWrite(red2[whichbyte], whichbit, bitRead(red, 2)); 
  bitWrite(red3[whichbyte], whichbit, bitRead(red, 3)); 

  bitWrite(green0[whichbyte], whichbit, bitRead(green, 0));
  bitWrite(green1[whichbyte], whichbit, bitRead(green, 1));
  bitWrite(green2[whichbyte], whichbit, bitRead(green, 2)); 
  bitWrite(green3[whichbyte], whichbit, bitRead(green, 3));

  bitWrite(blue0[whichbyte], whichbit, bitRead(blue, 0));
  bitWrite(blue1[whichbyte], whichbit, bitRead(blue, 1));
  bitWrite(blue2[whichbyte], whichbit, bitRead(blue, 2)); 
  bitWrite(blue3[whichbyte], whichbit, bitRead(blue, 3));
  
  //Are you now more confused?  You shouldn't be!  It's starting to make sense now.  Notice how each line is a bitWrite, which is,
  //bitWrite(the byte you want to write to, the bit of the byte to write, and the 0 or 1 you want to write)
  //This means that the 'whichbyte' is the byte from 0-63 in which the bit corresponding to the LED from 0-511
  //Is making sense now why we did that? taking a value from 0-511 and converting it to a value from 0-63, since each LED represents a bit in 
  //an array of 64 bytes.
  //Then next line is which bit 'wholebyte-(8*whichbyte)'  
  //This is simply taking the LED's value of 0-511 and subracting it from the BYTE its bit was located in times 8
  //Think about it, byte 63 will contain LEDs from 504 to 511, so if you took 505-(8*63), you get a 1, meaning that,
  //LED number 505 is is located in bit 1 of byte 63 in the array

  //is that it?  No, you still have to do the bitRead of the brightness 0-15 you are trying to write,
  //if you wrote a 15 to RED, all 4 arrays for that LED would have a 1 for that bit, meaning it will be on 100%
  //This is why the four arrays read 0-4 of the value entered in for RED, GREEN, and BLUE
  //hopefully this all makes some sense?

}

//***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM
ISR(TIMER1_COMPA_vect)
{

  //This routine is called in the background automatically at frequency set by OCR1A
  //In this code, I set OCR1A to 30, so this is called every 124us, giving each row in the cube 124us of ON time
  //There are 16 rows, so we have a maximum brightness of 1/16, since the level must turn off before the next level is turned on
  //The frequency of the multiplexing is then 124us*16=1984us, or 1/9=1984us= about 500Hz

  PORTD |= 1 << BLANK_PIN;  //The first thing we do is turn all of the LEDs OFF, by writing a 1 to the blank pin
  //Note, in my bread-boarded version, I was able to move this way down in the cube, meaning that the OFF time was minimized
  //do to signal integrity and parasitic capcitance, my rise/fall times, required all of the LEDs to first turn off, before updating
  //otherwise you get a ghosting effect on the previous level

  //This is 4 bit 'Bit angle Modulation' or BAM, There are 8 levels, so when a '1' is written to the color brightness, 
  //each level will have a chance to light up for 1 cycle, the BAM bit keeps track of which bit we are modulating out of the 4 bits
  //Bam counter is the cycle count, meaning as we light up each level, we increment the BAM_Counter
  if (BAM_Counter == (TILE_SIZE * 1))
    BAM_Bit++;
  else if (BAM_Counter == (TILE_SIZE * 3))
    BAM_Bit++;
  else if (BAM_Counter == (TILE_SIZE * 7))
    BAM_Bit++;

  BAM_Counter++;//Here is where we increment the BAM counter

  int bytes_per_row = LEDS_PER_ROW / 8;
  int shift_max = cathodeRow + bytes_per_row;
  switch (BAM_Bit) {
    //The BAM bit will be a value from 0-3, and only shift out the arrays corresponding to that bit, 0-3
    //Here's how this works, each case is the bit in the Bit angle modulation from 0-4, 
    //Next, it depends on which level we're on, so the byte in the array to be written depends on which level, 
    //For 8x8x8 each level contains 64 LED, we only shift out 8 bytes for each color
    //For 4x4x4 each level contains 16 LED, we only shift out 2 bytes for each color
    case 0:
      for (int shift_out = cathodeRow; shift_out < shift_max; shift_out++)
        SPI.transfer(red0[shift_out]);
      for (int shift_out = cathodeRow; shift_out < shift_max; shift_out++)
        SPI.transfer(green0[shift_out]); 
      for (int shift_out = cathodeRow; shift_out < shift_max; shift_out++)
        SPI.transfer(blue0[shift_out]);
      break;
  
    case 1:
      for (int shift_out = cathodeRow; shift_out < shift_max; shift_out++)
        SPI.transfer(red1[shift_out]);
      for (int shift_out = cathodeRow; shift_out < shift_max; shift_out++)
        SPI.transfer(green1[shift_out]); 
      for (int shift_out = cathodeRow; shift_out < shift_max; shift_out++)
        SPI.transfer(blue1[shift_out]);
      break;
  
    case 2:
      for (int shift_out = cathodeRow; shift_out < shift_max; shift_out++)
        SPI.transfer(red2[shift_out]);
      for (int shift_out = cathodeRow; shift_out < shift_max; shift_out++)
        SPI.transfer(green2[shift_out]); 
      for (int shift_out = cathodeRow; shift_out < shift_max; shift_out++)
        SPI.transfer(blue2[shift_out]);
      break;
  
    case 3:
      for (int shift_out = cathodeRow; shift_out < shift_max; shift_out++)
        SPI.transfer(red3[shift_out]);
      for (int shift_out = cathodeRow; shift_out < shift_max; shift_out++)
        SPI.transfer(green3[shift_out]); 
      for (int shift_out = cathodeRow; shift_out < shift_max; shift_out++)
        SPI.transfer(blue3[shift_out]);
  
      //Here is where the BAM_Counter is reset back to 0, it's only 4 bit, but since each cycle takes 8 counts,
      //, it goes 0 8 16 32, and when BAM_counter hits 64 we reset the BAM
      if (BAM_Counter == (TILE_SIZE * 15)) {
        BAM_Counter=0;
        BAM_Bit=0;
      }
      break;

  }//switch_case

  //SPI.transfer(anode[anodeRow]);//finally, send out the anode level byte
  digitalWrite(ANODE_A, bitRead(anodeRow, 0));
  digitalWrite(ANODE_B, bitRead(anodeRow, 1));
  digitalWrite(ANODE_C, bitRead(anodeRow, 2));
  digitalWrite(ANODE_D, bitRead(anodeRow, 3));

  PORTD |= 1<<LATCH_PIN;//Latch pin HIGH
  PORTD &= ~(1<<LATCH_PIN);//Latch pin LOW
  PORTD &= ~(1<<BLANK_PIN);//Blank pin LOW to turn on the LEDs with the new data

  anodeRow = (anodeRow + 1) & (TILE_MAX);   //inrement the anode level
  cathodeRow = (cathodeRow + bytes_per_row) & (COLOR_ARRAY_SIZE - 1);   //increment the level variable by 8, which is used to shift out data, since the next level would be the next 8 bytes in the arrays

  pinMode(BLANK_PIN, OUTPUT);  //moved down here so outputs are all off until the first call of this function
}


void fill(byte red, byte green, byte blue)
{
  for(int jj=0; jj < TILE_SIZE; jj++) {
    for(int kk=0; kk < TILE_SIZE; kk++) {
      LED(jj,kk,red,green,blue);
    }
  }
}

void clean()
{
  fill(0, 0, 0);
}


//*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE
//*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE
//*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE
//*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE

/*---------------------------------------------------------------------------------------------------
  Test LEDS
*/  
void test_leds()
{
  /*
  // send data only when you receive data:
  if (Serial.peek() > 0) {
    delay(300);
    // read the incoming byte:
    int x = 0;
    int y = 0;
    int v = 0;
    bool inputy = true;
    while (Serial.peek() > 0) {
      char chr = Serial.read();
      //Serial.print("I received: ");
      //Serial.println(chr, HEX);
      if ((inputy) && (chr == ',')) {
        y = v;
        v = 0;
        inputy = false;
      }
      else if ((chr >= '0') && (chr <= '9'))
        v = v * 16 + (int)(chr - '0');
      else if ((chr >= 'A') && (chr <= 'F'))
        v = v * 16 + (int)(chr - 'A') + 10;
      else if ((chr >= 'a') && (chr <= 'f'))
        v = v * 16 + (int)(chr - 'a') + 10;
    }
    x = v;
    clean();
    LED(y,x,15,0,0);
    // say what you got:
    Serial.print("Row: ");
    Serial.print(y, DEC);
    Serial.print(", Column: ");
    Serial.println(x, DEC);
  }
  */

  clean();
  delay(1000);
  fill(15, 0, 0);
  delay(1000);
  fill(0, 15, 0);
  delay(1000);
  fill(0, 0, 15);
  delay(1000);
  clean();
  for (int c = 1; c < 8; c++) {
    for (int y = 0; y < TILE_SIZE; y++) {
      for (int x = 0; x < TILE_SIZE; x++) {
        LED(y,x,((c & 1) > 0) ? 15 : 0,((c & 2) > 0) ? 15 : 0,((c & 4) > 0) ? 15 : 0);
        delay(50);
        LED(y,x,0,0,0);
      }
    }
  }
}

/*---------------------------------------------------------------------------------------------------
  Display random colors
*/
void randomColor(unsigned long runtimeInSeconds) 
{ 
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    for(int m=0; m<3; m++) {
      for(int k=0; k<200; k++) {
        LED(random(TILE_SIZE),random(TILE_SIZE),random(16),random(16),random(16));
        //c1=random(8);
        //c2=random(8);
        //c3=random(8);
        //LED(c1,c2,c3,15,15,15);
      }
      for(int k=0; k<200; k++){
        LED(random(TILE_SIZE),random(TILE_SIZE),0,0,0);
      }
    }//m
    // Clear the cube to ready it for the next iteration. 
    clean();
    //Next iteration
    iteration++;
  }
}  

/*---------------------------------------------------------------------------------------------------
  Display squares
*/
void squareInOut(unsigned long runtimeInSeconds) 
{ 
  int halfSize = TILE_SIZE / 2;
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    int primary = (iteration % 3);
    for (int d = 0; d < halfSize; d++) {
      int red = (primary == 0) ? 15 - (d * 2) : 0;
      int green = (primary == 1) ? 15 - (d * 2) : 0;
      int blue = (primary == 2) ? 15 - (d * 2) : 0;
      for (int x = d; x < (TILE_SIZE - d); x++) {
        LED(d, x, red, green, blue);
        LED(TILE_SIZE - d - 1, x, red, green, blue);
      }
      for (int y = d; y < (TILE_SIZE - d); y++) {
        LED(y, d, red, green, blue);
        LED(y, TILE_SIZE - d - 1, red, green, blue);
      }
      delay(100);
    }
    for (int d = halfSize - 1; d >= 0; d--) {
      int red = 15 - ((halfSize - 1 - d) * 2);
      int green = 15 - ((halfSize - 1 - d) * 2);
      int blue = 15 - ((halfSize - 1 - d) * 2);
      for (int x = d; x < (TILE_SIZE - d); x++) {
        LED(d, x, red, green, blue);
        LED(TILE_SIZE - d - 1, x, red, green, blue);
      }
      for (int y = d; y < (TILE_SIZE - d); y++) {
        LED(y, d, red, green, blue);
        LED(y, TILE_SIZE - d - 1, red, green, blue);
      }
      delay(100);
    }
    //Next iteration
    iteration++;
  }
}  

/*---------------------------------------------------------------------------------------------------
  Display squares
*/

#define SPIRAL_LEFT 0
#define SPIRAL_UP 1
#define SPIRAL_RIGHT 2
#define SPIRAL_DOWN 3

void spiralOut(unsigned long runtimeInSeconds) 
{ 
  int halfSize = TILE_SIZE / 2;
  int penX = 0;
  int penY = 0;
  int newX = 0;
  int newY = 0;
  int red = 0;
  int green = 0;
  int blue = 0;
  int lineDirection = 0;
  int lineLength = 0;
  int iteration = 0;
  int period = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    lineLength = 1;
    if ((iteration & 1) == 0) {
      lineDirection = SPIRAL_LEFT;
      penX = halfSize;
      penY = halfSize;
    }
    else {
      lineDirection = SPIRAL_RIGHT;
      penX = halfSize;
      penY = halfSize - 1;
    }
    red = random(0,8);
    green = random(0,8);
    blue = random(0,8);
    period = random(20, 100);
      
    while ((penX >= 0) && (penX < TILE_SIZE) && (penY >= 0) && (penY < TILE_SIZE)) {
      switch (lineDirection) {
        case SPIRAL_LEFT:
          newX = penX - lineLength;
          for (int x = penX; x >= newX; x--) {
            LED(penY, x, red, green, blue);
          }
          penX = newX;
          lineDirection = SPIRAL_UP;
          lineLength = lineLength + 1;
          break;
          
        case SPIRAL_UP:
          newY = penY - lineLength;
          for (int y = penY; y >= newY; y--) {
            LED(y, penX, red, green, blue);
          }
          penY = newY;
          lineDirection = SPIRAL_RIGHT;
          lineLength = lineLength + 1;
          break;
          
        case SPIRAL_RIGHT:
          newX = penX + lineLength;
          for (int x = penX; x <= newX; x++) {
            LED(penY, x, red, green, blue);
          }
          penX = newX;
          lineDirection = SPIRAL_DOWN;
          lineLength = lineLength + 1;
          break;
          
        case SPIRAL_DOWN:
          newY = penY + lineLength;
          for (int y = penY; y <= newY; y++) {
            LED(y, penX, red, green, blue);
          }
          penY = newY;
          lineDirection = SPIRAL_LEFT;
          lineLength = lineLength + 1;
          break;
      }
      delay(period);
    }
    //Next iteration
    iteration++;
  }
}  

/*---------------------------------------------------------------------------------------------------
    Snake game by John Bradnam
*/
//Global variables and definitions
#define MAX_SNAKE_SIZE 100
#define SNAKE_DOWN 0
#define SNAKE_LEFT 10
#define SNAKE_RIGHT 20
#define SNAKE_UP 30
#define PIECE_EMPTY -1
#define PIECE_SNAKE 0
#define PIECE_APPLE 1
#define APPLE_MOVE_TIMEOUT 5
bool gameOver;
bool gameResult;
int board[TILE_SIZE * TILE_SIZE];
int snakePoints;     //Point interval when snakes speeds up
int snakeDelta;      //Number of mS taken off delay each time it speeds up
int snake[100];
int snakeHead;
int snakeTail;
int snakeApple;
int snakeLength;
int snakeDirection;
int snakeSpeed;
unsigned long appleTimeout;

void playSnake(unsigned long runtimeInSeconds) 
{
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    //Initialise
    clean();
    gameOver = false;
    gameResult = false;
    snakePoints = 10;     //Point interval when snakes speeds up
    snakeDelta = 10;      //Number of mS taken off delay each time it speeds up
    snakeTail   = 0;
    snakeApple  = 78;
    snakeLength = 2;
    snakeHead   = snakeLength - 1;
    snakeDirection = SNAKE_LEFT;
    snakeSpeed  = snakePoints * snakeDelta;
    appleTimeout = 0;
    
    for (int i = 0;i < (TILE_SIZE * TILE_SIZE);i++)
      board[i] = PIECE_EMPTY;
    for (int i = 0;i < snakeLength;i++) {
      snake[i] = 136 - i;
      snakeDraw(snake[i], true);
    }
    appleDraw(snakeApple,true);
    appleTimeout = millis() + (APPLE_MOVE_TIMEOUT * 1000);
    
    //main loop
    while (!gameOver) {
      if (millis() >= appleTimeout) {
        appleMove(true);
        appleTimeout = millis() + (APPLE_MOVE_TIMEOUT * 1000);
      }
      snakeDirection = snakeGetComputerMove();
      snakeMove();
      delay(snakeSpeed); 
    }
    
    //Next iteration
    iteration++;
  }
}

//Move the snake in snakeDirection by adding a pixel to the head and removing a pixel from the tail
void snakeMove() 
{
  int newLocation = 0;
  int x  = snake[snakeHead] % TILE_SIZE;
  int y  = (int)floor(snake[snakeHead] / TILE_SIZE);
  
  switch (snakeDirection) {
    case SNAKE_LEFT: x--; break;
    case SNAKE_RIGHT: x++; break;
    case SNAKE_UP: y--; break;
    case SNAKE_DOWN: y++; break;
  }
  if ((x < 0) || (x == TILE_SIZE) || (y < 0) || (y == TILE_SIZE)) {
    //Game over - You hit the wall!
    //Serial.println("Game over - You hit the wall!");
    gameResult = false;
    gameOver = true;
  }
  else {
    newLocation  = y * TILE_SIZE + x;
    if ((board[newLocation] == PIECE_SNAKE) && (newLocation != snakeTail)) {
      //Game over - You hit yourself!
      //Serial.println("Game over - You hit yourself!");
      gameResult = false;
      gameOver = true;

    }
    else if ((board[newLocation] == PIECE_APPLE) && (snakeLength == (MAX_SNAKE_SIZE - 1))) {
      //Game over - You won!
      //Serial.println("Game over - You won!");
      gameResult = true;
      gameOver = true;
    }
    else {
      if (board[newLocation] == PIECE_APPLE) {
        snakeLength = snakeLength + 1;
        snakeSpeed = ((int)((MAX_SNAKE_SIZE - snakeLength) / snakePoints) * snakeDelta) + snakeDelta;
      }
      else {
        snakeDraw(snake[snakeTail],false);
        snake[snakeTail] = -1;
        snakeTail = snakeTail + 1;
        if (snakeTail >= MAX_SNAKE_SIZE)
          snakeTail = 0;
      }
      snakeHead = snakeHead + 1;
      if (snakeHead >= MAX_SNAKE_SIZE)
        snakeHead = 0;
      snake[snakeHead] = newLocation;
      snakeDraw(newLocation,true);
      if (newLocation == snakeApple) {
        appleMove(false);
        appleTimeout = millis() + (APPLE_MOVE_TIMEOUT * 1000);
      }
    }
  }
}

//Show or hide snake pixel at index
void snakeDraw(int location, bool show)
{
  int x  = location % TILE_SIZE;
  int y  = (int)floor(location / TILE_SIZE);
  board[location] = (show) ? PIECE_SNAKE : PIECE_EMPTY;
  if (show)
    LED(y,x,0,15,0);
  else
    LED(y,x,0,0,0);
}

//Randomly move the apple to a location that isn't used
void appleMove(int hide) 
{
  if (hide)
    appleDraw(snakeApple,false);
  bool foundLocation = false;
  while (!foundLocation) {
    int x = random(0, TILE_SIZE);
    int y = random(0, TILE_SIZE);
    snakeApple = y * TILE_SIZE + x;
    if (board[snakeApple] == PIECE_EMPTY) {
      foundLocation = true;
      appleDraw(snakeApple, true);
    }
  }
}

void appleDraw(int location, bool show)
{
  int x  = location % TILE_SIZE;
  int y  = (int)floor(location / TILE_SIZE);
  board[location] = (show) ? PIECE_APPLE : PIECE_EMPTY;
  if (show)
    LED(y,x,15,0,0);
  else
    LED(y,x,0,0,0);
}

//Computer emulating a player
int snakeGetComputerMove()
{
  int newDirection = snakeDirection;  
  bool turn = false;
  int ax  = snakeApple % TILE_SIZE;
  int ay  = (int)floor(snakeApple / TILE_SIZE);
  int sx  = snake[snakeHead] % TILE_SIZE;
  int sy  = (int)floor(snake[snakeHead] / TILE_SIZE);
  int nx = sx;
  int ny = sy;
  switch (snakeDirection) {
    case SNAKE_LEFT: nx--; break;
    case SNAKE_RIGHT: nx++; break;
    case SNAKE_UP: ny--; break;
    case SNAKE_DOWN: ny++; break;
  }
  if ((nx < 0) || (nx == TILE_SIZE) || (ny < 0) || (ny == TILE_SIZE)) {
    //If we keep going we will hit a wall so we must turn
    turn = true;
  }
  else {
    int ni = ny * TILE_SIZE + nx;
    if (board[ni] == PIECE_SNAKE) {
      //If we keep going we will hit ourselves to se must turn
      turn = true;
    }
    else if (board[ni] == PIECE_APPLE) {
      //If we keep going, we will hit the apple so keep going
      turn = false;
    }
    else if ((snakeDirection == SNAKE_LEFT) || (snakeDirection == SNAKE_RIGHT)) {
      //Turn if we are at the same x axis as the apple
      turn = (sx == ax);
    }
    else {
      //Turn if we are at the same y axis as the apple
      turn = (sy == ay);
    }
  }
  if (turn) {
    //Aways try and turn towards the apple
    if ((snakeDirection == SNAKE_LEFT) || (snakeDirection == SNAKE_RIGHT)) {
      if ((sy < ay) || ((sy == ay) && (random(0,2) == 0))) {
        //Preferrably move down
        if (snakeTestTurn(sx, sy, SNAKE_DOWN))
          newDirection = SNAKE_DOWN;
        else if (snakeTestTurn(sx, sy, SNAKE_UP))
          newDirection = SNAKE_UP;
      }
      else {
        //Preferrably move up
        if (snakeTestTurn(sx, sy, SNAKE_UP))
          newDirection = SNAKE_UP;
        else if (snakeTestTurn(sx, sy, SNAKE_DOWN))
          newDirection = SNAKE_DOWN;
      }
    }
    else {
      if ((sx < ax) || ((sx == ax) && (random(0,2) == 0))) {
        //Preferrably move right
        if (snakeTestTurn(sx, sy, SNAKE_RIGHT))
          newDirection = SNAKE_RIGHT;
        else if (snakeTestTurn(sx, sy, SNAKE_LEFT))
          newDirection = SNAKE_LEFT;
      }
      else {
        //Preferrably move left
        if (snakeTestTurn(sx, sy, SNAKE_LEFT))
          newDirection = SNAKE_LEFT;
        else if (snakeTestTurn(sx, sy, SNAKE_RIGHT))
          newDirection = SNAKE_RIGHT;
      }
    }
  }
  return newDirection;
}
            
bool snakeTestTurn(int tx, int ty, int td)
{
  bool okToTurn = true;
  switch (td) {
    case SNAKE_LEFT: tx--; break;
    case SNAKE_RIGHT: tx++; break;
    case SNAKE_UP: ty--; break;
    case SNAKE_DOWN: ty++; break;
  }
  if ((tx < 0) || (tx > TILE_MAX) || (ty < 0) || (ty > TILE_MAX)) {
    //If we keep going we will hit a wall so we must turn
    okToTurn = false;
  }
  else {
    int ti = ty * TILE_SIZE + tx;
    if (board[ti] == PIECE_SNAKE) {
      //If we keep going we will hit ourselves to se must turn
      okToTurn = false;
    }
  }
  return okToTurn;
}
            
/*---------------------------------------------------------------------------------------------------
    Gaem of life game by John Bradnam
*/
//Global variables and definitions
#define LIFE_DENSITY 50

void playGameOfLife(unsigned long runtimeInSeconds) 
{
  int red = 15;
  int green = 0;
  int blue = 0;
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    //Initialise
    clean();
    
    //Random set start
    int i = 0;
    for (int y = 0; y < TILE_SIZE; y++) {
      for (int x = 0; x < TILE_SIZE; x++) {
        if (random(100) >= LIFE_DENSITY)
          board[i] = 0;
        else {
          board[i] = 1;
          LED(y,x,red,green,blue);
        }
        i++;
      }
    }
    
    bool allDead = false;
    bool noChange = false;
    while ((millis() < endTime) && (!allDead) && (!noChange)) {
      // Birth and death cycle
      delay(100);
      allDead = true;
      noChange = true;
      i = 0;
      for (int y = 0; y < TILE_SIZE; y++) {
        for (int x = 0; x < TILE_SIZE; x++) {
          board[i] = board[i] | (board[i] << 1);
          int count = neighbours(y, x);
          if ((count == 3) && ((board[i] & 0x01) == 0)) {
            //A new cell is born
            board[i] = board[i] | 2;
            LED(y,x,red,green,blue);
            allDead = false;
            noChange = false;
          }
          else if (((count < 2) || (count > 3)) && ((board[i] & 0x01) != 0)) {
            //A cell is dies
            board[i] = board[i] & ~2;
            LED(y,x,0,0,0);
            noChange = false;
          }
          else if ((board[i] & 0x01) != 0)
            allDead = false;
          i++;
        }
      }
      //Transfer new board to old board
      for (int i = 0; i < (TILE_SIZE * TILE_SIZE); i++) {
        board[i] = board[i] >> 1;
      }
    }
    //Change color
    int temp = blue;
    blue = green;
    green = red;
    red = temp;
    //Next iteration
    iteration++;
  }
}

// This function is for the 4 laws in conway's game of life
int neighbours(int y, int x) 
{
  int yt = (((y == 0) ? TILE_SIZE : y) - 1) * TILE_SIZE;
  int yb = ((y + 1) % TILE_SIZE) * TILE_SIZE;
  y = y * TILE_SIZE;
  int xl = ((x == 0) ? TILE_SIZE : x) - 1;
  int xr = (x + 1) % TILE_SIZE;
  return (board[yt+xl] & 1) + (board[yt+x] & 1) + (board[yt+xr] & 1) + (board[y+xl] & 1) + (board[y+xr] & 1) + (board[yb+xl] & 1) + (board[yb+x] & 1) + (board[yb+xr] & 1);
}

/*------------------------------------------------------------------------------------------------------------------------------------
  Hatch
*/

void drawHatch(unsigned long runtimeInSeconds) 
{
  int red = 0;
  int green = 15;
  int blue = 0;
  int temp = 0;
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    //Initialise
    clean();
    for (int y = 0; y < TILE_SIZE; y = y + 2) {
      for (int x = 0; x < TILE_SIZE; x++) {
        LED(y, x, red, green, blue);
        delay(10);
      }
    }
    for (int x = 0; x < TILE_SIZE; x = x + 2) {
      for (int y = 0; y < TILE_SIZE; y++) {
        LED(y, x, red, green, blue);
        delay(10);
      }
    }
    temp = blue;
    blue = green;
    green = red;
    red = temp; 
    for (int y = 1; y < TILE_SIZE; y = y + 2) {
      for (int x = 0; x < TILE_SIZE; x++) {
        if ((x & 3) != 0) {
          LED(y, x, red, green, blue);
        }
        delay(10);
      }
    }
    for (int x = 1; x < TILE_SIZE; x = x + 2) {
      for (int y = 0; y < TILE_SIZE; y++) {
        if ((y & 3) != 0) {
          LED(y, x, red, green, blue);
        }
        delay(10);
      }
    }
    temp = blue;
    blue = green;
    green = red;
    red = temp; 
    for (int y = 2; y < TILE_SIZE; y = y + 4) {
      for (int x = 0; x < TILE_SIZE; x++) {
        if ((x & 3) == 2) {
          LED(y, x, red, green, blue);
        }
        delay(10);
      }
    }
    delay(1000);
    
    //Next iteration
    iteration++;
  }
}

/*------------------------------------------------------------------------------------------------------------------------------------
  Spiral
*/

void drawSpiral(unsigned long runtimeInSeconds) 
{
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    //Initialise
    clean();
    int halfSize = TILE_SIZE / 2;
    float spiralStep = (3 * M_PI) / 1000;
    float spiralAngle = 0;
    float spiralOffset = 0;
    for (int i = 0; i < 950; i++) {
      int x = spiralAngle * cos(spiralAngle) + halfSize;
      int y = spiralAngle * sin(spiralAngle) + halfSize;
      LED(y, x, 15, 0, 0);
      spiralAngle = spiralAngle + spiralStep;
    }
    spiralAngle = 0;
    spiralOffset = 2 * M_PI / 3;
    for (int i = 0; i < 950; i++) {
      int x = spiralAngle * cos(spiralAngle + spiralOffset) + halfSize;
      int y = spiralAngle * sin(spiralAngle + spiralOffset) + halfSize;
      LED(y, x, 0, 15, 0);
      spiralAngle = spiralAngle + spiralStep;
    }
    spiralAngle = 0;
    spiralOffset = (2 * M_PI / 3) * 2;
    for (int i = 0; i < 950; i++) {
      int x = spiralAngle * cos(spiralAngle + spiralOffset) + halfSize;
      int y = spiralAngle * sin(spiralAngle + spiralOffset) + halfSize;
      LED(y, x, 0, 0, 15);
      spiralAngle = spiralAngle + spiralStep;
    }
    delay (1000);
    
    clean();
    spiralAngle = 0;
    spiralOffset = 0;
    for (int i = 0; i < 950; i++) {
      int y = spiralAngle * cos(spiralAngle) + halfSize;
      int x = spiralAngle * sin(spiralAngle) + halfSize;
      LED(y, x, 15, 0, 0);
      spiralAngle = spiralAngle + spiralStep;
    }
...

This file has been truncated, please download it to see its full contents.

Credits

John Bradnam
147 projects • 181 followers
Thanks to Kevin Darrah.

Comments