ESP32 S3 Devkit C1 Arduino IDE Projects and Showcases

This will be the single thread where I will cover all of my ESP32 S3 N8R8 Devkit C1 firmware or hardware projects.

Part 1: ESP32 S3 Devkit C1 Arduino IDE Sanity Check

So you have just received your brand new ESP32 S3, and want to get started in Arduino IDE.

But you are confused with the spoilt choice of USB and UART connection.
Or you are stuck in sketch uploading, and the device not responding.

In any case, I have faced the same issues, and have managed to get inside it's brains finally.

So I share the details to provide a tested reference so that you can figure out whether there is actually something wrong with your hardware or connections, and reduce some of the frustrating load so that you can begin working on your planned projects ASAP.

=================================================

Arduino IDE version: 2.3.6
ESP32 board: ESP32 S3 Rev2 WROOM1 N8R8 Devkit C1
Port type used: UART port of ESP32 C1
Board Package: ESP32 3.3.0 by Espressif Systems
IDE Board: ESP32S3 Dev Module

=================================================
[It is recommended to start with UART first if you are a beginner, as it does not involve manually throwing the device into boot loading reset (as will be the case if USB is used.]

Make sure that your setup matches exactly this, if you have a the same Devkit C1 NxRx board. Double check the values, as other than clock speed, most of the IDE initiated fields/options will be misconfigured, or disabled.
Additional Note: E.g. N8R8 means:
N8 = 8MB flash
R8 = 8MB PSRAM (OPI for tested MC board)

// NO CODE
/*
//EDIT: For Additional clarity and any other ESP32 S3 boards: 
If you have ESP32 S3 board, you should have one of the two following 
configurations, use the "PSRAM" and and "Flash Size" from the above 
"Tools" menu accordingly:

 Nx: In built flash x MB (Ideally your "x" value in "Flash Size".)
 Rx: In built PSRAM (SPI accessed via SPI0 and SPI1 of the ESP32 S3 WROOM MC)
     All models don't have PSRAM, and thus, an Rx value. In such case,
     Keep "PSRAM" as disabled.
     If your board is of S3 NxRx series, it has an 
     Octal Peripheral Interfaced Pseudo Static Random Access memory (OPI PSRAM).
      Octal for: 8 data lines connected directly for faster and parallel
      connection to the PSRAM unit.   
*/

[NB: Readers with NxRx module can keep PSRAM disabled if needed in power constrained scenarios
Don't confuse Rx PSRAM as the main RAM for the MC. All ESP32 S3 have an internal 512KB S(tatic)RAM]

IMPORTANT: Whenever Sanity Checking for the first time, make sure that the ""Core Debug Level" is on Verbose, to catch any stray mistakes. Use warn or any other higher level of debug only when sanity is assured.

Make sure that PSRAM used is OPI (For Rev2 Devkit C1, but always check with your manufacturer for accurate info).

If everything else is setup here properly, you will be properly guided by the debug outputs themselves if there is anything wrong in your setup.
(e.g: for my case, I had the PSRAM setup to QSPI, which was causing the sketch to fail silently after upload, only came to know about it after I turned on Core Debug level, which remained disabled by default).

The examples I used to test:
In Arduino IDE:
File ─> Examples ─> ESP32 ─> ChipID > GetChipID.ino
..............................................└─> GPIO > BlinkRGB.ino

For the LED sketch, make sure that your defined LED pin is correct. For usual case (and the tested board), the RGB is at GPIO Pin 38.

Also make sure to uncomment the RGB brightness define on the top of the sketch, which is commented out by default.
In short, you should include these two preprocessing defines if you have exactly the same board:

#define RGB_BUILTIN 38        // Board-specific: RGB LED on GPIO 38
#define RGB_BRIGHTNESS 64     // Default brightness (max 255)

You can also try this sketch which is a rework on the available BlinkRGB example but enhanced for the current board.
BlinkRGB.ino sketch:
ESP32 S3 BlinkRGB.ino Advanced Example Sketch

Otherwise, do make sure that the cable is proper and supports data. A good expensive cable is essential for any USB communication with almost any MC.

For further information and USB port support, you can refer to these two resources on which I relied. I will update the post later when I actually test out a sketch over the USB port with tested results.

Sanity Check for Uploading Code to DevKitC-1 Using Arduino IDE (Solved)
#1 - How to Program ESP32-S3 with Arduino IDE + CP2102 Driver & WS2812/SK6812 RGB LED Setup

If there is anything anyone would like to add from their experience, or this works for any other ESPS3 boards as well, please feel free to add to the thread.

Happy LED-ing.

Additional Reads and Resources:
ESP32 S3 Official Datasheet - Espressif Systems
ESP32 S3 Devkit C1 Official Schematics
ESP32 S3 Devkit C1 Official Website

1 Like

Thanks for sharing!

1 Like

ESP32 S3 Sanity Check Part 2: 1.8 ST7735 TFT SPI 128*160

Assuming that the board has been properly set up, now you have one of those cheap KMR 1.8 128x160 ST7735 driven displays to try out.

I was able to get everything running smoothly with TFT_eSPI display library for ST7735.

Following are the briefs from my experience.

Tested for:

Inside the Arduino IDE

Press Ctrl + Shift + I to open Library Manager

>Install TFT_eSPI by @bodmer (Tested with v2.5.43)

>Press Alt + Ctrl + K to open the Sketch Folder/Directory

>Navigate to β€œlibrariesβ€œ folder

>Find TFT_eSPI folder there

>Go inside, and update the User_Setup.h file there as directed in the following thread.

  This is the header file that will tell the eSPI driver 
about the hardware configurations, so that it’s action 
matches the hardware expectations.

KET SETTINGS to change in the TFT_eSPI header

*    - #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) // Tested freq

Working and tested header file:

//                            USER DEFINED SETTINGS
//   Set driver type, fonts to be loaded, pins used and SPI control method etc
//
//                       EDIT THE PIN NUMBERS IN THE LINES FOLLOWING TO SUIT YOUR ESP32 SETUP                       

// ##################################################################################
//
// Section 1. Call up the right driver file and any options for it
//
// ##################################################################################

// Define to disable all #warnings in library (can be put in User_Setup_Select.h)
//#define DISABLE_ALL_LIBRARY_WARNINGS

// Tell the library to use the ESP32 SPI port 1 (VSPI)
#define USER_SETUP_ID 25

#define ST7735_DRIVER      // Define additional parameters below for this display

// For ST7735, ST7789 and ILI9341 ONLY, define the colour order IF the blue and red are swapped on your display
// Try ONE option at a time to find the correct colour order for your display

//  #define TFT_RGB_ORDER TFT_RGB  // Colour order Red-Green-Blue
//  #define TFT_RGB_ORDER TFT_BGR  // Colour order Blue-Green-Red

// For ST7735 ONLY
// Define the type of display and tab colour for 128x160 displays
#define ST7735_BLACKTAB
// #define ST7735_REDTAB
// #define ST7735_GREENTAB
// #define ST7735_GREENTAB2
// #define ST7735_GREENTAB3
// #define ST7735_GREENTAB128    // For 128x128 display
// #define ST7735_GREENTAB160x80 // For 160x80 display (BGR, inverted, 26 offset)

// ##################################################################################
//
// Section 2. Define the pins that are used to interface with the display here
//
// ##################################################################################

// For ESP32-S3 Dev board with ST7735 display
// Use dedicated SPI pins for better compatibility

#define TFT_MISO -1  // Not connected for this project
#define TFT_MOSI 11  // ESP32-S3 Hardware SPI MOSI (MUST use hardware SPI pins)
#define TFT_SCLK 12  // ESP32-S3 Hardware SPI SCLK (MUST use hardware SPI pins)
#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 RST pin)
#define TFT_BL    6  // LED back-light (Use -1 to disable pin in case of no PWM backlight control need

// ESP32-S3 specific: Force use of hardware SPI port
#define USE_HSPI_PORT

// ##################################################################################
//
// Section 3. Define the fonts that are to be used here
//
// ##################################################################################

// Comment out the #defines below with // to stop that font being loaded
// The ESP32 and ESP8266 have plenty of memory so commenting out fonts is not
// normally necessary. If all fonts are loaded the extra FLASH usage is ~15Kbytes.
// To save FLASH space only enable the fonts you need!

#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6  // Font 6. Large 48 pixel high font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7  // Font 7. 7 segment 48 pixel high font, needs ~2438 bytes in FLASH, only characters 1234567890:-.
#define LOAD_FONT8  // Font 8. Large 75 pixel high font needs ~3256 bytes in FLASH, only characters 1234567890:-.
//#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts

// Comment out the #define below to stop the SPIFFS filing system and smooth font code being loaded
// this will save ~20kbytes of FLASH
#define SMOOTH_FONT

// ##################################################################################
//
// Section 4. Other options
//
// ##################################################################################

// Reduced SPI frequency for ESP32-S3 compatibility
//#define SPI_FREQUENCY  10000000  // 10MHz - reduced for S3 stability
#define SPI_FREQUENCY  27000000  // Try this if 10MHz works
//#define SPI_FREQUENCY  40000000  // Maximum to use SPIFFS
//#define SPI_FREQUENCY  80000000  // Comment out if using SPIFFS or other functions that access the FLASH File system

// Optional reduced SPI frequency for reading TFT
#define SPI_READ_FREQUENCY  2000000  // 2MHz for reading

// The ESP32 has 2 free SPI ports i.e. VSPI and HSPI, the VSPI is the default.
// If the VSPI port is in use and pins are not accessible (e.g. TTGO T-Beam)
// then uncomment the following line:
//#define USE_HSPI_PORT

// Comment out the following #define if "SPI Transactions" do not need to be
// supported. When commented out the code size will be smaller and sketches will
// run slightly faster, so leave it commented out unless you need it!
// Transaction support is needed to work with SD library but not needed with TFT_SdFat
// Transaction support is required if other SPI devices are connected.
#define SUPPORT_TRANSACTIONS

Here is the connection layout. Bypass capacitor for the circuit and limiting resistors for the backlight are optional for regulated 3v3 [provided it is powered from the VOUT (3V3) marked pin) of Devkit C1, Devkit C1 handles it to a functional level]. If used, recommended values are 10ΞΌF and 220Ξ© respectively. Also ceramic capacitors are recommended for any effective noise reduction or decoupling. Use electrolytic only in the non availability of ceramic.

 * 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) (Optional)
 * 
 * NeoPixel -> ESP32-S3 DevKit C1
 * Data   -> GPIO 48 (Built-in RGB LED)

