山村春色al Project 5.00 Countdown

The Frog Countdown Timer lesson teaches students to program a Frog board (Arduino-based) to work as a countdown timer using buttons and a buzzer. Students 近亲交尾 event-driven programming, button input handling, and state-machine logic while exploring how embedded devices manage timing and user interactions.

The program is an event-driven countdown timer that uses button inputs to control different modes of operation. It is organized as a state machine with three main modes: setting the time, running the countdown, and triggering an alarm when time reaches zero.

Project Code:

				
					/*
  ----------------------------------------------------------
  Frog Countdown Timer
  ----------------------------------------------------------
  Behavior
    • Boot screen: title "Timer" + instructions.
    • Press ANY button → enter Set screen showing big "MM:SS".
    • RIGHT (hold): +1 s at 2 steps/sec; after 5 s hold → 20 steps/sec.
    • LEFT  (hold): −1 s at 2 steps/sec; after 5 s hold → 20 steps/sec.
    • Release all; then press BOTH (within grace window) → start countdown.
    • At 00:00 → piezo beeps with increasing urgency for ~5 s.
    • After alarm → return to big "00:00" and wait for adjustments.

  Educational Notes
    • Demonstrates event-driven programming and timing with millis().
    • Models UI state-machine design using an enum for program modes.
    • Good example of input debouncing and multi-button logic.

  Hardware (Arduino UNO R4 based Frog board)
    • OLED (SSD1306 128x64) via I2C
    • LEFT button  = BUTTON_ONE (pin 1)   [active-LOW]
    • RIGHT button = BUTTON_TWO (pin 0)   [active-LOW]
    • Piezo buzzer = PIEZO (pin 12)
*/

#include "Arduino.h"
#include "1ST_Maker_Frog.h"   // provides display, pixel, and pin constants

// ---------- Constants & Types -------------------------------------------------

const uint8_t LEFT_BTN  = BUTTON_ONE;   // more readable aliases
const uint8_t RIGHT_BTN = BUTTON_TWO;   // than numeric pin numbers

// Step-speed behavior for hold actions
const uint32_t HOLD_ACCEL_MS   = 2500UL;  // after 2.5 s, accelerate
const uint32_t STEP_SLOW_MS    = 500UL;   // 2 steps per second
const uint32_t STEP_FAST_MS    = 50UL;    // 20 steps per second

// Small delay window for detecting “both” presses
const uint16_t BOTH_GRACE_MS   = 125;     // feel-tested; adjust in milliseconds as needed

// Display and alarm constants
const uint8_t  BIG_TEXT_SIZE   = 4;
const uint8_t  TITLE_TEXT_SIZE = 2;
const uint32_t ALARM_TOTAL_MS  = 5000UL;  // run alarm for 5 s
const uint16_t ALARM_ON_MS     = 60;      // beep on duration
const uint16_t ALARM_OFF_MS    = 40;      // pause between beeps

// Program mode state machine
enum Mode { MODE_WELCOME, MODE_SET, MODE_COUNTDOWN, MODE_ALARM };
Mode mode = MODE_WELCOME;                 // start in welcome screen

// ---------- Globals -----------------------------------------------------------

volatile long totalSeconds = 0;           // countdown value in seconds
bool     seenAllReleased   = false;       // used to detect a fresh “both” press

// bookkeeping for auto-repeat behavior
bool     leftWasDown  = false;
bool     rightWasDown = false;
uint32_t leftDownAtMs = 0;
uint32_t rightDownAtMs= 0;
uint32_t nextStepLeftMs  = 0;
uint32_t nextStepRightMs = 0;

// countdown tracking
uint32_t lastTickMs = 0;

// tracking for both-button grace
bool     bothGraceArmed   = false;
uint32_t bothGraceStartMs = 0;

// ============================================================================
//  Button helper functions – read current button state
// ============================================================================
inline bool btnLeftDown()  { return digitalRead(LEFT_BTN)  == PRESSED; }
inline bool btnRightDown() { return digitalRead(RIGHT_BTN) == PRESSED; }
inline bool bothDown()     { return btnLeftDown() && btnRightDown(); }
inline bool noneDown()     { return !btnLeftDown() && !btnRightDown(); }

