// STM32 Nucleo-C031C6 SPI LCD Example
// Simulation: https://wokwi.com/projects/365549388158011393
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#define TFT_DC 2
#define TFT_CS 3
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
// === 按鈕腳位 ===
#define BUTTON_PIN 4 // 用 D4 當按鈕輸入腳
#define RESET_BUTTON_PIN 5 // Reset 按鈕:D5
// === 計時用變數 ===
unsigned long startTime = 0; // 記錄開始計時的時間點 (毫秒)
unsigned long lastUpdate = 0; // 上一次更新畫面的時間 (毫秒)
bool running = false; // 現在是否在計時中
// === Start/Stop 按鈕去抖動 ===
bool lastButtonState = HIGH; // 上一次讀到的按鈕狀態
unsigned long lastButtonChange = 0; // 上一次按鈕狀態改變的時間
bool buttonHandledLow = false;
// === Reset 按鈕去抖動 ===
bool lastResetState = HIGH;
unsigned long lastResetChange = 0;
bool resetHandledLow = false;
void drawStaticUI() {
tft.fillScreen(ILI9341_BLACK);
// 標題列
tft.setTextColor(ILI9341_CYAN);
tft.setTextSize(2);
tft.setCursor(10, 10);
tft.println("Running Watch");
// TIME 初始畫面
tft.setTextColor(ILI9341_YELLOW);
tft.setTextSize(3);
tft.setCursor(10, 50);
tft.println("TIME 00:00");
// PACE / DIST 先用假資料
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.setCursor(10, 100);
tft.println("PACE 6'00\"/km");
tft.setCursor(10, 130);
tft.println("DIST 0.00 km");
// 顯示按鈕提示
tft.setCursor(10, 170);
tft.println("BTN1(D4): START/STOP");
tft.setCursor(10, 190);
tft.println("BTN2(D5): RESET");
}
void setup() {
Serial.begin(115200);
tft.begin();
// tft.setRotation(1); // 目前你用預設方向
// Start/Stop 按鈕:一端接 GND,一端接 D4
// 沒按D4 HIGH,按下D4接到GND->變LOW
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Reset 按鈕:一端 D5,一端 GND
pinMode(RESET_BUTTON_PIN, INPUT_PULLUP);
drawStaticUI();
startTime = millis();
lastUpdate = millis();
}
void updateTimeDisplay(unsigned long elapsedSeconds) {
uint8_t minutes = elapsedSeconds / 60;
uint8_t seconds = elapsedSeconds % 60;
// 把原來時間那一行抹掉
tft.fillRect(10, 50, 220, 30, ILI9341_BLACK);
// 再畫新的時間
tft.setTextColor(ILI9341_YELLOW);
tft.setTextSize(3);
tft.setCursor(10, 50);
char buf[16];
sprintf(buf, "TIME %02d:%02d", minutes, seconds);
tft.println(buf);
}
void loop() {
unsigned long now = millis(); //抓現在時間
// === 1.Start/Stop 按鈕 ===
// === 讀按鈕,做去抖動並判斷是否「按下」 ===
bool currentState = digitalRead(BUTTON_PIN); // INPUT_PULLUP: 沒按 = HIGH, 按下 = LOW
if (currentState != lastButtonState) {
// 狀態改變,記錄時間(去抖動起點)
lastButtonChange = now;
lastButtonState = currentState;
}
// 狀態穩定超過 50ms
if ((now - lastButtonChange) > 50) {
// 檢查是否是「新的按下事件」
if (currentState == LOW && !buttonHandledLow) {
// 這次是從 HIGH 變 LOW 的第一次:當作一次按下
running = !running; // 切換 RUN / STOP 狀態
Serial.print("Button pressed. running = ");
Serial.println(running ? "true" : "false");
// 更新 startTime,讓暫停/繼續的時間接得上
if (running) {
// 現在開始跑:把 startTime 往前推「已經過的時間」
startTime = now - (lastUpdate - startTime);
}
buttonHandledLow = true; // 標記這次 LOW 已處理
}
else if (currentState == HIGH) {
// 放開按鈕,重置狀態,下一次 LOW 才會再次觸發
buttonHandledLow = false;
}
}
// === 2.Reset 按鈕 ===
bool resetState = digitalRead(RESET_BUTTON_PIN); // D5
if (resetState != lastResetState) {
lastResetChange = now;
lastResetState = resetState;
}
if ((now - lastResetChange) > 50) {
if (resetState == LOW && !resetHandledLow) {
// Reset 被按下
running = false; // 重設後先停住
startTime = now; // 重新計時起點
lastUpdate = now;
updateTimeDisplay(0); // 重畫成 00:00
Serial.println("Reset pressed. TIME -> 00:00");
resetHandledLow = true;
}
else if (resetState == HIGH) {
resetHandledLow = false;
}
}
// === 3.若正在計時,每秒更新一次 TIME 顯示 ===
if (running) {
if (now - lastUpdate >= 1000) {
unsigned long elapsed = (now - startTime) / 1000;
updateTimeDisplay(elapsed);
lastUpdate = now;
}
}
// 不用特別 delay,loop 會自然一直跑
}