Переглянути джерело

Add initial ESP32-S2 GC9A01 firmware

Implements startup logo, GPIO-driven System A/B status display, and
continuous input monitoring using TFT_eSPI with sprite rendering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Benjamin Harris 3 тижнів тому
батько
коміт
90e7b372a1
7 змінених файлів з 345 додано та 0 видалено
  1. 102 0
      CLAUDE.md
  2. 44 0
      User_Setup.h
  3. 40 0
      src/config.h
  4. 69 0
      src/display.cpp
  5. 7 0
      src/display.h
  6. 40 0
      src/logo.h
  7. 43 0
      src/main.ino

+ 102 - 0
CLAUDE.md

@@ -0,0 +1,102 @@
+# CLAUDE.md
+
+## Project Summary
+
+This project uses an ESP32-S2 and a 240x240 round GC9A01 TFT display to indicate which of two systems is currently active.
+
+At startup, a logo is displayed for approximately 10 seconds. After the startup period, the display shows either "SYSTEM A" or "SYSTEM B" based on the state of a GPIO input.
+
+## Development Goals
+
+* Keep firmware simple and maintainable.
+* Minimize memory usage.
+* Avoid unnecessary dependencies.
+* Ensure fast boot and reliable operation.
+* Use Arduino IDE compatibility.
+
+## Hardware
+
+### MCU
+
+ESP32-S2
+
+### Display
+
+* GC9A01
+* 240x240 pixels
+* SPI interface
+
+### Input
+
+Single GPIO input used to determine:
+
+* HIGH = System A
+* LOW = System B
+
+## Preferred Libraries
+
+### Display
+
+* TFT_eSPI
+
+### Image Support
+
+* TFT_eSPI sprite functions
+* PROGMEM image storage
+
+## Display Behaviour
+
+### Startup
+
+* Initialize display.
+* Show startup logo.
+* Hold logo for approximately 10 seconds.
+
+### Normal Operation
+
+Display one of:
+
+SYSTEM A
+
+or
+
+SYSTEM B
+
+The display should update immediately if the input state changes.
+
+## Coding Standards
+
+* Use clear descriptive variable names.
+* Avoid dynamic memory allocation.
+* Store constant strings in flash where practical.
+* Keep functions small and focused.
+* Comment hardware-specific sections.
+
+## File Structure
+
+src/
+├── main.ino
+├── display.cpp
+├── display.h
+├── logo.h
+└── config.h
+
+## Expected Features
+
+### Required
+
+* Display initialization
+* Startup logo
+* GPIO monitoring
+* System A/B status display
+
+### Optional
+
+* Screen dimming
+* Custom fonts
+* Status icons
+* Animated transitions
+
+## Notes
+
+This is an embedded appliance-style device intended for continuous operation in a rack-mounted environment. Reliability and simplicity take priority over additional features.

+ 44 - 0
User_Setup.h

@@ -0,0 +1,44 @@
+// ---------------------------------------------------------------------------
+// TFT_eSPI User_Setup.h for ESP32-S2 + GC9A01 240x240 round TFT
+//
+// INSTRUCTIONS:
+//   Copy this file to your TFT_eSPI library folder, replacing the existing
+//   User_Setup.h, then recompile. Alternatively, place it in the sketch
+//   folder if your TFT_eSPI version supports sketch-local setup files.
+//
+//   Pin numbers MUST match the wiring in src/config.h.
+// ---------------------------------------------------------------------------
+
+// Driver selection
+#define GC9A01_DRIVER
+
+// Resolution
+#define TFT_WIDTH   240
+#define TFT_HEIGHT  240
+
+// SPI pins — update to match your hardware wiring
+#define TFT_MOSI    35
+#define TFT_SCLK    36
+#define TFT_CS      34
+#define TFT_DC      33
+#define TFT_RST     38
+// Backlight is controlled in firmware (PIN_TFT_BL in config.h)
+
+// SPI bus (ESP32-S2 FSPI = SPI2)
+#define USE_FSPI_PORT
+
+// SPI clock frequency
+#define SPI_FREQUENCY       27000000
+#define SPI_READ_FREQUENCY   20000000
+
+// Load built-in fonts
+#define LOAD_GLCD
+#define LOAD_FONT2
+#define LOAD_FONT4
+#define LOAD_FONT6
+#define LOAD_FONT7
+#define LOAD_FONT8
+#define LOAD_GFXFF
+
+// Enable smooth (TrueType) font support
+#define SMOOTH_FONT

