OLED | Arduino |
---|---|
GND | GND |
VCC | 3,3V bzw. 5V |
SCL | A5 |
SDA | A4 |
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.
#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()
{
}
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.
#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();
}
Der neue Nano RP2040 Connect besitzt durch seinen RP2040-Chip deutlich mehr RAM als der Arduino Uno und kann somit ein größeres Simulations-Feld ansteuern und außerdem ist der Chip deutlich schneller bei den notwendigen Berechnungen.
OLED | Nano RP2040 Connect |
---|---|
GND | GND |
VCC | +3V3 |
SCL | A5 (GPIO13 bzw. D19) |
SDA | A4 (GPIO12 bzw. D18) |
Zunächst hatte ich einige Probleme, auf dem OLED überhaupt etwas
darzustellen, aber letztendlich funktionierte die Variante mit
der I²C-Adresse 0x3C
und einer Auflösung von 128×64 Pixeln.
Allerdings war es mir nicht möglich ein Spielfeld von mehr als 64×64
richtig darzustellen, obwohl der RAM-Verbrauch des Nano erst bei ca. 26% lag.
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_ADDRESS 0x3C
#define FIELD_WIDTH 64
#define FIELD_HEIGHT 64
// Arduino Nano RP2040 connect: A4(SDA), A5(SCL)
Adafruit_SSD1306 oled(OLED_WIDTH, OLED_HEIGHT, &Wire, -1);
byte field[FIELD_WIDTH][FIELD_HEIGHT];
byte fieldLast[FIELD_WIDTH][FIELD_HEIGHT];
int iteration = 0, cellsAlive = 0;
int SIMULATION_DELAY = 10;
void setup()
{
oled.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS);
oled.clearDisplay();
oled.display();
randomizeField();
}
void loop()
{
updateOled();
saveFieldState();
generateNextGeneration();
delay(SIMULATION_DELAY);
iteration++;
}
void randomizeField()
{
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()
{
oled.fillRect(FIELD_WIDTH + 3, 0, oled.width(), oled.height(), BLACK);
oled.setTextColor(WHITE);
oled.setTextSize(1);
oled.setCursor(FIELD_WIDTH + 10, 0);
oled.print("Iter:");
oled.setCursor(FIELD_WIDTH + 10, 10);
oled.print(String(iteration));
oled.setCursor(FIELD_WIDTH + 10, 30);
oled.print("Alive:");
oled.setCursor(FIELD_WIDTH + 10, 40);
oled.print(String(cellsAlive));
for (byte y = 0; y < FIELD_HEIGHT; y++) {
for (byte x = 0; x < FIELD_WIDTH; x++) {
if (field[y][x]==fieldLast[y][x]) {
continue;
}
if (field[y][x] == 1) {
oled.drawPixel(x, y, WHITE);
} else {
oled.drawPixel(x, y, BLACK);
}
}
}
oled.display();
}
Bei diesem Aufbau wurde kein Mikrotaster und Potentiometer verwendet; für ein Neustart des Spielfeldes kann einfach der RESET-Knopf des Nano betätigt werden.
zurück