开心四房播播al Project 9.0: Chess Timer

The program centers on a chess timer program built on the 1ST Maker Frog board that simulates a real chess clock using embedded programming concepts. At its core, the program is a finite state machine that manages different game states. It tracks each player’s remaining time using a subtract-elapsed timing method, switches turns based on button release events, and supports features like pause/resume gestures and optional Fischer time increments.

The program centers on a chess timer program built on the 1ST Maker Frog board that simulates a real chess clock using embedded programming concepts. At its core, the program is a finite state machine that manages different game states. It tracks each player’s remaining time using a subtract-elapsed timing method, switches turns based on button release events, and supports features like pause/resume gestures and optional Fischer time increments.

Sample Code:

				
					/**************************************************
 * Program: Chess_Timer.ino
 * Copyright (C) 成品人和精品人的区别在哪里, Inc. 2026
 *
 * Description:
 *   This program implements a chess timer using
 *   the 1ST Maker Frog hardware. On startup, the players
 *   choose the length of play by turning the 
 *   potentiometer. Then the play is started by pressing
 *   switch 1 or switch 2. After the player's turn is over
 *   they press their button, which counts down their
 *   opponent's timer. The game can be paused by pressing
 *   both buttons.
 *
 * Hardware:
 *   - 1ST Maker Frog board
 *   - OLED display (SSD1306)
 *   - Two NeoPixel eyes
 *   - Two buttons (BUTTON_ONE, BUTTON_TWO)
 *   - Piezo buzzer (PIEZO)
 **************************************************/

#include "1ST_Maker_Frog.h"

// ---------------------------------------------------------------------------
//  Constants and configuration
// ---------------------------------------------------------------------------

const uint16_t DISPLAY_FPS_MS = 100;   // refresh OLED about 10 times/second
const uint16_t DEBOUNCE_MS    = 35;    // simple button debounce

// Pause hold (pause happens while holding; resume happens after releasing)
const uint16_t PAUSE_HOLD_MS  = 1200;  // hold BOTH this long to pause / arm resume

// Time presets selected by the potentiometer (minutes)
const uint8_t PRESET_COUNT = 6;
const uint8_t PRESET_MINUTES[PRESET_COUNT] = {1, 3, 5, 10, 15, 20};

// Increment per move (seconds). Keep 0 for the first version; easy to change later.
const uint8_t INCREMENT_SECONDS = 0;

// Piezo tones (simple feedback)
const uint16_t BEEP_HZ        = 1500;
const uint16_t BEEP_SHORT_MS  = 40;
const uint16_t BEEP_ARM_MS    = 70;   // slightly longer so it's noticeable

// ---------------------------------------------------------------------------
//  Data structures and globals
// ---------------------------------------------------------------------------

enum ClockState {
  STATE_SET,
  STATE_RUN_A,
  STATE_RUN_B,
  STATE_PAUSED,
  STATE_OVER
};

struct ButtonTracker {
  uint8_t pin;
  bool stablePressed;         // debounced "pressed" state
  bool lastStablePressed;
  uint32_t lastChangeMs;      // for debounce timing
  bool pressedEvent;          // one-loop: transitioned to pressed
  bool releasedEvent;         // one-loop: transitioned to released
};

ClockState state = STATE_SET;
ClockState prevRunState = STATE_RUN_A; // remembers whether A or B was running before pause

uint32_t startTimeMs = 5UL * 60UL * 1000UL;
int32_t timeA_ms = 0;
int32_t timeB_ms = 0;

uint32_t lastTickMs = 0;
uint32_t lastDisplayMs = 0;

// BOTH-button hold tracking (one action per hold, no cycling)
uint32_t bothPressStartMs = 0;
bool bothGestureUsed = false;

// Resume is armed while holding BOTH, but only happens after BOTH are released.
bool resumePending = false;

// Prevent turn-switch release after a pause/resume hold
bool ignoreReleasesUntilBothUp = false;