Great! Now that you are setup, try any of the eSPI examples. For example:
File > Examples > TFT_eSPI > 128 x 160 > TFT_Clock_Digital.ino

Happy computing

Your two or more topics on the same or similar subject have been merged.

Please do not duplicate your questions as doing so wastes the time and effort of the volunteers trying to help you as they are then answering the same thing in different places.

Please create one topic only for your question and choose the forum category carefully. If you have multiple questions about the same project then please ask your questions in the one topic as the answers to one question provide useful context for the others, and also you won’t have to keep explaining your project repeatedly.

Repeated duplicate posting could result in a temporary or permanent ban from the forum.

Could you take a few moments to Learn How To Use The Forum

It will help you get the best out of the forum in the future.

Thank you.

1 Like

Noted @UKHeliBob. Thank you for the heads up and the merge.
As for the forum rule I have gone through it the very first day I opened the account.

I just did not connect it with this problem before your remark. Makes sense, and my life easier as well.

Footnote: I did not need to go through the Forum post, I just needed to see your posts as a reference. Now I have a clear format in my head.

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.

ESP32 S3 Sanity Check Part 4: Dual Screen Dual Core Double Buffer

Now that/if you are setup with the ESP32 S3 N8R8 and ST7735 128x160 SPI display, you might be wondering what more can be done.

How about, using two 128X160 ST7735 SPI displays?
And to top it off, what if each display is dedicated to each core?

Well, as they say, two is always better than one.

Here is a test suite to run on this hardware setup, with ASCII schematic and further brief explanations of the principles used. Using Adafruit GFX and ST3775 Libraries.

