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

Very cool, Riva, unfortunately does not (yet) fit the criteria of "budget" or "off-the-shelf" !
Logged

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 149
Posts: 5633
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I think I have an answer!

I just tried shining a LED through a black drinking straw, you get quite a nice little circle of light on the ceiling of a dark room.

The 'projector' would be quite big.... it won't be easy to get a nice regular grid of pixels to cover a large area...but it can be done!

Logged

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

Offline Offline
God Member
*****
Karma: 6
Posts: 524
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Use a magnifying glass and put the leds at the focal point.

Easy and makes a very simple projector.
Logged

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

I just tried shining a LED through a black drinking straw, you get quite a nice little circle of light on the ceiling of a dark room.

Interesting... how long does the straw need to be to get the effect, or rather how short can you make it and still get the effect?
Logged

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

Use a magnifying glass and put the leds at the focal point.

Easy and makes a very simple projector.

Yes, that's the kind of thing.

Remember I want to achieve a high pixel count, at least 64x64 but ideally higher than the 128x64 I have now. Clearly 4,096 individual LEDs is impractical, so some POV/scanning technique would be needed.

Another idea I had was to use a 128x64 OLED display and put a magnifying glass above that .
« Last Edit: February 12, 2014, 07:37:43 am by PaulRB » Logged

Offline Offline
God Member
*****
Karma: 6
Posts: 524
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

The magnifying glass is a projector without the collimator so the picture will be upside down.
The focalpoint is actually a plane and not a point so a ledmatrix will be projected as an image.

It works better than a straw because it collects the light instead of blocking it.
The projected image brightness will be dependent on the distance since all of the light in a single led is averaged over the size of the projected dot of the led.

I have built a number of led discolight effects using this and it works even if the room is not dark.
Logged

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 149
Posts: 5633
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Interesting... how long does the straw need to be to get the effect, or rather how short can you make it and still get the effect?

That's up to you. If you haven't got a straw then try rolling some paper around a pencil or something.



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: 47
Posts: 1434
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Next idea: this display placed at a 45 degree angle, illuminated by a bright LED, perhaps a colour-cycling RGB, with a focussing lens placed above. What do you think?
Logged

Norfolk UK
Offline Offline
Faraday Member
**
Karma: 70
Posts: 2567
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Next idea: this display placed at a 45 degree angle, illuminated by a bright LED, perhaps a colour-cycling RGB, with a focussing lens placed above. What do you think?
I don't think reflection will work very well but you never know.
I pulled apart one of the little Nokia LCD's and as suspected it does use the horrible rubberised connection to the PCB but I think it would be possible to cut out the centre of the PCB under the display matrix and shine a light through (I did not remove the reflective backing) the LCD mounted in a clear plastic/glass package.
Another idea would be a LCD like this (or this) that uses a flat ribbon connector so may lift off the backing board and work with light shone through it.
Logged


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

UPDATE: Recently purchased an inexpensive 128 x 64 OLED display on eBay which uses SPI bus. Inevitably, I just had to get Game Of Life running on that too...

http://youtu.be/6-LDtmLk0iE

Code:

// Conway's Game Of Life 128x64
// PaulRB
// Jun 2014

#include <SPI.h>

//Pins controlling SSD1306 Graphic OLED
#define OLED_DC     10
#define OLED_CS     9
#define OLED_RESET  7

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

void setup() {
 
  pinMode(OLED_DC, OUTPUT);
  pinMode(OLED_CS, OUTPUT);
  pinMode(OLED_RESET, OUTPUT);

  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV2); // 8 MHz

  digitalWrite(OLED_RESET, HIGH);
  delay(1);
  digitalWrite(OLED_RESET, LOW);
  delay(10);
  digitalWrite(OLED_RESET, HIGH);

  digitalWrite(OLED_DC, LOW);
  digitalWrite(OLED_CS, LOW);
 
  SPI.transfer(0xAE); // Display off
  SPI.transfer(0xD5); // Set display clock divider
  SPI.transfer(0x80);
  SPI.transfer(0xA8); // Set multiplex
  SPI.transfer(0x3F);
  SPI.transfer(0xD3); // Set display offset
  SPI.transfer(0x00);
  SPI.transfer(0x40); // Set start line to zero
  SPI.transfer(0x8D); // Set charge pump
  SPI.transfer(0x14);
  SPI.transfer(0x20); // Set memory mode
  SPI.transfer(0x00);
  SPI.transfer(0xA0 | 0x1); // Set segment remapping
  SPI.transfer(0xC8); // Set command Scan decode
  SPI.transfer(0xDA); // Set Comm pins
  SPI.transfer(0x12);
  SPI.transfer(0x81); // Set contrast
  SPI.transfer(0xCF);
  SPI.transfer(0xd9); // Set precharge
  SPI.transfer(0xF1);
  SPI.transfer(0xDB); // Set Vcom detect
  SPI.transfer(0x40);
  SPI.transfer(0xA4); // Allow display resume
  SPI.transfer(0xA6); // Set normal display
  SPI.transfer(0xAF); // Display On
 
  digitalWrite(OLED_CS, HIGH);

  //R-pentomino
  Matrix[64] = B0000010; Matrix[64] = Matrix[64] << 32;
  Matrix[65] = B0000111; Matrix[65] = Matrix[65] << 32;
  Matrix[66] = B0000100; Matrix[66] = Matrix[66] << 32;
  outputMatrix();

  Serial.begin(115200);
 
}

void loop() {
  unsigned long start = millis();
  for (int i=0; i<=500; i++) {
    generateMatrix();
    outputMatrix();
  }
  Serial.print("Gens/s:"); Serial.println((millis() - start)/500);

}

void outputMatrix() {
 
  digitalWrite(OLED_DC, LOW); //Command mode
  digitalWrite(OLED_CS, LOW); //Enable display on SPI bus
 
  SPI.transfer(0x21); // Set column address
  SPI.transfer(0);
  SPI.transfer(127);

  SPI.transfer(0x22); // Set page address
  SPI.transfer(0);
  SPI.transfer(7);

  digitalWrite(OLED_CS, HIGH); //Disable display on SPI bus
 
  digitalWrite(OLED_DC, HIGH); // Data mode
  digitalWrite(OLED_CS, LOW); //Enable display on SPI bus

  //Send matrix data for display on OLED
  for (byte x = 0; x < 64; x+=8) {
   
    for (byte row = 0; row <= 127; row++) {
      SPI.transfer(Matrix[row] >> x);
    }
   
  }
  digitalWrite(OLED_CS, 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] = random(0xffff) << 16 | random(0xffff);
    }
  }
}

void injectGlider() {

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

}

int generateMatrix() {

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

  //Variables used in calculating new cells
  unsigned long long tot1, carry, tot2, tot4, NewCells;
 
  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
  NeighbourN = Matrix[127];
  CurrCells = Matrix[0];
  Matrix[128] = CurrCells;  // copy row 0 to location after last row to remove need for wrap-around code in the loop

  NeighbourNW = NeighbourN >> 1 | NeighbourN << 63;
  NeighbourNE = NeighbourN << 1 | NeighbourN >> 63;

  NeighbourW = CurrCells >> 1 | CurrCells << 63;
  NeighbourE = CurrCells << 1 | CurrCells >> 63;
 
  //Process each row of the matrix
  for (byte row = 0; row <= 127; row++) {

    //Pick up new S, SW & SE neighbours
    NeighbourS = Matrix[row + 1];
 
    NeighbourSW = NeighbourS >> 1 | NeighbourS << 63;
    NeighbourSE = NeighbourS << 1 | NeighbourS >> 63;

    //Any live cells at all in this region?
    if (CurrCells | NeighbourN | NeighbourS | NeighbourE | NeighbourW | NeighbourNE | NeighbourNW | NeighbourSE | NeighbourSW > 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 = NeighbourN;
      tot2 = tot1 & NeighbourNW; tot1 = tot1 ^ NeighbourNW;
      carry = tot1 & NeighbourNE; tot1 = tot1 ^ NeighbourNE; tot4 = tot2 & carry; tot2 = tot2 ^ carry;
      carry = tot1 & NeighbourW; tot1 = tot1 ^ NeighbourW; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
      carry = tot1 & NeighbourE; tot1 = tot1 ^ NeighbourE; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
      carry = tot1 & NeighbourS; tot1 = tot1 ^ NeighbourS; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
      carry = tot1 & NeighbourSW; tot1 = tot1 ^ NeighbourSW; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
      carry = tot1 & NeighbourSE; tot1 = tot1 ^ NeighbourSE; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;

      //Calculate the updated cells:
      // <2 or >3 neighbours, cell dies
      // =2 neighbours, cell continues to live
      // =3 neighbours, new cell born
      NewCells = (CurrCells | tot1) & tot2 & ~ tot4;
     
      //Have any cells changed?
      if (NewCells != CurrCells) {       
        //Count the change for "stale" test
        changes++;
      }

      Matrix[row] = NewCells;
    }

    //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 = CurrCells;
    NeighbourNW = NeighbourW;
    NeighbourNE = NeighbourE;
    NeighbourE = NeighbourSE;
    NeighbourW = NeighbourSW;
    CurrCells = NeighbourS;
    }
   
    if (changes != prevChanges) staleCount = 0; else staleCount++; //Detect "stale" matrix
    if (staleCount > 32) injectGlider(); //Inject a glider

    prevChanges = changes;
  }

« Last Edit: June 05, 2014, 05:43:59 pm by PaulRB » Logged

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

In case anyone is interested, I'm getting around 45 generations per second with this setup (16MHz Pro Micro, updating 128x64 OLED using hardware SPI at max available speed of 8MHz). Each life matrix generation is taking roughly 12ms and updating the OLED (every pixel) is taking around 10ms.
« Last Edit: June 05, 2014, 05:45:31 pm by PaulRB » Logged

Pages: 1 [2]   Go Up
Jump to: