zurück

"Game Of Life" mit einem OLED-Display

12.03.2018
Video: Live-Demonstration

Verwendete Bauteile

Anschlüsse des OLED-Moduls

OLED Arduino
GND GND
VCC 3,3V bzw. 5V
SCL A5
SDA A4

Test der OLED

Zum Betrieb des OLED-Moduls (mittels I²C-Protokoll) wurden zwei vorgefertigte Software-Libraries verwendet, Adafruit_SSD1306 und Adafruit-GFX-Library. Damit kann das OLED sehr einfach angesteuert und einige Test durchgeführt werden.

Aufbau der Schaltung
Abb.: Aufbau der Schaltung

Sketch

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

void setup()
{
  Wire.begin();

  /**
   * initialize with the I2C address:
   *    0x3C  -> 128x32 resolution
   *    0x3D  -> 128x64 resolution
   */
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  /**
   * Show image buffer on the display hardware.
   * Since the buffer is intialized with an Adafruit splashscreen
   * internally, this will display the splashscreen.
   */
  display.display();
  delay(2000);


  display.clearDisplay();
  display.drawPixel(10, 10, WHITE);
  display.display();
  delay(2000);

  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(1);
  display.setCursor(5,0);
  display.print("Hello World!");
  display.display();
  delay(2000);
}

void loop()
{
}

"Game Of Life"

Conway´s Game Of Life hatte ich schon in "Zelluläre Automaten" implementiert und habe den Algorithmus hierfür übernommen aber noch die Feldgröße etwas erweitert. Zusätzlich wurde die Schaltung noch mit einem Reset-Knopf ausgestattet, damit man das Feld wieder mit zufälligen Zellen initialisieren kann. Außerdem gibt es noch ein Potentiometer, mit dem man die Geschwindigkeit der Generationen regeln kann.
Auf dem OLED werden natürlich die Zellen pro Generation angezeigt, aber auch die Generation ("Iter"), die Anzahl an gleichzeitig gezeigten Zellen ("Alive") und die aktuelle Verzögerungszeit/Geschwindigkeit ("Delay") der Simulation.

Aufbau der Schaltung
Abb.: Aufbau der Schaltung

Sketch

#define FIELD_WIDTH  20
#define FIELD_HEIGHT 20

#define PIN_BUTTON        2
#define PIN_POTENTIOMETER A2

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

byte field[FIELD_WIDTH][FIELD_HEIGHT];
byte fieldLast[FIELD_WIDTH][FIELD_HEIGHT];
int iteration = 0, cellsAlive = 0;
bool isReset = false;
int gameDelay = 150;


void setup()
{
  pinMode(PIN_BUTTON, INPUT);
  pinMode(PIN_POTENTIOMETER, INPUT);

  attachInterrupt(digitalPinToInterrupt(PIN_BUTTON), isrReset, RISING);

  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.display();

  randomizeField();
}

void loop()
{
  if (isReset == true) {
    isReset = false;
    randomizeField();
  }
  updateOled();
  saveFieldState();
  generateNextGeneration();

  gameDelay = round(analogRead(PIN_POTENTIOMETER) / 10) * 10;
  delay(gameDelay);
  iteration++;
}

void isrReset()
{
  isReset = true;
}

void randomizeField()
{
  iteration = 0;
  cellsAlive = 0;
  byte rnd = 0;
  randomSeed(analogRead(0));
  for(byte y=0; y<FIELD_HEIGHT; y++) {
    for(byte x=0; x<FIELD_WIDTH; x++) {
      field[y][x] = random(2);
      if (field[y][x] == 1) {
        cellsAlive++;
      }
    }
  }
}

void saveFieldState()
{
  for(byte y=0; y<FIELD_HEIGHT; y++) {
    for(byte x=0; x<FIELD_WIDTH; x++) {
      fieldLast[y][x] = field[y][x];
    }
  }
}

void generateNextGeneration()
{
  byte neighborSum = 0;
  for(byte y=0; y<FIELD_HEIGHT; y++) {
    for(byte x=0; x<FIELD_WIDTH; x++) {
      neighborSum = getNeightborSum(x, y);
      if (fieldLast[y][x] == 0) {
        if (neighborSum == 3) {
          // populate if 3 neighours around it
          field[y][x] = 1;
          cellsAlive++;
        }
      } else {
        if (neighborSum < 2 || neighborSum > 3) {
          // die if only one neighbour or 4 or more neighours
          field[y][x] = 0;
          cellsAlive--;
        }
      }
    }
  }
}

byte getNeightborSum(byte x, byte y)
{
  byte sum = 0;

  for(int j = -1; j<=1; j++) {
    for(int i = -1; i<=1; i++) {
      if (j==0 && i==0) {
        continue;
      }
      if (x+i<0 || x+i>FIELD_WIDTH-1) {
        continue;
      }
      if (y+j<0 || y+j>FIELD_HEIGHT-1) {
        continue;
      }
      sum += fieldLast[y+j][x+i];
    }
  }

  return sum;
}

void updateOled()
{
  display.fillRect(FIELD_WIDTH + 3, 0, display.width(), display.height(), BLACK);

  display.setTextColor(WHITE);
  display.setTextSize(1);

  display.setCursor(FIELD_WIDTH + 10, 0);
  display.print("Iter:");
  display.setCursor(FIELD_WIDTH + 50, 0);
  display.print(String(iteration));

  display.setCursor(FIELD_WIDTH + 10, 10);
  display.print("Alive:");
  display.setCursor(FIELD_WIDTH + 50, 10);
  display.print(String(cellsAlive));

  display.setCursor(FIELD_WIDTH + 10, 20);
  display.print("Delay:");
  display.setCursor(FIELD_WIDTH + 50, 20);
  display.print(String(gameDelay));

  for(byte y=0; y<FIELD_HEIGHT; y++) {
    for(byte x=0; x<FIELD_WIDTH; x++) {
      if (field[y][x] == 1) {
        display.drawPixel(x, y, WHITE);
      } else {
        display.drawPixel(x, y, BLACK);
      }
    }
  }
  display.display();
}