ESP32 S3 Sanity Check Part 3: Comprehensive test
Now that you are setup, and have achieved some βsanityβ, here is a comprehensive test suite which fully checks the device graphics and memory with assembled common algorithms and testing methods.
/**
* Core1D Automation Labs Present:
* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* β β
* βββββββββββββββββββββββ βββββββ βββββββ βββββββββββββββ β
* ββββββββββββββββββββββββββββββββββββββββ βββββββββββββββ β
* ββββββ ββββββββββββββββ βββββββ βββββββ ββββββββ ββββββ β
* ββββββ βββββββββββββββ ββββββββββββββ βββββββββββββββ β
* βββββββββββββββββββ ββββββββββββββββ βββββββββββββββ β
* βββββββββββββββββββ βββββββ ββββββββ βββββββββββββββ
* β ββββββββββββββββββββββββββββββββββ βββββββββββ βββββββββββββββββββββββ
* β ββββββββββββββββββββββββββββββββββ βββββββββββ βββββββββββββββββββββββ
* β βββ ββββββ ββββββββ βββ βββββββββββ ββββββ βββ ββββββ
* β βββ ββββββ ββββββββ βββ βββββββββββ ββββββ βββ ββββββ
* β βββ ββββββββββββββββ βββ ββββββββββββββββββββ βββ ββββββββ
* β βββ ββββββββββββββββ βββ ββββββββ βββββββ βββ βββ ββββββββ
* β ESP32-S3 Firmware Version V: 1.0.1 β
* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* ESP32-S3 Advanced TFT_eSPI ST7735 Comprehensive Test & Benchmark Master Suite
* Real-Time Hardware Diagnostics Engine, Memory Verification & Boot Statistics
*
* Version: 1.8.1 "TFT Raster Test Suite"
* Designed by: Sir Ronnie from Core1D Automation Labs
* License: MIT License + Core1D Labs Commercial Usage Rights
* Target: ESP32-S3 DevKit C1 N8R8 with 1.8" ST7735 SPI TFT Display (128x160)
* Built-in NeoPixel RGB LED (GPIO 38)
*
* HARDWARE CONNECTIONS:
* =====================
*
* ST7735 1.8" TFT Display (128x160 Portrait Mode):
* ββββββββββββββββββββββββββββββββββββββββββββββ
* β ST7735 Pin β ESP32-S3 DevKit C1 Pin β
* ββββββββββββββββββΌββββββββββββββββββββββββββββ
* β VCC β 3V3 β
* β GND β GND β
* β CS β GPIO 10 (FSPICS0) β
* β RESET/RST β GPIO 5 (TFT_RST) β
* β A0/DC β GPIO 4 (TFT_DC) β
* β SDA/MOSI β GPIO 11 (FSPID) β
* β SCK/SCLK β GPIO 12 (FSPICLK) β
* β LED (BL) β GPIO 6 (Optional BL) β
* ββββββββββββββββββββββββββββββββββββββββββββββ
*
* Built-in NeoPixel RGB LED:
* ββββββββββββββββββββββββββββββββββββββββββββββ
* β NeoPixel β ESP32-S3 DevKit C1 β
* ββββββββββββββββββΌββββββββββββββββββββββββββββ
* β Data Signal β GPIO 38 (Built-in) β
* β VCC β Internal 3V3 β
* β GND β Internal GND β
* ββββββββββββββββββββββββββββββββββββββββββββββ
*
* HARDWARE ASCII SCHEMATIC:
* ========================
*
* ESP32-S3 DevKit C1 N8R8 ST7735 1.8" TFT Display
* βββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββ
* β β β β
* β [USB-C] 3V3 ββββββββββββ€β VCC β
* β β β β
* β [WiFi Antenna] β β β
* β [Bluetooth 5.0 LE] β β β
* β β β β
* β GPIO 11 ββββββββββββ€β SDA (MOSI) β
* β GPIO 12 ββββββββββββ€β SCK (SCLK) β
* β GPIO 10 ββββββββββββ€β CS β
* β GPIO 5 ββββββββββββ€β RST β
* β GPIO 4 ββββββββββββ€β DC (A0) β
* β GPIO 6 ββββββββββββ€β LED (Optional) β
* β β β β
* β GND ββββββββββββ€β GND β
* β β β β
* β βββββββββββββββββββ β β βββββββββββββββββββββββ β
* β β ESP32-S3-WROOM-1β β β β 128x160 Portrait β β
* β β N8R8 Module β β β β 65K Colors β β
* β β 240MHz Dual β β β β SPI Interface β β
* β β Core + RISC-V β β β β ST7735 Driver β β
* β β 8MB Flash β β β βββββββββββββββββββββββ β
* β β 8MB PSRAM β β β β
* β βββββββββββββββββββ β βββββββββββββββββββββββββββ
* β β β
* β β GPIO 38 (NeoPixel RGB) β β
* β [WS2812B Built-in LED] β β
* β β β
* βββββββββββββββββββββββββββββββ β
* β β
* ββββ [ Xtensa LX7 Dual-Core @ 240MHz ] βββ
* [ WiFi 802.11 b/g/n + BLE 5.0 ]
*
* TFT_eSPI LIBRARY SETUP INSTRUCTIONS:
* =====================================
* 1. Install TFT_eSPI library (v2.5.4+) by Bodmer via Arduino Library Manager
* 2. Navigate to: Documents/Arduino/libraries/TFT_eSPI/
* 3. Edit User_Setup.h with these tested settings:
*
* // Driver Selection
* #define ST7735_DRIVER
* #define ST7735_BLACKTAB // or REDTAB/GREENTAB based on your display
*
* // Pin Definitions for ESP32-S3
* #define TFT_CS 10 // Chip select control pin
* #define TFT_DC 4 // Data Command control pin
* #define TFT_RST 5 // Reset pin (could connect to ESP32 RST)
* #define TFT_BL 6 // LED back-light (optional)
*
* // Hardware SPI pins (ESP32-S3 FSPI)
* #define TFT_MOSI 11 // SPI Master Out Slave In
* #define TFT_SCLK 12 // SPI Serial Clock
*
* // SPI Configuration
* #define USE_FSPI_PORT // Use FSPI port on ESP32-S3
* #define SPI_FREQUENCY 27000000 // 27MHz for optimal performance
* #define SPI_READ_FREQUENCY 20000000 // 20MHz for read operations
* #define SPI_TOUCH_FREQUENCY 2500000 // Not used but required
*
* // Font Configuration
* #define LOAD_GLCD // Font 1: Original Adafruit 8 pixel font
* #define LOAD_FONT2 // Font 2: Small 16 pixel high font
* #define LOAD_FONT4 // Font 4: Medium 26 pixel high font
* #define LOAD_FONT6 // Font 6: Large 48 pixel high font
* #define LOAD_GFXFF // FreeFonts
* #define SMOOTH_FONT // Enable anti-aliased fonts
*
* 4. Arduino IDE Settings:
* - Board: "ESP32S3 Dev Module"
* - CPU Frequency: "240MHz (WiFi)"
* - Flash Size: "8MB (64Mb)"
* - PSRAM: "OPI PSRAM"
* - Partition Scheme: "8M with spiffs (3MB APP/1.5MB SPIFFS)"
* - Core Debug Level: "Info"
* - Arduino Runs On: "Core 1"
* - Events Run On: "Core 1"
*
*/
/**
* ESP32-S3 TFT_eSPI ST7735 Comprehensive Sanity Test & Benchmark Suite V1.5
* ============================================================
*
* SETUP INSTRUCTIONS FOR TFT_eSPI:
* 1. Install TFT_eSPI library (v2.5.4+) by Bodmer via Library Manager
* 2. Navigate to: Documents/Arduino/libraries/TFT_eSPI/
* 3. Replace User_Setup.h with provided configuration
* 4. Key settings in User_Setup.h:
* - #define ST7735_DRIVER
* - #define ST7735_BLACKTAB (or REDTAB/GREENTAB as needed)
* - Pin definitions: CS=10, DC=4, RST=5, BL=6, MOSI=11, SCLK=12
* - #define USE_HSPI_PORT
* - #define SPI_FREQUENCY 10000000 (10MHz)
* 5. Use Arduino IDE 2.2.1 or ESP32 board package v2.0.14 for best compatibility
*
* HARDWARE CONNECTIONS:
* ST7735 -> ESP32-S3
* VCC -> 3.3V
* GND -> GND
* CS -> GPIO 10
* RESET -> GPIO 5
* A0/DC -> GPIO 4
* SDA -> GPIO 11 (MOSI)
* SCK -> GPIO 12 (SCLK)
* LED -> GPIO 6 (Backlight)
*
* NeoPixel -> ESP32-S3 DevKit C1
* Data -> GPIO 48 (Built-in RGB LED)
*
* FEATURES:
* - Hardware diagnostics and memory verification
* - Built-in NeoPixel RGB LED control
* - Advanced animations (plasma, matrix rain, starfield)
* - Comprehensive RAM/Flash/PSRAM testing
* - Chip ID and metadata display
*/
#include <TFT_eSPI.h>
#include <SPI.h>
#include <Adafruit_NeoPixel.h> // To drive the RGB LED
#include <esp_chip_info.h>
#include <esp_flash.h>
#include <soc/soc.h>
#include <soc/rtc_cntl_reg.h>
#include <Preferences.h> // For non-volatile storage
// NeoPixel Configuration (ESP32-S3 DevKit C1 built-in RGB LED)
constexpr uint8_t NEOPIXEL_PIN = 38; // Devkit C1
constexpr uint8_t NEOPIXEL_COUNT = 1;
constexpr uint8_t NEOPIXEL_MAX_BRIGHTNESS = 2; // Range: 0 - 255.
// Hardware test constants
constexpr size_t MEMORY_TEST_CHUNK_SIZE = 1024;
constexpr uint32_t MEMORY_TEST_PATTERN = 0xDEADBEEF;
// Animation constants
constexpr uint8_t PLASMA_SCALE = 11;
constexpr uint8_t MATRIX_DROPS = 17;
constexpr uint8_t STARFIELD_STARS = 200;
// Boot counter constants
constexpr const char* BOOT_COUNTER_NAMESPACE = "boot_stats";
constexpr const char* BOOT_COUNT_KEY = "boot_count";
constexpr const char* SUCCESSFUL_CYCLES_KEY = "cycles_ok";
// Create instances
TFT_eSPI tft = TFT_eSPI();
Adafruit_NeoPixel neopixel(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
Preferences preferences;
// Benchmark variables
unsigned long frameCount = 0;
unsigned long lastFPSUpdate = 0;
float currentFPS = 0.0;
size_t initialFreeHeap = 0;
size_t videoBufferSize = 0;
// Boot tracking variables
uint32_t bootCount = 0;
uint32_t successfulCycles = 0;
bool bootCounterAvailable = false;
// Animation variables
float angle = 0.0;
int waveOffset = 0;
unsigned long lastAnimUpdate = 0;
uint8_t plasmaPhase = 0;
int matrixDrops[MATRIX_DROPS];
struct Star {
float x, y, z;
} stars[STARFIELD_STARS];
// Hardware info structure
struct HardwareInfo {
esp_chip_info_t chipInfo;
uint32_t chipId;
uint32_t flashSize;
uint32_t psramSize;
size_t freeHeap;
size_t totalHeap;
size_t freePsram;
size_t totalPsram;
};
// Test colors array
constexpr uint16_t testColors[] = {
TFT_RED, TFT_GREEN, TFT_BLUE, TFT_YELLOW,
TFT_MAGENTA, TFT_CYAN, TFT_WHITE, TFT_ORANGE,
TFT_PINK, TFT_GREENYELLOW, TFT_SKYBLUE, TFT_VIOLET
};
// NeoPixel colors
constexpr uint32_t neoPixelColors[] = {
0xFF0000, // Red
0x00FF00, // Green
0x0000FF, // Blue
0xFFFF00, // Yellow
0xFF4500, // Orange Red
0x9400D3, // Violet
0x00FFFF, // Cyan
0xFF1493, // Deep Pink
0x32CD32, // Lime Green
0x8A2BE2, // Blue Violet
0xFF6347, // Tomato
0x4169E1 // Royal Blue
};
// Timing control
struct TestTiming {
unsigned long startTime;
unsigned long duration;
bool isRunning;
};
void initializeBootCounter() {
Serial.println(F("\n[BOOT COUNTER INITIALIZATION]"));
Serial.println(F("----------------------------------------"));
// Initialize preferences for non-volatile storage
bootCounterAvailable = preferences.begin(BOOT_COUNTER_NAMESPACE, false);
if (bootCounterAvailable) {
// Read current boot statistics
bootCount = preferences.getUInt(BOOT_COUNT_KEY, 0);
successfulCycles = preferences.getUInt(SUCCESSFUL_CYCLES_KEY, 0);
// Increment boot count
bootCount++;
preferences.putUInt(BOOT_COUNT_KEY, bootCount);
Serial.println(F(" Boot counter initialized successfully"));
Serial.printf(" Total boots: %lu\n", bootCount);
Serial.printf(" Successful test cycles: %lu\n", successfulCycles);
Serial.printf(" Success rate: %.1f%%\n",
bootCount > 0 ? (float)successfulCycles * 100 / bootCount : 0.0);
// Display boot info on screen
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(2, 15);
tft.printf("Boot #%lu", bootCount);
tft.setCursor(2, 25);
tft.printf("Cycles: %lu", successfulCycles);
if (bootCount > 1) {
tft.setCursor(2, 35);
tft.printf("Rate: %.1f%%", (float)successfulCycles * 100 / bootCount);
}
} else {
Serial.println(F("β Boot counter not available (Preferences failed)"));
Serial.println(F(" Continuing without boot tracking..."));
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setCursor(2, 15);
tft.println(F("Boot tracking"));
tft.setCursor(2, 25);
tft.println(F("unavailable"));
}
delay(2000); // Show boot info for 2 seconds
}
void recordSuccessfulCycle() {
if (bootCounterAvailable) {
successfulCycles++;
preferences.putUInt(SUCCESSFUL_CYCLES_KEY, successfulCycles);
Serial.printf(" Recorded successful cycle #%lu\n", successfulCycles);
Serial.printf(" Success rate: %.1f%%\n", (float)successfulCycles * 100 / bootCount);
}
}
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println(F("\n============================================================"));
Serial.println(F("ESP32-S3 TFT_eSPI ST7735 ADVANCED COMPREHENSIVE TEST SUITE"));
Serial.println(F("With Advanced NeoPixel Integration and Boot Tracking"));
Serial.println(F("============================================================"));
// Initialize NeoPixel with startup sequence
neopixel.begin();
neopixel.setBrightness(NEOPIXEL_MAX_BRIGHTNESS);
neoPixelStartupSequence();
// Record initial memory state
initialFreeHeap = ESP.getFreeHeap();
Serial.printf("Initial Free Heap: %zu bytes\n", initialFreeHeap);
// Initialize display
Serial.println(F("\n[1/13] DISPLAY INITIALIZATION TEST"));
Serial.println(F("----------------------------------------"));
setNeoPixelColor(0x0000FF); // Blue for initialization
tft.init();
tft.setRotation(0); // Portrait mode (Currently everything is static set for portrait aspect ratios only, ideal practice for embedded systems)
Serial.printf("Display Resolution: %d x %d\n", tft.width(), tft.height());
Serial.println(F("β TFT_eSPI initialization successful!"));
// Calculate approximate video buffer size
videoBufferSize = tft.width() * tft.height() * 2; // 16-bit color
Serial.printf("Video Buffer Size: %zu bytes (%.1f KB)\n",
videoBufferSize, videoBufferSize / 1024.0);
// Clear screen
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.setCursor(2, 2);
tft.println(F("Boot Tracking Init"));
// Initialize boot counter (test 2/13)
Serial.println(F("\n[2/13] BOOT COUNTER INITIALIZATION"));
setNeoPixelColor(0x9400D3); // Violet for boot tracking
initializeBootCounter();
setNeoPixelColor(0x00FF00); // Green - ready for tests
// Run comprehensive tests
runHardwareDiagnostics(); // 3/13
runEnhancedMemoryVerificationTest(); // 4/13
runColorAccuracyTest(); // 5/13
runGeometricDrawingTest(); // 6/13
runTextRenderingTest(); // 7/13
runEnhancedAdvancedAnimationTests(); // 8/13
runAnimationTest(); // 9/13 (FIXED)
runMemoryStressTest(); // 10/13
runPerformanceBenchmark(); // 11/13
runSpriteTest(); // 12/13 (Drawing performance test)
runEnhancedNeoPixelTest(); // 13/13
Serial.println(F("\n============================================================"));
Serial.println(F("ALL TESTS COMPLETED SUCCESSFULLY!"));
Serial.println(F("============================================================"));
// Clear screen completely before celebration
tft.fillScreen(TFT_BLACK);
delay(500);
// Success celebration with clear display
neoPixelCelebrationSequence();
// Show "ALL TESTS PASSED" message clearly
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setTextSize(2);
tft.setCursor(5, 40);
tft.println(F("ALL TESTS"));
tft.setCursor(20, 60);
tft.println(F("PASSED!"));
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(5, 90);
tft.println(F("13/13 tests successful"));
tft.setCursor(5, 100);
tft.printf("Memory used: %d KB", (initialFreeHeap - ESP.getFreeHeap())/1024);
tft.setCursor(5, 110);
tft.printf("Display: %dx%d", tft.width(), tft.height());
// Show this for 3 seconds
delay(3000);
// Final system check with clean display
runFinalSystemCheck();
Serial.println(F("\n============================================================"));
Serial.println(F("ENTERING DEMO CYCLE"));
Serial.println(F("============================================================"));
// Init starfield for demo mode
initializeStarfield();
}
void neoPixelStartupSequence() {
// Startup rainbow sweep
for (int i = 0; i < 12; i++) {
setNeoPixelColor(neoPixelColors[i]);
delay(150);
}
// Breathing effect
for (int cycle = 0; cycle < 3; cycle++) {
for (int brightness = 0; brightness <= NEOPIXEL_MAX_BRIGHTNESS; brightness += 2) {
neopixel.setBrightness(brightness);
setNeoPixelColor(0x4000FF);
delay(30);
}
for (int brightness = NEOPIXEL_MAX_BRIGHTNESS; brightness >= 0; brightness -= 2) {
neopixel.setBrightness(brightness);
setNeoPixelColor(0x4000FF);
delay(30);
}
}
neopixel.setBrightness(NEOPIXEL_MAX_BRIGHTNESS);
}
void neoPixelCelebrationSequence() {
// Rainbow celebration
for (int cycle = 0; cycle < 3; cycle++) {
for (int i = 0; i < 12; i++) {
setNeoPixelColor(neoPixelColors[i]);
delay(100);
}
}
// Final flash
for (int i = 0; i < 6; i++) {
setNeoPixelColor(0xFFFFFF); // White
delay(100);
setNeoPixelColor(0x000000); // Off
delay(100);
}
setNeoPixelColor(0x0000FF); // Blue for demo mode
}
void setNeoPixelColor(uint32_t color) {
neopixel.setPixelColor(0, color);
neopixel.show();
}
void setNeoPixelColorSmooth(uint32_t color, int transitionTime = 200) {
// Get current color (simplified - assumes we know the previous color)
static uint32_t lastColor = 0x000000;
uint8_t r1 = (lastColor >> 16) & 0xFF;
uint8_t g1 = (lastColor >> 8) & 0xFF;
uint8_t b1 = lastColor & 0xFF;
uint8_t r2 = (color >> 16) & 0xFF;
uint8_t g2 = (color >> 8) & 0xFF;
uint8_t b2 = color & 0xFF;
int steps = transitionTime / 10;
for (int i = 0; i <= steps; i++) {
uint8_t r = r1 + ((r2 - r1) * i / steps);
uint8_t g = g1 + ((g2 - g1) * i / steps);
uint8_t b = b1 + ((b2 - b1) * i / steps);
uint32_t intermediateColor = (r << 16) | (g << 8) | b;
setNeoPixelColor(intermediateColor);
delay(10);
}
lastColor = color;
}
TestTiming createTestTimer(unsigned long durationMs) {
TestTiming timer;
timer.startTime = millis();
timer.duration = durationMs;
timer.isRunning = true;
return timer;
}
bool isTestRunning(TestTiming& timer) {
if (!timer.isRunning) return false;
if (millis() - timer.startTime >= timer.duration) {
timer.isRunning = false;
return false;
}
return true;
}
float getTestProgress(const TestTiming& timer) {
if (!timer.isRunning) return 1.0;
return (float)(millis() - timer.startTime) / timer.duration;
}
void runHardwareDiagnostics() {
Serial.println(F("\n[2/12] HARDWARE DIAGNOSTICS"));
Serial.println(F("----------------------------------------"));
setNeoPixelColorSmooth(0x00FFFF); // Cyan for diagnostics
HardwareInfo hw = {};
// Get chip info
esp_chip_info(&hw.chipInfo);
hw.chipId = ESP.getChipModel() ? (uint32_t)ESP.getEfuseMac() : 0;
// Get mem info
hw.freeHeap = ESP.getFreeHeap();
hw.totalHeap = ESP.getHeapSize();
hw.freePsram = ESP.getFreePsram();
hw.totalPsram = ESP.getPsramSize();
// Get flash size
esp_flash_get_size(NULL, &hw.flashSize);
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.setTextSize(1);
tft.setCursor(2, 2);
tft.println(F("Hardware Diagnostics"));
// Diagnostic display
TestTiming timer = createTestTimer(6000); // 6 seconds for diagnostics
int displayStep = 0;
while (isTestRunning(timer)) {
unsigned long stepTime = (millis() - timer.startTime) / 500;
if (stepTime != displayStep) {
displayStep = stepTime;
// Pulse NeoPixel during diagnostics
int brightness = NEOPIXEL_MAX_BRIGHTNESS * (0.5 + 0.5 * sin(millis() * 0.01));
neopixel.setBrightness(brightness);
setNeoPixelColor(0x00FFFF);
switch (displayStep % 4) {
case 0:
// Display chip info
tft.fillRect(2, 15, 124, 50, TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(2, 15);
tft.printf("Model: %s", ESP.getChipModel());
tft.setCursor(2, 25);
tft.printf("Cores: %d", hw.chipInfo.cores);
tft.setCursor(2, 35);
tft.printf("Revision: %d", hw.chipInfo.revision);
tft.setCursor(2, 45);
tft.printf("CPU: %d MHz", ESP.getCpuFreqMHz());
break;
case 1:
// Mem info
tft.fillRect(2, 15, 124, 50, TFT_BLACK);
tft.setCursor(2, 15);
tft.printf("Flash: %lu MB", hw.flashSize / (1024*1024));
tft.setCursor(2, 25);
tft.printf("Heap: %zu/%zu KB", hw.freeHeap/1024, hw.totalHeap/1024);
tft.setCursor(2, 35);
tft.printf("PSRAM: %zu/%zu KB", hw.freePsram/1024, hw.totalPsram/1024);
break;
case 2:
// Features
tft.fillRect(2, 15, 124, 50, TFT_BLACK);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setCursor(2, 15);
tft.printf("WiFi: %s", (hw.chipInfo.features & CHIP_FEATURE_WIFI_BGN) ? "YES" : "NO");
tft.setCursor(2, 25);
tft.printf("BLE: %s", (hw.chipInfo.features & CHIP_FEATURE_BLE) ? "YES" : "NO");
tft.setCursor(2, 35);
tft.printf("IEEE802.15.4: %s", (hw.chipInfo.features & CHIP_FEATURE_IEEE802154) ? "YES" : "NO");
break;
case 3:
// Progress indicator
tft.fillRect(2, 15, 124, 50, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(2, 15);
tft.println("Scanning hardware...");
// Progress bar
float progress = getTestProgress(timer);
int barWidth = (int)(120 * progress);
tft.drawRect(2, 30, 120, 8, TFT_WHITE);
tft.fillRect(3, 31, barWidth-1, 6, TFT_GREEN);
break;
}
}
delay(1000);
}
neopixel.setBrightness(NEOPIXEL_MAX_BRIGHTNESS);
setNeoPixelColor(0x00FF00); // Green = success
Serial.println(F("Hardware Information:"));
Serial.printf(" Chip: %s (Rev %d)\n", ESP.getChipModel(), hw.chipInfo.revision);
Serial.printf(" Cores: %d, CPU: %d MHz\n", hw.chipInfo.cores, ESP.getCpuFreqMHz());
Serial.printf(" Flash: %lu bytes (%.1f MB)\n", hw.flashSize, hw.flashSize / (1024.0*1024.0));
Serial.printf(" Heap: %zu/%zu bytes\n", hw.freeHeap, hw.totalHeap);
Serial.printf(" PSRAM: %zu/%zu bytes\n", hw.freePsram, hw.totalPsram);
Serial.println(F("Hardware diagnostics completed"));
delay(1000);
}
void runEnhancedMemoryVerificationTest() {
Serial.println(F("\n[3/12] ENHANCED MEMORY VERIFICATION TEST"));
Serial.println(F("----------------------------------------"));
setNeoPixelColorSmooth(0xFF4500); // Orange for memory testing
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_ORANGE, TFT_BLACK);
tft.setCursor(2, 2);
tft.println(F("Memory Verification"));
TestTiming timer = createTestTimer(8000); // 8 seconds for thorough memory test
size_t totalTested = 0;
size_t errors = 0;
// Test heap memory in chunks
const size_t freeHeap = ESP.getFreeHeap();
const size_t testSize = (freeHeap / 4 < 32768) ? (freeHeap / 4) : 32768;
const size_t chunkSize = 256; // Small chunks for visual feedback
tft.setCursor(2, 15);
tft.printf("Testing %zu bytes", testSize);
// Visual memory map
const int mapWidth = 120;
const int mapHeight = 60;
const int mapStartY = 40;
tft.drawRect(2, mapStartY, mapWidth, mapHeight, TFT_WHITE);
tft.setCursor(2, mapStartY + mapHeight + 5);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
uint8_t* testBuffer = (uint8_t*)malloc(testSize);
if (testBuffer) {
size_t chunksTotal = testSize / chunkSize;
while (isTestRunning(timer) && totalTested < testSize) {
size_t currentChunk = totalTested / chunkSize;
size_t chunkStart = currentChunk * chunkSize;
size_t chunkEnd = (chunkStart + chunkSize < testSize) ? (chunkStart + chunkSize) : testSize;
// NeoPixel color based on test phase
if (totalTested < testSize / 3) {
setNeoPixelColor(0xFF0000); // Red = writing phase
} else if (totalTested < 2 * testSize / 3) {
setNeoPixelColor(0xFFFF00); // Yellow = verification phase
} else {
setNeoPixelColor(0x00FF00); // Green = final phase
}
// Write test pattern to current chunk
for (size_t i = chunkStart; i < chunkEnd; i++) {
testBuffer[i] = (uint8_t)(i ^ 0xAA);
}
// Verify test pattern in current chunk
for (size_t i = chunkStart; i < chunkEnd; i++) {
if (testBuffer[i] != (uint8_t)(i ^ 0xAA)) {
errors++;
}
totalTested++;
}
// Update visual memory map
int mapX = (currentChunk * mapWidth) / chunksTotal;
int mapY = mapStartY + 1;
uint16_t chunkColor = errors > 0 ? TFT_RED : TFT_GREEN;
// Draw memory chunk status
tft.fillRect(2 + mapX, mapY, (mapWidth / chunksTotal) > 1 ? (mapWidth / chunksTotal) : 1, mapHeight - 2, chunkColor);
// Update status text
tft.fillRect(2, 110, 124, 30, TFT_BLACK);
tft.setCursor(2, 110);
tft.setTextColor(errors > 0 ? TFT_RED : TFT_GREEN, TFT_BLACK);
tft.printf("Tested: %zu/%zu", totalTested, testSize);
tft.setCursor(2, 120);
tft.printf("Errors: %zu", errors);
tft.setCursor(2, 130);
tft.printf("Progress: %.1f%%", (float)totalTested * 100 / testSize);
delay(50); // Slow down for perception
}
free(testBuffer);
// Test PSRAM (if available)
if (ESP.getPsramSize() > 0) {
setNeoPixelColor(0x9400D3); // Violet for PSRAM test
const size_t freePsram = ESP.getFreePsram();
const size_t psramTestSize = (freePsram / 4 < 65536) ? (freePsram / 4) : 65536;
uint8_t* psramBuffer = (uint8_t*)ps_malloc(psramTestSize);
if (psramBuffer) {
size_t psramErrors = 0;
tft.fillRect(2, 140, 124, 20, TFT_BLACK);
tft.setCursor(2, 140);
tft.setTextColor(TFT_VIOLET, TFT_BLACK);
tft.printf("Testing PSRAM...");
// Test PSRAM with visual feedback
for (size_t i = 0; i < psramTestSize; i += 1024) {
size_t chunkEnd = min(i + 1024, psramTestSize);
// Write pattern
for (size_t j = i; j < chunkEnd; j++) {
psramBuffer[j] = (uint8_t)(j ^ 0x55);
}
// Verify pattern
for (size_t j = i; j < chunkEnd; j++) {
if (psramBuffer[j] != (uint8_t)(j ^ 0x55)) {
psramErrors++;
}
}
// Update PSRAM progress
if (i % 8192 == 0) {
tft.fillRect(2, 150, 124, 10, TFT_BLACK);
tft.setCursor(2, 150);
tft.printf("PSRAM: %.1f%%", (float)i * 100 / psramTestSize);
}
delay(1);
}
free(psramBuffer);
tft.fillRect(2, 140, 124, 20, TFT_BLACK);
tft.setCursor(2, 140);
tft.setTextColor(psramErrors > 0 ? TFT_RED : TFT_GREEN, TFT_BLACK);
tft.printf("PSRAM: %zu errors", psramErrors);
errors += psramErrors;
}
}
}
// Final result
setNeoPixelColor(errors > 0 ? 0xFF0000 : 0x00FF00); // Red if errors, Green if success
Serial.printf("Enhanced memory verification: %zu bytes tested, %zu errors\n", totalTested, errors);
delay(2000);
}
void runEnhancedAdvancedAnimationTests() {
Serial.println(F("\n[4/12] ADVANCED ANIMATION TESTS"));
Serial.println(F("----------------------------------------"));
// Plasma effect with NeoPixel sync
runEnhancedPlasmaEffect();
// Matrix rain effect with reactive NeoPixel
runEnhancedMatrixRain();
// Starfield effect with speed-reactive NeoPixel
runEnhancedStarfield();
Serial.println(F("Advanced animations completed"));
}
void runEnhancedPlasmaEffect() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_PURPLE, TFT_BLACK);
tft.setCursor(2, 2);
tft.println(F("Plasma"));
TestTiming timer = createTestTimer(5000); // Guaranteed 5 seconds
while (isTestRunning(timer)) {
float progress = getTestProgress(timer);
// NeoPixel color synced to plasma animation
uint8_t neoR = (uint8_t)(128 + 127 * sin(plasmaPhase * 0.1));
uint8_t neoG = (uint8_t)(128 + 127 * sin(plasmaPhase * 0.15));
uint8_t neoB = (uint8_t)(128 + 127 * sin(plasmaPhase * 0.2));
uint32_t neoColor = (neoR << 16) | (neoG << 8) | neoB;
setNeoPixelColor(neoColor);
for (int x = 0; x < tft.width(); x += 2) {
for (int y = 20; y < tft.height(); y += 2) {
int color1 = (int)(128 + 127 * sin((x + plasmaPhase) / 16.0));
int color2 = (int)(128 + 127 * sin((y + plasmaPhase) / 8.0));
int color3 = (int)(128 + 127 * sin(sqrt((x-64)*(x-64) + (y-80)*(y-80)) / 8.0 + plasmaPhase));
uint8_t r = (color1 + color3) / 2;
uint8_t g = (color2 + color3) / 2;
uint8_t b = (color1 + color2) / 2;
uint16_t color = tft.color565(r, g, b);
tft.drawPixel(x, y, color);
}
}
// Progress indicator
int progressBar = (int)(120 * progress);
tft.drawRect(2, 12, 120, 4, TFT_WHITE);
tft.fillRect(2, 12, progressBar, 4, TFT_GREEN);
plasmaPhase += 4;
delay(50);
}
}
void runEnhancedMatrixRain() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(2, 2);
tft.println(F("Matrix"));
// Initialize drops
for (int i = 0; i < MATRIX_DROPS; i++) {
matrixDrops[i] = random(-50, 0);
}
TestTiming timer = createTestTimer(5000); // Guaranteed 5 seconds
int frameCounter = 0;
while (isTestRunning(timer)) {
frameCounter++;
// NeoPixel pulses with matrix intensity
uint8_t intensity = (uint8_t)(128 + 127 * sin(frameCounter * 0.2));
uint32_t matrixGreen = (0x00 << 16) | (intensity << 8) | 0x00;
setNeoPixelColor(matrixGreen);
tft.fillRect(0, 15, tft.width(), tft.height()-15, TFT_BLACK);
for (int i = 0; i < MATRIX_DROPS; i++) {
int x = i * (tft.width() / MATRIX_DROPS) + 2;
// Draw trail
for (int j = 0; j < 8; j++) {
int y = matrixDrops[i] - j * 8;
if (y >= 15 && y < tft.height()) {
uint8_t brightness = 255 - j * 32;
uint16_t color = tft.color565(0, brightness, 0);
char c = random(33, 127);
tft.setTextColor(color, TFT_BLACK);
tft.setCursor(x, y);
tft.write(c);
}
}
matrixDrops[i] += 8;
if (matrixDrops[i] > tft.height()) {
matrixDrops[i] = random(-50, -8);
}
}
// Progress indicator
float progress = getTestProgress(timer);
int progressBar = (int)(120 * progress);
tft.drawRect(2, 12, 120, 2, TFT_WHITE);
tft.fillRect(2, 12, progressBar, 2, TFT_GREEN);
delay(100);
}
}
void runEnhancedStarfield() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(2, 2);
tft.println(F("FTL"));
TestTiming timer = createTestTimer(5000); // Guaranteed 5 seconds
int speedLevel = 1;
while (isTestRunning(timer)) {
float progress = getTestProgress(timer);
// Increase speed over time
speedLevel = (int)(1 + progress * 20);
// NeoPixel color represents warp speed
uint8_t blue = 255;
uint8_t white = (uint8_t)(progress * 255);
uint32_t warpColor = (white << 16) | (white << 8) | blue;
setNeoPixelColor(warpColor);
tft.fillRect(0, 15, tft.width(), tft.height()-15, TFT_BLACK);
for (int i = 0; i < STARFIELD_STARS; i++) {
// Move star towards viewer with increasing speed
stars[i].z -= speedLevel;
if (stars[i].z <= 0) {
stars[i].x = random(-1000, 1000);
stars[i].y = random(-1000, 1000);
stars[i].z = 1000;
}
// Project to screen coordinates
int sx = (int)(stars[i].x / stars[i].z * 100) + tft.width()/2;
int sy = (int)(stars[i].y / stars[i].z * 100) + tft.height()/2;
if (sx >= 0 && sx < tft.width() && sy >= 15 && sy < tft.height()) {
uint8_t brightness = 255 - (uint8_t)(stars[i].z / 4);
uint16_t color = tft.color565(brightness, brightness, brightness);
tft.drawPixel(sx, sy, color);
// Draw star trails at high speed
if (speedLevel > 10) {
int prevX = (int)(stars[i].x / (stars[i].z + speedLevel) * 100) + tft.width()/2;
int prevY = (int)(stars[i].y / (stars[i].z + speedLevel) * 100) + tft.height()/2;
if (prevX >= 0 && prevX < tft.width() && prevY >= 15 && prevY < tft.height()) {
tft.drawLine(prevX, prevY, sx, sy, color);
}
}
}
}
// Speed indicator
tft.fillRect(2, 12, 120, 10, TFT_BLACK);
tft.setCursor(2, 12);
tft.printf("Warp %d", speedLevel);
delay(20);
}
}
void runEnhancedNeoPixelTest() {
Serial.println(F("\n[13/13] ADVANCED NEOPIXEL RGB LED TEST"));
Serial.println(F("----------------------------------------"));
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_MAGENTA, TFT_BLACK);
tft.setCursor(2, 2);
tft.println(F("Enhanced NeoPixel"));
TestTiming timer = createTestTimer(8000); // 8 seconds for comprehensive test
// FIXED: Color cycle test with proper index matching
for (int i = 0; i < 12 && isTestRunning(timer); i++) {
// Use modulo to ensure we don't exceed neoPixelColors array size
int neoIndex = i % (sizeof(neoPixelColors) / sizeof(neoPixelColors[0]));
setNeoPixelColorSmooth(neoPixelColors[neoIndex], 300);
tft.fillRect(2, 20 + (i % 6)*15, 60, 10, TFT_BLACK);
tft.setCursor(2, 20 + (i % 6)*15);
tft.setTextColor(testColors[i % 12], TFT_BLACK); // Ensure we stay within testColors bounds
tft.printf("Color %d", i+1);
// Draw color sample on screen that matches the NeoPixel
// Convert NeoPixel color to 16-bit TFT color for accurate display
uint32_t neoColor = neoPixelColors[neoIndex];
uint8_t r = (neoColor >> 16) & 0xFF;
uint8_t g = (neoColor >> 8) & 0xFF;
uint8_t b = neoColor & 0xFF;
uint16_t tftColor = tft.color565(r, g, b);
tft.fillRect(70, 20 + (i % 6)*15, 50, 10, tftColor);
delay(250);
}
if (isTestRunning(timer)) {
// Breathing effect with multiple colors
tft.fillRect(2, 110, 124, 30, TFT_BLACK);
tft.setCursor(2, 110);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.println(F("Breathing Effects"));
uint32_t breatheColors[] = {0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00};
for (int colorIdx = 0; colorIdx < 4 && isTestRunning(timer); colorIdx++) {
tft.setCursor(2, 120);
tft.printf("Breathing: %d/4", colorIdx + 1);
for (int cycle = 0; cycle < 2 && isTestRunning(timer); cycle++) {
// Breathe in
for (int brightness = 0; brightness <= NEOPIXEL_MAX_BRIGHTNESS && isTestRunning(timer); brightness += 2) {
neopixel.setBrightness(brightness);
setNeoPixelColor(breatheColors[colorIdx]);
// Visual breathing indicator
int barHeight = (brightness * 20) / NEOPIXEL_MAX_BRIGHTNESS;
tft.fillRect(90, 140 - barHeight, 10, barHeight, testColors[colorIdx * 3]);
tft.fillRect(90, 120, 10, 140 - 120 - barHeight, TFT_BLACK);
delay(40);
}
// Breathe out
for (int brightness = NEOPIXEL_MAX_BRIGHTNESS; brightness >= 0 && isTestRunning(timer); brightness -= 2) {
neopixel.setBrightness(brightness);
setNeoPixelColor(breatheColors[colorIdx]);
// Visual breathing indicator
int barHeight = (brightness * 20) / NEOPIXEL_MAX_BRIGHTNESS;
tft.fillRect(90, 140 - barHeight, 10, barHeight, testColors[colorIdx * 3]);
tft.fillRect(90, 120, 10, 140 - 120 - barHeight, TFT_BLACK);
delay(40);
}
}
}
}
// Rainbow effect
if (isTestRunning(timer)) {
tft.fillRect(2, 120, 80, 20, TFT_BLACK);
tft.setCursor(2, 120);
tft.println(F("Rainbow Effect"));
unsigned long rainbowStart = millis();
while (isTestRunning(timer) && (millis() - rainbowStart < 3000)) {
for (int hue = 0; hue < 360 && isTestRunning(timer); hue += 5) {
// Convert HSV to RGB
float h = hue / 60.0;
float c = 1.0;
float x = c * (1.0 - abs(fmod(h, 2.0) - 1.0));
float r1, g1, b1;
if (h < 1) { r1 = c; g1 = x; b1 = 0; }
else if (h < 2) { r1 = x; g1 = c; b1 = 0; }
else if (h < 3) { r1 = 0; g1 = c; b1 = x; }
else if (h < 4) { r1 = 0; g1 = x; b1 = c; }
else if (h < 5) { r1 = x; g1 = 0; b1 = c; }
else { r1 = c; g1 = 0; b1 = x; }
uint8_t r = (uint8_t)(r1 * 255);
uint8_t g = (uint8_t)(g1 * 255);
uint8_t b = (uint8_t)(b1 * 255);
uint32_t rainbowColor = (r << 16) | (g << 8) | b;
setNeoPixelColor(rainbowColor);
// Rainbow progress bar that matches NeoPixel color
int progress = (hue * 100) / 360;
tft.fillRect(2, 130, progress, 8, tft.color565(r, g, b));
delay(30);
}
}
}
// Reset to full brightness
neopixel.setBrightness(NEOPIXEL_MAX_BRIGHTNESS);
setNeoPixelColor(0x00FF00); // Green for success
Serial.println(F("Enhanced NeoPixel test completed"));
delay(1000);
}
void runColorAccuracyTest() {
Serial.println(F("\n[5/12] COLOR ACCURACY TEST"));
Serial.println(F("----------------------------------------"));
setNeoPixelColorSmooth(0xFF1493); // Deep pink for color testing
TestTiming timer = createTestTimer(4000); // 4 seconds guaranteed
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(1);
tft.setCursor(5, 5);
tft.println(F("Color Accuracy Test"));
// Color bars with NeoPixel sync
for(int i = 0; i < 12 && isTestRunning(timer); i++) {
int y = 20 + (i * 10);
tft.fillRect(10, y, 60, 8, testColors[i]);
tft.setCursor(75, y);
tft.setTextColor(testColors[i], TFT_BLACK);
tft.printf("Color %d", i+1);
// Sync NeoPixel to current color being tested
if (i < 8) {
setNeoPixelColor(neoPixelColors[i]);
}
delay(200);
}
// RGB gradient test
if (isTestRunning(timer)) {
tft.setCursor(5, 140);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.println(F("RGB Gradient:"));
for(int x = 0; x < 128 && isTestRunning(timer); x += 4) {
uint16_t color = tft.color565(x*2, (127-x)*2, (x/2));
tft.drawFastVLine(x, 150, 8, color);
// Update NeoPixel to match gradient
if (x % 16 == 0) {
uint32_t neoGradient = ((x*2) << 16) | (((127-x)*2) << 8) | (x/2);
setNeoPixelColor(neoGradient);
}
delay(10);
}
}
setNeoPixelColor(0x00FF00); // Green for success
Serial.println(F("Color accuracy test completed"));
// Wait for remaining time
while (isTestRunning(timer)) {
delay(100);
}
}
void runGeometricDrawingTest() {
Serial.println(F("\n[6/12] GEOMETRIC DRAWING TEST"));
Serial.println(F("----------------------------------------"));
setNeoPixelColorSmooth(0x32CD32); // Lime green for geometry
TestTiming timer = createTestTimer(4000); // 4 seconds guaranteed
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(5, 5);
tft.println(F("Geometric Shapes"));
unsigned long startTime = millis();
// Animated line drawing
for(int i = 0; i < 8 && isTestRunning(timer); i++) {
tft.drawLine(20 + i*2, 20, 80 + i*2, 60, testColors[i % 12]);
setNeoPixelColor(neoPixelColors[i % 12]);
delay(200);
}
// Rectangles with animation
if (isTestRunning(timer)) {
setNeoPixelColor(0xFF0000); // Red for rectangles
tft.drawRect(10, 70, 40, 30, TFT_RED);
delay(300);
setNeoPixelColor(0x00FF00); // Green for filled rectangle
tft.fillRect(60, 70, 40, 30, TFT_GREEN);
delay(300);
}
// Circles with animation
if (isTestRunning(timer)) {
setNeoPixelColor(0x0000FF); // Blue for circle outline
tft.drawCircle(30, 120, 15, TFT_BLUE);
delay(300);
setNeoPixelColor(0xFFFF00); // Yellow for filled circle
tft.fillCircle(80, 120, 15, TFT_YELLOW);
delay(300);
}
// Triangles with animation
if (isTestRunning(timer)) {
setNeoPixelColor(0xFF00FF); // Magenta for triangle outline
tft.drawTriangle(10, 140, 30, 155, 50, 140, TFT_MAGENTA);
delay(300);
setNeoPixelColor(0x00FFFF); // Cyan for filled triangle
tft.fillTriangle(70, 140, 90, 155, 110, 140, TFT_CYAN);
delay(300);
}
unsigned long drawTime = millis() - startTime;
setNeoPixelColor(0x00FF00); // Green for success
Serial.printf("Geometric drawing completed in %lu ms\n", drawTime);
// Wait for remaining time
while (isTestRunning(timer)) {
delay(100);
}
}
void runTextRenderingTest() {
Serial.println(F("\n[7/12] TEXT RENDERING TEST"));
Serial.println(F("----------------------------------------"));
setNeoPixelColorSmooth(0x4169E1); // Royal blue for text
TestTiming timer = createTestTimer(4000); // 4 seconds guaranteed
tft.fillScreen(TFT_BLACK);
unsigned long startTime = millis();
// Different text sizes with NeoPixel feedback
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(1);
tft.setCursor(5, 5);
tft.println(F("Size 1: Hello World!"));
setNeoPixelColor(0xFFFFFF); // White for text
delay(400);
if (isTestRunning(timer)) {
tft.setTextSize(2);
tft.setCursor(5, 25);
tft.println(F("Size 2"));
setNeoPixelColor(0xFFFF00); // Yellow for larger text
delay(400);
}
if (isTestRunning(timer)) {
tft.setTextSize(1);
tft.setCursor(5, 50);
tft.println(F("Text Performance Test"));
delay(400);
}
// Colored text with NeoPixel sync
constexpr const char* testTexts[] = {"RED", "GREEN", "BLUE", "YELLOW", "PINK" };
constexpr uint16_t textColors[] = {TFT_RED, TFT_GREEN, TFT_BLUE, TFT_YELLOW, TFT_PINK};
constexpr uint32_t neoTextColors[] = {0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF1493};
for(int i = 0; i < 5 && isTestRunning(timer); i++) {
tft.setTextColor(textColors[i], TFT_BLACK);
tft.setCursor(5, 70 + i*15);
tft.println(testTexts[i]);
setNeoPixelColor(neoTextColors[i]);
delay(300);
}
// Performance text rendering
if (isTestRunning(timer)) {
setNeoPixelColor(0x8A2BE2); // Blue violet for performance test
for(int i = 0; i < 10 && isTestRunning(timer); i++) {
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(70, 70 + i*8);
tft.printf("Line %d", i);
delay(100);
}
}
unsigned long textTime = millis() - startTime;
setNeoPixelColor(0x00FF00); // Green for success
Serial.printf("Text rendering completed in %lu ms\n", textTime);
// Wait for remaining time
while (isTestRunning(timer)) {
delay(100);
}
}
void runAnimationTest() {
Serial.println(F("\n[9/13] ANIMATION PERFORMANCE TEST"));
Serial.println(F("----------------------------------------"));
setNeoPixelColorSmooth(0xFF6347); // Tomato for animation
TestTiming timer = createTestTimer(6000); // 6 seconds guaranteed
tft.fillScreen(TFT_BLACK);
// Define clear layout zones to avoid conflicts
// Zone 1: Header (0-15)
// Zone 2: Status info (16-35)
// Zone 3: Animation area (36-120)
// Zone 4: Progress/FPS info (121-160)
// Header zone
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(5, 5);
tft.println(F("Animation Test"));
unsigned long animStart = millis();
int frames = 0;
while(isTestRunning(timer)) {
// Clear only the animation zone (36-120) to avoid flicker
tft.fillRect(0, 36, 128, 84, TFT_BLACK);
// Bouncing ball animation with NeoPixel sync
float t = (millis() - animStart) / 1000.0;
int x = 64 + 50 * sin(t * 2);
int y = 70 + 25 * sin(t * 3); // Centered in animation zone
// Draw bouncing ball
tft.fillCircle(x, y, 8, TFT_RED);
// Sync NeoPixel to ball position
uint8_t neoIntensity = (uint8_t)(128 + 127 * sin(t * 4));
uint32_t ballColor = (neoIntensity << 16) | (0x00 << 8) | 0x00;
setNeoPixelColor(ballColor);
// Rotating line in animation zone
float angle = t * 180;
int x1 = 64 + 35 * cos(radians(angle));
int y1 = 100 + 15 * sin(radians(angle)); // Lower in animation zone
tft.drawLine(64, 100, x1, y1, TFT_GREEN);
// Frame counter
frames++;
// FIXED: Status display in dedicated zone (121-160) - no overlap
if (frames % 30 == 0) {
float currentFPS = frames / ((millis() - animStart) / 1000.0);
// Clear status zone
tft.fillRect(5, 121, 118, 39, TFT_BLACK);
// FPS display
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setCursor(5, 125);
tft.printf("FPS: %.1f", currentFPS);
// Frame count
tft.setCursor(5, 135);
tft.printf("Frames: %d", frames);
// Time remaining
float timeRemaining = 6.0 - t;
tft.setCursor(5, 145);
tft.printf("Time: %.1fs", timeRemaining);
}
// FIXED: Progress bar in status zone (not overlapping animation)
float progress = t / 6.0;
if (progress > 1.0) progress = 1.0;
int progressWidth = (int)(110 * progress);
tft.drawRect(5, 155, 110, 4, TFT_WHITE);
tft.fillRect(5, 155, progressWidth, 4, TFT_GREEN);
delay(16); // ~60 FPS target
}
float avgFPS = frames / 6.0;
setNeoPixelColor(0x00FF00); // Green for success
// FIXED: Clear result display in status zone
tft.fillRect(5, 121, 118, 39, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(5, 125);
tft.printf("ANIMATION COMPLETE");
tft.setCursor(5, 135);
tft.printf("Avg FPS: %.1f", avgFPS);
tft.setCursor(5, 145);
tft.printf("Total frames: %d", frames);
Serial.printf("Animation test: %d frames in 6s (%.1f FPS)\n", frames, avgFPS);
delay(2000); // Show results for 2 seconds
}
void runMemoryStressTest() {
Serial.println(F("\n[9/12] MEMORY STRESS TEST"));
Serial.println(F("----------------------------------------"));
setNeoPixelColorSmooth(0xFF4500); // Orange red for stress test
TestTiming timer = createTestTimer(5000); // 5 seconds guaranteed
size_t beforeTest = ESP.getFreeHeap();
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(5, 5);
tft.println(F("Memory Stress Test"));
// Fill screen multiple times with different patterns
for(int pattern = 0; pattern < 5 && isTestRunning(timer); pattern++) {
// NeoPixel indicates current pattern
setNeoPixelColor(neoPixelColors[pattern * 2]);
for(int y = 0; y < tft.height() && isTestRunning(timer); y += 4) {
for(int x = 0; x < tft.width(); x += 4) {
uint16_t color = testColors[(x + y + pattern) % 12];
tft.fillRect(x, y, 4, 4, color);
}
// Update memory info every few lines
if (y % 40 == 0) {
tft.fillRect(5, 20 + pattern*10, 118, 8, TFT_BLACK);
tft.setCursor(5, 20 + pattern*10);
tft.printf("Pattrn %d - Free: %zu", pattern+1, ESP.getFreeHeap());
}
delay(5);
}
delay(200);
}
size_t afterTest = ESP.getFreeHeap();
int memoryDiff = beforeTest - afterTest;
setNeoPixelColor(memoryDiff < 1000 ? 0x00FF00 : 0xFFFF00); // Green if low usage, Yellow if higher
Serial.printf(" Memory test completed\n");
Serial.printf(" Before: %zu bytes, After: %zu bytes\n", beforeTest, afterTest);
Serial.printf(" Memory usage: %d bytes\n", memoryDiff);
// Wait for remaining time
while (isTestRunning(timer)) {
delay(100);
}
}
void runPerformanceBenchmark() {
Serial.println(F("\n[10/12] PERFORMANCE BENCHMARK"));
Serial.println(F("----------------------------------------"));
setNeoPixelColorSmooth(0x9400D3); // Violet for performance
TestTiming timer = createTestTimer(5000); // 5 seconds guaranteed
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(5, 5);
tft.println(F("Performance Test"));
// Pixel drawing benchmark
setNeoPixelColor(0xFF0000); // Red for pixel test
unsigned long startTime = millis();
for(int i = 0; i < 1000 && isTestRunning(timer); i++) {
int x = random(0, tft.width());
int y = random(0, tft.height());
uint16_t color = random(0x0000, 0xFFFF);
tft.drawPixel(x, y, color);
if (i % 100 == 0) {
tft.fillRect(5, 20, 100, 8, TFT_BLACK);
tft.setCursor(5, 20);
tft.printf("Pixels: %d/1000", i);
}
}
unsigned long pixelTime = millis() - startTime;
// Rectangle drawing benchmark
if (isTestRunning(timer)) {
setNeoPixelColor(0x00FF00); // Green for rectangle test
startTime = millis();
for(int i = 0; i < 100 && isTestRunning(timer); i++) {
int x = random(0, tft.width()-20);
int y = random(0, tft.height()-20);
int w = random(5, 20);
int h = random(5, 20);
uint16_t color = random(0x0000, 0xFFFF);
tft.fillRect(x, y, w, h, color);
if (i % 10 == 0) {
tft.fillRect(5, 35, 100, 8, TFT_BLACK);
tft.setCursor(5, 35);
tft.printf("Rects: %d/100", i);
}
}
unsigned long rectTime = millis() - startTime;
// Circle drawing benchmark
if (isTestRunning(timer)) {
setNeoPixelColor(0x0000FF); // Blue for circle test
startTime = millis();
for(int i = 0; i < 50 && isTestRunning(timer); i++) {
int x = random(10, tft.width()-10);
int y = random(10, tft.height()-10);
int r = random(3, 10);
uint16_t color = random(0x0000, 0xFFFF);
tft.fillCircle(x, y, r, color);
if (i % 5 == 0) {
tft.fillRect(5, 50, 100, 8, TFT_BLACK);
tft.setCursor(5, 50);
tft.printf("Circles: %d/50", i);
}
}
unsigned long circleTime = millis() - startTime;
// Display results
tft.fillRect(5, 70, 118, 40, TFT_BLACK);
tft.setCursor(5, 70);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.println("Benchmark Results:");
tft.setCursor(5, 80);
tft.printf("Pixels: %.1f/ms", 1000.0/pixelTime);
tft.setCursor(5, 90);
tft.printf("Rects: %.1f/ms", 100.0/rectTime);
tft.setCursor(5, 100);
tft.printf("Circles: %.1f/ms", 50.0/circleTime);
Serial.printf(" Performance benchmarks:\n");
Serial.printf(" 1000 pixels: %lu ms (%.1f pixels/ms)\n", pixelTime, 1000.0/pixelTime);
Serial.printf(" 100 rectangles: %lu ms (%.1f rects/ms)\n", rectTime, 100.0/rectTime);
Serial.printf(" 50 circles: %lu ms (%.1f circles/ms)\n", circleTime, 50.0/circleTime);
}
}
setNeoPixelColor(0x00FF00); // Green for success
// Wait for remaining time
while (isTestRunning(timer)) {
delay(100);
}
}
// Sprite implementation were throwing core dumps. Lost the old function in the refactoring processes.
// A simpler drawing test, until the sprite problem is figured out.
void runSpriteTest() {
Serial.println(F("\n[11/12] ADVANCED DRAWING PERFORMANCE TEST"));
Serial.println(F("----------------------------------------"));
setNeoPixelColorSmooth(0x00FFFF); // Cyan for drawing test
TestTiming timer = createTestTimer(4000); // 4 seconds guaranteed
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(5, 5);
tft.println(F("Drawing Performance"));
Serial.println(F("Running advanced drawing performance tests"));
size_t freeHeap = ESP.getFreeHeap();
Serial.printf("Avail mem: %zu bytes\n", freeHeap);
// Test 1: Rectangle blitting performance
if (isTestRunning(timer)) {
setNeoPixelColor(0xFF0000); // Red for rectangle test
unsigned long rectStart = millis();
int rectangles = 0;
tft.setCursor(5, 20);
tft.println(F("Rectangle Blit Tst"));
while (isTestRunning(timer) && (millis() - rectStart < 1000)) {
int x = random(0, tft.width() - 16);
int y = random(30, tft.height() - 16);
int w = random(8, 17);
int h = random(8, 17);
uint16_t color = testColors[rectangles % 12];
tft.fillRect(x, y, w, h, color);
rectangles++;
if (rectangles % 10 == 0) {
tft.fillRect(5, 35, 100, 10, TFT_BLACK);
tft.setCursor(5, 35);
tft.printf("Rects: %d", rectangles);
}
}
unsigned long rectTime = millis() - rectStart;
Serial.printf("Rectangle performance: %d rects in %lu ms (%.1f rects/ms)\n",
rectangles, rectTime, (float)rectangles / rectTime);
}
// Test 2: Line drawing performance
if (isTestRunning(timer)) {
setNeoPixelColor(0x00FF00); // Green for line test
tft.fillRect(0, 50, tft.width(), 30, TFT_BLACK);
tft.setCursor(5, 50);
tft.println(F("Line Drawing Test"));
unsigned long lineStart = millis();
int lines = 0;
while (isTestRunning(timer) && (millis() - lineStart < 1000)) {
int x1 = random(0, tft.width());
int y1 = random(65, tft.height());
int x2 = random(0, tft.width());
int y2 = random(65, tft.height());
uint16_t color = testColors[lines % 12];
tft.drawLine(x1, y1, x2, y2, color);
lines++;
if (lines % 10 == 0) {
tft.fillRect(5, 80, 100, 10, TFT_BLACK);
tft.setCursor(5, 80);
tft.printf("Lines: %d", lines);
}
}
unsigned long lineTime = millis() - lineStart;
Serial.printf("Line performance: %d lines in %lu ms (%.1f lines/ms)\n",
lines, lineTime, (float)lines / lineTime);
}
// Test 3: Pattern copying simulation
if (isTestRunning(timer)) {
setNeoPixelColor(0x0000FF); // Blue for pattern test
tft.fillRect(0, 95, tft.width(), 25, TFT_BLACK);
tft.setCursor(5, 95);
tft.println(F("Pattern Copy Test"));
// Create simple 16x16 pattern using direct draw
const int patternSize = 16;
for (int pos = 0; pos < 4 && isTestRunning(timer); pos++) {
int destX = 10 + (pos % 2) * 50;
int destY = 110 + (pos / 2) * 25;
// "Copy" pattern by redrawing it
unsigned long copyStart = millis();
// Draw checkerboard pattern
for (int py = 0; py < patternSize; py++) {
for (int px = 0; px < patternSize; px++) {
uint16_t color = ((px/4 + py/4) % 2) ? testColors[pos*2] : testColors[pos*2+1];
tft.drawPixel(destX + px, destY + py, color);
}
}
unsigned long copyTime = millis() - copyStart;
setNeoPixelColor(neoPixelColors[pos * 2]);
tft.fillRect(5, 140, 118, 20, TFT_BLACK);
tft.setCursor(5, 140);
tft.printf("Pattern %d: %lu ms", pos+1, copyTime);
tft.setCursor(5, 150);
tft.printf("(%d pixels copied)", patternSize * patternSize);
Serial.printf("Pattern %d copy: %lu ms for %d pixels (%.2f pixels/ms)\n",
pos+1, copyTime, patternSize*patternSize,
(float)(patternSize*patternSize) / copyTime);
delay(500);
}
}
// Test 4: Memory usage simulation
if (isTestRunning(timer)) {
setNeoPixelColor(0xFFFF00); // Yellow for memory test
tft.fillRect(5, 140, 118, 20, TFT_BLACK);
tft.setCursor(5, 140);
tft.println(F("Memory Usage Check"));
size_t currentHeap = ESP.getFreeHeap();
size_t usedMemory = freeHeap - currentHeap;
tft.setCursor(5, 150);
tft.printf("Used: %zu bytes", usedMemory);
Serial.printf("Memory usage during test: %zu bytes\n", usedMemory);
Serial.printf("Memory efficiency: %.1f%% free\n", (float)currentHeap / freeHeap * 100);
delay(1000);
}
setNeoPixelColor(0x00FF00); // Green for success
tft.fillRect(5, 140, 118, 20, TFT_BLACK);
tft.setCursor(5, 140);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.println(F("Drawing Tests OK"));
tft.setCursor(5, 150);
tft.println(F("(Sprite avoided)"));
Serial.println(F("β Advanced drawing performance test completed"));
Serial.println(F(" Note: Sprite functionality skipped due to memory constraints"));
// Wait for remaining time
while (isTestRunning(timer)) {
delay(100);
}
}
void runFinalSystemCheck() {
Serial.println(F("\nFINAL SYSTEM STATUS"));
Serial.println(F("----------------------------------------"));
setNeoPixelColorSmooth(0x32CD32); // Lime green for final check
size_t currentFreeHeap = ESP.getFreeHeap();
int memoryUsed = initialFreeHeap - currentFreeHeap;
Serial.printf("System Status:\n");
Serial.printf(" CPU Frequency: %d MHz\n", ESP.getCpuFreqMHz());
Serial.printf(" Initial Free Heap: %zu bytes\n", initialFreeHeap);
Serial.printf(" Current Free Heap: %zu bytes\n", currentFreeHeap);
Serial.printf(" Memory Used by Tests: %d bytes\n", memoryUsed);
Serial.printf(" Display Buffer Size: %zu bytes\n", videoBufferSize);
Serial.printf(" Memory Efficiency: %.1f%%\n",
(float)currentFreeHeap / initialFreeHeap * 100);
// Display final status on screen
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setTextSize(1);
tft.setCursor(5, 5);
tft.println(F("ALL TESTS PASSED!"));
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(5, 25);
tft.printf("Free Heap: %zu", currentFreeHeap);
tft.setCursor(5, 35);
tft.printf("Used: %d bytes", memoryUsed);
tft.setCursor(5, 45);
tft.printf("Display: %dx%d", tft.width(), tft.height());
tft.setCursor(5, 55);
tft.printf("Efficiency: %.1f%%", (float)currentFreeHeap / initialFreeHeap * 100);
// Success animation
for (int i = 0; i < 3; i++) {
setNeoPixelColor(0x00FF00); // Green
delay(200);
setNeoPixelColor(0x000000); // Off
delay(200);
}
setNeoPixelColor(0x00FF00); // Final green
Serial.println(F("All tests completed successfully!"));
}
// Helper functions for better starfield and other effects
void initializeStarfield() {
for (int i = 0; i < STARFIELD_STARS; i++) {
stars[i].x = random(-1000, 1000);
stars[i].y = random(-1000, 1000);
stars[i].z = random(1, 1000);
}
}
void runEnhancedWaveDemo() {
// Multi-layered sine wave pattern with NeoPixel sync
static int wavePhase1 = 0;
static int wavePhase2 = 0;
static int wavePhase3 = 0;
// Clear wave area
tft.fillRect(0, 110, 128, 50, TFT_BLACK);
// Layer 1: Primary wave
for(int x = 0; x < 128; x += 1) {
int y1 = 130 + 15 * sin(radians(x * 3 + wavePhase1));
if(y1 >= 110 && y1 < 160) {
uint16_t waveColor1 = testColors[(x/8 + wavePhase1/20) % 12];
tft.drawPixel(x, y1, waveColor1);
}
}
// Layer 2: Secondary wave
for(int x = 0; x < 128; x += 2) {
int y2 = 135 + 10 * sin(radians(x * 5 + wavePhase2));
if(y2 >= 110 && y2 < 160) {
uint16_t waveColor2 = testColors[(x/12 + wavePhase2/30) % 12];
tft.drawPixel(x, y2, waveColor2);
}
}
// Layer 3: Tertiary wave
for(int x = 0; x < 128; x += 3) {
int y3 = 140 + 8 * sin(radians(x * 7 + wavePhase3));
if(y3 >= 110 && y3 < 160) {
uint16_t waveColor3 = testColors[(x/16 + wavePhase3/40) % 12];
tft.drawPixel(x, y3, waveColor3);
}
}
// Rotating indicator with trail
angle += 3.0;
if(angle >= 360) angle = 0;
int centerX = 110, centerY = 140;
// Draw trail
for (int i = 1; i <= 5; i++) {
float trailAngle = angle - i * 15;
int tx = centerX + (20 - i * 2) * cos(radians(trailAngle));
int ty = centerY + (20 - i * 2) * sin(radians(trailAngle));
uint8_t brightness = 255 - i * 40;
uint16_t trailColor = tft.color565(brightness, 0, 0);
tft.drawPixel(tx, ty, trailColor);
}
// Main indicator
int x1 = centerX + 18 * cos(radians(angle));
int y1 = centerY + 18 * sin(radians(angle));
tft.fillCircle(centerX, centerY, 20, TFT_BLACK);
tft.drawCircle(centerX, centerY, 19, TFT_WHITE);
tft.drawLine(centerX, centerY, x1, y1, TFT_RED);
tft.fillCircle(centerX, centerY, 2, TFT_WHITE);
// NeoPixel synced to primary wave amplitude
uint8_t waveIntensity = (uint8_t)(128 + 127 * sin(radians(wavePhase1 * 2)));
uint32_t waveNeoColor = (waveIntensity << 16) | (0x00 << 8) | (255 - waveIntensity);
setNeoPixelColor(waveNeoColor);
wavePhase1 += 4;
wavePhase2 += 6;
wavePhase3 += 8;
}
void runEnhancedSpinningShapesDemo() {
static float shapeAngle1 = 0;
static float shapeAngle2 = 0;
static float pulseFactor = 0;
// Clear animation area
tft.fillRect(0, 110, 128, 50, TFT_BLACK);
// Spinning triangle with pulsing
int cx1 = 32, cy1 = 135;
float pulseSize = 18 + 8 * sin(radians(pulseFactor));
for (int i = 0; i < 3; i++) {
float a = shapeAngle1 + i * 120;
int x = cx1 + pulseSize * cos(radians(a));
int y = cy1 + pulseSize * sin(radians(a));
tft.fillCircle(x, y, 4, testColors[i * 2]);
// Connect lines
if (i < 2) {
float nextA = shapeAngle1 + (i + 1) * 120;
int nextX = cx1 + pulseSize * cos(radians(nextA));
int nextY = cy1 + pulseSize * sin(radians(nextA));
tft.drawLine(x, y, nextX, nextY, testColors[i * 2]);
} else {
// Close the triangle
float firstA = shapeAngle1;
int firstX = cx1 + pulseSize * cos(radians(firstA));
int firstY = cy1 + pulseSize * sin(radians(firstA));
tft.drawLine(x, y, firstX, firstY, testColors[i * 2]);
}
}
// Spinning square with rotation trail
int cx2 = 96, cy2 = 135;
// Draw rotation trail
for (int trail = 1; trail <= 3; trail++) {
float trailAngle = shapeAngle2 - trail * 20;
for (int i = 0; i < 4; i++) {
float a = trailAngle + i * 90;
int x = cx2 + (18 - trail * 3) * cos(radians(a));
int y = cy2 + (18 - trail * 3) * sin(radians(a));
uint8_t alpha = 255 - trail * 60;
uint16_t trailColor = tft.color565(alpha/3, alpha/3, alpha);
tft.drawPixel(x, y, trailColor);
}
}
// Main spinning square
for (int i = 0; i < 4; i++) {
float a = shapeAngle2 + i * 90;
int x = cx2 + 15 * cos(radians(a));
int y = cy2 + 15 * sin(radians(a));
tft.fillRect(x-2, y-2, 4, 4, testColors[i+4]);
// Connect squares with lines
int nextI = (i + 1) % 4;
float nextA = shapeAngle2 + nextI * 90;
int nextX = cx2 + 15 * cos(radians(nextA));
int nextY = cy2 + 15 * sin(radians(nextA));
tft.drawLine(x, y, nextX, nextY, testColors[i+4]);
}
// NeoPixel color based on shape rotation
uint8_t rotR = (uint8_t)(128 + 127 * sin(radians(shapeAngle1)));
uint8_t rotG = (uint8_t)(128 + 127 * sin(radians(shapeAngle2)));
uint8_t rotB = (uint8_t)(128 + 127 * sin(radians(pulseFactor)));
uint32_t rotationColor = (rotR << 16) | (rotG << 8) | rotB;
setNeoPixelColor(rotationColor);
shapeAngle1 += 4;
shapeAngle2 += 6;
pulseFactor += 8;
}
void runEnhancedBouncingBallsDemo() {
static float ball1X = 20, ball1Y = 125, ball1VX = 2.2, ball1VY = 1.8;
static float ball2X = 60, ball2Y = 145, ball2VX = -1.8, ball2VY = -2.2;
static float ball3X = 100, ball3Y = 135, ball3VX = -2.5, ball3VY = 1.5;
static int trailIndex = 0;
static struct BallTrail {
int x, y;
uint8_t life;
} trails1[10], trails2[10], trails3[10];
// Clear animation area
tft.fillRect(0, 110, 128, 50, TFT_BLACK);
// Update ball 1 with enhanced physics
ball1X += ball1VX;
ball1Y += ball1VY;
if (ball1X <= 5 || ball1X >= 123) {
ball1VX = -ball1VX * 0.98; // Energy loss
ball1X = constrain(ball1X, 5, 123);
}
if (ball1Y <= 115 || ball1Y >= 155) {
ball1VY = -ball1VY * 0.98; // Energy loss
ball1Y = constrain(ball1Y, 115, 155);
}
// Update ball 2
ball2X += ball2VX;
ball2Y += ball2VY;
if (ball2X <= 5 || ball2X >= 123) {
ball2VX = -ball2VX * 0.98;
ball2X = constrain(ball2X, 5, 123);
}
if (ball2Y <= 115 || ball2Y >= 155) {
ball2VY = -ball2VY * 0.98;
ball2Y = constrain(ball2Y, 115, 155);
}
// Update ball 3
ball3X += ball3VX;
ball3Y += ball3VY;
if (ball3X <= 5 || ball3X >= 123) {
ball3VX = -ball3VX * 0.98;
ball3X = constrain(ball3X, 5, 123);
}
if (ball3Y <= 115 || ball3Y >= 155) {
ball3VY = -ball3VY * 0.98;
ball3Y = constrain(ball3Y, 115, 155);
}
// Add to trails
trails1[trailIndex] = {(int)ball1X, (int)ball1Y, 50};
trails2[trailIndex] = {(int)ball2X, (int)ball2Y, 50};
trails3[trailIndex] = {(int)ball3X, (int)ball3Y, 50};
trailIndex = (trailIndex + 1) % 10;
// Draw trails
for (int i = 0; i < 10; i++) {
if (trails1[i].life > 0) {
uint8_t alpha = trails1[i].life * 5;
tft.drawPixel(trails1[i].x, trails1[i].y, tft.color565(alpha, 0, 0));
trails1[i].life--;
}
if (trails2[i].life > 0) {
uint8_t alpha = trails2[i].life * 5;
tft.drawPixel(trails2[i].x, trails2[i].y, tft.color565(0, 0, alpha));
trails2[i].life--;
}
if (trails3[i].life > 0) {
uint8_t alpha = trails3[i].life * 5;
tft.drawPixel(trails3[i].x, trails3[i].y, tft.color565(0, alpha, 0));
trails3[i].life--;
}
}
// Draw balls with glow effect
for (int r = 8; r >= 4; r--) {
uint8_t alpha = (8 - r) * 60;
tft.drawCircle((int)ball1X, (int)ball1Y, r, tft.color565(255, alpha, alpha));
tft.drawCircle((int)ball2X, (int)ball2Y, r, tft.color565(alpha, alpha, 255 ));
tft.drawCircle((int)ball3X, (int)ball3Y, r, tft.color565(alpha, 255, alpha));
}
// Solid center
tft.fillCircle((int)ball1X, (int)ball1Y, 3, TFT_RED );
tft.fillCircle((int)ball2X, (int)ball2Y, 3, TFT_BLUE );
tft.fillCircle((int)ball3X, (int)ball3Y, 3, TFT_GREEN);
// NeoPixel reflects average ball energy
float totalEnergy = (abs(ball1VX) + abs(ball1VY) + abs(ball2VX) + abs(ball2VY) + abs(ball3VX) + abs(ball3VY)) / 6.0;
uint8_t energyLevel = (uint8_t)(totalEnergy * 40);
uint32_t energyColor = (energyLevel << 16) | (energyLevel << 8) | (255 - energyLevel);
setNeoPixelColor(energyColor);
}
void runEnhancedFireworksDemo() {
static struct Particle {
float x, y, vx, vy, ax, ay;
uint16_t color;
uint8_t life, maxLife;
uint8_t type; // 0=normal, 1=sparkler, 2=tracer
} particles[30];
static bool initialized = false;
static unsigned long lastExplosion = 0;
static int explosionCount = 0;
if (!initialized || millis() - lastExplosion > 3000) {
// Create new explosion with different types
int centerX = random(25, 103);
int centerY = random(120, 145);
explosionCount++;
for (int i = 0; i < 30; i++) {
particles[i].x = centerX;
particles[i].y = centerY;
// Vary explosion patterns
float angle = random(0, 360);
float speed = random(10, 40) / 10.0;
if (explosionCount % 3 == 0) {
// Circular explosion
particles[i].vx = speed * cos(radians(angle));
particles[i].vy = speed * sin(radians(angle));
} else if (explosionCount % 3 == 1) {
// Upward fountain
particles[i].vx = random(-20, 20) / 10.0;
particles[i].vy = -speed;
} else {
// Directional burst
float burstAngle = random(-45, 45);
particles[i].vx = speed * cos(radians(burstAngle));
particles[i].vy = speed * sin(radians(burstAngle)) - 1;
}
particles[i].ax = 0;
particles[i].ay = 0.15; // gravity
particles[i].color = testColors[random(0, 12)];
particles[i].life = particles[i].maxLife = random(30, 80);
particles[i].type = random(0, 3);
}
initialized = true;
lastExplosion = millis();
// Explosion flash on NeoPixel
setNeoPixelColor(0xFFFFFF);
delay(50);
}
// Clear animation area
tft.fillRect(0, 110, 128, 50, TFT_BLACK);
int activeParticles = 0;
// Update and draw particles
for (int i = 0; i < 30; i++) {
if (particles[i].life > 0) {
activeParticles++;
// Physics update
particles[i].vx += particles[i].ax;
particles[i].vy += particles[i].ay;
particles[i].x += particles[i].vx;
particles[i].y += particles[i].vy;
particles[i].life--;
// Add air resistance for sparklers
if (particles[i].type == 1) {
particles[i].vx *= 0.99;
particles[i].vy *= 0.99;
}
if (particles[i].x >= 0 && particles[i].x < 128 &&
particles[i].y >= 110 && particles[i].y < 160) {
// Different rendering based on type
if (particles[i].type == 0) {
// Normal particle
uint8_t alpha = (particles[i].life * 255) / particles[i].maxLife;
uint16_t fadedColor = tft.color565(
((particles[i].color >> 11) * alpha) >> 8,
(((particles[i].color >> 5) & 0x3F) * alpha) >> 8,
((particles[i].color & 0x1F) * alpha) >> 8
);
tft.drawPixel((int)particles[i].x, (int)particles[i].y, fadedColor);
} else if (particles[i].type == 1) {
// Sparkler - draw with trail
tft.fillCircle((int)particles[i].x, (int)particles[i].y, 1, particles[i].color);
// Trail effect
int prevX = (int)(particles[i].x - particles[i].vx);
int prevY = (int)(particles[i].y - particles[i].vy);
if (prevX >= 0 && prevX < 128 && prevY >= 110 && prevY < 160) {
tft.drawLine((int)particles[i].x, (int)particles[i].y, prevX, prevY, particles[i].color);
}
} else {
// Tracer - bright line
int prevX = (int)(particles[i].x - particles[i].vx * 2);
int prevY = (int)(particles[i].y - particles[i].vy * 2);
if (prevX >= 0 && prevX < 128 && prevY >= 110 && prevY < 160) {
tft.drawLine((int)particles[i].x, (int)particles[i].y, prevX, prevY, TFT_WHITE);
}
tft.drawPixel((int)particles[i].x, (int)particles[i].y, particles[i].color);
}
}
}
}
// NeoPixel fades from explosion brightness
uint8_t fadeLevel = 255 * activeParticles / 30;
uint32_t fadeColor = (fadeLevel << 16) | (fadeLevel << 8) | (fadeLevel >> 1);
setNeoPixelColor(fadeColor);
}
void loop() {
// Single demo cycle > record boot index > restart loop
static bool testsCompleted = false;
static unsigned long demoStartTime = 0;
static uint8_t demoMode = 0;
static unsigned long lastModeChange = 0;
static bool demoInitialized = false;
static bool finalCheckDisplayed = false;
// Test completion flags sanity check (i.e. if the test is not complete, it is still initial phase).
if (!testsCompleted) {
testsCompleted = true;
demoStartTime = millis();
demoInitialized = false;
finalCheckDisplayed = false;
return;
}
// Initialize demo cycle
if (!demoInitialized) {
Serial.println(F("\n============================================================"));
Serial.println(F("STARTING DEMO CYCLE (40 seconds total)"));
Serial.println(F("============================================================"));
demoInitialized = true;
lastModeChange = millis();
demoMode = 0;
frameCount = 0;
lastFPSUpdate = millis();
// Clear screen for demo
tft.fillScreen(TFT_BLACK);
// Demo start NeoPixel sequence
for (int i = 0; i < 3; i++) {
setNeoPixelColor(0x00FF00); // Green
delay(100);
setNeoPixelColor(0x000000); // Off
delay(100);
}
}
unsigned long currentTime = millis();
unsigned long demoElapsed = currentTime - demoStartTime;
// FIXED: Show final system check in the last 5 seconds (35-40s) before reboot sequence
if (demoElapsed >= 35000 && !finalCheckDisplayed) {
Serial.println(F("\n============================================================"));
Serial.println(F("PREPARING FOR REBOOT - FINAL STATUS"));
Serial.println(F("============================================================"));
// Clear screen and show final system status
tft.fillScreen(TFT_BLACK);
size_t currentFreeHeap = ESP.getFreeHeap();
int memoryUsed = initialFreeHeap - currentFreeHeap;
Serial.printf("Pre-reboot System Status:\n");
Serial.printf(" CPU Frequency: %d MHz\n", ESP.getCpuFreqMHz());
Serial.printf(" Current Free Heap: %zu bytes\n", currentFreeHeap);
Serial.printf(" Memory Used: %d bytes\n", memoryUsed);
Serial.printf(" Demo runtime: %.1fs\n", demoElapsed/1000.0);
// Display pre-reboot status
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.setTextSize(1);
tft.setCursor(5, 5);
tft.println(F("PRE-REBOOT STATUS"));
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(5, 25);
tft.printf("Demo: %.1fs complete", demoElapsed/1000.0);
tft.setCursor(5, 35);
tft.printf("Free Heap: %zu KB", currentFreeHeap/1024);
tft.setCursor(5, 45);
tft.printf("Memory Used: %d KB", memoryUsed/1024);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(5, 65);
tft.println(F("β All systems nominal"));
tft.setCursor(5, 75);
tft.println(F("β Ready for reboot"));
setNeoPixelColor(0x00FF00); // Green for ready
delay(3000);
finalCheckDisplayed = true;
Serial.println(F("Pre-reboot status check completed"));
}
// FIXED: Check if demo cycle is complete (40 seconds: 4 modes Γ 10 seconds each)
if (demoElapsed >= 40000) {
Serial.println(F("\n============================================================"));
Serial.println(F("DEMO CYCLE COMPLETE - RECORDING SUCCESS AND REBOOTING"));
Serial.println(F("============================================================"));
// Record successful cycle completion
recordSuccessfulCycle();
// Display reboot message on screen (this comes AFTER final system check)
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(2);
tft.setCursor(10, 20);
tft.println(F("CYCLE"));
tft.setCursor(5, 40);
tft.println(F("COMPLETE"));
tft.setTextSize(1);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setCursor(10, 70);
tft.println(F("REBOOTING..."));
// Display boot statistics if available
if (bootCounterAvailable) {
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(5, 90);
tft.printf("Total Boots: %lu", bootCount);
tft.setCursor(5, 100);
tft.printf("Successful: %lu", successfulCycles);
tft.setCursor(5, 110);
tft.printf("Success: %.1f%%", (float)successfulCycles * 100 / bootCount);
Serial.printf("Final Statistics:\n");
Serial.printf(" Total boots: %lu\n", bootCount);
Serial.printf(" Successful cycles: %lu\n", successfulCycles);
Serial.printf(" Success rate: %.1f%%\n", (float)successfulCycles * 100 / bootCount);
}
// Reboot countdown with NeoPixel
for (int countdown = 5; countdown > 0; countdown--) {
tft.fillRect(5, 125, 100, 15, TFT_BLACK);
tft.setTextColor(TFT_RED, TFT_BLACK);
tft.setCursor(5, 125);
tft.printf("Reboot in %d...", countdown);
// Flash NeoPixel during countdown
setNeoPixelColor(0xFF0000); // Red
delay(200);
setNeoPixelColor(0x000000); // Off
delay(300);
setNeoPixelColor(0xFF0000); // Red
delay(200);
setNeoPixelColor(0x000000); // Off
delay(300);
}
// Final reboot indication
tft.fillRect(5, 125, 100, 15, TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(5, 125);
tft.println(F("REBOOTING NOW!"));
// Final NeoPixel sequence
for (int i = 0; i < 10; i++) {
setNeoPixelColor(0xFFFFFF); // White flash
delay(50);
setNeoPixelColor(0x000000); // Off
delay(50);
}
// Close preferences properly
if (bootCounterAvailable) {
preferences.end();
}
Serial.println(F("Initiating ESP32 restart..."));
delay(500);
// Restart the ESP32
ESP.restart();
}
// Check to ensure demo triggering matches with final test phase
if (demoElapsed < 35000) {
// Change demo mode every 10 seconds
if (currentTime - lastModeChange > 8750) { // 35s / 4 modes = 8.75s per mode
demoMode = (demoMode + 1) % 4;
lastModeChange = currentTime;
tft.fillScreen(TFT_BLACK);
Serial.printf("Demo mode %d/4 starting (%.1fs elapsed)\n",
demoMode + 1, demoElapsed / 1000.0);
// Mode change NeoPixel indication
for (int i = 0; i < 3; i++) {
setNeoPixelColor(neoPixelColors[demoMode * 2]);
delay(100);
setNeoPixelColor(0x000000);
delay(100);
}
}
// Update FPS calculation every second
if(currentTime - lastFPSUpdate >= 1000) {
currentFPS = frameCount * 1000.0 / (currentTime - lastFPSUpdate);
frameCount = 0;
lastFPSUpdate = currentTime;
// Display system info in TOP area (y=20-50)
tft.fillRect(5, 20, 118, 30, TFT_BLACK);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setCursor(5, 25);
tft.printf("FPS: %.1f", currentFPS);
tft.setCursor(5, 35);
tft.printf("Heap: %zu KB", ESP.getFreeHeap()/1024);
// Mode and progress info
unsigned long modeElapsed = currentTime - lastModeChange;
unsigned long modeRemaining = (8750 - modeElapsed) / 1000;
tft.setCursor(5, 45);
tft.printf("Time: %lus (%.1fs total)", modeRemaining, demoElapsed / 1000.0);
// Progress bar in upper area
int progressWidth = (int)(110 * demoElapsed / 35000); // 35s total demo time
tft.drawRect(5, 55, 110, 4, TFT_WHITE);
tft.fillRect(5, 55, progressWidth, 4, TFT_GREEN);
// NeoPixel status indicator
if (currentFPS > 50) {
setNeoPixelColor(0x00FF00); // Green - excellent
} else if (currentFPS > 30) {
setNeoPixelColor(0xFFFF00); // Yellow - good
} else if (currentFPS > 15) {
setNeoPixelColor(0xFF4500); // Orange - moderate
} else {
setNeoPixelColor(0xFF0000); // Red - low
}
}
// Demo mode display in header area (y=5-15)
tft.fillRect(5, 5, 118, 12, TFT_BLACK);
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.setCursor(5, 5);
switch (demoMode) {
case 0:
tft.printf("Wave Patterns [%d/4]", demoMode + 1);
runEnhancedWaveDemo();
break;
case 1:
tft.printf("Spinning Shapes [%d/4]", demoMode + 1);
runEnhancedSpinningShapesDemo();
break;
case 2:
tft.printf("Bouncing Balls [%d/4]", demoMode + 1);
runEnhancedBouncingBallsDemo();
break;
case 3:
tft.printf("Fireworks [%d/4]", demoMode + 1);
runEnhancedFireworksDemo();
break;
}
frameCount++;
}
delay(16); // ~60 FPS target (Practical achievable: ~40 FPS)
}
PS: This is a single core sketch, Dual core Sanity Check Firmware is under development.