Conway's Game Of Life

Post Reply
PaulRB
Posts: 2
Joined: Sun Mar 10, 2019 1:39 pm

Conway's Game Of Life

Post by PaulRB » Sat Oct 26, 2019 4:48 pm

All,

I have adapted a version of my Game Of Life code to run on the NanoARM. Video here.

This involved setting up SPI to use the same pins as a regular Arduino Nano. I followed the instructions on the AdaFruit web site mentioned in other topics on this forum.

The display is an SSD1306 OLED. The NanoARM runs the code at around 300 frames per second, compared to around 50 frames per second on a regular Arduino Nano. As the NanoARM's clock speed is 3 times faster than the Arduino Nano, you might expect 150 frames per second, but because of the way the code works, the 32-bit SAMD21 outperforms the 8-bit ATYMEGA328 by a further factor of 2.

Here's the code for anyone interested:

Code: Select all

// Conway's Game Of Life 128x64 for Protoneer NanoARM
// PaulRB
// Oct 2019

#include <SPI.h>
#include "wiring_private.h" // pinPeripheral() function
SPIClass mySPI (&sercom1, 12, 13, 11, SPI_PAD_0_SCK_1, SERCOM_RX_PAD_3);
//#define mySPI SPI

//Pins controlling SSD1306 Graphic OLED
#define OLED_DC     3
#define OLED_CS     5
#define OLED_RESET  4
#define BIT63       0x8000000000000000ULL

union MatrixData {
  unsigned long long l;
  byte b[8];
};

MatrixData Matrix[129]; // Cell data in ram

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

  mySPI.begin();
  // Assign pins 11, 12, 13 to SERCOM functionality
  pinPeripheral(11, PIO_SERCOM);
  pinPeripheral(12, PIO_SERCOM);
  pinPeripheral(13, PIO_SERCOM);
  mySPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));

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

  digitalWrite(OLED_DC, LOW);
  digitalWrite(OLED_CS, LOW);

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

  //R-pentomino
  //Matrix[64].l = B0000010; Matrix[64].l = Matrix[64].l << 32;
  //Matrix[65].l = B0000111; Matrix[65].l = Matrix[65].l << 32;
  //Matrix[66].l = B0000100; Matrix[66].l = Matrix[66].l << 32;

  //Gosper's Glider Gun
  Matrix[64].l = 0b00000000000000000000000000010000000000000000000000;
  Matrix[65].l = 0b00000000000000000000000001010000000000000000000000;
  Matrix[66].l = 0b00000000000000011000000110000000000001100000000000;
  Matrix[67].l = 0b00000000000000100010000110000000000001100000000000;
  Matrix[68].l = 0b00011000000001000001000110000000000000000000000000;
  Matrix[69].l = 0b00011000000001000101100001010000000000000000000000;
  Matrix[70].l = 0b00000000000001000001000000010000000000000000000000;
  Matrix[71].l = 0b00000000000000100010000000000000000000000000000000;
  Matrix[72].l = 0b00000000000000011000000000000000000000000000000000;

  
  //randomiseMatrix();
  outputMatrix();

  SerialUSB.begin(115200);
  	
}

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

}

void outputMatrix() {
  
  digitalWrite(OLED_DC, LOW); //Command mode
  digitalWrite(OLED_CS, LOW); //Enable display on SPI bus

  byte myBuffer[128] = {0x21 /*Set column address*/, 0, 127, 0x22 /*Set page address*/, 0, 7};
  mySPI.transfer(myBuffer, 6);

  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 col = 0; col < 8; col++) {
    
    for (byte row = 0; row <= 127; row++) {
      myBuffer[row] = Matrix[row].b[col];
    }	
    mySPI.transfer(myBuffer, sizeof(myBuffer));
    
  }
  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 <= 8; col++) {
      Matrix[row].b[col] = random(0xff);
    }
  }
}
	
void injectGlider() {

  byte col = random(127);
  byte row = random(63);
  Matrix[col++].l |= ((unsigned long long) B0000111) << row;
  Matrix[col++].l |= ((unsigned long long) B0000001) << row;
  Matrix[col++].l |= ((unsigned long long) 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[4]; // counts the changes in the matrix on prev 4 generations
  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].l;
  CurrCells = Matrix[0].l;
  Matrix[128].l = 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].l;
    
    //NeighbourSW = NeighbourS >> 1 | NeighbourS << 63;

    //NeighbourSW = NeighbourS >> 1 | ((NeighbourS & 1) ? BIT63 : 0);

    if (NeighbourS & 1) NeighbourSW = NeighbourS >> 1 | BIT63; else NeighbourSW = NeighbourS >> 1;
    
    //NeighbourSE = NeighbourS << 1 | NeighbourS >> 63;

    if (NeighbourS & BIT63) NeighbourSE = NeighbourS << 1 | 1; else NeighbourSE = NeighbourS << 1;

    //NeighbourSE = NeighbourS << 1 | ((NeighbourS & BIT63) ? 1 : 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].l = 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[0] && changes != prevChanges[1] && changes != prevChanges[2] && changes != prevChanges[3]) {
    staleCount = 0;
  }
  else {
    staleCount++; //Detect "stale" matrix
  }
    
  if (staleCount > 64) injectGlider(); //Inject a glider
  //Serial.println(changes);

  for (int i=3; i>0; i--) {
    prevChanges[i] = prevChanges[i-1];
  }

  prevChanges[0] = changes;
}