// Button trackers
ButtonTracker btnA = {BUTTON_ONE, false, false, 0, false, false};
ButtonTracker btnB = {BUTTON_TWO, false, false, 0, false, false};

// ---------------------------------------------------------------------------
//  Function prototypes
// ---------------------------------------------------------------------------

void initHardware();
void updateButtons(uint32_t nowMs);
void handleStateMachine(uint32_t nowMs);

uint8_t readPresetIndexFromPot();
void applyPreset(uint8_t presetIndex);

void tickActiveClock(uint32_t nowMs);
void switchTurnTo(ClockState nextState);

void renderUI(uint32_t nowMs);
void drawTimeBox(int16_t x, int16_t y, const char* label, int32_t msRemaining, bool isActive);
void formatTime(int32_t msRemaining, char* out, size_t outSize);

void updateEyes();

void beep(uint16_t hz, uint16_t durationMs);
void beepClick();
void beepArmed();
void beepTimeUp();

// ---------------------------------------------------------------------------
//  Setup and main loop
// ---------------------------------------------------------------------------

void setup() {
  initHardware();

  state = STATE_SET;
  applyPreset(readPresetIndexFromPot());

  lastTickMs = millis();
  lastDisplayMs = 0;
}

void loop() {
  uint32_t nowMs = millis();

  updateButtons(nowMs);
  handleStateMachine(nowMs);

  if (nowMs - lastDisplayMs >= DISPLAY_FPS_MS) {
    lastDisplayMs = nowMs;
    renderUI(nowMs);
  }

  updateEyes();
}

// ---------------------------------------------------------------------------
//  Helper functions
// ---------------------------------------------------------------------------

/**************************************************
 * Function: initHardware
 * Purpose:  Initializes Frog hardware (OLED, LEDs,
 *           buttons, NeoPixels).
 **************************************************/
void initHardware() {
  for (uint8_t i = 0; i < 4; i++) {
    pinMode(LEDs[i], OUTPUT);
    digitalWrite(LEDs[i], LOW);
  }

  // Buttons are active-LOW, so use pullups.
  pinMode(BUTTON_ONE, INPUT_PULLUP);
  pinMode(BUTTON_TWO, INPUT_PULLUP);

  pinMode(PIEZO, OUTPUT);

  // Constructors provided by 1ST_Maker_Frog.h
  pixel.begin();
  pixel.show();

  Wire.begin();
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    while (true) {
      for (uint8_t i = 0; i < 4; i++) digitalWrite(LEDs[i], HIGH);
      delay(150);
      for (uint8_t i = 0; i < 4; i++) digitalWrite(LEDs[i], LOW);
      delay(150);
    }
  }

  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.display();
}

/**************************************************
 * Function: updateButtons
 * Purpose:  Debounces buttons and creates one-loop
 *           press/release events.
 *
 * Parameters:
 *    nowMs - current millis() time
 **************************************************/
void updateButtons(uint32_t nowMs) {
  ButtonTracker* buttons[2] = {&btnA, &btnB};

  for (uint8_t i = 0; i < 2; i++) {
    ButtonTracker* b = buttons[i];
    b->pressedEvent = false;
    b->releasedEvent = false;

    bool rawPressed = (digitalRead(b->pin) == PRESSED);

    if (rawPressed != b->stablePressed) {
      if (nowMs - b->lastChangeMs >= DEBOUNCE_MS) {
        b->lastStablePressed = b->stablePressed;
        b->stablePressed = rawPressed;
        b->lastChangeMs = nowMs;

        if (b->stablePressed && !b->lastStablePressed) b->pressedEvent = true;
        if (!b->stablePressed && b->lastStablePressed) b->releasedEvent = true;
      }
    } else {
      b->lastChangeMs = nowMs;
    }
  }
}

