M5Stack Electronic Animated Eyes
M5Stack で 電子アニメーションの目 を表示しました。 org 2019.05.06, rev.1 5.08, rev.2 5.10, rev.3 6.26
1. 原作は adafruit の Electronic Animated Eyes using Teensy 3.1/3.2 です。
2. これから Bodmer氏が TFT_eSPI で ESP8266_uncannyEyes を作成しました。
_ Bodmer氏に感謝致します。
3. これに基づき ESP866_uncannyEyes を M5Stackに移植しました。
機能:
_ BUTTON A:左目のウインク
_ BUTTON B:両目のウインク
_ BUTTON C:右目のウインク
.
目の種類:
defaultEye |
noScleraEye | dragonEye | goatEye |
catEye | newtEye | terminatorEye |
naugaEye | newtEye | owlEye |
.
移植:
_ ESP866_uncannyEyes を M5Stack で動作する様に移植します。
_ フォルダーの構成は以下の様になります。
_
1. ESP866_uncannyEyes の 修正 及び 追加 をしました。
_ スケッチのリストは ブログ最下位に記載しています。
_ SD_Update 対応です。
_ 名前は「ESP866_uncannyEyes」から「M5Stack_uncannyEyes」にしました。
2. Eyes データー:ESP866_uncannyEyes の中の Eyes データーに
_ defaultEye, dragonEye, goatEye, goatEye があり 2つ修正が必要です。
_ リアルな目の defaultEye だけの使用のみならば Eyes データーの編集や追加は不要です。
_ 各自使用のエデッターで編集してください。
_ 修正:dragonEye : 「-0x001」 は 誤記で「0x007F」に変更。
_ 修正:goatEye : 「-0x001」 は 誤記で「0x007F」に変更。
3. Eyes データーの追加:adafruit/Uncanny_Eyes にある catEye, doeEye, terminatorEye,
_ naugaEye, newtEye, owlEye の6つに 追加と修正をします。
_ defaultEye.h と dragonEye.h は正常に動作していますので この記述を参照します。
_ Eyes データーの配列の構成は sclera, upper, lower, polar の 4つでできています。
_ 追加: 「#include pgmspace.h」 の追加。
_ 修正:「][」 を 「 * 」 , 「 =」 を 「PROGMEM=」に変更。
.
抽画メモ:
_ drawEye ルーチン:
_ 左の表示ウインドウを設定する:if (e == 0){M5.Lcd.setAddrWindow( 0,0,128,128);}
_ 右の表示ウインドウを設定する:if (e == 1){M5.Lcd.setAddrWindow(192,0,128,128);}
_ M5.Lcd.setAddrWindow( x axis, y axis, Horizontal width, Vertical width );
_ 画像を抽画する:M5.Lcd.pushColors((uint8_t*)pbuffer,pixels*2); pixels=0;}
_ 2 は 縦を2倍にしています。
_ 左目の処理:左右反転。screenX
_ 右目:if (e == 1){screenX_ = screenX;}
_ 左目:if (e == 0){screenX_ = SCREEN_WIDTH – 1 – screenX;}
_ 次の処理:
_ if((pgm_read_byte(lower+screenY * SCREEN_WIDTH + screenX_) <= lT) ||
_ (pgm_read_byte(upper+screenY * SCREEN_WIDTH + screenX_) <= uT))
_ frame ルーチン:瞳, eyeX
_ 左の瞳:if (eyeIndex == 0){eyeX = map(eyeX,1023,0,0,SCLERA_WIDTH – 128);}
_ 右の瞳:if (eyeIndex == 1){eyeX = map(eyeX,0,1023,0,SCLERA_WIDTH – 128);}
.
レンズ:プラスチック レンズを使用しました。
価格:2個378円 ( 1個 189円 , $ 1.70 )。 仕様:23mm。 販売:ebay 2012topdeal shop:
.
Youtube:追記 2020.06.05
_ У Павла 氏が 動画を投稿しました。
_ Making eyes on the m5stack Board
DownLoad:ここから DLできます。
.
感想:
M5Stack を使用しない時 飾りに置くのも良いかと思います。
機能:BUTTON A,B、C で 目のウインクができます。他のアイデアでセンサー等と
_ 接続すると 面白い物が出来るかと思います。
左目と右目:
_ 原作の adafruit は 表示装置を2つ使用し 右と左を用意しています。
_ ESP866_uncannyEyes も 2つ使用する構造になっています。
_ 今回 M5Stack の中に 2つ同じデーターを表示する為、左の画像が左右逆になっています。
_ 左の画像を左右逆にするには レベルが高く 今回はこの程度にしておきました。
_ 次回、2019/05/08 では 綺麗なSPI DisplayのM5Stick版を記載しました。
_ M5StickC Electronic Animated Eyes:
_
メモ:この画像を表示しますと DISPLAY の SPI MOSI に複雑な信号がでます。
_ これにより MBUS MOSI (GPIO_23) の端子から 放射ノイズ が多くなり
_ 隣にある DAC(25) Pin とパターンへ 放射ノイズが乗りやすくなります。
_ Pin と PROTO基板等の条件により スピーカーからのノイズが多くなります。
_ ソフトの対策として
_ #include , dac_output_disable(DAC_CHANNEL_1); を記載し
_ DAC をオフにして スピーカーから ノイズを出さない様にしています。
_
_ M5Stackのスピーカーノイズの原因と対策:追記、2019.09.11
_ 要素: NS4150Bの基盤設計の不足等、幾つかの要素はありますが、
_ 大きな要素は、MBUS MOSI (GPIO_23) と MBUS DAC(25) が 隣
_ (2.54mm) にある為に、MBUS MOSIの放射ノイズによりスピーカー
_ からノイズがでます。MBUS DAC(25) ピンがあるだけでも誘導します。
_ MBUS DAC(25) のアンプ入力の配置と配線は アンプの入力にアンテナを
_ 立てている事と同じです。
_ 原則:MBUS MOSI と MBUS DAC は 隣に配置してはいけない。
_ レイアウトに於ける設計ミスと判断しています。
_ 確認:テスター等で誘導電圧が確認できます。23と25間にワイヤーを近接する
_ と誘導される事が解ります。周波数成分の大きいMOSI信号時も同様です。
_ 原因:基盤設計における放射ノイズやレイアウトの技術不足の為です。
_ たとえば MBUS MOSI (GPIO_23) と MBUS SCK (GPIO_18) を逆配置にする
_ とノイズは発生しません。放射ノイズを受けない距離の為です。
_ 情報:この件は 私のFaceBookメモから M5Stackチームが気がつき
_ ある製品開発では MBUS DAC(25) ピンの配線をしていません。
_ M5Stack Community では BOTTOM を外すというアドバイスがある。
_ 対策:MBUS DAC(25)ピンをカットします。
_ MBUS DAC(25)ピンをカットすると ノイズの無いPROTO基盤が可能です。
_ 補足:mはげ氏は M5Stack音割れ問題 の調査に努力され、ノイズ発生の
_ 少ないDACの入力方法を編み出しています。mはげ氏に感謝致します。
_ NS4150Bを入力抵抗30KΩのNS4148へ交換を実施してみましたが
_ ノイズの問題は解決しませんでした。NS4150BやNS4148はスマホの
_ アンプに使用されています。よってM5の基板設計の問題と判断しています。
_ たとえば GNDパターンに誘導があり 正しい GNDが形成されていません。
_
_ 自作設計者へ:
_ この件を把握していない為に スイッチサイエンスで委託販売している
_ PROTO基盤の製品はノイズ対策がされていない為にノイズがでるはずです。
_ 販売されている基盤の MBUS DAC(25) のレイアウトを見ると解ります。
_ MBUS DAC(25)ピンがあるだけでも発生し、50x50mmの基盤上にパターン
_ を設けると確実にノイズが発生します。ある基板は 23Pin と 25Pin間に
_ スルーホールを配置し 1.27mm以下の距離になりノイズを増加させています。
_ さらに フラットケーブルで延長している製品は確実にノイズが発生します。
.
リスト:
_ M5Stack_uncannyEyes :
_ org ___ 2019.05.06 Transplant by macsbug
_ rev.1 _ 2019.05.08 : add SD_Updat
_ rev.2 _ 2019.05.10 : Left eye position and movement correction
_ rev.3 _ 2019.06.26 : Correct the up and down movement of the pupil
追記:2019.05.08 rev.1 : スケッチ変更。
_ スケッチの org バージョンは TFT_eSPI Library 用でしたが
_ M5Stack Library 用に変更しました。 これにより SD_Update が可能です。
追記:2019.05.10 rev.2 : スケッチ変更。
_ 左目の位置を正常な位置(右の逆)に変更。目の移動も逆に設定。
追記:2019.06.26 rev.3 : スケッチ変更。
_ robo8080氏の指摘:157行目 変更。
_ 瞳の動きが上下逆になっているバグの修正。
_ irisX は削除し
_ irisY = scleraY – (SCLERA_HEIGHT – IRIS_HEIGHT ) / 2; // add : rev.3 2019.06.26
_ robo8080氏へ感謝!!。ありがとうございます。
追記:2020.06.05:Down Load 追加。
_ 🟠 Down Load:M5Stack_uncannyEyes.zip
_ DL後に File name を M5Stack_uncannyEyes.zip ( .zip ) に変更します。
_ 解凍すると M5Stack_uncannyEyes の Folder ができます。
// Adapted by Bodmer to work with a NodeMCU and ILI9341 or ST7735 display. // // This code currently does not "blink" the eye! // // Library used is here: // https://github.com/Bodmer/M5Display // // To do, maybe, one day: // 1. Get the eye to blink // 2. Add another screen for another eye // 3. Add varaible to set how wide open the eye is // 4. Add a reflected highlight to the cornea // 5. Add top eyelid shaddow to eye surface // 6. Add aliasing to blur mask edge // // With one lidded eye drawn the code runs at 28-33fps (at 27-40MHz SPI clock) // which is quite reasonable. Operation at an 80MHz SPI clock is possible but // the display may not be able to cope with a clock rate that high and the // performance improvement is small. Operate the ESP8266 at 160MHz for best // frame rate. Note the images are stored in SPI FLASH (PROGMEM) so performance // will be constrained by the increased memory access time. // Original header for this sketch is below. Note: the technical aspects of the // text no longer apply to this modified version of the sketch: /* //-------------------------------------------------------------------------- // Uncanny eyes for PJRC Teensy 3.1 with Adafruit 1.5" OLED (product #1431) // or 1.44" M5.Lcd LCD (#2088). This uses Teensy-3.1-specific features and // WILL NOT work on normal Arduino or other boards! Use 72 MHz (Optimized) // board speed -- OLED does not work at 96 MHz. // // Adafruit invests time and resources providing this open source code, // please support Adafruit and open-source hardware by purchasing products // from Adafruit! // // Written by Phil Burgess / Paint Your Dragon for Adafruit Industries. // MIT license. SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library. // Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept. //-------------------------------------------------------------------------- */ // M5Stack_uncannyEyes : org : 2019.05.06 Transplant by macsbug // M5Stack_uncannyEyes : rev.1 : 2019.05.08 modify by macsbug // M5Stack_uncannyEyes : rev.2 : 2019.05.10 modify by macsbug // M5Stack_uncannyEyes : rev.3 : 2019.06.26 modify by macsbug // https://macsbug.wordpress.com/2019/05/06/m5stack-electronic-animated-eyes/ // Bodmer/ESP8266_uncannyEyes/ESP8266_uncannyEyes.ino // https://github.com/Bodmer/ESP8266_uncannyEyes/blob/master/ESP8266_uncannyEyes.ino // graphics : catEye.h,terminatorEye.h,doeEye.h,naugaEye.h,newtEye.h,owlEye.h // https://github.com/adafruit/Uncanny_Eyes/tree/master/uncannyEyes/graphics // Electronic Animated Eyes using Teensy 3.1/3.2 // https://learn.adafruit.com/animated-electronic-eyes-using-teensy-3-1?view=all // adafruit/Uncanny_Eyes : // https://github.com/adafruit/Uncanny_Eyes/tree/master/uncannyEyes #include <M5Stack.h> #include "M5StackUpdater.h" #include <driver/dac.h> // Enable ONE of these #includes for the various eyes: #include "defaultEye.h" // Standard human-ish hazel eye //#include "noScleraEye.h" // Large iris, no sclera //#include "dragonEye.h" // Slit pupil fiery dragon// -0x001 to 0x007F //#include "goatEye.h" // Horizontal pupil goat // -0x001 to 0x007F //#include "catEye.h" // #include <pgmspace.h>,][ to *,= to PROGMEM= //#include "doeEye.h" // #include <pgmspace.h>,][ to *,= to PROGMEM= //#include "terminatorEye.h"// #include <pgmspace.h>,][ to *,= to PROGMEM= //#include "naugaEye.h" // #include <pgmspace.h>,][ to *,= to PROGMEM= //#include "newtEye.h" // #include <pgmspace.h>,][ to *,= to PROGMEM= //#include "owlEye.h" // #include <pgmspace.h>,][ to *,= to PROGMEM= #define DISPLAY_DC D3 // Data/command pin for BOTH displays #define DISPLAY_RESET D4 // Reset pin for BOTH displays #define SELECT_L_PIN 38 // LEFT eye chip select pin #define SELECT_R_PIN 39 // RIGHT eye chip select pin // INPUT CONFIG (for eye motion -- enable or comment out as needed) ------- // The ESP8266 is rather constrained here as it only has one analogue port. // An I2C ADC could be used for more analogue channels //#define JOYSTICK_X_PIN A0 // Analog pin for eye horiz pos (else auto) //#define JOYSTICK_Y_PIN A0 // Analog pin for eye vert position (") //#define JOYSTICK_X_FLIP // If set, reverse stick X axis //#define JOYSTICK_Y_FLIP // If set, reverse stick Y axis #define TRACKING // If enabled, eyelid tracks pupil #define IRIS_PIN 37 // Photocell or potentiometer (else auto iris) #define IRIS_PIN_FLIP 37 // If set, reverse reading from dial/photocell #define IRIS_SMOOTH // If enabled, filter input from IRIS_PIN #define IRIS_MIN 140 // Clip lower analogRead() range from IRIS_PIN #define IRIS_MAX 260 // Clip upper " #define WINK_L_PIN 39 // Pin for LEFT eye wink button #define BLINK_PIN 38 // Pin for blink button (BOTH eyes) #define WINK_R_PIN 37 // Pin for RIGHT eye wink button #define AUTOBLINK // If enabled, eyes blink autonomously // Probably don't need to edit any config below this line, ----------------- // unless building a single-eye project (pendant, etc.), in which case one // of the two elements in the eye[] array further down can be commented out. // Eye blinks are a tiny 3-state machine. Per-eye allows winks + blinks. #define NOBLINK 0 // Not currently engaged in a blink #define ENBLINK 1 // Eyelid is currently closing #define DEBLINK 2 // Eyelid is currently opening typedef struct { int8_t pin; // Optional button here for indiv. wink uint8_t state; // NOBLINK/ENBLINK/DEBLINK int32_t duration; // Duration of blink state (micros) uint32_t startTime; // Time (micros) of last state change } eyeBlink; struct { uint8_t cs; // Chip select pin eyeBlink blink; // Current blink state } eye[] = { SELECT_L_PIN, { WINK_L_PIN, NOBLINK }, SELECT_R_PIN, { WINK_R_PIN, NOBLINK } }; #define NUM_EYES 2 // pcs eye : 1 = 1 eye, 2 = 2 eye uint32_t fstart = 0; // start time to improve frame rate calculation at startup // INITIALIZATION -- runs once at startup ---------------------------------- void setup(void) { M5.begin(); Wire.begin();if(digitalRead(39)==0){updateFromFS(SD);ESP.restart();}//SD UPdate uint8_t e = 0; M5.Speaker.begin(); //M5.Speaker.setVolume(0); //M5.Speaker.mute(); dac_output_disable(DAC_CHANNEL_1); M5.Lcd.init(); M5.Lcd.fillScreen(TFT_BLACK); M5.Lcd.setRotation(1); fstart = millis()-1; // Subtract 1 to avoid divide by zero later pinMode(37, INPUT);pinMode(38, INPUT);pinMode(39, INPUT); } // EYE-RENDERING FUNCTION -------------------------------------------------- #define BUFFER_SIZE 256 // 64 to 512 seems optimum = 30 fps for default eye void drawEye( // Renders one eye. Inputs must be pre-clipped & valid. // Use native 32 bit variables where possible as this is 10% faster! uint8_t e, // Eye array index; 0 or 1 for left/right:0/1 = left/right uint32_t iScale, // Scale factor for iris uint32_t scleraX,// First pixel X offset into sclera image:Center of right and left uint32_t scleraY,// First pixel Y offset into sclera image:Upper and lower centers uint32_t uT, // Upper eyelid threshold value:Up and down uint32_t lT) // Lower eyelid threshold value:Up and down { uint32_t screenX, screenY, scleraXsave; uint32_t screenX_; // add macsbug int32_t irisX, irisY; uint32_t p, a; uint32_t d; uint32_t pixels = 0; uint16_t pbuffer[BUFFER_SIZE]; // This one needs to be 16 bit // Set up raw pixel dump to entire screen. Although such writes can wrap // around automatically from end of rect back to beginning, the region is // reset on each frame here in case of an SPI glitch. if (e == 0){ M5.Lcd.setAddrWindow ( 1,0,128,128);} // set left window if (e == 1){ M5.Lcd.setAddrWindow (191,0,128,128);} // set right window // Now just issue raw 16-bit values for every pixel... scleraXsave = scleraX; irisY = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT ) / 2; // add : rev.3 2019.06.26 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 (e == 1){screenX_ = screenX;} if (e == 0){screenX_ = SCREEN_WIDTH - 1 - screenX;} if((pgm_read_byte(lower+screenY * SCREEN_WIDTH + screenX_) <= lT) || (pgm_read_byte(upper+screenY * SCREEN_WIDTH + screenX_) <= uT)){ p = 0; //Covered by eyelid } else if((irisY < 0) || (irisY >= IRIS_HEIGHT) || (irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX); } else { // Maybe iris... p = pgm_read_word(polar+irisY*IRIS_WIDTH+irisX);// Polar angle/dist d = (iScale * (p & 0x7F)) / 128; // Distance (Y) if(d < IRIS_MAP_HEIGHT) { // Within iris area a = (IRIS_MAP_WIDTH * (p >> 7)) / 512; // Angle (X) p = pgm_read_word(iris+d*IRIS_MAP_WIDTH+a); // Pixel = iris } else { // Not in iris p = pgm_read_word(sclera+scleraY*SCLERA_WIDTH+scleraX);//Pixel=clear } } *(pbuffer + pixels++) = p>>8 | p<<8; if (pixels >= BUFFER_SIZE){yield(); M5.Lcd.pushColors((uint8_t*)pbuffer,pixels*2); pixels=0; //drawEye } } } if (pixels) { M5.Lcd.pushColors(pbuffer, pixels); pixels = 0;} } // EYE ANIMATION ----------------------------------------------------------- const uint8_t ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, // T 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, // h 11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23, // x 24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, // 2 40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58, // A 60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80, // l 81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98,100,101,103, // e 104,106,107,109,110,112,113,115,116,118,119,121,122,124,125,127, // c 128,130,131,133,134,136,137,139,140,142,143,145,146,148,149,151, // J 152,154,155,157,158,159,161,162,164,165,167,168,170,171,172,174, // a 175,177,178,179,181,182,183,185,186,188,189,190,192,193,194,195, // c 197,198,199,201,202,203,204,205,207,208,209,210,211,213,214,215, // o 216,217,218,219,220,221,222,224,225,226,227,228,228,229,230,231, // b 232,233,234,235,236,237,237,238,239,240,240,241,242,243,243,244, // s 245,245,246,246,247,248,248,249,249,250,250,251,251,251,252,252, // o 252,253,253,253,254,254,254,254,254,255,255,255,255,255,255,255 }; // n #ifdef AUTOBLINK uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L; #endif void frame( // Process motion for a single frame of left or right eye uint32_t iScale) { // Iris scale (0-1023) passed in static uint32_t frames = 0; // Used in frame rate calculation static uint8_t eyeIndex = 0; // eye[] array counter int32_t eyeX, eyeY; uint32_t t = micros(); // Time at start of function Serial.print((++frames * 1000) / (millis() - fstart)); Serial.println("fps"); // Show frame rate if(++eyeIndex >= NUM_EYES) eyeIndex = 0;// Cycle through eyes, 1 per call // Autonomous X/Y eye motion // Periodically initiates motion to a new random point, random speed, // holds there for random period until next motion. static boolean eyeInMotion = false; static int32_t eyeOldX=512, eyeOldY=512, eyeNewX=512, eyeNewY=512; static uint32_t eyeMoveStartTime = 0L; static int32_t eyeMoveDuration = 0L; int32_t dt = t - eyeMoveStartTime; // uS elapsed since last eye event if(eyeInMotion) { // Currently moving? if(dt >= eyeMoveDuration) { // Time up? Destination reached. eyeInMotion = false; // Stop moving eyeMoveDuration = random(3000000L);// 0-3 sec stop eyeMoveStartTime = t; // Save initial time of stop eyeX = eyeOldX = eyeNewX; // Save position eyeY = eyeOldY = eyeNewY; } else { // Move time's not yet fully elapsed -- interpolate position int16_t e = ease[255 * dt / eyeMoveDuration] + 1; // Ease curve eyeX = eyeOldX + (((eyeNewX - eyeOldX) * e) / 256); // Interp X eyeY = eyeOldY + (((eyeNewY - eyeOldY) * e) / 256); // and Y } } else { // Eye stopped eyeX = eyeOldX; eyeY = eyeOldY; if(dt > eyeMoveDuration) { // Time up? Begin new move. int16_t dx, dy; uint32_t d; do { // Pick new dest in circle eyeNewX = random(1024); eyeNewY = random(1024); dx = (eyeNewX * 2) - 1023; dy = (eyeNewY * 2) - 1023; } while((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying eyeMoveDuration = random(50000,150000); // ~1/14sec //random(72000,144000); // ~1/ 7sec eyeMoveStartTime = t; // Save initial time of move eyeInMotion = true; // Start move on next frame } } // Blinking #ifdef AUTOBLINK // Similar to the autonomous eye movement above -- blink start times // and durations are random (within ranges). if((t - timeOfLastBlink) >= timeToNextBlink) { // Start new blink? timeOfLastBlink = t; uint32_t blinkDuration = random(36000, 72000); // ~1/28 - ~1/14 sec // Set up durations for both eyes (if not already winking) for(uint8_t e=0; e<NUM_EYES; e++) { if(eye[e].blink.state == NOBLINK) { eye[e].blink.state = ENBLINK; eye[e].blink.startTime = t; eye[e].blink.duration = blinkDuration; } } timeToNextBlink = blinkDuration * 3 + random(4000000); } #endif if(eye[eyeIndex].blink.state) { // Eye currently blinking? // Check if current blink state time has elapsed if((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration){ // Yes -- increment blink state, unless... if((eye[eyeIndex].blink.state == ENBLINK) && // Enblinking and... ((digitalRead(BLINK_PIN) == LOW) || // blink or wink held... digitalRead(eye[eyeIndex].blink.pin) == LOW)) { // Don't advance state yet -- eye is held closed instead } else { // No buttons, or other state... if(++eye[eyeIndex].blink.state > DEBLINK) {// Deblinking finished? eye[eyeIndex].blink.state = NOBLINK; // No longer blinking } else { // Advancing from ENBLINK to DEBLINK mode eye[eyeIndex].blink.duration *= 2;//DEBLINK is 1/2 ENBLINK speed eye[eyeIndex].blink.startTime = t; } } } } else { // Not currently blinking...check buttons! if(digitalRead(BLINK_PIN) == LOW) { // Manually-initiated blinks have random durations like auto-blink uint32_t blinkDuration = random(36000, 72000); for(uint8_t e=0; e<NUM_EYES; e++) { if(eye[e].blink.state == NOBLINK) { eye[e].blink.state = ENBLINK; eye[e].blink.startTime = t; eye[e].blink.duration = blinkDuration; } } } else if(digitalRead(eye[eyeIndex].blink.pin) == LOW) { // Wink! eye[eyeIndex].blink.state = ENBLINK; eye[eyeIndex].blink.startTime = t; eye[eyeIndex].blink.duration = random(45000, 90000); } } // Process motion, blinking and iris scale into renderable values // Iris scaling: remap from 0-1023 input to iris map height pixel units iScale = ((IRIS_MAP_HEIGHT + 1) * 1024) / (1024 - (iScale * (IRIS_MAP_HEIGHT - 1) / IRIS_MAP_HEIGHT)); // Scale eye X/Y positions (0-1023) to pixel units used by drawEye() if (eyeIndex == 0){eyeX = map(eyeX,1023,0,0,SCLERA_WIDTH - 128);}// left if (eyeIndex == 1){eyeX = map(eyeX,0,1023,0,SCLERA_WIDTH - 128);}// right eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128); if(eyeIndex == 1) eyeX = (SCLERA_WIDTH - 128) - eyeX; // Mirrored display // Horizontal position is offset so that eyes are very slightly crossed // to appear fixated (converged) at a conversational distance. Number // here was extracted from my posterior and not mathematically based. // I suppose one could get all clever with a range sensor, but for now... eyeX += 4; if(eyeX > (SCLERA_WIDTH - 128)) eyeX = (SCLERA_WIDTH - 128); // Eyelids are rendered using a brightness threshold image. This same // map can be used to simplify another problem: making the upper eyelid // track the pupil (eyes tend to open only as much as needed -- e.g. look // down and the upper eyelid drops). Just sample a point in the upper // lid map slightly above the pupil to determine the rendering threshold. static uint8_t uThreshold = 128; uint8_t lThreshold, n; #ifdef TRACKING int16_t sampleX = SCLERA_WIDTH / 2 - (eyeX / 2), // Reduce X influence sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4); // Eyelid is slightly asymmetrical, so two readings are taken, averaged if(sampleY < 0) n = 0; else n = (pgm_read_byte(upper + sampleY * SCREEN_WIDTH + sampleX) + pgm_read_byte(upper+sampleY*SCREEN_WIDTH+(SCREEN_WIDTH-1-sampleX)))/2; uThreshold = (uThreshold * 3 + n) / 4; // Filter/soften motion // Lower eyelid doesn't track the same way, but seems to be pulled upward // by tension from the upper lid. lThreshold = 254 - uThreshold; #else // No tracking -- eyelids full open unless blink modifies them uThreshold = lThreshold = 0; #endif // The upper/lower thresholds are then scaled relative to the current // blink position so that blinks work together with pupil tracking. if(eye[eyeIndex].blink.state) { // Eye currently blinking? uint32_t s = (t - eye[eyeIndex].blink.startTime); if(s >= eye[eyeIndex].blink.duration) s = 255;// At or past blink end else s = 255 * s / eye[eyeIndex].blink.duration; // Mid-blink s = (eye[eyeIndex].blink.state == DEBLINK) ? 1 + s : 256 - s; n = (uThreshold * s + 254 * (257 - s)) / 256; lThreshold = (lThreshold * s + 254 * (257 - s)) / 256; } else { n = uThreshold; } // Pass all the derived values to the eye-rendering function: drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold); } // AUTONOMOUS IRIS SCALING (if no photocell or dial) ----------------------- #if !defined(IRIS_PIN) || (IRIS_PIN < 0) // Autonomous iris motion uses a fractal behavior to similate both the major // reaction of the eye plus the continuous smaller adjustments that occur. uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris; void split( // Subdivides motion path into two sub-paths w/randimization int16_t startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start int16_t endValue, // Iris scale value at end uint32_t startTime, // micros() at start int32_t duration, // Start-to-end time, in microseconds int16_t range) { // Allowable scale value variance when subdividing if(range >= 8) { // Limit subdvision count, because recursion range /= 2; // Split range & time in half for subdivision, duration /= 2; // then pick random center point within range: int16_t midValue = (startValue + endValue - range)/2+random(range); uint32_t midTime = startTime + duration; split(startValue, midValue, startTime, duration, range);//First half split(midValue , endValue, midTime , duration, range);//Second half } else { // No more subdivisons, do iris motion... int32_t dt; // Time (micros) since start of motion int16_t v; // Interim value while((dt = (micros() - startTime)) < duration) { v = startValue + (((endValue - startValue) * dt) / duration); if(v < IRIS_MIN) v = IRIS_MIN; // Clip just in case else if(v > IRIS_MAX) v = IRIS_MAX; frame(v); // Draw frame w/interim iris scale value } } } #endif // !IRIS_PIN // MAIN LOOP -- runs continuously after setup() ---------------------------- void loop() { #if defined(IRIS_PIN) && (IRIS_PIN >= 0) // Interactive iris uint16_t v = 512; //analogRead(IRIS_PIN);// Raw dial/photocell reading #ifdef IRIS_PIN_FLIP v = 1023 - v; #endif v = map(v, 0, 1023, IRIS_MIN, IRIS_MAX); // Scale to iris range #ifdef IRIS_SMOOTH // Filter input (gradual motion) static uint16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2; irisValue = ((irisValue * 15) + v) / 16; frame(irisValue); #else // Unfiltered (immediate motion) frame(v); #endif // IRIS_SMOOTH #else // Autonomous iris scaling -- invoke recursive function newIris = random(IRIS_MIN, IRIS_MAX); split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN); oldIris = newIris; #endif // IRIS_PIN //screenshotToConsole(); }
——————————————————————————————
M5Stack , Electronic Animated Eyes , TFT_eSPI , ESP866_uncannyEyes , macsbug , DAC, nois , ノイズ
[…] уже портированы на ESP32. Я же это подсмотрел вот в этой статье. Ну и по традиции демонстрация работы данного […]
Делаем анимированные глаза на esp32. - У Павла!
6月 5, 2020 at 4:55 am
Thank you for visiting.
Thank you for your blog posts and postings on Youtube.
YouTube was linked in the article.
macsbug
6月 5, 2020 at 7:36 am