2026年2月12日 星期四

[OTTO GO]使用者按鈕測試

 



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
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>

#define TFT_MOSI 19   // SDA (SPI MOSI)
#define TFT_SCLK 18   // SCL (SPI SCK)
#define TFT_CS    5
#define TFT_DC   16
#define TFT_RST  23

#define KEY1_PIN 35   // 使用者按鈕 P35

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);
}

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);
}

// ====== 按鍵去彈跳 + 切換 ======
bool mouthOpen = false;

// 若你的按鍵「按下讀到 LOW」,用 true;若「按下讀到 HIGH」,用 false
const bool ACTIVE_LOW = true;

int lastStable = HIGH;
int lastRead   = HIGH;
unsigned long lastDebounceMs = 0;
const unsigned long DEBOUNCE_MS = 30;

bool isPressedLevel(int level) {
  return ACTIVE_LOW ? (level == LOW) : (level == HIGH);
}

void setup() {
  SPI.begin(TFT_SCLK, -1, TFT_MOSI, TFT_CS);
  tft.init(240, 320);       // 若你確認是 170x320,再改成 tft.init(170, 320)
  tft.setRotation(1);

  pinMode(KEY1_PIN, INPUT); // P35 無內建上拉下拉,用板上電阻

  drawTigerMouth(mouthOpen);
}

void loop() {
  int reading = digitalRead(KEY1_PIN);

  if (reading != lastRead) {
    lastDebounceMs = millis();
    lastRead = reading;
  }

  if (millis() - lastDebounceMs > DEBOUNCE_MS) {
    if (reading != lastStable) {
      lastStable = reading;

      // 只在「按下瞬間」切換一次
      if (isPressedLevel(lastStable)) {
        mouthOpen = !mouthOpen;
        drawTigerMouth(mouthOpen);
      }
    }
  }
}

沒有留言:

張貼留言