Conway's Game Of Life

Post Reply
PaulRB
Posts: 3
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: 1521
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;
}


PaulRB
Posts: 3
Joined: Sun Mar 10, 2019 1:39 pm

Re: Conway's Game Of Life

Post by PaulRB » Mon Nov 18, 2019 8:02 pm

Thanks Bertus!
PaulRB wrote:
Sat Oct 26, 2019 4:48 pm
The NanoARM runs the code at around 300 frames per second
Now 650 frames per second, by using DMA transfer to update the display!

Code: Select all

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

#include <SPI.h>
#include "wiring_private.h" // pinPeripheral() function
#include <Adafruit_ZeroDMA.h>
#include "utility/dma.h"

Adafruit_ZeroDMA myDMA;
ZeroDMAstatus    stat;

volatile bool transfer_is_done = true;

// Callback for end-of-DMA-transfer
void dma_callback(Adafruit_ZeroDMA *dma) {
  transfer_is_done = true;
}

SPIClass mySPI (&sercom1, 12, 13, 11, SPI_PAD_0_SCK_1, SERCOM_RX_PAD_3);
//#define mySPI SPI

byte DMABuffer[128*8];

//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() {

  SerialUSB.begin(115200);

  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));

  SerialUSB.println("Configuring DMA trigger");
  myDMA.setTrigger(SERCOM1_DMAC_ID_TX);
  myDMA.setAction(DMA_TRIGGER_ACTON_BEAT);

  SerialUSB.print("Allocating DMA channel...");
  stat = myDMA.allocate();
  myDMA.printStatus(stat);

  SerialUSB.println("Setting up transfer");
  myDMA.addDescriptor(
    DMABuffer,                    // move data from here
    (void *)(&SERCOM1->SPI.DATA.reg), // to here (M0)
    sizeof(DMABuffer),                      // this many...
    DMA_BEAT_SIZE_BYTE,               // bytes/hword/words
    true,                             // increment source addr?
    false);                           // increment dest addr?

  SerialUSB.println("Adding callback");
  // register_callback() can optionally take a second argument
  // (callback type), default is DMA_CALLBACK_TRANSFER_DONE
  myDMA.setCallback(dma_callback);

  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();

}

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() {

  while (!transfer_is_done);
  digitalWrite(OLED_CS, HIGH);
  
  digitalWrite(OLED_DC, LOW); //Command mode
  digitalWrite(OLED_CS, LOW); //Enable display on SPI bus

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

  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
  int b = 0;
  for (byte col = 0; col < 8; col++) {
    for (byte row = 0; row <= 127; row++) {
      DMABuffer[b++] = Matrix[row].b[col];
    }
    //mySPI.transfer(DMABuffer, sizeof(DMABuffer));
  }
  transfer_is_done = false;
  stat = myDMA.startJob();

}

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;
}
(Obviously 300 frames per second is a little ridiculous, let alone 650 frames per second. This was an excersize for me to see how far I could increase the speed over an "old-style" Nano)

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

Re: Conway's Game Of Life

Post by Bertus Kruger » Wed Dec 04, 2019 6:09 pm

Haha... Push the limits!!! :D

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest