fix 本地依赖包
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
// An adaption of the "UncannyEyes" sketch (see eye_functions tab)
|
||||
// for the TFT_eSPI library. As written the sketch is for driving
|
||||
// one (240x320 minimum) TFT display, showing 2 eyes. See example
|
||||
// Animated_Eyes_2 for a dual 128x128 TFT display configured sketch.
|
||||
|
||||
// The size of the displayed eye is determined by the screen size and
|
||||
// resolution. The eye image is 128 pixels wide. In humans the palpebral
|
||||
// fissure (open eye) width is about 30mm so a low resolution, large
|
||||
// pixel size display works best to show a scale eye image. Note that
|
||||
// display manufacturers usually quote the diagonal measurement, so a
|
||||
// 128 x 128 1.7" display or 128 x 160 2" display is about right.
|
||||
|
||||
// Configuration settings for the eye, eye style, display count,
|
||||
// chip selects and x offsets can be defined in the sketch "config.h" tab.
|
||||
|
||||
// Performance (frames per second = fps) can be improved by using
|
||||
// DMA (for SPI displays only) on ESP32 and STM32 processors. Use
|
||||
// as high a SPI clock rate as is supported by the display. 27MHz
|
||||
// minimum, some displays can be operated at higher clock rates in
|
||||
// the range 40-80MHz.
|
||||
|
||||
// Single defaultEye performance for different processors
|
||||
// No DMA With DMA
|
||||
// ESP8266 (160MHz CPU) 40MHz SPI 36 fps
|
||||
// ESP32 27MHz SPI 53 fps 85 fps
|
||||
// ESP32 40MHz SPI 67 fps 102 fps
|
||||
// ESP32 80MHz SPI 82 fps 116 fps // Note: Few displays work reliably at 80MHz
|
||||
// STM32F401 55MHz SPI 44 fps 90 fps
|
||||
// STM32F446 55MHz SPI 83 fps 155 fps
|
||||
// STM32F767 55MHz SPI 136 fps 197 fps
|
||||
|
||||
// DMA can be used with RP2040, STM32 and ESP32 processors when the interface
|
||||
// is SPI, uncomment the next line:
|
||||
//#define USE_DMA
|
||||
|
||||
// Load TFT driver library
|
||||
#include <SPI.h>
|
||||
#include <TFT_eSPI.h>
|
||||
TFT_eSPI tft; // A single instance is used for 1 or 2 displays
|
||||
|
||||
// A pixel buffer is used during eye rendering
|
||||
#define BUFFER_SIZE 1024 // 128 to 1024 seems optimum
|
||||
|
||||
#ifdef USE_DMA
|
||||
#define BUFFERS 2 // 2 toggle buffers with DMA
|
||||
#else
|
||||
#define BUFFERS 1 // 1 buffer for no DMA
|
||||
#endif
|
||||
|
||||
uint16_t pbuffer[BUFFERS][BUFFER_SIZE]; // Pixel rendering buffer
|
||||
bool dmaBuf = 0; // DMA buffer selection
|
||||
|
||||
// This struct is populated in config.h
|
||||
typedef struct { // Struct is defined before including config.h --
|
||||
int8_t select; // pin numbers for each eye's screen select line
|
||||
int8_t wink; // and wink button (or -1 if none) specified there,
|
||||
uint8_t rotation; // also display rotation and the x offset
|
||||
int16_t xposition; // position of eye on the screen
|
||||
} eyeInfo_t;
|
||||
|
||||
#include "config.h" // ****** CONFIGURATION IS DONE IN HERE ******
|
||||
|
||||
extern void user_setup(void); // Functions in the user*.cpp files
|
||||
extern void user_loop(void);
|
||||
|
||||
#define SCREEN_X_START 0
|
||||
#define SCREEN_X_END SCREEN_WIDTH // Badly named, actually the "eye" width!
|
||||
#define SCREEN_Y_START 0
|
||||
#define SCREEN_Y_END SCREEN_HEIGHT // Actually "eye" height
|
||||
|
||||
// A simple state machine is used to control eye blinks/winks:
|
||||
#define NOBLINK 0 // Not currently engaged in a blink
|
||||
#define ENBLINK 1 // Eyelid is currently closing
|
||||
#define DEBLINK 2 // Eyelid is currently opening
|
||||
typedef struct {
|
||||
uint8_t state; // NOBLINK/ENBLINK/DEBLINK
|
||||
uint32_t duration; // Duration of blink state (micros)
|
||||
uint32_t startTime; // Time (micros) of last state change
|
||||
} eyeBlink;
|
||||
|
||||
struct { // One-per-eye structure
|
||||
int16_t tft_cs; // Chip select pin for each display
|
||||
eyeBlink blink; // Current blink/wink state
|
||||
int16_t xposition; // x position of eye image
|
||||
} eye[NUM_EYES];
|
||||
|
||||
uint32_t startTime; // For FPS indicator
|
||||
|
||||
// INITIALIZATION -- runs once at startup ----------------------------------
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
//while (!Serial);
|
||||
Serial.println("Starting");
|
||||
|
||||
#if defined(DISPLAY_BACKLIGHT) && (DISPLAY_BACKLIGHT >= 0)
|
||||
// Enable backlight pin, initially off
|
||||
Serial.println("Backlight turned off");
|
||||
pinMode(DISPLAY_BACKLIGHT, OUTPUT);
|
||||
digitalWrite(DISPLAY_BACKLIGHT, LOW);
|
||||
#endif
|
||||
|
||||
// User call for additional features
|
||||
user_setup();
|
||||
|
||||
// Initialise the eye(s), this will set all chip selects low for the tft.init()
|
||||
initEyes();
|
||||
|
||||
// Initialise TFT
|
||||
Serial.println("Initialising displays");
|
||||
tft.init();
|
||||
|
||||
#ifdef USE_DMA
|
||||
tft.initDMA();
|
||||
#endif
|
||||
|
||||
// Raise chip select(s) so that displays can be individually configured
|
||||
digitalWrite(eye[0].tft_cs, HIGH);
|
||||
if (NUM_EYES > 1) digitalWrite(eye[1].tft_cs, HIGH);
|
||||
|
||||
for (uint8_t e = 0; e < NUM_EYES; e++) {
|
||||
digitalWrite(eye[e].tft_cs, LOW);
|
||||
tft.setRotation(eyeInfo[e].rotation);
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
digitalWrite(eye[e].tft_cs, HIGH);
|
||||
}
|
||||
|
||||
#if defined(DISPLAY_BACKLIGHT) && (DISPLAY_BACKLIGHT >= 0)
|
||||
Serial.println("Backlight now on!");
|
||||
analogWrite(DISPLAY_BACKLIGHT, BACKLIGHT_MAX);
|
||||
#endif
|
||||
|
||||
startTime = millis(); // For frame-rate calculation
|
||||
}
|
||||
|
||||
// MAIN LOOP -- runs continuously after setup() ----------------------------
|
||||
void loop() {
|
||||
updateEye();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Pin selections here are based on the original Adafruit Learning System
|
||||
// guide for the Teensy 3.x project. Some of these pin numbers don't even
|
||||
// exist on the smaller SAMD M0 & M4 boards, so you may need to make other
|
||||
// selections:
|
||||
|
||||
// GRAPHICS SETTINGS (appearance of eye) -----------------------------------
|
||||
|
||||
// If using a SINGLE EYE, you might want this next line enabled, which
|
||||
// uses a simpler "football-shaped" eye that's left/right symmetrical.
|
||||
// Default shape includes the caruncle, creating distinct left/right eyes.
|
||||
|
||||
//#define SYMMETRICAL_EYELID
|
||||
|
||||
// Enable ONE of these #includes -- HUGE graphics tables for various eyes:
|
||||
#include "data/defaultEye.h" // Standard human-ish hazel eye -OR-
|
||||
//#include "data/dragonEye.h" // Slit pupil fiery dragon/demon eye -OR-
|
||||
//#include "data/noScleraEye.h" // Large iris, no sclera -OR-
|
||||
//#include "data/goatEye.h" // Horizontal pupil goat/Krampus eye -OR-
|
||||
//#include "data/newtEye.h" // Eye of newt -OR-
|
||||
//#include "data/terminatorEye.h" // Git to da choppah!
|
||||
//#include "data/catEye.h" // Cartoonish cat (flat "2D" colors)
|
||||
//#include "data/owlEye.h" // Minerva the owl (DISABLE TRACKING)
|
||||
//#include "data/naugaEye.h" // Nauga googly eye (DISABLE TRACKING)
|
||||
//#include "data/doeEye.h" // Cartoon deer eye (DISABLE TRACKING)
|
||||
|
||||
// DISPLAY HARDWARE SETTINGS (screen type & connections) -------------------
|
||||
#define TFT_COUNT 1 // Number of screens (1 or 2)
|
||||
#define TFT1_CS -1 // TFT 1 chip select pin (set to -1 to use TFT_eSPI setup)
|
||||
#define TFT2_CS -1 // TFT 2 chip select pin (set to -1 to use TFT_eSPI setup)
|
||||
#define TFT_1_ROT 1 // TFT 1 rotation
|
||||
#define TFT_2_ROT 1 // TFT 2 rotation
|
||||
#define EYE_1_XPOSITION 0 // x shift for eye 1 image on display
|
||||
#define EYE_2_XPOSITION 320 - 128 // x shift for eye 2 image on display
|
||||
|
||||
#define DISPLAY_BACKLIGHT -1 // Pin for backlight control (-1 for none)
|
||||
#define BACKLIGHT_MAX 255
|
||||
|
||||
// EYE LIST ----------------------------------------------------------------
|
||||
#define NUM_EYES 2 // Number of eyes to display (1 or 2)
|
||||
|
||||
#define BLINK_PIN -1 // Pin for manual blink button (BOTH eyes)
|
||||
#define LH_WINK_PIN -1 // Left wink pin (set to -1 for no pin)
|
||||
#define RH_WINK_PIN -1 // Right wink pin (set to -1 for no pin)
|
||||
|
||||
// This table contains ONE LINE PER EYE. The table MUST be present with
|
||||
// this name and contain ONE OR MORE lines. Each line contains THREE items:
|
||||
// a pin number for the corresponding TFT/OLED display's SELECT line, a pin
|
||||
// pin number for that eye's "wink" button (or -1 if not used), a screen
|
||||
// rotation value (0-3) and x position offset for that eye.
|
||||
|
||||
#if (NUM_EYES == 2)
|
||||
eyeInfo_t eyeInfo[] = {
|
||||
{ TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // LEFT EYE chip select and wink pins, rotation and offset
|
||||
{ TFT2_CS, RH_WINK_PIN, TFT_2_ROT, EYE_2_XPOSITION }, // RIGHT EYE chip select and wink pins, rotation and offset
|
||||
};
|
||||
#else
|
||||
eyeInfo_t eyeInfo[] = {
|
||||
{ TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // EYE chip select and wink pins, rotation and offset
|
||||
};
|
||||
#endif
|
||||
|
||||
// INPUT SETTINGS (for controlling eye motion) -----------------------------
|
||||
|
||||
// JOYSTICK_X_PIN and JOYSTICK_Y_PIN specify analog input pins for manually
|
||||
// controlling the eye with an analog joystick. If set to -1 or if not
|
||||
// defined, the eye will move on its own.
|
||||
// IRIS_PIN speficies an analog input pin for a photocell to make pupils
|
||||
// react to light (or potentiometer for manual control). If set to -1 or
|
||||
// if not defined, the pupils will change on their own.
|
||||
// BLINK_PIN specifies an input pin for a button (to ground) that will
|
||||
// make any/all eyes blink. If set to -1 or if not defined, the eyes will
|
||||
// only blink if AUTOBLINK is defined, or if the eyeInfo[] table above
|
||||
// includes wink button settings for each eye.
|
||||
|
||||
//#define JOYSTICK_X_PIN A0 // Analog pin for eye horiz pos (else auto)
|
||||
//#define JOYSTICK_Y_PIN A1 // Analog pin for eye vert position (")
|
||||
//#define JOYSTICK_X_FLIP // If defined, reverse stick X axis
|
||||
//#define JOYSTICK_Y_FLIP // If defined, reverse stick Y axis
|
||||
#define TRACKING // If defined, eyelid tracks pupil
|
||||
#define AUTOBLINK // If defined, eyes also blink autonomously
|
||||
|
||||
// #define LIGHT_PIN -1 // Light sensor pin
|
||||
#define LIGHT_CURVE 0.33 // Light sensor adjustment curve
|
||||
#define LIGHT_MIN 0 // Minimum useful reading from light sensor
|
||||
#define LIGHT_MAX 1023 // Maximum useful reading from sensor
|
||||
|
||||
#define IRIS_SMOOTH // If enabled, filter input from IRIS_PIN
|
||||
#if !defined(IRIS_MIN) // Each eye might have its own MIN/MAX
|
||||
#define IRIS_MIN 90 // Iris size (0-1023) in brightest light
|
||||
#endif
|
||||
#if !defined(IRIS_MAX)
|
||||
#define IRIS_MAX 130 // Iris size (0-1023) in darkest light
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
// Logo helps with screen orientation & positioning
|
||||
|
||||
#define LOGO_TOP_WIDTH 59
|
||||
#define LOGO_TOP_HEIGHT 59
|
||||
|
||||
const uint8_t logo_top[472] PROGMEM= {
|
||||
0X00, 0X00, 0X00, 0X01, 0XC0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03,
|
||||
0XC0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X07, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X0F,
|
||||
0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X0F, 0XF0, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X1F, 0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3F,
|
||||
0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X7F, 0XF8, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X7F, 0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0XFF,
|
||||
0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF, 0XFC, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X01, 0XFF, 0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF,
|
||||
0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XFF, 0XFC, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X03, 0XFF, 0XFE, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0X83, 0XFF,
|
||||
0XFE, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XF3, 0XFF, 0XFE, 0X00, 0X00, 0X00,
|
||||
0XFF, 0XFF, 0XFB, 0XFF, 0XFC, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFF, 0XFF,
|
||||
0XFC, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFF, 0XFB, 0XFC, 0X30, 0X00, 0X00,
|
||||
0X3F, 0XFF, 0XFF, 0XF1, 0XFB, 0XFF, 0X00, 0X00, 0X1F, 0XFF, 0XFF, 0XF1,
|
||||
0XFF, 0XFF, 0XE0, 0X00, 0X1F, 0XFF, 0XFF, 0XE1, 0XFF, 0XFF, 0XFE, 0X00,
|
||||
0X0F, 0XFF, 0XFF, 0XE1, 0XFF, 0XFF, 0XFF, 0X80, 0X07, 0XFF, 0XEF, 0XE1,
|
||||
0XFF, 0XFF, 0XFF, 0XE0, 0X03, 0XFF, 0XC1, 0XE3, 0XFF, 0XFF, 0XFF, 0XE0,
|
||||
0X03, 0XFF, 0XC0, 0XF3, 0XFF, 0XFF, 0XFF, 0XE0, 0X01, 0XFF, 0XF0, 0X7F,
|
||||
0XC3, 0XFF, 0XFF, 0XC0, 0X00, 0XFF, 0XF8, 0X7F, 0X01, 0XFF, 0XFF, 0X00,
|
||||
0X00, 0X7F, 0XFF, 0XFE, 0X03, 0XFF, 0XFE, 0X00, 0X00, 0X1F, 0XFF, 0XFF,
|
||||
0X0F, 0XFF, 0XFC, 0X00, 0X00, 0X07, 0XFF, 0XFF, 0XFF, 0XFF, 0XF0, 0X00,
|
||||
0X00, 0X01, 0XFF, 0X3F, 0XFF, 0XFF, 0XE0, 0X00, 0X00, 0X07, 0XFC, 0X39,
|
||||
0XFF, 0XFF, 0X80, 0X00, 0X00, 0X0F, 0XF8, 0X38, 0XFF, 0XFF, 0X00, 0X00,
|
||||
0X00, 0X1F, 0XF0, 0X78, 0X7F, 0XFC, 0X00, 0X00, 0X00, 0X3F, 0XF0, 0XF8,
|
||||
0X7F, 0X00, 0X00, 0X00, 0X00, 0X3F, 0XF1, 0XFC, 0X7F, 0X80, 0X00, 0X00,
|
||||
0X00, 0X7F, 0XFF, 0XFE, 0X3F, 0XC0, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFE,
|
||||
0X3F, 0XC0, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XE0, 0X00, 0X00,
|
||||
0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XE0, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF,
|
||||
0XFF, 0XE0, 0X00, 0X00, 0X01, 0XFF, 0XFF, 0XBF, 0XFF, 0XE0, 0X00, 0X00,
|
||||
0X01, 0XFF, 0XFF, 0XBF, 0XFF, 0XE0, 0X00, 0X00, 0X01, 0XFF, 0XFF, 0X1F,
|
||||
0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XFE, 0X1F, 0XFF, 0XE0, 0X00, 0X00,
|
||||
0X03, 0XFF, 0XFC, 0X0F, 0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XF0, 0X0F,
|
||||
0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XC0, 0X07, 0XFF, 0XE0, 0X00, 0X00,
|
||||
0X07, 0XFE, 0X00, 0X03, 0XFF, 0XE0, 0X00, 0X00, 0X07, 0XF0, 0X00, 0X01,
|
||||
0XFF, 0XE0, 0X00, 0X00, 0X03, 0X80, 0X00, 0X00, 0X7F, 0XE0, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0X3F, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X0F, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X07, 0XE0, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X00, 0XE0, 0X00, 0X00 };
|
||||
|
||||
#define LOGO_BOTTOM_WIDTH 128
|
||||
#define LOGO_BOTTOM_HEIGHT 37
|
||||
|
||||
const uint8_t logo_bottom[592] PROGMEM= {
|
||||
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X00, 0X7F, 0X00, 0X00, 0X00,
|
||||
0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X00,
|
||||
0XFF, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X3E, 0X00, 0X00, 0X01, 0XFF, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01, 0XFF, 0X00, 0X00, 0X00,
|
||||
0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01,
|
||||
0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00,
|
||||
0X3E, 0X00, 0X00, 0X01, 0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0,
|
||||
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01, 0XF0, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X03, 0XE0, 0X1F, 0XFF, 0X00, 0XFE, 0X3E, 0X07, 0XFF, 0XC1,
|
||||
0XFF, 0X1F, 0X0E, 0X7C, 0X03, 0XE3, 0XE3, 0XFF, 0X3F, 0XFF, 0X81, 0XFF,
|
||||
0XBE, 0X0F, 0XFF, 0XE1, 0XFF, 0X1F, 0X3E, 0X7C, 0X03, 0XE3, 0XE3, 0XFF,
|
||||
0X7F, 0XFF, 0XC3, 0XFF, 0XFE, 0X1F, 0XFF, 0XF1, 0XFF, 0X1F, 0X7E, 0X7C,
|
||||
0X03, 0XE3, 0XE3, 0XFF, 0X7F, 0XFF, 0XC7, 0XFF, 0XFE, 0X1F, 0XFF, 0XF1,
|
||||
0XFF, 0X1F, 0XFE, 0X7C, 0X03, 0XE3, 0XE3, 0XFF, 0X7E, 0X0F, 0XC7, 0XFF,
|
||||
0XFE, 0X1F, 0X83, 0XF1, 0XFF, 0X1F, 0XFE, 0X7C, 0X03, 0XE3, 0XE3, 0XFF,
|
||||
0X7C, 0X07, 0XC7, 0XE0, 0X3E, 0X1F, 0X01, 0XF1, 0XF0, 0X1F, 0XFE, 0X7C,
|
||||
0X03, 0XE3, 0XE3, 0XE0, 0X7C, 0X07, 0XC7, 0XE0, 0X3E, 0X1F, 0X01, 0XF1,
|
||||
0XF0, 0X1F, 0X80, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0X00, 0X07, 0XC7, 0XC0,
|
||||
0X3E, 0X00, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
|
||||
0X00, 0X07, 0XC7, 0XC0, 0X3E, 0X00, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
|
||||
0X03, 0XE3, 0XE3, 0XE0, 0X3F, 0XFF, 0XC7, 0XC0, 0X3E, 0X0F, 0XFF, 0XF1,
|
||||
0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0X7F, 0XFF, 0XC7, 0XC0,
|
||||
0X3E, 0X1F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
|
||||
0XFF, 0XFF, 0XC7, 0XC0, 0X3E, 0X3F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
|
||||
0X03, 0XE3, 0XE3, 0XE0, 0XFC, 0X07, 0XC7, 0XC0, 0X3E, 0X3F, 0X01, 0XF1,
|
||||
0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0XF8, 0X07, 0XC7, 0XC0,
|
||||
0X3E, 0X3E, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
|
||||
0XF8, 0X07, 0XC7, 0XE0, 0X3E, 0X3E, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
|
||||
0X03, 0XE3, 0XE3, 0XE0, 0XF8, 0X07, 0XC7, 0XE0, 0X7E, 0X3E, 0X01, 0XF1,
|
||||
0XF0, 0X1F, 0X00, 0X7E, 0X03, 0XE3, 0XE3, 0XE0, 0XFC, 0X1F, 0XC7, 0XFF,
|
||||
0XFE, 0X3F, 0X07, 0XF1, 0XF0, 0X1F, 0X00, 0X7F, 0XFF, 0XE3, 0XE3, 0XFF,
|
||||
0XFF, 0XFF, 0XC7, 0XFF, 0XFE, 0X3F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7F,
|
||||
0XFF, 0XE3, 0XE3, 0XFF, 0XFF, 0XFF, 0XC3, 0XFF, 0XBE, 0X3F, 0XFF, 0XF1,
|
||||
0XF0, 0X1F, 0X00, 0X7F, 0XFF, 0XE3, 0XE3, 0XFF, 0X7F, 0XE7, 0XC3, 0XFF,
|
||||
0X3E, 0X1F, 0XF9, 0XF1, 0XF0, 0X1F, 0X00, 0X3F, 0XE3, 0XE3, 0XE1, 0XFF,
|
||||
0X1F, 0X87, 0XC0, 0XFC, 0X3E, 0X07, 0XE1, 0XF1, 0XF0, 0X1F, 0X00, 0X0F,
|
||||
0XC1, 0XE3, 0XE0, 0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||
0XB7, 0X63, 0XDD, 0XC6, 0X08, 0X76, 0X1C, 0X7F, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||
0XFF, 0XFF, 0XFF, 0XFF, 0XB3, 0X6D, 0XDD, 0XBB, 0XBB, 0XB6, 0XFB, 0XBF,
|
||||
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XB3, 0X6E, 0XDD, 0XBF,
|
||||
0XBB, 0XB6, 0XFB, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||
0XB5, 0X6E, 0XDD, 0XC7, 0XB8, 0X76, 0X3C, 0X7F, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||
0XFF, 0XFF, 0XFF, 0XFF, 0XB5, 0X6E, 0XDD, 0XFB, 0XBB, 0XB6, 0XFF, 0XBF,
|
||||
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XB6, 0X6D, 0XEB, 0XBB,
|
||||
0XBB, 0XB6, 0XFB, 0XBF };
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,429 @@
|
||||
//
|
||||
// Code adapted by Bodmer as an example for TFT_eSPI, this runs on any
|
||||
// TFT_eSPI compatible processor so ignore the technical limitations
|
||||
// detailed in the original header below. Assorted changes have been
|
||||
// made including removal of the display mirror kludge.
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Uncanny eyes for Adafruit 1.5" OLED (product #1431) or 1.44" TFT LCD
|
||||
// (#2088). Works on PJRC Teensy 3.x and on Adafruit M0 and M4 boards
|
||||
// (Feather, Metro, etc.). This code uses features specific to these
|
||||
// boards and WILL NOT work on normal Arduino or other boards!
|
||||
//
|
||||
// SEE FILE "config.h" FOR MOST CONFIGURATION (graphics, pins, display type,
|
||||
// etc). Probably won't need to edit THIS file unless you're doing some
|
||||
// extremely custom modifications.
|
||||
//
|
||||
// Adafruit invests time and resources providing this open source code,
|
||||
// please support Adafruit and open-source hardware by purchasing products
|
||||
// from Adafruit!
|
||||
//
|
||||
// Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
|
||||
// MIT license. SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library.
|
||||
// Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept.
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
#if !defined(LIGHT_PIN) || (LIGHT_PIN < 0)
|
||||
// Autonomous iris motion uses a fractal behavior to similate both the major
|
||||
// reaction of the eye plus the continuous smaller adjustments that occur.
|
||||
uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris;
|
||||
#endif
|
||||
|
||||
// Initialise eyes ---------------------------------------------------------
|
||||
void initEyes(void)
|
||||
{
|
||||
Serial.println("Initialise eye objects");
|
||||
|
||||
// Initialise eye objects based on eyeInfo list in config.h:
|
||||
for (uint8_t e = 0; e < NUM_EYES; e++) {
|
||||
Serial.print("Create display #"); Serial.println(e);
|
||||
|
||||
eye[e].tft_cs = eyeInfo[e].select;
|
||||
eye[e].blink.state = NOBLINK;
|
||||
eye[e].xposition = eyeInfo[e].xposition;
|
||||
|
||||
pinMode(eye[e].tft_cs, OUTPUT);
|
||||
digitalWrite(eye[e].tft_cs, LOW);
|
||||
|
||||
// Also set up an individual eye-wink pin if defined:
|
||||
if (eyeInfo[e].wink >= 0) pinMode(eyeInfo[e].wink, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
|
||||
pinMode(BLINK_PIN, INPUT_PULLUP); // Ditto for all-eyes blink pin
|
||||
#endif
|
||||
}
|
||||
|
||||
// UPDATE EYE --------------------------------------------------------------
|
||||
void updateEye (void)
|
||||
{
|
||||
#if defined(LIGHT_PIN) && (LIGHT_PIN >= 0) // Interactive iris
|
||||
|
||||
int16_t v = analogRead(LIGHT_PIN); // Raw dial/photocell reading
|
||||
#ifdef LIGHT_PIN_FLIP
|
||||
v = 1023 - v; // Reverse reading from sensor
|
||||
#endif
|
||||
if (v < LIGHT_MIN) v = LIGHT_MIN; // Clamp light sensor range
|
||||
else if (v > LIGHT_MAX) v = LIGHT_MAX;
|
||||
v -= LIGHT_MIN; // 0 to (LIGHT_MAX - LIGHT_MIN)
|
||||
#ifdef LIGHT_CURVE // Apply gamma curve to sensor input?
|
||||
v = (int16_t)(pow((double)v / (double)(LIGHT_MAX - LIGHT_MIN),
|
||||
LIGHT_CURVE) * (double)(LIGHT_MAX - LIGHT_MIN));
|
||||
#endif
|
||||
// And scale to iris range (IRIS_MAX is size at LIGHT_MIN)
|
||||
v = map(v, 0, (LIGHT_MAX - LIGHT_MIN), IRIS_MAX, IRIS_MIN);
|
||||
#ifdef IRIS_SMOOTH // Filter input (gradual motion)
|
||||
static int16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2;
|
||||
irisValue = ((irisValue * 15) + v) / 16;
|
||||
frame(irisValue);
|
||||
#else // Unfiltered (immediate motion)
|
||||
frame(v);
|
||||
#endif // IRIS_SMOOTH
|
||||
|
||||
#else // Autonomous iris scaling -- invoke recursive function
|
||||
|
||||
newIris = random(IRIS_MIN, IRIS_MAX);
|
||||
split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN);
|
||||
oldIris = newIris;
|
||||
|
||||
#endif // LIGHT_PIN
|
||||
}
|
||||
|
||||
// EYE-RENDERING FUNCTION --------------------------------------------------
|
||||
void drawEye( // Renders one eye. Inputs must be pre-clipped & valid.
|
||||
// Use native 32 bit variables where possible as this is 10% faster!
|
||||
uint8_t e, // Eye array index; 0 or 1 for left/right
|
||||
uint32_t iScale, // Scale factor for iris
|
||||
uint32_t scleraX, // First pixel X offset into sclera image
|
||||
uint32_t scleraY, // First pixel Y offset into sclera image
|
||||
uint32_t uT, // Upper eyelid threshold value
|
||||
uint32_t lT) { // Lower eyelid threshold value
|
||||
|
||||
uint32_t screenX, screenY, scleraXsave;
|
||||
int32_t irisX, irisY;
|
||||
uint32_t p, a;
|
||||
uint32_t d;
|
||||
|
||||
uint32_t pixels = 0;
|
||||
|
||||
// Set up raw pixel dump to entire screen. Although such writes can wrap
|
||||
// around automatically from end of rect back to beginning, the region is
|
||||
// reset on each frame here in case of an SPI glitch.
|
||||
digitalWrite(eye[e].tft_cs, LOW);
|
||||
tft.startWrite();
|
||||
tft.setAddrWindow(eye[e].xposition, 0, 128, 128);
|
||||
|
||||
// Now just issue raw 16-bit values for every pixel...
|
||||
|
||||
scleraXsave = scleraX; // Save initial X value to reset on each line
|
||||
irisY = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;
|
||||
|
||||
// Eyelid image is left<>right swapped for two displays
|
||||
uint16_t lidX = 0;
|
||||
uint16_t dlidX = -1;
|
||||
if (e) dlidX = 1;
|
||||
for (screenY = 0; screenY < SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
|
||||
scleraX = scleraXsave;
|
||||
irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
|
||||
if (e) lidX = 0; else lidX = SCREEN_WIDTH - 1;
|
||||
for (screenX = 0; screenX < SCREEN_WIDTH; screenX++, scleraX++, irisX++, lidX += dlidX) {
|
||||
if ((pgm_read_byte(lower + screenY * SCREEN_WIDTH + lidX) <= lT) ||
|
||||
(pgm_read_byte(upper + screenY * SCREEN_WIDTH + lidX) <= uT)) { // Covered by eyelid
|
||||
p = 0;
|
||||
} else if ((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
|
||||
(irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
|
||||
p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX);
|
||||
} else { // Maybe iris...
|
||||
p = pgm_read_word(polar + irisY * IRIS_WIDTH + irisX); // Polar angle/dist
|
||||
d = (iScale * (p & 0x7F)) / 128; // Distance (Y)
|
||||
if (d < IRIS_MAP_HEIGHT) { // Within iris area
|
||||
a = (IRIS_MAP_WIDTH * (p >> 7)) / 512; // Angle (X)
|
||||
p = pgm_read_word(iris + d * IRIS_MAP_WIDTH + a); // Pixel = iris
|
||||
} else { // Not in iris
|
||||
p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX); // Pixel = sclera
|
||||
}
|
||||
}
|
||||
*(&pbuffer[dmaBuf][0] + pixels++) = p >> 8 | p << 8;
|
||||
|
||||
if (pixels >= BUFFER_SIZE) {
|
||||
yield();
|
||||
#ifdef USE_DMA
|
||||
tft.pushPixelsDMA(&pbuffer[dmaBuf][0], pixels);
|
||||
dmaBuf = !dmaBuf;
|
||||
#else
|
||||
tft.pushPixels(pbuffer, pixels);
|
||||
#endif
|
||||
pixels = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pixels) {
|
||||
#ifdef USE_DMA
|
||||
tft.pushPixelsDMA(&pbuffer[dmaBuf][0], pixels);
|
||||
#else
|
||||
tft.pushPixels(pbuffer, pixels);
|
||||
#endif
|
||||
}
|
||||
tft.endWrite();
|
||||
digitalWrite(eye[e].tft_cs, HIGH);
|
||||
}
|
||||
|
||||
// EYE ANIMATION -----------------------------------------------------------
|
||||
|
||||
const uint8_t ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, // T
|
||||
3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, // h
|
||||
11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23, // x
|
||||
24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, // 2
|
||||
40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58, // A
|
||||
60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80, // l
|
||||
81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98, 100, 101, 103, // e
|
||||
104, 106, 107, 109, 110, 112, 113, 115, 116, 118, 119, 121, 122, 124, 125, 127, // c
|
||||
128, 130, 131, 133, 134, 136, 137, 139, 140, 142, 143, 145, 146, 148, 149, 151, // J
|
||||
152, 154, 155, 157, 158, 159, 161, 162, 164, 165, 167, 168, 170, 171, 172, 174, // a
|
||||
175, 177, 178, 179, 181, 182, 183, 185, 186, 188, 189, 190, 192, 193, 194, 195, // c
|
||||
197, 198, 199, 201, 202, 203, 204, 205, 207, 208, 209, 210, 211, 213, 214, 215, // o
|
||||
216, 217, 218, 219, 220, 221, 222, 224, 225, 226, 227, 228, 228, 229, 230, 231, // b
|
||||
232, 233, 234, 235, 236, 237, 237, 238, 239, 240, 240, 241, 242, 243, 243, 244, // s
|
||||
245, 245, 246, 246, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, // o
|
||||
252, 253, 253, 253, 254, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255
|
||||
}; // n
|
||||
|
||||
#ifdef AUTOBLINK
|
||||
uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
|
||||
#endif
|
||||
|
||||
// Process motion for a single frame of left or right eye
|
||||
void frame(uint16_t iScale) // Iris scale (0-1023)
|
||||
{
|
||||
static uint32_t frames = 0; // Used in frame rate calculation
|
||||
static uint8_t eyeIndex = 0; // eye[] array counter
|
||||
int16_t eyeX, eyeY;
|
||||
uint32_t t = micros(); // Time at start of function
|
||||
|
||||
if (!(++frames & 255)) { // Every 256 frames...
|
||||
float elapsed = (millis() - startTime) / 1000.0;
|
||||
if (elapsed) Serial.println((uint16_t)(frames / elapsed)); // Print FPS
|
||||
}
|
||||
|
||||
if (++eyeIndex >= NUM_EYES) eyeIndex = 0; // Cycle through eyes, 1 per call
|
||||
|
||||
// X/Y movement
|
||||
|
||||
#if defined(JOYSTICK_X_PIN) && (JOYSTICK_X_PIN >= 0) && \
|
||||
defined(JOYSTICK_Y_PIN) && (JOYSTICK_Y_PIN >= 0)
|
||||
|
||||
// Read X/Y from joystick, constrain to circle
|
||||
int16_t dx, dy;
|
||||
int32_t d;
|
||||
eyeX = analogRead(JOYSTICK_X_PIN); // Raw (unclipped) X/Y reading
|
||||
eyeY = analogRead(JOYSTICK_Y_PIN);
|
||||
#ifdef JOYSTICK_X_FLIP
|
||||
eyeX = 1023 - eyeX;
|
||||
#endif
|
||||
#ifdef JOYSTICK_Y_FLIP
|
||||
eyeY = 1023 - eyeY;
|
||||
#endif
|
||||
dx = (eyeX * 2) - 1023; // A/D exact center is at 511.5. Scale coords
|
||||
dy = (eyeY * 2) - 1023; // X2 so range is -1023 to +1023 w/center at 0.
|
||||
if ((d = (dx * dx + dy * dy)) > (1023 * 1023)) { // Outside circle
|
||||
d = (int32_t)sqrt((float)d); // Distance from center
|
||||
eyeX = ((dx * 1023 / d) + 1023) / 2; // Clip to circle edge,
|
||||
eyeY = ((dy * 1023 / d) + 1023) / 2; // scale back to 0-1023
|
||||
}
|
||||
|
||||
#else // Autonomous X/Y eye motion
|
||||
// Periodically initiates motion to a new random point, random speed,
|
||||
// holds there for random period until next motion.
|
||||
|
||||
static bool eyeInMotion = false;
|
||||
static int16_t eyeOldX = 512, eyeOldY = 512, eyeNewX = 512, eyeNewY = 512;
|
||||
static uint32_t eyeMoveStartTime = 0L;
|
||||
static int32_t eyeMoveDuration = 0L;
|
||||
|
||||
int32_t dt = t - eyeMoveStartTime; // uS elapsed since last eye event
|
||||
if (eyeInMotion) { // Currently moving?
|
||||
if (dt >= eyeMoveDuration) { // Time up? Destination reached.
|
||||
eyeInMotion = false; // Stop moving
|
||||
eyeMoveDuration = random(3000000); // 0-3 sec stop
|
||||
eyeMoveStartTime = t; // Save initial time of stop
|
||||
eyeX = eyeOldX = eyeNewX; // Save position
|
||||
eyeY = eyeOldY = eyeNewY;
|
||||
} else { // Move time's not yet fully elapsed -- interpolate position
|
||||
int16_t e = ease[255 * dt / eyeMoveDuration] + 1; // Ease curve
|
||||
eyeX = eyeOldX + (((eyeNewX - eyeOldX) * e) / 256); // Interp X
|
||||
eyeY = eyeOldY + (((eyeNewY - eyeOldY) * e) / 256); // and Y
|
||||
}
|
||||
} else { // Eye stopped
|
||||
eyeX = eyeOldX;
|
||||
eyeY = eyeOldY;
|
||||
if (dt > eyeMoveDuration) { // Time up? Begin new move.
|
||||
int16_t dx, dy;
|
||||
uint32_t d;
|
||||
do { // Pick new dest in circle
|
||||
eyeNewX = random(1024);
|
||||
eyeNewY = random(1024);
|
||||
dx = (eyeNewX * 2) - 1023;
|
||||
dy = (eyeNewY * 2) - 1023;
|
||||
} while ((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying
|
||||
eyeMoveDuration = random(72000, 144000); // ~1/14 - ~1/7 sec
|
||||
eyeMoveStartTime = t; // Save initial time of move
|
||||
eyeInMotion = true; // Start move on next frame
|
||||
}
|
||||
}
|
||||
#endif // JOYSTICK_X_PIN etc.
|
||||
|
||||
// Blinking
|
||||
#ifdef AUTOBLINK
|
||||
// Similar to the autonomous eye movement above -- blink start times
|
||||
// and durations are random (within ranges).
|
||||
if ((t - timeOfLastBlink) >= timeToNextBlink) { // Start new blink?
|
||||
timeOfLastBlink = t;
|
||||
uint32_t blinkDuration = random(36000, 72000); // ~1/28 - ~1/14 sec
|
||||
// Set up durations for both eyes (if not already winking)
|
||||
for (uint8_t e = 0; e < NUM_EYES; e++) {
|
||||
if (eye[e].blink.state == NOBLINK) {
|
||||
eye[e].blink.state = ENBLINK;
|
||||
eye[e].blink.startTime = t;
|
||||
eye[e].blink.duration = blinkDuration;
|
||||
}
|
||||
}
|
||||
timeToNextBlink = blinkDuration * 3 + random(4000000);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (eye[eyeIndex].blink.state) { // Eye currently blinking?
|
||||
// Check if current blink state time has elapsed
|
||||
if ((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration) {
|
||||
// Yes -- increment blink state, unless...
|
||||
if ((eye[eyeIndex].blink.state == ENBLINK) && ( // Enblinking and...
|
||||
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
|
||||
(digitalRead(BLINK_PIN) == LOW) || // blink or wink held...
|
||||
#endif
|
||||
((eyeInfo[eyeIndex].wink >= 0) &&
|
||||
digitalRead(eyeInfo[eyeIndex].wink) == LOW) )) {
|
||||
// Don't advance state yet -- eye is held closed instead
|
||||
} else { // No buttons, or other state...
|
||||
if (++eye[eyeIndex].blink.state > DEBLINK) { // Deblinking finished?
|
||||
eye[eyeIndex].blink.state = NOBLINK; // No longer blinking
|
||||
} else { // Advancing from ENBLINK to DEBLINK mode
|
||||
eye[eyeIndex].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
|
||||
eye[eyeIndex].blink.startTime = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Not currently blinking...check buttons!
|
||||
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
|
||||
if (digitalRead(BLINK_PIN) == LOW) {
|
||||
// Manually-initiated blinks have random durations like auto-blink
|
||||
uint32_t blinkDuration = random(36000, 72000);
|
||||
for (uint8_t e = 0; e < NUM_EYES; e++) {
|
||||
if (eye[e].blink.state == NOBLINK) {
|
||||
eye[e].blink.state = ENBLINK;
|
||||
eye[e].blink.startTime = t;
|
||||
eye[e].blink.duration = blinkDuration;
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if ((eyeInfo[eyeIndex].wink >= 0) &&
|
||||
(digitalRead(eyeInfo[eyeIndex].wink) == LOW)) { // Wink!
|
||||
eye[eyeIndex].blink.state = ENBLINK;
|
||||
eye[eyeIndex].blink.startTime = t;
|
||||
eye[eyeIndex].blink.duration = random(45000, 90000);
|
||||
}
|
||||
}
|
||||
|
||||
// Process motion, blinking and iris scale into renderable values
|
||||
|
||||
// Scale eye X/Y positions (0-1023) to pixel units used by drawEye()
|
||||
eyeX = map(eyeX, 0, 1023, 0, SCLERA_WIDTH - 128);
|
||||
eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
|
||||
|
||||
// Horizontal position is offset so that eyes are very slightly crossed
|
||||
// to appear fixated (converged) at a conversational distance. Number
|
||||
// here was extracted from my posterior and not mathematically based.
|
||||
// I suppose one could get all clever with a range sensor, but for now...
|
||||
if (NUM_EYES > 1) {
|
||||
if (eyeIndex == 1) eyeX += 4;
|
||||
else eyeX -= 4;
|
||||
}
|
||||
if (eyeX > (SCLERA_WIDTH - 128)) eyeX = (SCLERA_WIDTH - 128);
|
||||
|
||||
// Eyelids are rendered using a brightness threshold image. This same
|
||||
// map can be used to simplify another problem: making the upper eyelid
|
||||
// track the pupil (eyes tend to open only as much as needed -- e.g. look
|
||||
// down and the upper eyelid drops). Just sample a point in the upper
|
||||
// lid map slightly above the pupil to determine the rendering threshold.
|
||||
static uint8_t uThreshold = 128;
|
||||
uint8_t lThreshold, n;
|
||||
#ifdef TRACKING
|
||||
int16_t sampleX = SCLERA_WIDTH / 2 - (eyeX / 2), // Reduce X influence
|
||||
sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4);
|
||||
// Eyelid is slightly asymmetrical, so two readings are taken, averaged
|
||||
if (sampleY < 0) n = 0;
|
||||
else n = (pgm_read_byte(upper + sampleY * SCREEN_WIDTH + sampleX) +
|
||||
pgm_read_byte(upper + sampleY * SCREEN_WIDTH + (SCREEN_WIDTH - 1 - sampleX))) / 2;
|
||||
uThreshold = (uThreshold * 3 + n) / 4; // Filter/soften motion
|
||||
// Lower eyelid doesn't track the same way, but seems to be pulled upward
|
||||
// by tension from the upper lid.
|
||||
lThreshold = 254 - uThreshold;
|
||||
#else // No tracking -- eyelids full open unless blink modifies them
|
||||
uThreshold = lThreshold = 0;
|
||||
#endif
|
||||
|
||||
// The upper/lower thresholds are then scaled relative to the current
|
||||
// blink position so that blinks work together with pupil tracking.
|
||||
if (eye[eyeIndex].blink.state) { // Eye currently blinking?
|
||||
uint32_t s = (t - eye[eyeIndex].blink.startTime);
|
||||
if (s >= eye[eyeIndex].blink.duration) s = 255; // At or past blink end
|
||||
else s = 255 * s / eye[eyeIndex].blink.duration; // Mid-blink
|
||||
s = (eye[eyeIndex].blink.state == DEBLINK) ? 1 + s : 256 - s;
|
||||
n = (uThreshold * s + 254 * (257 - s)) / 256;
|
||||
lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
|
||||
} else {
|
||||
n = uThreshold;
|
||||
}
|
||||
|
||||
// Pass all the derived values to the eye-rendering function:
|
||||
drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold);
|
||||
|
||||
if (eyeIndex == (NUM_EYES - 1)) {
|
||||
user_loop(); // Call user code after rendering last eye
|
||||
}
|
||||
}
|
||||
|
||||
// AUTONOMOUS IRIS SCALING (if no photocell or dial) -----------------------
|
||||
|
||||
#if !defined(LIGHT_PIN) || (LIGHT_PIN < 0)
|
||||
|
||||
// Autonomous iris motion uses a fractal behavior to similate both the major
|
||||
// reaction of the eye plus the continuous smaller adjustments that occur.
|
||||
|
||||
void split( // Subdivides motion path into two sub-paths w/randimization
|
||||
int16_t startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start
|
||||
int16_t endValue, // Iris scale value at end
|
||||
uint32_t startTime, // micros() at start
|
||||
int32_t duration, // Start-to-end time, in microseconds
|
||||
int16_t range) { // Allowable scale value variance when subdividing
|
||||
|
||||
if (range >= 8) { // Limit subdvision count, because recursion
|
||||
range /= 2; // Split range & time in half for subdivision,
|
||||
duration /= 2; // then pick random center point within range:
|
||||
int16_t midValue = (startValue + endValue - range) / 2 + random(range);
|
||||
uint32_t midTime = startTime + duration;
|
||||
split(startValue, midValue, startTime, duration, range); // First half
|
||||
split(midValue , endValue, midTime , duration, range); // Second half
|
||||
} else { // No more subdivisons, do iris motion...
|
||||
int32_t dt; // Time (micros) since start of motion
|
||||
int16_t v; // Interim value
|
||||
while ((dt = (micros() - startTime)) < duration) {
|
||||
v = startValue + (((endValue - startValue) * dt) / duration);
|
||||
if (v < IRIS_MIN) v = IRIS_MIN; // Clip just in case
|
||||
else if (v > IRIS_MAX) v = IRIS_MAX;
|
||||
frame(v); // Draw frame w/interim iris scale value
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !LIGHT_PIN
|
||||
@@ -0,0 +1,65 @@
|
||||
#if 1 // Change to 0 to disable this code (must enable ONE user*.cpp only!)
|
||||
|
||||
// This file provides a crude way to "drop in" user code to the eyes,
|
||||
// allowing concurrent operations without having to maintain a bunch of
|
||||
// special derivatives of the eye code (which is still undergoing a lot
|
||||
// of development). Just replace the source code contents of THIS TAB ONLY,
|
||||
// compile and upload to board. Shouldn't need to modify other eye code.
|
||||
|
||||
// User globals can go here, recommend declaring as static, e.g.:
|
||||
// static int foo = 42;
|
||||
|
||||
// Called once near the end of the setup() function.
|
||||
void user_setup(void) {
|
||||
}
|
||||
|
||||
// Called periodically during eye animation. This is invoked in the
|
||||
// interval before starting drawing on the last eye so it won't exacerbate
|
||||
// visible tearing in eye rendering.
|
||||
// This function BLOCKS, it does NOT multitask with the eye animation code,
|
||||
// and performance here will have a direct impact on overall refresh rates,
|
||||
// so keep it simple. Avoid loops (e.g. if animating something like a servo
|
||||
// or NeoPixels in response to some trigger) and instead rely on state
|
||||
// machines or similar. Additionally, calls to this function are NOT time-
|
||||
// constant -- eye rendering time can vary frame to frame, so animation or
|
||||
// other over-time operations won't look very good using simple +/-
|
||||
// increments, it's better to use millis() or micros() and work
|
||||
// algebraically with elapsed times instead.
|
||||
void user_loop(void) {
|
||||
/*
|
||||
Suppose we have a global bool "animating" (meaning something is in
|
||||
motion) and global uint32_t's "startTime" (the initial time at which
|
||||
something triggered movement) and "transitionTime" (the total time
|
||||
over which movement should occur, expressed in microseconds).
|
||||
Maybe it's servos, maybe NeoPixels, or something different altogether.
|
||||
This function might resemble something like (pseudocode):
|
||||
|
||||
if(!animating) {
|
||||
Not in motion, check sensor for trigger...
|
||||
if(read some sensor) {
|
||||
Motion is triggered! Record startTime, set transition
|
||||
to 1.5 seconds and set animating flag:
|
||||
startTime = micros();
|
||||
transitionTime = 1500000;
|
||||
animating = true;
|
||||
No motion actually takes place yet, that will begin on
|
||||
the next pass through this function.
|
||||
}
|
||||
} else {
|
||||
Currently in motion, ignore trigger and move things instead...
|
||||
uint32_t elapsed = millis() - startTime;
|
||||
if(elapsed < transitionTime) {
|
||||
Part way through motion...how far along?
|
||||
float ratio = (float)elapsed / (float)transitionTime;
|
||||
Do something here based on ratio, 0.0 = start, 1.0 = end
|
||||
} else {
|
||||
End of motion reached.
|
||||
Take whatever steps here to move into final position (1.0),
|
||||
and then clear the "animating" flag:
|
||||
animating = false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
#endif // 0
|
||||
@@ -0,0 +1,83 @@
|
||||
// SERVO BAT: flapping paper-cutout bat (attached to servo on SERVO_PIN)
|
||||
// triggered by contact-sensitive conductive thread on CAPTOUCH_PIN.
|
||||
// See user.cpp for basics of connecting user code to animated eyes.
|
||||
|
||||
#if 0 // Change to 1 to enable this code (must enable ONE user*.cpp only!)
|
||||
|
||||
#include "Adafruit_FreeTouch.h"
|
||||
#include <Servo.h>
|
||||
|
||||
#define CAPTOUCH_PIN A5 // Capacitive touch pin - attach conductive thread here
|
||||
#define SERVO_PIN 4 // Servo plugged in here
|
||||
|
||||
// Set up capacitive touch button using the FreeTouch library
|
||||
static Adafruit_FreeTouch touch(CAPTOUCH_PIN, OVERSAMPLE_4, RESISTOR_50K, FREQ_MODE_NONE);
|
||||
static long oldState; // Last-read touch value
|
||||
static bool isTouched = false; // When true, bat is flapping
|
||||
static uint32_t touchTime = 0; // millis() time when flapping started
|
||||
static uint32_t touchThreshold;
|
||||
|
||||
Servo servo;
|
||||
|
||||
void user_setup(void) {
|
||||
if (!touch.begin())
|
||||
Serial.println("Cap touch init failed");
|
||||
servo.attach(SERVO_PIN);
|
||||
servo.write(0); // Move servo to idle position
|
||||
servo.detach();
|
||||
|
||||
// Attempt to auto-calibrate the touch threshold
|
||||
// (assumes thread is NOT touched on startup!)
|
||||
touchThreshold = 0;
|
||||
for(int i=0; i<10; i++) {
|
||||
touchThreshold += touch.measure(); // Accumulate 10 readings
|
||||
delay(50);
|
||||
}
|
||||
touchThreshold /= 10; // Average "not touched" value
|
||||
touchThreshold = ((touchThreshold * 127) + 1023) / 128; // Threshold = ~1% toward max
|
||||
|
||||
oldState = touch.measure();
|
||||
}
|
||||
|
||||
#define FLAP_TIME_RISING 900 // 0-to-180 degree servo sweep time, in milliseconds
|
||||
#define FLAP_TIME_FALLING 1200 // 180-to-0 servo sweep time
|
||||
#define FLAP_REPS 3 // Number of times to flap
|
||||
#define FLAP_TIME_PER (FLAP_TIME_RISING + FLAP_TIME_FALLING)
|
||||
#define FLAP_TIME_TOTAL (FLAP_TIME_PER * FLAP_REPS)
|
||||
|
||||
void user_loop(void) {
|
||||
long newState = touch.measure();
|
||||
Serial.println(newState);
|
||||
|
||||
if (isTouched) {
|
||||
uint32_t elapsed = millis() - touchTime;
|
||||
if (elapsed >= FLAP_TIME_TOTAL) { // After all flaps are completed
|
||||
isTouched = false; // Bat goes idle again
|
||||
servo.write(0);
|
||||
servo.detach();
|
||||
} else {
|
||||
elapsed %= FLAP_TIME_PER; // Time within current flap cycle
|
||||
if (elapsed < FLAP_TIME_RISING) { // Over the course of 0 to FLAP_TIME_RISING...
|
||||
servo.write(elapsed * 180 / FLAP_TIME_RISING); // Move 0 to 180 degrees
|
||||
} else { // Over course of FLAP_TIME_FALLING, return to 0
|
||||
servo.write(180 - ((elapsed - FLAP_TIME_RISING) * 180 / FLAP_TIME_FALLING));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Bat is idle...check for capacitive touch...
|
||||
if (newState > touchThreshold && oldState < touchThreshold) {
|
||||
delay(100); // Short delay to debounce
|
||||
newState = touch.measure(); // Verify whether still touched
|
||||
if (newState > touchThreshold) { // It is!
|
||||
isTouched = true; // Start a new flap session
|
||||
touchTime = millis();
|
||||
servo.attach(SERVO_PIN);
|
||||
servo.write(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oldState = newState; // Save cap touch state
|
||||
}
|
||||
|
||||
#endif // 0
|
||||
@@ -0,0 +1,64 @@
|
||||
#if 0 // Change to 1 to enable this code (must enable ONE user*.cpp only!)
|
||||
|
||||
// Christmas demo for eye + NeoPixels. Randomly sets pixels in holiday-themed colors.
|
||||
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
// Pin 8 is the built-in NeoPixels on Circuit Playground Express & Bluetooth.
|
||||
// With a TFT Gizmo attached, you can use A1 or A2 to easily connect a strand.
|
||||
#define LED_PIN 8
|
||||
#define LED_COUNT 10
|
||||
#define LED_BRIGHTNESS 50 // about 1/5 brightness (max = 255)
|
||||
#define TWINKLE_INTERVAL 333 // Every 333 ms (1/3 second), change a pixel
|
||||
#define LIT_PIXELS (LED_COUNT / 3) // Must be LESS than LED_COUNT/2
|
||||
|
||||
Adafruit_NeoPixel pixels(LED_COUNT, LED_PIN);
|
||||
|
||||
|
||||
uint32_t timeOfLastTwinkle = 0; // Used for timing pixel changes
|
||||
uint8_t litPixel[LIT_PIXELS]; // Indices of which pixels are lit
|
||||
uint8_t pixelIndex = LIT_PIXELS; // Index of currently-changing litPixel
|
||||
|
||||
uint32_t colors[] = { 0xFF0000, 0x00FF00, 0xFFFFFF }; // Red, green, white
|
||||
#define NUM_COLORS (sizeof colors / sizeof colors[0])
|
||||
|
||||
void user_setup(void) {
|
||||
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
|
||||
pixels.show(); // Turn OFF all pixels ASAP
|
||||
pixels.setBrightness(LED_BRIGHTNESS);
|
||||
memset(litPixel, 255, sizeof litPixel); // Fill with out-of-range nonsense
|
||||
}
|
||||
|
||||
void user_loop(void) {
|
||||
uint32_t t = millis();
|
||||
|
||||
if((t - timeOfLastTwinkle) >= TWINKLE_INTERVAL) { // Time to update pixels?
|
||||
timeOfLastTwinkle = t;
|
||||
if(++pixelIndex >= LIT_PIXELS) pixelIndex = 0;
|
||||
|
||||
// Pick a NEW pixel that's not currently lit and not adjacent to a lit one.
|
||||
// This just brute-force randomly tries pixels until a valid one is found,
|
||||
// no mathematical cleverness. Should only take a few iterations and won't
|
||||
// significantly slow down the eyes.
|
||||
int newPixel, pixelAfter, pixelBefore;
|
||||
do {
|
||||
newPixel = random(LED_COUNT);
|
||||
pixelAfter = (newPixel + 1) % LED_COUNT;
|
||||
pixelBefore = (newPixel - 1);
|
||||
if(pixelBefore < 0) pixelBefore = LED_COUNT - 1;
|
||||
} while(pixels.getPixelColor(newPixel) ||
|
||||
pixels.getPixelColor(pixelAfter) ||
|
||||
pixels.getPixelColor(pixelBefore));
|
||||
|
||||
// Turn OFF litPixel[pixelIndex]
|
||||
pixels.setPixelColor(litPixel[pixelIndex], 0);
|
||||
// 'newPixel' is the winner. Save in the litPixel[] array for later...
|
||||
litPixel[pixelIndex] = newPixel;
|
||||
// Turn ON newPixel with a random color from the colors[] list.
|
||||
pixels.setPixelColor(newPixel, colors[random(NUM_COLORS)]);
|
||||
|
||||
pixels.show();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // 0
|
||||
@@ -0,0 +1,146 @@
|
||||
// An adaption of the "UncannyEyes" sketch (see eye_functions tab)
|
||||
// for the TFT_eSPI library. As written the sketch is for driving
|
||||
// two TFT displays.
|
||||
|
||||
// The size of the displayed eye is determined by the screen size and
|
||||
// resolution. The eye image is 128 pixels wide. In humans the palpebral
|
||||
// fissure (open eye) width is about 30mm so a low resolution, large
|
||||
// pixel size display works best to show a scale eye image. Note that
|
||||
// display manufacturers usually quote the diagonal measurement, so a
|
||||
// 128 x 128 1.7" display or 128 x 160 2" display is about right.
|
||||
|
||||
// The number of displays and chip selects used are defined in the
|
||||
// config.h tab. The display count can be set to 1. If using one
|
||||
// TFT and the chip select for that display is already defined in
|
||||
// the TFT_eSPI library then change the chip select pins to -1 in the
|
||||
// "config.h" tab.
|
||||
|
||||
// The wiring for 2 TFT displays to an ESP32 is described in the
|
||||
// "wiring" tab of this sketch.
|
||||
|
||||
// Configuration settings for the eye, eye style, display count,
|
||||
// chip selects and x offsets can be defined in the sketch "config.h" tab.
|
||||
|
||||
// Performance (frames per second = fps) can be improved by using
|
||||
// DMA (for SPI displays only) on ESP32 and STM32 processors. Use
|
||||
// as high a SPI clock rate as is supported by the display. 27MHz
|
||||
// minimum, some displays can be operated at higher clock rates in
|
||||
// the range 40-80MHz.
|
||||
|
||||
// Single defaultEye performance for different processors
|
||||
// No DMA With DMA
|
||||
// ESP8266 (160MHz CPU) 40MHz SPI 36 fps
|
||||
// ESP32 27MHz SPI 53 fps 85 fps
|
||||
// ESP32 40MHz SPI 67 fps 102 fps
|
||||
// ESP32 80MHz SPI 82 fps 116 fps // Note: Few displays work reliably at 80MHz
|
||||
// STM32F401 55MHz SPI 44 fps 90 fps
|
||||
// STM32F446 55MHz SPI 83 fps 155 fps
|
||||
// STM32F767 55MHz SPI 136 fps 197 fps
|
||||
|
||||
// DMA can be used with RP2040, STM32 and ESP32 processors when the interface
|
||||
// is SPI, uncomment the next line:
|
||||
//#define USE_DMA
|
||||
|
||||
// Load TFT driver library
|
||||
#include <SPI.h>
|
||||
#include <TFT_eSPI.h>
|
||||
TFT_eSPI tft; // A single instance is used for 1 or 2 displays
|
||||
|
||||
// A pixel buffer is used during eye rendering
|
||||
#define BUFFER_SIZE 1024 // 128 to 1024 seems optimum
|
||||
|
||||
#ifdef USE_DMA
|
||||
#define BUFFERS 2 // 2 toggle buffers with DMA
|
||||
#else
|
||||
#define BUFFERS 1 // 1 buffer for no DMA
|
||||
#endif
|
||||
|
||||
uint16_t pbuffer[BUFFERS][BUFFER_SIZE]; // Pixel rendering buffer
|
||||
bool dmaBuf = 0; // DMA buffer selection
|
||||
|
||||
// This struct is populated in config.h
|
||||
typedef struct { // Struct is defined before including config.h --
|
||||
int8_t select; // pin numbers for each eye's screen select line
|
||||
int8_t wink; // and wink button (or -1 if none) specified there,
|
||||
uint8_t rotation; // also display rotation and the x offset
|
||||
int16_t xposition; // position of eye on the screen
|
||||
} eyeInfo_t;
|
||||
|
||||
#include "config.h" // ****** CONFIGURATION IS DONE IN HERE ******
|
||||
|
||||
extern void user_setup(void); // Functions in the user*.cpp files
|
||||
extern void user_loop(void);
|
||||
|
||||
#define SCREEN_X_START 0
|
||||
#define SCREEN_X_END SCREEN_WIDTH // Badly named, actually the "eye" width!
|
||||
#define SCREEN_Y_START 0
|
||||
#define SCREEN_Y_END SCREEN_HEIGHT // Actually "eye" height
|
||||
|
||||
// A simple state machine is used to control eye blinks/winks:
|
||||
#define NOBLINK 0 // Not currently engaged in a blink
|
||||
#define ENBLINK 1 // Eyelid is currently closing
|
||||
#define DEBLINK 2 // Eyelid is currently opening
|
||||
typedef struct {
|
||||
uint8_t state; // NOBLINK/ENBLINK/DEBLINK
|
||||
uint32_t duration; // Duration of blink state (micros)
|
||||
uint32_t startTime; // Time (micros) of last state change
|
||||
} eyeBlink;
|
||||
|
||||
struct { // One-per-eye structure
|
||||
int16_t tft_cs; // Chip select pin for each display
|
||||
eyeBlink blink; // Current blink/wink state
|
||||
int16_t xposition; // x position of eye image
|
||||
} eye[NUM_EYES];
|
||||
|
||||
uint32_t startTime; // For FPS indicator
|
||||
|
||||
// INITIALIZATION -- runs once at startup ----------------------------------
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
//while (!Serial);
|
||||
Serial.println("Starting");
|
||||
|
||||
#if defined(DISPLAY_BACKLIGHT) && (DISPLAY_BACKLIGHT >= 0)
|
||||
// Enable backlight pin, initially off
|
||||
Serial.println("Backlight turned off");
|
||||
pinMode(DISPLAY_BACKLIGHT, OUTPUT);
|
||||
digitalWrite(DISPLAY_BACKLIGHT, LOW);
|
||||
#endif
|
||||
|
||||
// User call for additional features
|
||||
user_setup();
|
||||
|
||||
// Initialise the eye(s), this will set all chip selects low for the tft.init()
|
||||
initEyes();
|
||||
|
||||
// Initialise TFT
|
||||
Serial.println("Initialising displays");
|
||||
tft.init();
|
||||
|
||||
#ifdef USE_DMA
|
||||
tft.initDMA();
|
||||
#endif
|
||||
|
||||
// Raise chip select(s) so that displays can be individually configured
|
||||
digitalWrite(eye[0].tft_cs, HIGH);
|
||||
if (NUM_EYES > 1) digitalWrite(eye[1].tft_cs, HIGH);
|
||||
|
||||
for (uint8_t e = 0; e < NUM_EYES; e++) {
|
||||
digitalWrite(eye[e].tft_cs, LOW);
|
||||
tft.setRotation(eyeInfo[e].rotation);
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
digitalWrite(eye[e].tft_cs, HIGH);
|
||||
}
|
||||
|
||||
#if defined(DISPLAY_BACKLIGHT) && (DISPLAY_BACKLIGHT >= 0)
|
||||
Serial.println("Backlight now on!");
|
||||
analogWrite(DISPLAY_BACKLIGHT, BACKLIGHT_MAX);
|
||||
#endif
|
||||
|
||||
startTime = millis(); // For frame-rate calculation
|
||||
}
|
||||
|
||||
// MAIN LOOP -- runs continuously after setup() ----------------------------
|
||||
void loop() {
|
||||
updateEye();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Pin selections here are based on the original Adafruit Learning System
|
||||
// guide for the Teensy 3.x project. Some of these pin numbers don't even
|
||||
// exist on the smaller SAMD M0 & M4 boards, so you may need to make other
|
||||
// selections:
|
||||
|
||||
// GRAPHICS SETTINGS (appearance of eye) -----------------------------------
|
||||
|
||||
// If using a SINGLE EYE, you might want this next line enabled, which
|
||||
// uses a simpler "football-shaped" eye that's left/right symmetrical.
|
||||
// Default shape includes the caruncle, creating distinct left/right eyes.
|
||||
|
||||
//#define SYMMETRICAL_EYELID
|
||||
|
||||
// Enable ONE of these #includes -- HUGE graphics tables for various eyes:
|
||||
#include "data/defaultEye.h" // Standard human-ish hazel eye -OR-
|
||||
//#include "data/dragonEye.h" // Slit pupil fiery dragon/demon eye -OR-
|
||||
//#include "data/noScleraEye.h" // Large iris, no sclera -OR-
|
||||
//#include "data/goatEye.h" // Horizontal pupil goat/Krampus eye -OR-
|
||||
//#include "data/newtEye.h" // Eye of newt -OR-
|
||||
//#include "data/terminatorEye.h" // Git to da choppah!
|
||||
//#include "data/catEye.h" // Cartoonish cat (flat "2D" colors)
|
||||
//#include "data/owlEye.h" // Minerva the owl (DISABLE TRACKING)
|
||||
//#include "data/naugaEye.h" // Nauga googly eye (DISABLE TRACKING)
|
||||
//#include "data/doeEye.h" // Cartoon deer eye (DISABLE TRACKING)
|
||||
|
||||
// DISPLAY HARDWARE SETTINGS (screen type & connections) -------------------
|
||||
#define TFT_COUNT 2 // Number of screens (1 or 2)
|
||||
#define TFT1_CS 22 // TFT 1 chip select pin (set to -1 to use TFT_eSPI setup)
|
||||
#define TFT2_CS 21 // TFT 2 chip select pin (set to -1 to use TFT_eSPI setup)
|
||||
#define TFT_1_ROT 1 // TFT 1 rotation
|
||||
#define TFT_2_ROT 3 // TFT 2 rotation
|
||||
#define EYE_1_XPOSITION 0 // x shift for eye 1 image on display
|
||||
#define EYE_2_XPOSITION 0 // x shift for eye 2 image on display
|
||||
|
||||
#define DISPLAY_BACKLIGHT -1 // Pin for backlight control (-1 for none)
|
||||
#define BACKLIGHT_MAX 255
|
||||
|
||||
// EYE LIST ----------------------------------------------------------------
|
||||
#define NUM_EYES 2 // Number of eyes to display (1 or 2)
|
||||
|
||||
#define BLINK_PIN -1 // Pin for manual blink button (BOTH eyes)
|
||||
#define LH_WINK_PIN -1 // Left wink pin (set to -1 for no pin)
|
||||
#define RH_WINK_PIN -1 // Right wink pin (set to -1 for no pin)
|
||||
|
||||
// This table contains ONE LINE PER EYE. The table MUST be present with
|
||||
// this name and contain ONE OR MORE lines. Each line contains THREE items:
|
||||
// a pin number for the corresponding TFT/OLED display's SELECT line, a pin
|
||||
// pin number for that eye's "wink" button (or -1 if not used), a screen
|
||||
// rotation value (0-3) and x position offset for that eye.
|
||||
|
||||
#if (NUM_EYES == 2)
|
||||
eyeInfo_t eyeInfo[] = {
|
||||
{ TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // LEFT EYE chip select and wink pins, rotation and offset
|
||||
{ TFT2_CS, RH_WINK_PIN, TFT_2_ROT, EYE_2_XPOSITION }, // RIGHT EYE chip select and wink pins, rotation and offset
|
||||
};
|
||||
#else
|
||||
eyeInfo_t eyeInfo[] = {
|
||||
{ TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // EYE chip select and wink pins, rotation and offset
|
||||
};
|
||||
#endif
|
||||
|
||||
// INPUT SETTINGS (for controlling eye motion) -----------------------------
|
||||
|
||||
// JOYSTICK_X_PIN and JOYSTICK_Y_PIN specify analog input pins for manually
|
||||
// controlling the eye with an analog joystick. If set to -1 or if not
|
||||
// defined, the eye will move on its own.
|
||||
// IRIS_PIN speficies an analog input pin for a photocell to make pupils
|
||||
// react to light (or potentiometer for manual control). If set to -1 or
|
||||
// if not defined, the pupils will change on their own.
|
||||
// BLINK_PIN specifies an input pin for a button (to ground) that will
|
||||
// make any/all eyes blink. If set to -1 or if not defined, the eyes will
|
||||
// only blink if AUTOBLINK is defined, or if the eyeInfo[] table above
|
||||
// includes wink button settings for each eye.
|
||||
|
||||
//#define JOYSTICK_X_PIN A0 // Analog pin for eye horiz pos (else auto)
|
||||
//#define JOYSTICK_Y_PIN A1 // Analog pin for eye vert position (")
|
||||
//#define JOYSTICK_X_FLIP // If defined, reverse stick X axis
|
||||
//#define JOYSTICK_Y_FLIP // If defined, reverse stick Y axis
|
||||
#define TRACKING // If defined, eyelid tracks pupil
|
||||
#define AUTOBLINK // If defined, eyes also blink autonomously
|
||||
|
||||
// #define LIGHT_PIN -1 // Light sensor pin
|
||||
#define LIGHT_CURVE 0.33 // Light sensor adjustment curve
|
||||
#define LIGHT_MIN 0 // Minimum useful reading from light sensor
|
||||
#define LIGHT_MAX 1023 // Maximum useful reading from sensor
|
||||
|
||||
#define IRIS_SMOOTH // If enabled, filter input from IRIS_PIN
|
||||
#if !defined(IRIS_MIN) // Each eye might have its own MIN/MAX
|
||||
#define IRIS_MIN 90 // Iris size (0-1023) in brightest light
|
||||
#endif
|
||||
#if !defined(IRIS_MAX)
|
||||
#define IRIS_MAX 130 // Iris size (0-1023) in darkest light
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
// Logo helps with screen orientation & positioning
|
||||
|
||||
#define LOGO_TOP_WIDTH 59
|
||||
#define LOGO_TOP_HEIGHT 59
|
||||
|
||||
const uint8_t logo_top[472] PROGMEM= {
|
||||
0X00, 0X00, 0X00, 0X01, 0XC0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03,
|
||||
0XC0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X07, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X0F,
|
||||
0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X0F, 0XF0, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X1F, 0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3F,
|
||||
0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X7F, 0XF8, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X7F, 0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0XFF,
|
||||
0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF, 0XFC, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X01, 0XFF, 0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF,
|
||||
0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XFF, 0XFC, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X03, 0XFF, 0XFE, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0X83, 0XFF,
|
||||
0XFE, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XF3, 0XFF, 0XFE, 0X00, 0X00, 0X00,
|
||||
0XFF, 0XFF, 0XFB, 0XFF, 0XFC, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFF, 0XFF,
|
||||
0XFC, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFF, 0XFB, 0XFC, 0X30, 0X00, 0X00,
|
||||
0X3F, 0XFF, 0XFF, 0XF1, 0XFB, 0XFF, 0X00, 0X00, 0X1F, 0XFF, 0XFF, 0XF1,
|
||||
0XFF, 0XFF, 0XE0, 0X00, 0X1F, 0XFF, 0XFF, 0XE1, 0XFF, 0XFF, 0XFE, 0X00,
|
||||
0X0F, 0XFF, 0XFF, 0XE1, 0XFF, 0XFF, 0XFF, 0X80, 0X07, 0XFF, 0XEF, 0XE1,
|
||||
0XFF, 0XFF, 0XFF, 0XE0, 0X03, 0XFF, 0XC1, 0XE3, 0XFF, 0XFF, 0XFF, 0XE0,
|
||||
0X03, 0XFF, 0XC0, 0XF3, 0XFF, 0XFF, 0XFF, 0XE0, 0X01, 0XFF, 0XF0, 0X7F,
|
||||
0XC3, 0XFF, 0XFF, 0XC0, 0X00, 0XFF, 0XF8, 0X7F, 0X01, 0XFF, 0XFF, 0X00,
|
||||
0X00, 0X7F, 0XFF, 0XFE, 0X03, 0XFF, 0XFE, 0X00, 0X00, 0X1F, 0XFF, 0XFF,
|
||||
0X0F, 0XFF, 0XFC, 0X00, 0X00, 0X07, 0XFF, 0XFF, 0XFF, 0XFF, 0XF0, 0X00,
|
||||
0X00, 0X01, 0XFF, 0X3F, 0XFF, 0XFF, 0XE0, 0X00, 0X00, 0X07, 0XFC, 0X39,
|
||||
0XFF, 0XFF, 0X80, 0X00, 0X00, 0X0F, 0XF8, 0X38, 0XFF, 0XFF, 0X00, 0X00,
|
||||
0X00, 0X1F, 0XF0, 0X78, 0X7F, 0XFC, 0X00, 0X00, 0X00, 0X3F, 0XF0, 0XF8,
|
||||
0X7F, 0X00, 0X00, 0X00, 0X00, 0X3F, 0XF1, 0XFC, 0X7F, 0X80, 0X00, 0X00,
|
||||
0X00, 0X7F, 0XFF, 0XFE, 0X3F, 0XC0, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFE,
|
||||
0X3F, 0XC0, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XE0, 0X00, 0X00,
|
||||
0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XE0, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF,
|
||||
0XFF, 0XE0, 0X00, 0X00, 0X01, 0XFF, 0XFF, 0XBF, 0XFF, 0XE0, 0X00, 0X00,
|
||||
0X01, 0XFF, 0XFF, 0XBF, 0XFF, 0XE0, 0X00, 0X00, 0X01, 0XFF, 0XFF, 0X1F,
|
||||
0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XFE, 0X1F, 0XFF, 0XE0, 0X00, 0X00,
|
||||
0X03, 0XFF, 0XFC, 0X0F, 0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XF0, 0X0F,
|
||||
0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XC0, 0X07, 0XFF, 0XE0, 0X00, 0X00,
|
||||
0X07, 0XFE, 0X00, 0X03, 0XFF, 0XE0, 0X00, 0X00, 0X07, 0XF0, 0X00, 0X01,
|
||||
0XFF, 0XE0, 0X00, 0X00, 0X03, 0X80, 0X00, 0X00, 0X7F, 0XE0, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0X3F, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X0F, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X07, 0XE0, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X00, 0XE0, 0X00, 0X00 };
|
||||
|
||||
#define LOGO_BOTTOM_WIDTH 128
|
||||
#define LOGO_BOTTOM_HEIGHT 37
|
||||
|
||||
const uint8_t logo_bottom[592] PROGMEM= {
|
||||
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X00, 0X7F, 0X00, 0X00, 0X00,
|
||||
0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X00,
|
||||
0XFF, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X3E, 0X00, 0X00, 0X01, 0XFF, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01, 0XFF, 0X00, 0X00, 0X00,
|
||||
0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01,
|
||||
0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00,
|
||||
0X3E, 0X00, 0X00, 0X01, 0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0,
|
||||
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01, 0XF0, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X03, 0XE0, 0X1F, 0XFF, 0X00, 0XFE, 0X3E, 0X07, 0XFF, 0XC1,
|
||||
0XFF, 0X1F, 0X0E, 0X7C, 0X03, 0XE3, 0XE3, 0XFF, 0X3F, 0XFF, 0X81, 0XFF,
|
||||
0XBE, 0X0F, 0XFF, 0XE1, 0XFF, 0X1F, 0X3E, 0X7C, 0X03, 0XE3, 0XE3, 0XFF,
|
||||
0X7F, 0XFF, 0XC3, 0XFF, 0XFE, 0X1F, 0XFF, 0XF1, 0XFF, 0X1F, 0X7E, 0X7C,
|
||||
0X03, 0XE3, 0XE3, 0XFF, 0X7F, 0XFF, 0XC7, 0XFF, 0XFE, 0X1F, 0XFF, 0XF1,
|
||||
0XFF, 0X1F, 0XFE, 0X7C, 0X03, 0XE3, 0XE3, 0XFF, 0X7E, 0X0F, 0XC7, 0XFF,
|
||||
0XFE, 0X1F, 0X83, 0XF1, 0XFF, 0X1F, 0XFE, 0X7C, 0X03, 0XE3, 0XE3, 0XFF,
|
||||
0X7C, 0X07, 0XC7, 0XE0, 0X3E, 0X1F, 0X01, 0XF1, 0XF0, 0X1F, 0XFE, 0X7C,
|
||||
0X03, 0XE3, 0XE3, 0XE0, 0X7C, 0X07, 0XC7, 0XE0, 0X3E, 0X1F, 0X01, 0XF1,
|
||||
0XF0, 0X1F, 0X80, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0X00, 0X07, 0XC7, 0XC0,
|
||||
0X3E, 0X00, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
|
||||
0X00, 0X07, 0XC7, 0XC0, 0X3E, 0X00, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
|
||||
0X03, 0XE3, 0XE3, 0XE0, 0X3F, 0XFF, 0XC7, 0XC0, 0X3E, 0X0F, 0XFF, 0XF1,
|
||||
0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0X7F, 0XFF, 0XC7, 0XC0,
|
||||
0X3E, 0X1F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
|
||||
0XFF, 0XFF, 0XC7, 0XC0, 0X3E, 0X3F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
|
||||
0X03, 0XE3, 0XE3, 0XE0, 0XFC, 0X07, 0XC7, 0XC0, 0X3E, 0X3F, 0X01, 0XF1,
|
||||
0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0XF8, 0X07, 0XC7, 0XC0,
|
||||
0X3E, 0X3E, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
|
||||
0XF8, 0X07, 0XC7, 0XE0, 0X3E, 0X3E, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
|
||||
0X03, 0XE3, 0XE3, 0XE0, 0XF8, 0X07, 0XC7, 0XE0, 0X7E, 0X3E, 0X01, 0XF1,
|
||||
0XF0, 0X1F, 0X00, 0X7E, 0X03, 0XE3, 0XE3, 0XE0, 0XFC, 0X1F, 0XC7, 0XFF,
|
||||
0XFE, 0X3F, 0X07, 0XF1, 0XF0, 0X1F, 0X00, 0X7F, 0XFF, 0XE3, 0XE3, 0XFF,
|
||||
0XFF, 0XFF, 0XC7, 0XFF, 0XFE, 0X3F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7F,
|
||||
0XFF, 0XE3, 0XE3, 0XFF, 0XFF, 0XFF, 0XC3, 0XFF, 0XBE, 0X3F, 0XFF, 0XF1,
|
||||
0XF0, 0X1F, 0X00, 0X7F, 0XFF, 0XE3, 0XE3, 0XFF, 0X7F, 0XE7, 0XC3, 0XFF,
|
||||
0X3E, 0X1F, 0XF9, 0XF1, 0XF0, 0X1F, 0X00, 0X3F, 0XE3, 0XE3, 0XE1, 0XFF,
|
||||
0X1F, 0X87, 0XC0, 0XFC, 0X3E, 0X07, 0XE1, 0XF1, 0XF0, 0X1F, 0X00, 0X0F,
|
||||
0XC1, 0XE3, 0XE0, 0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||
0X00, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||
0XB7, 0X63, 0XDD, 0XC6, 0X08, 0X76, 0X1C, 0X7F, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||
0XFF, 0XFF, 0XFF, 0XFF, 0XB3, 0X6D, 0XDD, 0XBB, 0XBB, 0XB6, 0XFB, 0XBF,
|
||||
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XB3, 0X6E, 0XDD, 0XBF,
|
||||
0XBB, 0XB6, 0XFB, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||
0XB5, 0X6E, 0XDD, 0XC7, 0XB8, 0X76, 0X3C, 0X7F, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||
0XFF, 0XFF, 0XFF, 0XFF, 0XB5, 0X6E, 0XDD, 0XFB, 0XBB, 0XB6, 0XFF, 0XBF,
|
||||
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XB6, 0X6D, 0XEB, 0XBB,
|
||||
0XBB, 0XB6, 0XFB, 0XBF };
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,429 @@
|
||||
//
|
||||
// Code adapted by Bodmer as an example for TFT_eSPI, this runs on any
|
||||
// TFT_eSPI compatible processor so ignore the technical limitations
|
||||
// detailed in the original header below. Assorted changes have been
|
||||
// made including removal of the display mirror kludge.
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Uncanny eyes for Adafruit 1.5" OLED (product #1431) or 1.44" TFT LCD
|
||||
// (#2088). Works on PJRC Teensy 3.x and on Adafruit M0 and M4 boards
|
||||
// (Feather, Metro, etc.). This code uses features specific to these
|
||||
// boards and WILL NOT work on normal Arduino or other boards!
|
||||
//
|
||||
// SEE FILE "config.h" FOR MOST CONFIGURATION (graphics, pins, display type,
|
||||
// etc). Probably won't need to edit THIS file unless you're doing some
|
||||
// extremely custom modifications.
|
||||
//
|
||||
// Adafruit invests time and resources providing this open source code,
|
||||
// please support Adafruit and open-source hardware by purchasing products
|
||||
// from Adafruit!
|
||||
//
|
||||
// Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
|
||||
// MIT license. SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library.
|
||||
// Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept.
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
#if !defined(LIGHT_PIN) || (LIGHT_PIN < 0)
|
||||
// Autonomous iris motion uses a fractal behavior to similate both the major
|
||||
// reaction of the eye plus the continuous smaller adjustments that occur.
|
||||
uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris;
|
||||
#endif
|
||||
|
||||
// Initialise eyes ---------------------------------------------------------
|
||||
void initEyes(void)
|
||||
{
|
||||
Serial.println("Initialise eye objects");
|
||||
|
||||
// Initialise eye objects based on eyeInfo list in config.h:
|
||||
for (uint8_t e = 0; e < NUM_EYES; e++) {
|
||||
Serial.print("Create display #"); Serial.println(e);
|
||||
|
||||
eye[e].tft_cs = eyeInfo[e].select;
|
||||
eye[e].blink.state = NOBLINK;
|
||||
eye[e].xposition = eyeInfo[e].xposition;
|
||||
|
||||
pinMode(eye[e].tft_cs, OUTPUT);
|
||||
digitalWrite(eye[e].tft_cs, LOW);
|
||||
|
||||
// Also set up an individual eye-wink pin if defined:
|
||||
if (eyeInfo[e].wink >= 0) pinMode(eyeInfo[e].wink, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
|
||||
pinMode(BLINK_PIN, INPUT_PULLUP); // Ditto for all-eyes blink pin
|
||||
#endif
|
||||
}
|
||||
|
||||
// UPDATE EYE --------------------------------------------------------------
|
||||
void updateEye (void)
|
||||
{
|
||||
#if defined(LIGHT_PIN) && (LIGHT_PIN >= 0) // Interactive iris
|
||||
|
||||
int16_t v = analogRead(LIGHT_PIN); // Raw dial/photocell reading
|
||||
#ifdef LIGHT_PIN_FLIP
|
||||
v = 1023 - v; // Reverse reading from sensor
|
||||
#endif
|
||||
if (v < LIGHT_MIN) v = LIGHT_MIN; // Clamp light sensor range
|
||||
else if (v > LIGHT_MAX) v = LIGHT_MAX;
|
||||
v -= LIGHT_MIN; // 0 to (LIGHT_MAX - LIGHT_MIN)
|
||||
#ifdef LIGHT_CURVE // Apply gamma curve to sensor input?
|
||||
v = (int16_t)(pow((double)v / (double)(LIGHT_MAX - LIGHT_MIN),
|
||||
LIGHT_CURVE) * (double)(LIGHT_MAX - LIGHT_MIN));
|
||||
#endif
|
||||
// And scale to iris range (IRIS_MAX is size at LIGHT_MIN)
|
||||
v = map(v, 0, (LIGHT_MAX - LIGHT_MIN), IRIS_MAX, IRIS_MIN);
|
||||
#ifdef IRIS_SMOOTH // Filter input (gradual motion)
|
||||
static int16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2;
|
||||
irisValue = ((irisValue * 15) + v) / 16;
|
||||
frame(irisValue);
|
||||
#else // Unfiltered (immediate motion)
|
||||
frame(v);
|
||||
#endif // IRIS_SMOOTH
|
||||
|
||||
#else // Autonomous iris scaling -- invoke recursive function
|
||||
|
||||
newIris = random(IRIS_MIN, IRIS_MAX);
|
||||
split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN);
|
||||
oldIris = newIris;
|
||||
|
||||
#endif // LIGHT_PIN
|
||||
}
|
||||
|
||||
// EYE-RENDERING FUNCTION --------------------------------------------------
|
||||
void drawEye( // Renders one eye. Inputs must be pre-clipped & valid.
|
||||
// Use native 32 bit variables where possible as this is 10% faster!
|
||||
uint8_t e, // Eye array index; 0 or 1 for left/right
|
||||
uint32_t iScale, // Scale factor for iris
|
||||
uint32_t scleraX, // First pixel X offset into sclera image
|
||||
uint32_t scleraY, // First pixel Y offset into sclera image
|
||||
uint32_t uT, // Upper eyelid threshold value
|
||||
uint32_t lT) { // Lower eyelid threshold value
|
||||
|
||||
uint32_t screenX, screenY, scleraXsave;
|
||||
int32_t irisX, irisY;
|
||||
uint32_t p, a;
|
||||
uint32_t d;
|
||||
|
||||
uint32_t pixels = 0;
|
||||
|
||||
// Set up raw pixel dump to entire screen. Although such writes can wrap
|
||||
// around automatically from end of rect back to beginning, the region is
|
||||
// reset on each frame here in case of an SPI glitch.
|
||||
digitalWrite(eye[e].tft_cs, LOW);
|
||||
tft.startWrite();
|
||||
tft.setAddrWindow(eye[e].xposition, 0, 128, 128);
|
||||
|
||||
// Now just issue raw 16-bit values for every pixel...
|
||||
|
||||
scleraXsave = scleraX; // Save initial X value to reset on each line
|
||||
irisY = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;
|
||||
|
||||
// Eyelid image is left<>right swapped for two displays
|
||||
uint16_t lidX = 0;
|
||||
uint16_t dlidX = -1;
|
||||
if (e) dlidX = 1;
|
||||
for (screenY = 0; screenY < SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
|
||||
scleraX = scleraXsave;
|
||||
irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
|
||||
if (e) lidX = 0; else lidX = SCREEN_WIDTH - 1;
|
||||
for (screenX = 0; screenX < SCREEN_WIDTH; screenX++, scleraX++, irisX++, lidX += dlidX) {
|
||||
if ((pgm_read_byte(lower + screenY * SCREEN_WIDTH + lidX) <= lT) ||
|
||||
(pgm_read_byte(upper + screenY * SCREEN_WIDTH + lidX) <= uT)) { // Covered by eyelid
|
||||
p = 0;
|
||||
} else if ((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
|
||||
(irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
|
||||
p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX);
|
||||
} else { // Maybe iris...
|
||||
p = pgm_read_word(polar + irisY * IRIS_WIDTH + irisX); // Polar angle/dist
|
||||
d = (iScale * (p & 0x7F)) / 128; // Distance (Y)
|
||||
if (d < IRIS_MAP_HEIGHT) { // Within iris area
|
||||
a = (IRIS_MAP_WIDTH * (p >> 7)) / 512; // Angle (X)
|
||||
p = pgm_read_word(iris + d * IRIS_MAP_WIDTH + a); // Pixel = iris
|
||||
} else { // Not in iris
|
||||
p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX); // Pixel = sclera
|
||||
}
|
||||
}
|
||||
*(&pbuffer[dmaBuf][0] + pixels++) = p >> 8 | p << 8;
|
||||
|
||||
if (pixels >= BUFFER_SIZE) {
|
||||
yield();
|
||||
#ifdef USE_DMA
|
||||
tft.pushPixelsDMA(&pbuffer[dmaBuf][0], pixels);
|
||||
dmaBuf = !dmaBuf;
|
||||
#else
|
||||
tft.pushPixels(pbuffer, pixels);
|
||||
#endif
|
||||
pixels = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pixels) {
|
||||
#ifdef USE_DMA
|
||||
tft.pushPixelsDMA(&pbuffer[dmaBuf][0], pixels);
|
||||
#else
|
||||
tft.pushPixels(pbuffer, pixels);
|
||||
#endif
|
||||
}
|
||||
tft.endWrite();
|
||||
digitalWrite(eye[e].tft_cs, HIGH);
|
||||
}
|
||||
|
||||
// EYE ANIMATION -----------------------------------------------------------
|
||||
|
||||
const uint8_t ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, // T
|
||||
3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, // h
|
||||
11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23, // x
|
||||
24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, // 2
|
||||
40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58, // A
|
||||
60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80, // l
|
||||
81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98, 100, 101, 103, // e
|
||||
104, 106, 107, 109, 110, 112, 113, 115, 116, 118, 119, 121, 122, 124, 125, 127, // c
|
||||
128, 130, 131, 133, 134, 136, 137, 139, 140, 142, 143, 145, 146, 148, 149, 151, // J
|
||||
152, 154, 155, 157, 158, 159, 161, 162, 164, 165, 167, 168, 170, 171, 172, 174, // a
|
||||
175, 177, 178, 179, 181, 182, 183, 185, 186, 188, 189, 190, 192, 193, 194, 195, // c
|
||||
197, 198, 199, 201, 202, 203, 204, 205, 207, 208, 209, 210, 211, 213, 214, 215, // o
|
||||
216, 217, 218, 219, 220, 221, 222, 224, 225, 226, 227, 228, 228, 229, 230, 231, // b
|
||||
232, 233, 234, 235, 236, 237, 237, 238, 239, 240, 240, 241, 242, 243, 243, 244, // s
|
||||
245, 245, 246, 246, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, // o
|
||||
252, 253, 253, 253, 254, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255
|
||||
}; // n
|
||||
|
||||
#ifdef AUTOBLINK
|
||||
uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
|
||||
#endif
|
||||
|
||||
// Process motion for a single frame of left or right eye
|
||||
void frame(uint16_t iScale) // Iris scale (0-1023)
|
||||
{
|
||||
static uint32_t frames = 0; // Used in frame rate calculation
|
||||
static uint8_t eyeIndex = 0; // eye[] array counter
|
||||
int16_t eyeX, eyeY;
|
||||
uint32_t t = micros(); // Time at start of function
|
||||
|
||||
if (!(++frames & 255)) { // Every 256 frames...
|
||||
float elapsed = (millis() - startTime) / 1000.0;
|
||||
if (elapsed) Serial.println((uint16_t)(frames / elapsed)); // Print FPS
|
||||
}
|
||||
|
||||
if (++eyeIndex >= NUM_EYES) eyeIndex = 0; // Cycle through eyes, 1 per call
|
||||
|
||||
// X/Y movement
|
||||
|
||||
#if defined(JOYSTICK_X_PIN) && (JOYSTICK_X_PIN >= 0) && \
|
||||
defined(JOYSTICK_Y_PIN) && (JOYSTICK_Y_PIN >= 0)
|
||||
|
||||
// Read X/Y from joystick, constrain to circle
|
||||
int16_t dx, dy;
|
||||
int32_t d;
|
||||
eyeX = analogRead(JOYSTICK_X_PIN); // Raw (unclipped) X/Y reading
|
||||
eyeY = analogRead(JOYSTICK_Y_PIN);
|
||||
#ifdef JOYSTICK_X_FLIP
|
||||
eyeX = 1023 - eyeX;
|
||||
#endif
|
||||
#ifdef JOYSTICK_Y_FLIP
|
||||
eyeY = 1023 - eyeY;
|
||||
#endif
|
||||
dx = (eyeX * 2) - 1023; // A/D exact center is at 511.5. Scale coords
|
||||
dy = (eyeY * 2) - 1023; // X2 so range is -1023 to +1023 w/center at 0.
|
||||
if ((d = (dx * dx + dy * dy)) > (1023 * 1023)) { // Outside circle
|
||||
d = (int32_t)sqrt((float)d); // Distance from center
|
||||
eyeX = ((dx * 1023 / d) + 1023) / 2; // Clip to circle edge,
|
||||
eyeY = ((dy * 1023 / d) + 1023) / 2; // scale back to 0-1023
|
||||
}
|
||||
|
||||
#else // Autonomous X/Y eye motion
|
||||
// Periodically initiates motion to a new random point, random speed,
|
||||
// holds there for random period until next motion.
|
||||
|
||||
static bool eyeInMotion = false;
|
||||
static int16_t eyeOldX = 512, eyeOldY = 512, eyeNewX = 512, eyeNewY = 512;
|
||||
static uint32_t eyeMoveStartTime = 0L;
|
||||
static int32_t eyeMoveDuration = 0L;
|
||||
|
||||
int32_t dt = t - eyeMoveStartTime; // uS elapsed since last eye event
|
||||
if (eyeInMotion) { // Currently moving?
|
||||
if (dt >= eyeMoveDuration) { // Time up? Destination reached.
|
||||
eyeInMotion = false; // Stop moving
|
||||
eyeMoveDuration = random(3000000); // 0-3 sec stop
|
||||
eyeMoveStartTime = t; // Save initial time of stop
|
||||
eyeX = eyeOldX = eyeNewX; // Save position
|
||||
eyeY = eyeOldY = eyeNewY;
|
||||
} else { // Move time's not yet fully elapsed -- interpolate position
|
||||
int16_t e = ease[255 * dt / eyeMoveDuration] + 1; // Ease curve
|
||||
eyeX = eyeOldX + (((eyeNewX - eyeOldX) * e) / 256); // Interp X
|
||||
eyeY = eyeOldY + (((eyeNewY - eyeOldY) * e) / 256); // and Y
|
||||
}
|
||||
} else { // Eye stopped
|
||||
eyeX = eyeOldX;
|
||||
eyeY = eyeOldY;
|
||||
if (dt > eyeMoveDuration) { // Time up? Begin new move.
|
||||
int16_t dx, dy;
|
||||
uint32_t d;
|
||||
do { // Pick new dest in circle
|
||||
eyeNewX = random(1024);
|
||||
eyeNewY = random(1024);
|
||||
dx = (eyeNewX * 2) - 1023;
|
||||
dy = (eyeNewY * 2) - 1023;
|
||||
} while ((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying
|
||||
eyeMoveDuration = random(72000, 144000); // ~1/14 - ~1/7 sec
|
||||
eyeMoveStartTime = t; // Save initial time of move
|
||||
eyeInMotion = true; // Start move on next frame
|
||||
}
|
||||
}
|
||||
#endif // JOYSTICK_X_PIN etc.
|
||||
|
||||
// Blinking
|
||||
#ifdef AUTOBLINK
|
||||
// Similar to the autonomous eye movement above -- blink start times
|
||||
// and durations are random (within ranges).
|
||||
if ((t - timeOfLastBlink) >= timeToNextBlink) { // Start new blink?
|
||||
timeOfLastBlink = t;
|
||||
uint32_t blinkDuration = random(36000, 72000); // ~1/28 - ~1/14 sec
|
||||
// Set up durations for both eyes (if not already winking)
|
||||
for (uint8_t e = 0; e < NUM_EYES; e++) {
|
||||
if (eye[e].blink.state == NOBLINK) {
|
||||
eye[e].blink.state = ENBLINK;
|
||||
eye[e].blink.startTime = t;
|
||||
eye[e].blink.duration = blinkDuration;
|
||||
}
|
||||
}
|
||||
timeToNextBlink = blinkDuration * 3 + random(4000000);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (eye[eyeIndex].blink.state) { // Eye currently blinking?
|
||||
// Check if current blink state time has elapsed
|
||||
if ((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration) {
|
||||
// Yes -- increment blink state, unless...
|
||||
if ((eye[eyeIndex].blink.state == ENBLINK) && ( // Enblinking and...
|
||||
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
|
||||
(digitalRead(BLINK_PIN) == LOW) || // blink or wink held...
|
||||
#endif
|
||||
((eyeInfo[eyeIndex].wink >= 0) &&
|
||||
digitalRead(eyeInfo[eyeIndex].wink) == LOW) )) {
|
||||
// Don't advance state yet -- eye is held closed instead
|
||||
} else { // No buttons, or other state...
|
||||
if (++eye[eyeIndex].blink.state > DEBLINK) { // Deblinking finished?
|
||||
eye[eyeIndex].blink.state = NOBLINK; // No longer blinking
|
||||
} else { // Advancing from ENBLINK to DEBLINK mode
|
||||
eye[eyeIndex].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
|
||||
eye[eyeIndex].blink.startTime = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Not currently blinking...check buttons!
|
||||
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
|
||||
if (digitalRead(BLINK_PIN) == LOW) {
|
||||
// Manually-initiated blinks have random durations like auto-blink
|
||||
uint32_t blinkDuration = random(36000, 72000);
|
||||
for (uint8_t e = 0; e < NUM_EYES; e++) {
|
||||
if (eye[e].blink.state == NOBLINK) {
|
||||
eye[e].blink.state = ENBLINK;
|
||||
eye[e].blink.startTime = t;
|
||||
eye[e].blink.duration = blinkDuration;
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if ((eyeInfo[eyeIndex].wink >= 0) &&
|
||||
(digitalRead(eyeInfo[eyeIndex].wink) == LOW)) { // Wink!
|
||||
eye[eyeIndex].blink.state = ENBLINK;
|
||||
eye[eyeIndex].blink.startTime = t;
|
||||
eye[eyeIndex].blink.duration = random(45000, 90000);
|
||||
}
|
||||
}
|
||||
|
||||
// Process motion, blinking and iris scale into renderable values
|
||||
|
||||
// Scale eye X/Y positions (0-1023) to pixel units used by drawEye()
|
||||
eyeX = map(eyeX, 0, 1023, 0, SCLERA_WIDTH - 128);
|
||||
eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
|
||||
|
||||
// Horizontal position is offset so that eyes are very slightly crossed
|
||||
// to appear fixated (converged) at a conversational distance. Number
|
||||
// here was extracted from my posterior and not mathematically based.
|
||||
// I suppose one could get all clever with a range sensor, but for now...
|
||||
if (NUM_EYES > 1) {
|
||||
if (eyeIndex == 1) eyeX += 4;
|
||||
else eyeX -= 4;
|
||||
}
|
||||
if (eyeX > (SCLERA_WIDTH - 128)) eyeX = (SCLERA_WIDTH - 128);
|
||||
|
||||
// Eyelids are rendered using a brightness threshold image. This same
|
||||
// map can be used to simplify another problem: making the upper eyelid
|
||||
// track the pupil (eyes tend to open only as much as needed -- e.g. look
|
||||
// down and the upper eyelid drops). Just sample a point in the upper
|
||||
// lid map slightly above the pupil to determine the rendering threshold.
|
||||
static uint8_t uThreshold = 128;
|
||||
uint8_t lThreshold, n;
|
||||
#ifdef TRACKING
|
||||
int16_t sampleX = SCLERA_WIDTH / 2 - (eyeX / 2), // Reduce X influence
|
||||
sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4);
|
||||
// Eyelid is slightly asymmetrical, so two readings are taken, averaged
|
||||
if (sampleY < 0) n = 0;
|
||||
else n = (pgm_read_byte(upper + sampleY * SCREEN_WIDTH + sampleX) +
|
||||
pgm_read_byte(upper + sampleY * SCREEN_WIDTH + (SCREEN_WIDTH - 1 - sampleX))) / 2;
|
||||
uThreshold = (uThreshold * 3 + n) / 4; // Filter/soften motion
|
||||
// Lower eyelid doesn't track the same way, but seems to be pulled upward
|
||||
// by tension from the upper lid.
|
||||
lThreshold = 254 - uThreshold;
|
||||
#else // No tracking -- eyelids full open unless blink modifies them
|
||||
uThreshold = lThreshold = 0;
|
||||
#endif
|
||||
|
||||
// The upper/lower thresholds are then scaled relative to the current
|
||||
// blink position so that blinks work together with pupil tracking.
|
||||
if (eye[eyeIndex].blink.state) { // Eye currently blinking?
|
||||
uint32_t s = (t - eye[eyeIndex].blink.startTime);
|
||||
if (s >= eye[eyeIndex].blink.duration) s = 255; // At or past blink end
|
||||
else s = 255 * s / eye[eyeIndex].blink.duration; // Mid-blink
|
||||
s = (eye[eyeIndex].blink.state == DEBLINK) ? 1 + s : 256 - s;
|
||||
n = (uThreshold * s + 254 * (257 - s)) / 256;
|
||||
lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
|
||||
} else {
|
||||
n = uThreshold;
|
||||
}
|
||||
|
||||
// Pass all the derived values to the eye-rendering function:
|
||||
drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold);
|
||||
|
||||
if (eyeIndex == (NUM_EYES - 1)) {
|
||||
user_loop(); // Call user code after rendering last eye
|
||||
}
|
||||
}
|
||||
|
||||
// AUTONOMOUS IRIS SCALING (if no photocell or dial) -----------------------
|
||||
|
||||
#if !defined(LIGHT_PIN) || (LIGHT_PIN < 0)
|
||||
|
||||
// Autonomous iris motion uses a fractal behavior to similate both the major
|
||||
// reaction of the eye plus the continuous smaller adjustments that occur.
|
||||
|
||||
void split( // Subdivides motion path into two sub-paths w/randimization
|
||||
int16_t startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start
|
||||
int16_t endValue, // Iris scale value at end
|
||||
uint32_t startTime, // micros() at start
|
||||
int32_t duration, // Start-to-end time, in microseconds
|
||||
int16_t range) { // Allowable scale value variance when subdividing
|
||||
|
||||
if (range >= 8) { // Limit subdvision count, because recursion
|
||||
range /= 2; // Split range & time in half for subdivision,
|
||||
duration /= 2; // then pick random center point within range:
|
||||
int16_t midValue = (startValue + endValue - range) / 2 + random(range);
|
||||
uint32_t midTime = startTime + duration;
|
||||
split(startValue, midValue, startTime, duration, range); // First half
|
||||
split(midValue , endValue, midTime , duration, range); // Second half
|
||||
} else { // No more subdivisons, do iris motion...
|
||||
int32_t dt; // Time (micros) since start of motion
|
||||
int16_t v; // Interim value
|
||||
while ((dt = (micros() - startTime)) < duration) {
|
||||
v = startValue + (((endValue - startValue) * dt) / duration);
|
||||
if (v < IRIS_MIN) v = IRIS_MIN; // Clip just in case
|
||||
else if (v > IRIS_MAX) v = IRIS_MAX;
|
||||
frame(v); // Draw frame w/interim iris scale value
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !LIGHT_PIN
|
||||
@@ -0,0 +1,65 @@
|
||||
#if 1 // Change to 0 to disable this code (must enable ONE user*.cpp only!)
|
||||
|
||||
// This file provides a crude way to "drop in" user code to the eyes,
|
||||
// allowing concurrent operations without having to maintain a bunch of
|
||||
// special derivatives of the eye code (which is still undergoing a lot
|
||||
// of development). Just replace the source code contents of THIS TAB ONLY,
|
||||
// compile and upload to board. Shouldn't need to modify other eye code.
|
||||
|
||||
// User globals can go here, recommend declaring as static, e.g.:
|
||||
// static int foo = 42;
|
||||
|
||||
// Called once near the end of the setup() function.
|
||||
void user_setup(void) {
|
||||
}
|
||||
|
||||
// Called periodically during eye animation. This is invoked in the
|
||||
// interval before starting drawing on the last eye so it won't exacerbate
|
||||
// visible tearing in eye rendering.
|
||||
// This function BLOCKS, it does NOT multitask with the eye animation code,
|
||||
// and performance here will have a direct impact on overall refresh rates,
|
||||
// so keep it simple. Avoid loops (e.g. if animating something like a servo
|
||||
// or NeoPixels in response to some trigger) and instead rely on state
|
||||
// machines or similar. Additionally, calls to this function are NOT time-
|
||||
// constant -- eye rendering time can vary frame to frame, so animation or
|
||||
// other over-time operations won't look very good using simple +/-
|
||||
// increments, it's better to use millis() or micros() and work
|
||||
// algebraically with elapsed times instead.
|
||||
void user_loop(void) {
|
||||
/*
|
||||
Suppose we have a global bool "animating" (meaning something is in
|
||||
motion) and global uint32_t's "startTime" (the initial time at which
|
||||
something triggered movement) and "transitionTime" (the total time
|
||||
over which movement should occur, expressed in microseconds).
|
||||
Maybe it's servos, maybe NeoPixels, or something different altogether.
|
||||
This function might resemble something like (pseudocode):
|
||||
|
||||
if(!animating) {
|
||||
Not in motion, check sensor for trigger...
|
||||
if(read some sensor) {
|
||||
Motion is triggered! Record startTime, set transition
|
||||
to 1.5 seconds and set animating flag:
|
||||
startTime = micros();
|
||||
transitionTime = 1500000;
|
||||
animating = true;
|
||||
No motion actually takes place yet, that will begin on
|
||||
the next pass through this function.
|
||||
}
|
||||
} else {
|
||||
Currently in motion, ignore trigger and move things instead...
|
||||
uint32_t elapsed = millis() - startTime;
|
||||
if(elapsed < transitionTime) {
|
||||
Part way through motion...how far along?
|
||||
float ratio = (float)elapsed / (float)transitionTime;
|
||||
Do something here based on ratio, 0.0 = start, 1.0 = end
|
||||
} else {
|
||||
End of motion reached.
|
||||
Take whatever steps here to move into final position (1.0),
|
||||
and then clear the "animating" flag:
|
||||
animating = false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
#endif // 0
|
||||
@@ -0,0 +1,83 @@
|
||||
// SERVO BAT: flapping paper-cutout bat (attached to servo on SERVO_PIN)
|
||||
// triggered by contact-sensitive conductive thread on CAPTOUCH_PIN.
|
||||
// See user.cpp for basics of connecting user code to animated eyes.
|
||||
|
||||
#if 0 // Change to 1 to enable this code (must enable ONE user*.cpp only!)
|
||||
|
||||
#include "Adafruit_FreeTouch.h"
|
||||
#include <Servo.h>
|
||||
|
||||
#define CAPTOUCH_PIN A5 // Capacitive touch pin - attach conductive thread here
|
||||
#define SERVO_PIN 4 // Servo plugged in here
|
||||
|
||||
// Set up capacitive touch button using the FreeTouch library
|
||||
static Adafruit_FreeTouch touch(CAPTOUCH_PIN, OVERSAMPLE_4, RESISTOR_50K, FREQ_MODE_NONE);
|
||||
static long oldState; // Last-read touch value
|
||||
static bool isTouched = false; // When true, bat is flapping
|
||||
static uint32_t touchTime = 0; // millis() time when flapping started
|
||||
static uint32_t touchThreshold;
|
||||
|
||||
Servo servo;
|
||||
|
||||
void user_setup(void) {
|
||||
if (!touch.begin())
|
||||
Serial.println("Cap touch init failed");
|
||||
servo.attach(SERVO_PIN);
|
||||
servo.write(0); // Move servo to idle position
|
||||
servo.detach();
|
||||
|
||||
// Attempt to auto-calibrate the touch threshold
|
||||
// (assumes thread is NOT touched on startup!)
|
||||
touchThreshold = 0;
|
||||
for(int i=0; i<10; i++) {
|
||||
touchThreshold += touch.measure(); // Accumulate 10 readings
|
||||
delay(50);
|
||||
}
|
||||
touchThreshold /= 10; // Average "not touched" value
|
||||
touchThreshold = ((touchThreshold * 127) + 1023) / 128; // Threshold = ~1% toward max
|
||||
|
||||
oldState = touch.measure();
|
||||
}
|
||||
|
||||
#define FLAP_TIME_RISING 900 // 0-to-180 degree servo sweep time, in milliseconds
|
||||
#define FLAP_TIME_FALLING 1200 // 180-to-0 servo sweep time
|
||||
#define FLAP_REPS 3 // Number of times to flap
|
||||
#define FLAP_TIME_PER (FLAP_TIME_RISING + FLAP_TIME_FALLING)
|
||||
#define FLAP_TIME_TOTAL (FLAP_TIME_PER * FLAP_REPS)
|
||||
|
||||
void user_loop(void) {
|
||||
long newState = touch.measure();
|
||||
Serial.println(newState);
|
||||
|
||||
if (isTouched) {
|
||||
uint32_t elapsed = millis() - touchTime;
|
||||
if (elapsed >= FLAP_TIME_TOTAL) { // After all flaps are completed
|
||||
isTouched = false; // Bat goes idle again
|
||||
servo.write(0);
|
||||
servo.detach();
|
||||
} else {
|
||||
elapsed %= FLAP_TIME_PER; // Time within current flap cycle
|
||||
if (elapsed < FLAP_TIME_RISING) { // Over the course of 0 to FLAP_TIME_RISING...
|
||||
servo.write(elapsed * 180 / FLAP_TIME_RISING); // Move 0 to 180 degrees
|
||||
} else { // Over course of FLAP_TIME_FALLING, return to 0
|
||||
servo.write(180 - ((elapsed - FLAP_TIME_RISING) * 180 / FLAP_TIME_FALLING));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Bat is idle...check for capacitive touch...
|
||||
if (newState > touchThreshold && oldState < touchThreshold) {
|
||||
delay(100); // Short delay to debounce
|
||||
newState = touch.measure(); // Verify whether still touched
|
||||
if (newState > touchThreshold) { // It is!
|
||||
isTouched = true; // Start a new flap session
|
||||
touchTime = millis();
|
||||
servo.attach(SERVO_PIN);
|
||||
servo.write(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oldState = newState; // Save cap touch state
|
||||
}
|
||||
|
||||
#endif // 0
|
||||
@@ -0,0 +1,64 @@
|
||||
#if 0 // Change to 1 to enable this code (must enable ONE user*.cpp only!)
|
||||
|
||||
// Christmas demo for eye + NeoPixels. Randomly sets pixels in holiday-themed colors.
|
||||
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
// Pin 8 is the built-in NeoPixels on Circuit Playground Express & Bluetooth.
|
||||
// With a TFT Gizmo attached, you can use A1 or A2 to easily connect a strand.
|
||||
#define LED_PIN 8
|
||||
#define LED_COUNT 10
|
||||
#define LED_BRIGHTNESS 50 // about 1/5 brightness (max = 255)
|
||||
#define TWINKLE_INTERVAL 333 // Every 333 ms (1/3 second), change a pixel
|
||||
#define LIT_PIXELS (LED_COUNT / 3) // Must be LESS than LED_COUNT/2
|
||||
|
||||
Adafruit_NeoPixel pixels(LED_COUNT, LED_PIN);
|
||||
|
||||
|
||||
uint32_t timeOfLastTwinkle = 0; // Used for timing pixel changes
|
||||
uint8_t litPixel[LIT_PIXELS]; // Indices of which pixels are lit
|
||||
uint8_t pixelIndex = LIT_PIXELS; // Index of currently-changing litPixel
|
||||
|
||||
uint32_t colors[] = { 0xFF0000, 0x00FF00, 0xFFFFFF }; // Red, green, white
|
||||
#define NUM_COLORS (sizeof colors / sizeof colors[0])
|
||||
|
||||
void user_setup(void) {
|
||||
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
|
||||
pixels.show(); // Turn OFF all pixels ASAP
|
||||
pixels.setBrightness(LED_BRIGHTNESS);
|
||||
memset(litPixel, 255, sizeof litPixel); // Fill with out-of-range nonsense
|
||||
}
|
||||
|
||||
void user_loop(void) {
|
||||
uint32_t t = millis();
|
||||
|
||||
if((t - timeOfLastTwinkle) >= TWINKLE_INTERVAL) { // Time to update pixels?
|
||||
timeOfLastTwinkle = t;
|
||||
if(++pixelIndex >= LIT_PIXELS) pixelIndex = 0;
|
||||
|
||||
// Pick a NEW pixel that's not currently lit and not adjacent to a lit one.
|
||||
// This just brute-force randomly tries pixels until a valid one is found,
|
||||
// no mathematical cleverness. Should only take a few iterations and won't
|
||||
// significantly slow down the eyes.
|
||||
int newPixel, pixelAfter, pixelBefore;
|
||||
do {
|
||||
newPixel = random(LED_COUNT);
|
||||
pixelAfter = (newPixel + 1) % LED_COUNT;
|
||||
pixelBefore = (newPixel - 1);
|
||||
if(pixelBefore < 0) pixelBefore = LED_COUNT - 1;
|
||||
} while(pixels.getPixelColor(newPixel) ||
|
||||
pixels.getPixelColor(pixelAfter) ||
|
||||
pixels.getPixelColor(pixelBefore));
|
||||
|
||||
// Turn OFF litPixel[pixelIndex]
|
||||
pixels.setPixelColor(litPixel[pixelIndex], 0);
|
||||
// 'newPixel' is the winner. Save in the litPixel[] array for later...
|
||||
litPixel[pixelIndex] = newPixel;
|
||||
// Turn ON newPixel with a random color from the colors[] list.
|
||||
pixels.setPixelColor(newPixel, colors[random(NUM_COLORS)]);
|
||||
|
||||
pixels.show();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // 0
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
This is the example wiring used for the sketch testing.
|
||||
|
||||
You must not define the TFT_CS pin in the TFT_eSPI library if you are
|
||||
using two independant displays. Instead the chip selects (CS) must be
|
||||
defined in the "config.h" tab of this sketch. The sketch can then select
|
||||
the dispay to send graphics to.
|
||||
|
||||
If you are only using one display, then TFT_CS can be defined in the
|
||||
TFT_eSPI library.
|
||||
|
||||
The "Setup47_ST7735.h" file was used for the two TFT test using the wiring
|
||||
as shown below:
|
||||
|
||||
Function ESP32 pin TFT 1 TFT 2
|
||||
MOSI 23 -> SDA -> SDA // The TFT pin may be named DIN
|
||||
MISO 19 // Not connected
|
||||
SCLK 18 -> CLK -> CLK // The TFT pin may be named SCK
|
||||
TFT_DC 2 -> DC -> DC // The TFT pin may be named AO
|
||||
TFT_RST 4 -> RST -> RST
|
||||
CS 1 22 -> CS // Connected to TFT 1 only
|
||||
CS 2 21 -> CS // Connected to TFT 2 only
|
||||
+5V/VIN -> VCC -> VCC
|
||||
0V -> GND -> GND
|
||||
+5V/VIN -> LED -> LED // Some displays do not have a backlight BL/LED pin
|
||||
|
||||
The displays used for testing were 128x128 ST7735 displays, the TFT_eSPI library setup file may need
|
||||
to be changed as these displays come in many configuration variants.
|
||||
|
||||
|
||||
*/
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,268 @@
|
||||
// This sketch if for an ESP32, it draws Jpeg images pulled from an SD Card
|
||||
// onto the TFT.
|
||||
|
||||
// As well as the TFT_eSPI library you will need the JPEG Decoder library.
|
||||
// A copy can be downloaded here, it is based on the library by Makoto Kurauchi.
|
||||
// https://github.com/Bodmer/JPEGDecoder
|
||||
|
||||
// Images on SD Card must be put in the root folder (top level) to be found
|
||||
// Use the SD library examples to verify your SD Card interface works!
|
||||
|
||||
// The example images used to test this sketch can be found in the library
|
||||
// JPEGDecoder/extras folder
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
#include <SPI.h>
|
||||
|
||||
#include <FS.h>
|
||||
#include <SD.h>
|
||||
|
||||
#include <TFT_eSPI.h>
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
|
||||
// JPEG decoder library
|
||||
#include <JPEGDecoder.h>
|
||||
|
||||
//####################################################################################################
|
||||
// Setup
|
||||
//####################################################################################################
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
// Set all chip selects high to avoid bus contention during initialisation of each peripheral
|
||||
digitalWrite(22, HIGH); // Touch controller chip select (if used)
|
||||
digitalWrite(15, HIGH); // TFT screen chip select
|
||||
digitalWrite( 5, HIGH); // SD card chips select, must use GPIO 5 (ESP32 SS)
|
||||
|
||||
tft.begin();
|
||||
|
||||
if (!SD.begin()) {
|
||||
Serial.println("Card Mount Failed");
|
||||
return;
|
||||
}
|
||||
uint8_t cardType = SD.cardType();
|
||||
|
||||
if (cardType == CARD_NONE) {
|
||||
Serial.println("No SD card attached");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.print("SD Card Type: ");
|
||||
if (cardType == CARD_MMC) {
|
||||
Serial.println("MMC");
|
||||
} else if (cardType == CARD_SD) {
|
||||
Serial.println("SDSC");
|
||||
} else if (cardType == CARD_SDHC) {
|
||||
Serial.println("SDHC");
|
||||
} else {
|
||||
Serial.println("UNKNOWN");
|
||||
}
|
||||
|
||||
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
|
||||
Serial.printf("SD Card Size: %lluMB\n", cardSize);
|
||||
|
||||
Serial.println("initialisation done.");
|
||||
}
|
||||
|
||||
//####################################################################################################
|
||||
// Main loop
|
||||
//####################################################################################################
|
||||
void loop() {
|
||||
|
||||
tft.setRotation(2); // portrait
|
||||
tft.fillScreen(random(0xFFFF));
|
||||
|
||||
// The image is 300 x 300 pixels so we do some sums to position image in the middle of the screen!
|
||||
// Doing this by reading the image width and height from the jpeg info is left as an exercise!
|
||||
int x = (tft.width() - 300) / 2 - 1;
|
||||
int y = (tft.height() - 300) / 2 - 1;
|
||||
|
||||
drawSdJpeg("/EagleEye.jpg", x, y); // This draws a jpeg pulled off the SD Card
|
||||
delay(2000);
|
||||
|
||||
tft.setRotation(2); // portrait
|
||||
tft.fillScreen(random(0xFFFF));
|
||||
drawSdJpeg("/Baboon40.jpg", 0, 0); // This draws a jpeg pulled off the SD Card
|
||||
delay(2000);
|
||||
|
||||
tft.setRotation(2); // portrait
|
||||
tft.fillScreen(random(0xFFFF));
|
||||
drawSdJpeg("/lena20k.jpg", 0, 0); // This draws a jpeg pulled off the SD Card
|
||||
delay(2000);
|
||||
|
||||
tft.setRotation(1); // landscape
|
||||
tft.fillScreen(random(0xFFFF));
|
||||
drawSdJpeg("/Mouse480.jpg", 0, 0); // This draws a jpeg pulled off the SD Card
|
||||
|
||||
delay(2000);
|
||||
|
||||
while(1); // Wait here
|
||||
}
|
||||
|
||||
//####################################################################################################
|
||||
// Draw a JPEG on the TFT pulled from SD Card
|
||||
//####################################################################################################
|
||||
// xpos, ypos is top left corner of plotted image
|
||||
void drawSdJpeg(const char *filename, int xpos, int ypos) {
|
||||
|
||||
// Open the named file (the Jpeg decoder library will close it)
|
||||
File jpegFile = SD.open( filename, FILE_READ); // or, file handle reference for SD library
|
||||
|
||||
if ( !jpegFile ) {
|
||||
Serial.print("ERROR: File \""); Serial.print(filename); Serial.println ("\" not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.println("===========================");
|
||||
Serial.print("Drawing file: "); Serial.println(filename);
|
||||
Serial.println("===========================");
|
||||
|
||||
// Use one of the following methods to initialise the decoder:
|
||||
bool decoded = JpegDec.decodeSdFile(jpegFile); // Pass the SD file handle to the decoder,
|
||||
//bool decoded = JpegDec.decodeSdFile(filename); // or pass the filename (String or character array)
|
||||
|
||||
if (decoded) {
|
||||
// print information about the image to the serial port
|
||||
jpegInfo();
|
||||
// render the image onto the screen at given coordinates
|
||||
jpegRender(xpos, ypos);
|
||||
}
|
||||
else {
|
||||
Serial.println("Jpeg file format not supported!");
|
||||
}
|
||||
}
|
||||
|
||||
//####################################################################################################
|
||||
// Draw a JPEG on the TFT, images will be cropped on the right/bottom sides if they do not fit
|
||||
//####################################################################################################
|
||||
// This function assumes xpos,ypos is a valid screen coordinate. For convenience images that do not
|
||||
// fit totally on the screen are cropped to the nearest MCU size and may leave right/bottom borders.
|
||||
void jpegRender(int xpos, int ypos) {
|
||||
|
||||
//jpegInfo(); // Print information from the JPEG file (could comment this line out)
|
||||
|
||||
uint16_t *pImg;
|
||||
uint16_t mcu_w = JpegDec.MCUWidth;
|
||||
uint16_t mcu_h = JpegDec.MCUHeight;
|
||||
uint32_t max_x = JpegDec.width;
|
||||
uint32_t max_y = JpegDec.height;
|
||||
|
||||
bool swapBytes = tft.getSwapBytes();
|
||||
tft.setSwapBytes(true);
|
||||
|
||||
// Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs)
|
||||
// Typically these MCUs are 16x16 pixel blocks
|
||||
// Determine the width and height of the right and bottom edge image blocks
|
||||
uint32_t min_w = jpg_min(mcu_w, max_x % mcu_w);
|
||||
uint32_t min_h = jpg_min(mcu_h, max_y % mcu_h);
|
||||
|
||||
// save the current image block size
|
||||
uint32_t win_w = mcu_w;
|
||||
uint32_t win_h = mcu_h;
|
||||
|
||||
// record the current time so we can measure how long it takes to draw an image
|
||||
uint32_t drawTime = millis();
|
||||
|
||||
// save the coordinate of the right and bottom edges to assist image cropping
|
||||
// to the screen size
|
||||
max_x += xpos;
|
||||
max_y += ypos;
|
||||
|
||||
// Fetch data from the file, decode and display
|
||||
while (JpegDec.read()) { // While there is more data in the file
|
||||
pImg = JpegDec.pImage ; // Decode a MCU (Minimum Coding Unit, typically a 8x8 or 16x16 pixel block)
|
||||
|
||||
// Calculate coordinates of top left corner of current MCU
|
||||
int mcu_x = JpegDec.MCUx * mcu_w + xpos;
|
||||
int mcu_y = JpegDec.MCUy * mcu_h + ypos;
|
||||
|
||||
// check if the image block size needs to be changed for the right edge
|
||||
if (mcu_x + mcu_w <= max_x) win_w = mcu_w;
|
||||
else win_w = min_w;
|
||||
|
||||
// check if the image block size needs to be changed for the bottom edge
|
||||
if (mcu_y + mcu_h <= max_y) win_h = mcu_h;
|
||||
else win_h = min_h;
|
||||
|
||||
// copy pixels into a contiguous block
|
||||
if (win_w != mcu_w)
|
||||
{
|
||||
uint16_t *cImg;
|
||||
int p = 0;
|
||||
cImg = pImg + win_w;
|
||||
for (int h = 1; h < win_h; h++)
|
||||
{
|
||||
p += mcu_w;
|
||||
for (int w = 0; w < win_w; w++)
|
||||
{
|
||||
*cImg = *(pImg + w + p);
|
||||
cImg++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate how many pixels must be drawn
|
||||
uint32_t mcu_pixels = win_w * win_h;
|
||||
|
||||
// draw image MCU block only if it will fit on the screen
|
||||
if (( mcu_x + win_w ) <= tft.width() && ( mcu_y + win_h ) <= tft.height())
|
||||
tft.pushImage(mcu_x, mcu_y, win_w, win_h, pImg);
|
||||
else if ( (mcu_y + win_h) >= tft.height())
|
||||
JpegDec.abort(); // Image has run off bottom of screen so abort decoding
|
||||
}
|
||||
|
||||
tft.setSwapBytes(swapBytes);
|
||||
|
||||
showTime(millis() - drawTime); // These lines are for sketch testing only
|
||||
}
|
||||
|
||||
//####################################################################################################
|
||||
// Print image information to the serial port (optional)
|
||||
//####################################################################################################
|
||||
// JpegDec.decodeFile(...) or JpegDec.decodeArray(...) must be called before this info is available!
|
||||
void jpegInfo() {
|
||||
|
||||
// Print information extracted from the JPEG file
|
||||
Serial.println("JPEG image info");
|
||||
Serial.println("===============");
|
||||
Serial.print("Width :");
|
||||
Serial.println(JpegDec.width);
|
||||
Serial.print("Height :");
|
||||
Serial.println(JpegDec.height);
|
||||
Serial.print("Components :");
|
||||
Serial.println(JpegDec.comps);
|
||||
Serial.print("MCU / row :");
|
||||
Serial.println(JpegDec.MCUSPerRow);
|
||||
Serial.print("MCU / col :");
|
||||
Serial.println(JpegDec.MCUSPerCol);
|
||||
Serial.print("Scan type :");
|
||||
Serial.println(JpegDec.scanType);
|
||||
Serial.print("MCU width :");
|
||||
Serial.println(JpegDec.MCUWidth);
|
||||
Serial.print("MCU height :");
|
||||
Serial.println(JpegDec.MCUHeight);
|
||||
Serial.println("===============");
|
||||
Serial.println("");
|
||||
}
|
||||
|
||||
//####################################################################################################
|
||||
// Show the execution time (optional)
|
||||
//####################################################################################################
|
||||
// WARNING: for UNO/AVR legacy reasons printing text to the screen with the Mega might not work for
|
||||
// sketch sizes greater than ~70KBytes because 16 bit address pointers are used in some libraries.
|
||||
|
||||
// The Due will work fine with the HX8357_Due library.
|
||||
|
||||
void showTime(uint32_t msTime) {
|
||||
//tft.setCursor(0, 0);
|
||||
//tft.setTextFont(1);
|
||||
//tft.setTextSize(2);
|
||||
//tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||
//tft.print(F(" JPEG drawn in "));
|
||||
//tft.print(msTime);
|
||||
//tft.println(F(" ms "));
|
||||
Serial.print(F(" JPEG drawn in "));
|
||||
Serial.print(msTime);
|
||||
Serial.println(F(" ms "));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
// Adapted by Bodmer to work with a NodeMCU and ILI9341 or ST7735 display.
|
||||
//
|
||||
// This code currently does not "blink" the eye!
|
||||
//
|
||||
// Library used is here:
|
||||
// https://github.com/Bodmer/TFT_eSPI
|
||||
//
|
||||
// To do, maybe, one day:
|
||||
// 1. Get the eye to blink
|
||||
// 2. Add another screen for another eye
|
||||
// 3. Add variable to set how wide open the eye is
|
||||
// 4. Add a reflected highlight to the cornea
|
||||
// 5. Add top eyelid shadow to eye surface
|
||||
// 6. Add aliasing to blur mask edge
|
||||
//
|
||||
// With one lidded eye drawn the code runs at 28-33fps (at 27-40MHz SPI clock)
|
||||
// which is quite reasonable. Operation at an 80MHz SPI clock is possible but
|
||||
// the display may not be able to cope with a clock rate that high and the
|
||||
// performance improvement is small. Operate the ESP8266 at 160MHz for best
|
||||
// frame rate. Note the images are stored in SPI FLASH (PROGMEM) so performance
|
||||
// will be constrained by the increased memory access time.
|
||||
|
||||
// Original header for this sketch is below. Note: the technical aspects of the
|
||||
// text no longer apply to this modified version of the sketch:
|
||||
/*
|
||||
//--------------------------------------------------------------------------
|
||||
// Uncanny eyes for PJRC Teensy 3.1 with Adafruit 1.5" OLED (product #1431)
|
||||
// or 1.44" TFT LCD (#2088). This uses Teensy-3.1-specific features and
|
||||
// WILL NOT work on normal Arduino or other boards! Use 72 MHz (Optimized)
|
||||
// board speed -- OLED does not work at 96 MHz.
|
||||
//
|
||||
// Adafruit invests time and resources providing this open source code,
|
||||
// please support Adafruit and open-source hardware by purchasing products
|
||||
// from Adafruit!
|
||||
//
|
||||
// Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
|
||||
// MIT license. SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library.
|
||||
// Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept.
|
||||
//--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include <SPI.h>
|
||||
#include <TFT_eSPI.h> // Hardware-specific library
|
||||
|
||||
// Enable ONE of these #includes for the various eyes:
|
||||
#include "defaultEye.h" // Standard human-ish hazel eye
|
||||
//#include "noScleraEye.h" // Large iris, no sclera
|
||||
//#include "dragonEye.h" // Slit pupil fiery dragon/demon eye
|
||||
//#include "goatEye.h" // Horizontal pupil goat/Krampus eye
|
||||
|
||||
|
||||
#define DISPLAY_DC D3 // Data/command pin for BOTH displays
|
||||
#define DISPLAY_RESET D4 // Reset pin for BOTH displays
|
||||
#define SELECT_L_PIN D8 // LEFT eye chip select pin
|
||||
#define SELECT_R_PIN D8 // RIGHT eye chip select pin
|
||||
|
||||
// INPUT CONFIG (for eye motion -- enable or comment out as needed) --------
|
||||
|
||||
// The ESP8266 is rather constrained here as it only has one analogue port.
|
||||
// An I2C ADC could be used for more analogue channels
|
||||
//#define JOYSTICK_X_PIN A0 // Analogue pin for eye horiz pos (else auto)
|
||||
//#define JOYSTICK_Y_PIN A0 // Analogue pin for eye vert position (")
|
||||
//#define JOYSTICK_X_FLIP // If set, reverse stick X axis
|
||||
//#define JOYSTICK_Y_FLIP // If set, reverse stick Y axis
|
||||
#define TRACKING // If enabled, eyelid tracks pupil
|
||||
//#define IRIS_PIN A0 // Photocell or potentiometer (else auto iris)
|
||||
//#define IRIS_PIN_FLIP // If set, reverse reading from dial/photocell
|
||||
//#define IRIS_SMOOTH // If enabled, filter input from IRIS_PIN
|
||||
#define IRIS_MIN 140 // Clip lower analogRead() range from IRIS_PIN
|
||||
#define IRIS_MAX 260 // Clip upper "
|
||||
#define WINK_L_PIN 0 // Pin for LEFT eye wink button
|
||||
#define BLINK_PIN 1 // Pin for blink button (BOTH eyes)
|
||||
#define WINK_R_PIN 2 // Pin for RIGHT eye wink button
|
||||
#define AUTOBLINK // If enabled, eyes blink autonomously
|
||||
|
||||
// Probably don't need to edit any config below this line, -----------------
|
||||
// unless building a single-eye project (pendant, etc.), in which case one
|
||||
// of the two elements in the eye[] array further down can be commented out.
|
||||
|
||||
// Eye blinks are a tiny 3-state machine. Per-eye allows winks + blinks.
|
||||
#define NOBLINK 0 // Not currently engaged in a blink
|
||||
#define ENBLINK 1 // Eyelid is currently closing
|
||||
#define DEBLINK 2 // Eyelid is currently opening
|
||||
typedef struct {
|
||||
int8_t pin; // Optional button here for indiv. wink
|
||||
uint8_t state; // NOBLINK/ENBLINK/DEBLINK
|
||||
int32_t duration; // Duration of blink state (micros)
|
||||
uint32_t startTime; // Time (micros) of last state change
|
||||
} eyeBlink;
|
||||
|
||||
struct {
|
||||
TFT_eSPI tft; // OLED/eye[e].tft object
|
||||
uint8_t cs; // Chip select pin
|
||||
eyeBlink blink; // Current blink state
|
||||
} eye[] = { // OK to comment out one of these for single-eye display:
|
||||
TFT_eSPI(),SELECT_L_PIN,{WINK_L_PIN,NOBLINK},
|
||||
//TFT_eSPI(),SELECT_R_PIN,{WINK_R_PIN,NOBLINK},
|
||||
};
|
||||
|
||||
#define NUM_EYES (sizeof(eye) / sizeof(eye[0]))
|
||||
|
||||
uint32_t fstart = 0; // start time to improve frame rate calculation at startup
|
||||
|
||||
// INITIALIZATION -- runs once at startup ----------------------------------
|
||||
|
||||
void setup(void) {
|
||||
uint8_t e = 0;
|
||||
|
||||
Serial.begin(250000);
|
||||
randomSeed(analogRead(A0)); // Seed random() from floating analogue input
|
||||
|
||||
eye[e].tft.init();
|
||||
eye[e].tft.fillScreen(TFT_BLACK);
|
||||
eye[e].tft.setRotation(0);
|
||||
|
||||
fstart = millis()-1; // Subtract 1 to avoid divide by zero later
|
||||
}
|
||||
|
||||
|
||||
// EYE-RENDERING FUNCTION --------------------------------------------------
|
||||
#define BUFFER_SIZE 256 // 64 to 512 seems optimum = 30 fps for default eye
|
||||
void drawEye( // Renders one eye. Inputs must be pre-clipped & valid.
|
||||
// Use native 32 bit variables where possible as this is 10% faster!
|
||||
uint8_t e, // Eye array index; 0 or 1 for left/right
|
||||
uint32_t iScale, // Scale factor for iris
|
||||
uint32_t scleraX, // First pixel X offset into sclera image
|
||||
uint32_t scleraY, // First pixel Y offset into sclera image
|
||||
uint32_t uT, // Upper eyelid threshold value
|
||||
uint32_t lT) { // Lower eyelid threshold value
|
||||
|
||||
uint32_t screenX, screenY, scleraXsave;
|
||||
int32_t irisX, irisY;
|
||||
uint32_t p, a;
|
||||
uint32_t d;
|
||||
|
||||
uint32_t pixels = 0;
|
||||
uint16_t pbuffer[BUFFER_SIZE]; // This one needs to be 16 bit
|
||||
|
||||
// Set up raw pixel dump to entire screen. Although such writes can wrap
|
||||
// around automatically from end of rect back to beginning, the region is
|
||||
// reset on each frame here in case of an SPI glitch.
|
||||
|
||||
//eye[e].tft.setAddrWindow(319-127, 0, 319, 127);
|
||||
eye[e].tft.setAddrWindow(0, 0, 128, 128);
|
||||
|
||||
//digitalWrite(eye[e].cs, LOW); // Chip select
|
||||
|
||||
// Now just issue raw 16-bit values for every pixel...
|
||||
|
||||
scleraXsave = scleraX; // Save initial X value to reset on each line
|
||||
irisY = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;
|
||||
for(screenY=0; screenY<SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
|
||||
scleraX = scleraXsave;
|
||||
irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
|
||||
for(screenX=0; screenX<SCREEN_WIDTH; screenX++, scleraX++, irisX++) {
|
||||
if((pgm_read_byte(lower + screenY * SCREEN_WIDTH + screenX) <= lT) ||
|
||||
(pgm_read_byte(upper + screenY * SCREEN_WIDTH + screenX) <= uT)) { // Covered by eyelid
|
||||
p = 0;
|
||||
} else if((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
|
||||
(irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
|
||||
p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX);
|
||||
} else { // Maybe iris...
|
||||
p = pgm_read_word(polar + irisY * IRIS_WIDTH + irisX); // Polar angle/dist
|
||||
d = (iScale * (p & 0x7F)) / 128; // Distance (Y)
|
||||
if(d < IRIS_MAP_HEIGHT) { // Within iris area
|
||||
a = (IRIS_MAP_WIDTH * (p >> 7)) / 512; // Angle (X)
|
||||
p = pgm_read_word(iris + d * IRIS_MAP_WIDTH + a); // Pixel = iris
|
||||
} else { // Not in iris
|
||||
p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX); // Pixel = sclera
|
||||
}
|
||||
}
|
||||
*(pbuffer + pixels++) = p>>8 | p<<8;
|
||||
|
||||
if (pixels >= BUFFER_SIZE) { yield(); eye[e].tft.pushColors((uint8_t*)pbuffer, pixels*2); pixels = 0;}
|
||||
}
|
||||
}
|
||||
|
||||
if (pixels) { eye[e].tft.pushColors(pbuffer, pixels); pixels = 0;}
|
||||
}
|
||||
|
||||
|
||||
// EYE ANIMATION -----------------------------------------------------------
|
||||
|
||||
const uint8_t ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, // T
|
||||
3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, // h
|
||||
11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23, // x
|
||||
24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, // 2
|
||||
40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58, // A
|
||||
60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80, // l
|
||||
81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98,100,101,103, // e
|
||||
104,106,107,109,110,112,113,115,116,118,119,121,122,124,125,127, // c
|
||||
128,130,131,133,134,136,137,139,140,142,143,145,146,148,149,151, // J
|
||||
152,154,155,157,158,159,161,162,164,165,167,168,170,171,172,174, // a
|
||||
175,177,178,179,181,182,183,185,186,188,189,190,192,193,194,195, // c
|
||||
197,198,199,201,202,203,204,205,207,208,209,210,211,213,214,215, // o
|
||||
216,217,218,219,220,221,222,224,225,226,227,228,228,229,230,231, // b
|
||||
232,233,234,235,236,237,237,238,239,240,240,241,242,243,243,244, // s
|
||||
245,245,246,246,247,248,248,249,249,250,250,251,251,251,252,252, // o
|
||||
252,253,253,253,254,254,254,254,254,255,255,255,255,255,255,255 }; // n
|
||||
|
||||
#ifdef AUTOBLINK
|
||||
uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
|
||||
#endif
|
||||
|
||||
void frame( // Process motion for a single frame of left or right eye
|
||||
uint32_t iScale) { // Iris scale (0-1023) passed in
|
||||
static uint32_t frames = 0; // Used in frame rate calculation
|
||||
static uint8_t eyeIndex = 0; // eye[] array counter
|
||||
int32_t eyeX, eyeY;
|
||||
uint32_t t = micros(); // Time at start of function
|
||||
|
||||
Serial.print((++frames * 1000) / (millis() - fstart)); Serial.println("fps");// Show frame rate
|
||||
|
||||
if(++eyeIndex >= NUM_EYES) eyeIndex = 0; // Cycle through eyes, 1 per call
|
||||
|
||||
// Autonomous X/Y eye motion
|
||||
// Periodically initiates motion to a new random point, random speed,
|
||||
// holds there for random period until next motion.
|
||||
|
||||
static bool eyeInMotion = false;
|
||||
static int32_t eyeOldX=512, eyeOldY=512, eyeNewX=512, eyeNewY=512;
|
||||
static uint32_t eyeMoveStartTime = 0L;
|
||||
static int32_t eyeMoveDuration = 0L;
|
||||
|
||||
int32_t dt = t - eyeMoveStartTime; // uS elapsed since last eye event
|
||||
if(eyeInMotion) { // Currently moving?
|
||||
if(dt >= eyeMoveDuration) { // Time up? Destination reached.
|
||||
eyeInMotion = false; // Stop moving
|
||||
eyeMoveDuration = random(3000000L); // 0-3 sec stop
|
||||
eyeMoveStartTime = t; // Save initial time of stop
|
||||
eyeX = eyeOldX = eyeNewX; // Save position
|
||||
eyeY = eyeOldY = eyeNewY;
|
||||
} else { // Move time's not yet fully elapsed -- interpolate position
|
||||
int16_t e = ease[255 * dt / eyeMoveDuration] + 1; // Ease curve
|
||||
eyeX = eyeOldX + (((eyeNewX - eyeOldX) * e) / 256); // Interp X
|
||||
eyeY = eyeOldY + (((eyeNewY - eyeOldY) * e) / 256); // and Y
|
||||
}
|
||||
} else { // Eye stopped
|
||||
eyeX = eyeOldX;
|
||||
eyeY = eyeOldY;
|
||||
if(dt > eyeMoveDuration) { // Time up? Begin new move.
|
||||
int16_t dx, dy;
|
||||
uint32_t d;
|
||||
do { // Pick new dest in circle
|
||||
eyeNewX = random(1024);
|
||||
eyeNewY = random(1024);
|
||||
dx = (eyeNewX * 2) - 1023;
|
||||
dy = (eyeNewY * 2) - 1023;
|
||||
} while((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying
|
||||
eyeMoveDuration = random(50000, 150000);//random(72000, 144000); // ~1/14 - ~1/7 sec
|
||||
eyeMoveStartTime = t; // Save initial time of move
|
||||
eyeInMotion = true; // Start move on next frame
|
||||
}
|
||||
}
|
||||
|
||||
// Blinking
|
||||
/*
|
||||
#ifdef AUTOBLINK
|
||||
// Similar to the autonomous eye movement above -- blink start times
|
||||
// and durations are random (within ranges).
|
||||
if((t - timeOfLastBlink) >= timeToNextBlink) { // Start new blink?
|
||||
timeOfLastBlink = t;
|
||||
uint32_t blinkDuration = random(36000, 72000); // ~1/28 - ~1/14 sec
|
||||
// Set up durations for both eyes (if not already winking)
|
||||
for(uint8_t e=0; e<NUM_EYES; e++) {
|
||||
if(eye[e].blink.state == NOBLINK) {
|
||||
eye[e].blink.state = ENBLINK;
|
||||
eye[e].blink.startTime = t;
|
||||
eye[e].blink.duration = blinkDuration;
|
||||
}
|
||||
}
|
||||
timeToNextBlink = blinkDuration * 3 + random(4000000);
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
/*
|
||||
if(eye[eyeIndex].blink.state) { // Eye currently blinking?
|
||||
// Check if current blink state time has elapsed
|
||||
if((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration) {
|
||||
// Yes -- increment blink state, unless...
|
||||
if((eye[eyeIndex].blink.state == ENBLINK) && // Enblinking and...
|
||||
((digitalRead(BLINK_PIN) == LOW) || // blink or wink held...
|
||||
digitalRead(eye[eyeIndex].blink.pin) == LOW)) {
|
||||
// Don't advance state yet -- eye is held closed instead
|
||||
} else { // No buttons, or other state...
|
||||
if(++eye[eyeIndex].blink.state > DEBLINK) { // Deblinking finished?
|
||||
eye[eyeIndex].blink.state = NOBLINK; // No longer blinking
|
||||
} else { // Advancing from ENBLINK to DEBLINK mode
|
||||
eye[eyeIndex].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
|
||||
eye[eyeIndex].blink.startTime = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Not currently blinking...check buttons!
|
||||
if(digitalRead(BLINK_PIN) == LOW) {
|
||||
// Manually-initiated blinks have random durations like auto-blink
|
||||
uint32_t blinkDuration = random(36000, 72000);
|
||||
for(uint8_t e=0; e<NUM_EYES; e++) {
|
||||
if(eye[e].blink.state == NOBLINK) {
|
||||
eye[e].blink.state = ENBLINK;
|
||||
eye[e].blink.startTime = t;
|
||||
eye[e].blink.duration = blinkDuration;
|
||||
}
|
||||
}
|
||||
} else if(digitalRead(eye[eyeIndex].blink.pin) == LOW) { // Wink!
|
||||
eye[eyeIndex].blink.state = ENBLINK;
|
||||
eye[eyeIndex].blink.startTime = t;
|
||||
eye[eyeIndex].blink.duration = random(45000, 90000);
|
||||
}
|
||||
}
|
||||
*/
|
||||
// Process motion, blinking and iris scale into renderable values
|
||||
|
||||
// Iris scaling: remap from 0-1023 input to iris map height pixel units
|
||||
iScale = ((IRIS_MAP_HEIGHT + 1) * 1024) /
|
||||
(1024 - (iScale * (IRIS_MAP_HEIGHT - 1) / IRIS_MAP_HEIGHT));
|
||||
|
||||
// Scale eye X/Y positions (0-1023) to pixel units used by drawEye()
|
||||
eyeX = map(eyeX, 0, 1023, 0, SCLERA_WIDTH - 128);
|
||||
eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
|
||||
if(eyeIndex == 1) eyeX = (SCLERA_WIDTH - 128) - eyeX; // Mirrored display
|
||||
|
||||
// Horizontal position is offset so that eyes are very slightly crossed
|
||||
// to appear fixated (converged) at a conversational distance. Number
|
||||
// here was extracted from my posterior and not mathematically based.
|
||||
// I suppose one could get all clever with a range sensor, but for now...
|
||||
eyeX += 4;
|
||||
if(eyeX > (SCLERA_WIDTH - 128)) eyeX = (SCLERA_WIDTH - 128);
|
||||
|
||||
// Eyelids are rendered using a brightness threshold image. This same
|
||||
// map can be used to simplify another problem: making the upper eyelid
|
||||
// track the pupil (eyes tend to open only as much as needed -- e.g. look
|
||||
// down and the upper eyelid drops). Just sample a point in the upper
|
||||
// lid map slightly above the pupil to determine the rendering threshold.
|
||||
static uint8_t uThreshold = 128;
|
||||
uint8_t lThreshold, n;
|
||||
|
||||
#ifdef TRACKING
|
||||
int16_t sampleX = SCLERA_WIDTH / 2 - (eyeX / 2), // Reduce X influence
|
||||
sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4);
|
||||
// Eyelid is slightly asymmetrical, so two readings are taken, averaged
|
||||
if(sampleY < 0) n = 0;
|
||||
else n = (pgm_read_byte(upper + sampleY * SCREEN_WIDTH + sampleX) +
|
||||
pgm_read_byte(upper + sampleY * SCREEN_WIDTH + (SCREEN_WIDTH - 1 - sampleX))) / 2;
|
||||
uThreshold = (uThreshold * 3 + n) / 4; // Filter/soften motion
|
||||
// Lower eyelid doesn't track the same way, but seems to be pulled upward
|
||||
// by tension from the upper lid.
|
||||
lThreshold = 254 - uThreshold;
|
||||
#else // No tracking -- eyelids full open unless blink modifies them
|
||||
uThreshold = lThreshold = 0;
|
||||
#endif
|
||||
|
||||
// The upper/lower thresholds are then scaled relative to the current
|
||||
// blink position so that blinks work together with pupil tracking.
|
||||
if(eye[eyeIndex].blink.state) { // Eye currently blinking?
|
||||
uint32_t s = (t - eye[eyeIndex].blink.startTime);
|
||||
if(s >= eye[eyeIndex].blink.duration) s = 255; // At or past blink end
|
||||
else s = 255 * s / eye[eyeIndex].blink.duration; // Mid-blink
|
||||
s = (eye[eyeIndex].blink.state == DEBLINK) ? 1 + s : 256 - s;
|
||||
n = (uThreshold * s + 254 * (257 - s)) / 256;
|
||||
lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
|
||||
} else {
|
||||
n = uThreshold;
|
||||
}
|
||||
|
||||
// Pass all the derived values to the eye-rendering function:
|
||||
drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// AUTONOMOUS IRIS SCALING (if no photocell or dial) -----------------------
|
||||
|
||||
#if !defined(IRIS_PIN) || (IRIS_PIN < 0)
|
||||
|
||||
// Autonomous iris motion uses a fractal behavior to similate both the major
|
||||
// reaction of the eye plus the continuous smaller adjustments that occur.
|
||||
|
||||
uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris;
|
||||
|
||||
void split( // Subdivides motion path into two sub-paths w/randimization
|
||||
int16_t startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start
|
||||
int16_t endValue, // Iris scale value at end
|
||||
uint32_t startTime, // micros() at start
|
||||
int32_t duration, // Start-to-end time, in microseconds
|
||||
int16_t range) { // Allowable scale value variance when subdividing
|
||||
|
||||
if(range >= 8) { // Limit subdvision count, because recursion
|
||||
range /= 2; // Split range & time in half for subdivision,
|
||||
duration /= 2; // then pick random center point within range:
|
||||
int16_t midValue = (startValue + endValue - range) / 2 + random(range);
|
||||
uint32_t midTime = startTime + duration;
|
||||
split(startValue, midValue, startTime, duration, range); // First half
|
||||
split(midValue , endValue, midTime , duration, range); // Second half
|
||||
} else { // No more subdivisons, do iris motion...
|
||||
int32_t dt; // Time (micros) since start of motion
|
||||
int16_t v; // Interim value
|
||||
while((dt = (micros() - startTime)) < duration) {
|
||||
v = startValue + (((endValue - startValue) * dt) / duration);
|
||||
if(v < IRIS_MIN) v = IRIS_MIN; // Clip just in case
|
||||
else if(v > IRIS_MAX) v = IRIS_MAX;
|
||||
frame(v); // Draw frame w/interim iris scale value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !IRIS_PIN
|
||||
|
||||
|
||||
// MAIN LOOP -- runs continuously after setup() ----------------------------
|
||||
|
||||
void loop() {
|
||||
|
||||
#if defined(IRIS_PIN) && (IRIS_PIN >= 0) // Interactive iris
|
||||
|
||||
uint16_t v = 512; //analogRead(IRIS_PIN); // Raw dial/photocell reading
|
||||
#ifdef IRIS_PIN_FLIP
|
||||
v = 1023 - v;
|
||||
#endif
|
||||
v = map(v, 0, 1023, IRIS_MIN, IRIS_MAX); // Scale to iris range
|
||||
#ifdef IRIS_SMOOTH // Filter input (gradual motion)
|
||||
static uint16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2;
|
||||
irisValue = ((irisValue * 15) + v) / 16;
|
||||
frame(irisValue);
|
||||
#else // Unfiltered (immediate motion)
|
||||
frame(v);
|
||||
#endif // IRIS_SMOOTH
|
||||
|
||||
#else // Autonomous iris scaling -- invoke recursive function
|
||||
|
||||
newIris = random(IRIS_MIN, IRIS_MAX);
|
||||
split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN);
|
||||
oldIris = newIris;
|
||||
|
||||
#endif // IRIS_PIN
|
||||
|
||||
//screenshotToConsole();
|
||||
}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,220 @@
|
||||
// Include code in this tab and call screenshotToConsole() this dumps an
|
||||
// image off the screen and sends it to a PC via the serial port in a Run
|
||||
// Length Encoded format for viewing with a "ILIScreenshotViewer" utility.
|
||||
|
||||
// The PC "ILIScreenshotViewer" is part of the ILI9241_due library in the
|
||||
// Tools folder, that library can be found here:
|
||||
// https://github.com/marekburiak/ILI9341_Due
|
||||
|
||||
// Converted by Bodmer to operate with the TFT_ILI9341_ESP library:
|
||||
// https://github.com/Bodmer/TFT_ILI9341_ESP
|
||||
|
||||
/*
|
||||
The functions below have been adapted from the ILI9341_due library, the file
|
||||
header from the .cpp source file is included below:
|
||||
|
||||
ILI9341_due_.cpp - Arduino Due library for interfacing with ILI9341-based TFTs
|
||||
|
||||
Copyright (c) 2014 Marek Buriak
|
||||
|
||||
This library is based on ILI9341_t3 library from Paul Stoffregen
|
||||
(https://github.com/PaulStoffregen/ILI9341_t3), Adafruit_ILI9341
|
||||
and Adafruit_GFX libraries from Limor Fried/Ladyada
|
||||
(https://github.com/adafruit/Adafruit_ILI9341).
|
||||
|
||||
This file is part of the Arduino ILI9341_due library.
|
||||
Sources for this library can be found at https://github.com/marekburiak/ILI9341_Due.
|
||||
|
||||
ILI9341_due is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
ILI9341_due is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details:
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
//====================================================================================
|
||||
|
||||
void screenshotToConsole()
|
||||
{
|
||||
uint8_t e = 0;
|
||||
uint8_t lastColor[3];
|
||||
uint8_t color[3];
|
||||
uint32_t sameColorPixelCount = 0;
|
||||
uint16_t sameColorPixelCount16 = 0;
|
||||
uint32_t sameColorStartIndex = 0;
|
||||
uint32_t totalImageDataLength = 0;
|
||||
|
||||
// delay(1000);
|
||||
|
||||
// Header text
|
||||
Serial.println((eye[e].tft.width() - 1));
|
||||
Serial.println((eye[e].tft.height() - 1));
|
||||
Serial.println(F("==== PIXEL DATA START ===="));
|
||||
|
||||
// Get first pixel to prime the Run Length Encoded
|
||||
// Function format is: tft.readRectRGB( x, y, width, height, buffer);
|
||||
// color is a pointer to a buffer that the RGB 8 bit values are piped into
|
||||
// the buffer size must be >= (width * height * 3) bytes
|
||||
eye[e].tft.readRectRGB(0, 0, 1, 1, color); // 1 x 1 so reading 1 pixel at 0,0
|
||||
|
||||
lastColor[0] = color[0]; // Red
|
||||
lastColor[1] = color[1]; // Green
|
||||
lastColor[2] = color[2]; // Blue
|
||||
|
||||
printHex8(color, 3); //Send color of the first pixel to serial port
|
||||
totalImageDataLength += 6;
|
||||
sameColorStartIndex = 0;
|
||||
|
||||
for (uint32_t py = 0; py < (eye[e].tft.height() - 1); py++)
|
||||
{
|
||||
for (uint32_t px = 0; px < (eye[e].tft.width() - 1); px++)
|
||||
{
|
||||
uint32_t i = px + eye[e].tft.width() * py;
|
||||
yield();
|
||||
if (i)
|
||||
{
|
||||
eye[e].tft.readRectRGB(px, py, 1, 1, color);
|
||||
|
||||
if (color[0] != lastColor[0] ||
|
||||
color[1] != lastColor[1] ||
|
||||
color[2] != lastColor[2])
|
||||
{
|
||||
sameColorPixelCount = i - sameColorStartIndex;
|
||||
if (sameColorPixelCount > 65535)
|
||||
{
|
||||
sameColorPixelCount16 = 65535;
|
||||
printHex16(&sameColorPixelCount16, 1);
|
||||
printHex8(lastColor, 3);
|
||||
totalImageDataLength += 10;
|
||||
sameColorPixelCount16 = sameColorPixelCount - 65535;
|
||||
}
|
||||
else
|
||||
sameColorPixelCount16 = sameColorPixelCount;
|
||||
printHex16(&sameColorPixelCount16, 1);
|
||||
printHex8(color, 3);
|
||||
totalImageDataLength += 10;
|
||||
|
||||
sameColorStartIndex = i;
|
||||
lastColor[0] = color[0];
|
||||
lastColor[1] = color[1];
|
||||
lastColor[2] = color[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sameColorPixelCount = (uint32_t)eye[e].tft.width() * (uint32_t)eye[e].tft.height() - sameColorStartIndex;
|
||||
if (sameColorPixelCount > 65535)
|
||||
{
|
||||
sameColorPixelCount16 = 65535;
|
||||
printHex16(&sameColorPixelCount16, 1);
|
||||
printHex8(lastColor, 3);
|
||||
totalImageDataLength += 10;
|
||||
sameColorPixelCount16 = sameColorPixelCount - 65535;
|
||||
}
|
||||
else
|
||||
sameColorPixelCount16 = sameColorPixelCount;
|
||||
printHex16(&sameColorPixelCount16, 1);
|
||||
totalImageDataLength += 4;
|
||||
printHex32(&totalImageDataLength, 1);
|
||||
|
||||
// Footer text
|
||||
Serial.println();
|
||||
Serial.println(F("==== PIXEL DATA END ===="));
|
||||
Serial.print(F("Total Image Data Length: "));
|
||||
Serial.println(totalImageDataLength);
|
||||
}
|
||||
|
||||
void printHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex
|
||||
{
|
||||
char tmp[length * 2 + 1];
|
||||
byte first;
|
||||
byte second;
|
||||
for (int i = 0; i < length; i++) {
|
||||
first = (data[i] >> 4) & 0x0f;
|
||||
second = data[i] & 0x0f;
|
||||
// base for converting single digit numbers to ASCII is 48
|
||||
// base for 10-16 to become upper-case characters A-F is 55
|
||||
// note: difference is 7
|
||||
tmp[i * 2] = first + 48;
|
||||
tmp[i * 2 + 1] = second + 48;
|
||||
if (first > 9) tmp[i * 2] += 7;
|
||||
if (second > 9) tmp[i * 2 + 1] += 7;
|
||||
}
|
||||
tmp[length * 2] = 0;
|
||||
Serial.print(tmp);
|
||||
}
|
||||
|
||||
void printHex16(uint16_t *data, uint8_t length) // prints 8-bit data in hex
|
||||
{
|
||||
char tmp[length * 4 + 1];
|
||||
byte first;
|
||||
byte second;
|
||||
byte third;
|
||||
byte fourth;
|
||||
for (int i = 0; i < length; i++) {
|
||||
first = (data[i] >> 12) & 0x0f;
|
||||
second = (data[i] >> 8) & 0x0f;
|
||||
third = (data[i] >> 4) & 0x0f;
|
||||
fourth = data[i] & 0x0f;
|
||||
//Serial << first << " " << second << " " << third << " " << fourth << endl;
|
||||
// base for converting single digit numbers to ASCII is 48
|
||||
// base for 10-16 to become upper-case characters A-F is 55
|
||||
// note: difference is 7
|
||||
tmp[i * 4] = first + 48;
|
||||
tmp[i * 4 + 1] = second + 48;
|
||||
tmp[i * 4 + 2] = third + 48;
|
||||
tmp[i * 4 + 3] = fourth + 48;
|
||||
//tmp[i*5+4] = 32; // add trailing space
|
||||
if (first > 9) tmp[i * 4] += 7;
|
||||
if (second > 9) tmp[i * 4 + 1] += 7;
|
||||
if (third > 9) tmp[i * 4 + 2] += 7;
|
||||
if (fourth > 9) tmp[i * 4 + 3] += 7;
|
||||
}
|
||||
tmp[length * 4] = 0;
|
||||
Serial.print(tmp);
|
||||
}
|
||||
|
||||
void printHex32(uint32_t *data, uint8_t length) // prints 8-bit data in hex
|
||||
{
|
||||
char tmp[length * 8 + 1];
|
||||
byte dataByte[8];
|
||||
for (int i = 0; i < length; i++) {
|
||||
dataByte[0] = (data[i] >> 28) & 0x0f;
|
||||
dataByte[1] = (data[i] >> 24) & 0x0f;
|
||||
dataByte[2] = (data[i] >> 20) & 0x0f;
|
||||
dataByte[3] = (data[i] >> 16) & 0x0f;
|
||||
dataByte[4] = (data[i] >> 12) & 0x0f;
|
||||
dataByte[5] = (data[i] >> 8) & 0x0f;
|
||||
dataByte[6] = (data[i] >> 4) & 0x0f;
|
||||
dataByte[7] = data[i] & 0x0f;
|
||||
//Serial << first << " " << second << " " << third << " " << fourth << endl;
|
||||
// base for converting single digit numbers to ASCII is 48
|
||||
// base for 10-16 to become upper-case characters A-F is 55
|
||||
// note: difference is 7
|
||||
tmp[i * 4] = dataByte[0] + 48;
|
||||
tmp[i * 4 + 1] = dataByte[1] + 48;
|
||||
tmp[i * 4 + 2] = dataByte[2] + 48;
|
||||
tmp[i * 4 + 3] = dataByte[3] + 48;
|
||||
tmp[i * 4 + 4] = dataByte[4] + 48;
|
||||
tmp[i * 4 + 5] = dataByte[5] + 48;
|
||||
tmp[i * 4 + 6] = dataByte[6] + 48;
|
||||
tmp[i * 4 + 7] = dataByte[7] + 48;
|
||||
//tmp[i*5+4] = 32; // add trailing space
|
||||
if (dataByte[0] > 9) tmp[i * 4] += 7;
|
||||
if (dataByte[1] > 9) tmp[i * 4 + 1] += 7;
|
||||
if (dataByte[2] > 9) tmp[i * 4 + 2] += 7;
|
||||
if (dataByte[3] > 9) tmp[i * 4 + 3] += 7;
|
||||
if (dataByte[4] > 9) tmp[i * 4 + 4] += 7;
|
||||
if (dataByte[5] > 9) tmp[i * 4 + 5] += 7;
|
||||
if (dataByte[6] > 9) tmp[i * 4 + 6] += 7;
|
||||
if (dataByte[7] > 9) tmp[i * 4 + 7] += 7;
|
||||
}
|
||||
tmp[length * 8] = 0;
|
||||
Serial.print(tmp);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
This sketch demonstrates the use of the horizontal and vertical gradient
|
||||
rectangle fill functions.
|
||||
|
||||
Example for library:
|
||||
https://github.com/Bodmer/TFT_eSPI
|
||||
|
||||
Created by Bodmer 27/1/22
|
||||
*/
|
||||
|
||||
#include <TFT_eSPI.h> // Include the graphics library
|
||||
TFT_eSPI tft = TFT_eSPI(); // Create object "tft"
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Setup
|
||||
// -------------------------------------------------------------------------
|
||||
void setup(void) {
|
||||
tft.init();
|
||||
tft.setRotation(1);
|
||||
tft.fillScreen(TFT_DARKGREY);
|
||||
tft.setTextFont(2);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Main loop
|
||||
// -------------------------------------------------------------------------
|
||||
void loop()
|
||||
{
|
||||
tft.fillRectHGradient(0, 0, 160, 50, TFT_MAGENTA, TFT_BLUE);
|
||||
tft.setCursor(10,10);
|
||||
tft.print("Horizontal gradient");
|
||||
|
||||
tft.fillRectVGradient(0, 60, 160, 50, TFT_ORANGE, TFT_RED);
|
||||
tft.setCursor(10,70);
|
||||
tft.print("Vertical gradient");
|
||||
|
||||
while(1) delay(100); // Wait here
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
Example for TFT_eSPI library
|
||||
|
||||
This example shows the use of a Adafruit_GFX custom font with a
|
||||
character code range of 32 - 255, this means accented characters
|
||||
(amongst others) are available.
|
||||
|
||||
The custom font file is attached to this sketch as a header file. The
|
||||
font data has been created following the instructions here:
|
||||
https://www.youtube.com/watch?v=L8MmTISmwZ8
|
||||
|
||||
Note that online converters for Adafruit_GFX compatible fonts are
|
||||
available but these typically only use characters in the range 32-127,
|
||||
and thus do not include the accented characters. These online converters
|
||||
can however still be used with this sketch but the example characters
|
||||
used must be changed.
|
||||
|
||||
The Arduino IDE uses UTF8 encoding for these characters. The TFT_eSPI
|
||||
library also expects characters in the range 128 to 255 to be UTF-8
|
||||
encoded. See link here for details:
|
||||
|
||||
https://playground.arduino.cc/Code/UTF-8
|
||||
|
||||
To summarise, UTF-8 characters are encoded as more than 1 byte so care must
|
||||
be taken:
|
||||
|
||||
char c = 'µ'; // Wrong
|
||||
char bad[4] = "5µA"; // Wrong
|
||||
char good[] = "5µA"; // Good
|
||||
String okay = "5µA"; // Good
|
||||
|
||||
Created by Bodmer 08/02/19
|
||||
|
||||
Make sure LOAD_GFXFF is defined in the used User_Setup file
|
||||
within the library folder.
|
||||
|
||||
#########################################################################
|
||||
###### DON'T FORGET TO UPDATE THE User_Setup.h FILE IN THE LIBRARY ######
|
||||
###### TO SELECT YOUR DISPLAY TYPE, PINS USED AND ENABLE FONTS ######
|
||||
#########################################################################
|
||||
*/
|
||||
|
||||
#define TEST_TEXT "ßäöü ñâàå" // Text that will be printed on screen in the font
|
||||
//#define TEST_TEXT "Hello" // Text that will be printed on screen in the font
|
||||
|
||||
#include "SPI.h"
|
||||
#include "TFT_eSPI.h"
|
||||
|
||||
// The custom font file attached to this sketch must be included
|
||||
#include "MyFont.h"
|
||||
|
||||
// Stock font and GFXFF reference handle
|
||||
#define GFXFF 1
|
||||
|
||||
// Easily remembered name for the font
|
||||
#define MYFONT32 &myFont32pt8b
|
||||
|
||||
// Use hardware SPI
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
|
||||
void setup(void) {
|
||||
|
||||
Serial.begin(250000);
|
||||
|
||||
tft.begin();
|
||||
|
||||
tft.setRotation(1);
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||||
// Show custom fonts
|
||||
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||||
|
||||
// Where font sizes increase the screen is not cleared as the larger fonts overwrite
|
||||
// the smaller one with the background colour.
|
||||
|
||||
// We can set the text datum to be Top, Middle, Bottom vertically and Left, Centre
|
||||
// and Right horizontally. These are the text datums that can be used:
|
||||
// TL_DATUM = Top left (default)
|
||||
// TC_DATUM = Top centre
|
||||
// TR_DATUM = Top right
|
||||
// ML_DATUM = Middle left
|
||||
// MC_DATUM = Middle centre <<< This is used below
|
||||
// MR_DATUM = Middle right
|
||||
// BL_DATUM = Bottom left
|
||||
// BC_DATUM = Bottom centre
|
||||
// BR_DATUM = Bottom right
|
||||
// L_BASELINE = Left character baseline (Line the 'A' character would sit on)
|
||||
// C_BASELINE = Centre character baseline
|
||||
// R_BASELINE = Right character baseline
|
||||
|
||||
//Serial.println();
|
||||
|
||||
// Set text datum to middle centre (MC_DATUM)
|
||||
tft.setTextDatum(MC_DATUM);
|
||||
|
||||
// Set text colour to white with black background
|
||||
// Unlike the stock Adafruit_GFX library, the TFT_eSPI library DOES optionally draw
|
||||
// the background colour for the custom and Free Fonts when using drawString()
|
||||
tft.setTextColor(TFT_WHITE, TFT_BLACK); // White characters on black background
|
||||
//tft.setTextColor(TFT_WHITE); // or white characters, no background
|
||||
|
||||
tft.fillScreen(TFT_BLUE); // Clear screen
|
||||
tft.setFreeFont(MYFONT32); // Select the font
|
||||
tft.drawString("MyFont 32", 160, 60, GFXFF); // Print the name of the font
|
||||
tft.setFreeFont(MYFONT32); // Select the font
|
||||
tft.drawString(TEST_TEXT, 160, 140, GFXFF); // Print the test text in the custom font
|
||||
delay(2000);
|
||||
|
||||
// Setting textDatum does nothing when using tft.print
|
||||
tft.fillScreen(TFT_BLUE); // Clear screen
|
||||
tft.setCursor(0,60); // To be compatible with Adafruit_GFX the cursor datum is always bottom left
|
||||
tft.print("âäàå"); // Using tft.print means text background is NEVER rendered
|
||||
delay(2000);
|
||||
|
||||
// Reset text padding to zero (default)
|
||||
tft.setTextPadding(0);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,206 @@
|
||||
// Example of drawing a graphical "switch" and using
|
||||
// the touch screen to change it's state.
|
||||
|
||||
// This sketch does not use the libraries button drawing
|
||||
// and handling functions.
|
||||
|
||||
// Based on Adafruit_GFX library onoffbutton example.
|
||||
|
||||
// Touch handling for XPT2046 based screens is handled by
|
||||
// the TFT_eSPI library.
|
||||
|
||||
// Calibration data is stored in SPIFFS so we need to include it
|
||||
#include "FS.h"
|
||||
|
||||
#include <SPI.h>
|
||||
|
||||
#include <TFT_eSPI.h> // Hardware-specific library
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library
|
||||
|
||||
// This is the file name used to store the touch coordinate
|
||||
// calibration data. Change the name to start a new calibration.
|
||||
#define CALIBRATION_FILE "/TouchCalData3"
|
||||
|
||||
// Set REPEAT_CAL to true instead of false to run calibration
|
||||
// again, otherwise it will only be done once.
|
||||
// Repeat calibration if you change the screen rotation.
|
||||
#define REPEAT_CAL false
|
||||
|
||||
bool SwitchOn = false;
|
||||
|
||||
// Comment out to stop drawing black spots
|
||||
#define BLACK_SPOT
|
||||
|
||||
// Switch position and size
|
||||
#define FRAME_X 100
|
||||
#define FRAME_Y 64
|
||||
#define FRAME_W 120
|
||||
#define FRAME_H 50
|
||||
|
||||
// Red zone size
|
||||
#define REDBUTTON_X FRAME_X
|
||||
#define REDBUTTON_Y FRAME_Y
|
||||
#define REDBUTTON_W (FRAME_W/2)
|
||||
#define REDBUTTON_H FRAME_H
|
||||
|
||||
// Green zone size
|
||||
#define GREENBUTTON_X (REDBUTTON_X + REDBUTTON_W)
|
||||
#define GREENBUTTON_Y FRAME_Y
|
||||
#define GREENBUTTON_W (FRAME_W/2)
|
||||
#define GREENBUTTON_H FRAME_H
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------------------
|
||||
void setup(void)
|
||||
{
|
||||
Serial.begin(9600);
|
||||
tft.init();
|
||||
|
||||
// Set the rotation before we calibrate
|
||||
tft.setRotation(1);
|
||||
|
||||
// call screen calibration
|
||||
touch_calibrate();
|
||||
|
||||
// clear screen
|
||||
tft.fillScreen(TFT_BLUE);
|
||||
|
||||
// Draw button (this example does not use library Button class)
|
||||
redBtn();
|
||||
}
|
||||
//------------------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------------------
|
||||
void loop()
|
||||
{
|
||||
uint16_t x, y;
|
||||
|
||||
// See if there's any touch data for us
|
||||
if (tft.getTouch(&x, &y))
|
||||
{
|
||||
// Draw a block spot to show where touch was calculated to be
|
||||
#ifdef BLACK_SPOT
|
||||
tft.fillCircle(x, y, 2, TFT_BLACK);
|
||||
#endif
|
||||
|
||||
if (SwitchOn)
|
||||
{
|
||||
if ((x > REDBUTTON_X) && (x < (REDBUTTON_X + REDBUTTON_W))) {
|
||||
if ((y > REDBUTTON_Y) && (y <= (REDBUTTON_Y + REDBUTTON_H))) {
|
||||
Serial.println("Red btn hit");
|
||||
redBtn();
|
||||
}
|
||||
}
|
||||
}
|
||||
else //Record is off (SwitchOn == false)
|
||||
{
|
||||
if ((x > GREENBUTTON_X) && (x < (GREENBUTTON_X + GREENBUTTON_W))) {
|
||||
if ((y > GREENBUTTON_Y) && (y <= (GREENBUTTON_Y + GREENBUTTON_H))) {
|
||||
Serial.println("Green btn hit");
|
||||
greenBtn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println(SwitchOn);
|
||||
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
||||
void touch_calibrate()
|
||||
{
|
||||
uint16_t calData[5];
|
||||
uint8_t calDataOK = 0;
|
||||
|
||||
// check file system exists
|
||||
if (!SPIFFS.begin()) {
|
||||
Serial.println("Formatting file system");
|
||||
SPIFFS.format();
|
||||
SPIFFS.begin();
|
||||
}
|
||||
|
||||
// check if calibration file exists and size is correct
|
||||
if (SPIFFS.exists(CALIBRATION_FILE)) {
|
||||
if (REPEAT_CAL)
|
||||
{
|
||||
// Delete if we want to re-calibrate
|
||||
SPIFFS.remove(CALIBRATION_FILE);
|
||||
}
|
||||
else
|
||||
{
|
||||
File f = SPIFFS.open(CALIBRATION_FILE, "r");
|
||||
if (f) {
|
||||
if (f.readBytes((char *)calData, 14) == 14)
|
||||
calDataOK = 1;
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (calDataOK && !REPEAT_CAL) {
|
||||
// calibration data valid
|
||||
tft.setTouch(calData);
|
||||
} else {
|
||||
// data not valid so recalibrate
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setCursor(20, 0);
|
||||
tft.setTextFont(2);
|
||||
tft.setTextSize(1);
|
||||
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||
|
||||
tft.println("Touch corners as indicated");
|
||||
|
||||
tft.setTextFont(1);
|
||||
tft.println();
|
||||
|
||||
if (REPEAT_CAL) {
|
||||
tft.setTextColor(TFT_RED, TFT_BLACK);
|
||||
tft.println("Set REPEAT_CAL to false to stop this running again!");
|
||||
}
|
||||
|
||||
tft.calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15);
|
||||
|
||||
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
||||
tft.println("Calibration complete!");
|
||||
|
||||
// store data
|
||||
File f = SPIFFS.open(CALIBRATION_FILE, "w");
|
||||
if (f) {
|
||||
f.write((const unsigned char *)calData, 14);
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void drawFrame()
|
||||
{
|
||||
tft.drawRect(FRAME_X, FRAME_Y, FRAME_W, FRAME_H, TFT_BLACK);
|
||||
}
|
||||
|
||||
// Draw a red button
|
||||
void redBtn()
|
||||
{
|
||||
tft.fillRect(REDBUTTON_X, REDBUTTON_Y, REDBUTTON_W, REDBUTTON_H, TFT_RED);
|
||||
tft.fillRect(GREENBUTTON_X, GREENBUTTON_Y, GREENBUTTON_W, GREENBUTTON_H, TFT_DARKGREY);
|
||||
drawFrame();
|
||||
tft.setTextColor(TFT_WHITE);
|
||||
tft.setTextSize(2);
|
||||
tft.setTextDatum(MC_DATUM);
|
||||
tft.drawString("ON", GREENBUTTON_X + (GREENBUTTON_W / 2), GREENBUTTON_Y + (GREENBUTTON_H / 2));
|
||||
SwitchOn = false;
|
||||
}
|
||||
|
||||
// Draw a green button
|
||||
void greenBtn()
|
||||
{
|
||||
tft.fillRect(GREENBUTTON_X, GREENBUTTON_Y, GREENBUTTON_W, GREENBUTTON_H, TFT_GREEN);
|
||||
tft.fillRect(REDBUTTON_X, REDBUTTON_Y, REDBUTTON_W, REDBUTTON_H, TFT_DARKGREY);
|
||||
drawFrame();
|
||||
tft.setTextColor(TFT_WHITE);
|
||||
tft.setTextSize(2);
|
||||
tft.setTextDatum(MC_DATUM);
|
||||
tft.drawString("OFF", REDBUTTON_X + (REDBUTTON_W / 2) + 1, REDBUTTON_Y + (REDBUTTON_H / 2));
|
||||
SwitchOn = true;
|
||||
}
|
||||
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
The TFT_eSPI library incorporates an Adafruit_GFX compatible
|
||||
button handling class.
|
||||
|
||||
This example displays a column of buttons with varying label
|
||||
alignments.
|
||||
|
||||
The sketch has been tested on the ESP32 (which supports SPIFFS)
|
||||
|
||||
Adjust the definitions below according to your screen size
|
||||
*/
|
||||
|
||||
#include "FS.h"
|
||||
|
||||
#include <SPI.h>
|
||||
|
||||
#include <TFT_eSPI.h>
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
|
||||
// This is the file name used to store the calibration data
|
||||
// You can change this to create new calibration files.
|
||||
// The SPIFFS file name must start with "/".
|
||||
#define CALIBRATION_FILE "/TouchCalData1"
|
||||
|
||||
// Set REPEAT_CAL to true instead of false to run calibration
|
||||
// again, otherwise it will only be done once.
|
||||
// Repeat calibration if you change the screen rotation.
|
||||
#define REPEAT_CAL false
|
||||
|
||||
// Keypad start position, key sizes and spacing
|
||||
#define KEY_X 160 // Centre of key
|
||||
#define KEY_Y 50
|
||||
#define KEY_W 320 // Width and height
|
||||
#define KEY_H 22
|
||||
#define KEY_SPACING_X 0 // X and Y gap
|
||||
#define KEY_SPACING_Y 1
|
||||
#define KEY_TEXTSIZE 1 // Font size multiplier
|
||||
#define BUTTON_X_DELTA 22
|
||||
#define NUM_KEYS 6
|
||||
|
||||
TFT_eSPI_Button key[NUM_KEYS];
|
||||
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
|
||||
tft.init();
|
||||
|
||||
// Set the rotation before we calibrate
|
||||
tft.setRotation(1);
|
||||
|
||||
// Check for backlight pin if not connected to VCC
|
||||
#ifndef TFT_BL
|
||||
Serial.println("No TFT backlight pin defined");
|
||||
#else
|
||||
pinMode(TFT_BL, OUTPUT);
|
||||
digitalWrite(TFT_BL, HIGH);
|
||||
#endif
|
||||
|
||||
// call screen calibration
|
||||
touch_calibrate();
|
||||
|
||||
// Clear screen
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
|
||||
tft.setFreeFont(&FreeMono9pt7b);
|
||||
|
||||
drawButtons();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint16_t t_x = 0, t_y = 0; // To store the touch coordinates
|
||||
|
||||
// Get current touch state and coordinates
|
||||
bool pressed = tft.getTouch(&t_x, &t_y);
|
||||
|
||||
// Adjust press state of each key appropriately
|
||||
for (uint8_t b = 0; b < NUM_KEYS; b++) {
|
||||
if (pressed && key[b].contains(t_x, t_y))
|
||||
key[b].press(true); // tell the button it is pressed
|
||||
else
|
||||
key[b].press(false); // tell the button it is NOT pressed
|
||||
}
|
||||
|
||||
// Check if any key has changed state
|
||||
for (uint8_t b = 0; b < NUM_KEYS; b++) {
|
||||
// If button was just pressed, redraw inverted button
|
||||
if (key[b].justPressed()) {
|
||||
Serial.println("Button " + (String)b + " pressed");
|
||||
key[b].drawButton(true, "ML_DATUM + " + (String)(b * 10) + "px");
|
||||
}
|
||||
|
||||
// If button was just released, redraw normal color button
|
||||
if (key[b].justReleased()) {
|
||||
Serial.println("Button " + (String)b + " released");
|
||||
key[b].drawButton(false, "ML_DATUM + " + (String)(b * 10) + "px");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void drawButtons()
|
||||
{
|
||||
// Generate buttons with different size X deltas
|
||||
for (int i = 0; i < NUM_KEYS; i++)
|
||||
{
|
||||
key[i].initButton(&tft,
|
||||
KEY_X + 0 * (KEY_W + KEY_SPACING_X),
|
||||
KEY_Y + i * (KEY_H + KEY_SPACING_Y), // x, y, w, h, outline, fill, text
|
||||
KEY_W,
|
||||
KEY_H,
|
||||
TFT_BLACK, // Outline
|
||||
TFT_CYAN, // Fill
|
||||
TFT_BLACK, // Text
|
||||
"", // 10 Byte Label
|
||||
KEY_TEXTSIZE);
|
||||
|
||||
// Adjust button label X delta according to array position
|
||||
// setLabelDatum(uint16_t x_delta, uint16_t y_delta, uint8_t datum)
|
||||
key[i].setLabelDatum(i * 10 - (KEY_W/2), 0, ML_DATUM);
|
||||
|
||||
// Draw button and specify label string
|
||||
// Specifying label string here will allow more than the default 10 byte label
|
||||
key[i].drawButton(false, "ML_DATUM + " + (String)(i * 10) + "px");
|
||||
}
|
||||
}
|
||||
|
||||
void touch_calibrate()
|
||||
{
|
||||
uint16_t calData[5];
|
||||
uint8_t calDataOK = 0;
|
||||
|
||||
// check file system exists
|
||||
if (!SPIFFS.begin()) {
|
||||
Serial.println("Formatting file system");
|
||||
SPIFFS.format();
|
||||
SPIFFS.begin();
|
||||
}
|
||||
|
||||
// check if calibration file exists and size is correct
|
||||
if (SPIFFS.exists(CALIBRATION_FILE)) {
|
||||
if (REPEAT_CAL)
|
||||
{
|
||||
// Delete if we want to re-calibrate
|
||||
SPIFFS.remove(CALIBRATION_FILE);
|
||||
}
|
||||
else
|
||||
{
|
||||
File f = SPIFFS.open(CALIBRATION_FILE, "r");
|
||||
if (f) {
|
||||
if (f.readBytes((char *)calData, 14) == 14)
|
||||
calDataOK = 1;
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (calDataOK && !REPEAT_CAL) {
|
||||
// calibration data valid
|
||||
tft.setTouch(calData);
|
||||
} else {
|
||||
// data not valid so recalibrate
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setCursor(20, 0);
|
||||
tft.setTextFont(2);
|
||||
tft.setTextSize(1);
|
||||
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||
|
||||
tft.println("Touch corners as indicated");
|
||||
|
||||
tft.setTextFont(1);
|
||||
tft.println();
|
||||
|
||||
if (REPEAT_CAL) {
|
||||
tft.setTextColor(TFT_RED, TFT_BLACK);
|
||||
tft.println("Set REPEAT_CAL to false to stop this running again!");
|
||||
}
|
||||
|
||||
tft.calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15);
|
||||
|
||||
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
||||
tft.println("Calibration complete!");
|
||||
|
||||
// store data
|
||||
File f = SPIFFS.open(CALIBRATION_FILE, "w");
|
||||
if (f) {
|
||||
f.write((const unsigned char *)calData, 14);
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// We need this header file to use FLASH as storage with PROGMEM directive:
|
||||
|
||||
// Icon width and height
|
||||
const uint16_t alertWidth = 32;
|
||||
const uint16_t alertHeight = 32;
|
||||
|
||||
const unsigned short alert[1024] PROGMEM={
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0840,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 0, 32 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x1080,0xAC66,0xEDE8,0xFE69,0xC4C6,0x2901,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 1, 64 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0xBCC6,0xFE68,0xFE68,0xFE6A,0xFE68,0xEDE8,0x18A1,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 2, 96 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x8344,0xFE48,0xFE8C,0xFFDD,0xFFFF,0xFEF0,0xFE48,0xB466,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 3, 128 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x1880,0xEDC7,0xFE48,0xFF99,0xFFBC,0xFF9B,0xFFBD,0xFE6A,0xFE48,0x5A23,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 4, 160 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x9BE5,0xFE28,0xFED0,0xFFBC,0xFF7A,0xFF9A,0xFF9B,0xFF35,0xFE28,0xBCA6,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 5, 192 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x3962,0xFE28,0xFE28,0xFF9A,0xFF79,0xFF9A,0xFF9B,0xFF9A,0xFFBD,0xFE6B,0xFE28,0x72E3,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 6, 224 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0xB465,0xFE28,0xFEF2,0xFF7A,0xFF79,0xFF7A,0xFF9A,0xFF7A,0xFF7A,0xFF78,0xFE28,0xDD67,0x0860,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 7, 256 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x5A22,0xFE07,0xFE29,0xFF9B,0xFF37,0xFF58,0xFF79,0xFF79,0xFF79,0xFF58,0xFF9B,0xFEAE,0xFE07,0x93A4,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 8, 288 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0xC4A5,0xFE07,0xFF15,0xFF37,0xFF36,0xAD11,0x2965,0x2965,0xCDF4,0xFF37,0xFF37,0xFF79,0xFE07,0xFE07,0x2901,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 9, 320 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x7B03,0xFDE7,0xFE4B,0xFF79,0xFEF4,0xFF15,0xB552,0x2945,0x2945,0xDE55,0xFF16,0xFF15,0xFF58,0xFED1,0xFDE7,0xAC25,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 10, 352 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0840,0xDD26,0xFDE7,0xFF57,0xFED3,0xFED2,0xFEF4,0xBD93,0x2124,0x2124,0xDE75,0xFF14,0xFED3,0xFED3,0xFF7A,0xFE08,0xFDE7,0x49A2,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 11, 384 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x9BA4,0xFDC6,0xFE6E,0xFF36,0xFE90,0xFEB1,0xFED3,0xC592,0x2124,0x2124,0xE675,0xFED3,0xFEB2,0xFEB1,0xFEF3,0xFEF3,0xFDC6,0xBC45,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 12, 416 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x3141,0xF5C6,0xF5C7,0xFF58,0xFE90,0xFE6F,0xFE8F,0xFEB1,0xCDB2,0x2104,0x2104,0xF6B4,0xFEB1,0xFE90,0xFE8F,0xFE90,0xFF58,0xFE0A,0xF5C6,0x72A3,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 13, 448 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0xABE4,0xF5A6,0xFEB1,0xFED3,0xFE4E,0xFE6E,0xFE6F,0xFE90,0xD5F2,0x18E3,0x18E3,0xFED4,0xFE90,0xFE6F,0xFE6F,0xFE6E,0xFE91,0xFF36,0xF5A6,0xCCA5,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 14, 480 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x5202,0xF5A6,0xF5C7,0xFF58,0xFE4D,0xFE4D,0xFE4D,0xFE4E,0xFE6F,0xDE11,0x18C3,0x18C3,0xFED3,0xFE6F,0xFE6E,0xFE4E,0xFE4D,0xFE4D,0xFF16,0xFE2C,0xF5A6,0x9363,0x0000,0x0000,0x0000,0x0000,0x0000, // row 15, 512 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0xBC44,0xF585,0xFED3,0xFE6F,0xFE2C,0xFE2C,0xFE2D,0xFE4D,0xFE4E,0xE630,0x10A2,0x2104,0xFED1,0xFE4E,0xFE4D,0xFE4D,0xFE2D,0xFE2C,0xFE4D,0xFF37,0xF586,0xF585,0x28E1,0x0000,0x0000,0x0000,0x0000, // row 16, 544 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x7282,0xF565,0xF5EA,0xFF16,0xFE0B,0xFE0B,0xFE0B,0xFE2C,0xFE2C,0xFE4D,0xF670,0x1082,0x2924,0xFEB0,0xFE2D,0xFE2C,0xFE2C,0xFE2C,0xFE0B,0xFE0B,0xFEB2,0xFE6F,0xF565,0xA383,0x0000,0x0000,0x0000,0x0000, // row 17, 576 pixels
|
||||
0x0000,0x0000,0x0000,0x0840,0xD4C4,0xF565,0xFEF5,0xFE0C,0xFDE9,0xFDEA,0xFE0A,0xFE0B,0xFE0B,0xFE2C,0xFE8F,0x0861,0x2964,0xFE8F,0xFE2C,0xFE0B,0xFE0B,0xFE0B,0xFE0A,0xFDEA,0xFE0B,0xFF37,0xF586,0xF565,0x4181,0x0000,0x0000,0x0000, // row 18, 608 pixels
|
||||
0x0000,0x0000,0x0000,0x9343,0xF545,0xF60C,0xFED3,0xFDC8,0xFDC8,0xFDC9,0xFDE9,0xFDEA,0xFDEA,0xFE0B,0xFE8E,0x0861,0x3184,0xFE6D,0xFE0B,0xFE0A,0xFDEA,0xFDEA,0xFDE9,0xFDC9,0xFDC9,0xFE4E,0xFEB2,0xF545,0xB3E3,0x0000,0x0000,0x0000, // row 19, 640 pixels
|
||||
0x0000,0x0000,0x28E0,0xF544,0xF545,0xFF17,0xFDC8,0xFDA7,0xFDA7,0xFDC8,0xFDC8,0xFDC9,0xFDC9,0xFDE9,0xFE6C,0x10A2,0x39C4,0xFE4C,0xFDEA,0xFDE9,0xFDC9,0xFDC9,0xFDC8,0xFDC8,0xFDA7,0xFDA8,0xFF16,0xF588,0xF544,0x6222,0x0000,0x0000, // row 20, 672 pixels
|
||||
0x0000,0x0000,0xA383,0xF524,0xF64E,0xFE4E,0xFD86,0xFD86,0xFD87,0xFDA7,0xFDA7,0xFDA8,0xFDC8,0xFDC8,0xFE2A,0xA469,0xB4EA,0xFE2A,0xFDC9,0xFDC8,0xFDC8,0xFDA8,0xFDA7,0xFDA7,0xFD87,0xFD86,0xFDEA,0xFED3,0xF524,0xC443,0x0000,0x0000, // row 21, 704 pixels
|
||||
0x0000,0x51C1,0xF504,0xF546,0xFF16,0xF565,0xFD65,0xFD65,0xFD86,0xFD86,0xFD86,0xFDA7,0xFDA7,0xFDA7,0xFDE8,0xFE6A,0xFE4A,0xFDE8,0xFDA7,0xFDA7,0xFDA7,0xFDA7,0xFD86,0xFD86,0xFD86,0xFD65,0xFD65,0xFEB2,0xF5CA,0xF504,0x8AE2,0x0000, // row 22, 736 pixels
|
||||
0x0000,0xB3A2,0xED03,0xFE92,0xFDC9,0xF543,0xF544,0xFD44,0xFD65,0xFD65,0xFD65,0xFD86,0xFD86,0xFD86,0xFDA7,0xFDC7,0xFDC7,0xFDA7,0xFD86,0xFD86,0xFD86,0xFD86,0xFD65,0xFD65,0xFD65,0xFD44,0xF544,0xFD86,0xFEF5,0xED03,0xE4C3,0x1880, // row 23, 768 pixels
|
||||
0x7241,0xECE3,0xF567,0xFED3,0xF523,0xF523,0xF523,0xF543,0xF544,0xF544,0xFD65,0xFD65,0xFD65,0xFD65,0xD4E6,0x39C5,0x39A5,0xD4E6,0xFD86,0xFD65,0xFD65,0xFD65,0xFD65,0xF544,0xF544,0xF543,0xF523,0xF523,0xFE2E,0xF5EC,0xECE3,0x9B42, // row 24, 800 pixels
|
||||
0xD443,0xECE3,0xFED4,0xF565,0xF502,0xF502,0xF522,0xF523,0xF523,0xF543,0xF544,0xF544,0xF544,0xFD65,0x8B64,0x18C3,0x18C3,0x8344,0xFD85,0xFD44,0xF544,0xF544,0xF544,0xF543,0xF523,0xF523,0xF522,0xF502,0xF523,0xFEF5,0xED04,0xECE3, // row 25, 832 pixels
|
||||
0xECC3,0xF5AB,0xFE6F,0xF501,0xF4E1,0xF501,0xF502,0xF502,0xF522,0xF522,0xF523,0xF523,0xF523,0xFD84,0xC504,0x20E1,0x18E1,0xC4E4,0xFD84,0xF543,0xF523,0xF523,0xF523,0xF522,0xF522,0xF502,0xF502,0xF501,0xF501,0xFDC9,0xF62F,0xECC3, // row 26, 864 pixels
|
||||
0xECC2,0xFE92,0xF523,0xF4E0,0xF4E0,0xF4E1,0xF4E1,0xF501,0xF501,0xF502,0xF502,0xF522,0xF522,0xF543,0xFDE3,0xFEA5,0xF6A4,0xFE04,0xF543,0xF522,0xF522,0xF522,0xF502,0xF502,0xF501,0xF501,0xF4E1,0xF4E1,0xF4E0,0xF4E1,0xFED4,0xECC2, // row 27, 896 pixels
|
||||
0xECA2,0xF5EC,0xF4E0,0xF4C0,0xF4E0,0xF4E0,0xF4E0,0xF4E1,0xF4E1,0xF501,0xF501,0xF501,0xF502,0xF502,0xF542,0xFDA2,0xFDA2,0xF542,0xF502,0xF502,0xF502,0xF501,0xF501,0xF501,0xF4E1,0xF4E1,0xF4E0,0xF4E0,0xF4E0,0xF4C0,0xF5A9,0xECA2, // row 28, 928 pixels
|
||||
0xECA2,0xECA2,0xECC2,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4E1,0xF4E2,0xF4E2,0xF4E2,0xF4E2,0xF4E2,0xF4E2,0xF4E2,0xF4E2,0xF4E2,0xF4E1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xECC2,0xECC3,0xECA2, // row 29, 960 pixels
|
||||
0x8AC1,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0x9B01, // row 30, 992 pixels
|
||||
0x0000,0x1880,0x51A0,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x61E0,0x28E0,0x0000}; // row 31, 1024 pixels
|
||||
@@ -0,0 +1,39 @@
|
||||
// We need this header file to use FLASH as storage with PROGMEM directive:
|
||||
|
||||
// Icon width and height
|
||||
const uint16_t closeWidth = 32;
|
||||
const uint16_t closeHeight = 32;
|
||||
|
||||
const unsigned short closeX[1024] PROGMEM={
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x30C3,0x4124,0x61C7,0x61C7,0x4124,0x30E3,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 0, 32 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x48E3,0xA249,0xEB8E,0xFCB2,0xFD14,0xFD75,0xFD96,0xFD34,0xFCF3,0xEBEF,0xA28A,0x4904,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 1, 64 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x58E3,0xC228,0xFC10,0xFD34,0xFE18,0xFE59,0xFE79,0xFE9A,0xFE9A,0xFE9A,0xFE9A,0xFE59,0xFD75,0xFC51,0xC28A,0x5904,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 2, 96 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x2041,0x8945,0xF34D,0xFD34,0xFDB6,0xFD75,0xFD55,0xFD55,0xFD96,0xFDD7,0xFDF7,0xFDF7,0xFDB6,0xFDB6,0xFDD7,0xFDF7,0xFD75,0xF38E,0x8965,0x2041,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 3, 128 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x4082,0xE208,0xF410,0xFD34,0xFC92,0xFBEF,0xFBAE,0xFBEF,0xFC71,0xFD14,0xFD75,0xFDB6,0xFD75,0xFD14,0xFC92,0xFC51,0xFC71,0xFCF3,0xFD75,0xFC30,0xEA28,0x40A2,0x0000,0x0000,0x0000,0x0000,0x0000, // row 4, 160 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x3861,0xE1E7,0xF451,0xFC92,0xFB4D,0xFA49,0xFA49,0xFAEB,0xFBAE,0xFC71,0xFD34,0xFDB6,0xFE18,0xFDB6,0xFD34,0xFC71,0xFBAE,0xFB0C,0xFAEB,0xFBAE,0xFCD3,0xFC71,0xE208,0x4082,0x0000,0x0000,0x0000,0x0000, // row 5, 192 pixels
|
||||
0x0000,0x0000,0x0000,0x1020,0xD986,0xF430,0xFC30,0xFA28,0xF924,0xF965,0xFA8A,0xFB0C,0xFBAE,0xFC51,0xFD14,0xFD75,0xFDB6,0xFD75,0xFD14,0xFC51,0xFC71,0xFBEF,0xFA28,0xF9C7,0xFA8A,0xFC51,0xF430,0xD9A6,0x1020,0x0000,0x0000,0x0000, // row 6, 224 pixels
|
||||
0x0000,0x0000,0x0000,0x78A2,0xEB6D,0xFC30,0xF9C7,0xF861,0xF8A2,0xFA08,0xFEDB,0xFD55,0xFB4D,0xFC10,0xFC92,0xFD14,0xFD34,0xFD14,0xFC92,0xFCB2,0xFF7D,0xFF7D,0xFB2C,0xF945,0xF8E3,0xF9E7,0xFC30,0xEB8E,0x78C3,0x0000,0x0000,0x0000, // row 7, 256 pixels
|
||||
0x0000,0x0000,0x3841,0xD9E7,0xF492,0xF208,0xF041,0xF800,0xF945,0xFE9A,0xFFFF,0xFFFF,0xFD75,0xFB8E,0xFC10,0xFC51,0xFC71,0xFC51,0xFCB2,0xFF7D,0xFFFF,0xFFFF,0xFF3C,0xFA8A,0xF882,0xF841,0xFA08,0xFC92,0xDA08,0x3841,0x0000,0x0000, // row 8, 288 pixels
|
||||
0x0000,0x0000,0x88A2,0xEBCF,0xF2EB,0xF061,0xF000,0xF8E3,0xFE79,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFD75,0xFB4D,0xFBAE,0xFBAE,0xFC71,0xFF7D,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFEFB,0xFA28,0xF800,0xF061,0xF2EB,0xEBEF,0x90C3,0x0000,0x0000, // row 9, 320 pixels
|
||||
0x0000,0x2820,0xD1C7,0xF410,0xE945,0xE800,0xF000,0xFE9A,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFD34,0xFAEB,0xFBCF,0xFF5D,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFF1C,0xF986,0xF000,0xF145,0xF410,0xD1E7,0x2820,0x0000, // row 10, 352 pixels
|
||||
0x0000,0x6841,0xDB2C,0xEACB,0xE041,0xE800,0xF000,0xFEFB,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFD14,0xFF1C,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFBCF,0xF082,0xF000,0xE841,0xEACB,0xE34D,0x7061,0x0000, // row 11, 384 pixels
|
||||
0x0000,0x9861,0xE3CF,0xE186,0xE000,0xE800,0xE800,0xF145,0xFEDB,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFB8E,0xF000,0xF000,0xE800,0xE800,0xE986,0xEBCF,0xA082,0x0000, // row 12, 416 pixels
|
||||
0x0800,0xB8A2,0xE3AE,0xD8A2,0xD800,0xE000,0xE800,0xE800,0xF145,0xFEFB,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFB8E,0xF000,0xF000,0xE800,0xE800,0xE000,0xE0A2,0xEBAE,0xC0C3,0x0800, // row 13, 448 pixels
|
||||
0x1800,0xC124,0xE30C,0xD020,0xD800,0xE000,0xE000,0xE800,0xE800,0xF145,0xFEDB,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFB8E,0xF000,0xF000,0xE800,0xE800,0xE000,0xE000,0xD820,0xE30C,0xC124,0x1800, // row 14, 480 pixels
|
||||
0x2800,0xC165,0xDAAA,0xC800,0xD000,0xD800,0xE000,0xE000,0xE800,0xE800,0xF124,0xFE79,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFB6D,0xF000,0xF000,0xE800,0xE800,0xE000,0xE000,0xD800,0xD000,0xDAAA,0xC165,0x2800, // row 15, 512 pixels
|
||||
0x2000,0xB924,0xD269,0xC800,0xD000,0xD000,0xD800,0xE000,0xE000,0xE800,0xE924,0xFEFB,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xF36D,0xE800,0xE800,0xE800,0xE000,0xE000,0xD800,0xD000,0xD000,0xDA69,0xC145,0x2800, // row 16, 544 pixels
|
||||
0x1000,0xB0A2,0xD28A,0xC000,0xC800,0xD000,0xD000,0xD800,0xD800,0xE165,0xFEFB,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xF3AE,0xE000,0xE000,0xD800,0xD800,0xD000,0xD000,0xC800,0xD28A,0xB8C3,0x1000, // row 17, 576 pixels
|
||||
0x0000,0xA800,0xD2AA,0xB800,0xC000,0xC800,0xC800,0xD000,0xD965,0xFEFB,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xEBAE,0xD800,0xD800,0xD000,0xC800,0xC800,0xC000,0xD2AA,0xB020,0x0000, // row 18, 608 pixels
|
||||
0x0000,0x8000,0xCA69,0xB841,0xB800,0xC000,0xC800,0xD186,0xFEFB,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xEBCF,0xD000,0xC800,0xC800,0xC000,0xC041,0xCA69,0x8000,0x0000, // row 19, 640 pixels
|
||||
0x0000,0x4800,0xC1C7,0xB8E3,0xB800,0xB800,0xC000,0xF69A,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xEBEF,0xFE79,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xE410,0xC841,0xC000,0xB800,0xC0E3,0xC1C7,0x4800,0x0000, // row 20, 672 pixels
|
||||
0x0000,0x1000,0xB061,0xC1E7,0xB000,0xB000,0xB800,0xD269,0xFFBE,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xE38E,0xD000,0xD965,0xF69A,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xDB0C,0xC020,0xB800,0xB000,0xC1E7,0xB061,0x1000,0x0000, // row 21, 704 pixels
|
||||
0x0000,0x0000,0x6000,0xB9C7,0xB061,0xB000,0xB000,0xB800,0xCA49,0xFF9E,0xFFFF,0xFFFF,0xFFFF,0xE38E,0xC800,0xC800,0xC800,0xD186,0xF69A,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xDB0C,0xB800,0xB800,0xB000,0xB061,0xC1C7,0x6000,0x0000,0x0000, // row 22, 736 pixels
|
||||
0x0000,0x0000,0x1800,0xB041,0xB986,0xA800,0xA800,0xB000,0xB000,0xCA49,0xFF7D,0xFFFF,0xDB8E,0xC000,0xC000,0xC000,0xC000,0xC000,0xC986,0xF6DB,0xFFFF,0xFFFF,0xD30C,0xB800,0xB000,0xB000,0xA800,0xB986,0xB041,0x1800,0x0000,0x0000, // row 23, 768 pixels
|
||||
0x0000,0x0000,0x0000,0x5800,0xB0E3,0xA8C3,0xA800,0xA800,0xA800,0xB000,0xCACB,0xD38E,0xB000,0xB800,0xB800,0xB800,0xB800,0xB800,0xB800,0xC145,0xF6DB,0xD34D,0xB000,0xB000,0xA800,0xA800,0xB0C3,0xB0E3,0x5800,0x0000,0x0000,0x0000, // row 24, 800 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x6000,0xB124,0xA882,0xA000,0xA800,0xA800,0xA800,0xA800,0xB000,0xB000,0xB000,0xB000,0xB000,0xB000,0xB000,0xB000,0xB000,0xA800,0xA800,0xA800,0xA800,0xA882,0xB124,0x6000,0x0000,0x0000,0x0000,0x0000, // row 25, 832 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x6000,0xB104,0xA882,0xA000,0xA000,0xA000,0xA800,0xA800,0xA800,0xA800,0xA800,0xA800,0xA800,0xA800,0xA800,0xA800,0xA800,0xA000,0xA000,0xA882,0xB104,0x6000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 26, 864 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x6000,0xB0A2,0xA8C3,0xA020,0xA000,0xA000,0xA000,0xA000,0xA000,0xA000,0xA000,0xA000,0xA000,0xA000,0xA000,0xA000,0xA020,0xA8C3,0xB0A2,0x6000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 27, 896 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x4800,0xA800,0xB0C3,0xA0A2,0x9800,0x9800,0x9800,0x9800,0xA000,0xA000,0xA000,0x9800,0x9800,0x9800,0xA082,0xB0E3,0xA800,0x4800,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 28, 928 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x5800,0xA800,0xB0A2,0xA8E3,0xA0A2,0xA041,0x9800,0x9800,0xA041,0xA0A2,0xA8E3,0xB0A2,0xA800,0x5800,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 29, 960 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x3000,0x6000,0x8800,0xA000,0xA800,0xA800,0xA000,0x8800,0x6000,0x3000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 30, 992 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; // row 31, 1024 pixels
|
||||
@@ -0,0 +1,39 @@
|
||||
// We need this header file to use FLASH as storage with PROGMEM directive:
|
||||
|
||||
// Icon width and height
|
||||
const uint16_t infoWidth = 32;
|
||||
const uint16_t infoHeight = 32;
|
||||
|
||||
const unsigned short info[1024] PROGMEM={
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 0, 32 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0861,0x4A69,0x8C71,0xA514,0xBDF7,0xBDF7,0xA514,0x8C71,0x4A69,0x0861,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 1, 64 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x39E7,0x9CF3,0xEF7D,0xF79E,0xFFDF,0xFFDF,0xFFDF,0xFFDF,0xFFDF,0xFFDF,0xF79E,0xEF7D,0x9CF3,0x39E7,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 2, 96 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x2965,0x9492,0xF79E,0xFFDF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFDF,0xF79E,0x9492,0x2965,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 3, 128 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x630C,0xEF7D,0xFFDF,0xFFFF,0xFFFF,0xFFFF,0xD75F,0xB6BF,0x9E5F,0x963F,0x963F,0x9E5F,0xB6BF,0xD75F,0xFFFF,0xFFFF,0xFFFF,0xFFDF,0xEF7D,0x630C,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 4, 160 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x73AE,0xEF7D,0xFFDF,0xFFFF,0xFFDF,0xBEDF,0x7DBF,0x7DBF,0x7DDF,0x7DDF,0x7DDF,0x7DDF,0x7DDF,0x7DBF,0x759F,0x7DBE,0xBEBF,0xFFDF,0xFFFF,0xFFDF,0xEF7D,0x73AE,0x0000,0x0000,0x0000,0x0000,0x0000, // row 5, 192 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x630C,0xEF7D,0xFFFF,0xFFFF,0xE77F,0x7DBE,0x759E,0x759F,0x7DBF,0x7DDF,0x7DDF,0x85FF,0x7DDF,0x7DDF,0x7DBF,0x759F,0x759E,0x6D7E,0x7DBE,0xDF7F,0xFFFF,0xFFFF,0xEF7D,0x630C,0x0000,0x0000,0x0000,0x0000, // row 6, 224 pixels
|
||||
0x0000,0x0000,0x0000,0x31A6,0xEF5D,0xFFDF,0xFFFF,0xCF1E,0x6D7E,0x6D7E,0x759E,0x759F,0x7DBF,0x7DDF,0x8E1F,0xBEDF,0xC6FF,0x8DFF,0x75BF,0x759F,0x759E,0x6D7E,0x655E,0x655D,0xCF1E,0xFFFF,0xFFDF,0xEF5D,0x31A6,0x0000,0x0000,0x0000, // row 7, 256 pixels
|
||||
0x0000,0x0000,0x0000,0x94B2,0xF7BE,0xFFFF,0xDF5E,0x655D,0x655D,0x6D7E,0x6D7E,0x759E,0x75BF,0x759F,0xEFBF,0xFFFF,0xFFFF,0xEFBF,0x759F,0x759E,0x6D7E,0x6D7E,0x655D,0x653D,0x653D,0xDF5E,0xFFFF,0xF7BE,0x94B2,0x0000,0x0000,0x0000, // row 8, 288 pixels
|
||||
0x0000,0x0000,0x4228,0xEF7D,0xFFFF,0xF7BF,0x6D5D,0x653D,0x655D,0x6D5E,0x6D7E,0x759E,0x759E,0x85DF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0x8DFE,0x6D7E,0x6D7E,0x6D5E,0x655D,0x653D,0x5D1D,0x6D5D,0xF7BF,0xFFFF,0xEF7D,0x4228,0x0000,0x0000, // row 9, 320 pixels
|
||||
0x0000,0x0000,0xA534,0xFFDF,0xFFDF,0xA65D,0x5D1D,0x5D1D,0x653D,0x655E,0x6D7E,0x6D7E,0x6D7E,0x651E,0xE77F,0xFFFF,0xFFFF,0xF7BF,0x5CFE,0x6D7E,0x6D7E,0x655E,0x653D,0x5D1D,0x5D1D,0x54FC,0xA65D,0xFFDF,0xFFDF,0xA534,0x0000,0x0000, // row 10, 352 pixels
|
||||
0x0000,0x18E3,0xEF5D,0xFFFF,0xEF9E,0x5CFC,0x54FC,0x5D1D,0x5D3D,0x653D,0x655E,0x6D7E,0x6D7E,0x653E,0x6D3E,0xB67E,0xBEBE,0x755E,0x5D1E,0x6D5E,0x655E,0x653D,0x5D3D,0x5D1D,0x54FC,0x54DC,0x54FC,0xEF9E,0xFFFF,0xEF5D,0x18E3,0x0000, // row 11, 384 pixels
|
||||
0x0000,0x630C,0xEF7D,0xFFDF,0xB69D,0x54DC,0x54FC,0x5CFC,0x5D1D,0x653D,0x653D,0x655E,0x6D5E,0x655E,0x5CFE,0x4C9D,0x4C7D,0x54DD,0x653E,0x655E,0x653D,0x653D,0x5D1D,0x5CFC,0x54FC,0x54DC,0x4CBC,0xB69D,0xFFDF,0xEF7D,0x630C,0x0000, // row 12, 416 pixels
|
||||
0x0000,0x94B2,0xF7BE,0xFFDF,0x85BC,0x4CBC,0x54DC,0x54FC,0x5CFD,0x5D1D,0x5D3D,0x653D,0x655D,0x653D,0x85DE,0xC6FE,0xC6FE,0x85BE,0x653D,0x653D,0x5D3D,0x5D1D,0x5CFD,0x54FC,0x54DC,0x4CBC,0x4CBB,0x85BC,0xFFDF,0xF7BE,0x94B2,0x0000, // row 13, 448 pixels
|
||||
0x0000,0xB5B6,0xFFDF,0xF7BE,0x651C,0x4CBB,0x4CBC,0x54DC,0x54FC,0x5CFC,0x5D1D,0x5D1D,0x653D,0x5D1D,0xE77E,0xFFDF,0xFFDF,0xEF9E,0x5CFD,0x5D1D,0x5D1D,0x5CFC,0x54FC,0x54DC,0x4CBC,0x4CBB,0x449B,0x651B,0xF7BE,0xFFDF,0xB5B6,0x0000, // row 14, 480 pixels
|
||||
0x0000,0xC638,0xFFDF,0xF7BE,0x54DB,0x449B,0x4CBB,0x4CBC,0x54DC,0x54FC,0x54FC,0x5D1D,0x5D1D,0x7D7D,0xF7BE,0xF7BE,0xF7BE,0xF7BE,0x7D7D,0x5CFD,0x54FC,0x54FC,0x54DC,0x4CBC,0x4CBB,0x449B,0x447B,0x54BB,0xF7BE,0xFFDF,0xC638,0x0000, // row 15, 512 pixels
|
||||
0x0000,0xC638,0xFFDF,0xF79E,0x4CBB,0x449B,0x449B,0x4CBB,0x4CBC,0x54DC,0x54DC,0x54FC,0x54DC,0x753C,0xF7BE,0xF7BE,0xF7BE,0xF7BE,0x753C,0x54DC,0x54DC,0x54DC,0x4CBC,0x4CBB,0x449B,0x449B,0x3C7B,0x4C9B,0xF79E,0xFFDF,0xC638,0x0000, // row 16, 544 pixels
|
||||
0x0000,0xB5B6,0xFFDF,0xF7BE,0x5CFB,0x3C7B,0x447B,0x449B,0x4CBB,0x4CBC,0x4CBC,0x4CDC,0x4CBC,0x6D1C,0xF7BE,0xF7BE,0xF7BE,0xF7BE,0x6CFC,0x4CBC,0x4CBC,0x4CBC,0x4CBB,0x449B,0x447B,0x3C7B,0x3C5A,0x54DB,0xF7BE,0xFFDF,0xB5B6,0x0000, // row 17, 576 pixels
|
||||
0x0000,0x94B2,0xF7BE,0xF7BE,0x755B,0x3C5A,0x3C7B,0x447B,0x449B,0x449B,0x4CBB,0x4CBB,0x4C9B,0x6CFB,0xF79E,0xF79E,0xF79E,0xF79E,0x64FB,0x449B,0x4CBB,0x449B,0x449B,0x447B,0x3C7B,0x3C5A,0x3C5A,0x753B,0xF7BE,0xF7BE,0x9CD3,0x0000, // row 18, 608 pixels
|
||||
0x0000,0x6B4D,0xEF7D,0xF7BE,0xA61C,0x3C5A,0x3C5A,0x3C7B,0x447B,0x447B,0x449B,0x449B,0x447B,0x64DB,0xF79E,0xF79E,0xF79E,0xF79E,0x64DB,0x447B,0x449B,0x447B,0x447B,0x3C7B,0x3C5A,0x3C5A,0x343A,0xA61C,0xF7BE,0xEF7D,0x6B4D,0x0000, // row 19, 640 pixels
|
||||
0x0000,0x2124,0xE71C,0xFFDF,0xDF3D,0x3C5A,0x343A,0x3C5A,0x3C5A,0x3C7B,0x3C7B,0x447B,0x3C5B,0x64BA,0xF79E,0xF79E,0xF79E,0xF79E,0x64BA,0x3C5B,0x3C7B,0x3C7B,0x3C5A,0x3C5A,0x343A,0x343A,0x343A,0xDF3D,0xFFDF,0xE71C,0x2124,0x0000, // row 20, 672 pixels
|
||||
0x0000,0x0000,0xAD75,0xF7BE,0xF79E,0x859B,0x343A,0x343A,0x345A,0x3C5A,0x3C5A,0x3C5A,0x3C5A,0x5C9A,0xEF7D,0xEF7D,0xEF7D,0xEF7D,0x5C9A,0x3C3A,0x3C5A,0x3C5A,0x345A,0x343A,0x343A,0x341A,0x859B,0xF79E,0xF7BE,0xAD75,0x0000,0x0000, // row 21, 704 pixels
|
||||
0x0000,0x0000,0x528A,0xE71C,0xFFDF,0xDF3D,0x3C5A,0x343A,0x343A,0x343A,0x343A,0x3C5A,0x343A,0x4C5A,0xEF7D,0xEF7D,0xEF7D,0xEF7D,0x4C59,0x343A,0x343A,0x343A,0x343A,0x343A,0x341A,0x3C5A,0xDF3D,0xFFDF,0xE71C,0x528A,0x0000,0x0000, // row 22, 736 pixels
|
||||
0x0000,0x0000,0x0000,0x9CD3,0xF79E,0xF7BE,0xBE7C,0x3419,0x341A,0x341A,0x343A,0x343A,0x341A,0x2B99,0xC69C,0xEF7D,0xEF7D,0xD6DC,0x2398,0x341A,0x343A,0x341A,0x341A,0x2C19,0x2C19,0xBE7C,0xF7BE,0xF79E,0x9CD3,0x0000,0x0000,0x0000, // row 23, 768 pixels
|
||||
0x0000,0x0000,0x0000,0x39E7,0xDEDB,0xFFDF,0xF79E,0x9DFB,0x2C19,0x2C19,0x2C1A,0x341A,0x341A,0x2BB9,0x2B57,0x6459,0x74B9,0x2337,0x2BB9,0x341A,0x2C1A,0x2C19,0x2C19,0x2C19,0x9DFB,0xF79E,0xFFDF,0xDEDB,0x39E7,0x0000,0x0000,0x0000, // row 24, 800 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x632C,0xDEFB,0xFFDF,0xEF7D,0xB65C,0x3C39,0x2BF9,0x2C19,0x2C19,0x2BF9,0x2398,0x1B58,0x1B37,0x2398,0x2BF9,0x2C19,0x2BF9,0x2BF9,0x3439,0xB65C,0xEF7D,0xFFDF,0xDEFB,0x632C,0x0000,0x0000,0x0000,0x0000, // row 25, 832 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x73AE,0xDEFB,0xF7BE,0xF79E,0xDF1C,0x7D5A,0x2BF9,0x2BF9,0x2BF9,0x2BF9,0x23D9,0x23D9,0x2BF9,0x2BF9,0x2BF9,0x2BF9,0x7D5A,0xDF1C,0xF79E,0xF7BE,0xDEFB,0x73AE,0x0000,0x0000,0x0000,0x0000,0x0000, // row 26, 864 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x632C,0xDEDB,0xF79E,0xFFDF,0xEF7D,0xD6FC,0x9DFB,0x5CDA,0x4C9A,0x3419,0x3419,0x4C9A,0x5CDA,0x9DFB,0xD6FC,0xEF7D,0xFFDF,0xF79E,0xDEDB,0x632C,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 27, 896 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x4208,0x94B2,0xDEFB,0xF7BE,0xFFDF,0xF7BE,0xF79E,0xEF7D,0xEF5D,0xEF5D,0xEF7D,0xF79E,0xF7BE,0xFFDF,0xF7BE,0xDEFB,0x94B2,0x4208,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 28, 928 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x528A,0xA534,0xDEDB,0xE73C,0xF79E,0xF7BE,0xF7BE,0xF7BE,0xF7BE,0xF79E,0xE73C,0xDEDB,0xA534,0x528A,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 29, 960 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x18C3,0x5AEB,0x8C71,0xAD55,0xBDD7,0xBDD7,0xAD55,0x8C71,0x5AEB,0x18C3,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 30, 992 pixels
|
||||
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; // row 31, 1024 pixels
|
||||
@@ -0,0 +1,72 @@
|
||||
// Icon images are stored in tabs ^ e.g. Alert.h etc above this line
|
||||
// more than one icon can be in a header file
|
||||
|
||||
// Arrays containing FLASH images can be created with UTFT library tool:
|
||||
// (libraries\UTFT\Tools\ImageConverter565.exe)
|
||||
// Convert to .c format then copy into a new tab
|
||||
|
||||
/*
|
||||
This sketch demonstrates loading images from arrays stored in program (FLASH) memory.
|
||||
|
||||
Works with TFT_eSPI library here:
|
||||
https://github.com/Bodmer/TFT_eSPI
|
||||
|
||||
This sketch does not use/need any fonts at all...
|
||||
|
||||
Code derived from ILI9341_due library example
|
||||
|
||||
Make sure all the display driver and pin connections are correct by
|
||||
editing the User_Setup.h file in the TFT_eSPI library folder.
|
||||
|
||||
#########################################################################
|
||||
###### DON'T FORGET TO UPDATE THE User_Setup.h FILE IN THE LIBRARY ######
|
||||
#########################################################################
|
||||
*/
|
||||
|
||||
#include <TFT_eSPI.h> // Hardware-specific library
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library
|
||||
|
||||
// Include the header files that contain the icons
|
||||
#include "Alert.h"
|
||||
#include "Close.h"
|
||||
#include "Info.h"
|
||||
|
||||
long count = 0; // Loop count
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
tft.begin();
|
||||
tft.setRotation(1); // landscape
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
|
||||
// Swap the colour byte order when rendering
|
||||
tft.setSwapBytes(true);
|
||||
|
||||
// Draw the icons
|
||||
tft.pushImage(100, 100, infoWidth, infoHeight, info);
|
||||
tft.pushImage(140, 100, alertWidth, alertHeight, alert);
|
||||
tft.pushImage(180, 100, closeWidth, closeHeight, closeX);
|
||||
|
||||
// Pause here to admire the icons!
|
||||
delay(2000);
|
||||
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
// Loop filling and clearing screen
|
||||
tft.pushImage(random(tft.width() - infoWidth), random(tft.height() - infoHeight), infoWidth, infoHeight, info);
|
||||
tft.pushImage(random(tft.width() - alertWidth), random(tft.height() - alertHeight), alertWidth, alertHeight, alert);
|
||||
tft.pushImage(random(tft.width() - closeWidth), random(tft.height() - closeHeight), alertWidth, closeHeight, closeX);
|
||||
|
||||
// Clear screen after 100 x 3 = 300 icons drawn
|
||||
if (1000 == count++) {
|
||||
count = 1;
|
||||
tft.setRotation(2 * random(2)); // Rotate randomly to clear display left>right or right>left to reduce monotony!
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setRotation(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Bodmer's BMP image rendering function
|
||||
|
||||
void drawBmp(const char *filename, int16_t x, int16_t y) {
|
||||
|
||||
if ((x >= tft.width()) || (y >= tft.height())) return;
|
||||
|
||||
fs::File bmpFS;
|
||||
|
||||
// Open requested file on SD card
|
||||
bmpFS = SPIFFS.open(filename, "r");
|
||||
|
||||
if (!bmpFS)
|
||||
{
|
||||
Serial.print("File not found");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t seekOffset;
|
||||
uint16_t w, h, row, col;
|
||||
uint8_t r, g, b;
|
||||
|
||||
uint32_t startTime = millis();
|
||||
|
||||
if (read16(bmpFS) == 0x4D42)
|
||||
{
|
||||
read32(bmpFS);
|
||||
read32(bmpFS);
|
||||
seekOffset = read32(bmpFS);
|
||||
read32(bmpFS);
|
||||
w = read32(bmpFS);
|
||||
h = read32(bmpFS);
|
||||
|
||||
if ((read16(bmpFS) == 1) && (read16(bmpFS) == 24) && (read32(bmpFS) == 0))
|
||||
{
|
||||
y += h - 1;
|
||||
|
||||
bool oldSwapBytes = tft.getSwapBytes();
|
||||
tft.setSwapBytes(true);
|
||||
bmpFS.seek(seekOffset);
|
||||
|
||||
uint16_t padding = (4 - ((w * 3) & 3)) & 3;
|
||||
uint8_t lineBuffer[w * 3 + padding];
|
||||
|
||||
for (row = 0; row < h; row++) {
|
||||
|
||||
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
||||
uint8_t* bptr = lineBuffer;
|
||||
uint16_t* tptr = (uint16_t*)lineBuffer;
|
||||
// Convert 24 to 16 bit colours
|
||||
for (uint16_t col = 0; col < w; col++)
|
||||
{
|
||||
b = *bptr++;
|
||||
g = *bptr++;
|
||||
r = *bptr++;
|
||||
*tptr++ = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
||||
}
|
||||
|
||||
// Push the pixel row to screen, pushImage will crop the line if needed
|
||||
// y is decremented as the BMP image is drawn bottom up
|
||||
tft.pushImage(x, y--, w, 1, (uint16_t*)lineBuffer);
|
||||
}
|
||||
tft.setSwapBytes(oldSwapBytes);
|
||||
Serial.print("Loaded in "); Serial.print(millis() - startTime);
|
||||
Serial.println(" ms");
|
||||
}
|
||||
else Serial.println("BMP format not recognized.");
|
||||
}
|
||||
bmpFS.close();
|
||||
}
|
||||
|
||||
// These read 16- and 32-bit types from the SD card file.
|
||||
// BMP data is stored little-endian, Arduino is little-endian too.
|
||||
// May need to reverse subscript order if porting elsewhere.
|
||||
|
||||
uint16_t read16(fs::File &f) {
|
||||
uint16_t result;
|
||||
((uint8_t *)&result)[0] = f.read(); // LSB
|
||||
((uint8_t *)&result)[1] = f.read(); // MSB
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t read32(fs::File &f) {
|
||||
uint32_t result;
|
||||
((uint8_t *)&result)[0] = f.read(); // LSB
|
||||
((uint8_t *)&result)[1] = f.read();
|
||||
((uint8_t *)&result)[2] = f.read();
|
||||
((uint8_t *)&result)[3] = f.read(); // MSB
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// This sketch draws BMP images pulled from SPIFFS onto the TFT. It is an
|
||||
// an example from this library: https://github.com/Bodmer/TFT_eSPI
|
||||
|
||||
// Images in SPIFFS must be put in the root folder (top level) to be found
|
||||
// Use the SPIFFS library example to verify SPIFFS works!
|
||||
|
||||
// The example image used to test this sketch can be found in the sketch
|
||||
// Data folder, press Ctrl+K to see this folder. Use the IDE "Tools" menu
|
||||
// option to upload the sketches data folder to the SPIFFS
|
||||
|
||||
// This sketch has been tested on the ESP32 and ESP8266
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
//====================================================================================
|
||||
// Libraries
|
||||
//====================================================================================
|
||||
// Call up the SPIFFS FLASH filing system this is part of the ESP Core
|
||||
#define FS_NO_GLOBALS
|
||||
#include <FS.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#include "SPIFFS.h" // For ESP32 only
|
||||
#endif
|
||||
|
||||
// Call up the TFT library
|
||||
#include <TFT_eSPI.h> // Hardware-specific library for ESP8266
|
||||
|
||||
// Invoke TFT library
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
|
||||
//====================================================================================
|
||||
// Setup
|
||||
//====================================================================================
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
||||
if (!SPIFFS.begin()) {
|
||||
Serial.println("SPIFFS initialisation failed!");
|
||||
while (1) yield(); // Stay here twiddling thumbs waiting
|
||||
}
|
||||
Serial.println("\r\nSPIFFS initialised.");
|
||||
|
||||
// Now initialise the TFT
|
||||
tft.begin();
|
||||
tft.setRotation(0); // 0 & 2 Portrait. 1 & 3 landscape
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
}
|
||||
|
||||
//====================================================================================
|
||||
// Loop
|
||||
//====================================================================================
|
||||
void loop()
|
||||
{
|
||||
int x = random(tft.width() - 128);
|
||||
int y = random(tft.height() - 160);
|
||||
|
||||
drawBmp("/parrot.bmp", x, y);
|
||||
|
||||
delay(1000);
|
||||
}
|
||||
//====================================================================================
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
This sketch has been written to test the Processing screenshot client.
|
||||
|
||||
It has been created to work with the TFT_eSPI library here:
|
||||
https://github.com/Bodmer/TFT_eSPI
|
||||
|
||||
It sends screenshots to a PC running a Processing client sketch.
|
||||
|
||||
The Processing IDE that will run the client sketch can be downloaded
|
||||
here: https://processing.org/
|
||||
|
||||
The Processing sketch needed is contained within a tab attached to this
|
||||
Arduino sketch. Cut and paste that tab into the Processing IDE and run.
|
||||
Read the Processing sketch header for instructions.
|
||||
|
||||
This sketch uses the GLCD, 2, 4, 6 fonts only.
|
||||
|
||||
Make sure all the display driver and pin connections are correct by
|
||||
editing the User_Setup.h file in the TFT_eSPI library folder.
|
||||
|
||||
Maximum recommended SPI clock rate is 27MHz when reading pixels, 40MHz
|
||||
seems to be OK with ILI9341 displays but this is above the manufacturers
|
||||
specified maximum clock rate.
|
||||
|
||||
In the setup file you can define different write and read SPI clock rates
|
||||
|
||||
In the setup file you can define TFT_SDA_READ for a TFT with bi-directional
|
||||
SDA pin (otherwise the normal MISO pin will be used to read from the TFT)
|
||||
|
||||
>>>> NOTE: NOT ALL TFTs SUPPORT READING THE CGRAM (pixel) MEMORY <<<<
|
||||
|
||||
#########################################################################
|
||||
###### DON'T FORGET TO UPDATE THE User_Setup.h FILE IN THE LIBRARY ######
|
||||
#########################################################################
|
||||
*/
|
||||
|
||||
// Created by: Bodmer 5/3/17
|
||||
// Updated by: Bodmer 10/3/17
|
||||
// Updated by: Bodmer 23/11/18 to support SDA reads and the ESP32
|
||||
// Version: 0.07
|
||||
|
||||
// MIT licence applies, all text above must be included in derivative works
|
||||
|
||||
#include <TFT_eSPI.h> // Hardware-specific library
|
||||
#include <SPI.h>
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library
|
||||
|
||||
unsigned long targetTime = 0;
|
||||
byte red = 0x1F;
|
||||
byte green = 0;
|
||||
byte blue = 0;
|
||||
byte state = 0;
|
||||
unsigned int colour = red << 11; // Colour order is RGB 5+6+5 bits each
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(921600); // Set to a high rate for fast image transfer to a PC
|
||||
|
||||
tft.init();
|
||||
tft.setRotation(0);
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
|
||||
randomSeed(analogRead(A0));
|
||||
|
||||
targetTime = millis() + 1000;
|
||||
}
|
||||
|
||||
#define RGB_TEST false // true produces a simple RGB color test screen
|
||||
|
||||
void loop() {
|
||||
|
||||
if (targetTime < millis()) {
|
||||
if (!RGB_TEST)
|
||||
{
|
||||
targetTime = millis() + 1500; // Wait a minimum of 1.5s
|
||||
|
||||
tft.setRotation(random(4));
|
||||
rainbow_fill(); // Fill the screen with rainbow colours
|
||||
|
||||
tft.setTextColor(TFT_BLACK); // Text background is not defined so it is transparent
|
||||
tft.setTextDatum(TC_DATUM); // Top Centre datum
|
||||
int xpos = tft.width() / 2; // Centre of screen
|
||||
|
||||
tft.setTextFont(0); // Select font 0 which is the Adafruit font
|
||||
tft.drawString("Original Adafruit font!", xpos, 5);
|
||||
|
||||
// The new larger fonts do not need to use the .setCursor call, coords are embedded
|
||||
tft.setTextColor(TFT_BLACK); // Do not plot the background colour
|
||||
|
||||
// Overlay the black text on top of the rainbow plot (the advantage of not drawing the background colour!)
|
||||
tft.drawString("Font size 2", xpos, 14, 2); // Draw text centre at position xpos, 14 using font 2
|
||||
tft.drawString("Font size 4", xpos, 30, 4); // Draw text centre at position xpos, 30 using font 4
|
||||
tft.drawString("12.34", xpos, 54, 6); // Draw text centre at position xpos, 54 using font 6
|
||||
|
||||
tft.drawString("12.34 is in font size 6", xpos, 92, 2); // Draw text centre at position xpos, 92 using font 2
|
||||
// Note the x position is the top of the font!
|
||||
|
||||
// draw a floating point number
|
||||
float pi = 3.1415926; // Value to print
|
||||
int precision = 3; // Number of digits after decimal point
|
||||
|
||||
int ypos = 110; // y position
|
||||
|
||||
tft.setTextDatum(TR_DATUM); // Top Right datum so text butts neatly to xpos (right justified)
|
||||
|
||||
tft.drawFloat(pi, precision, xpos, ypos, 2); // Draw rounded number and return new xpos delta for next print position
|
||||
|
||||
tft.setTextDatum(TL_DATUM); // Top Left datum so text butts neatly to xpos (left justified)
|
||||
|
||||
tft.drawString(" is pi", xpos, ypos, 2);
|
||||
|
||||
tft.setTextSize(1); // We are using a font size multiplier of 1
|
||||
tft.setTextDatum(TC_DATUM); // Top Centre datum
|
||||
tft.setTextColor(TFT_BLACK); // Set text colour to black, no background (so transparent)
|
||||
|
||||
tft.drawString("Transparent...", xpos, 125, 4); // Font 4
|
||||
|
||||
tft.setTextColor(TFT_WHITE, TFT_BLACK); // Set text colour to white and background to black
|
||||
tft.drawString("White on black", xpos, 150, 4); // Font 4
|
||||
|
||||
tft.setTextColor(TFT_GREEN, TFT_BLACK); // This time we will use green text on a black background
|
||||
|
||||
tft.setTextFont(2); // Select font 2, now we do not need to specify the font in drawString()
|
||||
|
||||
// An easier way to position text and blank old text is to set the datum and use width padding
|
||||
tft.setTextDatum(BC_DATUM); // Bottom centre for text datum
|
||||
tft.setTextPadding(tft.width() + 1); // Pad text to full screen width + 1 spare for +/-1 position rounding
|
||||
|
||||
tft.drawString("Ode to a Small Lump of Green Putty", xpos, 230 - 32);
|
||||
tft.drawString("I Found in My Armpit One Midsummer", xpos, 230 - 16);
|
||||
tft.drawString("Morning", xpos, 230);
|
||||
|
||||
tft.setTextDatum(TL_DATUM); // Reset to top left for text datum
|
||||
tft.setTextPadding(0); // Reset text padding to 0 pixels
|
||||
|
||||
// Now call the screen server to send a copy of the TFT screen to the PC running the Processing client sketch
|
||||
screenServer();
|
||||
}
|
||||
else
|
||||
{
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.fillRect( 0, 0, 16, 16, TFT_RED);
|
||||
tft.fillRect(16, 0, 16, 16, TFT_GREEN);
|
||||
tft.fillRect(32, 0, 16, 16, TFT_BLUE);
|
||||
screenServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill screen with a rainbow pattern
|
||||
void rainbow_fill()
|
||||
{
|
||||
// The colours and state are not initialised so the start colour changes each time the function is called
|
||||
int rotation = tft.getRotation();
|
||||
tft.setRotation(random(4));
|
||||
for (int i = tft.height() - 1; i >= 0; i--) {
|
||||
// This is a "state machine" that ramps up/down the colour brightnesses in sequence
|
||||
switch (state) {
|
||||
case 0:
|
||||
green ++;
|
||||
if (green == 64) {
|
||||
green = 63;
|
||||
state = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
red--;
|
||||
if (red == 255) {
|
||||
red = 0;
|
||||
state = 2;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
blue ++;
|
||||
if (blue == 32) {
|
||||
blue = 31;
|
||||
state = 3;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
green --;
|
||||
if (green == 255) {
|
||||
green = 0;
|
||||
state = 4;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
red ++;
|
||||
if (red == 32) {
|
||||
red = 31;
|
||||
state = 5;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
blue --;
|
||||
if (blue == 255) {
|
||||
blue = 0;
|
||||
state = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
colour = red << 11 | green << 5 | blue;
|
||||
// Draw a line 1 pixel wide in the selected colour
|
||||
tft.drawFastHLine(0, i, tft.width(), colour); // tft.width() returns the pixel width of the display
|
||||
}
|
||||
tft.setRotation(rotation);
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
// This is a copy of the processing sketch that can be used to capture the images
|
||||
// Copy the sketch below and remove the /* and */ at the beginning and end.
|
||||
|
||||
// The sketch runs in Processing version 3.3 on a PC, it can be downloaded here:
|
||||
// https://processing.org/download/
|
||||
|
||||
/*
|
||||
|
||||
// This is a Processing sketch, see https://processing.org/ to download the IDE
|
||||
|
||||
// The sketch is a client that requests TFT screenshots from an Arduino board.
|
||||
// The Arduino must call a screenshot server function to respond with pixels.
|
||||
|
||||
// It has been created to work with the TFT_eSPI library here:
|
||||
// https://github.com/Bodmer/TFT_eSPI
|
||||
|
||||
// The sketch must only be run when the designated serial port is available and enumerated
|
||||
// otherwise the screenshot window may freeze and that process will need to be terminated
|
||||
// This is a limitation of the Processing environment and not the sketch.
|
||||
// If anyone knows how to determine if a serial port is available at start up the PM me
|
||||
// on (Bodmer) the Arduino forum.
|
||||
|
||||
// The block below contains variables that the user may need to change for a particular setup
|
||||
// As a minimum set the serial port and baud rate must be defined. The capture window is
|
||||
// automatically resized for landscape, portrait and different TFT resolutions.
|
||||
|
||||
// Captured images are stored in the sketch folder, use the Processing IDE "Sketch" menu
|
||||
// option "Show Sketch Folder" or press Ctrl+K
|
||||
|
||||
// Created by: Bodmer 5/3/17
|
||||
// Updated by: Bodmer 12/3/17
|
||||
// Version: 0.07
|
||||
|
||||
// MIT licence applies, all text above must be included in derivative works
|
||||
|
||||
|
||||
// ###########################################################################################
|
||||
// # These are the values to change for a particular setup #
|
||||
// #
|
||||
int serial_port = 0; // Use enumerated value from list provided when sketch is run #
|
||||
// #
|
||||
// On an Arduino Due Programming Port use a baud rate of:115200) #
|
||||
// On an Arduino Due Native USB Port use a baud rate of any value #
|
||||
int serial_baud_rate = 921600; // #
|
||||
// #
|
||||
// Change the image file type saved here, comment out all but one #
|
||||
//String image_type = ".jpg"; // #
|
||||
String image_type = ".png"; // Lossless compression #
|
||||
//String image_type = ".bmp"; // #
|
||||
//String image_type = ".tif"; // #
|
||||
// #
|
||||
boolean save_border = true; // Save the image with a border #
|
||||
int border = 5; // Border pixel width #
|
||||
boolean fade = false; // Fade out image after saving #
|
||||
// #
|
||||
int max_images = 100; // Maximum of numbered file images before over-writing files #
|
||||
// #
|
||||
int max_allowed = 1000; // Maximum number of save images allowed before a restart #
|
||||
// #
|
||||
// # End of the values to change for a particular setup #
|
||||
// ###########################################################################################
|
||||
|
||||
// These are default values, this sketch obtains the actual values from the Arduino board
|
||||
int tft_width = 480; // default TFT width (automatic - sent by Arduino)
|
||||
int tft_height = 480; // default TFT height (automatic - sent by Arduino)
|
||||
int color_bytes = 2; // 2 for 16 bit, 3 for three RGB bytes (automatic - sent by Arduino)
|
||||
|
||||
import processing.serial.*;
|
||||
|
||||
Serial serial; // Create an instance called serial
|
||||
|
||||
int serialCount = 0; // Count of colour bytes arriving
|
||||
|
||||
// Stage window graded background colours
|
||||
color bgcolor1 = color(0, 100, 104); // Arduino IDE style background color 1
|
||||
color bgcolor2 = color(77, 183, 187); // Arduino IDE style background color 2
|
||||
//color bgcolor2 = color(255, 255, 255); // White
|
||||
|
||||
// TFT image frame greyscale value (dark grey)
|
||||
color frameColor = 42;
|
||||
|
||||
color buttonStopped = color(255, 0, 0);
|
||||
color buttonRunning = color(128, 204, 206);
|
||||
color buttonDimmed = color(180, 0, 0);
|
||||
boolean dimmed = false;
|
||||
boolean running = true;
|
||||
boolean mouseClick = false;
|
||||
|
||||
int[] rgb = new int[3]; // Buffer for the colour bytes
|
||||
int indexRed = 0; // Colour byte index in the array
|
||||
int indexGreen = 1;
|
||||
int indexBlue = 2;
|
||||
|
||||
int n = 0;
|
||||
|
||||
int x_offset = (500 - tft_width) /2; // Image offsets in the window
|
||||
int y_offset = 20;
|
||||
|
||||
int xpos = 0, ypos = 0; // Current pixel position
|
||||
|
||||
int beginTime = 0;
|
||||
int pixelWaitTime = 1000; // Maximum 1000ms wait for image pixels to arrive
|
||||
int lastPixelTime = 0; // Time that "image send" command was sent
|
||||
|
||||
int requestTime = 0;
|
||||
int requestCount = 0;
|
||||
|
||||
int state = 0; // State machine current state
|
||||
|
||||
int progress_bar = 0; // Console progress bar dot count
|
||||
int pixel_count = 0; // Number of pixels read for 1 screen
|
||||
float percentage = 0; // Percentage of pixels received
|
||||
|
||||
int saved_image_count = 0; // Stats - number of images processed
|
||||
int bad_image_count = 0; // Stats - number of images that had lost pixels
|
||||
String filename = "";
|
||||
|
||||
int drawLoopCount = 0; // Used for the fade out
|
||||
|
||||
void setup() {
|
||||
|
||||
size(500, 540); // Stage size, can handle 480 pixels wide screen
|
||||
noStroke(); // No border on the next thing drawn
|
||||
noSmooth(); // No anti-aliasing to avoid adjacent pixel colour merging
|
||||
|
||||
// Graded background and title
|
||||
drawWindow();
|
||||
|
||||
frameRate(2000); // High frame rate so draw() loops fast
|
||||
|
||||
// Print a list of the available serial ports
|
||||
println("-----------------------");
|
||||
println("Available Serial Ports:");
|
||||
println("-----------------------");
|
||||
printArray(Serial.list());
|
||||
println("-----------------------");
|
||||
|
||||
print("Port currently used: [");
|
||||
print(serial_port);
|
||||
println("]");
|
||||
|
||||
String portName = Serial.list()[serial_port];
|
||||
|
||||
serial = new Serial(this, portName, serial_baud_rate);
|
||||
|
||||
state = 99;
|
||||
}
|
||||
|
||||
void draw() {
|
||||
|
||||
if (mouseClick) buttonClicked();
|
||||
|
||||
switch(state) {
|
||||
|
||||
case 0: // Init varaibles, send start request
|
||||
if (running) {
|
||||
tint(0, 0, 0, 255);
|
||||
flushBuffer();
|
||||
println("");
|
||||
print("Ready: ");
|
||||
|
||||
xpos = 0;
|
||||
ypos = 0;
|
||||
serialCount = 0;
|
||||
progress_bar = 0;
|
||||
pixel_count = 0;
|
||||
percentage = 0;
|
||||
drawLoopCount = frameCount;
|
||||
lastPixelTime = millis() + 1000;
|
||||
|
||||
state = 1;
|
||||
} else {
|
||||
if (millis() > beginTime) {
|
||||
beginTime = millis() + 500;
|
||||
dimmed = !dimmed;
|
||||
if (dimmed) drawButton(buttonDimmed);
|
||||
else drawButton(buttonStopped);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // Console message, give server some time
|
||||
print("requesting image ");
|
||||
serial.write("S");
|
||||
delay(10);
|
||||
beginTime = millis();
|
||||
requestTime = millis() + 1000;
|
||||
requestCount = 1;
|
||||
state = 2;
|
||||
break;
|
||||
|
||||
case 2: // Get size and set start time for rendering duration report
|
||||
if (millis() > requestTime) {
|
||||
requestCount++;
|
||||
print("*");
|
||||
serial.clear();
|
||||
serial.write("S");
|
||||
if (requestCount > 32) {
|
||||
requestCount = 0;
|
||||
System.err.println(" - no response!");
|
||||
state = 0;
|
||||
}
|
||||
requestTime = millis() + 1000;
|
||||
}
|
||||
if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel
|
||||
getFilename();
|
||||
flushBuffer(); // Precaution in case image header size increases in later versions
|
||||
lastPixelTime = millis() + 1000;
|
||||
beginTime = millis();
|
||||
state = 3;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // Request pixels and render returned RGB values
|
||||
state = renderPixels(); // State will change when all pixels are rendered
|
||||
|
||||
// Request more pixels, changing the number requested allows the average transfer rate to be controlled
|
||||
// The pixel transfer rate is dependant on four things:
|
||||
// 1. The frame rate defined in this Processing sketch in setup()
|
||||
// 2. The baud rate of the serial link (~10 bit periods per byte)
|
||||
// 3. The number of request bytes 'R' sent in the lines below
|
||||
// 4. The number of pixels sent in a burst by the server sketch (defined via NPIXELS)
|
||||
|
||||
//serial.write("RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"); // 32 x NPIXELS more
|
||||
serial.write("RRRRRRRRRRRRRRRR"); // 16 x NPIXELS more
|
||||
//serial.write("RRRRRRRR"); // 8 x NPIXELS more
|
||||
//serial.write("RRRR"); // 4 x NPIXELS more
|
||||
//serial.write("RR"); // 2 x NPIXELS more
|
||||
//serial.write("R"); // 1 x NPIXELS more
|
||||
if (!running) state = 4;
|
||||
break;
|
||||
|
||||
case 4: // Pixel receive time-out, flush serial buffer
|
||||
flushBuffer();
|
||||
state = 6;
|
||||
break;
|
||||
|
||||
case 5: // Save the image to the sketch folder (Ctrl+K to access)
|
||||
saveScreenshot();
|
||||
saved_image_count++;
|
||||
println("Saved image count = " + saved_image_count);
|
||||
if (bad_image_count > 0) System.err.println(" Bad image count = " + bad_image_count);
|
||||
drawLoopCount = frameCount; // Reset value ready for counting in step 6
|
||||
state = 6;
|
||||
break;
|
||||
|
||||
case 6: // Fade the old image if enabled
|
||||
if ( fadedImage() == true ) state = 0; // Go to next state when image has faded
|
||||
break;
|
||||
|
||||
case 99: // Draw image viewer window
|
||||
drawWindow();
|
||||
delay(50); // Delay here seems to be required for the IDE console to get ready
|
||||
state = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
println("");
|
||||
System.err.println("Error state reached - check sketch!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void drawWindow()
|
||||
{
|
||||
// Graded background in Arduino colours
|
||||
for (int i = 0; i < height - 25; i++) {
|
||||
float inter = map(i, 0, height - 25, 0, 1);
|
||||
color c = lerpColor(bgcolor1, bgcolor2, inter);
|
||||
stroke(c);
|
||||
line(0, i, 500, i);
|
||||
}
|
||||
fill(bgcolor2);
|
||||
rect( 0, height-25, width-1, 24);
|
||||
textAlign(CENTER);
|
||||
textSize(20);
|
||||
fill(0);
|
||||
text("Bodmer's TFT image viewer", width/2, height-6);
|
||||
|
||||
if (running) drawButton(buttonRunning);
|
||||
else drawButton(buttonStopped);
|
||||
}
|
||||
|
||||
void flushBuffer()
|
||||
{
|
||||
//println("Clearing serial pipe after a time-out");
|
||||
int clearTime = millis() + 50;
|
||||
while ( millis() < clearTime ) serial.clear();
|
||||
}
|
||||
|
||||
boolean getSize()
|
||||
{
|
||||
if ( serial.available() > 6 ) {
|
||||
println();
|
||||
char code = (char)serial.read();
|
||||
if (code == 'W') {
|
||||
tft_width = serial.read()<<8 | serial.read();
|
||||
}
|
||||
code = (char)serial.read();
|
||||
if (code == 'H') {
|
||||
tft_height = serial.read()<<8 | serial.read();
|
||||
}
|
||||
code = (char)serial.read();
|
||||
if (code == 'Y') {
|
||||
int bits_per_pixel = (char)serial.read();
|
||||
if (bits_per_pixel == 24) color_bytes = 3;
|
||||
else color_bytes = 2;
|
||||
}
|
||||
code = (char)serial.read();
|
||||
if (code == '?') {
|
||||
drawWindow();
|
||||
|
||||
x_offset = (500 - tft_width) /2;
|
||||
tint(0, 0, 0, 255);
|
||||
noStroke();
|
||||
fill(frameColor);
|
||||
rect((width - tft_width)/2 - border, y_offset - border, tft_width + 2 * border, tft_height + 2 * border);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void saveScreenshot()
|
||||
{
|
||||
println();
|
||||
if (saved_image_count < max_allowed)
|
||||
{
|
||||
if (filename == "") filename = "tft_screen_" + (n++);
|
||||
filename = filename + image_type;
|
||||
println("Saving image as \"" + filename + "\"");
|
||||
if (save_border)
|
||||
{
|
||||
PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border);
|
||||
partialSave.save(filename);
|
||||
} else {
|
||||
PImage partialSave = get(x_offset, y_offset, tft_width, tft_height);
|
||||
partialSave.save(filename);
|
||||
}
|
||||
|
||||
if (n>=max_images) n = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.err.println(max_allowed + " saved image count exceeded, restart the sketch");
|
||||
}
|
||||
}
|
||||
|
||||
void getFilename()
|
||||
{
|
||||
int readTime = millis() + 20;
|
||||
int inByte = 0;
|
||||
filename = "";
|
||||
while ( serial.available() > 0 && millis() < readTime && inByte != '.')
|
||||
{
|
||||
inByte = serial.read();
|
||||
if (inByte == ' ') inByte = '_';
|
||||
if ( unicodeCheck(inByte) ) filename += (char)inByte;
|
||||
}
|
||||
|
||||
inByte = serial.read();
|
||||
if (inByte == '@') filename += "_" + timeCode();
|
||||
else if (inByte == '#') filename += "_" + saved_image_count%100;
|
||||
else if (inByte == '%') filename += "_" + millis();
|
||||
else if (inByte != '*') filename = "";
|
||||
|
||||
inByte = serial.read();
|
||||
if (inByte == 'j') image_type =".jpg";
|
||||
else if (inByte == 'b') image_type =".bmp";
|
||||
else if (inByte == 'p') image_type =".png";
|
||||
else if (inByte == 't') image_type =".tif";
|
||||
}
|
||||
|
||||
boolean unicodeCheck(int unicode)
|
||||
{
|
||||
if ( unicode >= '0' && unicode <= '9' ) return true;
|
||||
if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true;
|
||||
if ( unicode == '_' || unicode == '/' ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
String timeCode()
|
||||
{
|
||||
String timeCode = (int)year() + "_" + (int)month() + "_" + (int)day() + "_";
|
||||
timeCode += (int)hour() + "_" + (int)minute() + "_" + (int)second();
|
||||
return timeCode;
|
||||
}
|
||||
|
||||
int renderPixels()
|
||||
{
|
||||
if ( serial.available() > 0 ) {
|
||||
|
||||
// Add the latest byte from the serial port to array:
|
||||
while (serial.available()>0)
|
||||
{
|
||||
rgb[serialCount++] = serial.read();
|
||||
|
||||
// If we have 3 colour bytes:
|
||||
if ( serialCount >= color_bytes ) {
|
||||
serialCount = 0;
|
||||
pixel_count++;
|
||||
if (color_bytes == 3)
|
||||
{
|
||||
stroke(rgb[indexRed], rgb[indexGreen], rgb[indexBlue], 1000);
|
||||
} else
|
||||
{ // Can cater for various byte orders
|
||||
//stroke( (rgb[0] & 0x1F)<<3, (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[1] & 0xF8));
|
||||
//stroke( (rgb[1] & 0x1F)<<3, (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[0] & 0xF8));
|
||||
stroke( (rgb[0] & 0xF8), (rgb[1] & 0xE0)>>3 | (rgb[0] & 0x07)<<5, (rgb[1] & 0x1F)<<3);
|
||||
//stroke( (rgb[1] & 0xF8), (rgb[0] & 0xE0)>>3 | (rgb[1] & 0x07)<<5, (rgb[0] & 0x1F)<<3);
|
||||
}
|
||||
// We get some pixel merge aliasing if smooth() is defined, so draw pixel twice
|
||||
point(xpos + x_offset, ypos + y_offset);
|
||||
//point(xpos + x_offset, ypos + y_offset);
|
||||
|
||||
lastPixelTime = millis();
|
||||
xpos++;
|
||||
if (xpos >= tft_width) {
|
||||
xpos = 0;
|
||||
progressBar();
|
||||
ypos++;
|
||||
if (ypos>=tft_height) {
|
||||
ypos = 0;
|
||||
if ((int)percentage <100) {
|
||||
while (progress_bar++ < 64) print(" ");
|
||||
percent(100);
|
||||
}
|
||||
println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s");
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
if (millis() > (lastPixelTime + pixelWaitTime))
|
||||
{
|
||||
println("");
|
||||
System.err.println(pixelWaitTime + "ms time-out for pixels exceeded...");
|
||||
if (pixel_count > 0) {
|
||||
bad_image_count++;
|
||||
System.err.print("Pixels missing = " + (tft_width * tft_height - pixel_count));
|
||||
System.err.println(", corrupted image not saved");
|
||||
System.err.println("Good image count = " + saved_image_count);
|
||||
System.err.println(" Bad image count = " + bad_image_count);
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
void progressBar()
|
||||
{
|
||||
progress_bar++;
|
||||
print(".");
|
||||
if (progress_bar >63)
|
||||
{
|
||||
progress_bar = 0;
|
||||
percentage = 0.5 + 100 * pixel_count/(0.001 + tft_width * tft_height);
|
||||
percent(percentage);
|
||||
}
|
||||
}
|
||||
|
||||
void percent(float percentage)
|
||||
{
|
||||
if (percentage > 100) percentage = 100;
|
||||
println(" [ " + (int)percentage + "% ]");
|
||||
textAlign(LEFT);
|
||||
textSize(16);
|
||||
noStroke();
|
||||
fill(bgcolor2);
|
||||
rect(10, height - 25, 70, 20);
|
||||
fill(0);
|
||||
text(" [ " + (int)percentage + "% ]", 10, height-8);
|
||||
}
|
||||
|
||||
boolean fadedImage()
|
||||
{
|
||||
int opacity = frameCount - drawLoopCount; // So we get increasing fade
|
||||
if (fade)
|
||||
{
|
||||
tint(255, opacity);
|
||||
//image(tft_img, x_offset, y_offset);
|
||||
noStroke();
|
||||
fill(50, 50, 50, opacity);
|
||||
rect( (width - tft_width)/2, y_offset, tft_width, tft_height);
|
||||
delay(10);
|
||||
}
|
||||
if (opacity > 50) // End fade after 50 cycles
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void drawButton(color buttonColor)
|
||||
{
|
||||
stroke(0);
|
||||
fill(buttonColor);
|
||||
rect(500 - 100, 540 - 26, 80, 24);
|
||||
textAlign(CENTER);
|
||||
textSize(20);
|
||||
fill(0);
|
||||
if (running) text(" Pause ", 500 - 60, height-7);
|
||||
else text(" Run ", 500 - 60, height-7);
|
||||
}
|
||||
|
||||
void buttonClicked()
|
||||
{
|
||||
mouseClick = false;
|
||||
if (running) {
|
||||
running = false;
|
||||
drawButton(buttonStopped);
|
||||
System.err.println("");
|
||||
System.err.println("Stopped - click 'Run' button: ");
|
||||
//noStroke();
|
||||
//fill(50);
|
||||
//rect( (width - tft_width)/2, y_offset, tft_width, tft_height);
|
||||
beginTime = millis() + 500;
|
||||
dimmed = false;
|
||||
state = 4;
|
||||
} else {
|
||||
running = true;
|
||||
drawButton(buttonRunning);
|
||||
}
|
||||
}
|
||||
|
||||
void mousePressed() {
|
||||
if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) {
|
||||
mouseClick = true;
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,196 @@
|
||||
// Reads a screen image off the TFT and send it to a processing client sketch
|
||||
// over the serial port. Use a high baud rate, e.g. for an ESP8266:
|
||||
// Serial.begin(921600);
|
||||
|
||||
// At 921600 baud a 320 x 240 image with 16 bit colour transfers can be sent to the
|
||||
// PC client in ~1.67s and 24 bit colour in ~2.5s which is close to the theoretical
|
||||
// minimum transfer time.
|
||||
|
||||
// This sketch has been created to work with the TFT_eSPI library here:
|
||||
// https://github.com/Bodmer/TFT_eSPI
|
||||
|
||||
// Created by: Bodmer 27/1/17
|
||||
// Updated by: Bodmer 10/3/17
|
||||
// Updated by: Bodmer 23/11/18 to support SDA reads and the ESP32
|
||||
// Version: 0.08
|
||||
|
||||
// MIT licence applies, all text above must be included in derivative works
|
||||
|
||||
//====================================================================================
|
||||
// Definitions
|
||||
//====================================================================================
|
||||
#define PIXEL_TIMEOUT 100 // 100ms Time-out between pixel requests
|
||||
#define START_TIMEOUT 10000 // 10s Maximum time to wait at start transfer
|
||||
|
||||
#define BITS_PER_PIXEL 16 // 24 for RGB colour format, 16 for 565 colour format
|
||||
|
||||
// File names must be alpha-numeric characters (0-9, a-z, A-Z) or "/" underscore "_"
|
||||
// other ascii characters are stripped out by client, including / generates
|
||||
// sub-directories
|
||||
#define DEFAULT_FILENAME "tft_screenshots/screenshot" // In case none is specified
|
||||
#define FILE_TYPE "png" // jpg, bmp, png, tif are valid
|
||||
|
||||
// Filename extension
|
||||
// '#' = add incrementing number, '@' = add timestamp, '%' add millis() timestamp,
|
||||
// '*' = add nothing
|
||||
// '@' and '%' will generate new unique filenames, so beware of cluttering up your
|
||||
// hard drive with lots of images! The PC client sketch is set to limit the number of
|
||||
// saved images to 1000 and will then prompt for a restart.
|
||||
#define FILE_EXT '@'
|
||||
|
||||
// Number of pixels to send in a burst (minimum of 1), no benefit above 8
|
||||
// NPIXELS values and render times:
|
||||
// NPIXELS 1 = use readPixel() = >5s and 16 bit pixels only
|
||||
// NPIXELS >1 using rectRead() 2 = 1.75s, 4 = 1.68s, 8 = 1.67s
|
||||
#define NPIXELS 8 // Must be integer division of both TFT width and TFT height
|
||||
|
||||
//====================================================================================
|
||||
// Screen server call with no filename
|
||||
//====================================================================================
|
||||
// Start a screen dump server (serial or network) - no filename specified
|
||||
bool screenServer(void)
|
||||
{
|
||||
// With no filename the screenshot will be saved with a default name e.g. tft_screen_#.xxx
|
||||
// where # is a number 0-9 and xxx is a file type specified below
|
||||
return screenServer(DEFAULT_FILENAME);
|
||||
}
|
||||
|
||||
//====================================================================================
|
||||
// Screen server call with filename
|
||||
//====================================================================================
|
||||
// Start a screen dump server (serial or network) - filename specified
|
||||
bool screenServer(String filename)
|
||||
{
|
||||
delay(0); // Equivalent to yield() for ESP8266;
|
||||
|
||||
bool result = serialScreenServer(filename); // Screenshot serial port server
|
||||
//bool result = wifiScreenServer(filename); // Screenshot WiFi UDP port server (WIP)
|
||||
|
||||
delay(0); // Equivalent to yield()
|
||||
|
||||
//Serial.println();
|
||||
//if (result) Serial.println(F("Screen dump passed :-)"));
|
||||
//else Serial.println(F("Screen dump failed :-("));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//====================================================================================
|
||||
// Serial server function that sends the data to the client
|
||||
//====================================================================================
|
||||
bool serialScreenServer(String filename)
|
||||
{
|
||||
// Precautionary receive buffer garbage flush for 50ms
|
||||
uint32_t clearTime = millis() + 50;
|
||||
while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;
|
||||
|
||||
bool wait = true;
|
||||
uint32_t lastCmdTime = millis(); // Initialise start of command time-out
|
||||
|
||||
// Wait for the starting flag with a start time-out
|
||||
while (wait)
|
||||
{
|
||||
delay(0); // Equivalent to yield() for ESP8266;
|
||||
// Check serial buffer
|
||||
if (Serial.available() > 0) {
|
||||
// Read the command byte
|
||||
uint8_t cmd = Serial.read();
|
||||
// If it is 'S' (start command) then clear the serial buffer for 100ms and stop waiting
|
||||
if ( cmd == 'S' ) {
|
||||
// Precautionary receive buffer garbage flush for 50ms
|
||||
clearTime = millis() + 50;
|
||||
while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;
|
||||
|
||||
wait = false; // No need to wait anymore
|
||||
lastCmdTime = millis(); // Set last received command time
|
||||
|
||||
// Send screen size etc using a simple header with delimiters for client checks
|
||||
sendParameters(filename);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check for time-out
|
||||
if ( millis() > lastCmdTime + START_TIMEOUT) return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t color[3 * NPIXELS]; // RGB and 565 format color buffer for N pixels
|
||||
|
||||
// Send all the pixels on the whole screen
|
||||
for ( uint32_t y = 0; y < tft.height(); y++)
|
||||
{
|
||||
// Increment x by NPIXELS as we send NPIXELS for every byte received
|
||||
for ( uint32_t x = 0; x < tft.width(); x += NPIXELS)
|
||||
{
|
||||
delay(0); // Equivalent to yield() for ESP8266;
|
||||
|
||||
// Wait here for serial data to arrive or a time-out elapses
|
||||
while ( Serial.available() == 0 )
|
||||
{
|
||||
if ( millis() > lastCmdTime + PIXEL_TIMEOUT) return false;
|
||||
delay(0); // Equivalent to yield() for ESP8266;
|
||||
}
|
||||
|
||||
// Serial data must be available to get here, read 1 byte and
|
||||
// respond with N pixels, i.e. N x 3 RGB bytes or N x 2 565 format bytes
|
||||
if ( Serial.read() == 'X' ) {
|
||||
// X command byte means abort, so clear the buffer and return
|
||||
clearTime = millis() + 50;
|
||||
while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;
|
||||
return false;
|
||||
}
|
||||
// Save arrival time of the read command (for later time-out check)
|
||||
lastCmdTime = millis();
|
||||
|
||||
#if defined BITS_PER_PIXEL && BITS_PER_PIXEL >= 24 && NPIXELS > 1
|
||||
// Fetch N RGB pixels from x,y and put in buffer
|
||||
tft.readRectRGB(x, y, NPIXELS, 1, color);
|
||||
// Send buffer to client
|
||||
Serial.write(color, 3 * NPIXELS); // Write all pixels in the buffer
|
||||
#else
|
||||
// Fetch N 565 format pixels from x,y and put in buffer
|
||||
if (NPIXELS > 1) tft.readRect(x, y, NPIXELS, 1, (uint16_t *)color);
|
||||
else
|
||||
{
|
||||
uint16_t c = tft.readPixel(x, y);
|
||||
color[0] = c>>8;
|
||||
color[1] = c & 0xFF; // Swap bytes
|
||||
}
|
||||
// Send buffer to client
|
||||
Serial.write(color, 2 * NPIXELS); // Write all pixels in the buffer
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
Serial.flush(); // Make sure all pixel bytes have been despatched
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//====================================================================================
|
||||
// Send screen size etc using a simple header with delimiters for client checks
|
||||
//====================================================================================
|
||||
void sendParameters(String filename)
|
||||
{
|
||||
Serial.write('W'); // Width
|
||||
Serial.write(tft.width() >> 8);
|
||||
Serial.write(tft.width() & 0xFF);
|
||||
|
||||
Serial.write('H'); // Height
|
||||
Serial.write(tft.height() >> 8);
|
||||
Serial.write(tft.height() & 0xFF);
|
||||
|
||||
Serial.write('Y'); // Bits per pixel (16 or 24)
|
||||
if (NPIXELS > 1) Serial.write(BITS_PER_PIXEL);
|
||||
else Serial.write(16); // readPixel() only provides 16 bit values
|
||||
|
||||
Serial.write('?'); // Filename next
|
||||
Serial.print(filename);
|
||||
|
||||
Serial.write('.'); // End of filename marker
|
||||
|
||||
Serial.write(FILE_EXT); // Filename extension identifier
|
||||
|
||||
Serial.write(*FILE_TYPE); // First character defines file type j,b,p,t
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
Sketch to generate the setup() calibration values, these are reported
|
||||
to the Serial Monitor.
|
||||
|
||||
The sketch has been tested on the ESP8266 and screen with XPT2046 driver.
|
||||
*/
|
||||
|
||||
#include <SPI.h>
|
||||
#include <TFT_eSPI.h> // Hardware-specific library
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
||||
void setup() {
|
||||
// Use serial port
|
||||
Serial.begin(115200);
|
||||
|
||||
// Initialise the TFT screen
|
||||
tft.init();
|
||||
|
||||
// Set the rotation to the orientation you wish to use in your project before calibration
|
||||
// (the touch coordinates returned then correspond to that rotation only)
|
||||
tft.setRotation(1);
|
||||
|
||||
// Calibrate the touch screen and retrieve the scaling factors
|
||||
touch_calibrate();
|
||||
|
||||
/*
|
||||
// Replace above line with the code sent to Serial Monitor
|
||||
// once calibration is complete, e.g.:
|
||||
uint16_t calData[5] = { 286, 3534, 283, 3600, 6 };
|
||||
tft.setTouch(calData);
|
||||
*/
|
||||
|
||||
// Clear the screen
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.drawCentreString("Touch screen to test!",tft.width()/2, tft.height()/2, 2);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
||||
void loop(void) {
|
||||
uint16_t x = 0, y = 0; // To store the touch coordinates
|
||||
|
||||
// Pressed will be set true is there is a valid touch on the screen
|
||||
bool pressed = tft.getTouch(&x, &y);
|
||||
|
||||
// Draw a white spot at the detected coordinates
|
||||
if (pressed) {
|
||||
tft.fillCircle(x, y, 2, TFT_WHITE);
|
||||
//Serial.print("x,y = ");
|
||||
//Serial.print(x);
|
||||
//Serial.print(",");
|
||||
//Serial.println(y);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------
|
||||
|
||||
// Code to run a screen calibration, not needed when calibration values set in setup()
|
||||
void touch_calibrate()
|
||||
{
|
||||
uint16_t calData[5];
|
||||
uint8_t calDataOK = 0;
|
||||
|
||||
// Calibrate
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setCursor(20, 0);
|
||||
tft.setTextFont(2);
|
||||
tft.setTextSize(1);
|
||||
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||
|
||||
tft.println("Touch corners as indicated");
|
||||
|
||||
tft.setTextFont(1);
|
||||
tft.println();
|
||||
|
||||
tft.calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15);
|
||||
|
||||
Serial.println(); Serial.println();
|
||||
Serial.println("// Use this calibration code in setup():");
|
||||
Serial.print(" uint16_t calData[5] = ");
|
||||
Serial.print("{ ");
|
||||
|
||||
for (uint8_t i = 0; i < 5; i++)
|
||||
{
|
||||
Serial.print(calData[i]);
|
||||
if (i < 4) Serial.print(", ");
|
||||
}
|
||||
|
||||
Serial.println(" };");
|
||||
Serial.print(" tft.setTouch(calData);");
|
||||
Serial.println(); Serial.println();
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
|
||||
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
||||
tft.println("Calibration complete!");
|
||||
tft.println("Calibration code sent to Serial port.");
|
||||
|
||||
delay(4000);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
// Viewport Demo
|
||||
|
||||
// See viewport_commands tab for details of functions available
|
||||
|
||||
// This example uses the viewport commands to create a "virtual TFT" within the
|
||||
// normal TFT display area. This allows a sketch written for a smaller screen to
|
||||
// be run in a viewport window. By default, the graphics 0,0 datum is set to the
|
||||
// top left corner of the viewport, but optionally the datum can be kept at the
|
||||
// corner of the TFT.
|
||||
|
||||
// Viewports have a number of potential uses:
|
||||
// - create a "virtual" TFT screen smaller than the actual TFT screen
|
||||
// - render GUI items (menus etc) in a viewport, erase GUI item by redrawing whole screen,
|
||||
// this will be fast because only the viewport will be refreshed (e.g. clearing menu)
|
||||
// - limit screen refresh to a particular area, e.g. changing numbers, icons or graph plotting
|
||||
// - showing a small portion of a larger image or sprite, this allows panning and scrolling
|
||||
|
||||
// A viewport can have the coordinate datum (position 0,0) either at the top left corner of
|
||||
// the viewport or at the normal top left corner of the TFT.
|
||||
// Putting the coordinate datum at the viewport corner means that functions that draw graphics
|
||||
// in a fixed position can be relocated anywhere on the screen. (see plotBox() below). This
|
||||
// makes it easier to reposition groups of graphical objects (for example GUI buttons) that have
|
||||
// fixed relative positions.
|
||||
|
||||
#include <SPI.h>
|
||||
#include <TFT_eSPI.h>
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
tft.init();
|
||||
tft.setRotation(1);
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
// Normal Screen
|
||||
drawX();
|
||||
|
||||
delay(2000);
|
||||
|
||||
// Viewport screen
|
||||
tft.setViewport(10, 10, 140, 100);
|
||||
tft.frameViewport(TFT_NAVY, -2);
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
drawX();
|
||||
tft.resetViewport();
|
||||
|
||||
delay(2000);
|
||||
|
||||
//Normal screen
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
drawX();
|
||||
|
||||
delay(2000);
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
|
||||
// Viewport as a clipping window (false parameter means coordinate datum stays at TFT top left)
|
||||
tft.setViewport(10, 10, tft.width()/2 - 10, tft.height() - 20, false);
|
||||
//tft.frameViewport(TFT_NAVY, 2); // Add 2 pixel border inside viewport
|
||||
//tft.frameViewport(TFT_NAVY, -2); // Add 2 pixel border outside viewport
|
||||
drawX();
|
||||
|
||||
delay(2000);
|
||||
|
||||
while(1)
|
||||
{
|
||||
tft.resetViewport(); // Reset viewport so width() and height() return TFT size
|
||||
|
||||
uint16_t w = 40;
|
||||
uint16_t h = 40;
|
||||
uint16_t x = random(tft.width() - w);
|
||||
uint16_t y = random(tft.height() - h);
|
||||
|
||||
tft.setViewport(x, y, w, h);
|
||||
|
||||
plotBox();
|
||||
|
||||
delay(0);
|
||||
}
|
||||
}
|
||||
|
||||
void drawX(void)
|
||||
{
|
||||
tft.fillScreen(tft.color565(25,25,25)); // Grey
|
||||
|
||||
// Draw circle
|
||||
tft.drawCircle(tft.width()/2, tft.height()/2, tft.width()/4, TFT_RED);
|
||||
|
||||
// Draw diagonal lines
|
||||
tft.drawLine(0 , 0, tft.width()-1, tft.height()-1, TFT_GREEN);
|
||||
tft.drawLine(0 , tft.height()-1, tft.width()-1, 0, TFT_BLUE);
|
||||
|
||||
tft.setTextDatum(MC_DATUM);
|
||||
tft.setTextColor(TFT_WHITE, tft.color565(25,25,25));
|
||||
tft.drawString("Hello World!", tft.width()/2, tft.height()/2, 4); // Font 4
|
||||
}
|
||||
|
||||
void plotBox(void)
|
||||
{
|
||||
// These are always plotted at a fixed position but they can
|
||||
// be plotted into a viewport anywhere on the screen because
|
||||
// a viewport can move the screen datum
|
||||
tft.fillScreen(TFT_BLACK); // When a viewport is set, this just fills the viewport
|
||||
tft.drawRect(0,0, 40,40, TFT_BLUE);
|
||||
tft.setTextDatum(MC_DATUM);
|
||||
tft.setTextColor(TFT_WHITE);
|
||||
tft.drawNumber( random(100), 20, 23, 4); // Number in font 4
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
|
||||
// Create a viewport at TFT screen coordinated X,Y of width W and height H
|
||||
tft.setViewport(X, Y, W, H); // By default the 0,0 coordinate datum is moved to top left
|
||||
// corner of viewport
|
||||
// Note: tft.width() and tft.height() now return viewport size!
|
||||
// The above command is identical to:
|
||||
tft.setViewport(VP_X, VP_Y, VP_W, VP_H, true); // true parameter is optional
|
||||
|
||||
// To create a viewport that keeps the coordinate datum at top left of TFT, use false parameter
|
||||
tft.setViewport(VP_X, VP_Y, VP_W, VP_H, false); // Note: tft.width() and tft.height() return TFT size!
|
||||
|
||||
// To get viewport x, y coordinates, width, height and datum position flag
|
||||
int32_t x = tft.getViewportX(); // Always returns viewport x coordinate relative to screen left edge
|
||||
int32_t y = tft.getViewportY(void); // Always returns viewport y coordinate relative to screen top edge
|
||||
int32_t w = tft.getViewportWidth(); // Always returns width of viewport
|
||||
int32_t h = tft.getViewportHeight(); // Always returns height of viewport
|
||||
bool f = tft.getViewportDatum(); // Datum of the viewport (false = TFT corner, true = viewport corner)
|
||||
// To check if all or part of an area is in the viewport
|
||||
checkViewport(x, y, w, h); // Returns "true" if all or part of area is in viewport
|
||||
|
||||
// To draw a rectangular frame outside viewport of width W (when W is negative)
|
||||
tft.frameViewport(TFT_RED, -W); // Note setting the width to a large negative value will clear the screen
|
||||
// outside the viewport
|
||||
|
||||
// To draw a rectangular frame inside viewport of width W (when W is positive)
|
||||
tft.frameViewport(TFT_RED, W); // Note setting the width to a large positive value will clear the screen
|
||||
// inside the viewport
|
||||
|
||||
// To reset the viewport to the normal TFT full screen
|
||||
tft.resetViewport(); // Note: Graphics will NOT be drawn to the TFT outside a viewport until
|
||||
// this command is used! ( The exception is using the frameViewport command
|
||||
// detailed above with a negative width.)
|
||||
|
||||
// Note:
|
||||
// Using setRotation rotates the whole TFT screen it does not just
|
||||
// rotate the viewport (this is a possible future enhancement).
|
||||
// Redraw all graphics after a rotation since some TFT's do not
|
||||
// re-map the TFT graphics RAM to the screen pixels as expected.
|
||||
*/
|
||||
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
This sketch demonstrates the Adafruit graphicstest sketch running in a
|
||||
viewport (aka window) within the TFT screen area. To do this line 37 has
|
||||
been added. Line 39 draws a frame outside the viewport.
|
||||
|
||||
This sketch uses the GLCD font (font 1) only.
|
||||
|
||||
Make sure all the display driver and pin connections are correct by
|
||||
editing the User_Setup.h file in the TFT_eSPI library folder.
|
||||
|
||||
#########################################################################
|
||||
###### DON'T FORGET TO UPDATE THE User_Setup.h FILE IN THE LIBRARY ######
|
||||
#########################################################################
|
||||
*/
|
||||
|
||||
|
||||
#include "SPI.h"
|
||||
#include "TFT_eSPI.h"
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
|
||||
unsigned long total = 0;
|
||||
unsigned long tn = 0;
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
while (!Serial);
|
||||
|
||||
Serial.println(""); Serial.println("");
|
||||
Serial.println("TFT_eSPI library test!");
|
||||
|
||||
tft.init();
|
||||
|
||||
tn = micros();
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
|
||||
// Create a viewport 220 x 300 pixels
|
||||
tft.setViewport(10,10,220,300);
|
||||
|
||||
tft.frameViewport(TFT_RED, -1); // 1 pixel wide frame around viewport
|
||||
|
||||
yield(); Serial.println(F("Benchmark Time (microseconds)"));
|
||||
|
||||
yield(); Serial.print(F("Screen fill "));
|
||||
yield(); Serial.println(testFillScreen());
|
||||
//total+=testFillScreen();
|
||||
//delay(500);
|
||||
|
||||
yield(); Serial.print(F("Text "));
|
||||
yield(); Serial.println(testText());
|
||||
//total+=testText();
|
||||
//delay(3000);
|
||||
|
||||
yield(); Serial.print(F("Lines "));
|
||||
yield(); Serial.println(testLines(TFT_CYAN));
|
||||
//total+=testLines(TFT_CYAN);
|
||||
//delay(500);
|
||||
|
||||
yield(); Serial.print(F("Horiz/Vert Lines "));
|
||||
yield(); Serial.println(testFastLines(TFT_RED, TFT_BLUE));
|
||||
//total+=testFastLines(TFT_RED, TFT_BLUE);
|
||||
//delay(500);
|
||||
|
||||
yield(); Serial.print(F("Rectangles (outline) "));
|
||||
yield(); Serial.println(testRects(TFT_GREEN));
|
||||
//total+=testRects(TFT_GREEN);
|
||||
//delay(500);
|
||||
|
||||
yield(); Serial.print(F("Rectangles (filled) "));
|
||||
yield(); Serial.println(testFilledRects(TFT_YELLOW, TFT_MAGENTA));
|
||||
//total+=testFilledRects(TFT_YELLOW, TFT_MAGENTA);
|
||||
//delay(500);
|
||||
|
||||
yield(); Serial.print(F("Circles (filled) "));
|
||||
yield(); Serial.println(testFilledCircles(10, TFT_MAGENTA));
|
||||
//total+= testFilledCircles(10, TFT_MAGENTA);
|
||||
|
||||
yield(); Serial.print(F("Circles (outline) "));
|
||||
yield(); Serial.println(testCircles(10, TFT_WHITE));
|
||||
//total+=testCircles(10, TFT_WHITE);
|
||||
//delay(500);
|
||||
|
||||
yield(); Serial.print(F("Triangles (outline) "));
|
||||
yield(); Serial.println(testTriangles());
|
||||
//total+=testTriangles();
|
||||
//delay(500);
|
||||
|
||||
yield(); Serial.print(F("Triangles (filled) "));
|
||||
yield(); Serial.println(testFilledTriangles());
|
||||
//total += testFilledTriangles();
|
||||
//delay(500);
|
||||
|
||||
yield(); Serial.print(F("Rounded rects (outline) "));
|
||||
yield(); Serial.println(testRoundRects());
|
||||
//total+=testRoundRects();
|
||||
//delay(500);
|
||||
|
||||
yield(); Serial.print(F("Rounded rects (filled) "));
|
||||
yield(); Serial.println(testFilledRoundRects());
|
||||
//total+=testFilledRoundRects();
|
||||
//delay(500);
|
||||
|
||||
yield(); Serial.println(F("Done!")); yield();
|
||||
//Serial.print(F("Total = ")); Serial.println(total);
|
||||
|
||||
//yield();Serial.println(millis()-tn);
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
for (uint8_t rotation = 0; rotation < 4; rotation++) {
|
||||
tft.setRotation(rotation);
|
||||
tft.resetViewport(); // reset viewport to whole screen
|
||||
tft.fillScreen(TFT_BLACK); // so it can be cleared
|
||||
|
||||
// Create a viewport 220 x 300 pixels
|
||||
tft.setViewport(10,10,220,300);
|
||||
tft.frameViewport(TFT_RED, -1); // 1 pixel wide frame around viewport
|
||||
|
||||
testText();
|
||||
delay(2000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsigned long testFillScreen() {
|
||||
unsigned long start = micros();
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.fillScreen(TFT_RED);
|
||||
tft.fillScreen(TFT_GREEN);
|
||||
tft.fillScreen(TFT_BLUE);
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testText() {
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
unsigned long start = micros();
|
||||
tft.setCursor(0, 0);
|
||||
tft.setTextColor(TFT_WHITE); tft.setTextSize(1);
|
||||
tft.println("Hello World!");
|
||||
tft.setTextColor(TFT_YELLOW); tft.setTextSize(2);
|
||||
tft.println(1234.56);
|
||||
tft.setTextColor(TFT_RED); tft.setTextSize(3);
|
||||
tft.println(0xDEADBEEF, HEX);
|
||||
tft.println();
|
||||
tft.setTextColor(TFT_GREEN);
|
||||
tft.setTextSize(5);
|
||||
tft.println("Groop");
|
||||
tft.setTextSize(2);
|
||||
tft.println("I implore thee,");
|
||||
//tft.setTextColor(TFT_GREEN,TFT_BLACK);
|
||||
tft.setTextSize(1);
|
||||
tft.println("my foonting turlingdromes.");
|
||||
tft.println("And hooptiously drangle me");
|
||||
tft.println("with crinkly bindlewurdles,");
|
||||
tft.println("Or I will rend thee");
|
||||
tft.println("in the gobberwarts");
|
||||
tft.println("with my blurglecruncheon,");
|
||||
tft.println("see if I don't!");
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testLines(uint16_t color) {
|
||||
unsigned long start, t;
|
||||
int x1, y1, x2, y2,
|
||||
w = tft.width(),
|
||||
h = tft.height();
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
|
||||
x1 = y1 = 0;
|
||||
y2 = h - 1;
|
||||
start = micros();
|
||||
for (x2 = 0; x2 < w; x2 += 6) tft.drawLine(x1, y1, x2, y2, color);
|
||||
x2 = w - 1;
|
||||
for (y2 = 0; y2 < h; y2 += 6) tft.drawLine(x1, y1, x2, y2, color);
|
||||
t = micros() - start; // fillScreen doesn't count against timing
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
|
||||
x1 = w - 1;
|
||||
y1 = 0;
|
||||
y2 = h - 1;
|
||||
start = micros();
|
||||
for (x2 = 0; x2 < w; x2 += 6) tft.drawLine(x1, y1, x2, y2, color);
|
||||
x2 = 0;
|
||||
for (y2 = 0; y2 < h; y2 += 6) tft.drawLine(x1, y1, x2, y2, color);
|
||||
t += micros() - start;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
|
||||
x1 = 0;
|
||||
y1 = h - 1;
|
||||
y2 = 0;
|
||||
start = micros();
|
||||
for (x2 = 0; x2 < w; x2 += 6) tft.drawLine(x1, y1, x2, y2, color);
|
||||
x2 = w - 1;
|
||||
for (y2 = 0; y2 < h; y2 += 6) tft.drawLine(x1, y1, x2, y2, color);
|
||||
t += micros() - start;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
|
||||
x1 = w - 1;
|
||||
y1 = h - 1;
|
||||
y2 = 0;
|
||||
start = micros();
|
||||
for (x2 = 0; x2 < w; x2 += 6) tft.drawLine(x1, y1, x2, y2, color);
|
||||
x2 = 0;
|
||||
for (y2 = 0; y2 < h; y2 += 6) tft.drawLine(x1, y1, x2, y2, color);
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testFastLines(uint16_t color1, uint16_t color2) {
|
||||
unsigned long start;
|
||||
int x, y, w = tft.width(), h = tft.height();
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
start = micros();
|
||||
for (y = 0; y < h; y += 5) tft.drawFastHLine(0, y, w, color1);
|
||||
for (x = 0; x < w; x += 5) tft.drawFastVLine(x, 0, h, color2);
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testRects(uint16_t color) {
|
||||
unsigned long start;
|
||||
int n, i, i2,
|
||||
cx = tft.width() / 2,
|
||||
cy = tft.height() / 2;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
n = min(tft.width(), tft.height());
|
||||
start = micros();
|
||||
for (i = 2; i < n; i += 6) {
|
||||
i2 = i / 2;
|
||||
tft.drawRect(cx - i2, cy - i2, i, i, color);
|
||||
}
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testFilledRects(uint16_t color1, uint16_t color2) {
|
||||
unsigned long start, t = 0;
|
||||
int n, i, i2,
|
||||
cx = tft.width() / 2 - 1,
|
||||
cy = tft.height() / 2 - 1;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
n = min(tft.width(), tft.height());
|
||||
for (i = n - 1; i > 0; i -= 6) {
|
||||
i2 = i / 2;
|
||||
start = micros();
|
||||
tft.fillRect(cx - i2, cy - i2, i, i, color1);
|
||||
t += micros() - start;
|
||||
// Outlines are not included in timing results
|
||||
tft.drawRect(cx - i2, cy - i2, i, i, color2);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
unsigned long testFilledCircles(uint8_t radius, uint16_t color) {
|
||||
unsigned long start;
|
||||
int x, y, w = tft.width(), h = tft.height(), r2 = radius * 2;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
start = micros();
|
||||
for (x = radius; x < w; x += r2) {
|
||||
for (y = radius; y < h; y += r2) {
|
||||
tft.fillCircle(x, y, radius, color);
|
||||
}
|
||||
}
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testCircles(uint8_t radius, uint16_t color) {
|
||||
unsigned long start;
|
||||
int x, y, r2 = radius * 2,
|
||||
w = tft.width() + radius,
|
||||
h = tft.height() + radius;
|
||||
|
||||
// Screen is not cleared for this one -- this is
|
||||
// intentional and does not affect the reported time.
|
||||
start = micros();
|
||||
for (x = 0; x < w; x += r2) {
|
||||
for (y = 0; y < h; y += r2) {
|
||||
tft.drawCircle(x, y, radius, color);
|
||||
}
|
||||
}
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testTriangles() {
|
||||
unsigned long start;
|
||||
int n, i, cx = tft.width() / 2 - 1,
|
||||
cy = tft.height() / 2 - 1;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
n = min(cx, cy);
|
||||
start = micros();
|
||||
for (i = 0; i < n; i += 5) {
|
||||
tft.drawTriangle(
|
||||
cx , cy - i, // peak
|
||||
cx - i, cy + i, // bottom left
|
||||
cx + i, cy + i, // bottom right
|
||||
tft.color565(0, 0, i));
|
||||
}
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testFilledTriangles() {
|
||||
unsigned long start, t = 0;
|
||||
int i, cx = tft.width() / 2 - 1,
|
||||
cy = tft.height() / 2 - 1;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
start = micros();
|
||||
for (i = min(cx, cy); i > 10; i -= 5) {
|
||||
start = micros();
|
||||
tft.fillTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i,
|
||||
tft.color565(0, i, i));
|
||||
t += micros() - start;
|
||||
tft.drawTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i,
|
||||
tft.color565(i, i, 0));
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
unsigned long testRoundRects() {
|
||||
unsigned long start;
|
||||
int w, i, i2,
|
||||
cx = tft.width() / 2 - 1,
|
||||
cy = tft.height() / 2 - 1;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
w = min(tft.width(), tft.height());
|
||||
start = micros();
|
||||
for (i = 0; i < w; i += 6) {
|
||||
i2 = i / 2;
|
||||
tft.drawRoundRect(cx - i2, cy - i2, i, i, i / 8, tft.color565(i, 0, 0));
|
||||
}
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
unsigned long testFilledRoundRects() {
|
||||
unsigned long start;
|
||||
int i, i2,
|
||||
cx = tft.width() / 2 - 1,
|
||||
cy = tft.height() / 2 - 1;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
start = micros();
|
||||
for (i = min(tft.width(), tft.height()); i > 20; i -= 6) {
|
||||
i2 = i / 2;
|
||||
tft.fillRoundRect(cx - i2, cy - i2, i, i, i / 8, tft.color565(0, i, 0));
|
||||
}
|
||||
|
||||
return micros() - start;
|
||||
}
|
||||
|
||||
/***************************************************
|
||||
Original Adafruit text:
|
||||
|
||||
This is an example sketch for the Adafruit 2.2" SPI display.
|
||||
This library works with the Adafruit 2.2" TFT Breakout w/SD card
|
||||
----> http://www.adafruit.com/products/1480
|
||||
|
||||
Check out the links above for our tutorials and wiring diagrams
|
||||
These displays use SPI to communicate, 4 or 5 pins are required to
|
||||
interface (RST is optional)
|
||||
Adafruit invests time and resources providing this open source code,
|
||||
please support Adafruit and open-source hardware by purchasing
|
||||
products from Adafruit!
|
||||
|
||||
Written by Limor Fried/Ladyada for Adafruit Industries.
|
||||
MIT license, all text above must be included in any redistribution
|
||||
****************************************************/
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
This tests the alpha blending function that is used with the anti-aliased
|
||||
fonts:
|
||||
|
||||
Alpha = 0 = 100% background, alpha = 255 = 100% foreground colour
|
||||
|
||||
blendedColor = tft.alphaBlend(alpha, fg_color, bg_color);
|
||||
|
||||
The alphaBlend() function operates on 16 bit colours only
|
||||
A test is included where the colours are mapped to 8 bits after blending
|
||||
|
||||
Information on alpha blending is here
|
||||
https://en.wikipedia.org/wiki/Alpha_compositing
|
||||
|
||||
Example for library:
|
||||
https://github.com/Bodmer/TFT_eSPI
|
||||
|
||||
The sketch has been tested on a 320x240 ILI9341 based TFT, it
|
||||
could be adapted for other screen sizes.
|
||||
|
||||
Created by Bodmer 10/2/18
|
||||
|
||||
#########################################################################
|
||||
###### DON'T FORGET TO UPDATE THE User_Setup.h FILE IN THE LIBRARY ######
|
||||
#########################################################################
|
||||
*/
|
||||
|
||||
#include <TFT_eSPI.h> // Include the graphics library
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI(); // Create object "tft"
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Setup
|
||||
// -------------------------------------------------------------------------
|
||||
void setup(void) {
|
||||
tft.init();
|
||||
tft.setRotation(0);
|
||||
tft.fillScreen(TFT_DARKGREY);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Main loop
|
||||
// -------------------------------------------------------------------------
|
||||
void loop()
|
||||
{
|
||||
// 16 bit colours (5 bits red, 6 bits green, 5 bits blue)
|
||||
// Blend from white to full spectrum
|
||||
for (int a = 0; a < 256; a+=2) // Alpha 0 = 100% background, alpha 255 = 100% foreground
|
||||
{
|
||||
for (int c = 0; c < 192; c++) tft.drawPixel(c, a/2, tft.alphaBlend(a, rainbow(c), TFT_WHITE));
|
||||
}
|
||||
|
||||
// Blend from full spectrum to black
|
||||
for (int a = 255; a > 2; a-=2)
|
||||
{
|
||||
for (int c = 0; c < 192; c++) tft.drawPixel(c, 128 + (255-a)/2, tft.alphaBlend(a, rainbow(c), TFT_BLACK));
|
||||
}
|
||||
|
||||
// Blend from white to black (32 grey levels)
|
||||
for (uint16_t a = 0; a < 255; a++) // Alpha 0 = 100% background, alpha 255 = 100% foreground
|
||||
{
|
||||
tft.drawFastHLine(192, a, 12, tft.alphaBlend(a, TFT_BLACK, TFT_WHITE));
|
||||
tft.drawFastHLine(204, a, 12, tft.alphaBlend(a, TFT_BLACK, TFT_RED));
|
||||
tft.drawFastHLine(216, a, 12, tft.alphaBlend(a, TFT_BLACK, TFT_GREEN));
|
||||
tft.drawFastHLine(228, a, 12, tft.alphaBlend(a, TFT_BLACK, TFT_BLUE));
|
||||
}
|
||||
|
||||
delay(4000);
|
||||
|
||||
// Blend from white to colour (32 grey levels)
|
||||
for (uint16_t a = 0; a < 255; a++) // Alpha 0 = 100% background, alpha 255 = 100% foreground
|
||||
{
|
||||
//tft.drawFastHLine(192, a, 12, tft.alphaBlend(a, TFT_BLACK, TFT_WHITE));
|
||||
tft.drawFastHLine(204, a, 12, tft.alphaBlend(a, TFT_RED, TFT_WHITE));
|
||||
tft.drawFastHLine(216, a, 12, tft.alphaBlend(a, TFT_GREEN, TFT_WHITE));
|
||||
tft.drawFastHLine(228, a, 12, tft.alphaBlend(a, TFT_BLUE, TFT_WHITE));
|
||||
}
|
||||
|
||||
delay(4000);
|
||||
|
||||
//*
|
||||
// Decrease to 8 bit colour (3 bits red, 3 bits green, 2 bits blue)
|
||||
// Blend from white to full spectrum
|
||||
for (int a = 0; a < 256; a+=2) // Alpha 0 = 100% background, alpha 255 = 100% foreground
|
||||
{
|
||||
// Convert blended 16 bit colour to 8 bits to reduce colour resolution, then map back to 16 bits for displaying
|
||||
for (int c = 0; c < 192; c++) tft.drawPixel(c, a/2, tft.color8to16(tft.color16to8(tft.alphaBlend(a, rainbow(c), 0xFFFF))));
|
||||
}
|
||||
|
||||
// Blend from full spectrum to black
|
||||
for (int a = 255; a > 2; a-=2)
|
||||
{
|
||||
// Convert blended 16 bit colour to 8 bits to reduce colour resolution, then map back to 16 bits for displaying
|
||||
for (int c = 0; c < 192; c++) tft.drawPixel(c, 128 + (255-a)/2, tft.color8to16(tft.color16to8(tft.alphaBlend(a, rainbow(c), 0))));
|
||||
}
|
||||
|
||||
// Blend from white to black (4 grey levels - it will draw 4 more with a blue tinge due to lower blue bit count)
|
||||
// Blend from black to a primary colour
|
||||
for (uint16_t a = 0; a < 255; a++) // Alpha 0 = 100% background, alpha 255 = 100% foreground
|
||||
{
|
||||
tft.drawFastHLine(192, a, 12, tft.color8to16(tft.color16to8(tft.alphaBlend(a, TFT_BLACK, TFT_WHITE))));
|
||||
tft.drawFastHLine(204, a, 12, tft.color8to16(tft.color16to8(tft.alphaBlend(a, TFT_BLACK, TFT_RED))));
|
||||
tft.drawFastHLine(216, a, 12, tft.color8to16(tft.color16to8(tft.alphaBlend(a, TFT_BLACK, TFT_GREEN))));
|
||||
tft.drawFastHLine(228, a, 12, tft.color8to16(tft.color16to8(tft.alphaBlend(a, TFT_BLACK, TFT_BLUE))));
|
||||
}
|
||||
|
||||
delay(4000);
|
||||
//*/
|
||||
|
||||
/*
|
||||
// 16 bit colours (5 bits red, 6 bits green, 5 bits blue)
|
||||
for (int a = 0; a < 256; a+=2) // Alpha 0 = 100% background, alpha 255 = 100% foreground
|
||||
{
|
||||
for (int c = 0; c < 192; c++) tft.drawPixel(c, a/2, tft.alphaBlend(a, rainbow(c), TFT_CYAN));
|
||||
}
|
||||
|
||||
// Blend from full spectrum to cyan
|
||||
for (int a = 255; a > 2; a-=2)
|
||||
{
|
||||
for (int c = 0; c < 192; c++) tft.drawPixel(c, 128 + (255-a)/2, tft.alphaBlend(a, rainbow(c), TFT_YELLOW));
|
||||
}
|
||||
//*/
|
||||
|
||||
/*
|
||||
// Blend other colour transitions for test purposes
|
||||
for (uint16_t a = 0; a < 255; a++) // Alpha 0 = 100% background, alpha 255 = 100% foreground
|
||||
{
|
||||
tft.drawFastHLine(192, a, 12, tft.alphaBlend(a, TFT_WHITE, TFT_WHITE)); // Should show as solid white
|
||||
tft.drawFastHLine(204, a, 12, tft.alphaBlend(a, TFT_BLACK, TFT_BLACK)); // Should show as solid black
|
||||
tft.drawFastHLine(216, a, 12, tft.alphaBlend(a, TFT_YELLOW, TFT_CYAN)); // Brightness should be fairly even
|
||||
tft.drawFastHLine(228, a, 12, tft.alphaBlend(a, TFT_CYAN, TFT_MAGENTA));// Brightness should be fairly even
|
||||
}
|
||||
|
||||
delay(4000);
|
||||
//*/
|
||||
}
|
||||
|
||||
|
||||
// #########################################################################
|
||||
// Return a 16 bit rainbow colour
|
||||
// #########################################################################
|
||||
unsigned int rainbow(byte value)
|
||||
{
|
||||
// If 'value' is in the range 0-159 it is converted to a spectrum colour
|
||||
// from 0 = red through to 127 = blue to 159 = violet
|
||||
// Extending the range to 0-191 adds a further violet to red band
|
||||
|
||||
value = value%192;
|
||||
|
||||
byte red = 0; // Red is the top 5 bits of a 16 bit colour value
|
||||
byte green = 0; // Green is the middle 6 bits, but only top 5 bits used here
|
||||
byte blue = 0; // Blue is the bottom 5 bits
|
||||
|
||||
byte sector = value >> 5;
|
||||
byte amplit = value & 0x1F;
|
||||
|
||||
switch (sector)
|
||||
{
|
||||
case 0:
|
||||
red = 0x1F;
|
||||
green = amplit; // Green ramps up
|
||||
blue = 0;
|
||||
break;
|
||||
case 1:
|
||||
red = 0x1F - amplit; // Red ramps down
|
||||
green = 0x1F;
|
||||
blue = 0;
|
||||
break;
|
||||
case 2:
|
||||
red = 0;
|
||||
green = 0x1F;
|
||||
blue = amplit; // Blue ramps up
|
||||
break;
|
||||
case 3:
|
||||
red = 0;
|
||||
green = 0x1F - amplit; // Green ramps down
|
||||
blue = 0x1F;
|
||||
break;
|
||||
case 4:
|
||||
red = amplit; // Red ramps up
|
||||
green = 0;
|
||||
blue = 0x1F;
|
||||
break;
|
||||
case 5:
|
||||
red = 0x1F;
|
||||
green = 0;
|
||||
blue = 0x1F - amplit; // Blue ramps down
|
||||
break;
|
||||
}
|
||||
|
||||
return red << 11 | green << 6 | blue;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Example sketch to demonstrate the drawing of X BitMap (XBM)
|
||||
// format image onto the display.
|
||||
|
||||
// Information on the X BitMap (XBM) format can be found here:
|
||||
// https://en.wikipedia.org/wiki/X_BitMap
|
||||
|
||||
// This example is part of the TFT_eSPI library:
|
||||
// https://github.com/Bodmer/TFT_eSPI
|
||||
|
||||
// Created by Bodmer 23/04/18
|
||||
|
||||
#include "xbm.h" // Sketch tab header for xbm images
|
||||
|
||||
#include <TFT_eSPI.h> // Hardware-specific library
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI(); // Invoke library
|
||||
|
||||
|
||||
void setup()
|
||||
{
|
||||
tft.begin(); // Initialise the display
|
||||
tft.fillScreen(TFT_BLACK); // Black screen fill
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
|
||||
// Example 1
|
||||
// =========
|
||||
// Random x and y coordinates
|
||||
int x = random(tft.width() - logoWidth);
|
||||
int y = random(tft.height() - logoHeight);
|
||||
|
||||
// Draw bitmap with top left corner at x,y with foreground only color
|
||||
// Bits set to 1 plot as the defined color, bits set to 0 are not plotted
|
||||
// x y xbm xbm width xbm height color
|
||||
tft.drawXBitmap(x, y, logo, logoWidth, logoHeight, TFT_WHITE);
|
||||
|
||||
delay(500);
|
||||
|
||||
// Erase old one by drawing over with background colour
|
||||
tft.drawXBitmap(x, y, logo, logoWidth, logoHeight, TFT_BLACK);
|
||||
|
||||
|
||||
// Example 2
|
||||
// =========
|
||||
// New random x and y coordinates
|
||||
x = random(tft.width() - logoWidth);
|
||||
y = random(tft.height() - logoHeight);
|
||||
|
||||
// Draw bitmap with top left corner at x,y with foreground and background colors
|
||||
// Bits set to 1 plot as the defined fg color, bits set to 0 are plotted as bg color
|
||||
// x y xbm xbm width xbm height fg color bg color
|
||||
tft.drawXBitmap(x, y, logo, logoWidth, logoHeight, TFT_WHITE, TFT_RED);
|
||||
|
||||
delay(500);
|
||||
|
||||
// Erase old one by drawing over with background colour
|
||||
tft.drawXBitmap(x, y, logo, logoWidth, logoHeight, TFT_BLACK, TFT_BLACK);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Images can be converted to XBM format by using the online converter here:
|
||||
// https://www.online-utility.org/image/convert/to/XBM
|
||||
|
||||
// The output must be pasted in a header file, renamed and adjusted to appear
|
||||
// as as a const unsigned char array in PROGMEM (FLASH program memory).
|
||||
|
||||
// The xbm format adds padding to pixel rows so they are a whole number of bytes
|
||||
// In this example 50 pixel width means 56 bits = 7 bytes
|
||||
// the 50 height then means array uses 50 x 7 = 350 bytes of FLASH
|
||||
// The library ignores the padding bits when drawing the image on the display.
|
||||
|
||||
// Example of the correct format is shown below
|
||||
|
||||
// Espressif logo 50 x 50 pixel array in XBM format
|
||||
#define logoWidth 50 // logo width
|
||||
#define logoHeight 50 // logo height
|
||||
|
||||
// Image is stored in this array
|
||||
PROGMEM const unsigned char logo[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x07, 0xFC, 0x07, 0x00, 0x00, 0x00, 0x82, 0x7F, 0xF0,
|
||||
0x1F, 0x00, 0x00, 0x00, 0xC6, 0xFF, 0xC3, 0x3F, 0x00, 0x00, 0x00, 0xE7,
|
||||
0xFF, 0x8F, 0x7F, 0x00, 0x00, 0x80, 0xE3, 0xFF, 0x1F, 0xFE, 0x00, 0x00,
|
||||
0x80, 0xE1, 0xFF, 0x7F, 0xFC, 0x01, 0x00, 0xC0, 0x00, 0xFF, 0xFF, 0xF8,
|
||||
0x03, 0x00, 0xE0, 0x00, 0xE0, 0xFF, 0xF1, 0x03, 0x00, 0x60, 0xF0, 0x81,
|
||||
0xFF, 0xE3, 0x07, 0x00, 0x60, 0xFC, 0x1F, 0xFE, 0xC7, 0x07, 0x00, 0x30,
|
||||
0xFE, 0x7F, 0xF8, 0x8F, 0x0F, 0x00, 0x30, 0xFF, 0xFF, 0xF1, 0x9F, 0x0F,
|
||||
0x00, 0xB0, 0xFF, 0xFF, 0xE3, 0x3F, 0x0F, 0x00, 0xB0, 0xFF, 0xFF, 0xC7,
|
||||
0x3F, 0x1E, 0x00, 0xB8, 0xFF, 0xFF, 0x8F, 0x7F, 0x1E, 0x00, 0x98, 0x1F,
|
||||
0xFC, 0x3F, 0xFF, 0x1C, 0x00, 0xB8, 0x3F, 0xE0, 0x3F, 0xFE, 0x1C, 0x00,
|
||||
0x98, 0xFF, 0xC3, 0x7F, 0xFE, 0x19, 0x00, 0x98, 0xFF, 0x0F, 0xFF, 0xFC,
|
||||
0x19, 0x00, 0x38, 0xFF, 0x3F, 0xFF, 0xFC, 0x01, 0x00, 0x30, 0xFE, 0x7F,
|
||||
0xFE, 0xF9, 0x03, 0x00, 0x30, 0xFC, 0xFF, 0xFC, 0xF9, 0x03, 0x00, 0x30,
|
||||
0xF8, 0xFF, 0xF8, 0xF3, 0x03, 0x00, 0x30, 0x00, 0xFF, 0xF9, 0xF3, 0x03,
|
||||
0x00, 0x70, 0x00, 0xFC, 0xF9, 0xF3, 0x07, 0x00, 0x60, 0x00, 0xF8, 0xF3,
|
||||
0xF3, 0x07, 0x00, 0xE0, 0xF8, 0xF8, 0xF3, 0xF7, 0x03, 0x00, 0xC0, 0xF8,
|
||||
0xF1, 0xF3, 0xE3, 0x03, 0x00, 0xC0, 0xFD, 0xF1, 0xF3, 0xF7, 0x01, 0x00,
|
||||
0x80, 0xFD, 0xF1, 0xF3, 0xE7, 0x00, 0x00, 0x00, 0xFF, 0xF1, 0xF3, 0x07,
|
||||
0x00, 0x00, 0x00, 0xFF, 0xF8, 0xF3, 0x07, 0x00, 0x00, 0x00, 0x7E, 0xF8,
|
||||
0xF3, 0x83, 0x03, 0x00, 0x00, 0x3C, 0xF8, 0xF3, 0xC3, 0x01, 0x00, 0x00,
|
||||
0x70, 0xF8, 0xF9, 0xE3, 0x00, 0x00, 0x00, 0xE0, 0xE1, 0x41, 0x78, 0x00,
|
||||
0x00, 0x00, 0xC0, 0x0F, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD,
|
||||
0x07, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x80, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, };
|
||||
|
||||
Reference in New Issue
Block a user