🐻

残り時間が目盛りでわかるタイマーをLCD1602で作る

に公開

やりたいこと

キングジムから目盛りで残り時間が分かるタイマーというのが出ていました。良さそうなので自分の手持ちで作ってみました。

https://www.kingjim.co.jp/sp/vbt10/

できたもの

  • LCD1602AとArduino互換品で動いている
  • 25分と5分の設定で、1分経つごとに目盛りを減らす
  • 0分台のとき1目盛り残るので、例えば22分台では23個残っている
  • ごちゃごちゃしているのが使いにくそうですね。まぁ試作ということで・・・。

コード

(ChatGPT5.1)

コード
#include <LiquidCrystal.h>

// RS, E, D4, D5, D6, D7
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

// ---------- Timer settings (minutes) ----------
const unsigned int phase1Minutes = 25;  // Phase 1: work time (minutes)
const unsigned int phase2Minutes = 5;   // Phase 2: break time (minutes)

// convert to seconds
const unsigned long phase1Seconds = phase1Minutes * 60UL;
const unsigned long phase2Seconds = phase2Minutes * 60UL;

// convert to milliseconds
const unsigned long phase1Millis = phase1Seconds * 1000UL;
const unsigned long phase2Millis = phase2Seconds * 1000UL;

// ---------- Phase 1 cells (25 cells) ----------
const int upperCells = 9;   // top line cells
const int lowerCells = 16;  // bottom line cells
const int totalCellsPhase1 = upperCells + lowerCells;  // 25

// ---------- Phase 2 cells (5 diamonds) ----------
const int diamondsCount = 5;

// ---------- Phase control ----------
enum Phase {
  PHASE1,
  PHASE2,
  DONE
};

Phase currentPhase = PHASE1;
unsigned long phaseStartMillis = 0;

// custom diamond character (index 0)
byte diamondChar[8] = {
  B00100,
  B01110,
  B11111,
  B11111,
  B11111,
  B01110,
  B00100,
  B00000
};

void setup() {
  lcd.begin(16, 2);
  delay(100);
  lcd.clear();

  lcd.createChar(0, diamondChar);  // register diamond as char 0

  currentPhase = PHASE1;
  phaseStartMillis = millis();
}

void loop() {
  unsigned long now = millis();

  switch (currentPhase) {
    case PHASE1:
      handlePhase1(now);
      break;
    case PHASE2:
      handlePhase2(now);
      break;
    case DONE:
      // nothing to do
      break;
  }
}

// ---------- Phase 1: 25 minutes, 25 cells bar ----------
void handlePhase1(unsigned long now) {
  unsigned long elapsed = now - phaseStartMillis;

  if (elapsed >= phase1Millis) {
    // ensure final view, then move to phase 2
    updatePhase1Display(0);
    lcd.clear();
    currentPhase = PHASE2;
    phaseStartMillis = now;
    return;
  }

  unsigned long remainingMillis = phase1Millis - elapsed;
  updatePhase1Display(remainingMillis);
}

void updatePhase1Display(unsigned long remainingMillis) {
  // time (mm:ss), right aligned
  unsigned long remainingSeconds = (remainingMillis + 500) / 1000;
  unsigned int minutes = remainingSeconds / 60;
  unsigned int seconds = remainingSeconds % 60;

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

  // cleared cells: 1 cell per minute
  unsigned long elapsedMillis = phase1Millis - remainingMillis;
  int cleared = elapsedMillis / 60000UL;  // 60000 ms = 1 minute
  if (cleared > totalCellsPhase1) {
    cleared = totalCellsPhase1;
  }

  // ----- upper line: 9 cells (right -> left) -----
  for (int i = 0; i < upperCells; i++) {
    int cellIndex = upperCells - 1 - i;  // rightmost cellIndex = 0
    lcd.setCursor(i, 0);
    if (cleared > cellIndex) {
      lcd.print(' ');
    } else {
      lcd.write((uint8_t)255);  // filled block
    }
  }

  // clear right side area of upper line for time
  lcd.setCursor(upperCells, 0);
  lcd.print("       ");  // 7 spaces (9 + 7 = 16)

  // ----- lower line: 16 cells (right -> left) -----
  for (int i = 0; i < lowerCells; i++) {
    int cellIndex = upperCells + (lowerCells - 1 - i);  // 16..24
    lcd.setCursor(i, 1);
    if (cleared > cellIndex) {
      lcd.print(' ');
    } else {
      lcd.write((uint8_t)255);  // filled block
    }
  }

  // clear any extra area on lower line (safety)
  lcd.setCursor(lowerCells, 1);
  lcd.print("        ");

  // ----- print time, right aligned on upper line -----
  int textLen = 5;              // "mm:ss"
  int startCol = 16 - textLen;  // 11
  lcd.setCursor(startCol, 0);
  lcd.print(buf);
}

// ---------- Phase 2: 5 minutes, 5 diamonds ----------
void handlePhase2(unsigned long now) {
  unsigned long elapsed = now - phaseStartMillis;

  if (elapsed >= phase2Millis) {
    updatePhase2Display(0);
    currentPhase = DONE;
    return;
  }

  unsigned long remainingMillis = phase2Millis - elapsed;
  updatePhase2Display(remainingMillis);
}

void updatePhase2Display(unsigned long remainingMillis) {
  // time (mm:ss), right aligned
  unsigned long remainingSeconds = (remainingMillis + 500) / 1000;
  unsigned int minutes = remainingSeconds / 60;
  unsigned int seconds = remainingSeconds % 60;

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

  // clear upper line
  lcd.setCursor(0, 0);
  lcd.print("                ");

  // print time on upper line (right aligned)
  int textLen = 5;
  int startCol = 16 - textLen;  // 11
  lcd.setCursor(startCol, 0);
  lcd.print(buf);

  // cleared diamonds: 1 per minute
  unsigned long elapsedMillis = phase2Millis - remainingMillis;
  int cleared = elapsedMillis / 60000UL;  // 1 diamond / minute
  if (cleared > diamondsCount) {
    cleared = diamondsCount;
  }

  // lower line: 5 diamonds, disappear from right to left
  for (int i = 0; i < diamondsCount; i++) {
    int cellIndex = diamondsCount - 1 - i;  // rightmost cellIndex = 0
    lcd.setCursor(i, 1);
    if (cleared > cellIndex) {
      lcd.print(' ');
    } else {
      lcd.write((uint8_t)0);  // custom diamond char
    }
  }

  // clear the rest of the lower line
  lcd.setCursor(diamondsCount, 1);
  lcd.print("               ");
}

まとめ

  • 動くものが作れて満足。なんでもできる
  • (おとなしくプロダクトを買ったほうが使いやすい)

参考

配線の仕方を忘れていたのでこちらを参考にしていました。(※このコードとは番号が異なる)

https://workshop.aaa-plaza.net/archives/1181

Discussion