Bertus Kruger
Site Admin
Posts: 1503
Joined: Wed Feb 03, 2016 10:26 pm
Location: Wellington , New Zealand
Contact:

Re: Conway's Game Of Life

Post by Bertus Kruger » Sun Nov 03, 2019 6:21 pm

Nice work!!!
PaulRB wrote:
Sat Oct 26, 2019 4:48 pm
All,

I have adapted a version of my Game Of Life code to run on the NanoARM. Video here.

This involved setting up SPI to use the same pins as a regular Arduino Nano. I followed the instructions on the AdaFruit web site mentioned in other topics on this forum.

The display is an SSD1306 OLED. The NanoARM runs the code at around 300 frames per second, compared to around 50 frames per second on a regular Arduino Nano. As the NanoARM's clock speed is 3 times faster than the Arduino Nano, you might expect 150 frames per second, but because of the way the code works, the 32-bit SAMD21 outperforms the 8-bit ATYMEGA328 by a further factor of 2.

Here's the code for anyone interested:

Code: Select all

// Conway's Game Of Life 128x64 for Protoneer NanoARM
// PaulRB
// Oct 2019

#include <SPI.h>
#include "wiring_private.h" // pinPeripheral() function
SPIClass mySPI (&sercom1, 12, 13, 11, SPI_PAD_0_SCK_1, SERCOM_RX_PAD_3);
//#define mySPI SPI

//Pins controlling SSD1306 Graphic OLED
#define OLED_DC     3
#define OLED_CS     5
#define OLED_RESET  4
#define BIT63       0x8000000000000000ULL

union MatrixData {
  unsigned long long l;
  byte b[8];
};

MatrixData Matrix[129]; // Cell data in ram

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

  mySPI.begin();
  // Assign pins 11, 12, 13 to SERCOM functionality
  pinPeripheral(11, PIO_SERCOM);
  pinPeripheral(12, PIO_SERCOM);
  pinPeripheral(13, PIO_SERCOM);
  mySPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));

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

  digitalWrite(OLED_DC, LOW);
  digitalWrite(OLED_CS, LOW);

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

  //R-pentomino
  //Matrix[64].l = B0000010; Matrix[64].l = Matrix[64].l << 32;
  //Matrix[65].l = B0000111; Matrix[65].l = Matrix[65].l << 32;
  //Matrix[66].l = B0000100; Matrix[66].l = Matrix[66].l << 32;

  //Gosper's Glider Gun
  Matrix[64].l = 0b00000000000000000000000000010000000000000000000000;
  Matrix[65].l = 0b00000000000000000000000001010000000000000000000000;
  Matrix[66].l = 0b00000000000000011000000110000000000001100000000000;
  Matrix[67].l = 0b00000000000000100010000110000000000001100000000000;
  Matrix[68].l = 0b00011000000001000001000110000000000000000000000000;
  Matrix[69].l = 0b00011000000001000101100001010000000000000000000000;
  Matrix[70].l = 0b00000000000001000001000000010000000000000000000000;
  Matrix[71].l = 0b00000000000000100010000000000000000000000000000000;
  Matrix[72].l = 0b00000000000000011000000000000000000000000000000000;

  
  //randomiseMatrix();
  outputMatrix();

  SerialUSB.begin(115200);
  	
}

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

}

void outputMatrix() {
  
  digitalWrite(OLED_DC, LOW); //Command mode
  digitalWrite(OLED_CS, LOW); //Enable display on SPI bus

  byte myBuffer[128] = {0x21 /*Set column address*/, 0, 127, 0x22 /*Set page address*/, 0, 7};
  mySPI.transfer(myBuffer, 6);

  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 col = 0; col < 8; col++) {
    
    for (byte row = 0; row <= 127; row++) {
      myBuffer[row] = Matrix[row].b[col];
    }	
    mySPI.transfer(myBuffer, sizeof(myBuffer));
    
  }
  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 <= 8; col++) {
      Matrix[row].b[col] = random(0xff);
    }
  }
}
	
void injectGlider() {

  byte col = random(127);
  byte row = random(63);
  Matrix[col++].l |= ((unsigned long long) B0000111) << row;
  Matrix[col++].l |= ((unsigned long long) B0000001) << row;
  Matrix[col++].l |= ((unsigned long long) 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[4]; // counts the changes in the matrix on prev 4 generations
  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].l;
  CurrCells = Matrix[0].l;
  Matrix[128].l = 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].l;
    
    //NeighbourSW = NeighbourS >> 1 | NeighbourS << 63;

    //NeighbourSW = NeighbourS >> 1 | ((NeighbourS & 1) ? BIT63 : 0);

    if (NeighbourS & 1) NeighbourSW = NeighbourS >> 1 | BIT63; else NeighbourSW = NeighbourS >> 1;
    
    //NeighbourSE = NeighbourS << 1 | NeighbourS >> 63;

    if (NeighbourS & BIT63) NeighbourSE = NeighbourS << 1 | 1; else NeighbourSE = NeighbourS << 1;

    //NeighbourSE = NeighbourS << 1 | ((NeighbourS & BIT63) ? 1 : 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].l = 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[0] && changes != prevChanges[1] && changes != prevChanges[2] && changes != prevChanges[3]) {
    staleCount = 0;
  }
  else {
    staleCount++; //Detect "stale" matrix
  }
    
  if (staleCount > 64) injectGlider(); //Inject a glider
  //Serial.println(changes);

  for (int i=3; i>0; i--) {
    prevChanges[i] = prevChanges[i-1];
  }

  prevChanges[0] = changes;
}


Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest