添加子目录
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
// ESP32-S3 N16R8 + ILI9341 + XPT2046-compatible touch.
|
||||
// Libraries: Adafruit ILI9341, Adafruit GFX Library, XPT2046_Touchscreen.
|
||||
// LCD: SCK=12 MOSI=11 CS=10 DC=9 RESET=8. LCD SDO disconnected.
|
||||
// Touch: T_CLK=12 T_DIN=11 T_DO=13 T_CS=7 T_IRQ=6 (polled).
|
||||
// SD disconnected. Keep the existing working power/backlight connections.
|
||||
// No startup calibration. Adjust touch mapping constants below if needed.
|
||||
#include <SPI.h>
|
||||
#include <Adafruit_GFX.h>
|
||||
#include <Adafruit_ILI9341.h>
|
||||
#include <XPT2046_Touchscreen.h>
|
||||
#include <math.h>
|
||||
|
||||
Adafruit_ILI9341 tft(&SPI, 9, 10, 8);
|
||||
XPT2046_Touchscreen touch(7);
|
||||
// Small off-screen canvas avoids visible erase/redraw flashes.
|
||||
GFXcanvas16 eyes(280, 112);
|
||||
// Approximate raw range after touch.setRotation(1), not calibrated pixels.
|
||||
constexpr int TOUCH_MIN_X = 200, TOUCH_MAX_X = 3900;
|
||||
constexpr int TOUCH_MIN_Y = 200, TOUCH_MAX_Y = 3900;
|
||||
constexpr bool TOUCH_SWAP_XY = false;
|
||||
constexpr bool TOUCH_FLIP_X = true, TOUCH_FLIP_Y = true;
|
||||
constexpr uint32_t IDLE_AFTER_MS = 15000;
|
||||
uint32_t idleBlinkAt = 0, idleLookAt = 0;
|
||||
bool idle = false, idleLookingAway = false;
|
||||
float gazeX = 0, gazeY = 0, targetX = 0, targetY = 0;
|
||||
uint32_t lastTouch = 0, blinkStart = 0, lastFrame = 0, lastDown = 0;
|
||||
bool looking = false, blinking = false, contact = false;
|
||||
int previousX = 999, previousY = 999, previousOpen = -1;
|
||||
|
||||
void renderEyes(int dx, int dy, int opening) {
|
||||
eyes.fillScreen(ILI9341_BLACK);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
int x = i == 0 ? 72 : 208;
|
||||
eyes.fillRoundRect(x-49, 12, 98, 88, 30, ILI9341_WHITE);
|
||||
eyes.fillCircle(x+dx, 56+dy, 25, ILI9341_CYAN);
|
||||
eyes.fillCircle(x+dx, 56+dy, 14, ILI9341_BLACK);
|
||||
eyes.fillCircle(x+dx-7, 49+dy, 5, ILI9341_WHITE);
|
||||
int cover = (100-opening)*44/100;
|
||||
if (cover > 0) {
|
||||
eyes.fillRect(x-50, 12, 100, cover, ILI9341_BLACK);
|
||||
eyes.fillRect(x-50, 100-cover, 100, cover, ILI9341_BLACK);
|
||||
}
|
||||
if (opening < 7) eyes.fillRoundRect(x-39, 54, 78, 4, 2, ILI9341_CYAN);
|
||||
}
|
||||
tft.drawRGBBitmap(20, 54, eyes.getBuffer(), 280, 112);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
pinMode(10, OUTPUT); digitalWrite(10, HIGH);
|
||||
pinMode(7, OUTPUT); digitalWrite(7, HIGH);
|
||||
pinMode(6, INPUT_PULLUP);
|
||||
SPI.begin(12, 13, 11, 10);
|
||||
tft.begin(10000000);
|
||||
tft.setRotation(1);
|
||||
touch.begin(SPI);
|
||||
touch.setRotation(1);
|
||||
if (!eyes.getBuffer()) {
|
||||
tft.fillScreen(ILI9341_RED);
|
||||
Serial.println("Eye canvas allocation failed.");
|
||||
while (true) delay(1000);
|
||||
}
|
||||
randomSeed(micros());
|
||||
tft.fillScreen(ILI9341_BLACK);
|
||||
tft.setTextColor(ILI9341_CYAN);
|
||||
tft.setTextSize(2);
|
||||
tft.setCursor(46, 207);
|
||||
tft.print("Touch to look!");
|
||||
lastFrame = lastTouch = millis();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint32_t now = millis();
|
||||
if (touch.touched()) {
|
||||
TS_Point p = touch.getPoint();
|
||||
float rawX = TOUCH_SWAP_XY ? p.y : p.x;
|
||||
float rawY = TOUCH_SWAP_XY ? p.x : p.y;
|
||||
float px = constrain((rawX-TOUCH_MIN_X)*319.0f/(TOUCH_MAX_X-TOUCH_MIN_X), 0.0f, 319.0f);
|
||||
if (TOUCH_FLIP_X) px = 319.0f-px;
|
||||
float py = constrain((rawY-TOUCH_MIN_Y)*239.0f/(TOUCH_MAX_Y-TOUCH_MIN_Y), 0.0f, 239.0f);
|
||||
if (TOUCH_FLIP_Y) py = 239.0f-py;
|
||||
idle = false;
|
||||
idleLookingAway = false;
|
||||
if (!contact) { blinking = true; blinkStart = now; }
|
||||
contact = true;
|
||||
lastDown = lastTouch = now;
|
||||
looking = true;
|
||||
targetX = (px-159.5f)/159.5f*20.0f;
|
||||
targetY = (py-119.5f)/119.5f*14.0f;
|
||||
} else if (contact && now-lastDown > 100) {
|
||||
contact = false; // Short release debounce prevents repeated blinks.
|
||||
}
|
||||
if (looking && now-lastTouch >= 3000) {
|
||||
looking = false;
|
||||
targetX = targetY = 0;
|
||||
}
|
||||
// "Unattended" currently means no touch, not camera-based presence detection.
|
||||
if (!contact && !looking && now-lastTouch >= IDLE_AFTER_MS) {
|
||||
if (!idle) {
|
||||
idle = true;
|
||||
idleLookingAway = false;
|
||||
idleBlinkAt = now + random(400, 1600);
|
||||
idleLookAt = now + random(900, 2200);
|
||||
}
|
||||
if (!blinking && int32_t(now-idleBlinkAt) >= 0) {
|
||||
blinking = true;
|
||||
blinkStart = now;
|
||||
idleBlinkAt = now + random(2500, 6500);
|
||||
}
|
||||
if (int32_t(now-idleLookAt) >= 0) {
|
||||
idleLookingAway = !idleLookingAway;
|
||||
if (idleLookingAway) {
|
||||
targetX = random(0, 2) ? random(9, 21) : -random(9, 21);
|
||||
targetY = random(-12, 13);
|
||||
idleLookAt = now + random(800, 2000);
|
||||
} else {
|
||||
targetX = targetY = 0;
|
||||
idleLookAt = now + random(1800, 4500);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (now-lastFrame < 33) { delay(2); return; }
|
||||
float dt = (now-lastFrame)/1000.0f;
|
||||
lastFrame = now;
|
||||
float blend = 1.0f-expf(-12.0f*dt);
|
||||
gazeX += (targetX-gazeX)*blend;
|
||||
gazeY += (targetY-gazeY)*blend;
|
||||
int opening = 100;
|
||||
if (blinking) {
|
||||
uint32_t elapsed = now-blinkStart;
|
||||
if (elapsed < 100) opening = 100-int(elapsed);
|
||||
else if (elapsed < 170) opening = 0;
|
||||
else if (elapsed < 330) opening = int((elapsed-170)*100/160);
|
||||
else blinking = false;
|
||||
}
|
||||
int dx = lroundf(gazeX), dy = lroundf(gazeY);
|
||||
if (dx != previousX || dy != previousY || opening != previousOpen) {
|
||||
renderEyes(dx, dy, opening);
|
||||
previousX = dx; previousY = dy; previousOpen = opening;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user