// ============================================================================
//  drawCenteredBigTime
//  Show current time in large “MM:SS” format centered on display.
// ============================================================================
void drawCenteredBigTime(long seconds)
{
  // Keep value within bounds 0–5999 s (99 min 59 s)
  seconds = constrain(seconds, 0, 5999);
  uint8_t mm = seconds / 60;
  uint8_t ss = seconds % 60;

  char buf[6];
  snprintf(buf, sizeof(buf), "%02u:%02u", mm, ss);

  // Clear screen and draw centered
  display.clearDisplay();
  display.setTextSize(BIG_TEXT_SIZE);
  display.setTextColor(SSD1306_WHITE);

  // crude centering math based on font size
  int16_t x = (SCREEN_WIDTH  - (6 * BIG_TEXT_SIZE * 5)) / 2;
  int16_t y = (SCREEN_HEIGHT - (8 * BIG_TEXT_SIZE)) / 2;
  display.setCursor(x, y);
  display.print(buf);
  display.display();
}

// ============================================================================
//  drawWelcome
//  Display the splash / instruction screen at startup.
// ============================================================================
void drawWelcome()
{
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);

  display.setTextSize(TITLE_TEXT_SIZE);
  display.setCursor(0, 0);
  display.println(F("   Timer"));

  display.setTextSize(1);
  display.println();
  display.println(F("    Right: + time"));
  display.println(F("    Left : - time"));
  display.println(F("    Both : start"));
  display.println();
  display.println(F("   Press any button"));
  display.display();
}

// ============================================================================
//  drawSetScreen
//  Wrapper to refresh the current time display.
// ============================================================================
void drawSetScreen() { drawCenteredBigTime(totalSeconds); }

// ============================================================================
//  handleSetButton
//  Increment or decrement time when a button is held down.
// ============================================================================
void handleSetButton(uint8_t pin, bool &wasDown, uint32_t &downAtMs,
                     uint32_t &nextStepMs, int stepDir)
{
  const bool isDown = (digitalRead(pin) == PRESSED);
  const uint32_t now = millis();

  // detect edges of press/release
  if (isDown && !wasDown) {              // just pressed
    wasDown   = true;
    downAtMs  = now;
    nextStepMs= now;                     // first change happens instantly
  } else if (!isDown && wasDown) {       // just released
    wasDown = false;
  }

  // if still holding, handle auto-repeat
  if (isDown) {
    const uint32_t heldFor = now - downAtMs;
    const uint32_t cadence = (heldFor >= HOLD_ACCEL_MS)
                             ? STEP_FAST_MS : STEP_SLOW_MS;

    // time for another increment/decrement?
    if (now >= nextStepMs) {
      long candidate = totalSeconds + stepDir;   // +1 s or −1 s
      candidate = constrain(candidate, 0, 5999L);
      if (candidate != totalSeconds) {
        totalSeconds = candidate;
        drawSetScreen();                         // update display immediately
      }
      nextStepMs += cadence;                     // schedule next step
    }
  }
}

// ============================================================================
//  runAlarm
//  Make buzzer chirp and NeoPixel eyes flash for five seconds.
// ============================================================================
void runAlarm()
{
  const uint32_t startMs = millis();
  uint32_t now = startMs;

  pixel.begin();      // ensure NeoPixels initialized
  pixel.clear();
  pixel.show();

  while ((now = millis()) - startMs < ALARM_TOTAL_MS) {
    uint32_t elapsed = now - startMs;

    // Map 0→5000 ms to frequency range 220→1200 Hz for rising pitch
    uint16_t freq = 220 + (elapsed * 1000UL) / ALARM_TOTAL_MS;

    // shorten off-gap slightly as time passes (feels more urgent)
    uint16_t offGap = ALARM_OFF_MS - (elapsed * 20UL) / ALARM_TOTAL_MS;

    tone(PIEZO, freq);           // start tone
    delay(ALARM_ON_MS);          // short beep
    noTone(PIEZO);               // silence

    // Dim red eye blink proportional to elapsed time
    uint8_t level = map(elapsed, 0, ALARM_TOTAL_MS, 16, 64);
    pixel.setPixelColor(0, pixel.Color(level, 0, 0));
    pixel.setPixelColor(1, pixel.Color(level, 0, 0));
    pixel.show();

    delay(offGap);               // pause before next beep
  }

  // tidy up
  noTone(PIEZO);
  pixel.clear();
  pixel.show();
}

