I have created life!….

Ive been sorting out the workshop here and found a project that I started around 12 years ago. I thought I would finish it and make it do something fun.

The project was an 8 x 8 LED grid. I used high brightness blue LEDs.

I wanted it to display something interesting and varying. I chose Conway’s Game of Life as it is interesting, random, relatively simple rules and keeps modulating.

 There are loads of other projects out there doing much cooler things, but I felt like I ought to finish this project as it had been sitting around for sooo long.


Here is a video of the unit in action:

The LEDs were connected together using waste 1mm copper wire and arranged in a grid so that all the anodes from one column of 8 are connected together and all the cathodes from one row of 8 are connected together.

The board is controlled by an AVR ATmega 328, programmed using the Arduino IDE. I wanted to use fewer pins, so rather than use 8 pins for the anodes and 8 pins for the cathode (16 pins in total), I used a 4017 decade counter to provide an output for the anodes. This has 10 outputs which go high in order when it is clocked. There is a reset pin to return the unit to its initial condition. I only needed 8 outputs, so I connected the 9th output pin to the reset. I also added a clock line and a reset line back to the arduino. Hence I could display everything with 10 pins (8 data, 1 clock, 1 reset). The reset is required to ensure that the unit always starts in the correct position, otherwise the starting line was slightly arbitrary.

This display could be made to display any 8×8 graphic. One day I’ll turn it into a scrolling display.

The home-made Arduino PCB is stuck to the back. The front has been covered with diffuser polypropylene.

Conway’s Game of Life is a mathematical puzzle with a few simple rules. Each ‘cell’ within the grid lives or dies depending upon four simple mathematical rules. These are based upon its neighbours:

  1. Any live cell with fewer than two live neighbours dies, as if caused by under-population.
  2. Any live cell with two or three live neighbours lives on to the next generation.
  3. Any live cell with more than three live neighbours dies, as if by overcrowding.
  4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

This can give rise to some interesting patterns.

I used the code from Jimmie P. Rogers LoL Shield, which already had Life programmed in (although his board uses Charlieplexing in order to control even more LEDs from hardly any output pins).

The Arduino code is here:

/*
 LED 8x8 display board
 by Matt Little
 matt@re-innovation.co.uk
 www.re-innovation.co.uk
 
 Controls an 8x8 LED grid using 8 digital pins and a decade counter.
 
 The grid is connected via digital pins 2-9 for the cathodes 
 and via a 4017 decade counter for the anodes.
 Hence clocking through the decade counter while changing pins 2-9
 will give an output to the display.
 
 4017 decade counter data sheet is available from:
 
			

Click to access 4017.pdf

It is wired so that the clock connects with pin 10 and the reset is wired to output Q8, so only Q0-Q7 are used (8 outputs for the 8 lines). The idea is that we clock through each segment of the display and upload a digital number for the pixels within the segment. If we do this quick enough, we cannot tell that there is any pause and out brain builds up an image (persistance of vision (POV)). We use a lot of port mainipulation here, as it speeds up the code. PortB = Pins 8-13 PortC = Analog input pins PortD = Pins 0-7 This example code is in the public domain. */ // 1-dimensional array of row pin numbers: const int col[8] = { 2,3,4,5,6,7,8,9 }; // The clock pin: int clock = 10; //The reset pin: int resetCounter = 11; // This is used to reset the decade counter and ensure it starts correctly // Array of pixels - these are bytes (8 bit numbers): byte pixels[8] = {B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000}; byte dataD; byte dataB; long int delayCounter = 0; // Used to set the refresh rate for the display // Data for LIFE - #define DELAY 0 //Sets the time each generation is shown #define RESEEDRATE 1000 //Sets the rate the world is re-seeded #define SIZEX 8 //Sets the X axis size #define SIZEY 8 //Sets the Y axis size byte world[SIZEX][SIZEY][2]; //Creates a double buffer world long density = 50; //Sets density % during seeding int geck = 0; //Counter for re-seeding // the setup routine runs once when you press reset: void setup() { // initialize the I/O pins as outputs // iterate over the pins: for (int thisPin = 0; thisPin < 8; thisPin++) { // initialize the output pins: pinMode(col[thisPin], OUTPUT); // take the col pins (i.e. the cathodes) high to ensure that // the LEDS are off: digitalWrite(col[thisPin], HIGH); } pinMode(clock, OUTPUT); digitalWrite(clock, HIGH); // turn the LED on (HIGH is the voltage level) pinMode(resetCounter, OUTPUT); digitalWrite(resetCounter, LOW); // turn the LED on (HIGH is the voltage level) delay(10); digitalWrite(resetCounter, HIGH); // turn the LED on (HIGH is the voltage level) pinMode(resetCounter, INPUT); // Set this pin to be a high impedance input - allows external reset to work for(int y=0;y<7;y++) // This loop just clock through to the correct starting point { digitalWrite(clock, LOW); // turn the LED on (HIGH is the voltage level) delay(1); // very slight delay betwewen segments digitalWrite(clock, HIGH); // turn the LED on (HIGH is the voltage level) } // Initialise the LIFE randomSeed(analogRead(5)); //Builds the world with an initial seed. for (int i = 0; i < SIZEX; i++) { for (int j = 0; j < SIZEY; j++) { if (random(100) < density) { world[i][j][0] = 1; } else { world[i][j][0] = 0; } world[i][j][1] = 0; } } } // the loop routine runs over and over again forever: void loop() { for (int c=0;c<=7;c++) { digitalWrite(clock, LOW); // turn the LED on (HIGH is the voltage level) delayMicroseconds(100); digitalWrite(clock, HIGH); // turn the LED on (HIGH is the voltage level) writeData(pixels[c]); } if(delayCounter>=500) { // Data for LIFE // Birth and death cycle for (int x = 0; x < SIZEX; x++) { for (int y = 0; y < SIZEY; y++) { // Default is for cell to stay the same world[x][y][1] = world[x][y][0]; int count = neighbours(x, y); geck++; if (count == 3 && world[x][y][0] == 0) { // A new cell is born world[x][y][1] = 1; // Here we need to give birth to a life writePixels(x,y,HIGH); } else if ((count < 2 || count > 3) && world[x][y][0] == 1) { // Cell dies world[x][y][1] = 0; // Here we remove a life writePixels(x,y,LOW); } } } //Counts and then checks for re-seeding //Otherwise the display will die out at some point geck++; if (geck > RESEEDRATE){ seedWorld(); geck = 0; } // Copy next generation into place for (int x = 0; x < SIZEX; x++) { for (int y = 0; y < SIZEY; y++) { world[x][y][0] = world[x][y][1]; } } delayCounter = 0; } delayCounter++; // We use a delay loop (rather than a delay), as it keeps th display refreshed. } void writeData(byte data) { // We need to write the bottom 6 bits of data to PORTD (PINS 2-7) // We need to write the top 2 bits of data to PORTB (PINS 8-9) data = data ^ B11111111; // Invert the data (as it displays the opposite. dataD = data<<2; dataB = (data>>6)&B00000011; PORTD = dataD + (PORTD&B00000011); PORTB = dataB + (PORTB&B11111100); } void writePixels(int xx, int yy, boolean on) { // We need to be able toe write to specific pixels to run the game of life // This is done by updating the pixels array // We need to find column y then replace row x with the boolean // Finding the column is easy = pixels[y] // Finding the row is harder. We need to take the 8 bit number in pixels[y] // Then alter just the bit that x relates to. if(on == HIGH) { // We want to set the bit bitSet(pixels[yy],xx); } else { // We want to clear the bit bitClear(pixels[yy],xx); } } //Re-seeds based off of RESEEDRATE void seedWorld(){ randomSeed(analogRead(5)); for (int i = 0; i < SIZEX; i++) { for (int j = 0; j < SIZEY; j++) { if (random(100) < density) { world[i][j][1] = 1; } } } } //Runs the rule checks, including screen wrap int neighbours(int x, int y) { return world[(x + 1) % SIZEX][y][0] + world[x][(y + 1) % SIZEY][0] + world[(x + SIZEX - 1) % SIZEX][y][0] + world[x][(y + SIZEY - 1) % SIZEY][0] + world[(x + 1) % SIZEX][(y + 1) % SIZEY][0] + world[(x + SIZEX - 1) % SIZEX][(y + 1) % SIZEY][0] + world[(x + SIZEX - 1) % SIZEX][(y + SIZEY - 1) % SIZEY][0] + world[(x + 1) % SIZEX][(y + SIZEY - 1) % SIZEY][0]; }

I know there are much better and more complicated LED display boards out there, but I really wanted to finish this project and I learnt quite a lot about bit-wise programming (Cheers for your help, lwk).

One response to “I have created life!….

Leave a Reply

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