/**
 * @file ESP32-S3_DualScreen_Demo.ino
 * @brief ESP32-S3 Dual Core Dual Screen Graphics Demo using Adafruit GFX Library
 * @version 2.0
 * @date 2025-08-22
 * 
 * @author Sir Ronnie from Core 1D Automation Labs
 * @license MIT License
 * 
 * @section description Description
 * This project demonstrates advanced dual-core programming on ESP32-S3 with two ST7735 TFT displays.
 * Each CPU core controls one screen independently, showcasing true parallel processing capabilities.
 * Features double buffering, thread-safe SPI communication, and smooth 60 FPS animations.
 * 
 * @section hardware Hardware Requirements
 * - ESP32-S3 DevKit C1 (or compatible with PSRAM)
 * - 2x ST7735 128x160 TFT displays (1.8" recommended)
 * - Breadboard and jumper wires
 * - 5V/3.3V power supply (USB sufficient for testing)
 * 
 * @section wiring Wiring Diagram
 * @code
 *                    ESP32-S3 N8 R8 DevKit C1
 *                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 *                   β”‚                     β”‚
 *    ╔─────────────── GPIO 11 (MOSI)  ────┼───────────────────────────────────╗
 *    ║─────────────── GPIO 12 (SCLK)  ────┼───────────────────────────────────║ 
 *    β•‘              β”‚                     β”‚                                   β•‘
 *    β•‘              β”‚ GPIO 10 (CS1)   ────┼─────┐                             β•‘
 *    β•‘              β”‚ GPIO 4  (DC1)   ────┼───┐ β”‚                             β•‘
 *    β•‘              β”‚ GPIO 5  (RST1)  ────┼─┐ β”‚ β”‚                             β•‘
 *    β•‘              β”‚                     β”‚ β”‚ β”‚ β”‚                             β•‘
 *    β•‘              β”‚ GPIO 15 (CS0)   ────┼─┼─┼─┼─────┐                       β•‘
 *    β•‘              β”‚ GPIO 7  (DC0)   ────┼─┼─┼─┼───┐ β”‚                       β•‘
 *    β•‘              β”‚ GPIO 6  (RST0)  ────┼─┼─┼─┼─┐ β”‚ β”‚                       β•‘
 *    β•‘              β”‚                     β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚                       β•‘
 *    β•‘              β”‚ 3.3V ───────────────┼─┼─┼─┼─┼─┼─┼─── [VCC (Common 3v3)] β•‘
 *    β•‘              β”‚ GND ────────────────┼─┼─┼─┼─┼─┼─┼─── [GND (Common)]     β•‘
 *    β•‘              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚                       β•‘
 *    β•‘                                 RST1<β”˜ β”‚ β”‚ β”‚ β”‚ β”‚                       β•‘
 *    β•‘    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”   ST7735 #0         DC1<β”˜ β”‚ β”‚ β”‚ β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β•‘                ST7735 #1
 *    ║───── SCK     β”‚   (LEFT SCREEN)       CS1<β”˜ β”‚ β”‚ β”‚    β”‚ SCK        β”œβ”€β”€β”€β”€β”€β•‘                (RIGHT SCREEN) 
 *    β•šβ”€β”€β”€β”€β”€ MOSI    β”‚   Core 0 Control            β”‚ β”‚ β”‚    β”‚ MOSI       β”œβ”€β”€β”€β”€β”€β•    β”Œ>GPIO 10     Core 1 Control
 *         β”‚ CS   β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”Όβ”€β”˜    β”‚ CS      β”€β”€β”€β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œ>GPIO 4
 *         β”‚ DC   β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”˜      β”‚ DC      β”€β”€β”€β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œ>GPIO 5
 *         β”‚ RST  β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚ RST     β”€β”€β”€β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 *         β”‚ VCC  ───┼─────     [Common 3v3]     ───────────┼ VCC        β”‚
 *         β”‚ GND  ───┼─────     [Common GND]     ───────────┼ GND        β”‚
 *         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 * 
 *         Core 0 (LEFT)                                    Core 1 (RIGHT)
 *         β”œβ”€ Basic Colors                                  β”œβ”€ Color Cycle  
 *         β”œβ”€ Bouncing Ball                                 β”œβ”€ Moving Lines
 *         β”œβ”€ Rainbow Bars                                  β”œβ”€ Color Noise
 *         β”œβ”€ Geometric Shapes                              β”œβ”€ Pixel Rain
 *         └─ Text Test                                     └─ Pattern Test
 * @endcode
 * 
 * @section features Key Features
 * True dual-core processing (Core 0 + Core 1)
 * Thread-safe SPI communication with mutex
 * Double buffering for flicker-free graphics  
 * ~60 FPS butter animations on both screens
 * PSRAM utilization for large frame buffers
 * Memory-efficient canvas rendering
 * Automatic test cycling every 60 seconds
 * Real-time performance monitoring
 * 
 * @section libraries Required Libraries
 * Install these libraries through Arduino IDE Library Manager:
 * - Adafruit GFX Library (>=1.11.0)
 * - Adafruit ST7735 and ST7789 Library (>=1.9.0)
 * 
 * @section performance Performance Expectations
 * - Frame Rate: ~60 FPS per screen
 * - Memory Usage: ~65KB RAM + 82KB PSRAM
 * - SPI Speed: 40MHz (optimized for ST7735)
 * - CPU Usage: ~30% per core during complex animations
 * 
 * @section license License Information
 * MIT License
 * 
 * Copyright (c) 2025 Sir Ronnie, Core 1D Automation Labs
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * 
 * @section adafruit_license Adafruit License
 * This project uses Adafruit GFX Library and ST7735 Library:
 * 
 * Copyright (c) 2012 Adafruit Industries. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED.
 */

// ============================================================================
// LIBRARY INCLUDES - Essential Graphics Libraries
// ============================================================================

#include <Adafruit_GFX.h>    // Core graphics library - provides drawing functions
#include <Adafruit_ST7735.h> // ST7735 TFT display driver - handles display communication  
#include <SPI.h>             // SPI communication protocol - connects to displays

// ============================================================================
// HARDWARE PIN CONFIGURATION - ESP32-S3 DevKit C1
// ============================================================================
// Using constexpr for compile-time constants
// Use constexpr as a replacement for #define whenever possible, to have compile time evaluation

// Screen 1 (RIGHT) - Controlled by Core 1
constexpr uint8_t SCREEN1_CS    = 10;   // Chip Select - tells display when data is for it
constexpr uint8_t SCREEN1_RST   = 5;    // Reset pin - restarts the display
constexpr uint8_t SCREEN1_DC    = 4;    // Data/Command - tells display if data is command or pixel data

// Screen 0 (LEFT) - Controlled by Core 0  
constexpr uint8_t SCREEN0_CS    = 15;   // Chip Select for left display
constexpr uint8_t SCREEN0_RST   = 6;    // Reset pin for left display
constexpr uint8_t SCREEN0_DC    = 7;    // Data/Command for left display

// Shared SPI pins - both displays use same data lines (saved pins + hard SPI support)
constexpr uint8_t SHARED_MOSI   = 11;   // Master Out Slave In - sends data to displays
constexpr uint8_t SHARED_SCLK   = 12;   // Serial Clock - synchronizes data transmission

// ============================================================================
// DISPLAY CONFIGURATION CONSTANTS
// ============================================================================

constexpr uint16_t       SCREEN_WIDTH = 128;          // ST7735 display width in pixels
constexpr uint16_t      SCREEN_HEIGHT = 160;         // ST7735 display height in pixels
constexpr uint16_t   TEXT_AREA_HEIGHT = 35;         // Reserved space at top for test names
constexpr uint16_t        DRAW_AREA_Y = TEXT_AREA_HEIGHT;      // Where drawing area starts
constexpr uint16_t   DRAW_AREA_HEIGHT = SCREEN_HEIGHT - TEXT_AREA_HEIGHT; // Available drawing height
constexpr unsigned long TEST_DURATION = 60000;              // How long each test runs (60 seconds)

// ============================================================================
// DISPLAY AND GRAPHICS OBJECTS
// ============================================================================

