Hardware components | ||||||
![]() |
| × | 2 | |||
Software apps and online services | ||||||
![]() |
|
Many of us have used breadboards to prototype various project, usually packed with jumpers like spaghetti. My latest prototype setup uses the ESP32-S3 N16R8 module with a AHT20 I2C device (to quickly identify my I2C main buss) and an ILI9341 tft display. There is ample prototyping area for add-ons without the clutter. For temporary add-ons, I'll use the jumpers.
My example happens to be running my variation of the late Dr John Conways's Game of Life, in color with life span and other simple additions. I'll include that code for fun. It runs as its own task.
The prototyping setup code has the psram enabled (Arduino only sees 4MB, but the board as 8MB).
// Adafruit library used for LCD display
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
// For modules that include Octal PSRAM (any module that has 8MB PSRAM) you MUST NOT use GPIO35, GPIO36 or GPIO37 ???.
// S3_PROTO pins
#define TFT_DC 37
#define TFT_CS 39
#define TFT_MOSI 36
#define TFT_CLK 35
#define TFT_MISO 48
#define TFT_RST 38
#define TFT_BL 47
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO);
Setup
C/C++This is my setup.. Most of the other code is pretty esoteric, but If there is interest, I'm willing to discuss any of it.
//********************************
String BoardType = "S3";
String Module = "ESP32-S3 N16R8 16MB FL,8MB QSPI PS(shows 4176396?)";
#define NEO_DEF 33 // S3M=7, S3D=33, C3M,C3D,C3X=3 Has to be a define, can't wait for the code NEO_PIN
#define NEO_BRIGHTNESS 2 // Change white brightness (max 255)
#define RGB_BUILTIN 33
#define SCL 16
#define SDA 15
#define VCC_PIN 14
//********************************
String version = BoardType + "_proto_life";
String ino = "_S3_PROTO/S3_proto_life";
String rev = (String)__DATE__ + " " + (String)__TIME__;
#include "globals.h"
/////////////////////////////////////////////////
void setup(void)
{
// turn the screen off first
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, LOW);
EEPROM.begin(EEPROM_SIZE);
psramInit();
Serial.begin(115200);
Wire.setPins(15, 16);
Wire.begin();
initAHT();
delay(2000);
Serial.println();
tick.attach(1, [] {
tickcnt++; // updates tickcnt every second
// ESPNOW flag timeouts
if (nowSendClear > 0) nowSendClear--;
if (nowRecvClear > 0) nowRecvClear--;
});
// setup before readCfg
readConfig(); // *** true sets forceDefaultConfig
forceDefaultCfg = false; // one shot deal
// after readConfig
if (useESPNOW && Qota == 1)
{
// make sure this is cleared so we don't go into OTA on reboot
Qota = 0;
saveFlags();
gotoOTA();
}
else
{
sendStatusJson(0);
}
NetQ = xQueueCreate(6, sizeof(qMsg_t));
if (NetQ == NULL)
{
Serial.println("NetQ create failed");
}
if (!useESPNOW)
{
initWifi();
ArduinoOTA.setHostname(config.DeviceName.c_str());
ArduinoOTA.begin();
serverSetup();
}
Serial.println(config.DeviceName);
Serial.println(version);
Serial.println(rev);
char temp[300];
sprintf(temp, "Heap:\nFree:%i\nMin:%i\nSize:%i\nAlloc:%i\nPSRAM:%i",
ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getHeapSize(), ESP.getMaxAllocHeap(), ESP.getFreePsram());
Serial.println(temp);
// psram example
// char *str;
// str = (char *)ps_calloc(5000, sizeof(char) ); // put str buffer into PSRAM
// Serial.println((String)"PSRAM after alloc: " +ESP.getFreePsram());
tft.begin();
welcomeScreen();
getVcc(); // sets last_battVoltage
readAHT();
sendStatus(true);
if(config.Life == 1)
{
delay(5000); // Let me read the Welcome screen
xTaskCreatePinnedToCore(life_loop, "lifeTask", 10000, NULL, 1, &lifeTaskHand, 1);
}
Serial.println("Setup complete");
logMsg(config.DeviceName + ": Setup complete");
delay(2000);
}
Life
C/C++This is my variation of the Game of Life
Rules are the same as the original with a couple of additions added:
Mature cells are Green
Newborn cells are Blue
Cells about to die are Red
Cells have a 75 generation lifespan
Rules are the same as the original with a couple of additions added:
Mature cells are Green
Newborn cells are Blue
Cells about to die are Red
Cells have a 75 generation lifespan
// Life generations and seed value
int seedValue = 1;
int generations = 0;
int maxGenerations = 5000; // max cycles per game
// Set display stuff
const int pixelSize = 8; // Set how many native pixels = pixels per cell length or width
const int len = 40; // 320 / pixelSize
const int ht = 30; // 240 / pixelSize
const bool flipScreen = false; // If you need to flip the screen
// Initialize the board - ADDING 1 stops the border wrap
int board[len+1][ht+1];
int newBoard[len+1][ht+1];
int lifeSpan[len+1][ht+1]; // dies after 75 generations
bool gameOver = true; // on init
int lastBirthRate = 0;
int birthRateLoss = 0;
int birthRate = 0;
///////////////////////////////////////////////////////////
/*
Any live cell with fewer than two live neighbours dies, as if by underpopulation.
Any live cell with two or three live neighbours lives on to the next generation.
Any live cell with more than three live neighbours dies, as if by overpopulation.
Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
These rules, which compare the behavior of the automaton to real life, can be condensed into the following:
Any live cell with two or three live neighbours survives.
Any dead cell with three live neighbours becomes a live cell.
All other live cells die in the next generation. Similarly, all other dead cells stay dead.
Lifespans are set at birth to 75, death by natural cause
GameOver (Extinction) occurs if the net Births is negative for 25 generations or MaxGeneraions
*/
///////////////////////////////////////////////////////////
void life_loop(void *)
{
// Do-while is added for lifeTask
do {
birthRate = 0;
// Catch for number of cycles per game
if (gameOver)
{
blankScreen();
if(generations > 0)
{
tft.setCursor(0, tft.height()-24);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.print("Extinct in ");
tft.print(generations);
tft.print(" generations");
delay(3000);
blankScreen();
}
// reset the game board
gameOver = false;
generations = 0;
randomBoard(); // makes newBoard and zeros board
}
// Draw the board, increment cycle generations
drawBoard(); // draws newBoard and sets board = newBoard
birthRate = nextGeneration(); // calcs neighbors and sets up newBoard
// limit the number of stagnent cycles
// if we don't see any growth for 10 consecutive generations then end
if (birthRate <= 0)
{
birthRateLoss++;
}
else
{
lastBirthRate = birthRate;
birthRateLoss = 0;
}
if (birthRateLoss > 25 || ++generations >= maxGenerations)
{
gameOver = true;
}
} while (lifeTaskHand != NULL);
}
///////////////////////////////////////////////////////////
int countNeighbors(int x, int y)
{
int neighbors = 0;
if (board[x - 1][y] > 1) neighbors++;
if (board[x - 1][y - 1] > 1) neighbors++;
if (board[x][y - 1] > 1) neighbors++;
if (board[x + 1][y - 1] > 1) neighbors++;
if (board[x + 1][y] > 1) neighbors++;
if (board[x + 1][y + 1] > 1) neighbors++;
if (board[x][y + 1] > 1) neighbors++;
if (board[x - 1][y + 1] > 1) neighbors++;
return neighbors;
}
///////////////////////////////////////////////////////////
int nextGeneration()
{
int deaths = 0;
int births = 0;
for (int x = 0; x < len; x++)
{
for (int y = 0; y < ht; y++)
{
int neighbors = countNeighbors(x, y);
newBoard[x][y] = 0; // initialize
// Any live cell with fewer than 2 live neighbours dies, as if by underpopulation.
// or
// Any live cell with more than 3 live neighbours dies, as if by overpopulation.
if (board[x][y] > 1)
{
if ( neighbors < 2 || neighbors > 3 )
{
newBoard[x][y] = 1; // red - dying
deaths++;
}
else
{
newBoard[x][y] = 3; // green survives
if (--lifeSpan[x][y] == 0)
{
newBoard[x][y] = 0; // dies of old age, natural causes
deaths++;
}
}
}
// Any dead cell with 3 live neighbours becomes a live cell.
if ((board[x][y] < 2) && neighbors == 3)
{
newBoard[x][y] = 2; // blue - born
lifeSpan[x][y] = 75; // expected natural life span
births++;
}
}
}
return (births - deaths);
}
///////////////////////////////////////////////////////////
void drawBoard()
{
for (int x = 0; x < len; x++)
{
for (int y = 0; y < ht; y++)
{
if (board[x][y] != newBoard[x][y])
{
board[x][y] = newBoard[x][y];
int color;
switch (newBoard[x][y]) {
case 1:
color = TFT_RED;
break;
case 2:
color = TFT_BLUE;
break;
case 3:
color = TFT_GREEN;
break;
default:
color = TFT_BLACK;
break;
}
tft.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize, color);
}
}
}
}
///////////////////////////////////////////////////////////
void randomBoard()
{
for (int x = 0; x < len; x++)
{
for (int y = 0; y < ht; y++)
{
newBoard[x][y] = (random(0, 4)) > 2 ? 3 : 0;
board[x][y] = 0;
}
}
}
Comments
Please log in or sign up to comment.