Pages: [1] 2   Go Down
Author Topic: Conway's Game Of Life with 128x64 graphic LCD. UPDATE: also with 128x64 SPI OLED  (Read 4237 times)
0 Members and 3 Guests are viewing this topic.
West Yorkshire, UK
Offline Offline
Edison Member
*
Karma: 50
Posts: 1482
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

This is a bit of an "unfinished work", but I though I should write it up before I forget the details!

Conway's game of life, in case you don't know, is a simulation of colonies of cells living on a rectangular grid. Each position on the grid can contain a cell or be empty, and over each generation, a cell can continue to live, or die, or a new cell can be born, depending on a few very simple rules to do with how many cells are living in the immediate neighbourhood.

I originally developed the code on a Nano 3, then transferred it to a stand-alone ATmega328, using the Nano running ArduinoISP to program it. It should be easy to get running on any Arduino with a similar display connected.

Video:


* 20140208_150015.jpg (345.1 KB, 1280x720 - viewed 68 times.)

* 20140208_150155.jpg (225.68 KB, 1280x720 - viewed 63 times.)
« Last Edit: June 05, 2014, 03:35:43 pm by PaulRB » Logged

West Yorkshire, UK
Offline Offline
Edison Member
*
Karma: 50
Posts: 1482
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
// Conway's Game Of Life 128x64
// PaulRB
// Jun 2013
// Sept 2013 updated to use digitalWriteFast

#include <digitalWriteFast.h>

//Pins controlling KS0108 Graphic LCD
const byte GLCD_RS = 12; // H = data, L = instruction
const byte GLCD_E = 8;
const byte GLCD_DATA0 = 7;
const byte GLCD_DATA1 = 6;
const byte GLCD_DATA2 = 5;
const byte GLCD_DATA3 = 9;
const byte GLCD_DATA4 = 10;
const byte GLCD_DATA5 = 11;
const byte GLCD_DATA6 = 13;
const byte GLCD_DATA7 = 4;
const byte GLCD_CS1 = 3; // left half of screen
const byte GLCD_CS2 = 2; // right half of screen

const byte GLCDDelay = 3; // Enable GLCD to keep up with Arduino
const int redrawMethodBoundary = 320;
const byte SpeedPot = A6;

unsigned long Matrix[129][2]; // Cell data in ram

void setup() {
 
  pinModeFast(GLCD_CS1, OUTPUT);
  pinModeFast(GLCD_CS2, OUTPUT);
  pinModeFast(GLCD_RS, OUTPUT);
  pinModeFast(GLCD_E, OUTPUT);
  pinModeFast(GLCD_DATA0, OUTPUT);
  pinModeFast(GLCD_DATA1, OUTPUT);
  pinModeFast(GLCD_DATA2, OUTPUT);
  pinModeFast(GLCD_DATA3, OUTPUT);
  pinModeFast(GLCD_DATA4, OUTPUT);
  pinModeFast(GLCD_DATA5, OUTPUT);
  pinModeFast(GLCD_DATA6, OUTPUT);
  pinModeFast(GLCD_DATA7, OUTPUT);

  digitalWriteFast(GLCD_CS1, HIGH);
  digitalWriteFast(GLCD_CS2, HIGH);
  digitalWriteFast(GLCD_RS, LOW);
  digitalWriteFast(GLCD_E, LOW);

  sendGLCD(B00111111); // Enable display
  sendGLCD(B11000000); // Set Scroll Position = 0
       
  randomiseMatrix();
  outputMatrix();
 
  //Serial.begin(38400);

}

void loop() {
  generateMatrix();
  //delay(analogRead(SpeedPot));
}

