Hello,
I'm currently having issues combining my M4 Express with a pair of GC9A01 tft lcds. I am still learning things but have hit a brick wall. I'm trying to get them to work before I start customizing for my project. Currently I'm just getting static on both displays. I've tried swapping out displays with a third one that I have but still get static. I'm unsure if I don't have something coded correctly or if I wired things wrong. I've combined a few different tutorials to get to this point. Any help or pointing of any directions would be helpful I've been using Dronebot Workshop to figure out how to wire the GC9A01s and Wermy for wiring the M4 Express. Here is what I currently have:
(Cirkit Designer IDE)
It likely depends on the code.
It looks to me like you have an I2C interface on the LCDs (SDA, SCL), but have them connected to the SPI interface of the M4 (MO, SCK)
Hard to say whether that's a mistake or intentional, without knowing exactly which GC9A01 displays you have - there seem to be quite a variety!
Also, it looks like you have Vcc on the display, which should be 3.3V, connected to USB on the feather, which is probably 5V.
These are the ones that I'm using, the set up is the same as in the photo (which I'm surprised compared to the other known style. It is SPI style interface.
According to the item description, it is 3-5v but have tried both USB (5v) and the 3v connection on the board
I have the code connected in with the Cirkit Designer link, I changed all the ports to the supported but here is the code as well.
Main code:
//--------------------------------------------------------------------------
// 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.
//--------------------------------------------------------------------------
#include <SPI.h>
#include <Adafruit_GFX.h>
#ifdef ARDUINO_ARCH_SAMD
#include <Adafruit_ZeroDMA.h>
#endif
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.
} eyeInfo_t;
#include "config.h" // ****** CONFIGURATION IS DONE IN HERE ******
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_)
typedef Adafruit_ST7735 displayType; // Using TFT display(s)
#else
typedef Adafruit_SSD1351 displayType; // Using OLED display(s)
#endif
// 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
uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
} eyeBlink;
typedef struct {
boolean eyeInMotion = false;
int16_t eyeOldX=512, eyeOldY=512, eyeNewX=512, eyeNewY=512;
uint32_t eyeMoveStartTime = 0L;
int32_t eyeMoveDuration = 0L;
int16_t eyeX, eyeY;
uint8_t uThreshold = 128;
} eyeMove;
#define NUM_EYES (sizeof eyeInfo / sizeof eyeInfo[0]) // config.h pin list
struct { // One-per-eye structure
displayType *display; // -> OLED/TFT object
eyeBlink blink; // Current blink/wink state
eyeMove move;
} eye[NUM_EYES];
#ifdef ARDUINO_ARCH_SAMD
// SAMD boards use DMA (Teensy uses SPI FIFO instead):
// Two single-line 128-pixel buffers (16bpp) are used for DMA.
// Though you'd think fewer larger transfers would improve speed,
// multi-line buffering made no appreciable difference.
uint16_t dmaBuf[2][128];
uint8_t dmaIdx = 0; // Active DMA buffer # (alternate fill/send)
Adafruit_ZeroDMA dma;
DmacDescriptor *descriptor;
// DMA transfer-in-progress indicator and callback
static volatile bool dma_busy = false;
static void dma_callback(Adafruit_ZeroDMA *dma) { dma_busy = false; }
#endif
uint32_t startTime; // For FPS indicator
// INITIALIZATION -- runs once at startup ----------------------------------
void setup(void) {
uint8_t e; // Eye index, 0 to NUM_EYES-1
Serial.begin(115200);
randomSeed(analogRead(A3)); // Seed random() from floating analog input
#ifdef DISPLAY_BACKLIGHT
// Enable backlight pin, initially off
pinMode(DISPLAY_BACKLIGHT, OUTPUT);
digitalWrite(DISPLAY_BACKLIGHT, LOW);
#endif
// Initialize eye objects based on eyeInfo list in config.h:
for(e=0; e<NUM_EYES; e++) {
eye[e].display = new displayType(eyeInfo[e].select, DISPLAY_DC, -1);
eye[e].blink.state = NOBLINK;
// If project involves only ONE eye and NO other SPI devices, its
// select line can be permanently tied to GND and corresponding pin
// in config.h set to -1. Best to use it though.
if(eyeInfo[e].select >= 0) {
pinMode(eyeInfo[e].select, OUTPUT);
digitalWrite(eyeInfo[e].select, HIGH); // Deselect them all
}
// 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
#if defined(DISPLAY_RESET) && (DISPLAY_RESET >= 0)
// Because both displays share a common reset pin, -1 is passed to
// the display constructor above to prevent the begin() function from
// resetting both displays after one is initialized. Instead, handle
// the reset manually here to take care of both displays just once:
pinMode(DISPLAY_RESET, OUTPUT);
digitalWrite(DISPLAY_RESET, LOW); delay(1);
digitalWrite(DISPLAY_RESET, HIGH); delay(50);
// Alternately, all display reset pin(s) could be connected to the
// microcontroller reset, in which case DISPLAY_RESET should be set
// to -1 or left undefined in config.h.
#endif
// After all-displays reset, now call init/begin func for each display:
for(e=0; e<NUM_EYES; e++) {
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
eye[e].display->initR(INITR_144GREENTAB);
#else // OLED
eye[e].display->begin();
#endif
eye[e].display->setRotation(eyeInfo[e].rotation);
}
#if defined(LOGO_TOP_WIDTH) || defined(COLOR_LOGO_WIDTH)
// I noticed lots of folks getting right/left eyes flipped, or
// installing upside-down, etc. Logo split across screens may help:
for(e=0; e<NUM_EYES; e++) { // Another pass, after all screen inits
eye[e].display->fillScreen(0);
#ifdef LOGO_TOP_WIDTH
// Monochrome Adafruit logo is 2 mono bitmaps:
eye[e].display->drawBitmap(NUM_EYES*64 - e*128 - 20,
0, logo_top, LOGO_TOP_WIDTH, LOGO_TOP_HEIGHT, 0xFFFF);
eye[e].display->drawBitmap(NUM_EYES*64 - e*128 - LOGO_BOTTOM_WIDTH/2,
LOGO_TOP_HEIGHT, logo_bottom, LOGO_BOTTOM_WIDTH, LOGO_BOTTOM_HEIGHT,
0xFFFF);
#else
// Color sponsor logo is one RGB bitmap:
eye[e].display->fillScreen(color_logo[0]);
eye[0].display->drawRGBBitmap(
(eye[e].display->width() - COLOR_LOGO_WIDTH ) / 2,
(eye[e].display->height() - COLOR_LOGO_HEIGHT) / 2,
color_logo, COLOR_LOGO_WIDTH, COLOR_LOGO_HEIGHT);
#endif
// After logo is drawn
}
#ifdef DISPLAY_BACKLIGHT
int i;
for(i=0; i<BACKLIGHT_MAX; i++) { // Fade logo in
analogWrite(DISPLAY_BACKLIGHT, i);
delay(2);
}
delay(1400); // Pause for screen layout/orientation
for(; i>=0; i--) {
analogWrite(DISPLAY_BACKLIGHT, i);
delay(2);
}
for(e=0; e<NUM_EYES; e++) { // Clear display(s)
eye[e].display->fillScreen(0);
}
delay(100);
#else
delay(2000); // Pause for screen layout/orientation
#endif // DISPLAY_BACKLIGHT
#endif // LOGO_TOP_WIDTH
// One of the displays is configured to mirror on the X axis. Simplifies
// eyelid handling in the drawEye() function -- no need for distinct
// L-to-R or R-to-L inner loops. Just the X coordinate of the iris is
// then reversed when drawing this eye, so they move the same. Magic!
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
const uint8_t mirrorTFT[] = { 0x88, 0x28, 0x48, 0xE8 }; // Mirror+rotate
digitalWrite(eyeInfo[0].select, LOW);
digitalWrite(DISPLAY_DC, LOW);
#ifdef ST77XX_MADCTL
SPI.transfer(ST77XX_MADCTL); // Current TFT lib
#else
SPI.transfer(ST7735_MADCTL); // Older TFT lib
#endif
digitalWrite(DISPLAY_DC, HIGH);
SPI.transfer(mirrorTFT[eyeInfo[0].rotation & 3]);
digitalWrite(eyeInfo[0].select , HIGH);
#else // OLED
const uint8_t rotateOLED[] = { 0x74, 0x77, 0x66, 0x65 },
mirrorOLED[] = { 0x76, 0x67, 0x64, 0x75 }; // Mirror+rotate
// If OLED, loop through ALL eyes and set up remap register
// from either mirrorOLED[] (first eye) or rotateOLED[] (others).
// The OLED library doesn't normally use the remap reg (TFT does).
for(e=0; e<NUM_EYES; e++) {
eye[e].display->writeCommand(SSD1351_CMD_SETREMAP);
eye[e].display->writeData(e ?
rotateOLED[eyeInfo[e].rotation & 3] :
mirrorOLED[eyeInfo[e].rotation & 3]);
}
#endif
#ifdef ARDUINO_ARCH_SAMD
// Set up SPI DMA on SAMD boards:
int dmac_id;
volatile uint32_t *data_reg;
if(&PERIPH_SPI == &sercom0) {
dmac_id = SERCOM0_DMAC_ID_TX;
data_reg = &SERCOM0->SPI.DATA.reg;
#if defined SERCOM1
} else if(&PERIPH_SPI == &sercom1) {
dmac_id = SERCOM1_DMAC_ID_TX;
data_reg = &SERCOM1->SPI.DATA.reg;
#endif
#if defined SERCOM2
} else if(&PERIPH_SPI == &sercom2) {
dmac_id = SERCOM2_DMAC_ID_TX;
data_reg = &SERCOM2->SPI.DATA.reg;
#endif
#if defined SERCOM3
} else if(&PERIPH_SPI == &sercom3) {
dmac_id = SERCOM3_DMAC_ID_TX;
data_reg = &SERCOM3->SPI.DATA.reg;
#endif
#if defined SERCOM4
} else if(&PERIPH_SPI == &sercom4) {
dmac_id = SERCOM4_DMAC_ID_TX;
data_reg = &SERCOM4->SPI.DATA.reg;
#endif
#if defined SERCOM5
} else if(&PERIPH_SPI == &sercom5) {
dmac_id = SERCOM5_DMAC_ID_TX;
data_reg = &SERCOM5->SPI.DATA.reg;
#endif
}
dma.allocate();
dma.setTrigger(dmac_id);
dma.setAction(DMA_TRIGGER_ACTON_BEAT);
descriptor = dma.addDescriptor(
NULL, // move data
(void *)data_reg, // to here
sizeof dmaBuf[0], // this many...
DMA_BEAT_SIZE_BYTE, // bytes/hword/words
true, // increment source addr?
false); // increment dest addr?
dma.setCallback(dma_callback);
#endif // End SAMD-specific SPI DMA init
#ifdef DISPLAY_BACKLIGHT
analogWrite(DISPLAY_BACKLIGHT, BACKLIGHT_MAX);
#endif
startTime = millis(); // For frame-rate calculation
}
// EYE-RENDERING FUNCTION --------------------------------------------------
SPISettings settings(SPI_FREQ, MSBFIRST, SPI_MODE0);
void drawEye( // Renders one eye. Inputs must be pre-clipped & valid.
uint8_t e, // Eye array index; 0 or 1 for left/right
uint32_t iScale, // Scale factor for iris
uint8_t scleraX, // First pixel X offset into sclera image
uint8_t scleraY, // First pixel Y offset into sclera image
uint8_t uT, // Upper eyelid threshold value
uint8_t lT) { // Lower eyelid threshold value
uint8_t screenX, screenY, scleraXsave;
int16_t irisX, irisY;
uint16_t p, a;
uint32_t d;
// 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.
SPI.beginTransaction(settings);
digitalWrite(eyeInfo[e].select, LOW); // Chip select
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
eye[e].display->setAddrWindow(0, 0, 128, 128);
#else // OLED
eye[e].display->writeCommand(SSD1351_CMD_SETROW); // Y range
eye[e].display->writeData(0); eye[e].display->writeData(SCREEN_HEIGHT - 1);
eye[e].display->writeCommand(SSD1351_CMD_SETCOLUMN); // X range
eye[e].display->writeData(0); eye[e].display->writeData(SCREEN_WIDTH - 1);
eye[e].display->writeCommand(SSD1351_CMD_WRITERAM); // Begin write
#endif
digitalWrite(eyeInfo[e].select, LOW); // Re-chip-select
digitalWrite(DISPLAY_DC, HIGH); // Data mode
// 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++) {
#ifdef ARDUINO_ARCH_SAMD
uint16_t *ptr = &dmaBuf[dmaIdx][0];
#endif
scleraX = scleraXsave;
irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
for(screenX=0; screenX<SCREEN_WIDTH; screenX++, scleraX++, irisX++) {
if((lower[screenY][screenX] <= lT) ||
(upper[screenY][screenX] <= uT)) { // Covered by eyelid
p = 0;
} else if((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
(irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
p = sclera[scleraY][scleraX];
} else { // Maybe iris...
p = polar[irisY][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 = iris[d][a]; // Pixel = iris
} else { // Not in iris
p = sclera[scleraY][scleraX]; // Pixel = sclera
}
}
#ifdef ARDUINO_ARCH_SAMD
*ptr++ = __builtin_bswap16(p); // DMA: store in scanline buffer
#else
// SPI FIFO technique from Paul Stoffregen's ILI9341_t3 library:
while(KINETISK_SPI0.SR & 0xC000); // Wait for space in FIFO
KINETISK_SPI0.PUSHR = p | SPI_PUSHR_CTAS(1) | SPI_PUSHR_CONT;
#endif
} // end column
#ifdef ARDUINO_ARCH_SAMD
while(dma_busy); // Wait for prior DMA xfer to finish
descriptor->SRCADDR.reg = (uint32_t)&dmaBuf[dmaIdx] + sizeof dmaBuf[0];
dma_busy = true;
dmaIdx = 1 - dmaIdx;
dma.startJob();
#endif
} // end scanline
#ifdef ARDUINO_ARCH_SAMD
while(dma_busy); // Wait for last scanline to transmit
#else
KINETISK_SPI0.SR |= SPI_SR_TCF; // Clear transfer flag
while((KINETISK_SPI0.SR & 0xF000) || // Wait for SPI FIFO to drain
!(KINETISK_SPI0.SR & SPI_SR_TCF)); // Wait for last bit out
#endif
digitalWrite(eyeInfo[e].select, HIGH); // Deselect
SPI.endTransaction();
}
// 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
void frame( // Process motion for a single frame of left or right eye
uint16_t iScale) { // Iris scale (0-1023) passed in
static uint32_t frames = 0; // Used in frame rate calculation
uint32_t t = micros(); // Time at start of function
if(!(++frames & 255)) { // Every 256 frames...
uint32_t elapsed = (millis() - startTime) / 1000;
if(elapsed) Serial.println(frames / elapsed); // Print FPS
}
for(uint8_t e=0; e<NUM_EYES; e++) {
// X/Y movement
// Autonomous X/Y eye motion
// Periodically initiates motion to a new random point, random speed,
// holds there for random period until next motion.
int32_t dt = t - eye[e].move.eyeMoveStartTime; // uS elapsed since last eye event
if(eye[e].move.eyeInMotion) { // Currently moving?
if(dt >= eye[e].move.eyeMoveDuration) { // Time up? Destination reached.
eye[e].move.eyeInMotion = false; // Stop moving
eye[e].move.eyeMoveDuration = random(3000000); // 0-3 sec stop
eye[e].move.eyeMoveStartTime = t; // Save initial time of stop
eye[e].move.eyeX = eye[e].move.eyeOldX = eye[e].move.eyeNewX; // Save position
eye[e].move.eyeY = eye[e].move.eyeOldY = eye[e].move.eyeNewY;
} else { // Move time's not yet fully elapsed -- interpolate position
int16_t easeVal = ease[255 * dt / eye[e].move.eyeMoveDuration] + 1; // Ease curve
eye[e].move.eyeX = eye[e].move.eyeOldX + (((eye[e].move.eyeNewX - eye[e].move.eyeOldX) * easeVal) / 256); // Interp X
eye[e].move.eyeY = eye[e].move.eyeOldY + (((eye[e].move.eyeNewY - eye[e].move.eyeOldY) * easeVal) / 256); // and Y
}
} else { // Eye stopped
eye[e].move.eyeX = eye[e].move.eyeOldX;
eye[e].move.eyeY = eye[e].move.eyeOldY;
if(dt > eye[e].move.eyeMoveDuration) { // Time up? Begin new move.
int16_t dx, dy;
uint32_t d;
do { // Pick new dest in circle
eye[e].move.eyeNewX = random(1024);
eye[e].move.eyeNewY = random(1024);
dx = (eye[e].move.eyeNewX * 2) - 1023;
dy = (eye[e].move.eyeNewY * 2) - 1023;
} while((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying
eye[e].move.eyeMoveDuration = random(72000, 144000); // ~1/14 - ~1/7 sec
eye[e].move.eyeMoveStartTime = t; // Save initial time of move
eye[e].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 - eye[e].blink.timeOfLastBlink) >= eye[e].blink.timeToNextBlink) { // Start new blink?
eye[e].blink.timeOfLastBlink = t;
eye[e].blink.duration = 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.timeToNextBlink = eye[e].blink.duration * 3 + random(8000000);
}
#endif
if(eye[e].blink.state) { // Eye currently blinking?
// Check if current blink state time has elapsed
if((t - eye[e].blink.startTime) >= eye[e].blink.duration) {
// Yes -- increment blink state, unless...
if((eye[e].blink.state == ENBLINK) && ( // Enblinking and...
((eyeInfo[e].wink >= 0) &&
digitalRead(eyeInfo[e].wink) == LOW) )) {
// Don't advance state yet -- eye is held closed instead
} else { // No buttons, or other state...
if(++eye[e].blink.state > DEBLINK) { // Deblinking finished?
eye[e].blink.state = NOBLINK; // No longer blinking
} else { // Advancing from ENBLINK to DEBLINK mode
eye[e].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
eye[e].blink.startTime = t;
}
}
}
} else { // Not currently blinking...check buttons!
if((eyeInfo[e].wink >= 0) &&
(digitalRead(eyeInfo[e].wink) == LOW)) { // Wink!
eye[e].blink.state = ENBLINK;
eye[e].blink.startTime = t;
eye[e].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()
eye[e].move.eyeX = map(eye[e].move.eyeX, 0, 1023, 0, SCLERA_WIDTH - 128);
eye[e].move.eyeY = map(eye[e].move.eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
if(e == 1) eye[e].move.eyeX = (SCLERA_WIDTH - 128) - eye[e].move.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...
if(NUM_EYES > 1) eye[e].move.eyeX += 4;
if(eye[e].move.eyeX > (SCLERA_WIDTH - 128)) eye[e].move.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.
uint8_t lThreshold, n;
#ifdef TRACKING
int16_t sampleX = SCLERA_WIDTH / 2 - (eye[e].move.eyeX / 2), // Reduce X influence
sampleY = SCLERA_HEIGHT / 2 - (eye[e].move.eyeY + IRIS_HEIGHT / 4);
// Eyelid is slightly asymmetrical, so two readings are taken, averaged
if(sampleY < 0) n = 0;
else n = (upper[sampleY][sampleX] +
upper[sampleY][SCREEN_WIDTH - 1 - sampleX]) / 2;
eye[e].move.uThreshold = (eye[e].move.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 - eye[e].move.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[e].blink.state) { // Eye currently blinking?
uint32_t s = (t - eye[e].blink.startTime);
if(s >= eye[e].blink.duration) s = 255; // At or past blink end
else s = 255 * s / eye[e].blink.duration; // Mid-blink
s = (eye[e].blink.state == DEBLINK) ? 1 + s : 256 - s;
n = (eye[e].move.uThreshold * s + 254 * (257 - s)) / 256;
lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
} else {
n = eye[e].move.uThreshold;
}
// Pass all the derived values to the eye-rendering function:
drawEye(e, iScale, eye[e].move.eyeX, eye[e].move.eyeY, n, lThreshold);
}
}
// 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.
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 // !LIGHT_PIN
// MAIN LOOP -- runs continuously after setup() ----------------------------
void loop() {
#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
}
Config:
// 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.
#ifdef ADAFRUIT_HALLOWING // Hallowing, with one eye, does this by default
#define SYMMETRICAL_EYELID
#else // Otherwise your choice, standard is asymmetrical
#define SYMMETRICAL_EYELID
#endif
// Enable ONE of these #includes -- HUGE graphics tables for various eyes:
#include "graphics/defaultEye.h" // Standard human-ish hazel eye -OR-
//#include "graphics/dragonEye.h" // Slit pupil fiery dragon/demon eye -OR-
//#include "graphics/noScleraEye.h" // Large iris, no sclera -OR-
//#include "graphics/goatEye.h" // Horizontal pupil goat/Krampus eye -OR-
//#include "graphics/newtEye.h" // Eye of newt
// Optional: enable this line for startup logo (screen test/orient):
#if !defined ADAFRUIT_HALLOWING // Hallowing can't always fit logo+eye
#include "graphics/logo.h" // Otherwise your choice, if it fits
#endif
// EYE LIST ----------------------------------------------------------------
// 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), and a screen
// rotation value (0-3) for that eye.
eyeInfo_t eyeInfo[] = {
#ifdef ADAFRUIT_HALLOWING
{ 10, -1, 2 }, // SINGLE EYE display-select and wink pins, rotate 180
#else
// LEFT EYE display-select and wink pins, no rotation
{ 10, 2, 2 }, // RIGHT EYE display-select and wink pins, no rotation
{ 12, 2, 2 }
#endif
};
// DISPLAY HARDWARE SETTINGS (screen type & connections) -------------------
#ifdef ADAFRUIT_HALLOWING
#include <Adafruit_ST7735.h> // TFT display library
#define DISPLAY_DC 7 // Display data/command pin
#define DISPLAY_RESET 11 // Display reset pin
#define DISPLAY_BACKLIGHT
#define BACKLIGHT_MAX 128
#else
// Enable ONE of these #includes to specify the display type being used
//#include <Adafruit_SSD1351.h> // OLED display library -OR-
#include <Adafruit_ST7735.h> // TFT display library (enable one only)
#define DISPLAY_DC 7 // Data/command pin for ALL displays
#define DISPLAY_RESET 11 // Reset pin for ALL displays
#endif
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_)
#define SPI_FREQ 24000000 // TFT: use max SPI (clips to 12 MHz on M0)
#else // OLED
#if !defined(ARDUINO_ARCH_SAMD) && (F_CPU <= 72000000)
#define SPI_FREQ 24000000 // OLED: 24 MHz on 72 MHz Teensy only
#else
#define SPI_FREQ 12000000 // OLED: 12 MHz in all other cases
#endif
#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 BLINK_PIN 1 // Pin for manual blink button (BOTH eyes)
#define AUTOBLINK // If defined, eyes also blink autonomously
#ifdef ADAFRUIT_HALLOWING
#define LIGHT_PIN A1 // Hallowing light sensor pin
#define LIGHT_CURVE 0.33 // Light sensor adjustment curve
#define LIGHT_MIN 30 // Minimum useful reading from light sensor
#define LIGHT_MAX 980 // Maximum useful reading from sensor
#else
#define LIGHT_PIN A2 // Photocell or potentiometer (else auto iris)
//#define LIGHT_PIN_FLIP // If defined, reverse reading from dial/photocell
#define LIGHT_MIN 0 // Lower reading from sensor
#define LIGHT_MAX 1023 // Upper reading from sensor
#endif
#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 120 // Iris size (0-1023) in brightest light
#endif
#if !defined(IRIS_MAX)
#define IRIS_MAX 720 // Iris size (0-1023) in darkest light
#endif
Thanks.
I don't see any mention of the GC9A01 controller in your code.
Have you downloaded/installed GitHub - adafruit/Adafruit_GC9A01A: Adafruit_GFX-compatible library for GC9A01A display driver ?
Maybe start with the examples in that library, and a single display...
I did the example with both eyes one eye at a time and it worked. I'm assuming that I just don't have something correct in the coding then.
I added the #include_GC9A01A.h into the code and still only produces static.
// 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.
//--------------------------------------------------------------------------
#include <SPI.h>
#include <Adafruit_GFX.h>
#include "Adafruit_GC9A01A.h"
#ifdef ARDUINO_ARCH_SAMD
#include <Adafruit_ZeroDMA.h>
#endif
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.
} eyeInfo_t;
#include "config.h" // ****** CONFIGURATION IS DONE IN HERE ******
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_)
typedef Adafruit_ST7735 displayType; // Using TFT display(s)
#else
typedef Adafruit_SSD1351 displayType; // Using OLED display(s)
#endif
// 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
uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
} eyeBlink;
typedef struct {
boolean eyeInMotion = false;
int16_t eyeOldX=512, eyeOldY=512, eyeNewX=512, eyeNewY=512;
uint32_t eyeMoveStartTime = 0L;
int32_t eyeMoveDuration = 0L;
int16_t eyeX, eyeY;
uint8_t uThreshold = 128;
} eyeMove;
#define NUM_EYES (sizeof eyeInfo / sizeof eyeInfo[0]) // config.h pin list
struct { // One-per-eye structure
displayType *display; // -> OLED/TFT object
eyeBlink blink; // Current blink/wink state
eyeMove move;
} eye[NUM_EYES];
#ifdef ARDUINO_ARCH_SAMD
// SAMD boards use DMA (Teensy uses SPI FIFO instead):
// Two single-line 128-pixel buffers (16bpp) are used for DMA.
// Though you'd think fewer larger transfers would improve speed,
// multi-line buffering made no appreciable difference.
uint16_t dmaBuf[2][128];
uint8_t dmaIdx = 0; // Active DMA buffer # (alternate fill/send)
Adafruit_ZeroDMA dma;
DmacDescriptor *descriptor;
// DMA transfer-in-progress indicator and callback
static volatile bool dma_busy = false;
static void dma_callback(Adafruit_ZeroDMA *dma) { dma_busy = false; }
#endif
uint32_t startTime; // For FPS indicator
// INITIALIZATION -- runs once at startup ----------------------------------
void setup(void) {
uint8_t e; // Eye index, 0 to NUM_EYES-1
Serial.begin(115200);
randomSeed(analogRead(A3)); // Seed random() from floating analog input
#ifdef DISPLAY_BACKLIGHT
// Enable backlight pin, initially off
pinMode(DISPLAY_BACKLIGHT, OUTPUT);
digitalWrite(DISPLAY_BACKLIGHT, LOW);
#endif
// Initialize eye objects based on eyeInfo list in config.h:
for(e=0; e<NUM_EYES; e++) {
eye[e].display = new displayType(eyeInfo[e].select, DISPLAY_DC, -1);
eye[e].blink.state = NOBLINK;
// If project involves only ONE eye and NO other SPI devices, its
// select line can be permanently tied to GND and corresponding pin
// in config.h set to -1. Best to use it though.
if(eyeInfo[e].select >= 0) {
pinMode(eyeInfo[e].select, OUTPUT);
digitalWrite(eyeInfo[e].select, HIGH); // Deselect them all
}
// 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
#if defined(DISPLAY_RESET) && (DISPLAY_RESET >= 0)
// Because both displays share a common reset pin, -1 is passed to
// the display constructor above to prevent the begin() function from
// resetting both displays after one is initialized. Instead, handle
// the reset manually here to take care of both displays just once:
pinMode(DISPLAY_RESET, OUTPUT);
digitalWrite(DISPLAY_RESET, LOW); delay(1);
digitalWrite(DISPLAY_RESET, HIGH); delay(50);
// Alternately, all display reset pin(s) could be connected to the
// microcontroller reset, in which case DISPLAY_RESET should be set
// to -1 or left undefined in config.h.
#endif
// After all-displays reset, now call init/begin func for each display:
for(e=0; e<NUM_EYES; e++) {
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
eye[e].display->initR(INITR_144GREENTAB);
#else // OLED
eye[e].display->begin();
#endif
eye[e].display->setRotation(eyeInfo[e].rotation);
}
#if defined(LOGO_TOP_WIDTH) || defined(COLOR_LOGO_WIDTH)
// I noticed lots of folks getting right/left eyes flipped, or
// installing upside-down, etc. Logo split across screens may help:
for(e=0; e<NUM_EYES; e++) { // Another pass, after all screen inits
eye[e].display->fillScreen(0);
#ifdef LOGO_TOP_WIDTH
// Monochrome Adafruit logo is 2 mono bitmaps:
eye[e].display->drawBitmap(NUM_EYES*64 - e*128 - 20,
0, logo_top, LOGO_TOP_WIDTH, LOGO_TOP_HEIGHT, 0xFFFF);
eye[e].display->drawBitmap(NUM_EYES*64 - e*128 - LOGO_BOTTOM_WIDTH/2,
LOGO_TOP_HEIGHT, logo_bottom, LOGO_BOTTOM_WIDTH, LOGO_BOTTOM_HEIGHT,
0xFFFF);
#else
// Color sponsor logo is one RGB bitmap:
eye[e].display->fillScreen(color_logo[0]);
eye[0].display->drawRGBBitmap(
(eye[e].display->width() - COLOR_LOGO_WIDTH ) / 2,
(eye[e].display->height() - COLOR_LOGO_HEIGHT) / 2,
color_logo, COLOR_LOGO_WIDTH, COLOR_LOGO_HEIGHT);
#endif
// After logo is drawn
}
#ifdef DISPLAY_BACKLIGHT
int i;
for(i=0; i<BACKLIGHT_MAX; i++) { // Fade logo in
analogWrite(DISPLAY_BACKLIGHT, i);
delay(2);
}
delay(1400); // Pause for screen layout/orientation
for(; i>=0; i--) {
analogWrite(DISPLAY_BACKLIGHT, i);
delay(2);
}
for(e=0; e<NUM_EYES; e++) { // Clear display(s)
eye[e].display->fillScreen(0);
}
delay(100);
#else
delay(2000); // Pause for screen layout/orientation
#endif // DISPLAY_BACKLIGHT
#endif // LOGO_TOP_WIDTH
// One of the displays is configured to mirror on the X axis. Simplifies
// eyelid handling in the drawEye() function -- no need for distinct
// L-to-R or R-to-L inner loops. Just the X coordinate of the iris is
// then reversed when drawing this eye, so they move the same. Magic!
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
const uint8_t mirrorTFT[] = { 0x88, 0x28, 0x48, 0xE8 }; // Mirror+rotate
digitalWrite(eyeInfo[0].select, LOW);
digitalWrite(DISPLAY_DC, LOW);
#ifdef ST77XX_MADCTL
SPI.transfer(ST77XX_MADCTL); // Current TFT lib
#else
SPI.transfer(ST7735_MADCTL); // Older TFT lib
#endif
digitalWrite(DISPLAY_DC, HIGH);
SPI.transfer(mirrorTFT[eyeInfo[0].rotation & 3]);
digitalWrite(eyeInfo[0].select , HIGH);
#else // OLED
const uint8_t rotateOLED[] = { 0x74, 0x77, 0x66, 0x65 },
mirrorOLED[] = { 0x76, 0x67, 0x64, 0x75 }; // Mirror+rotate
// If OLED, loop through ALL eyes and set up remap register
// from either mirrorOLED[] (first eye) or rotateOLED[] (others).
// The OLED library doesn't normally use the remap reg (TFT does).
for(e=0; e<NUM_EYES; e++) {
eye[e].display->writeCommand(SSD1351_CMD_SETREMAP);
eye[e].display->writeData(e ?
rotateOLED[eyeInfo[e].rotation & 3] :
mirrorOLED[eyeInfo[e].rotation & 3]);
}
#endif
#ifdef ARDUINO_ARCH_SAMD
// Set up SPI DMA on SAMD boards:
int dmac_id;
volatile uint32_t *data_reg;
if(&PERIPH_SPI == &sercom0) {
dmac_id = SERCOM0_DMAC_ID_TX;
data_reg = &SERCOM0->SPI.DATA.reg;
#if defined SERCOM1
} else if(&PERIPH_SPI == &sercom1) {
dmac_id = SERCOM1_DMAC_ID_TX;
data_reg = &SERCOM1->SPI.DATA.reg;
#endif
#if defined SERCOM2
} else if(&PERIPH_SPI == &sercom2) {
dmac_id = SERCOM2_DMAC_ID_TX;
data_reg = &SERCOM2->SPI.DATA.reg;
#endif
#if defined SERCOM3
} else if(&PERIPH_SPI == &sercom3) {
dmac_id = SERCOM3_DMAC_ID_TX;
data_reg = &SERCOM3->SPI.DATA.reg;
#endif
#if defined SERCOM4
} else if(&PERIPH_SPI == &sercom4) {
dmac_id = SERCOM4_DMAC_ID_TX;
data_reg = &SERCOM4->SPI.DATA.reg;
#endif
#if defined SERCOM5
} else if(&PERIPH_SPI == &sercom5) {
dmac_id = SERCOM5_DMAC_ID_TX;
data_reg = &SERCOM5->SPI.DATA.reg;
#endif
}
dma.allocate();
dma.setTrigger(dmac_id);
dma.setAction(DMA_TRIGGER_ACTON_BEAT);
descriptor = dma.addDescriptor(
NULL, // move data
(void *)data_reg, // to here
sizeof dmaBuf[0], // this many...
DMA_BEAT_SIZE_BYTE, // bytes/hword/words
true, // increment source addr?
false); // increment dest addr?
dma.setCallback(dma_callback);
#endif // End SAMD-specific SPI DMA init
#ifdef DISPLAY_BACKLIGHT
analogWrite(DISPLAY_BACKLIGHT, BACKLIGHT_MAX);
#endif
startTime = millis(); // For frame-rate calculation
}
// EYE-RENDERING FUNCTION --------------------------------------------------
SPISettings settings(SPI_FREQ, MSBFIRST, SPI_MODE0);
void drawEye( // Renders one eye. Inputs must be pre-clipped & valid.
uint8_t e, // Eye array index; 0 or 1 for left/right
uint32_t iScale, // Scale factor for iris
uint8_t scleraX, // First pixel X offset into sclera image
uint8_t scleraY, // First pixel Y offset into sclera image
uint8_t uT, // Upper eyelid threshold value
uint8_t lT) { // Lower eyelid threshold value
uint8_t screenX, screenY, scleraXsave;
int16_t irisX, irisY;
uint16_t p, a;
uint32_t d;
// 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.
SPI.beginTransaction(settings);
digitalWrite(eyeInfo[e].select, LOW); // Chip select
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
eye[e].display->setAddrWindow(0, 0, 128, 128);
#else // OLED
eye[e].display->writeCommand(SSD1351_CMD_SETROW); // Y range
eye[e].display->writeData(0); eye[e].display->writeData(SCREEN_HEIGHT - 1);
eye[e].display->writeCommand(SSD1351_CMD_SETCOLUMN); // X range
eye[e].display->writeData(0); eye[e].display->writeData(SCREEN_WIDTH - 1);
eye[e].display->writeCommand(SSD1351_CMD_WRITERAM); // Begin write
#endif
digitalWrite(eyeInfo[e].select, LOW); // Re-chip-select
digitalWrite(DISPLAY_DC, HIGH); // Data mode
// 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++) {
#ifdef ARDUINO_ARCH_SAMD
uint16_t *ptr = &dmaBuf[dmaIdx][0];
#endif
scleraX = scleraXsave;
irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
for(screenX=0; screenX<SCREEN_WIDTH; screenX++, scleraX++, irisX++) {
if((lower[screenY][screenX] <= lT) ||
(upper[screenY][screenX] <= uT)) { // Covered by eyelid
p = 0;
} else if((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
(irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
p = sclera[scleraY][scleraX];
} else { // Maybe iris...
p = polar[irisY][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 = iris[d][a]; // Pixel = iris
} else { // Not in iris
p = sclera[scleraY][scleraX]; // Pixel = sclera
}
}
#ifdef ARDUINO_ARCH_SAMD
*ptr++ = __builtin_bswap16(p); // DMA: store in scanline buffer
#else
// SPI FIFO technique from Paul Stoffregen's ILI9341_t3 library:
while(KINETISK_SPI0.SR & 0xC000); // Wait for space in FIFO
KINETISK_SPI0.PUSHR = p | SPI_PUSHR_CTAS(1) | SPI_PUSHR_CONT;
#endif
} // end column
#ifdef ARDUINO_ARCH_SAMD
while(dma_busy); // Wait for prior DMA xfer to finish
descriptor->SRCADDR.reg = (uint32_t)&dmaBuf[dmaIdx] + sizeof dmaBuf[0];
dma_busy = true;
dmaIdx = 1 - dmaIdx;
dma.startJob();
#endif
} // end scanline
#ifdef ARDUINO_ARCH_SAMD
while(dma_busy); // Wait for last scanline to transmit
#else
KINETISK_SPI0.SR |= SPI_SR_TCF; // Clear transfer flag
while((KINETISK_SPI0.SR & 0xF000) || // Wait for SPI FIFO to drain
!(KINETISK_SPI0.SR & SPI_SR_TCF)); // Wait for last bit out
#endif
digitalWrite(eyeInfo[e].select, HIGH); // Deselect
SPI.endTransaction();
}
// 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
void frame( // Process motion for a single frame of left or right eye
uint16_t iScale) { // Iris scale (0-1023) passed in
static uint32_t frames = 0; // Used in frame rate calculation
uint32_t t = micros(); // Time at start of function
if(!(++frames & 255)) { // Every 256 frames...
uint32_t elapsed = (millis() - startTime) / 1000;
if(elapsed) Serial.println(frames / elapsed); // Print FPS
}
for(uint8_t e=0; e<NUM_EYES; e++) {
// X/Y movement
// Autonomous X/Y eye motion
// Periodically initiates motion to a new random point, random speed,
// holds there for random period until next motion.
int32_t dt = t - eye[e].move.eyeMoveStartTime; // uS elapsed since last eye event
if(eye[e].move.eyeInMotion) { // Currently moving?
if(dt >= eye[e].move.eyeMoveDuration) { // Time up? Destination reached.
eye[e].move.eyeInMotion = false; // Stop moving
eye[e].move.eyeMoveDuration = random(3000000); // 0-3 sec stop
eye[e].move.eyeMoveStartTime = t; // Save initial time of stop
eye[e].move.eyeX = eye[e].move.eyeOldX = eye[e].move.eyeNewX; // Save position
eye[e].move.eyeY = eye[e].move.eyeOldY = eye[e].move.eyeNewY;
} else { // Move time's not yet fully elapsed -- interpolate position
int16_t easeVal = ease[255 * dt / eye[e].move.eyeMoveDuration] + 1; // Ease curve
eye[e].move.eyeX = eye[e].move.eyeOldX + (((eye[e].move.eyeNewX - eye[e].move.eyeOldX) * easeVal) / 256); // Interp X
eye[e].move.eyeY = eye[e].move.eyeOldY + (((eye[e].move.eyeNewY - eye[e].move.eyeOldY) * easeVal) / 256); // and Y
}
} else { // Eye stopped
eye[e].move.eyeX = eye[e].move.eyeOldX;
eye[e].move.eyeY = eye[e].move.eyeOldY;
if(dt > eye[e].move.eyeMoveDuration) { // Time up? Begin new move.
int16_t dx, dy;
uint32_t d;
do { // Pick new dest in circle
eye[e].move.eyeNewX = random(1024);
eye[e].move.eyeNewY = random(1024);
dx = (eye[e].move.eyeNewX * 2) - 1023;
dy = (eye[e].move.eyeNewY * 2) - 1023;
} while((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying
eye[e].move.eyeMoveDuration = random(72000, 144000); // ~1/14 - ~1/7 sec
eye[e].move.eyeMoveStartTime = t; // Save initial time of move
eye[e].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 - eye[e].blink.timeOfLastBlink) >= eye[e].blink.timeToNextBlink) { // Start new blink?
eye[e].blink.timeOfLastBlink = t;
eye[e].blink.duration = 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.timeToNextBlink = eye[e].blink.duration * 3 + random(8000000);
}
#endif
if(eye[e].blink.state) { // Eye currently blinking?
// Check if current blink state time has elapsed
if((t - eye[e].blink.startTime) >= eye[e].blink.duration) {
// Yes -- increment blink state, unless...
if((eye[e].blink.state == ENBLINK) && ( // Enblinking and...
((eyeInfo[e].wink >= 0) &&
digitalRead(eyeInfo[e].wink) == LOW) )) {
// Don't advance state yet -- eye is held closed instead
} else { // No buttons, or other state...
if(++eye[e].blink.state > DEBLINK) { // Deblinking finished?
eye[e].blink.state = NOBLINK; // No longer blinking
} else { // Advancing from ENBLINK to DEBLINK mode
eye[e].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
eye[e].blink.startTime = t;
}
}
}
} else { // Not currently blinking...check buttons!
if((eyeInfo[e].wink >= 0) &&
(digitalRead(eyeInfo[e].wink) == LOW)) { // Wink!
eye[e].blink.state = ENBLINK;
eye[e].blink.startTime = t;
eye[e].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()
eye[e].move.eyeX = map(eye[e].move.eyeX, 0, 1023, 0, SCLERA_WIDTH - 128);
eye[e].move.eyeY = map(eye[e].move.eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
if(e == 1) eye[e].move.eyeX = (SCLERA_WIDTH - 128) - eye[e].move.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...
if(NUM_EYES > 1) eye[e].move.eyeX += 4;
if(eye[e].move.eyeX > (SCLERA_WIDTH - 128)) eye[e].move.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.
uint8_t lThreshold, n;
#ifdef TRACKING
int16_t sampleX = SCLERA_WIDTH / 2 - (eye[e].move.eyeX / 2), // Reduce X influence
sampleY = SCLERA_HEIGHT / 2 - (eye[e].move.eyeY + IRIS_HEIGHT / 4);
// Eyelid is slightly asymmetrical, so two readings are taken, averaged
if(sampleY < 0) n = 0;
else n = (upper[sampleY][sampleX] +
upper[sampleY][SCREEN_WIDTH - 1 - sampleX]) / 2;
eye[e].move.uThreshold = (eye[e].move.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 - eye[e].move.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[e].blink.state) { // Eye currently blinking?
uint32_t s = (t - eye[e].blink.startTime);
if(s >= eye[e].blink.duration) s = 255; // At or past blink end
else s = 255 * s / eye[e].blink.duration; // Mid-blink
s = (eye[e].blink.state == DEBLINK) ? 1 + s : 256 - s;
n = (eye[e].move.uThreshold * s + 254 * (257 - s)) / 256;
lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
} else {
n = eye[e].move.uThreshold;
}
// Pass all the derived values to the eye-rendering function:
drawEye(e, iScale, eye[e].move.eyeX, eye[e].move.eyeY, n, lThreshold);
}
}
// 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.
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 // !LIGHT_PIN
// MAIN LOOP -- runs continuously after setup() ----------------------------
void loop() {
#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
}```
I'm assuming this is the portion that would let me change the pinouts for the displays. I have them set as I did with the library example
// EYE LIST ----------------------------------------------------------------
// 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), and a screen
// rotation value (0-3) for that eye.
eyeInfo_t eyeInfo[] = {
#ifdef ADAFRUIT_HALLOWING
{ 10, -1, 2 }, // SINGLE EYE display-select and wink pins, rotate 180
#else
// LEFT EYE display-select and wink pins, no rotation
{ 10, 2, 2 }, // RIGHT EYE display-select and wink pins, no rotation
{ 12, 2, 2 }
#endif
};
// DISPLAY HARDWARE SETTINGS (screen type & connections) -------------------
#ifdef ADAFRUIT_HALLOWING
#include <Adafruit_ST7735.h> // TFT display library
#define DISPLAY_DC 7 // Display data/command pin
#define DISPLAY_RESET 11 // Display reset pin
#define DISPLAY_BACKLIGHT
#define BACKLIGHT_MAX 128
#else
// Enable ONE of these #includes to specify the display type being used
//#include <Adafruit_SSD1351.h> // OLED display library -OR-
#include <Adafruit_ST7735.h> // TFT display library (enable one only)
#define DISPLAY_DC 7 // Data/command pin for ALL displays
#define DISPLAY_RESET 11 // Reset pin for ALL displays
#endif
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_)
#define SPI_FREQ 24000000 // TFT: use max SPI (clips to 12 MHz on M0)
#else // OLED
#if !defined(ARDUINO_ARCH_SAMD) && (F_CPU <= 72000000)
#define SPI_FREQ 24000000 // OLED: 24 MHz on 72 MHz Teensy only
#else
#define SPI_FREQ 12000000 // OLED: 12 MHz in all other cases
#endif
#endif
I believe you'll need a
typedef Adafruit_GC9A01A displayType;
somewhere...
Sounds simple enough, I'll figure out where it goes
So I found out that it requires a bunch of rewriting of code in order to get the uncanny eyes working with GC9A01s due to the difference in libraries and controllers I did find an example in the GC9A01 library though which apparently is for uncanny eyes.
#include <elapsedMillis.h>
//This example is configured for using 2 displays on SPI and SPI
//using a ST7789 240x240 display without a CS pin.
//If using a display with a CS pin you can change pin configuration
//in config.h
#define USE_ASYNC_UPDATES
// Define if you wish to see debug information on the ST7789 displays
//#define DEBUG_ST7789
// Define if you wish to debug memory usage. Only works on T4.x
//#define DEBUG_MEMORY
#define BUTTON_ISR 7
//--------------------------------------------------------------------------
// 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.
//--------------------------------------------------------------------------
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <GC9A01A_t3n.h>
typedef struct { // Struct is defined before including config.h --
//int8_t select; // pin numbers for each eye's screen select line
int8_t cs; // Chip select pin.
int8_t dc; // DC pin
int8_t mosi; // mosi
int8_t sck; // sck pin
int8_t rst; // reset pin
int8_t wink; // and wink button (or -1 if none) specified there,
uint8_t rotation; // also display rotation.
uint8_t init_option; // option for Init
} eyeInfo_t;
typedef GC9A01A_t3n displayType; // Using TFT display(s)
#define DISPLAY_SIZE 240
#include "config.h" // ****** CONFIGURATION IS DONE IN HERE ******
#define RGBColor(r, g, b) GC9A01A_t3n::Color565(r, g, b)
// 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;
#define NUM_EYES (sizeof eyeInfo / sizeof eyeInfo[0]) // config.h pin list
struct { // One-per-eye structure
displayType *display; // -> OLED/TFT object
eyeBlink blink; // Current blink/wink state
} eye[NUM_EYES];
uint32_t startTime; // For FPS indicator
// INITIALIZATION -- runs once at startup ----------------------------------
void setup(void) {
uint8_t e; // Eye index, 0 to NUM_EYES-1
Serial.begin(115200);
while (!Serial && millis() < 2000 );
delay(500);
DumpMemoryInfo();
Serial.println("Init");
Serial.flush();
randomSeed(analogRead(A3)); // Seed random() from floating analog input
#ifdef DISPLAY_BACKLIGHT
// Enable backlight pin, initially off
Serial.println("Backlight off");
pinMode(DISPLAY_BACKLIGHT, OUTPUT);
digitalWrite(DISPLAY_BACKLIGHT, LOW);
#endif
// Initialize eye objects based on eyeInfo list in config.h:
for (e = 0; e < NUM_EYES; e++) {
Serial.print("Create display #"); Serial.println(e);
//eye[e].display = new displayType(&TFT_SPI, eyeInfo[e].cs,
// DISPLAY_DC, -1);
//for SPI
//(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
eye[e].display = new displayType(eyeInfo[e].cs, eyeInfo[e].dc, eyeInfo[e].rst,
eyeInfo[e].mosi, eyeInfo[e].sck);
eye[e].blink.state = NOBLINK;
// If project involves only ONE eye and NO other SPI devices, its
// select line can be permanently tied to GND and corresponding pin
// in config.h set to -1. Best to use it though.
if (eyeInfo[e].cs >= 0) {
pinMode(eyeInfo[e].cs, OUTPUT);
digitalWrite(eyeInfo[e].cs, HIGH); // Deselect them all
}
// 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
#if defined(DISPLAY_RESET) && (DISPLAY_RESET >= 0)
// Because both displays share a common reset pin, -1 is passed to
// the display constructor above to prevent the begin() function from
// resetting both displays after one is initialized. Instead, handle
// the reset manually here to take care of both displays just once:
Serial.println("Reset displays");
pinMode(DISPLAY_RESET, OUTPUT);
digitalWrite(DISPLAY_RESET, LOW); delay(1);
digitalWrite(DISPLAY_RESET, HIGH); delay(50);
// Alternately, all display reset pin(s) could be connected to the
// microcontroller reset, in which case DISPLAY_RESET should be set
// to -1 or left undefined in config.h.
#endif
// After all-displays reset, now call init/begin func for each display:
for (e = 0; e < NUM_EYES; e++) {
eye[e].display->begin();
Serial.print("Init ST77xx display #"); Serial.println(e);
Serial.println("Rotate");
eye[e].display->setRotation(eyeInfo[e].rotation);
}
Serial.println("done");
#if defined(LOGO_TOP_WIDTH) || defined(COLOR_LOGO_WIDTH)
Serial.println("Display logo");
// I noticed lots of folks getting right/left eyes flipped, or
// installing upside-down, etc. Logo split across screens may help:
for (e = 0; e < NUM_EYES; e++) { // Another pass, after all screen inits
eye[e].display->fillScreen(0);
#ifdef LOGO_TOP_WIDTH
// Monochrome Adafruit logo is 2 mono bitmaps:
eye[e].display->drawBitmap(NUM_EYES * DISPLAY_SIZE / 2 - e * DISPLAY_SIZE - 20,
0, logo_top, LOGO_TOP_WIDTH, LOGO_TOP_HEIGHT, 0xFFFF);
eye[e].display->drawBitmap(NUM_EYES * DISPLAY_SIZE / 2 - e * DISPLAY_SIZE - LOGO_BOTTOM_WIDTH / 2,
LOGO_TOP_HEIGHT, logo_bottom, LOGO_BOTTOM_WIDTH, LOGO_BOTTOM_HEIGHT,
0xFFFF);
#else
// Color sponsor logo is one RGB bitmap:
eye[e].display->fillScreen(color_logo[0]);
eye[0].display->drawRGBBitmap(
(eye[e].display->width() - COLOR_LOGO_WIDTH ) / 2,
(eye[e].display->height() - COLOR_LOGO_HEIGHT) / 2,
color_logo, COLOR_LOGO_WIDTH, COLOR_LOGO_HEIGHT);
#endif
// After logo is drawn
}
#ifdef DISPLAY_BACKLIGHT
int i;
Serial.println("Fade in backlight");
for (i = 0; i < BACKLIGHT_MAX; i++) { // Fade logo in
analogWrite(DISPLAY_BACKLIGHT, i);
delay(2);
}
delay(1400); // Pause for screen layout/orientation
Serial.println("Fade out backlight");
for (; i >= 0; i--) {
analogWrite(DISPLAY_BACKLIGHT, i);
delay(2);
}
for (e = 0; e < NUM_EYES; e++) { // Clear display(s)
eye[e].display->fillScreen(0);
}
delay(100);
#else
delay(2000); // Pause for screen layout/orientation
#endif // DISPLAY_BACKLIGHT
#endif // LOGO_TOP_WIDTH
// One of the displays is configured to mirror on the X axis. Simplifies
// eyelid handling in the drawEye() function -- no need for distinct
// L-to-R or R-to-L inner loops. Just the X coordinate of the iris is
// then reversed when drawing this eye, so they move the same. Magic!
// The values for setRotation would be: 0XC8(-MX), 0xA8(+MY), 0x8(-MX), 0x68(+MY)
// 0xC0, A0, 0, 60
const uint8_t mirrorTFT[] = { 0x8, 0x20, 0x40, 0xE0 }; // Mirror+rotate
eye[0].display->sendCommand(
GC9A01A_MADCTL, // Current TFT lib
&mirrorTFT[eyeInfo[0].rotation & 3], 1);
#ifdef DISPLAY_BACKLIGHT
Serial.println("Backlight on!");
analogWrite(DISPLAY_BACKLIGHT, BACKLIGHT_MAX);
#endif
for (e = 0; e < NUM_EYES; e++) {
if (!eye[e].display->useFrameBuffer(1)) {
Serial.printf("%d: Use Frame Buffer failed\n", e);
} else {
Serial.printf("$%d: Using Frame buffer\n", e);
}
}
startTime = millis(); // For frame-rate calculation
}
// EYE-RENDERING FUNCTION --------------------------------------------------
SPISettings settings(SPI_FREQ, MSBFIRST, SPI_MODE0);
void drawEye( // Renders one eye. Inputs must be pre-clipped & valid.
uint8_t e, // Eye array index; 0 or 1 for left/right
uint16_t iScale, // Scale factor for iris (0-1023)
uint16_t scleraX, // First pixel X offset into sclera image
uint16_t scleraY, // First pixel Y offset into sclera image
uint8_t uT, // Upper eyelid threshold value
uint8_t lT) { // Lower eyelid threshold value
uint8_t screenX, screenY;
uint16_t scleraXsave;
int16_t irisX, irisY;
uint16_t p, a;
uint32_t d;
uint16_t max_d = 0;
uint16_t max_a = 0;
uint16_t min_d = 0xff;
uint16_t min_a = 0xff;
uint32_t irisThreshold = (DISPLAY_SIZE * (1023 - iScale) + 512) / 1024;
uint32_t irisScale = IRIS_MAP_HEIGHT * 65536 / irisThreshold;
// 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.
// 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;
// Lets wait for any previous update screen to complete.
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 ((lower[screenY][screenX] <= lT) ||
(upper[screenY][screenX] <= uT)) { // Covered by eyelid
p = 0;
} else if ((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
(irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
p = sclera[scleraY][scleraX];
} else { // Maybe iris...
p = polar[irisY][irisX]; // Polar angle/dist
d = p & 0x7F; // Distance from edge (0-127)
if (d < irisThreshold) { // Within scaled iris area
d = d * irisScale / 65536; // d scaled to iris image height
a = (IRIS_MAP_WIDTH * (p >> 7)) / 512; // Angle (X)
p = iris[d][a]; // Pixel = iris
if (d > max_d) max_d = d;
if (a > max_a) max_a = a;
if (d < min_d) min_d = d;
if (a < min_a) min_a = a;
} else { // Not in iris
p = sclera[scleraY][scleraX]; // Pixel = sclera
}
}
eye[e].display->drawPixel(screenX, screenY, p);
} // end column
} // end scanline
#ifdef DEBUG_ST7789
eye[e].display->setCursor(0, 0);
eye[e].display->setTextSize(2);
eye[e].display->setTextColor(ST77XX_RED, ST77XX_BLACK);
eye[e].display->printf("%4u %4u %5u\n(%3u,%3u)", iScale, irisThreshold, irisScale, scleraX, scleraY);
eye[e].display->setCursor(0, DISPLAY_SIZE - 20);
eye[e].display->printf("%u %3u %3u %3u %3u\n", uT, lT, eye[e].blink.state, max_d, max_a);
// Debug
static uint32_t iScale_printed[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
bool print_iScale = (iScale_printed[iScale >> 5] & (1 << (iScale & 0x1f))) ? false : true;
if (print_iScale) {
iScale_printed[iScale >> 5] |= (1 << (iScale & 0x1f));
Serial.printf("%4u : %6u %6u %4u:%4u %4u:%4u\n", iScale, irisThreshold, irisScale, min_d, max_d, min_a, max_a);
}
#endif
#if defined(USE_ASYNC_UPDATES)
if (!eye[e].display->updateScreenAsync()) {
Serial.printf("%d : updateScreenAsync FAILED\n", e);
} else {
//Serial.printf("%d : updateScreenAsync started\n", e);
}
#else
eye[e].display->updateScreen();
#endif
}
// EYE ANIMATION -----------------------------------------------------------
const uint8_t ease[] PROGMEM = { // 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
uint16_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
int16_t eyeX, eyeY;
if (++eyeIndex >= NUM_EYES) eyeIndex = 0; // Cycle through eyes, 1 per call
#if defined(USE_ASYNC_UPDATES)
elapsedMillis emWait = 0;
while (eye[eyeIndex].display->asyncUpdateActive() && (emWait < 1000)) ;
if (emWait >= 1000) Serial.println("Long wait");
#endif
uint32_t t = micros(); // Time at start of function
if (!(++frames & 255)) { // Every 256 frames...
uint32_t elapsed = (millis() - startTime) / 1000;
if (elapsed) Serial.println(frames / elapsed); // Print FPS
EstimateStackUsage();
}
// 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 boolean 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
//Serial.printf("%d: Motion: %d %d (%d,%d)\n", eyeIndex, eyeMoveStartTime,
//eyeMoveDuration, dx, dy);
}
}
#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 - DISPLAY_SIZE);
eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - DISPLAY_SIZE);
if (eyeIndex == 1) eyeX = (SCLERA_WIDTH - DISPLAY_SIZE) - 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...
if (NUM_EYES > 1) eyeX += 4;
if (eyeX > (SCLERA_WIDTH - DISPLAY_SIZE)) eyeX = (SCLERA_WIDTH - DISPLAY_SIZE);
// 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 uint16_t uThreshold = DISPLAY_SIZE;
uint16_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 = (upper[sampleY][sampleX] +
upper[sampleY][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(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;
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
// MAIN LOOP -- runs continuously after setup() ----------------------------
void loop() {
#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
}
// from the linker
// extern unsigned long _stextload;
extern unsigned long _stext;
extern unsigned long _etext;
// extern unsigned long _sdataload;
extern unsigned long _sdata;
extern unsigned long _edata;
extern unsigned long _sbss;
extern unsigned long _ebss;
// extern unsigned long _flexram_bank_config;
extern unsigned long _estack;
void DumpMemoryInfo() {
#if defined(__IMXRT1062__) && defined(DEBUG_MEMORY)
uint32_t flexram_config = IOMUXC_GPR_GPR17;
Serial.printf("IOMUXC_GPR_GPR17:%x IOMUXC_GPR_GPR16:%x IOMUXC_GPR_GPR14:%x\n",
flexram_config, IOMUXC_GPR_GPR16, IOMUXC_GPR_GPR14);
Serial.printf("Initial Stack pointer: %x\n", &_estack);
uint32_t dtcm_size = 0;
uint32_t itcm_size = 0;
for (; flexram_config; flexram_config >>= 2) {
if ((flexram_config & 0x3) == 0x2) dtcm_size += 32768;
else if ((flexram_config & 0x3) == 0x3) itcm_size += 32768;
}
Serial.printf("ITCM allocated: %u DTCM allocated: %u\n", itcm_size, dtcm_size);
Serial.printf("ITCM init range: %x - %x Count: %u\n", &_stext, &_etext, (uint32_t)&_etext - (uint32_t)&_stext);
Serial.printf("DTCM init range: %x - %x Count: %u\n", &_sdata, &_edata, (uint32_t)&_edata - (uint32_t)&_sdata);
Serial.printf("DTCM cleared range: %x - %x Count: %u\n", &_sbss, &_ebss, (uint32_t)&_ebss - (uint32_t)&_sbss);
Serial.printf("Now fill rest of DTCM with known pattern(%x - %x\n", (&_ebss + 1), (&itcm_size - 10)); Serial.flush(); //
// Guess of where it is safe to fill memory... Maybe address of last variable we have defined - some slop...
for (uint32_t *pfill = (&_ebss + 32); pfill < (&itcm_size - 10); pfill++) {
*pfill = 0x01020304; // some random value
}
#endif
}
void EstimateStackUsage() {
#if defined(__IMXRT1062__) && defined(DEBUG_MEMORY)
uint32_t *pmem = (&_ebss + 32);
while (*pmem == 0x01020304) pmem++;
Serial.printf("Estimated max stack usage: %d\n", (uint32_t)&_estack - (uint32_t)pmem);
#endif
}
/#define SERIAL_tt Serial // Send debug_tt output here. Must have SERIAL_tt.begin( ## )
//#include "debug_tt.h"
// 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.
// Otherwise your choice, standard is asymmetrical
#define SYMMETRICAL_EYELID
// Enable ONE of these #includes -- HUGE graphics tables for various eyes:
#if( DISPLAY_SIZE == 240 )
#include "graphics/default_large.h" // 240x240
#else
//#include "graphics/defaultEye.h" // Standard human-ish hazel eye -OR-
//#include "graphics/dragonEye.h" // Slit pupil fiery dragon/demon eye -OR-
//#include "graphics/noScleraEye.h" // Large iris, no sclera -OR-
//#include "graphics/goatEye.h" // Horizontal pupil goat/Krampus eye -OR-
//#include "graphics/newtEye.h" // Eye of newt -OR-
//#include "graphics/terminatorEye.h" // Git to da choppah!
//#include "graphics/catEye.h" // Cartoonish cat (flat "2D" colors)
//#include "graphics/owlEye.h" // Minerva the owl (DISABLE TRACKING)
//#include "graphics/naugaEye.h" // Nauga googly eye (DISABLE TRACKING)
//#include "graphics/doeEye.h" // Cartoon deer eye (DISABLE TRACKING)
//#include "graphics/Nebula.h" //Dont work
//#include "graphics/MyEyeHuman1.h"
//#include "graphics/Human-HAL9000.h"
//#include "graphics/NebulaBlueGreen.h"
//#include "graphics/SpiralGalaxy.h"
//#include "graphics/ChameleonX_Eye.h" //no work
//#include "graphics/MyEye.h"
#endif
// Optional: enable this line for startup logo (screen test/orient):
// EYE LIST ----------------------------------------------------------------
// DISPLAY HARDWARE SETTINGS (screen type & connections) -------------------
//#define TFT_SPI SPI
//#define TFT_PERIPH PERIPH_SPI
// Enable ONE of these #includes to specify the display type being used
#include <GC9A01A_t3n.h>
#define SPI_FREQ 48000000 // TFT: use max SPI (clips to 12 MHz on M0)
// 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), and a screen
// rotation value (0-3) for that eye.
eyeInfo_t eyeInfo[] = {
//CS DC MOSI SCK RST WINK ROT INIT
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
{29,31, 21, 32, 28, -1, 0, 0 }, // RIGHT EYE display-select and wink pins, no rotation
{10, 9, 11, 13, 8, -1, 0, 0 }, // LEFT EYE display-select and wink pins, no rotation
#else
{0, 2, 26, 27, 3, -1, 0, 0 }, // RIGHT EYE display-select and wink pins, no rotation
{10, 9, 11, 13, 8, -1, 0, 0 }, // LEFT EYE display-select and wink pins, no rotation
#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 BLINK_PIN 1 // Pin for manual blink button (BOTH eyes)
//#define LIGHT_PIN A3 // Photocell or potentiometer (else auto iris)
//#define LIGHT_PIN_FLIP // If defined, reverse reading from dial/photocell
#define LIGHT_MIN 0 // Lower reading from sensor
#define LIGHT_MAX 1023 // Upper 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 120 // Iris size (0-1023) in brightest light
#endif
#if !defined(IRIS_MAX)
#define IRIS_MAX 900 // Iris size (0-1023) in darkest light
#endif
Unfortunately I am working on fixing the errors that pop up from it:
In file included from it.
C:\Users\mrsno\Documents\Arduino\uncannyEyes_GC9A01A\uncannyEyes_GC9A01A.ino:37:
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:587:13: error: 'SPI_Hardware_t' in 'class SPIClass' does not name a type
587 | SPIClass::SPI_Hardware_t *_spi_hardware;
| ^~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h: In member function 'void GC9A01A_t3n::setAddr(uint16_t, uint16_t, uint16_t, uint16_t)':
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:760:5: error: 'writecommand_cont' was not declared in this scope
760 | writecommand_cont(GC9A01A_CASET); // Column addr set
| ^~~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:761:5: error: 'writedata16_cont' was not declared in this scope
761 | writedata16_cont(x0); // XSTART
| ^~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h: In member function 'void GC9A01A_t3n::HLine(int16_t, int16_t, int16_t, uint16_t)':
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:1104:5: error: 'writecommand_cont' was not declared in this scope
1104 | writecommand_cont(GC9A01A_RAMWR);
| ^~~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:1106:7: error: 'writedata16_cont' was not declared in this scope
1106 | writedata16_cont(color);
| ^~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h: In member function 'void GC9A01A_t3n::VLine(int16_t, int16_t, int16_t, uint16_t)':
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:1133:5: error: 'writecommand_cont' was not declared in this scope
1133 | writecommand_cont(GC9A01A_RAMWR);
| ^~~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:1135:7: error: 'writedata16_cont' was not declared in this scope
1135 | writedata16_cont(color);
| ^~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h: In member function 'void GC9A01A_t3n::Pixel(int16_t, int16_t, uint16_t)':
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:1192:5: error: 'writecommand_cont' was not declared in this scope
1192 | writecommand_cont(GC9A01A_RAMWR);
| ^~~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:1193:5: error: 'writedata16_cont' was not declared in this scope
1193 | writedata16_cont(color);
| ^~~~~~~~~~~~~~~~
exit status 1
Compilation error: exit status 1
I'm assuming based off the errors that all the issues are in this code:
//***************************************************
// https://github.com/kurte/ILI9341_t3n
// http://forum.pjrc.com/threads/26305-Highly-optimized-ILI9341-(320x240-TFT-color-display)-library
//
// Warning this is Kurt's updated version which allows it to work on different SPI busses.
//
// On Teensy 3.x allows you to use on only one valid hardware CS pin which must
// be used for DC
//
// On Teensy 4.x including Micromod you are free to use any digital pin for
// CS and DC, but you might get a modest speed increase if hardware CS is
// used for the DC pin
//
/***************************************************
This is our library for the Adafruit ILI9341 Breakout and Shield
----> http://www.adafruit.com/products/1651
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
****************************************************/
// <SoftEgg>
// Additional graphics routines by Tim Trzepacz, SoftEgg LLC added December 2015
//(And then accidentally deleted and rewritten March 2016. Oops!)
// Gradient support
//----------------
// fillRectVGradient - fills area with vertical gradient
// fillRectHGradient - fills area with horizontal gradient
// fillScreenVGradient - fills screen with vertical gradient
// fillScreenHGradient - fills screen with horizontal gradient
// Additional Color Support
//------------------------
// color565toRGB - converts 565 format 16 bit color to
//RGB
// color565toRGB14 - converts 16 bit 565 format color to
//14 bit RGB (2 bits clear for math and sign)
// RGB14tocolor565 - converts 14 bit RGB back to 16 bit
//565 format color
// Low Memory Bitmap Support
//-------------------------
// writeRect8BPP - write 8 bit per pixel paletted bitmap
// writeRect4BPP - write 4 bit per pixel paletted bitmap
// writeRect2BPP - write 2 bit per pixel paletted bitmap
// writeRect1BPP - write 1 bit per pixel paletted bitmap
// String Pixel Length support
//---------------------------
// strPixelLen - gets pixel length of given ASCII
//string
// <\SoftEgg>
// Also some of this comes from the DMA version of the library...
/* ILI9341_t3DMA library code is placed under the MIT license
* Copyright (c) 2016 Frank Bösing
*
*/
#ifndef _GC9A01A_t3N_H_
#define _GC9A01A_t3N_H_
#define GC9A01A_USE_DMAMEM
// Allow us to enable or disable capabilities, particully Frame Buffer and
// Clipping for speed and size
#ifndef DISABLE_GC9A01A_FRAMEBUFFER
#if defined(__MK66FX1M0__) // T3.6
#define ENABLE_GC9A01A_FRAMEBUFFER
#define SCREEN_DMA_NUM_SETTINGS \
2 // see if making it a constant value makes difference...
#elif defined(__MK64FX512__) // T3.5
#define ENABLE_GC9A01A_FRAMEBUFFER
#define SCREEN_DMA_NUM_SETTINGS \
4 // see if making it a constant value makes difference...
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
#define ENABLE_GC9A01A_FRAMEBUFFER
#define SCREEN_DMA_NUM_SETTINGS \
2 // see if making it a constant value makes difference...
#endif
#endif
// Allow way to override using SPI
#ifdef __cplusplus
#include "Arduino.h"
#include <DMAChannel.h>
#include <SPI.h>
#endif
#include <stdint.h>
#include "ILI9341_fonts.h"
#define GC9A01A_TFTWIDTH 240
#define GC9A01A_TFTHEIGHT 240
// NOTE: ILI9341 registers defined (but commented out) are ones that
// *might* be compatible with the GC9A01A, but aren't documented in
// the device datasheet. A couple are defined (with ILI name) and NOT
// commented out because they appeared in the manufacturer example code
// as raw register addresses, no documentation in datasheet, they SEEM
// to do the same thing as their ILI equivalents but this is not 100%
// confirmed so they've not been assigned GC9A01A register defines.
#define GC9A01A_NOP 0x00 ///< No-op register
#define GC9A01A_SWRESET 0x01 ///< Software reset register
//#define GC9A01A 0x04 ///< Read display identification information
//#define GC9A01A 0x09 ///< Read Display Status
#define GC9A01A_SLPIN 0x10 ///< Enter Sleep Mode
#define GC9A01A_SLPOUT 0x11 ///< Sleep Out
#define GC9A01A_PTLON 0x12 ///< Partial Mode ON
#define GC9A01A_NORON 0x13 ///< Normal Display Mode ON
//#define ILI9341_RDMODE 0x0A ///< Read Display Power Mode
//#define ILI9341_RDMADCTL 0x0B ///< Read Display MADCTL
//#define ILI9341_RDPIXFMT 0x0C ///< Read Display Pixel Format
//#define ILI9341_RDIMGFMT 0x0D ///< Read Display Image Format
//#define ILI9341_RDSELFDIAG 0x0F ///< Read Display Self-Diagnostic Result
#define GC9A01A_INVOFF 0x20 ///< Display Inversion OFF
#define GC9A01A_INVON 0x21 ///< Display Inversion ON
//#define ILI9341_GAMMASET 0x26 ///< Gamma Set
#define GC9A01A_DISPOFF 0x28 ///< Display OFF
#define GC9A01A_DISPON 0x29 ///< Display ON
#define GC9A01A_CASET 0x2A ///< Column Address Set
#define GC9A01A_PASET 0x2B ///< Page Address Set
#define GC9A01A_RAMWR 0x2C ///< Memory Write
#define GC9A01A_RAMRD 0x2E ///< Memory Read
#define GC9A01A_PTLAR 0x30 ///< Partial Area
#define GC9A01A_VSCRDEF 0x33 ///< Vertical Scrolling Definition
#define GC9A01A_TEOFF 0x34 ///< Tearing effect line off
#define GC9A01A_TEON 0x35 ///< Tearing effect line on
#define GC9A01A_MADCTL 0x36 ///< Memory Access Control
#define GC9A01A_VSCRSADD 0x37 ///< Vertical Scrolling Start Address
#define GC9A01A_PIXFMT 0x3A ///< COLMOD: Pixel Format Set
// 0x3C = Write memory continue
// 0x44 = Set tear scanline
// 0x45 = Get scanline
// 0x51 = Write display brightness
// 0x53 = Write CTRL display
// 0xA7 = Vcore voltage control
// 0xB0 = RGB interface signal control
//#define ILI9341_FRMCTR1 0xB1 ///< Frame Rate Control (In Normal Mode/Full Colors)
//#define ILI9341_FRMCTR2 0xB2 ///< Frame Rate Control (In Idle Mode/8 colors)
//#define ILI9341_FRMCTR3 0xB3 ///< Frame Rate control (In Partial Mode/Full Colors)
//#define ILI9341_INVCTR 0xB4 ///< Display Inversion Control
// 0xB5 = Blanking porch control
#define GC9A01A_DFUNCTR 0xB6 ///< Display Function Control
// 0xBA = TE control
// 0xC1 = Power criterion control
#define GC9A01A_VREG1A 0xC3 ///< Vreg1a voltage control
#define GC9A01A_VREG1B 0xC4 ///< Vreg1b voltage control
//#define ILI9341_PWCTR1 0xC0 ///< Power Control 1
//#define ILI9341_PWCTR2 0xC1 ///< Power Control 2
//#define ILI9341_PWCTR3 0xC2 ///< Power Control 3
//#define ILI9341_PWCTR4 0xC3 ///< Power Control 4
//#define ILI9341_PWCTR5 0xC4 ///< Power Control 5
//#define ILI9341_VMCTR1 0xC5 ///< VCOM Control 1
//#define ILI9341_VMCTR2 0xC7 ///< VCOM Control 2
#define GC9A01A_VREG2A 0xC9 ///< Vreg2a voltage control
#define GC9A01A_RDID1 0xDA ///< Read ID 1
#define GC9A01A_RDID2 0xDB ///< Read ID 2
#define GC9A01A_RDID3 0xDC ///< Read ID 3
//#define ILI9341_RDID4 0xDD ///< Read ID 4
#define ILI9341_GMCTRP1 0xE0 ///< Positive Gamma Correction
#define ILI9341_GMCTRN1 0xE1 ///< Negative Gamma Correction
#define ILI9341_FRAMERATE 0xE8 ///< Frame rate control
// 0xE9 = SPI 2data control
// 0xEC = Charge pump frequent control
#define GC9A01A_INREGEN2 0xEF ///< Inter register enable 2
#define GC9A01A_GAMMA1 0xF0 ///< Set gamma 1
#define GC9A01A_GAMMA2 0xF1 ///< Set gamma 2
#define GC9A01A_GAMMA3 0xF2 ///< Set gamma 3
#define GC9A01A_GAMMA4 0xF3 ///< Set gamma 4
//#define ILI9341_PWCTR6 0xFC
// 0xF6 = Interface control
#define GC9A01A_INREGEN1 0xFE ///< Inter register enable 1
/*
#define ILI9341_PWCTR6 0xFC
*/
// Color definitions
#define BLACK 0x0000 /* 0, 0, 0 */
#define NAVY 0x000F /* 0, 0, 128 */
#define DARKGREEN 0x03E0 /* 0, 128, 0 */
#define DARKCYAN 0x03EF /* 0, 128, 128 */
#define MAROON 0x7800 /* 128, 0, 0 */
#define PURPLE 0x780F /* 128, 0, 128 */
#define OLIVE 0x7BE0 /* 128, 128, 0 */
#define LIGHTGREY 0xC618 /* 192, 192, 192 */
#define DARKGREY 0x7BEF /* 128, 128, 128 */
#define BLUE 0x001F /* 0, 0, 255 */
#define GREEN 0x07E0 /* 0, 255, 0 */
#define CYAN 0x07FF /* 0, 255, 255 */
#define RED 0xF800 /* 255, 0, 0 */
#define MAGENTA 0xF81F /* 255, 0, 255 */
#define YELLOW 0xFFE0 /* 255, 255, 0 */
#define WHITE 0xFFFF /* 255, 255, 255 */
#define ORANGE 0xFD20 /* 255, 165, 0 */
#define GREENYELLOW 0xAFE5 /* 173, 255, 47 */
#define PINK 0xF81F
#ifndef CL
#define CL(_r,_g,_b) ((((_r)&0xF8)<<8)|(((_g)&0xFC)<<3)|((_b)>>3))
#endif
#define sint16_t int16_t
// Lets see about supporting Adafruit fonts as well?
#if __has_include(<gfxfont.h>)
#include <gfxfont.h>
#endif
#ifndef _GFXFONT_H_
#define _GFXFONT_H_
/// Font data stored PER GLYPH
typedef struct {
uint16_t bitmapOffset; ///< Pointer into GFXfont->bitmap
uint8_t width; ///< Bitmap dimensions in pixels
uint8_t height; ///< Bitmap dimensions in pixels
uint8_t xAdvance; ///< Distance to advance cursor (x axis)
int8_t xOffset; ///< X dist from cursor pos to UL corner
int8_t yOffset; ///< Y dist from cursor pos to UL corner
} GFXglyph;
/// Data stored for FONT AS A WHOLE
typedef struct {
uint8_t *bitmap; ///< Glyph bitmaps, concatenated
GFXglyph *glyph; ///< Glyph array
uint8_t first; ///< ASCII extents (first char)
uint8_t last; ///< ASCII extents (last char)
uint8_t yAdvance; ///< Newline distance (y axis)
} GFXfont;
#endif // _GFXFONT_H_
// These enumerate the text plotting alignment (reference datum point)
#define TL_DATUM 0 // Top left (default)
#define TC_DATUM 1 // Top centre
#define TR_DATUM 2 // Top right
#define ML_DATUM 3 // Middle left
#define CL_DATUM 3 // Centre left, same as above
#define MC_DATUM 4 // Middle centre
#define CC_DATUM 4 // Centre centre, same as above
#define MR_DATUM 5 // Middle right
#define CR_DATUM 5 // Centre right, same as above
#define BL_DATUM 6 // Bottom left
#define BC_DATUM 7 // Bottom centre
#define BR_DATUM 8 // Bottom right
//#define L_BASELINE 9 // Left character baseline (Line the 'A' character would
//sit on)
//#define C_BASELINE 10 // Centre character baseline
//#define R_BASELINE 11 // Right character baseline
#ifdef __cplusplus
// At all other speeds, _pspi->beginTransaction() will use the fastest available
// clock
#ifdef KINETISK
#define GC9A01A_SPICLOCK 30000000
#define GC9A01A_SPICLOCK_READ 2000000
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
#define GC9A01A_SPICLOCK 30000000u
#define GC9A01A_SPICLOCK_READ 2000000
#else
#define GC9A01A_SPICLOCK 30000000
#define GC9A01A_SPICLOCK_READ 2000000
#endif
#if defined(__IMXRT1062__) // Teensy 4.x
// Also define these in lower memory so as to make sure they are not cached...
typedef struct {
DMASetting _dmasettings[2];
DMAChannel _dmatx;
} GC9A01A_DMA_Data_t;
#endif
class GC9A01A_t3n : public Print {
public:
GC9A01A_t3n(uint8_t _CS, uint8_t _DC, uint8_t _RST = 255, uint8_t _MOSI = 11,
uint8_t _SCLK = 13);
void begin(uint32_t spi_clock = GC9A01A_SPICLOCK,
uint32_t spi_clock_read = GC9A01A_SPICLOCK_READ);
void sleep(bool enable);
void pushColor(uint16_t color);
void fillScreen(uint16_t color);
inline void fillWindow(uint16_t color) { fillScreen(color); }
void drawPixel(int16_t x, int16_t y, uint16_t color);
void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
void fillRectHGradient(int16_t x, int16_t y, int16_t w, int16_t h,
uint16_t color1, uint16_t color2);
void fillRectVGradient(int16_t x, int16_t y, int16_t w, int16_t h,
uint16_t color1, uint16_t color2);
void fillScreenVGradient(uint16_t color1, uint16_t color2);
void fillScreenHGradient(uint16_t color1, uint16_t color2);
void setRotation(uint8_t r);
void setScrollMargins(uint16_t top, uint16_t bottom);
void setScroll(uint16_t offset);
void invertDisplay(boolean i);
void setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
// Pass 8-bit (each) R,G,B, get back 16-bit packed color
static uint16_t color565(uint8_t r, uint8_t g, uint8_t b) {
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
// color565toRGB - converts 565 format 16 bit color to RGB
static void color565toRGB(uint16_t color, uint8_t &r, uint8_t &g,
uint8_t &b) {
r = (color >> 8) & 0x00F8;
g = (color >> 3) & 0x00FC;
b = (color << 3) & 0x00F8;
}
// color565toRGB14 - converts 16 bit 565 format color to 14 bit RGB (2
// bits clear for math and sign)
// returns 00rrrrr000000000,00gggggg00000000,00bbbbb000000000
// thus not overloading sign, and allowing up to double for additions for
// fixed point delta
static void color565toRGB14(uint16_t color, int16_t &r, int16_t &g,
int16_t &b) {
r = (color >> 2) & 0x3E00;
g = (color << 3) & 0x3F00;
b = (color << 9) & 0x3E00;
}
// RGB14tocolor565 - converts 14 bit RGB back to 16 bit 565 format
// color
static uint16_t RGB14tocolor565(int16_t r, int16_t g, int16_t b) {
return (((r & 0x3E00) << 2) | ((g & 0x3F00) >> 3) | ((b & 0x3E00) >> 9));
}
// Device does not have MISO line so query is not valid
// uint8_t readdata(void);
//uint8_t readcommand8(uint8_t r//eg, uint8_t index = 0);
//uint16_t readScanLine();
void setFrameRateControl(uint8_t mode);
// Added functions to read pixel data...
// These will only work if there is a frame buffer
// as no MISO pin on display
uint16_t readPixel(int16_t x, int16_t y);
void readRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t *pcolors);
void writeRect(int16_t x, int16_t y, int16_t w, int16_t h,
const uint16_t *pcolors);
void writeSubImageRect(int16_t x, int16_t y, int16_t w, int16_t h,
int16_t image_offset_x, int16_t image_offset_y, int16_t image_width, int16_t image_height,
const uint16_t *pcolors);
void writeSubImageRectBytesReversed(int16_t x, int16_t y, int16_t w, int16_t h,
int16_t image_offset_x, int16_t image_offset_y, int16_t image_width, int16_t image_height,
const uint16_t *pcolors);
// writeRect8BPP - write 8 bit per pixel paletted bitmap
// bitmap data in array at pixels, one byte per
//pixel
// color palette data in array at palette
void writeRect8BPP(int16_t x, int16_t y, int16_t w, int16_t h,
const uint8_t *pixels, const uint16_t *palette);
// writeRect4BPP - write 4 bit per pixel paletted bitmap
// bitmap data in array at pixels, 4 bits per
//pixel
// color palette data in array at palette
// width must be at least 2 pixels
void writeRect4BPP(int16_t x, int16_t y, int16_t w, int16_t h,
const uint8_t *pixels, const uint16_t *palette);
// writeRect2BPP - write 2 bit per pixel paletted bitmap
// bitmap data in array at pixels, 4 bits per
//pixel
// color palette data in array at palette
// width must be at least 4 pixels
void writeRect2BPP(int16_t x, int16_t y, int16_t w, int16_t h,
const uint8_t *pixels, const uint16_t *palette);
// writeRect1BPP - write 1 bit per pixel paletted bitmap
// bitmap data in array at pixels, 4 bits per
//pixel
// color palette data in array at palette
// width must be at least 8 pixels
void writeRect1BPP(int16_t x, int16_t y, int16_t w, int16_t h,
const uint8_t *pixels, const uint16_t *palette);
// writeRectNBPP - write N(1, 2, 4, 8) bit per pixel paletted bitmap
// bitmap data in array at pixels
// Currently writeRect1BPP, writeRect2BPP, writeRect4BPP use this to do all
// of the work.
//
void writeRectNBPP(int16_t x, int16_t y, int16_t w, int16_t h,
uint8_t bits_per_pixel, const uint8_t *pixels,
const uint16_t *palette);
// from Adafruit_GFX.h
void drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color);
void drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername,
uint16_t color);
void fillCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color);
void fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername,
int16_t delta, uint16_t color);
void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2,
int16_t y2, uint16_t color);
void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2,
int16_t y2, uint16_t color);
void drawRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h,
int16_t radius, uint16_t color);
void fillRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h,
int16_t radius, uint16_t color);
void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w,
int16_t h, uint16_t color);
void drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color,
uint16_t bg, uint8_t size_x, uint8_t size_y);
void inline drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color,
uint16_t bg, uint8_t size) {
drawChar(x, y, c, color, bg, size);
}
#ifndef CENTER
static const int16_t CENTER = 9998;
#endif
void setCursor(int16_t x, int16_t y, bool autoCenter = false);
void getCursor(int16_t *x, int16_t *y);
void setTextColor(uint16_t c);
void setTextColor(uint16_t c, uint16_t bg);
void setTextSize(uint8_t sx, uint8_t sy);
void inline setTextSize(uint8_t s) { setTextSize(s, s); }
uint8_t getTextSizeX();
uint8_t getTextSizeY();
uint8_t getTextSize();
void setTextWrap(boolean w);
boolean getTextWrap();
// setOrigin sets an offset in display pixels where drawing to (0,0) will
// appear
// for example: setOrigin(10,10); drawPixel(5,5); will cause a pixel to be
// drawn at hardware pixel (15,15)
void setOrigin(int16_t x = 0, int16_t y = 0) {
_originx = x;
_originy = y;
// if (Serial) Serial.printf("Set Origin %d %d\n", x, y);
updateDisplayClip();
}
void getOrigin(int16_t *x, int16_t *y) {
*x = _originx;
*y = _originy;
}
// setClipRect() sets a clipping rectangle (relative to any set origin) for
// drawing to be limited to.
// Drawing is also restricted to the bounds of the display
void setClipRect(int16_t x1, int16_t y1, int16_t w, int16_t h) {
_clipx1 = x1;
_clipy1 = y1;
_clipx2 = x1 + w;
_clipy2 = y1 + h;
// if (Serial) Serial.printf("Set clip Rect %d %d %d %d\n", x1, y1, w, h);
updateDisplayClip();
}
void setClipRect() {
_clipx1 = 0;
_clipy1 = 0;
_clipx2 = _width;
_clipy2 = _height;
// if (Serial) Serial.printf("clear clip Rect\n");
updateDisplayClip();
}
// overwrite print functions:
virtual size_t write(uint8_t);
virtual size_t write(const uint8_t *buffer, size_t size);
int16_t width(void) { return _width; }
int16_t height(void) { return _height; }
uint8_t getRotation(void);
void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color);
void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
int16_t getCursorX(void) const { return cursor_x; }
int16_t getCursorY(void) const { return cursor_y; }
void setFont(const ILI9341_t3_font_t &f);
void setFont(const GFXfont *f = NULL);
void setFontAdafruit(void) { setFont(); }
void drawFontChar(unsigned int c);
void drawGFXFontChar(unsigned int c);
void getTextBounds(const uint8_t *buffer, uint16_t len, int16_t x, int16_t y,
int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h);
void getTextBounds(const char *string, int16_t x, int16_t y, int16_t *x1,
int16_t *y1, uint16_t *w, uint16_t *h);
void getTextBounds(const String &str, int16_t x, int16_t y, int16_t *x1,
int16_t *y1, uint16_t *w, uint16_t *h);
int16_t strPixelLen(const char *str, uint16_t cb=0xffff); // optional number of characters...
// added support for drawing strings/numbers/floats with centering
// modified from tft_ili9341_ESP github library
// Handle numbers
int16_t drawNumber(long long_num, int poX, int poY);
int16_t drawFloat(float floatNumber, int decimal, int poX, int poY);
// Handle char arrays
int16_t drawString(const String &string, int poX, int poY);
int16_t drawString(const char string[], int16_t len, int poX, int poY);
void setTextDatum(uint8_t datum);
// added support for scrolling text area
// https://github.com/vitormhenrique/ILI9341_t3
// Discussion regarding this optimized version:
// http://forum.pjrc.com/threads/26305-Highly-optimized-ILI9341-%28320x240-TFT-color-display%29-library
//
void setScrollTextArea(int16_t x, int16_t y, int16_t w, int16_t h);
void setScrollBackgroundColor(uint16_t color);
void enableScroll(void);
void disableScroll(void);
void scrollTextArea(uint8_t scrollSize);
void resetScrollBackgroundColor(uint16_t color);
// added support to use optional Frame buffer
enum {
GC9A01A_DMA_INIT = 0x01,
GC9A01A_DMA_EVER_INIT = 0x08,
GC9A01A_DMA_CONT = 0x02,
GC9A01A_DMA_FINISH = 0x04,
GC9A01A_DMA_ACTIVE = 0x80
};
void setFrameBuffer(uint16_t *frame_buffer);
uint8_t
useFrameBuffer(boolean b); // use the frame buffer? First call will allocate
void freeFrameBuffer(void); // explicit call to release the buffer
void updateScreen(void); // call to say update the screen now.
bool updateScreenAsync(bool update_cont = false); // call to say update the
// screen optinoally turn
// into continuous mode.
void waitUpdateAsyncComplete(void);
void endUpdateAsync(); // Turn of the continueous mode fla
void dumpDMASettings();
#ifdef ENABLE_GC9A01A_FRAMEBUFFER
uint16_t *getFrameBuffer() { return _pfbtft; }
uint32_t frameCount() { return _dma_frame_count; }
uint16_t subFrameCount() { return _dma_sub_frame_count; }
boolean asyncUpdateActive(void) { return (_dma_state & GC9A01A_DMA_ACTIVE); }
void initDMASettings(void);
void setFrameCompleteCB(void (*pcb)(), bool fCallAlsoHalfDone = false);
#else
uint32_t frameCount() { return 0; }
uint16_t subFrameCount() { return 0; }
uint16_t *getFrameBuffer() { return NULL; }
boolean asyncUpdateActive(void) { return false; }
#endif
void updateChangedAreasOnly(bool updateChangedOnly) {
#ifdef ENABLE_GC9A01A_FRAMEBUFFER
_updateChangedAreasOnly = updateChangedOnly;
#endif
}
// Setup to send a command with parameters like ST77xx code
void sendCommand(uint8_t commandByte, const uint8_t *dataBytes, uint8_t numDataBytes);
protected:
SPIClass *_pspi = nullptr;
SPIClass::SPI_Hardware_t *_spi_hardware;
uint8_t _spi_num; // Which buss is this spi on?
uint32_t _SPI_CLOCK; // #define ILI9341_SPICLOCK 30000000
uint32_t _SPI_CLOCK_READ; //#define ILI9341_SPICLOCK_READ 2000000
#if defined(KINETISK)
KINETISK_SPI_t *_pkinetisk_spi;
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
IMXRT_LPSPI_t *_pimxrt_spi;
#elif defined(KINETISL)
KINETISL_SPI_t *_pkinetisl_spi;
#endif
int16_t _width, _height; // Display w/h as modified by current rotation
int16_t cursor_x, cursor_y;
bool _center_x_text = false;
bool _center_y_text = false;
int16_t _clipx1, _clipy1, _clipx2, _clipy2;
int16_t _originx, _originy;
int16_t _displayclipx1, _displayclipy1, _displayclipx2, _displayclipy2;
bool _invisible = false;
bool _standard = true; // no bounding rectangle or origin set.
uint16_t _x0_last = 0xffff;
uint16_t _x1_last = 0xffff;
uint16_t _y0_last = 0xffff;
uint16_t _y1_last = 0xffff;
inline void updateDisplayClip() {
_displayclipx1 = max(0, min(_clipx1 + _originx, width()));
_displayclipx2 = max(0, min(_clipx2 + _originx, width()));
_displayclipy1 = max(0, min(_clipy1 + _originy, height()));
_displayclipy2 = max(0, min(_clipy2 + _originy, height()));
_invisible =
(_displayclipx1 == _displayclipx2 || _displayclipy1 == _displayclipy2);
_standard = (_displayclipx1 == 0) && (_displayclipx2 == _width) &&
(_displayclipy1 == 0) && (_displayclipy2 == _height);
if (Serial) {
// Serial.printf("UDC (%d %d)-(%d %d) %d %d\n", _displayclipx1,
// _displayclipy1, _displayclipx2,
// _displayclipy2, _invisible, _standard);
}
}
int16_t scroll_x, scroll_y, scroll_width, scroll_height;
boolean scrollEnable,
isWritingScrollArea; // If set, 'wrap' text at right edge of display
uint16_t textcolor, textbgcolor, scrollbgcolor;
uint32_t textcolorPrexpanded, textbgcolorPrexpanded;
uint8_t textsize_x, textsize_y, rotation, textdatum;
boolean wrap; // If set, 'wrap' text at right edge of display
const ILI9341_t3_font_t *font;
// Anti-aliased font support
uint8_t fontbpp = 1;
uint8_t fontbppindex = 0;
uint8_t fontbppmask = 1;
uint8_t fontppb = 8;
uint8_t *fontalphalut;
float fontalphamx = 1;
uint32_t padX;
// GFX Font support
const GFXfont *gfxFont = nullptr;
int8_t _gfxFont_min_yOffset = 0;
// Opaque font chracter overlap?
unsigned int _gfx_c_last;
int16_t _gfx_last_cursor_x, _gfx_last_cursor_y;
int16_t _gfx_last_char_x_write = 0;
uint16_t _gfx_last_char_textcolor;
uint16_t _gfx_last_char_textbgcolor;
bool gfxFontLastCharPosFG(int16_t x, int16_t y);
uint8_t _rst;
uint8_t _cs, _dc;
uint8_t pcs_data, pcs_command;
uint8_t _mosi, _sclk;
///////////////////////////////
// BUGBUG:: reorganize this area better!
#if defined(KINETISK)
// inline uint8_t sizeFIFO() {return _fifo_size; }
uint32_t _fifo_full_test;
void waitFifoNotFull(void);
void waitFifoEmpty(void);
void waitTransmitComplete(void);
void waitTransmitComplete(uint32_t mcr);
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
uint8_t pending_rx_count = 0; // hack ...
void waitFifoNotFull(void);
void waitFifoEmpty(void);
void waitTransmitComplete(void);
uint16_t waitTransmitCompleteReturnLast();
void waitTransmitComplete(uint32_t mcr);
#elif defined(KINETISL)
#endif
//////////////////////////////
// add support to allow only one hardware CS (used for dc)
#if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
uint32_t _cspinmask;
volatile uint32_t *_csport;
uint32_t _spi_tcr_current;
uint32_t _dcpinmask;
uint32_t _tcr_dc_assert;
uint32_t _tcr_dc_not_assert;
volatile uint32_t *_dcport;
#else
uint8_t _cspinmask;
volatile uint8_t *_csport;
#endif
#ifdef KINETISL
volatile uint8_t *_dcport;
uint8_t _dcpinmask;
#endif
#ifdef ENABLE_GC9A01A_FRAMEBUFFER
// Add support for optional frame buffer
uint16_t *_pfbtft; // Optional Frame buffer
uint8_t _use_fbtft; // Are we in frame buffer mode?
uint16_t *_we_allocated_buffer; // We allocated the buffer;
int16_t _changed_min_x, _changed_max_x, _changed_min_y, _changed_max_y;
bool _updateChangedAreasOnly = false; // current default off,
void (*_frame_complete_callback)() = nullptr;
bool _frame_callback_on_HalfDone = false;
// Add DMA support.
static GC9A01A_t3n *_dmaActiveDisplay[3]; // Use pointer to this as a way to
volatile uint8_t _dma_state = 0; // DMA status
volatile uint32_t _dma_frame_count = 0; // Can return a frame count...
volatile uint16_t _dma_sub_frame_count = 0; // Can return a frame count...
#if defined(__MK66FX1M0__)
// T3.6 use Scatter/gather with chain to do transfer
static DMASetting _dmasettings[3][3];
DMAChannel _dmatx;
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
// try work around DMA memory cached. So have a couple of buffers we copy
// frame buffer into
// as to move it out of the memory that is cached...
static const uint32_t _count_pixels = GC9A01A_TFTWIDTH * GC9A01A_TFTHEIGHT;
static GC9A01A_DMA_Data_t _dma_data[3]; // one structure for each possible SPI buss
//DMASetting _dmasettings[3];
//DMAChannel _dmatx;
volatile uint32_t _dma_pixel_index = 0;
uint16_t _dma_buffer_size; // the actual size we are using <= DMA_BUFFER_SIZE;
uint16_t _dma_cnt_sub_frames_per_frame;
uint32_t _spi_fcr_save; // save away previous FCR register value
#elif defined(__MK64FX512__)
// T3.5 - had issues scatter/gather so do just use channels/interrupts
// and update and continue
static DMAChannel _dmatx;
static DMAChannel _dmarx;
static uint16_t _dma_count_remaining;
static uint16_t _dma_write_size_words;
#endif
static void dmaInterrupt(void);
static void dmaInterrupt1(void);
static void dmaInterrupt2(void);
void process_dma_interrupt(void);
#endif
void charBounds(char c, int16_t *x, int16_t *y, int16_t *minx, int16_t *miny,
int16_t *maxx, int16_t *maxy);
void setAddr(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
__attribute__((always_inline)) {
#if 1
writecommand_cont(GC9A01A_CASET); // Column addr set
writedata16_cont(x0); // XSTART
writedata16_cont(x1); // XEND
writecommand_cont(GC9A01A_PASET); // Row addr set
writedata16_cont(y0); // YSTART
writedata16_cont(y1); // YEND
#else
if ((x0 != _x0_last) || (x1 != _x1_last)) {
writecommand_cont(GC9A01A_CASET); // Column addr set
writedata16_cont(x0); // XSTART
writedata16_cont(x1); // XEND
_x0_last = x0;
_x1_last = x1;
}
if ((y0 != _y0_last) || (y1 != _y1_last)) {
writecommand_cont(GC9A01A_PASET); // Row addr set
writedata16_cont(y0); // YSTART
writedata16_cont(y1); // YEND
_y0_last = y0;
_y1_last = y1;
}
#endif
}
//. From Onewire utility files
#if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
void DIRECT_WRITE_LOW(volatile uint32_t *base, uint32_t mask)
__attribute__((always_inline)) {
*(base + 34) = mask;
}
void DIRECT_WRITE_HIGH(volatile uint32_t *base, uint32_t mask)
__attribute__((always_inline)) {
*(base + 33) = mask;
}
#endif
void beginSPITransaction(uint32_t clock) __attribute__((always_inline)) {
_pspi->beginTransaction(SPISettings(clock, MSBFIRST, SPI_MODE0));
#if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
if (!_dcport)
_spi_tcr_current = _pimxrt_spi->TCR; // Only if DC is on hardware CS
#endif
if (_csport) {
#if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
DIRECT_WRITE_LOW(_csport, _cspinmask);
#else
*_csport &= ~_cspinmask;
#endif
}
}
void endSPITransaction() __attribute__((always_inline)) {
if (_csport) {
#if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
DIRECT_WRITE_HIGH(_csport, _cspinmask);
#else
*_csport |= _cspinmask;
#endif
}
_pspi->endTransaction();
}
#if defined(KINETISK)
void writecommand_cont(uint8_t c) __attribute__((always_inline)) {
_pkinetisk_spi->PUSHR =
c | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT;
waitFifoNotFull();
}
void writedata8_cont(uint8_t c) __attribute__((always_inline)) {
_pkinetisk_spi->PUSHR =
c | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT;
waitFifoNotFull();
}
void writedata16_cont(uint16_t d) __attribute__((always_inline)) {
_pkinetisk_spi->PUSHR =
d | (pcs_data << 16) | SPI_PUSHR_CTAS(1) | SPI_PUSHR_CONT;
waitFifoNotFull();
}
void writecommand_last(uint8_t c) __attribute__((always_inline)) {
uint32_t mcr = _pkinetisk_spi->MCR;
_pkinetisk_spi->PUSHR =
c | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ;
waitTransmitComplete(mcr);
}
void writedata8_last(uint8_t c) __attribute__((always_inline)) {
uint32_t mcr = _pkinetisk_spi->MCR;
_pkinetisk_spi->PUSHR =
c | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ;
waitTransmitComplete(mcr);
}
void writedata16_last(uint16_t d) __attribute__((always_inline)) {
uint32_t mcr = _pkinetisk_spi->MCR;
_pkinetisk_spi->PUSHR =
d | (pcs_data << 16) | SPI_PUSHR_CTAS(1) | SPI_PUSHR_EOQ;
waitTransmitComplete(mcr);
}
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
#ifndef TCR_MASK
#define TCR_MASK (LPSPI_TCR_PCS(3) | LPSPI_TCR_FRAMESZ(31) | LPSPI_TCR_CONT | LPSPI_TCR_RXMSK )
#endif
void maybeUpdateTCR(
uint32_t requested_tcr_state) /*__attribute__((always_inline)) */ {
if ((_spi_tcr_current & TCR_MASK) != requested_tcr_state) {
bool dc_state_change = (_spi_tcr_current & LPSPI_TCR_PCS(3)) !=
(requested_tcr_state & LPSPI_TCR_PCS(3));
_spi_tcr_current = (_spi_tcr_current & ~TCR_MASK) | requested_tcr_state;
// only output when Transfer queue is empty.
if (!dc_state_change || !_dcpinmask) {
while ((_pimxrt_spi->FSR & 0x1f))
;
_pimxrt_spi->TCR = _spi_tcr_current; // update the TCR
} else {
waitTransmitComplete();
if (requested_tcr_state & LPSPI_TCR_PCS(3))
DIRECT_WRITE_HIGH(_dcport, _dcpinmask);
else
DIRECT_WRITE_LOW(_dcport, _dcpinmask);
_pimxrt_spi->TCR = _spi_tcr_current &
~(LPSPI_TCR_PCS(3) |
LPSPI_TCR_CONT); // go ahead and update TCR anyway?
}
}
}
// BUGBUG:: currently assumming we only have CS_0 as valid CS
void writecommand_cont(uint8_t c) __attribute__((always_inline)) {
maybeUpdateTCR(_tcr_dc_assert | LPSPI_TCR_FRAMESZ(7) /*| LPSPI_TCR_CONT*/);
_pimxrt_spi->TDR = c;
pending_rx_count++; //
waitFifoNotFull();
}
void writedata8_cont(uint8_t c) __attribute__((always_inline)) {
maybeUpdateTCR(_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(7) | LPSPI_TCR_CONT);
_pimxrt_spi->TDR = c;
pending_rx_count++; //
waitFifoNotFull();
}
void writedata16_cont(uint16_t d) __attribute__((always_inline)) {
maybeUpdateTCR(_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(15) | LPSPI_TCR_CONT);
_pimxrt_spi->TDR = d;
pending_rx_count++; //
waitFifoNotFull();
}
void writecommand_last(uint8_t c) __attribute__((always_inline)) {
maybeUpdateTCR(_tcr_dc_assert | LPSPI_TCR_FRAMESZ(7));
_pimxrt_spi->TDR = c;
// _pimxrt_spi->SR = LPSPI_SR_WCF | LPSPI_SR_FCF | LPSPI_SR_TCF;
pending_rx_count++; //
waitTransmitComplete();
}
void writedata8_last(uint8_t c) __attribute__((always_inline)) {
maybeUpdateTCR(_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(7));
_pimxrt_spi->TDR = c;
// _pimxrt_spi->SR = LPSPI_SR_WCF | LPSPI_SR_FCF | LPSPI_SR_TCF;
pending_rx_count++; //
waitTransmitComplete();
}
void writedata16_last(uint16_t d) __attribute__((always_inline)) {
maybeUpdateTCR(_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(15));
_pimxrt_spi->TDR = d;
// _pimxrt_spi->SR = LPSPI_SR_WCF | LPSPI_SR_FCF | LPSPI_SR_TCF;
pending_rx_count++; //
waitTransmitComplete();
}
#elif defined(KINETISL)
// Lets see how hard to make it work OK with T-LC
uint8_t _dcpinAsserted;
uint8_t _data_sent_not_completed;
void waitTransmitComplete() {
while (_data_sent_not_completed) {
uint16_t timeout_count = 0xff; // hopefully enough
while (!(_pkinetisl_spi->S & SPI_S_SPRF) && timeout_count--)
; // wait
uint8_t d __attribute__((unused));
d = _pkinetisl_spi->DL;
d = _pkinetisl_spi->DH;
_data_sent_not_completed--; // We hopefully received our data...
}
}
uint16_t waitTransmitCompleteReturnLast() {
uint16_t d = 0;
while (_data_sent_not_completed) {
uint16_t timeout_count = 0xff; // hopefully enough
while (!(_pkinetisl_spi->S & SPI_S_SPRF) && timeout_count--)
; // wait
d = (_pkinetisl_spi->DH << 8) | _pkinetisl_spi->DL;
_data_sent_not_completed--; // We hopefully received our data...
}
return d;
}
void setCommandMode() __attribute__((always_inline)) {
if (!_dcpinAsserted) {
waitTransmitComplete();
*_dcport &= ~_dcpinmask;
_dcpinAsserted = 1;
}
}
void setDataMode() __attribute__((always_inline)) {
if (_dcpinAsserted) {
waitTransmitComplete();
*_dcport |= _dcpinmask;
_dcpinAsserted = 0;
}
}
void outputToSPI(uint8_t c) {
if (_pkinetisl_spi->C2 & SPI_C2_SPIMODE) {
// Wait to change modes until any pending output has been done.
waitTransmitComplete();
_pkinetisl_spi->C2 = 0; // make sure 8 bit mode.
}
while (!(_pkinetisl_spi->S & SPI_S_SPTEF))
; // wait if output buffer busy.
// Clear out buffer if there is something there...
if ((_pkinetisl_spi->S & SPI_S_SPRF)) {
uint8_t d __attribute__((unused));
d = _pkinetisl_spi->DL;
_data_sent_not_completed--;
}
_pkinetisl_spi->DL = c; // output byte
_data_sent_not_completed++; // let system know we sent something
}
void outputToSPI16(uint16_t data) {
if (!(_pkinetisl_spi->C2 & SPI_C2_SPIMODE)) {
// Wait to change modes until any pending output has been done.
waitTransmitComplete();
_pkinetisl_spi->C2 = SPI_C2_SPIMODE; // make sure 8 bit mode.
}
uint8_t s;
do {
s = _pkinetisl_spi->S;
// wait if output buffer busy.
// Clear out buffer if there is something there...
if ((s & SPI_S_SPRF)) {
uint8_t d __attribute__((unused));
d = _pkinetisl_spi->DL;
d = _pkinetisl_spi->DH;
_data_sent_not_completed--; // let system know we sent something
}
} while (!(s & SPI_S_SPTEF) || (s & SPI_S_SPRF));
_pkinetisl_spi->DL = data; // output low byte
_pkinetisl_spi->DH = data >> 8; // output high byte
_data_sent_not_completed++; // let system know we sent something
}
void writecommand_cont(uint8_t c) {
setCommandMode();
outputToSPI(c);
}
void writedata8_cont(uint8_t c) {
setDataMode();
outputToSPI(c);
}
void writedata16_cont(uint16_t c) {
setDataMode();
outputToSPI16(c);
}
void writecommand_last(uint8_t c) {
setCommandMode();
outputToSPI(c);
waitTransmitComplete();
}
void writedata8_last(uint8_t c) {
setDataMode();
outputToSPI(c);
waitTransmitComplete();
}
void writedata16_last(uint16_t c) {
setDataMode();
outputToSPI16(c);
waitTransmitComplete();
_pkinetisl_spi->C2 = 0; // Set back to 8 bit mode...
_pkinetisl_spi->S; // Read in the status;
}
#endif
#ifdef ENABLE_GC9A01A_FRAMEBUFFER
void clearChangedRange() {
_changed_min_x = 0x7fff;
_changed_max_x = -1;
_changed_min_y = 0x7fff;
_changed_max_y = -1;
}
void updateChangedRange(int16_t x, int16_t y, int16_t w, int16_t h)
__attribute__((always_inline)) {
if (x < _changed_min_x)
_changed_min_x = x;
if (y < _changed_min_y)
_changed_min_y = y;
x += w - 1;
y += h - 1;
if (x > _changed_max_x)
_changed_max_x = x;
if (y > _changed_max_y)
_changed_max_y = y;
}
// could combine with above, but avoids the +-...
void updateChangedRange(int16_t x, int16_t y) __attribute__((always_inline)) {
if (x < _changed_min_x)
_changed_min_x = x;
if (y < _changed_min_y)
_changed_min_y = y;
if (x > _changed_max_x)
_changed_max_x = x;
if (y > _changed_max_y)
_changed_max_y = y;
}
#endif
void HLine(int16_t x, int16_t y, int16_t w, uint16_t color)
__attribute__((always_inline)) {
#ifdef ENABLE_GC9A01A_FRAMEBUFFER
if (_use_fbtft) {
drawFastHLine(x, y, w, color);
return;
}
#endif
x += _originx;
y += _originy;
// Rectangular clipping
if ((y < _displayclipy1) || (x >= _displayclipx2) || (y >= _displayclipy2))
return;
if (x < _displayclipx1) {
w = w - (_displayclipx1 - x);
x = _displayclipx1;
}
if ((x + w - 1) >= _displayclipx2)
w = _displayclipx2 - x;
if (w < 1)
return;
setAddr(x, y, x + w - 1, y);
writecommand_cont(GC9A01A_RAMWR);
do {
writedata16_cont(color);
} while (--w > 0);
}
void VLine(int16_t x, int16_t y, int16_t h, uint16_t color)
__attribute__((always_inline)) {
#ifdef ENABLE_GC9A01A_FRAMEBUFFER
if (_use_fbtft) {
drawFastVLine(x, y, h, color);
return;
}
#endif
x += _originx;
y += _originy;
// Rectangular clipping
if ((x < _displayclipx1) || (x >= _displayclipx2) || (y >= _displayclipy2))
return;
if (y < _displayclipy1) {
h = h - (_displayclipy1 - y);
y = _displayclipy1;
}
if ((y + h - 1) >= _displayclipy2)
h = _displayclipy2 - y;
if (h < 1)
return;
setAddr(x, y, x, y + h - 1);
writecommand_cont(GC9A01A_RAMWR);
do {
writedata16_cont(color);
} while (--h > 0);
}
/**
* Found in a pull request for the Adafruit framebuffer library. Clever!
* https://github.com/tricorderproject/arducordermini/pull/1/files#diff-d22a481ade4dbb4e41acc4d7c77f683d
* Converts 0000000000000000rrrrrggggggbbbbb
* into 00000gggggg00000rrrrr000000bbbbb
* with mask 00000111111000001111100000011111
* This is useful because it makes space for a parallel fixed-point multiply
* This implements the linear interpolation formula: result = bg * (1.0 -
*alpha) + fg * alpha
* This can be factorized into: result = bg + (fg - bg) * alpha
* alpha is in Q1.5 format, so 0.0 is represented by 0, and 1.0 is represented
*by 32
* @param fg Color to draw in RGB565 (16bit)
* @param bg Color to draw over in RGB565 (16bit)
* @param alpha Alpha in range 0-255
**/
uint16_t alphaBlendRGB565(uint32_t fg, uint32_t bg, uint8_t alpha)
__attribute__((always_inline)) {
alpha = (alpha + 4) >> 3; // from 0-255 to 0-31
bg = (bg | (bg << 16)) & 0b00000111111000001111100000011111;
fg = (fg | (fg << 16)) & 0b00000111111000001111100000011111;
uint32_t result =
((((fg - bg) * alpha) >> 5) + bg) & 0b00000111111000001111100000011111;
return (uint16_t)((result >> 16) | result); // contract result
}
/**
* Same as above, but fg and bg are premultiplied, and alpah is already in
* range 0-31
*/
uint16_t alphaBlendRGB565Premultiplied(uint32_t fg, uint32_t bg,
uint8_t alpha)
__attribute__((always_inline)) {
uint32_t result =
((((fg - bg) * alpha) >> 5) + bg) & 0b00000111111000001111100000011111;
return (uint16_t)((result >> 16) | result); // contract result
}
void Pixel(int16_t x, int16_t y, uint16_t color)
__attribute__((always_inline)) {
x += _originx;
y += _originy;
if ((x < _displayclipx1) || (x >= _displayclipx2) || (y < _displayclipy1) ||
(y >= _displayclipy2))
return;
#ifdef ENABLE_GC9A01A_FRAMEBUFFER
if (_use_fbtft) {
updateChangedRange(
x, y); // update the range of the screen that has been changed;
_pfbtft[y * _width + x] = color;
return;
}
#endif
setAddr(x, y, x, y);
writecommand_cont(GC9A01A_RAMWR);
writedata16_cont(color);
}
void drawFontBits(bool opaque, uint32_t bits, uint32_t numbits, int32_t x,
int32_t y, uint32_t repeat);
void drawFontPixel(uint8_t alpha, uint32_t x, uint32_t y);
uint32_t fetchpixel(const uint8_t *p, uint32_t index, uint32_t x);
};
#ifndef GC9A01A_swap
#define GC9A01A_swap(a, b) \
{ \
typeof(a) t = a; \
a = b; \
b = t; \
}
#endif
// To avoid conflict when also using Adafruit_GFX or any Adafruit library
// which depends on Adafruit_GFX, #include the Adafruit library *BEFORE*
// you #include ILI9341_t3.h.
// Warning the implemention of class needs to be here, else the code
// compiled in the c++ file will cause duplicate defines in the link phase.
//#ifndef _ADAFRUIT_GFX_H
#ifdef Adafruit_GFX_Button
#undef Adafruit_GFX_Button
#endif
#define Adafruit_GFX_Button GC9A01A_Button
class GC9A01A_Button {
public:
GC9A01A_Button(void) { _gfx = NULL; }
void initButton(GC9A01A_t3n *gfx, int16_t x, int16_t y, uint8_t w, uint8_t h,
uint16_t outline, uint16_t fill, uint16_t textcolor,
const char *label, uint8_t textsize_x, uint8_t textsize_y) {
_x = x;
_y = y;
_w = w;
_h = h;
_outlinecolor = outline;
_fillcolor = fill;
_textcolor = textcolor;
_textsize_x = textsize_x;
_textsize_y = textsize_y;
_gfx = gfx;
strncpy(_label, label, 9);
_label[9] = 0;
}
void drawButton(bool inverted = false) {
uint16_t fill, outline, text;
if (!inverted) {
fill = _fillcolor;
outline = _outlinecolor;
text = _textcolor;
} else {
fill = _textcolor;
outline = _outlinecolor;
text = _fillcolor;
}
_gfx->fillRoundRect(_x - (_w / 2), _y - (_h / 2), _w, _h, min(_w, _h) / 4,
fill);
_gfx->drawRoundRect(_x - (_w / 2), _y - (_h / 2), _w, _h, min(_w, _h) / 4,
outline);
_gfx->setCursor(_x - strlen(_label) * 3 * _textsize_x,
_y - 4 * _textsize_y);
_gfx->setTextColor(text);
_gfx->setTextSize(_textsize_x, _textsize_y);
_gfx->print(_label);
}
bool contains(int16_t x, int16_t y) {
if ((x < (_x - _w / 2)) || (x > (_x + _w / 2)))
return false;
if ((y < (_y - _h / 2)) || (y > (_y + _h / 2)))
return false;
return true;
}
void press(boolean p) {
laststate = currstate;
currstate = p;
}
bool isPressed() { return currstate; }
bool justPressed() { return (currstate && !laststate); }
bool justReleased() { return (!currstate && laststate); }
private:
GC9A01A_t3n *_gfx;
int16_t _x, _y;
uint16_t _w, _h;
uint8_t _textsize_x, _textsize_y;
uint16_t _outlinecolor, _fillcolor, _textcolor;
char _label[10];
boolean currstate, laststate;
};
//#endif
#endif // __cplusplus
#endif
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.
