Arduinoで作る本格キッチンタイマー:LCD表示・4ボタン操作・ブザー搭載
必要な部品と材料
| Arduino UNO | ×1 |
| Adafruit 16x2 LCD(白文字/青背景) | ×1 |
| タクトスイッチ 12mm | ×4 |
| 抵抗 10kΩ(プルダウン用) | ×4 |
| 抵抗 220Ω(ブザー/LED用) | ×2 |
| ポテンショメータ 100kΩ(LCDコントラスト調整) | ×1 |
| ブザー(圧電スピーカー) | ×1 |
| ブレッドボード & ジャンパーワイヤー | 各1 |
プロジェクト概要
市販の100円キッチンタイマーを自作し、その仕組みを理解しようというプロジェクトです。Arduino Starter Kitの部品だけで構築でき、電源はPCのUSBポートで動作します。
動作モード(4つのステート)
タイマーは常に以下のいずれか1つのモードで動作します。
- IDLE(待機):設定時刻を表示し、入力待ち。起動時・リセット後の初期状態。
- SETUP(設定):リセットボタン(S4)長押しで突入。スタート/ストップボタン(S3)で「時/分/秒」を選択、アップ/ダウンボタン(S1/S2)で値を変更。
- RUNNING(カウントダウン中):スタートボタン(S3)押下で開始。再度S3、またはリセット(S4)でIDLEへ戻る。
- RINGING(アラーム鳴動):設定時刻経過で自動突入。任意のボタン押下で停止しIDLEへ。
ハードウェア構成のポイント
- 各ボタンは10kΩ抵抗でプルダウン、押下で5Vに接続(LOW→HIGHで検知)
- リセットボタン(S4)のみ「長押し検知」ロジックを別途実装(カウンタで約1秒判定)
- LCDは標準的なHitachi HD44780互換、6ピン制御(RS, EN, D4-D7)
- ブザーはPWM出力(pin 10)で音量調整可能
ソフトウェア構成
ライブラリ
LiquidCrystal.h(標準付属)TimeLib.h(Paul Stoffregen氏作、GitHubからZIPインストール)
主要な処理フロー(loop内)
- ボタン状態読み取り:前回値と比較し、変化があれば「押下フラグ」を立てる
- モード遷移(switch文):現在モードとボタンフラグに基づき次のモードへ
- 時刻計算:RUNNINGモードでは
now() - startTimeで経過時間を求め、設定時間を超えたらRINGINGへ - LCD表示更新:モードごとに適切な文字列・数値を所定位置へ出力
- ブザー制御:RINGINGモードでPWMオン/オフを繰り返しビープ音生成
コード全文(ArduinoSketch v1.0)
/**************************************************
* Arduino Kitchen Timer v1.0 - 2016/01/27
* By Angelo Fiorillo (Rome, IT) - GPL3+
**************************************************/
#include <LiquidCrystal.h>
#include <TimeLib.h> // now()関数のため必須
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int buzzerPin = 10;
const int resetButtonPin = 6;
const int startStopButtonPin = 7;
const int downButtonPin = 8;
const int upButtonPin = 9;
// 設定値(セットアップモードで変更)
int setupHours = 0, setupMinutes = 0, setupSeconds = 0;
time_t setupTime = 0;
// 現在値(カウントダウン中)
int currentHours = 0, currentMinutes = 0, currentSeconds = 0;
time_t currentTime = 0, startTime = 0, elapsedTime = 0;
// ボタン状態管理変数群
int resetButtonState = LOW, startStopButtonState = LOW, upButtonState = LOW, downButtonState = LOW;
int resetButtonPrevState = LOW, startStopButtonPrevState = LOW, upButtonPrevState = LOW, downButtonPrevState = LOW;
bool resetButtonPressed = false, resetButtonLongPressed = false;
bool startStopButtonPressed = false, upButtonPressed = false, downButtonPressed = false;
long resetButtonLongPressCounter = 0;
// モード定義
const int MODE_IDLE = 0, MODE_SETUP = 1, MODE_RUNNING = 2, MODE_RINGING = 3;
int currentMode = MODE_IDLE;
int dataSelection = 0; // 設定モードでの選択項目:0=時, 1=分, 2=秒
void setup() {
lcd.begin(16, 2);
pinMode(resetButtonPin, INPUT);
pinMode(startStopButtonPin, INPUT);
pinMode(upButtonPin, INPUT);
pinMode(downButtonPin, INPUT);
pinMode(buzzerPin, OUTPUT);
Serial.begin(9600);
}
void loop() {
// --- ボタン読み取り & エッジ検出 ---
resetButtonPressed = false; resetButtonLongPressed = false;
resetButtonState = digitalRead(resetButtonPin);
if (resetButtonState != resetButtonPrevState) {
resetButtonPressed = (resetButtonState == HIGH);
resetButtonPrevState = resetButtonState;
} else {
// 長押し判定
if (resetButtonState == HIGH) {
if (++resetButtonLongPressCounter == 100) {
resetButtonPressed = false;
resetButtonLongPressed = true;
resetButtonLongPressCounter = 0;
}
} else {
resetButtonLongPressCounter = 0;
}
}
startStopButtonPressed = false;
startStopButtonState = digitalRead(startStopButtonPin);
if (startStopButtonState != startStopButtonPrevState) {
startStopButtonPressed = (startStopButtonState == HIGH);
startStopButtonPrevState = startStopButtonState;
}
downButtonPressed = false;
downButtonState = digitalRead(downButtonPin);
if (downButtonState != downButtonPrevState) {
downButtonPressed = (downButtonState == HIGH);
downButtonPrevState = downButtonState;
}
upButtonPressed = false;
upButtonState = digitalRead(upButtonPin);
if (upButtonState != upButtonPrevState) {
upButtonPressed = (upButtonState == HIGH);
upButtonPrevState = upButtonState;
}
// --- モード遷移 ---
switch (currentMode) {
case MODE_IDLE:
if (resetButtonPressed) Reset();
if (resetButtonLongPressed) currentMode = MODE_SETUP;
if (startStopButtonPressed) {
currentMode = (currentMode == MODE_IDLE) ? MODE_RUNNING : MODE_IDLE;
if (currentMode == MODE_RUNNING) startTime = now();
}
break;
case MODE_SETUP:
if (resetButtonPressed) { // 設定確定・終了
setupTime = setupSeconds + 60L * setupMinutes + 3600L * setupHours;
currentHours = setupHours; currentMinutes = setupMinutes; currentSeconds = setupSeconds;
dataSelection = 0; currentMode = MODE_IDLE;
}
if (startStopButtonPressed) { dataSelection = (dataSelection + 1) % 3; }
if (downButtonPressed) { // 値デクリメント(ロールオーバーあり)
switch (dataSelection) {
case 0: if (--setupHours < 0) setupHours = 99; break;
case 1: if (--setupMinutes < 0) setupMinutes = 59; break;
case 2: if (--setupSeconds < 0) setupSeconds = 59; break;
}
}
if (upButtonPressed) { // 値インクリメント
switch (dataSelection) {
case 0: if (++setupHours == 100) setupHours = 0; break;
case 1: if (++setupMinutes == 60) setupMinutes = 0; break;
case 2: if (++setupSeconds == 60) setupSeconds = 0; break;
}
}
break;
case MODE_RUNNING:
if (startStopButtonPressed || resetButtonPressed) {
currentMode = MODE_IDLE;
if (resetButtonPressed) Reset();
}
break;
case MODE_RINGING:
if (resetButtonPressed || startStopButtonPressed || downButtonPressed || upButtonPressed)
currentMode = MODE_IDLE;
break;
}
// --- 時刻計算 & ブザー ---
switch (currentMode) {
case MODE_RUNNING:
currentTime = setupTime - (now() - startTime);
if (currentTime <= 0) currentMode = MODE_RINGING;
break;
case MODE_RINGING:
analogWrite(buzzerPin, 20); delay(20);
analogWrite(buzzerPin, 0); delay(40);
break;
}
// --- LCD表示 ---
lcd.setCursor(0, 0);
switch (currentMode) {
case MODE_IDLE:
lcd.print("Timer ready ");
lcd.setCursor(0, 1);
lcd.print(currentHours); lcd.print(" ");
lcd.print(currentMinutes); lcd.print(" ");
lcd.print(currentSeconds); lcd.print(" ");
break;
case MODE_SETUP:
lcd.print("Setup mode: ");
lcd.print(dataSelection == 0 ? "HRS " : dataSelection == 1 ? "MINS" : "SECS");
lcd.setCursor(0, 1);
lcd.print(setupHours); lcd.print(" ");
lcd.print(setupMinutes); lcd.print(" ");
lcd.print(setupSeconds); lcd.print(" ");
break;
case MODE_RUNNING:
lcd.print("Counting down...");
lcd.setCursor(0, 1);
if (hour(currentTime) < 10) lcd.print("0");
lcd.print(hour(currentTime)); lcd.print(":");
if (minute(currentTime) < 10) lcd.print("0");
lcd.print(minute(currentTime)); lcd.print(":");
if (second(currentTime) < 10) lcd.print("0");
lcd.print(second(currentTime));
break;
case MODE_RINGING:
lcd.print(" RING-RING! ");
lcd.setCursor(0, 1); lcd.print(" ");
break;
}
delay(10);
}
void Reset() {
currentMode = MODE_IDLE;
currentHours = setupHours; currentMinutes = setupMinutes; currentSeconds = setupSeconds;
}
配線図・回路図
ブレッドボード上の配線はフリッツィング図を参照してください。Starter Kit付属の小型ブレッドボードでも収まります(ブザーを黄色ワイヤーの下に配置)。
まとめ
ステートマシン(状態遷移)の基本を学ぶのに最適なプロジェクトです。ボタンのエッジ検出、長押し判定、TimeLibを使った時刻計算、LCDの動的表示など、Arduinoプログラミングの重要なテクニックが詰まっています。自分専用のタイマーを作って、キッチンだけでなく作業時間管理などにも活用してみてください。
製造プロセス
- スマート渇きアラート植物アラーム – 最適な水やりタイミングを知らせるDIYセンサー
- Arduino Nanoで20kHzオシロスコープを自作:Nokia 5110 LCD搭載・簡易DIYガイド
- スマートブラインド:Arduinoとソーラー電源で実現する自動光制御
- イタリア語ワードクロック自作:Arduino Nano、NeoPixel、LCD、RTCで時刻を文字で表示
- SparkFun 7セグメントディスプレイで作る信頼性の高いArduinoカウントダウンタイマー
- Arduino MKR Fox 1200で作る信頼性の高いSigfox kWhメーター
- Bluetooth対応温度モニター:Arduino NanoとHC-06でスマホ監視
- 非接触ジェスチャー錠: APDS-9960とArduinoでセキュアアクセス
- Arduino Nanoコンパニオンキット – 電子工作に必須の部品とツールを搭載した多機能IC型デバイス
- Arduino用絶縁アナログ入力回路の設計:LM331 V-F変換+フォトカプラでノイズ耐性を確保
- Arduino UNOで作る精密反応速度テスター