Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 3 | ||||
| × | 1 | ||||
| × | 16 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
|
This is an extension to my previous Red/Green Disco Tile. By using Red/Green/Blue 8x8 matrixes, more colors can be displayed. The only change to the circuit of the Red/Green variant was to add another DM13A shift register to handle the cathodes of the blue LEDs in the Red/Green/Blue Matrix. The anodes of the blue LEDs connect to those of the red and green LEDs.
Rather than repeat the description of how a shift register works (see the description of the Red/Green Disco Tile), I would try an explain a technique called Bit Angle Modulation or BAM for short. Before I do that, let's look at Pulse Width Modulation (PWM) first.
Pulse Width Modulation (PWM)To light an LED, you can apply a steady current to the LED usually via a resistor to limit the current so the LED doesn't burn out. You can vary the brightness by varying the current or in this case by varying the resistance. Note that the current following in the LED is continuous.
In Pulse Width Modulation, the current is switched on and off at a speed that our eyes/brain see as a continuous light. This is called Persistence Of Vision (POV). By switching on and off the LED very quickly, the brightness of that LED will depend on the length of the ON state compared to the OFF state. For example and LED that is only on for 50% of the time period will be half as bright as one that is on for 100% of the time period.
Note that in PWM the full cycle time is constant. If one full cycle is broken down into 256 points, then a setting of 128 means a 50% duty cycle. This is what the function AnalogWrite is doing when you control a servo using a PWM enabled pin on an Arduino.
By controlling the brightness of each red, green and blue LEDs inside the RGB LED, you can fool the eye in seeing a wider range of colors than just the 3 primary colors.
Bit Angle Modulation (BAM)BAM differs in that the ON time isn't always done as a single duty cycle. BAM follows a binary pattern.
Because the matrix has only three 16-bit Shift Registers, it can only have 48 columns (16 red, 16 green and 16 blue). This means it also must have 16 rows that are need to be multiplexed. That is each row must be displayed in turn at a speed fast enough to maintain persistence of vision.
Using PWM, all 768 LEDs (256 Red, 256 Green and 256 Blue) would need their associated PWM brightness value tested against the duty cycle counter. Because there are 16 rows to refresh, the loop in the code that refreshes the shift registers must run 16 times faster than the minimum period of the duty cycle timer. Also on each iteration of the loop, it needs to push 48 bits through the shift registers.
However by using BAM, the timing isn't so critical because it doesn't need a duty cycle timer. The final code uses 4 bit BAM allowing each LED to have up to 16 brightness levels (16 bit duty cycle). The main loop cycles through the 16 rows. Before each column value is shifted out, it is compared to the current BAM bits to see it should be switched off.
DemonstrationWiringThe wiring between Arduino and PCB is as follows:
LATCH D2
BLANK D4
DATA D11
CLOCK D13
ROW_A D5
ROW_B D6
ROW_C D7
ROW_D D8
AssemblyThe Schematic and PCB are attached. I have included the Eagle files in case you want to get the board commercially made or do as I did and make it yourself. I used the Toner method.
Since I was making my own board, I kept it single-sided and hand-wired the DM13A output pins to their respective pins on the RGB LED Matrixes using wire-wrap wire.
I made a case using 40mm x 9mm dressed pine.
The software was transferred to a Arduino Pro Mini and covered with a heat shrink sleeve. I used a LM2596 DC-DC Step Down Module set to 5V output so that the Disco Tile can be driven by a 12V power brick.
/*
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.
Comments