void sendGLCD(byte b) { //Send a byte to the GLCD

  static byte oldb; // static variables keep their value between calls

  digitalWriteFast(GLCD_E, HIGH);
  delayMicroseconds(GLCDDelay);
 
  if (b != oldb) { // do we need to change the pins?
    if (b & B00000001) digitalWriteFast(GLCD_DATA0, HIGH); else digitalWriteFast(GLCD_DATA0, LOW);
    if (b & B00000010) digitalWriteFast(GLCD_DATA1, HIGH); else digitalWriteFast(GLCD_DATA1, LOW);
    if (b & B00000100) digitalWriteFast(GLCD_DATA2, HIGH); else digitalWriteFast(GLCD_DATA2, LOW);
    if (b & B00001000) digitalWriteFast(GLCD_DATA3, HIGH); else digitalWriteFast(GLCD_DATA3, LOW);
    if (b & B00010000) digitalWriteFast(GLCD_DATA4, HIGH); else digitalWriteFast(GLCD_DATA4, LOW);
    if (b & B00100000) digitalWriteFast(GLCD_DATA5, HIGH); else digitalWriteFast(GLCD_DATA5, LOW);
    if (b & B01000000) digitalWriteFast(GLCD_DATA6, HIGH); else digitalWriteFast(GLCD_DATA6, LOW);
    if (b & B10000000) digitalWriteFast(GLCD_DATA7, HIGH); else digitalWriteFast(GLCD_DATA7, LOW);
    oldb = b;
  }
 
  digitalWriteFast(GLCD_E, LOW); // data is read by glcd on falling edge
  delayMicroseconds(GLCDDelay);

}

void outputMatrix() {
 
  //Send matrix data for display on GLCD
  for (byte x = 0; x <= 7; x++) {
   
    byte col = x >> 2;
    byte b = (x & 3) << 3;
    digitalWriteFast(GLCD_CS1, HIGH);
    digitalWriteFast(GLCD_CS2, HIGH);
    digitalWriteFast(GLCD_RS, LOW);
    sendGLCD(B01000000); // Set Y = 0
    sendGLCD(B10111000 | x); // set X
    digitalWriteFast(GLCD_RS, HIGH);
    digitalWriteFast(GLCD_CS1, HIGH);
    digitalWriteFast(GLCD_CS2, LOW);
    for (byte row = 0; row <= 127; row++) {
      sendGLCD(Matrix[row][col] >> b);
      if (row == 63) {
        digitalWriteFast(GLCD_CS1, LOW);
        digitalWriteFast(GLCD_CS2, HIGH);
      }
    }
  }
}

void randomiseMatrix() {

  //Set up initial cells in matrix
  randomSeed(analogRead(0));
  for (byte row = 0; row <= 127; row++) {
    for (byte col = 0; col <= 1; col++) {
      Matrix[row][col] = random(0xffff) << 16 | random(0xffff);
    }
  }
}

void injectGlider() {

  byte col = random(127);
  byte row = random(1);
  Matrix[col++][row] |= B0000111;
  Matrix[col++][row] |= B0000001;
  Matrix[col++][row] |= B0000010;

}