/**************************************************
 * Function: handleStateMachine
 * Purpose:  Runs the chess-clock logic.
 *           - Turn changes on button RELEASE.
 *           - Pause happens while BOTH are held.
 *           - Resume is ARMED while holding BOTH, and
 *             resumes AFTER both buttons are released.
 *
 * Parameters:
 *    nowMs - current millis() time
 **************************************************/
void handleStateMachine(uint32_t nowMs) {
  bool bothPressed = btnA.stablePressed && btnB.stablePressed;

  // If we used a pause/resume hold, ignore releases until BOTH are fully up.
  if (ignoreReleasesUntilBothUp) {
    if (!btnA.stablePressed && !btnB.stablePressed) {
      ignoreReleasesUntilBothUp = false;

      // Clear any "button up" events caused by letting go after a hold.
      btnA.pressedEvent = false;
      btnA.releasedEvent = false;
      btnB.pressedEvent = false;
      btnB.releasedEvent = false;
    } else {
      return;
    }
  }

  // -------------------------
  //  Resume pending (wait for BOTH released)
  // -------------------------
  if (resumePending) {
    // Only resume when BOTH are fully released.
    if (!btnA.stablePressed && !btnB.stablePressed) {
      state = prevRunState;
      lastTickMs = nowMs; // prevents time "jump" after pause
      beepClick();

      resumePending = false;
      ignoreReleasesUntilBothUp = true; // blocks the release events from switching turns
    }
    return;
  }

  // -------------------------
  //  BOTH-button pause handling
  // -------------------------
  if (bothPressed) {
    if (bothPressStartMs == 0) {
      bothPressStartMs = nowMs;
      bothGestureUsed = false;
    }

    uint32_t heldMs = nowMs - bothPressStartMs;

    // One action per hold (prevents cycling).
    if (!bothGestureUsed && heldMs >= PAUSE_HOLD_MS) {
      if (state == STATE_RUN_A || state == STATE_RUN_B) {
        // Pause immediately while holding.
        prevRunState = state;
        state = STATE_PAUSED;
        beepClick();

        bothGestureUsed = true;
        ignoreReleasesUntilBothUp = true; // prevents player switch when releasing
        return;
      } else if (state == STATE_PAUSED) {
        // ARM resume now, but do not resume until BOTH are released.
        resumePending = true;

        // Beep NOW to tell the user "okay, resume is armed."
        beepArmed();

        bothGestureUsed = true;
        return;
      }
    }

    // While both are held, do not tick and do not allow turn switching.
    return;
  }

  // Not both pressed anymore: reset hold tracking for next time.
  bothPressStartMs = 0;
  bothGestureUsed = false;

  // -------------------------
  //  Normal state machine
  // -------------------------
  switch (state) {
    case STATE_SET: {
      applyPreset(readPresetIndexFromPot());

      // Start on RELEASE.
      if (btnA.releasedEvent && !btnB.stablePressed) {
        state = STATE_RUN_A;
        prevRunState = STATE_RUN_A;
        lastTickMs = nowMs;
        beepClick();
      } else if (btnB.releasedEvent && !btnA.stablePressed) {
        state = STATE_RUN_B;
        prevRunState = STATE_RUN_B;
        lastTickMs = nowMs;
        beepClick();
      }
    } break;

    case STATE_RUN_A:
    case STATE_RUN_B: {
      tickActiveClock(nowMs);

      if (state == STATE_RUN_A && btnA.releasedEvent && !btnB.stablePressed) {
        timeA_ms += (int32_t)INCREMENT_SECONDS * 1000;
        switchTurnTo(STATE_RUN_B);
        prevRunState = STATE_RUN_B;
        beepClick();
      } else if (state == STATE_RUN_B && btnB.releasedEvent && !btnA.stablePressed) {
        timeB_ms += (int32_t)INCREMENT_SECONDS * 1000;
        switchTurnTo(STATE_RUN_A);
        prevRunState = STATE_RUN_A;
        beepClick();
      }

      if (timeA_ms <= 0 || timeB_ms <= 0) {
        timeA_ms = max(timeA_ms, 0);
        timeB_ms = max(timeB_ms, 0);
        state = STATE_OVER;
        beepTimeUp();
      }
    } break;

    case STATE_PAUSED:
      // Resume only via BOTH-button hold (arm) + release BOTH.
      break;

    case STATE_OVER:
      // Use the board's reset button to restart.
      break;
  }
}