+ 40 - 0
src/config.h

@@ -0,0 +1,40 @@
+#pragma once
+
+// ---------------------------------------------------------------------------
+// Hardware pin assignments — update to match your wiring
+// ---------------------------------------------------------------------------
+
+// System select GPIO
+// HIGH = System A active
+// LOW  = System B active
+#define PIN_SYS_SELECT      1
+
+// TFT backlight (active high)
+#define PIN_TFT_BL          21
+
+// ---------------------------------------------------------------------------
+// Display geometry
+// ---------------------------------------------------------------------------
+#define DISPLAY_W           240
+#define DISPLAY_H           240
+#define DISPLAY_CX          120     // centre x
+#define DISPLAY_CY          120     // centre y
+#define DISPLAY_RADIUS      115     // usable inner radius
+
+// ---------------------------------------------------------------------------
+// Timing
+// ---------------------------------------------------------------------------
+#define LOGO_HOLD_MS        10000UL
+#define POLL_INTERVAL_MS    50UL
+
+// ---------------------------------------------------------------------------
+// Colours (RGB565)
+// ---------------------------------------------------------------------------
+#define COL_BG_SYS_A        0x1600  // dark green
+#define COL_BG_SYS_B        0x0010  // dark navy
+#define COL_BG_LOGO         TFT_BLACK
+#define COL_RING            TFT_WHITE
+#define COL_LABEL           TFT_WHITE
+#define COL_LETTER_A        0x07E0  // bright green
+#define COL_LETTER_B        0x5C1F  // cornflower blue
+#define COL_LOGO_ACCENT     0xFEA0  // gold

+ 69 - 0
src/display.cpp

@@ -0,0 +1,69 @@
+#include "display.h"
+#include "config.h"
+#include "logo.h"
+#include <Fonts/FreeSansBold24pt7b.h>
+#include <Fonts/FreeSans9pt7b.h>
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+
+static void drawStatusScreen(TFT_eSPI &tft,
+                              uint16_t bgColour,
+                              uint16_t letterColour,
+                              const char *letter) {
+    TFT_eSprite spr = TFT_eSprite(&tft);
+    spr.createSprite(DISPLAY_W, DISPLAY_H);
+    spr.fillSprite(bgColour);
+
+    // Outer ring
+    spr.drawCircle(DISPLAY_CX, DISPLAY_CY, DISPLAY_RADIUS,     COL_RING);
+    spr.drawCircle(DISPLAY_CX, DISPLAY_CY, DISPLAY_RADIUS - 1, COL_RING);
+
+    // "SYSTEM" label
+    spr.setTextDatum(MC_DATUM);
+    spr.setFreeFont(&FreeSans9pt7b);
+    spr.setTextColor(COL_LABEL, bgColour);
+    spr.drawString("SYSTEM", DISPLAY_CX, 72);
+
+    // Separator line under label
+    spr.drawFastHLine(DISPLAY_CX - 55, 88, 110, COL_LABEL);
+
+    // Large system letter (A or B)
+    spr.setFreeFont(&FreeSansBold24pt7b);
+    spr.setTextColor(letterColour, bgColour);
+    spr.drawString(letter, DISPLAY_CX, 138);
+
+    // Separator line above ACTIVE
+    spr.drawFastHLine(DISPLAY_CX - 55, 164, 110, COL_LABEL);
+
+    // "ACTIVE" label
+    spr.setFreeFont(&FreeSans9pt7b);
+    spr.setTextColor(COL_LABEL, bgColour);
+    spr.drawString("ACTIVE", DISPLAY_CX, 180);
+
+    spr.pushSprite(0, 0);
+    spr.deleteSprite();
+}
+
+// ---------------------------------------------------------------------------
+// Public API
+// ---------------------------------------------------------------------------
+
+void displayInit(TFT_eSPI &tft) {
+    tft.init();
+    tft.setRotation(0);
+    tft.fillScreen(TFT_BLACK);
+}
+
+void displayShowLogo(TFT_eSPI &tft) {
+    drawLogo(tft);
+}
+
+void displayShowSystemA(TFT_eSPI &tft) {
+    drawStatusScreen(tft, COL_BG_SYS_A, COL_LETTER_A, "A");
+}
+
+void displayShowSystemB(TFT_eSPI &tft) {
+    drawStatusScreen(tft, COL_BG_SYS_B, COL_LETTER_B, "B");
+}

+ 7 - 0
src/display.h

@@ -0,0 +1,7 @@
+#pragma once
+#include <TFT_eSPI.h>
+
+void displayInit(TFT_eSPI &tft);
+void displayShowLogo(TFT_eSPI &tft);
+void displayShowSystemA(TFT_eSPI &tft);
+void displayShowSystemB(TFT_eSPI &tft);

+ 40 - 0
src/logo.h

@@ -0,0 +1,40 @@
+#pragma once
+#include <TFT_eSPI.h>
+#include "config.h"
+
+// ---------------------------------------------------------------------------
+// Startup logo rendered via TFT_eSPI sprites.
+// Replace drawLogo() with custom artwork stored in PROGMEM if required.
+// ---------------------------------------------------------------------------
+
+inline void drawLogo(TFT_eSPI &tft) {
+    // Use a full-screen sprite to eliminate flicker during render
+    TFT_eSprite spr = TFT_eSprite(&tft);
+    spr.createSprite(DISPLAY_W, DISPLAY_H);
+    spr.fillSprite(COL_BG_LOGO);
+
+    // Triple gold outer ring
+    spr.drawCircle(DISPLAY_CX, DISPLAY_CY, DISPLAY_RADIUS,     COL_LOGO_ACCENT);
+    spr.drawCircle(DISPLAY_CX, DISPLAY_CY, DISPLAY_RADIUS - 2, COL_LOGO_ACCENT);
+    spr.drawCircle(DISPLAY_CX, DISPLAY_CY, DISPLAY_RADIUS - 4, COL_LOGO_ACCENT);
+
+    // Inner separator ring
+    spr.drawCircle(DISPLAY_CX, DISPLAY_CY, 88, COL_LOGO_ACCENT);
+
+    // Horizontal separator lines (clipped inside outer ring)
+    spr.drawFastHLine(DISPLAY_CX - 82, DISPLAY_CY - 25, 164, COL_LOGO_ACCENT);
+    spr.drawFastHLine(DISPLAY_CX - 82, DISPLAY_CY + 30, 164, COL_LOGO_ACCENT);
+
+    // Primary brand text
+    spr.setTextDatum(MC_DATUM);
+    spr.setTextColor(COL_LOGO_ACCENT, COL_BG_LOGO);
+    spr.drawString("MMC", DISPLAY_CX, DISPLAY_CY - 50, 6);
+
+    // Sub-label text
+    spr.setTextColor(TFT_WHITE, COL_BG_LOGO);
+    spr.drawString("RACK", DISPLAY_CX, DISPLAY_CY + 3, 4);
+    spr.drawString("SYSTEM", DISPLAY_CX, DISPLAY_CY + 33, 4);
+
+    spr.pushSprite(0, 0);
+    spr.deleteSprite();
+}

+ 43 - 0
src/main.ino

@@ -0,0 +1,43 @@
+#include <TFT_eSPI.h>
+#include "config.h"
+#include "display.h"
+
+static TFT_eSPI tft = TFT_eSPI();
+static bool lastSelectState = false;
+
+// ---------------------------------------------------------------------------
+// Show the correct screen for the current input state
+// ---------------------------------------------------------------------------
+static void applyState(bool sysSelect) {
+    if (sysSelect) {
+        displayShowSystemA(tft);
+    } else {
+        displayShowSystemB(tft);
+    }
+}
+
+void setup() {
+    // System select input — internal pull-up so the pin has a defined level
+    // when nothing is connected (defaults to HIGH = System A)
+    pinMode(PIN_SYS_SELECT, INPUT_PULLUP);
+
+    // Backlight on
+    pinMode(PIN_TFT_BL, OUTPUT);
+    digitalWrite(PIN_TFT_BL, HIGH);
+
+    displayInit(tft);
+    displayShowLogo(tft);
+    delay(LOGO_HOLD_MS);
+
+    lastSelectState = digitalRead(PIN_SYS_SELECT);
+    applyState(lastSelectState);
+}
+
+void loop() {
+    bool state = digitalRead(PIN_SYS_SELECT);
+    if (state != lastSelectState) {
+        lastSelectState = state;
+        applyState(state);
+    }
+    delay(POLL_INTERVAL_MS);
+}