fix 本地依赖包

This commit is contained in:
2023-07-02 18:44:21 +08:00
parent d9b85f5fd1
commit 210ef88273
585 changed files with 471970 additions and 4 deletions
@@ -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;
}
@@ -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, };