// Create display objects - these handle low-level display communication
Adafruit_ST7735 screen0(SCREEN0_CS, SCREEN0_DC, SCREEN0_RST);  // Left screen for Core 0
Adafruit_ST7735 screen1(SCREEN1_CS, SCREEN1_DC, SCREEN1_RST);  // Right screen for Core 1

// Double buffer canvases - draw in memory first, then transfer to screen (prevents flicker)
// GFXcanvas16 = 16-bit color depth (65,536 colors) - good balance of quality and memory
GFXcanvas16 canvas0(SCREEN_WIDTH, SCREEN_HEIGHT);  // Memory buffer for left screen
GFXcanvas16 canvas1(SCREEN_WIDTH, SCREEN_HEIGHT);  // Memory buffer for right screen

// ============================================================================
// MULTI-THREADING SYNCHRONIZATION
// ============================================================================

// Task handles - references to our two CPU core tasks
TaskHandle_t Core0Task;  // Handle for Core 0 task (left screen)
TaskHandle_t Core1Task;  // Handle for Core 1 task (right screen)

// Mutex (mutual exclusion) - prevents both cores from using SPI at same time
// Think of it like a bathroom key - only one person can use it at a time!
SemaphoreHandle_t spiMutex;

// ============================================================================
// TEST STATE MANAGEMENT
// ============================================================================

/**
 * @brief Structure to track the state of each core's test execution
 * 
 * This keeps track of what test is running, timing, and update flags
 * for each CPU core independently.
 */
struct TestState {
  uint8_t       currentTest     = 0;        // Which test is currently running (0-4)
  unsigned long testStartTime   = 0; // When current test started (for auto-switching)
  uint32_t      frameCount      = 0;        // Count frames for performance monitoring  
  bool          needsUpdate     = true;        // Flag: does screen need refreshing?
  bool          forceFullRedraw = true;    // Flag: force complete screen redraw?
};

// Create separate state tracking for each core
TestState core0State;  // State for Core 0 (left screen)
TestState core1State;  // State for Core 1 (right screen)

// ============================================================================
// COLOR PALETTE - 16-bit RGB565 Format
// ============================================================================
// RGB565 packs colors into 16 bits: 5 bits red, 6 bits green, 5 bits blue
// This gives us 65,536 possible colors with efficient memory usage

const uint16_t colors[] = {
  ST77XX_BLACK, ST77XX_BLUE,    ST77XX_RED,    ST77XX_GREEN,      // Basic colors
  ST77XX_CYAN,  ST77XX_MAGENTA, ST77XX_YELLOW, ST77XX_WHITE,     // More basic colors
  0x0010,       0x0200,         0x8000,        0x8010,          // Dark shades (custom RGB565 values)
  0xFBE0,       0x07E0,         0xF81F,        0x7BEF          // Bright shades (custom RGB565 values)
};

// ============================================================================
// TEST NAMES FOR DISPLAY
// ============================================================================

// Test names for Core 0 (left screen) - simple graphics
const char* core0Tests[] = {
  "Basic Colors",      // Simple color fills and transitions
  "Bouncing Ball",     // Physics simulation with collision detection
  "Rainbow Bars",      // Scrolling color bands
  "Geometric Shapes",  // Moving circles and rectangles
  "Text Display"       // Typography and text effects
};

// Test names for Core 1 (right screen) - more advanced effects
const char* core1Tests[] = {
  "Color Cycle",      // Smooth color transitions with concentric circles
  "Moving Lines",     // Scrolling line patterns
  "Color Noise",      // Mathematical noise effect (sine/cosine waves)
  "Pixel Rain",       // Falling pixels simulation (like Matrix effect)
  "Pattern Test"      // Dynamic checkerboard patterns
};

// ============================================================================
// FUNCTION DECLARATIONS - Forward declarations for compiler
// ============================================================================

// Core task functions - these run continuously on each CPU core
void Core0Loop(void* pvParameters);
void Core1Loop(void* pvParameters);

// Canvas update functions - decide which test to run
void updateCanvas0();
void updateCanvas1();

// Utility function for drawing test headers
void drawTestHeader(GFXcanvas16& canvas, const char* core, const char* test);

// Core 0 test functions - simpler effects for learning
void core0_basicColors    (GFXcanvas16& canvas);
void core0_bouncingBall   (GFXcanvas16& canvas);
void core0_rainbowBars    (GFXcanvas16& canvas);
void core0_geometricShapes(GFXcanvas16& canvas);
void core0_textDisplay    (GFXcanvas16& canvas);

// Core 1 test functions - more advanced effects
void core1_colorCycle     (GFXcanvas16& canvas);
void core1_movingLines    (GFXcanvas16& canvas);
void core1_colorNoise     (GFXcanvas16& canvas);
void core1_pixelRain      (GFXcanvas16& canvas);
void core1_patternTest    (GFXcanvas16& canvas);

// ============================================================================
// SETUP FUNCTION - Runs once at startup
// ============================================================================

/**
 * @brief Main setup function - initializes hardware and starts dual-core tasks
 * 
 * This function runs once when the ESP32 boots up. It:
 * 1. Initializes serial communication for debugging
 * 2. Checks for PSRAM (external memory for better performance)
 * 3. Sets up SPI communication protocol
 * 4. Creates and starts tasks on both CPU cores
 */
void setup() {
  // Initialize serial communication for debugging output
  Serial.begin(115200);  // 115200 baud rate - fast enough for real-time monitoring
  delay(2000);           // Give serial monitor time to connect
  
  // Print startup banner
  Serial.println("ESP32-S3 Dual Screen with Adafruit GFX - Simplified");
  Serial.println("===================================================");
  
  // Check for PSRAM (external RAM) - improves performance with large graphics
  if (psramFound()) {
    Serial.printf("PSRAM found! Free: %.1f MB\n", ESP.getFreePsram() / 1024.0 / 1024.0);
  } else {
    Serial.println("WARNING: PSRAM not found. Performance may be reduced.");
    // Note: Code will still work without PSRAM, just slower
  }
  
  // Create mutex for thread-safe SPI access
  // This prevents both cores from trying to talk to displays simultaneously
  spiMutex = xSemaphoreCreateMutex();
  
  // Initialize SPI bus with our pin configuration
  // -1 for MISO (not used) and SS (we handle CS manually)
  SPI.begin(SHARED_SCLK, -1, SHARED_MOSI, -1);
  
  Serial.println("Creating dual-core tasks...");
  
  // Create task for Core 0 (left screen)
  // Parameters: function, name, stack size, parameters, priority, handle, core number
  xTaskCreatePinnedToCore(Core0Loop, "Core0Task", 8192, NULL, 2, &Core0Task, 0);
  delay(1000);  // Brief delay to let Core 0 initialize first
  
  // Create task for Core 1 (right screen) 
  xTaskCreatePinnedToCore(Core1Loop, "Core1Task", 8192, NULL, 2, &Core1Task, 1);
  
  Serial.println("Adafruit GFX dual-core system initialized!");
}