int generateMatrix() {

  //Variables holding data on neighbouring cells
  unsigned long NeighbourN[2], NeighbourNW[2], NeighbourNE[2], CurrCells[2], NeighbourW[2], NeighbourE[2], NeighbourS[2], NeighbourSW[2], NeighbourSE[2];

  //Variables used in calculating new cells
  unsigned long tot1[2], carry[2], tot2[2], tot4[2], NewCells[2];
 
  int changes = 0; // counts the changes in the matrix
  static int prevChanges = 256; // counts the changes in the matrix on prev generation
  static int staleCount = 0; // counts the consecutive occurrances of the same number of changes in the matrix

  //set up N, NW, NE, W & E neighbour data
  for (byte b = 0; b <= 1; b++) {
    NeighbourN[b] = Matrix[127][b];
    CurrCells[b] = Matrix[0][b];
    Matrix[128][b] = CurrCells[b];  // copy row 0 to location after last row to remove need for wrap-around code in the loop
  }

  for (byte b = 0; b <= 1; b++) {
    NeighbourNW[b] = NeighbourN[b] >> 1;
    bitWrite(NeighbourNW[b], 31, bitRead(NeighbourN[1-b], 0));
    NeighbourNE[b] = NeighbourN[b] << 1;
    bitWrite(NeighbourNE[b], 0, bitRead(NeighbourN[1-b], 31));

    NeighbourW[b] = CurrCells[b] >> 1;
    bitWrite(NeighbourW[b], 31, bitRead(CurrCells[1-b], 0));
    NeighbourE[b] = CurrCells[b] << 1;
    bitWrite(NeighbourE[b], 0, bitRead(CurrCells[1-b], 31));

  }
 
  digitalWriteFast(GLCD_CS1, HIGH);
  digitalWriteFast(GLCD_CS2, LOW);
 
  //Process each row of the matrix
  for (byte row = 0; row <= 127; row++) {

    //Pick up new S, SW & SE neighbours
    for (byte b = 0; b <= 1; b++) {
      NeighbourS[b] = Matrix[row + 1][b];
      }
 
    for (byte b = 0; b <= 1; b++) {
      NeighbourSW[b] = NeighbourS[b] >> 1;
      bitWrite(NeighbourSW[b], 31, bitRead(NeighbourS[1-b], 0));
      NeighbourSE[b] = NeighbourS[b] << 1;
      bitWrite(NeighbourSE[b], 0, bitRead(NeighbourS[1-b], 31));
 
      //Any live cells at all in this region?
      if (CurrCells[b] | NeighbourN[b] | NeighbourS[b] | NeighbourE[b] | NeighbourW[b] | NeighbourNE[b] | NeighbourNW[b] | NeighbourSE[b] | NeighbourSW[b] > 0) {
     
        //Count the live neighbours (in parallel) for the current row of cells
        //However, if total goes over 3, we don't care (see below), so counting stops at 4
        tot1[b] = NeighbourN[b];
        tot2[b] = tot1[b] & NeighbourNW[b]; tot1[b] = tot1[b] ^ NeighbourNW[b];
        carry[b] = tot1[b] & NeighbourNE[b]; tot1[b] = tot1[b] ^ NeighbourNE[b]; tot4[b] = tot2[b] & carry[b]; tot2[b] = tot2[b] ^ carry[b];
        carry[b] = tot1[b] & NeighbourW[b]; tot1[b] = tot1[b] ^ NeighbourW[b]; tot4[b] = tot2[b] & carry[b] | tot4[b]; tot2[b] = tot2[b] ^ carry[b];
        carry[b] = tot1[b] & NeighbourE[b]; tot1[b] = tot1[b] ^ NeighbourE[b]; tot4[b] = tot2[b] & carry[b] | tot4[b]; tot2[b] = tot2[b] ^ carry[b];
        carry[b] = tot1[b] & NeighbourS[b]; tot1[b] = tot1[b] ^ NeighbourS[b]; tot4[b] = tot2[b] & carry[b] | tot4[b]; tot2[b] = tot2[b] ^ carry[b];
        carry[b] = tot1[b] & NeighbourSW[b]; tot1[b] = tot1[b] ^ NeighbourSW[b]; tot4[b] = tot2[b] & carry[b] | tot4[b]; tot2[b] = tot2[b] ^ carry[b];
        carry[b] = tot1[b] & NeighbourSE[b]; tot1[b] = tot1[b] ^ NeighbourSE[b]; tot4[b] = tot2[b] & carry[b] | tot4[b]; tot2[b] = tot2[b] ^ carry[b];

        //Calculate the updated cells:
        // <2 or >3 neighbours, cell dies
        // =2 neighbours, cell continues to live
        // =3 neighbours, new cell born
        NewCells[b] = (CurrCells[b] | tot1[b]) & tot2[b] & ~ tot4[b];
       
        //Have any cells changed?
        if (NewCells[b] != CurrCells[b]) {
         
          //Count the change for "stale" test
          changes++;
         
          if (prevChanges <= redrawMethodBoundary) {
           
            //Faster to redraw just changed parts of screen at this low level of changes
            unsigned long before = CurrCells[b];
            unsigned long after = NewCells[b];
            byte col = b << 2;
         
            for (byte x = 0; x <= 3; x++) {
           
              if ((before & 0xFF) != (after & 0xFF)) {
             
                digitalWriteFast(GLCD_RS, LOW);
                sendGLCD(B01000000 | row); // Set y
                sendGLCD(B10111000 | col); // set X
                digitalWriteFast(GLCD_RS, HIGH);
                sendGLCD(after & 0xFF);
             
              }
              before = before >> 8;
              after = after >> 8;
              col++;
            }
          }
        }

        Matrix[row][b] = NewCells[b];
        }
 
      //Current cells (before update), E , W, SE, SW and S neighbours become
      //new N, NW, NE, E, W neighbours and current cells for next loop
      NeighbourN[b] = CurrCells[b];
      NeighbourNW[b] = NeighbourW[b];
      NeighbourNE[b] = NeighbourE[b];
      NeighbourE[b] = NeighbourSE[b];
      NeighbourW[b] = NeighbourSW[b];
      CurrCells[b] = NeighbourS[b];
      }
    if (row == 63) {
      digitalWriteFast(GLCD_CS1, LOW);
      digitalWriteFast(GLCD_CS2, HIGH);
      }
    } //next row
   
    if (changes != prevChanges) staleCount = 0; else staleCount++; //Detect "stale" matrix
    if (staleCount > 32) injectGlider(); //Inject a glider

    if (prevChanges > redrawMethodBoundary) outputMatrix(); //Faster to redraw entire screen at this high level of changes

    prevChanges = changes;
  }