/**************************************************
 * Function: readPresetIndexFromPot
 * Purpose:  Maps the potentiometer reading to one of
 *           the preset time options.
 *
 * Returns:
 *    Index into PRESET_MINUTES array.
 **************************************************/
uint8_t readPresetIndexFromPot() {
  int raw = analogRead(POT_PIN);
  int binSize = 1024 / PRESET_COUNT;
  uint8_t idx = raw / binSize;
  if (idx >= PRESET_COUNT) idx = PRESET_COUNT - 1;
  return idx;
}

/**************************************************
 * Function: applyPreset
 * Purpose:  Sets both players' timers to the selected
 *           preset.
 *
 * Parameters:
 *    presetIndex - index in PRESET_MINUTES
 **************************************************/
void applyPreset(uint8_t presetIndex) {
  uint32_t minutes = PRESET_MINUTES[presetIndex];
  startTimeMs = minutes * 60UL * 1000UL;
  timeA_ms = (int32_t)startTimeMs;
  timeB_ms = (int32_t)startTimeMs;
}

/**************************************************
 * Function: tickActiveClock
 * Purpose:  Subtracts elapsed time from the active
 *           player using millis() (no delay()).
 **************************************************/
void tickActiveClock(uint32_t nowMs) {
  uint32_t dt = nowMs - lastTickMs;
  lastTickMs = nowMs;

  if (state == STATE_RUN_A) {
    timeA_ms -= (int32_t)dt;
  } else if (state == STATE_RUN_B) {
    timeB_ms -= (int32_t)dt;
  }
}

/**************************************************
 * Function: switchTurnTo
 * Purpose:  Switches which player is active and
 *           resets the tick timer so no time "jumps."
 **************************************************/
void switchTurnTo(ClockState nextState) {
  state = nextState;
  lastTickMs = millis();
}

/**************************************************
 * Function: renderUI
 * Purpose:  Draws the clock and instructions on the OLED.
 **************************************************/
void renderUI(uint32_t nowMs) {
  (void)nowMs;

  display.clearDisplay();

  display.setTextSize(1);
  display.setCursor(0, 0);

  if (state == STATE_SET) {
    display.print("Knob: time, 1/2 start");
  } else if (state == STATE_PAUSED) {
    if (resumePending) display.print("PAUSED");
    else              display.print("PAUSED");
  } else if (state == STATE_OVER) {
    display.print("TIME! Press board RESET");
  } else {
    display.print("RUN: Press SW1 or SW2");
  }

  bool aActive = (state == STATE_RUN_A);
  bool bActive = (state == STATE_RUN_B);

  drawTimeBox(0, 14, "A", timeA_ms, aActive);
  drawTimeBox(0, 40, "B", timeB_ms, bActive);

  display.display();
}

/**************************************************
 * Function: drawTimeBox
 * Purpose:  Draws one player's label and time.
 *           A small filled marker shows whose turn it is.
 **************************************************/
void drawTimeBox(int16_t x, int16_t y, const char* label, int32_t msRemaining, bool isActive) {
  display.drawRect(x, y, 128, 22, SSD1306_WHITE);

  if (isActive) display.fillRect(x + 2, y + 2, 6, 6, SSD1306_WHITE);
  else          display.drawRect(x + 2, y + 2, 6, 6, SSD1306_WHITE);

  display.setTextSize(1);
  display.setCursor(x + 12, y + 3);
  display.print("P");
  display.print(label);

  char buf[12];
  formatTime(msRemaining, buf, sizeof(buf));

  display.setTextSize(2);
  display.setCursor(x + 52, y + 5);
  display.print(buf);
}