// ============================================================================
// MAIN LOOP - Runs continuously on Core 1 (monitoring and reporting)
// ============================================================================

/**
 * @brief Main monitoring loop - provides performance feedback
 * 
 * This runs on Core 1 alongside the graphics task. It monitors both cores
 * and reports performance statistics every 3 seconds. This helps with
 * debugging and performance optimization.
 */
void loop() {
  static unsigned long lastReport = 0;
  
  // Report performance statistics every 3 seconds
  if (millis() - lastReport > 3000) {
    Serial.println("\n=== PERFORMANCE REPORT ===");
    Serial.printf("Core 0 - Test: %s, Frames: %d (%.1f FPS)\n", 
                  core0Tests[core0State.currentTest % 5], 
                  core0State.frameCount, 
                  core0State.frameCount / 3.0);  // FPS = frames / 3 seconds
    Serial.printf("Core 1 - Test: %s, Frames: %d (%.1f FPS)\n", 
                  core1Tests[core1State.currentTest % 5], 
                  core1State.frameCount, 
                  core1State.frameCount / 3.0);
    Serial.printf("Free Heap: %d bytes, Free PSRAM: %d bytes\n", 
                  ESP.getFreeHeap(), ESP.getFreePsram());
    
    // Reset frame counters for next measurement period
    core0State.frameCount = 0;
    core1State.frameCount = 0;
    lastReport = millis();
  }
  
  delay(100);  // Don't hog CPU time - this is just monitoring
}

// ============================================================================
// CORE 0 TASK - Screen 0 (simple effects)
// ============================================================================

/**
 * @brief Core 0 main loop - runs continuously on CPU Core 0
 * @param pvParameters Unused (required by FreeRTOS task signature)
 * 
 * This task handles the left screen and demonstrates fundamental graphics concepts.
 */