// ============================================================================
//  setup
//  Initialize hardware and show the welcome screen.
// ============================================================================
void setup()
{
  pinMode(LEFT_BTN,  INPUT_PULLUP);   // active-LOW buttons
  pinMode(RIGHT_BTN, INPUT_PULLUP);
  pinMode(PIEZO,     OUTPUT);

  // initialize OLED display; fail-soft if absent
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    // Could flash NeoPixels here to signal failure
  }

  display.clearDisplay();
  drawWelcome();
  mode = MODE_WELCOME;
}

// ============================================================================
//  loop
//  Main state-machine loop. Handles grace-timing for both buttons.
// ============================================================================
void loop()
{
  const uint32_t now = millis();

  switch (mode) {

    // --------------------------------------------------------
    // WELCOME SCREEN
    // --------------------------------------------------------
    case MODE_WELCOME:
      if (btnLeftDown() || btnRightDown()) {
        // any button → enter set mode
        totalSeconds = constrain(totalSeconds, 0, 5999);
        drawSetScreen();
        mode = MODE_SET;

        // reset all state trackers
        seenAllReleased = false;
        leftWasDown = rightWasDown = false;
        bothGraceArmed = false;
      }
      break;

    // --------------------------------------------------------
    // SET MODE: adjusting countdown value
    // --------------------------------------------------------
    case MODE_SET:
      // if both released, we can arm a new grace window
      if (noneDown()) {
        seenAllReleased = true;
        bothGraceArmed = false;
      }

      // first button press after full release → arm grace window
      if (seenAllReleased && (btnLeftDown() || btnRightDown()) && !bothGraceArmed) {
        bothGraceArmed   = true;
        bothGraceStartMs = now;
      }

      // If within the grace window, wait to see if the other button joins in
      if (bothGraceArmed) {
        if (bothDown()) {
          // Treat as BOTH — start countdown without changing time
          if (totalSeconds > 0) {
            lastTickMs = now;
            mode = MODE_COUNTDOWN;
          }
          // lock state until buttons released again
          seenAllReleased = false;
          bothGraceArmed = false;
          break; // skip rest of logic
        }

        // Still inside grace window → do nothing yet
        if ((now - bothGraceStartMs) < BOTH_GRACE_MS)
          break;

        // grace expired → resume normal single-button behavior
        bothGraceArmed = false;
      }

      // Normal adjustment after grace window
      handleSetButton(LEFT_BTN,  leftWasDown,  leftDownAtMs,  nextStepLeftMs,  -1);
      handleSetButton(RIGHT_BTN, rightWasDown, rightDownAtMs, nextStepRightMs, +1);
      break;

    // --------------------------------------------------------
    // COUNTDOWN MODE
    // --------------------------------------------------------
    case MODE_COUNTDOWN:
      // Decrement once per second using millis() instead of delay()
      if (now - lastTickMs >= 1000UL) {
        lastTickMs += 1000UL;

        if (totalSeconds > 0) {
          totalSeconds--;
          drawCenteredBigTime(totalSeconds);     // refresh display
        }
        if (totalSeconds == 0) mode = MODE_ALARM;
      }
      break;

    // --------------------------------------------------------
    // ALARM MODE
    // --------------------------------------------------------
    case MODE_ALARM:
      runAlarm();                                // play sound and light
      totalSeconds = 0;
      drawSetScreen();                           // return to “00:00”
      mode = MODE_SET;                           // back to set mode
      seenAllReleased = false;
      leftWasDown = rightWasDown = false;
      bothGraceArmed = false;
      break;
  }
}
				
			

*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!