Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 16 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
|
Today many LED displays use the ubiquitous WS2812 RGB LED. With its built in logic, multiple LEDs can all be controlled via a single wire without any extra components. They come either a single RGB led or combined into a strip or matrix. Internally each WS2812B LED contain three individual LEDs.
The Shift RegisterTo control multiple LEDs via single wire, shift registers are used. A shift register converts a serial data to a parallel form.
A shift register usually has three control pins. The first is the clock. The clock shifts the data through the shift register on each clock cycle. The second pin is the data input. This pin has the logic states that will be shifted through the shift register. The correct data must be available at the data input pin when the clock pin is fired.
If each of the outputs are connected to the LEDs, you would see the data shift through the shift registers. This isn't what we want so a third pin called latch or load is used. This controls a latch that sits between each shift register and the actual output pin.
So the operation is to shift the data into all the shift registers and when that process is complete, open the latch to have the output pins reflect the internal state of each shift register. Then close the latch and shift in the updated data while the outputs continue to show what has latched in.
In a lot of LED matrix or LED cube type projects, it is common to see the 74HC595 IC used. It is a 8-Bit Shift Register with Output Latches. However in this circuit the DM13A chip is used. It is a 16-Bit Shift Register with Output Latches as well as each output having a constant current driver eliminating the resistor normally used in series with each LED to limit its current.
Controlling a matrix of LEDsA 16x16 matrix has 256 LEDs. If we wire each LED to a single Shift Register output pin, we would need 16 x 16-Bit Shift Registers. Rather than do that, another option is to divide the single line of LEDs into 16 sets. Each set is connected to the same single 16-Bit Shift Register. The microprocessor now not only handles the clock, data & load signals of the shift-register but also which of the 16 sets of 16 LEDs that will be shown. If this is done fast enough, persistence of vision will make it seem that all the 16 sets of 16 LEDs are operating as if they were all individually controlled.
In this design, I am using red/green 8x8 matrixes. This means each matrix has 8 cathodes for each column of red LEDs and 8 cathodes for each column of green LEDs. There are 8 anode pins connected to each row (containing 8 red LEDs and 8 green LEDs).
DemonstrationWiringThe wiring between Arduino and PCB is as follows:
LATCH D2
BLANK D4
COLUMN_MSB_IN D11
CLOCK D13
ROW_A D5
ROW_B D6
ROW_C D7
ROW_D D8
Also connect
COLUMN_MSB_OUT to COLUMN_LSB_IN
COLUMN_LSB_OUT not connected
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.
I originally tested the matrix and created the software for it using an Arduino UNO. Once tested, I made a case using 40mm x 9mm dressed pine.
The software was transferred to a Arduino Pro Mini an 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 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_A 5 //74154 A Input (BLU)
#define ANODE_B 6 //74154 B Input (PUR)
#define ANODE_C 7 //74154 C Input (GRY)
#define ANODE_D 8 //74154 D 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];
int row=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(10); 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 = ((TILE_MAX - column) * TILE_SIZE) + (row);
// 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 = row + 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 = row; shift_out < shift_max; shift_out++)
// SPI.transfer(blue0[shift_out]);
for (int shift_out = row; shift_out < shift_max; shift_out++)
SPI.transfer(green0[shift_out]);
for (int shift_out = row; shift_out < shift_max; shift_out++)
SPI.transfer(red0[shift_out]);
break;
case 1:
//for (int shift_out = row; shift_out < shift_max; shift_out++)
// SPI.transfer(blue1[shift_out]);
for (int shift_out = row; shift_out < shift_max; shift_out++)
SPI.transfer(green1[shift_out]);
for (int shift_out = row; shift_out < shift_max; shift_out++)
SPI.transfer(red1[shift_out]);
break;
case 2:
//for (int shift_out = row; shift_out < shift_max; shift_out++)
// SPI.transfer(blue2[shift_out]);
for (int shift_out = row; shift_out < shift_max; shift_out++)
SPI.transfer(green2[shift_out]);
for (int shift_out = row; shift_out < shift_max; shift_out++)
SPI.transfer(red2[shift_out]);
break;
case 3:
//for (int shift_out = row; shift_out < shift_max; shift_out++)
// SPI.transfer(blue3[shift_out]);
for (int shift_out = row; shift_out < shift_max; shift_out++)
SPI.transfer(green3[shift_out]);
for (int shift_out = row; shift_out < shift_max; shift_out++)
SPI.transfer(red3[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
row = (row + 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()
{
clean();
delay(1000);
fill(15, 0, 0);
delay(1000);
fill(0, 15, 0);
delay(1000);
fill(15, 15, 0);
delay(1000);
//fill(0, 0, 15);
//delay(1000);
clean();
for (int c = 1; c <= 3; 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,0);
delay(40);
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),0);
LED(random(TILE_SIZE),random(TILE_SIZE),random(16),0 ,random(16));
//LED(random(TILE_SIZE),random(TILE_SIZE),0, 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) {
for (int d = 0; d < halfSize; d++) {
int red = ((iteration & 1) == 0) ? 15 - (d * 2) : 0;
int green = ((iteration & 1) != 0) ? 15 - (d * 2) : 0;
int blue = 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 = 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);
}
//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);
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;
while ((millis() < endTime) && (!allDead)) {
// Birth and death cycle
delay(100);
allDead = 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;
}
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);
}
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;
}
}
red = 15 - red;
green = 15 - green;
//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 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, 0, 15, 0);
delay(10);
}
}
for (int x = 0; x < TILE_SIZE; x = x + 2) {
for (int y = 0; y < TILE_SIZE; y++) {
LED(y, x, 0, 15, 0);
delay(10);
}
}
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, 15, 15, 0);
}
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, 15, 15, 0);
}
delay(10);
}
}
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, 15, 0, 0);
}
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, 15, 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, 15, 0);
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;
}
spiralAngle = 0;
spiralOffset = 2 * M_PI / 3;
for (int i = 0; i < 950; i++) {
int y = spiralAngle * cos(spiralAngle + spiralOffset) + halfSize;
int x = spiralAngle * sin(spiralAngle + spiralOffset) + halfSize;
LED(y, x, 15, 15, 0);
spiralAngle = spiralAngle + spiralStep;
}
spiralAngle = 0;
spiralOffset = (2 * M_PI / 3) * 2;
for (int i = 0; i < 950; i++) {
int y = spiralAngle * cos(spiralAngle + spiralOffset) + halfSize;
int x = spiralAngle * sin(spiralAngle + spiralOffset) + halfSize;
LED(y, x, 0, 15, 0);
spiralAngle = spiralAngle + spiralStep;
}
delay (1000);
//Next iteration
iteration++;
}
}
/*------------------------------------------------------------------------------------------------------------------------------------
Particles
x = v*cos(a)*t
y = v*sin(a)*t - gt
*/
void drawParticles(unsigned long runtimeInSeconds)
{
#define MAX_PARTICLES 8
int halfSize = TILE_SIZE / 2;
int last[MAX_PARTICLES];
int iteration = 0;
unsigned long endTime = millis() + (runtimeInSeconds * 1000);
while (millis() < endTime) {
//Initialise
clean();
//Fire firework
int lx = 0;
int ly = 0;
for (int t = 0; t < halfSize; t++) {
if (t != 0) {
LED(ly, lx, 0, 0, 0);
}
ly = TILE_SIZE - t - 1;
lx = t;
LED(ly, lx, 15, 15, 0);
delay(40);
}
float halfGravity = 9.8 / 2;
float timeStep = 0.01;
float time = 0;
for (int t = 0; t < 600; t++) {
float timeSquared = time * time;
float angle = 0;
float angleStep = 2 * PI / MAX_PARTICLES;
bool finished = true;
for(int a = 0; a < MAX_PARTICLES; a++) {
int red = ((a & 1) == 0) ? 15 : 0;
int green = ((a & 1) != 0) ? 15 : 0;
int v = halfSize;
int x = halfSize + floor(v * cos(angle) * time);
int y = halfSize - 1 - floor(v * sin(angle) * time - (halfGravity * timeSquared));
if (t != 0) {
ly = floor(last[a] / TILE_SIZE);
lx = last[a] % TILE_SIZE;
LED(ly, lx, 0, 0, 0);
}
if ((y >= 0) && (y < TILE_SIZE) && (x >= 0) && (x < TILE_SIZE)) {
LED(y, x, red, green, 0);
finished = false;
}
last[a] = y * TILE_SIZE + x;
angle = angle + angleStep;
}
time = time + timeStep;
if (finished)
break;
}
//delay (1000);
...
This file has been truncated, please download it to see its full contents.
Comments