/*
ESP32-S3 + ILI9341 (240x320) Spiral Animation
-----------------------------------------------
Companion firmware for esp32_ili9341_spiral_simulator.html
Library required (Arduino IDE > Library Manager):
- Adafruit GFX Library
- Adafruit ILI9341 Library
(No User_Setup.h / TFT_eSPI config needed — pins are set in this sketch.)
Wiring (ESP32-S3 default hardware SPI pins — adjust to your board silkscreen):
ILI9341 CS -> GPIO 10
ILI9341 DC -> GPIO 11
ILI9341 RST -> GPIO 12
ILI9341 SCLK -> GPIO 13 (SPI CLK)
ILI9341 MOSI -> GPIO 14 (SPI MOSI)
ILI9341 MISO -> GPIO 21 (SPI MISO, optional — only needed for touch/read)
ILI9341 LED -> 3V3 (or a GPIO w/ PWM for brightness control)
ILI9341 VCC -> 3V3
ILI9341 GND -> GND
Controls:
- BOOT button (GPIO 0) -> advance to next spiral manually
- Auto-advances every 1400ms, matching the HTML simulator's timing
Arvind Patil / AI Centre Nandurbar
*/
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>
// ---------- Pin configuration ----------
#define TFT_CS 10
#define TFT_DC 11
#define TFT_RST 12
#define TFT_SCLK 13
#define TFT_MOSI 14
#define TFT_MISO 21
#define BTN_NEXT 0 // BOOT button, active LOW
#define SCREEN_W 240
#define SCREEN_H 320
Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI, TFT_DC, TFT_CS, TFT_RST);
const int cx = SCREEN_W / 2;
const int cy = SCREEN_H / 2;
int spiralIndex = 0;
bool autoAdvance = true;
unsigned long lastAdvance = 0;
const unsigned long ADVANCE_MS = 1400;
bool lastBtnState = HIGH;
// 10 palettes, one per spiral mode (RGB565)
const uint16_t palettes[10][4] = {
{0xFA4A, 0xFEA3, 0x0740, 0x4E5F}, // 1
{0xFCE3, 0xFDF3, 0xCF9E, 0x2E6D}, // 2
{0xF9B1, 0xB8D3, 0x7205, 0x580B}, // 3
{0x07FA, 0x05FD, 0xF7E1, 0xF2DA}, // 4
{0xADFD, 0x43D3, 0x1DAB, 0xF7DE}, // 5
{0xFBA0, 0xFC40, 0xFE80, 0xFF95}, // 6
{0x7996, 0x9A5D, 0xC7BE, 0xE555}, // 7
{0x07FF, 0x1FFF, 0x67FD, 0xB7F6}, // 8
{0xF80D, 0xFACB, 0xFDE0, 0x81DF}, // 9
{0xFFFF, 0x7FFF, 0x0537, 0x01AA} // 10
};
uint16_t colorFor(int mode, int i) {
return palettes[mode % 10][i % 4];
}
void clearScreen() {
tft.fillScreen(ILI9341_BLACK);
}
void drawSpiral(int n, unsigned long timeMs) {
int mode = n % 10;
clearScreen();
float turns = 4.5f + mode * 0.35f;
float maxR = (18.0f + mode * 2.6f) * 1.14f; // scaled up for 320px-tall panel
const int loops = 220; // reduced vs. HTML sim (620) for MCU draw speed
for (int i = 0; i < loops; i++) {
float p = (float)i / loops;
float angle = p * TWO_PI * turns + timeMs * 0.0015f * (1 + mode * 0.08f);
float radius = p * maxR * 10.2f;
switch (mode) {
case 1: radius = pow(p, 0.82f) * maxR * 10.5f; break;
case 2: radius = (1 - cos(p * PI * turns)) * 12.0f; break;
case 3: radius = p * maxR * 9.4f + sin(p * 16 + timeMs * 0.002f) * 3.0f; break;
case 4: radius = (p * maxR * 8.8f) * (0.7f + 0.3f * sin(angle * 3)); break;
case 5: radius = p * maxR * 9.8f + cos(p * 22 + timeMs * 0.003f) * 5.0f; break;
case 6: radius = p * maxR * 10.0f + sin(angle * 5) * 2.5f; break;
case 7: radius = p * maxR * 8.7f + sin(p * 30 + timeMs * 0.001f) * 4.0f; break;
case 8: radius = p * maxR * 10.2f; break;
case 9: radius = p * maxR * 9.6f + sin(p * 12 * PI + timeMs * 0.002f) * 6.0f; break;
default: break; // mode 0 keeps base formula
}
int x = cx + (int)(cos(angle) * radius);
int y = cy + (int)(sin(angle) * radius);
if (x < 0 || x >= SCREEN_W || y < 0 || y >= SCREEN_H) continue;
uint16_t c = colorFor(mode, i);
float size = 2.1f - p * 1.6f;
int r = max(1, (int)size);
tft.fillCircle(x, y, r, c);
}
// Header text
tft.setCursor(6, 6);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.print("Spiral ");
tft.print(n + 1);
tft.print(" / 10");
tft.setCursor(6, 18);
tft.setTextColor(0x7BCF); // muted gray-blue
tft.print("ESP32-S3 ILI9341 240x320");
}
void setup() {
Serial.begin(115200);
pinMode(BTN_NEXT, INPUT_PULLUP);
SPI.begin(TFT_SCLK, TFT_MISO, TFT_MOSI, TFT_CS);
tft.begin();
tft.setRotation(0); // portrait 240x320
clearScreen();
}
void loop() {
unsigned long now = millis();
// Manual advance via BOOT button (falling edge)
bool btn = digitalRead(BTN_NEXT);
if (lastBtnState == HIGH && btn == LOW) {
spiralIndex = (spiralIndex + 1) % 10;
lastAdvance = now;
}
lastBtnState = btn;
// Auto advance
if (autoAdvance && now - lastAdvance > ADVANCE_MS) {
spiralIndex = (spiralIndex + 1) % 10;
lastAdvance = now;
}
drawSpiral(spiralIndex, now);
}