/**************************************************
 * Function: formatTime
 * Purpose:  Converts milliseconds to M:SS format.
 **************************************************/
void formatTime(int32_t msRemaining, char* out, size_t outSize) {
  if (msRemaining < 0) msRemaining = 0;

  uint32_t totalSec = (uint32_t)(msRemaining / 1000);
  uint16_t minutes = totalSec / 60;
  uint8_t seconds  = totalSec % 60;

  snprintf(out, outSize, "%u:%02u", minutes, seconds);
}

/**************************************************
 * Function: updateEyes
 * Purpose:  Shows the active player and state using
 *           the NeoPixel eyes.
 **************************************************/
void updateEyes() {
  uint8_t off  = 0;
  uint8_t dim  = 20;
  uint8_t on   = 80;
  uint8_t over = 120;

  auto setEye = [&](uint8_t idx, uint8_t r, uint8_t g, uint8_t b) {
    pixel.setPixelColor(idx, pixel.Color(r, g, b));
  };

  if (state == STATE_RUN_A) {
    setEye(0, off, on, off);
    setEye(1, off, off, off);
  } else if (state == STATE_RUN_B) {
    setEye(0, off, off, off);
    setEye(1, off, on, off);
  } else if (state == STATE_PAUSED) {
    setEye(0, dim, dim, dim);
    setEye(1, dim, dim, dim);
  } else if (state == STATE_OVER) {
    setEye(0, over, off, off);
    setEye(1, over, off, off);
  } else { // STATE_SET
    setEye(0, off, off, dim);
    setEye(1, off, off, dim);
  }

  pixel.show();
}

/**************************************************
 * Function: beep
 * Purpose:  Plays a tone on the piezo for feedback.
 **************************************************/
void beep(uint16_t hz, uint16_t durationMs) {
  tone(PIEZO, hz, durationMs);
}

/**************************************************
 * Function: beepClick
 * Purpose:  Short click for common button actions.
 **************************************************/
void beepClick() {
  beep(BEEP_HZ, BEEP_SHORT_MS);
}

/**************************************************
 * Function: beepArmed
 * Purpose:  A slightly longer beep so the user knows
 *           the resume action is armed.
 **************************************************/
void beepArmed() {
  beep(BEEP_HZ, BEEP_ARM_MS);
}

/**************************************************
 * Function: beepTimeUp
 * Purpose:  Simple alert when time runs out.
 **************************************************/
void beepTimeUp() {
  beep(900, 160);
  delay(180);
  beep(700, 240);
}
				
			

*If you’re copying and pasting the code, or typing from scratch, delete everything out of a new Arduino sketch and paste / type in the above text.

成品人和精品人的区别在哪里 More In Our Free Instructional Guidebook

This comprehensive guidebook for the 成品人和精品人的区别在哪里 开心四房播播 Trainer provides a comprehensive introduction to the world of Arduino programming for beginners. It guides users through the foundational concepts of 开心四房播播s, detailing the unique features of the Arduino Leonardo-compatible MCU Trainer board. The manual offers a step-by-step journey from understanding the hardware components and the Arduino programming language to the vibrant global community of Arduino enthusiasts. It delves into the intricacies of each onboard circuit, explaining their functionalities and applications. With a focus on hands-on 成品人和精品人的区别在哪里ing, the manual includes a series of coding exercises, tutorials in C/C++, and insights into the Arduino IDE.

More Projects

Project 1.00 Blink

In this project, you’ll 成品人和精品人的区别在哪里 how to blink an LED!

Project 1.01 Blink x2

In this project, you’ll 成品人和精品人的区别在哪里 how to blink more than one LED!