Arduino:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | #include <SPI.h> #include <Adafruit_GFX.h> #include <Adafruit_ST7789.h> // ===== TFT pins (OTTO GO) ===== #define TFT_MOSI 19 #define TFT_SCLK 18 #define TFT_CS 5 #define TFT_DC 16 #define TFT_RST 23 // ===== Ultrasonic pins (OTTO GO) ===== #define US_TRIG 27 #define US_ECHO 39 // ESP32 輸入專用腳,適合 ECHO Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); static inline uint16_t RGB565(uint8_t r, uint8_t g, uint8_t b) { return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); } // ================== 虎嘴繪圖 ================== void drawMuzzleBase(int cx, int cy) { uint16_t furLight = RGB565(235, 228, 210); uint16_t shadow = RGB565(160, 150, 135); uint16_t dotDark = RGB565(70, 70, 70); tft.fillScreen(ST77XX_BLACK); // 口鼻白毛區 tft.fillCircle(cx - 55, cy + 10, 55, furLight); tft.fillCircle(cx + 55, cy + 10, 55, furLight); tft.fillRoundRect(cx - 90, cy - 25, 180, 110, 28, furLight); // 下巴陰影 tft.fillRoundRect(cx - 85, cy + 60, 170, 22, 12, shadow); tft.fillRoundRect(cx - 40, cy + 55, 80, 10, 5, RGB565(120,110,100)); // 鬍鬚點 int dotY1 = cy + 6; int dotY2 = cy + 26; for (int k = 0; k < 3; k++) { tft.fillCircle(cx - 55 - k * 12, dotY1 + k * 2, 2, dotDark); tft.fillCircle(cx - 55 - k * 12, dotY2 + k * 2, 2, dotDark); tft.fillCircle(cx + 55 + k * 12, dotY1 + k * 2, 2, dotDark); tft.fillCircle(cx + 55 + k * 12, dotY2 + k * 2, 2, dotDark); } } void drawMouthClosed(int cx, int cy) { uint16_t lineDark = RGB565(40, 40, 40); // 人中線 tft.drawLine(cx, cy - 5, cx, cy + 20, lineDark); tft.drawLine(cx - 1, cy - 5, cx - 1, cy + 20, RGB565(80,80,80)); // 上唇弧線 int mouthY = cy + 18; for (int i = 0; i < 34; i++) { int y = mouthY + (i * i) / 95; tft.drawPixel(cx - i, y, lineDark); tft.drawPixel(cx - i, y + 1, lineDark); tft.drawPixel(cx + i, y, lineDark); tft.drawPixel(cx + i, y + 1, lineDark); } // 嘴角 tft.fillCircle(cx - 36, mouthY + 12, 3, lineDark); tft.fillCircle(cx + 36, mouthY + 12, 3, lineDark); // 嘴中間小暗線 tft.fillRoundRect(cx - 10, mouthY + 20, 20, 4, 2, RGB565(90,80,70)); } void drawMouthOpen(int cx, int cy) { uint16_t lineDark = RGB565(40, 40, 40); uint16_t mouthIn = RGB565(15, 15, 15); uint16_t tongue = RGB565(210, 120, 120); // 人中線(到上唇) tft.drawLine(cx, cy - 5, cx, cy + 10, lineDark); tft.drawLine(cx - 1, cy - 5, cx - 1, cy + 10, RGB565(80,80,80)); // 上唇弧線(更彎) int mouthY = cy + 16; for (int i = 0; i < 36; i++) { int y = mouthY + (i * i) / 85; tft.drawPixel(cx - i, y, lineDark); tft.drawPixel(cx - i, y + 1, lineDark); tft.drawPixel(cx + i, y, lineDark); tft.drawPixel(cx + i, y + 1, lineDark); } // 嘴角 tft.fillCircle(cx - 38, mouthY + 14, 3, lineDark); tft.fillCircle(cx + 38, mouthY + 14, 3, lineDark); // 口腔 int w = 90, h = 45; int x = cx - w / 2; int y0 = cy + 28; tft.fillRoundRect(x, y0, w, h, 18, mouthIn); tft.fillCircle(cx, y0 + h, 22, mouthIn); // 舌頭 tft.fillRoundRect(cx - 22, y0 + 18, 44, 20, 10, tongue); tft.fillCircle(cx, y0 + 36, 14, tongue); } bool mouthOpen = false; void drawTigerMouth(bool open) { int cx = tft.width() / 2; int cy = tft.height() / 2; drawMuzzleBase(cx, cy); if (open) drawMouthOpen(cx, cy); else drawMouthClosed(cx, cy); // 顯示距離用的小區塊(可刪) tft.setTextSize(2); tft.setTextColor(ST77XX_YELLOW, ST77XX_BLACK); tft.setCursor(10, 10); tft.print(open ? "MOUTH: OPEN " : "MOUTH: CLOSE"); } // ================== 超音波測距 ================== long readDistanceCM() { // 觸發 10us 脈衝 digitalWrite(US_TRIG, LOW); delayMicroseconds(2); digitalWrite(US_TRIG, HIGH); delayMicroseconds(10); digitalWrite(US_TRIG, LOW); // 讀回波時間(避免卡住,加 timeout) // 30ms 約等於 5m(足夠) unsigned long duration = pulseIn(US_ECHO, HIGH, 30000UL); if (duration == 0) return -1; // 代表沒量到 // 距離(cm) = 時間(us) / 58 return (long)(duration / 58UL); } // ================== 主程式:距離控制開闔 ================== // 遲滯門檻:靠近到 OPEN_CM 才張開;遠離到 CLOSE_CM 才閉合 const int OPEN_CM = 15; const int CLOSE_CM = 20; // 更新間隔 const unsigned long UPDATE_MS = 80; unsigned long lastUpdateMs = 0; // 讓距離比較穩:簡單做 3 次取中位數 long median3(long a, long b, long c) { if (a > b) { long t=a; a=b; b=t; } if (b > c) { long t=b; b=c; c=t; } if (a > b) { long t=a; a=b; b=t; } return b; } void setup() { // TFT SPI.begin(TFT_SCLK, -1, TFT_MOSI, TFT_CS); tft.init(240, 320); // 若你確定是 170x320 才改 tft.setRotation(1); // Ultrasonic pinMode(US_TRIG, OUTPUT); pinMode(US_ECHO, INPUT); // 初始狀態 mouthOpen = false; drawTigerMouth(mouthOpen); } void loop() { unsigned long now = millis(); if (now - lastUpdateMs < UPDATE_MS) return; lastUpdateMs = now; long d1 = readDistanceCM(); long d2 = readDistanceCM(); long d3 = readDistanceCM(); long d = median3(d1, d2, d3); // 沒量到就不改狀態 if (d < 0) return; // 在畫面顯示距離(可刪) tft.fillRect(10, 30, 140, 24, ST77XX_BLACK); tft.setTextSize(2); tft.setTextColor(ST77XX_CYAN, ST77XX_BLACK); tft.setCursor(10, 30); tft.print("D="); tft.print(d); tft.print("cm"); bool newState = mouthOpen; if (!mouthOpen && d <= OPEN_CM) newState = true; // 靠近 -> 張開 if ( mouthOpen && d >= CLOSE_CM) newState = false; // 遠離 -> 閉合 if (newState != mouthOpen) { mouthOpen = newState; drawTigerMouth(mouthOpen); } } |
沒有留言:
張貼留言