Logged

Norfolk UK
Offline Offline
Faraday Member
**
Karma: 71
Posts: 2614
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Nice project, I've always been interested in cellular automata and I may be tempted to knock this up just for the hell of it.  smiley
Logged


Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 152
Posts: 5757
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Joe Brand...

Logged

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

West Yorkshire, UK
Offline Offline
Edison Member
*
Karma: 50
Posts: 1482
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

She's married. You've got no chance.

Seriously,  how come you even know that name fungus? Are you not a real Spaniard?
« Last Edit: February 09, 2014, 06:07:16 pm by PaulRB » Logged

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 152
Posts: 5757
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

She's married. You've got no chance.

Seriously,  how come you even know that name fungus? Are you not a real Spaniard?

She was on QI. Everybody watches QI...

(and Top Gear)
Logged

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

West Yorkshire, UK
Offline Offline
Edison Member
*
Karma: 50
Posts: 1482
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Everybody watches QI...

I did not know that. I know Top Gear is one of the most popular programmes in the world and must make a lot of money for the BBC (and Clarkson), but I imagined QI would only appeal to the Brits...  Anyway, we are getting off topic!

I would like to take the Game of Life toy/ornament idea to a new level. Its a bit of a pipe dream, I have no real idea how to achieve it. I would like to project a larger grid, perhaps 256 x 256, onto the walls/ceiling of a darkened room. Any thoughts how this could be achieved on a budget with off-the-shelf components?
Logged

Norfolk UK
Offline Offline
Faraday Member
**
Karma: 71
Posts: 2614
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I would like to take the Game of Life toy/ornament idea to a new level. Its a bit of a pipe dream, I have no real idea how to achieve it. I would like to project a larger grid, perhaps 256 x 256, onto the walls/ceiling of a darkened room. Any thoughts how this could be achieved on a budget with off-the-shelf components?
I think the bedside projection alarm clocks just shine a focused light through a LCD panel. Maybe you could prove the idea by hardware hacking a cheap LCD matrix display and see if you can work out the required optics to focus a projection.
A more expensive (though > £120) idea would be to use a Raspberry Pi and a pico projector
Logged