void Core0Loop(void* pvParameters) {
  Serial.printf("Core 0 starting on core: %d\n", xPortGetCoreID());
  
  // Initialize left screen (Screen 0) with thread safety
  if (xSemaphoreTake(spiMutex, portMAX_DELAY) == pdTRUE) {
    Serial.println("Core 0: Initializing LEFT screen...");
    
    // Initialize ST7735 display
    screen0.initR(INITR_BLACKTAB);  // Use black tab variant settings
    screen0.setRotation(0);         // Portrait orientation
    screen0.fillScreen(ST77XX_BLACK);
    
    // Display startup test pattern to verify connections
    screen0.fillScreen(ST77XX_RED);   delay(300);  // Flash red
    screen0.fillScreen(ST77XX_GREEN); delay(300);  // Flash green  
    screen0.fillScreen(ST77XX_BLUE);  delay(300);  // Flash blue
    screen0.fillScreen(ST77XX_BLACK);               // Return to black
    
    Serial.println("Core 0: LEFT screen ready!");
    xSemaphoreGive(spiMutex);  // Release SPI for other core to use
  }
  
  // Initialize canvas buffer and timing
  canvas0.fillScreen(ST77XX_BLACK);
  core0State.testStartTime = millis();
  
  // Main render loop - runs forever
  while (true) {
    // Check if it's time to switch to next test (every 60 seconds)
    if (millis() - core0State.testStartTime >= TEST_DURATION) {
      core0State.currentTest++;          // Move to next test
      core0State.testStartTime   = millis(); // Reset timer
      core0State.forceFullRedraw = true;     // Force complete redraw
      core0State.needsUpdate     = true;     // Mark as needing screen update
      Serial.printf("Core 0 -> %s\n", core0Tests[core0State.currentTest % 5]);
    }
    
    // Update the canvas with current test graphics
    updateCanvas0();
    
    // Transfer canvas to physical screen if changes were made
    if (core0State.needsUpdate) {
      // Try to get exclusive SPI access (timeout after 50ms)
      if (xSemaphoreTake(spiMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
        // This is the magic! One function call transfers entire canvas to screen
        screen0.drawRGBBitmap(0, 0, canvas0.getBuffer(), SCREEN_WIDTH, SCREEN_HEIGHT);
        xSemaphoreGive(spiMutex);  // Release SPI for other core
      }
      core0State.needsUpdate = false;  // Mark as updated
    }
    
    core0State.frameCount++;  // Count frame for performance monitoring
    vTaskDelay(pdMS_TO_TICKS(16)); // Target ~60 FPS (16ms per frame)
  }
}

// ============================================================================
// CORE 1 TASK - Screen 1 (advanced effects)
// ============================================================================

/**
 * @brief Core 1 main loop - runs continuously on CPU Core 1  
 * @param pvParameters Unused (required by FreeRTOS task signature)
 * 
 * This task handles the right screen and demonstrates advanced graphics effects.
 * Advanced Effects have been reserved on Core1, as only Arduino Core lives on Core 1, while Core 0 is dedicated for 
 * It shows more complex mathematical and visual concepts.
 */
void Core1Loop(void* pvParameters) {
  Serial.printf("Core 1 starting on core: %d\n", xPortGetCoreID());
  delay(500);  // Brief delay to offset from Core 0 initialization
  
  // Initialize right screen (Screen 1) 
  if (xSemaphoreTake(spiMutex, portMAX_DELAY) == pdTRUE) {
    Serial.println("Core 1: Initializing RIGHT screen...");
    
    screen1.initR(INITR_BLACKTAB);
    screen1.setRotation(0);
    screen1.fillScreen(ST77XX_BLACK);
    
    // Different test pattern to distinguish from Core 0
    screen1.fillScreen(ST77XX_YELLOW);  delay(300);
    screen1.fillScreen(ST77XX_CYAN);    delay(300);  
    screen1.fillScreen(ST77XX_MAGENTA); delay(300);
    screen1.fillScreen(ST77XX_BLACK);
    
    Serial.println("Core 1: RIGHT screen ready!");
    xSemaphoreGive(spiMutex);
  }
  
  canvas1.fillScreen(ST77XX_BLACK);
  core1State.testStartTime = millis();
  
  // Main render loop - identical structure to Core 0 but different tests
  while (true) {
    if (millis() - core1State.testStartTime >= TEST_DURATION) {
      core1State.currentTest++;
      core1State.testStartTime   = millis();
      core1State.forceFullRedraw = true;
      core1State.needsUpdate     = true;
      Serial.printf("Core 1 -> %s\n", core1Tests[core1State.currentTest % 5]);
    }
    
    updateCanvas1();
    
    if (core1State.needsUpdate) {
      if (xSemaphoreTake(spiMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
        screen1.drawRGBBitmap(0, 0, canvas1.getBuffer(), SCREEN_WIDTH, SCREEN_HEIGHT);
        xSemaphoreGive(spiMutex);
      }
      core1State.needsUpdate = false;
    }
    
    core1State.frameCount++;
    vTaskDelay(pdMS_TO_TICKS(16));
  }
}

// ============================================================================
// CANVAS UPDATE DISPATCHERS - Route to correct test function
// ============================================================================

/**
 * @brief Dispatch Core 0 canvas updates to appropriate test function
 * 
 * This acts as a router, calling the correct test function based on 
 * the current test index. Using modulo (%) ensures we cycle through tests.
 */
void updateCanvas0() {
  switch (core0State.currentTest % 5) {
    case 0: core0_basicColors    (canvas0); break;       // Color fundamentals
    case 1: core0_bouncingBall   (canvas0); break;      // Physics simulation  
    case 2: core0_rainbowBars    (canvas0); break;     // Color animation
    case 3: core0_geometricShapes(canvas0); break;    // Shape drawing
    case 4: core0_textDisplay    (canvas0); break;   // Typography
  }
}

/**
 * @brief Dispatch Core 1 canvas updates to appropriate test function
 */
void updateCanvas1() {
  switch (core1State.currentTest % 5) {
    case 0: core1_colorCycle (canvas1); break;            // Advanced color effects
    case 1: core1_movingLines(canvas1); break;           // Line animations
    case 2: core1_colorNoise (canvas1); break;          // Mathematical effects
    case 3: core1_pixelRain  (canvas1); break;         // Particle simulation
    case 4: core1_patternTest(canvas1); break;        // Pattern generation
  }
}

// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================

/**
 * @brief Draw header section with core name and test name
 * @param canvas The graphics canvas to draw on
 * @param core Core identifier ("CORE 0" or "CORE 1")  
 * @param test Current test name
 * 
 * Creates a consistent header area at the top of each screen showing
 * which core is running and what test is currently active.
 */
void drawTestHeader(GFXcanvas16& canvas, const char* core, const char* test) {
  // Clear header area with black background
  canvas.fillRect(0, 0, SCREEN_WIDTH, TEXT_AREA_HEIGHT, ST77XX_BLACK);
  
  // Draw core name in large white text
  canvas.setTextColor(ST77XX_WHITE);
  canvas.setTextSize(2);  // 2x normal size
  
  // Auto-center the text using getTextBounds()
  int16_t x1, y1;
  uint16_t w, h;
  canvas.getTextBounds(core, 0, 0, &x1, &y1, &w, &h);
  canvas.setCursor((SCREEN_WIDTH - w) / 2, 5);  // Center horizontally
  canvas.print(core);
  
  // Draw test name in smaller yellow text
  canvas.setTextColor(ST77XX_YELLOW);
  canvas.setTextSize(1);  // Normal size
  canvas.getTextBounds(test, 0, 0, &x1, &y1, &w, &h);
  canvas.setCursor((SCREEN_WIDTH - w) / 2, 22);
  canvas.print(test);
}

// ============================================================================
// CORE 0 TESTS - Simple Graphics Demonstrations
// ============================================================================

/**
 * @brief Test 1: Basic Colors - Demonstrates color fundamentals
 * @param canvas Graphics canvas to draw on
 * 
 * This test cycles through different colors, showing:
 * - RGB565 color format usage
 * - Rectangle drawing functions
 * - Color palette management
 * - Frame-based animation timing
 */
void core0_basicColors(GFXcanvas16& canvas) {
  static int colorIndex     = 0;      // Current color in palette
  static int lastColorIndex = -1; // Track changes to avoid unnecessary redraws
  
  // Only redraw if color changed or forced full redraw
  if (core0State.forceFullRedraw || colorIndex != lastColorIndex) {
    canvas.fillScreen(ST77XX_BLACK);
    drawTestHeader(canvas, "CORE 0", "Basic Colors");
    
    // Get two contrasting colors from palette  
    uint16_t color1 = colors[colorIndex % 16];
    uint16_t color2 = colors[(colorIndex + 8) % 16];  // Offset for contrast
    
    // Draw nested rectangles to show color relationships
    canvas.fillRect( 0, DRAW_AREA_Y,      SCREEN_WIDTH,      DRAW_AREA_HEIGHT,      color1);
    canvas.fillRect(10, DRAW_AREA_Y + 10, SCREEN_WIDTH - 20, DRAW_AREA_HEIGHT - 20, color2);
    
    core0State.needsUpdate     = true;
    lastColorIndex             = colorIndex;
    core0State.forceFullRedraw = false;
  }
  
  // Change color every 60 frames (1 second at 60 FPS)
  static int frameCount = 0;
  if (++frameCount >= 60) {
    colorIndex++;
    frameCount = 0;
  }
}

/**
 * @brief Test 2: Bouncing Ball - Physics simulation
 * @param canvas Graphics canvas to draw on
 * 
 * This test demonstrates:
 * - Position and velocity vectors
 * - Collision detection with screen boundaries
 * - Circle drawing functions
 * - Real-time physics animation
 * - Dynamic color changes on collision
 */
void core0_bouncingBall(GFXcanvas16& canvas) {
  static int ballX = 64, ballY = 80;  // Ball position (starts at center)
  static int velX  =  3, velY  =  4;      // Ball velocity (pixels per frame)
  static int colorIndex = 0;          // Color changes when ball bounces
  
  // Always update for smooth animation (no optimization here.)
  canvas.fillScreen(ST77XX_BLACK);
  drawTestHeader(canvas, "CORE 0", "Bouncing Ball");
  
  // Draw ball as two circles - colored outer, white center for 3D effect
  canvas.fillCircle(ballX, ballY, 10, colors[colorIndex % 16]);  // Outer circle
  canvas.fillCircle(ballX, ballY,  6, ST77XX_WHITE           );  // Inner highlight
  
  // Update ball position (add velocity to position)
  ballX += velX;
  ballY += velY;
  
  // Collision detection - bounce off walls and change color
  if (ballX <= 15 || ballX >= SCREEN_WIDTH - 15) {  // Hit left or right wall
    velX = -velX;    // Reverse horizontal direction
    colorIndex++;    // Change color on bounce
  }
  if (ballY <= DRAW_AREA_Y + 15 || ballY >= SCREEN_HEIGHT - 15) {  // Hit top or bottom
    velY = -velY;    // Reverse vertical direction  
    colorIndex++;    // Change color on bounce
  }
  
  core0State.needsUpdate = true;  // Always need screen update for animation
}

/**
 * @brief Test 3: Rainbow Bars - Scrolling color animation
 * @param canvas Graphics canvas to draw on
 * 
 * This test shows:
 * - Loop-based drawing patterns
 * - Color palette cycling
 * - Scrolling animations with modulo arithmetic
 * - Horizontal rectangle drawing
 */
void core0_rainbowBars(GFXcanvas16& canvas) {
  static int offset     = 0;          // Scroll position
  static int lastOffset = -1;     // Optimization: track changes
  
  // Only redraw when scroll position changes
  if (core0State.forceFullRedraw || offset != lastOffset) {
    canvas.fillScreen(ST77XX_BLACK);
    drawTestHeader(canvas, "CORE 0", "Rainbow Bars");
    
    const int barHeight = 8;  // Height of each color bar
    
    // Draw horizontal bars across the screen
    for (int y = DRAW_AREA_Y; y < SCREEN_HEIGHT; y += barHeight) {
      // Calculate color index based on position + scroll offset
      uint16_t color = colors[((y - DRAW_AREA_Y)/barHeight + offset) % 16];
      canvas.fillRect(0, y, SCREEN_WIDTH, barHeight, color);
    }
    
    core0State.needsUpdate     = true;
    lastOffset                 = offset;
    core0State.forceFullRedraw = false;
  }
  
  // Scroll every 10 frames for smooth but not too fast movement
  static int frameCount = 0;
  if (++frameCount >= 10) {
    offset++;
    frameCount = 0;
  }
}

/**
 * @brief Test 4: Geometric Shapes - Moving shapes with trigonometry
 * @param canvas Graphics canvas to draw on
 * 
 * This test demonstrates:
 * - Trigonometric functions (sin, cos) for circular motion
 * - Multiple object animation
 * - Shape drawing (rectangles and circles)
 * - Boundary checking and constraint functions
 */
void core0_geometricShapes(GFXcanvas16& canvas) {
  static int frame = 0;  // Animation frame counter
  
  canvas.fillScreen(ST77XX_BLACK);
  drawTestHeader(canvas, "CORE 0", "Geometric Shapes");
  
  int centerY = DRAW_AREA_Y + DRAW_AREA_HEIGHT/2;  // Vertical center of drawing area
  
  // Draw 4 shapes moving in circular patterns
  for (int i = 0; i < 4; i++) {
    int       size = 12 + i * 8;  // Each shape gets progressively larger
    uint16_t color = colors[(frame/4 + i * 4) % 16];  // Different color per shape
    
    // Calculate position using trigonometry for circular motion
    int x =      64 + cos(frame * 0.15 + i) * 30;  // Horizontal orbit
    int y = centerY + sin(frame * 0.15 + i) * 20;  // Vertical orbit (smaller)
    
    // Keep shapes on screen (constrain function limits values to range)
    x = constrain(x,               size/2, SCREEN_WIDTH  - size/2);
    y = constrain(y, DRAW_AREA_Y + size/2, SCREEN_HEIGHT - size/2);
    
    // Alternate between rectangles and circles for variety
    if (i % 2 == 0) {
      canvas.fillRect(x - size/2, y - size/2, size, size, color);
    } else {
      canvas.fillCircle(x, y, size/2, color);
    }
  }
  
  frame++;  // Advance animation
  core0State.needsUpdate = true;
}

/**
 * @brief Test 5: Text Display - Typography and text effects
 * @param canvas Graphics canvas to draw on
 * 
 * This test shows:
 * - Text rendering at different sizes
 * - Color-changing text effects
 * - Font sizing and positioning
 * - Multi-line text layouts
 */
void core0_textDisplay(GFXcanvas16& canvas) {
  static int     textFrame = 0;
  static int lastTextFrame = -1;
  
  // Only redraw when text frame changes (every 30 animation frames)
  if (core0State.forceFullRedraw || textFrame != lastTextFrame) {
    uint16_t bgColor = colors[textFrame % 16];  // Cycling background color
    canvas.fillScreen(bgColor);
    
    // Get contrasting text colors
    uint16_t color1 = colors[(textFrame +  5) % 16];
    uint16_t color2 = colors[(textFrame + 10) % 16];
    uint16_t color3 = colors[(textFrame + 15) % 16];
    
    // Large title text
    canvas.setTextColor(color1);
    canvas.setTextSize(3);  // 3x normal size
    canvas.setCursor(15, 40);
    canvas.print("CORE 0");
    
    // Medium subtitle
    canvas.setTextColor(color2);
    canvas.setTextSize(2);  // 2x normal size
    canvas.setCursor(5, 80);
    canvas.print("ADAFRUIT");
    
    // Small descriptive text
    canvas.setTextColor(color3);
    canvas.setTextSize(1);  // Normal size
    canvas.setCursor(10, 110);
    canvas.print("GFX Library");
    
    // Bottom text in white for contrast
    canvas.setTextColor(ST77XX_WHITE);
    canvas.setCursor(25, 130);
    canvas.print("Core1D Auto Labs");
    
    core0State.needsUpdate = true;
    lastTextFrame = textFrame;
    core0State.forceFullRedraw = false;
  }
  
  // Change text colors every 30 frames (0.5 seconds at 60 FPS)
  static int frameCount = 0;
  if (++frameCount >= 30) {
    textFrame++;
    frameCount = 0;
  }
}

// ============================================================================
// CORE 1 TESTS - Advanced Graphics Demonstrations  
// ============================================================================

/**
 * @brief Test 1: Color Cycle - Advanced color transitions with geometry
 * @param canvas Graphics canvas to draw on
 * 
 * This test demonstrates:
 * - Smooth color transitions
 * - Concentric circle patterns
 * - Color theory and complementary colors
 * - Geometric design principles
 */
void core1_colorCycle(GFXcanvas16& canvas) {
  static int colorIndex = 0;
  static int lastColorIndex = -1;
  
  if (core1State.forceFullRedraw || colorIndex != lastColorIndex) {
    canvas.fillScreen(colors[colorIndex % 16]);  // Dynamic background color
    drawTestHeader(canvas, "CORE 1", "Color Cycle");
    
    int centerY = DRAW_AREA_Y + DRAW_AREA_HEIGHT/2;
    
    // Draw concentric circles with mathematically related colors
    // Outer ring - thick circle outline
    canvas.drawCircle(64, centerY, 45, colors[(colorIndex +  4) % 16]);
    canvas.drawCircle(64, centerY, 44, colors[(colorIndex +  4) % 16]);
    
    // Middle ring - complementary color
    canvas.drawCircle(64, centerY, 30, colors[(colorIndex +  8) % 16]);
    canvas.drawCircle(64, centerY, 29, colors[(colorIndex +  8) % 16]);
    
    // Center - filled circle with triadic color
    canvas.fillCircle(64, centerY, 15, colors[(colorIndex + 12) % 16]);
    
    core1State.needsUpdate     = true;
    lastColorIndex             = colorIndex;
    core1State.forceFullRedraw = false;
  }
  
  // Slower color change for dramatic effect
  static int frameCount = 0;
  if (++frameCount >= 40) {
    colorIndex++;
    frameCount = 0;
  }
}

/**
 * @brief Test 2: Moving Lines - Scrolling line patterns
 * @param canvas Graphics canvas to draw on
 * 
 * This test shows:
 * - Continuous scrolling animation
 * - Line drawing functions
 * - Modulo arithmetic for wrapping
 * - Multi-thickness line effects
 */
void core1_movingLines(GFXcanvas16& canvas) {
  static int linePos = 0;  // Scroll position
  
  canvas.fillScreen(ST77XX_BLACK);
  drawTestHeader(canvas, "CORE 1", "Moving Lines");
  
  // Draw multiple scrolling lines with different colors
  for (int i = 0; i < 15; i++) {
    int y = (linePos + i * 10) % DRAW_AREA_HEIGHT + DRAW_AREA_Y;  // Wrap around
    
    // Only draw if line is in visible area
    if (y >= DRAW_AREA_Y && y < SCREEN_HEIGHT - 3) {
      uint16_t color = colors[i % 16];
      // Draw thick lines (3 pixels high) for better visibility
      canvas.drawFastHLine(0,   y, SCREEN_WIDTH, color);
      canvas.drawFastHLine(0, y+1, SCREEN_WIDTH, color);
      canvas.drawFastHLine(0, y+2, SCREEN_WIDTH, color);
    }
  }
  
  linePos += 2;  // Scroll speed: 2 pixels per frame
  core1State.needsUpdate = true;
}

/**
 * @brief Test 3: Color Noise - Mathematical wave effects
 * @param canvas Graphics canvas to draw on
 * 
 * This test demonstrates:
 * - Mathematical functions (sine, cosine) for visual effects
 * - Color noise algorithm (simplified plasma-like effect)
 * - Block-based rendering for performance
 * - Color mapping from mathematical values
 */
void core1_colorNoise(GFXcanvas16& canvas) {
  static int frame = 0;  // Animation time
  
  canvas.fillScreen(ST77XX_BLACK);
  drawTestHeader(canvas, "CORE 1", "Color Noise");
  
  // Use blocks instead of individual pixels for better performance
  const int blockSize = 8;
  
  for (int y = DRAW_AREA_Y; y < SCREEN_HEIGHT; y += blockSize) {
    for (int x = 0; x < SCREEN_WIDTH; x += blockSize) {
      // Color noise formula: combine sine waves at different frequencies
      float value = sin(x * 0.2 + frame * 0.1) + cos(y * 0.2 + frame * 0.15);
      
      // Convert mathematical result (-2 to +2) to color index (0-15)
      uint8_t colorIndex = (int)(value * 4 + 8) % 16;
      canvas.fillRect(x, y, blockSize, blockSize, colors[colorIndex]);
    }
  }
  
  frame++;  // Advance animation time
  core1State.needsUpdate = true;
}

/**
 * @brief Test 4: Pixel Rain - Particle simulation system
 * @param canvas Graphics canvas to draw on
 * 
 * This test shows:
 * - Particle system programming
 * - Array-based object management
 * - Random number generation
 * - Variable-speed animation
 * - Dynamic object recycling
 */
void core1_pixelRain(GFXcanvas16& canvas) {
  // Particle system: array of falling "stars"
  static struct { int x, y, speed; } stars[25];  // 25 falling particles
  static bool initialized = false;
  
  // Initialize star positions on first run
  if (!initialized) {
    for (int i = 0; i < 25; i++) {
      stars[i].x     = random(SCREEN_WIDTH);           // Random horizontal position
      stars[i].y     = random(DRAW_AREA_Y, SCREEN_HEIGHT); // Random vertical start
      stars[i].speed = 1 + (i % 4);                // Speed 1-4 pixels per frame
    }
    initialized = true;
  }
  
  canvas.fillScreen(ST77XX_BLACK);
  drawTestHeader(canvas, "CORE 1", "Pixel Rain");
  
  // Update and draw each star
  for (int i = 0; i < 25; i++) {
    stars[i].y += stars[i].speed;  // Move star down
    
    // Reset star at top when it goes off bottom
    if (stars[i].y >= SCREEN_HEIGHT) {
      stars[i].x     = random(SCREEN_WIDTH);
      stars[i].y     = DRAW_AREA_Y;
      stars[i].speed = 1 + random(4);  // Random new speed
    }
    
    // Choose color based on speed (faster = brighter)
    uint16_t color = (stars[i].speed == 1) ? 0x4208 :      // Dim gray
                     (stars[i].speed == 2) ? 0x8410 :      // Medium gray
                     (stars[i].speed == 3) ? ST77XX_WHITE : // Bright white
                     colors[i % 8];                         // Colored
    
    // Draw star as vertical streak (length based on speed)
    canvas.fillRect(stars[i].x, stars[i].y, 2, stars[i].speed + 1, color);
  }
  
  core1State.needsUpdate = true;
}

/**
 * @brief Test 5: Pattern Test - Dynamic geometric patterns
 * @param canvas Graphics canvas to draw on
 * 
 * This test demonstrates:
 * - Procedural pattern generation
 * - Nested loop algorithms
 * - Boolean logic for pattern creation
 * - Grid-based graphics
 * - Time-based pattern evolution
 */
void core1_patternTest(GFXcanvas16& canvas) {
  static int     patternFrame = 0;
  static int lastPatternFrame = -1;
  
  // Only redraw when pattern changes (every 15 frames for smooth transition)
  if (core1State.forceFullRedraw || patternFrame != lastPatternFrame) {
    canvas.fillScreen(ST77XX_BLACK);
    drawTestHeader(canvas, "CORE 1", "Pattern Test");
    
    // Create dynamic checkerboard with smooth transitions
    const int squareSize = 12;  // Size of each pattern square
    
    for (int y = DRAW_AREA_Y; y < SCREEN_HEIGHT; y += squareSize) {
      for (int x = 0; x < SCREEN_WIDTH; x += squareSize) {
        // Complex boolean logic creates evolving checkerboard
        bool isColored = ((x/squareSize + (y-DRAW_AREA_Y)/squareSize + patternFrame/15) % 2) == 0;
        
        // Choose color: either from palette or dark gray
        uint16_t color = isColored ? colors[(patternFrame/15 + (x+y)/30) % 16] : 0x2104;
        canvas.fillRect(x, y, squareSize, squareSize, color);
      }
    }
    
    core1State.needsUpdate     = true;
    lastPatternFrame           = patternFrame;
    core1State.forceFullRedraw = false;
  }
  
  // Pattern evolution speed
  static int frameCount = 0;
  if (++frameCount >= 15) {
    patternFrame++;
    frameCount = 0;
  }
}

Have a nice week!