West Yorkshire, UK
Offline Offline
Edison Member
*
Karma: 50
Posts: 1482
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Yeah, those are the lines I've been thinking along too. The RasPi/Pico projector is more than I want to spend, and its a bit too ready made anyway. Projecting through a graphic LCD is one I though of too. The concern for me with that idea may be that "hacking" them may well destroy them. You would have to remove the reflective backing and the PCB...

Another idea I'm mulling over is a POV-type solution involving a densely-packed line of LEDs, such as 8 of these http://www.ebay.co.uk/itm/5V-LED-TEST-PCB-Arduino-Basic-Stamp-Digital-Electronics-Prototype-Breadboard-PIC-/321240815778?pt=UK_BOI_Electrical_Components_Supplies_ET&var=&hash=item4acb71d8a2, a lens and a rotating mirror?
« Last Edit: February 11, 2014, 09:38:25 am by PaulRB » Logged

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 152
Posts: 5757
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Projecting through a graphic LCD is one I though of too.

Very unlikely to work. It will be almost impossible to focus the image.

Another idea I'm mulling over is a POV-type solution involving a densely-packed line of LEDs, such as 8 of these  a lens and a rotating mirror?

Same problem. The light from a LED isn't going to form a tiny dot on a wall/ceiling.
Logged

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Norfolk UK
Offline Offline
Faraday Member
**
Karma: 71
Posts: 2614
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Projecting through a graphic LCD is one I though of too. The concern for me with that idea may be that "hacking" them may well destroy them. You would have to remove the reflective backing and the PCB...
But for £1.89 it might be worth testing.

Another idea I'm mulling over is a POV-type solution involving a densely-packed line of LEDs, such as 8 of these http://www.ebay.co.uk/itm/5V-LED-TEST-PCB-Arduino-Basic-Stamp-Digital-Electronics-Prototype-Breadboard-PIC-/321240815778?pt=UK_BOI_Electrical_Components_Supplies_ET&var=&hash=item4acb71d8a2, a lens and a rotating mirror?
They look remarkably like Majenco's LED panels. I wonder if they would be bright enough. How far are you hoping to project and to what size?
Logged


West Yorkshire, UK
Offline Offline
Edison Member
*
Karma: 50
Posts: 1482
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It will be almost impossible to focus the image... The light from a LED isn't going to form a tiny dot on a wall/ceiling.

Sharp focus is not critical. In fact, soft focused patches might look quite good, as long as they don't overlap too much.

£1.89 it might be worth testing.
Yes I suppose.

They look remarkably like Majenco's LED panels. I wonder if they would be bright enough. How far are you hoping to project and to what size?

Who or what is Majenco?  Did you mean Jameco? I just imagined projecting the image onto the ceiling and perhaps the walls also in an averaged size room, so distances 2~4m.

I should really move this discussion to Project Guidance.
« Last Edit: February 11, 2014, 12:05:13 pm by PaulRB » Logged

West Yorkshire, UK
Offline Offline
Edison Member
*
Karma: 50
Posts: 1482
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The penny has dropped!

Majenco is this guy. He helped me with a sprintf question recently.

So you must mean this or this.
« Last Edit: February 11, 2014, 05:22:47 pm by PaulRB » Logged

Norfolk UK
Offline Offline
Faraday Member
**
Karma: 71
Posts: 2614
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Yes that Majenco and the latter of the two ebay links you posted.

I wonder if you could do this on Arduino with a 2D MEMS chip and laser(s)
Logged


Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 152
Posts: 5757
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I wonder if you could do this on Arduino with a 2D MEMS chip and laser(s)

Sure... that's what some of those pico-projectors use to generate the image. It won't be cheap though.


Logged

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Pages: [1] 2   Go Up
Jump to: