ポモドーロタイマーを作る

ポモドーロタイマーを作りました。

ポモドーロタイマーとは、ポモドーロテクニックによる時間管理術のためのタイマーで、あ〜、詳しいことはググって下さい (^_^;)

ポモドーロタイマー

ポモドーロタイマーは、Chrome の拡張機能とかスマホのアプリとかで使っていて、それぞれ便利です。が、パソコン使ってないとダメ、とか、スマホの電池がぁ〜、とか、まぁそんなもの。
キッチンタイマーとか使うこともできるわけですが、いちいち設定するのは面倒くさい。なら、てめーの都合の良いように作っちゃえ、ってことで、巣籠り工作気分のガジェットです。

写真が完成品。秋月電子通商にあったプラスチックケースに入れました。基板は毎度のユニバーサル基板、ケースに合わせての C 基板です。裏側に Arduino NANO が付いています。
電源は単 3 電池 4 本。USB からでも給電できます。電池がどのくらいもつかわからないのですが、毎日使って 2~3 週間ぐらいもってくれたら嬉しいかな、と。

7 セグメント LED は部品箱にあったアノードコモン 1 桁のものを 2 個並べました。ケースと基板、タクトスイッチ、ピエゾスピーカーなどは今回購入しましたが、あとは部品箱にあった部品です。

回路図

回路図です。

7 セグメント LED をダイナミック点灯させるなど、これまでにやったことのあることばかりですね。強いて言えば、電池駆動とピエゾスピーカーが俺は初めてだけど、難しいことではありません。

ポモドーロタイマー回路図

電池は Arduino の Vin につなぎます。電圧が 4V ぐらいに下がっても、Arduino の定格外ですが、問題なく動きました。

7 セグメント LED はアノードコモンですので、セグメントをつなぐデジタルピンはシンクで動きます。セグメントの順方向電圧は 1.7V 、電流は 7mA です。
デジット電流は 49mA になりますから、デジタルピンをそのまま使うことはできません。デジット制御は PNP トランジスタ 2SA1015 を使います。ベース電流は 0.9mA です。

タイマーのスタートとストップにタクトスイッチを使っています。割り込み処理も利用できるように、スイッチは D2 、D3 につなぎ、内部プルアップしています。

表示用の LED には 0.9mA 流しています。少ない電流ですが、表示用としては十分な明るさ。もう少し落としたほうが良かったかなと思うくらいです。A3 、A4 ピンにつないでいますが、デジタル出力として使用します。

ピエゾスピーカーも A5 ピンにつないでいますが、デジタル出力として使います。
ピエゾスピーカーを Arduino に直接つないでいる回路を見かけますが、ピエゾスピーカーは指で弾いただけでも 10V 以上を発電しますので、IC など壊してしまいます。データシートに書かれているように 1~2KΩ の抵抗を入れておきましょう。

スケッチ

Arduino NANO のスケッチです。UNO でもいけると思います。

7 セグメント LED の制御に関して「Arduinoで遊ぶページ」を参考にさせていただきました。いつもありがとうございます。

頭から順に、簡単に、備忘録的解説していきます。

  1. /*
  2.   Pomodoro Timer v.1.5b 2021.02.22 meyon
  3.  */
  4. #include <avr/sleep.h>
  5. #include <TimerOne.h>
  6. #include "pitches.h"

電池駆動しますので使わないときにスリープさせるための avr/sleep.h 、ダイナミック点灯の割込みに使う TimerOne.h 、音を出すためにスケッチ例 toneMelody の pitches.h をインクルードしています。

  1. #define DIGIT_ON LOW
  2. #define DIGIT_OFF HIGH
  3. #define SEGMENT_ON LOW
  4. #define SEGMENT_OFF HIGH
  5. #define BUTTON_ON LOW
  6. #define BUTTON_OFF HIGH
  7. #define LED_ON HIGH
  8. #define LED_OFF LOW

デジタルピンをシンクやソース、あるいはプルアップ、プルダウンで使うとき、HIGH 、LOW で表記するとわけわからなくなるので、それぞれ意味のある表記に定義しています。

  1. const int digitPins[] = {11, 12};
  2. const int segmentPins[] = {4, 5, 6, 7, 8, 9, 10};
  3. const int buttonPins[] = {2, 3};
  4. const int ledPins[] = {17, 18};
  5. const int piezoPin = 19;
  6. const int periodsTable[] = {25, 5, 25, 5, 25, 15}; // {FORCUS, REST, ...}
  7. const int numberOfDigitPins = sizeof(digitPins) / sizeof(digitPins[0]);
  8. const int numberOfSegmentPins = sizeof(segmentPins) / sizeof(segmentPins[0]);
  9. const int numberOfButtonPins = sizeof(buttonPins) / sizeof(buttonPins[0]);
  10. const int numberOfLedPins = sizeof(ledPins) / sizeof(ledPins[0]);
  11. const int numberOfPeriods = sizeof(periodsTable) / sizeof(periodsTable[0]);
  12. const unsigned long unitTime = 60000; // mS
  13. const unsigned long sleepTime = 180000; // mS

使用するピンの定義など。

24 行目の periodsTable[] は集中期間 (FORCUS) 、休憩期間 (REST) の時間テーブルです。25 分集中して 5 分休憩、3 回目の休憩は 15 分です。お好きに設定して下さい。

32 行目の unitTime はタイマーの単位時間 (1 分) 、33 行目の sleepTime はスリープモードまでの時間 (3 分) です。

  1. bool resetTimer = false;
  2. bool isRun = false;
  3. int currPeriod = 0;
  4. volatile int numbersToDisplay = 0;

その他、グローバル変数など。

  1. // Data for displaying numbers
  2. const byte digits[] = {
  3.   0b00111111, // 0
  4.   0b00000110, // 1
  5.   0b01011011, // 2
  6.   0b01001111, // 3
  7.   0b01100110, // 4
  8.   0b01101101, // 5
  9.   0b01111101, // 6
  10.   0b00100111, // 7
  11.   0b01111111, // 8
  12.   0b01101111, // 9
  13.   0b00000000, // blank
  14. };

数字を表示するためのセグメントのデータ。10番目はゼロサプレスのためのブランクデータです。

  1. // Notes in the melody:
  2. // Sample
  3. const int melody[] = {
  4.   NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3, 0, NOTE_B3, NOTE_C4
  5. };
  6. const int noteDurations[] = {
  7.   4, 8, 8, 4, 4, 4, 4, 4
  8. };
  9. const int noteTempo = 1000;
  10. const float notePause = 1.30;
  11. const int numberOfMelody = sizeof(melody) / sizeof(melody[0]);
  12. bool stopNote = false;

期間終了時に鳴らすメロディーデータです。スケッチ例の toneMelody をそのまま利用していますので、pitches.h をコピーしておく必要があります。
お好きなメロディーを設定して下さい。ちなみに俺は「パプリカ」を入れてたりします (^_^;) が、著作権とかややこしいので、データは非公開です。

  1. // Display interrupt
  2. void displayInterrupt() {
  3.   displayNumbers();
  4. }

ダイナミック点灯を割り込み処理で行ないますので、その割り込みハンドラ。
えーと、これは必要? さぁ (^_^;) いいんじゃね?

  1. // Turn off digits display
  2. void turnOffDigits() {
  3.   for (int i = 0; i < numberOfDigitPins; i++) {
  4.     digitalWrite(digitPins[i], DIGIT_OFF);
  5.   }
  6. }

すべてのデジットを消す処理。

  1. // Display number in segments
  2. void displayNumber(int n) {
  3.   for (int i = 0; i < numberOfSegmentPins; i++) {
  4.     digitalWrite(segmentPins[i], digits[n] & (1 << i) ? SEGMENT_ON : SEGMENT_OFF);
  5.   }
  6. }

数字のセグメントデータに応じてセグメントを操作する処理。引数 n を渡すと、digits[n] の各ビットが 1 なら点灯、0 なら消灯させて、その数字を表示します。

  1. // Display numbers in digits
  2. void displayNumbers() {
  3.   static int pos = 0;
  4.   static int num = numbersToDisplay;
  5.   int rem = num % 10;
  6.   if ((0 < pos) & (0 == rem)) rem = 10;
  7.   turnOffDigits();
  8.   displayNumber(rem);
  9.   digitalWrite(digitPins[pos], DIGIT_ON);
  10.   num /= 10;
  11.   pos++;
  12.   if (numberOfDigitPins <= pos) {
  13.     pos = 0;
  14.     num = numbersToDisplay;
  15.   }
  16. }

複数桁の 7 セグメント LED をダイナミック点灯させる処理。割り込み処理で実行します。

ダイナミック点灯でこれまでやっていたのは、セグメントをセットしてからデジットを点灯、数ミリ秒待って消灯し、次の桁の処理へ行くってやり方です。が、割り込み処理のなかで delay() するのはうまくありません。
そこで、割り込み毎に一桁ずつ点灯させるようにしています。割り込み処理は 1 ミリ秒毎に行ないますので、2 ミリ秒で二桁表示することになります。

  1. // Unit timer
  2. bool unitTimer() {
  3.   static unsigned long prevTime = millis();
  4.   bool timeUp = false;
  5.   if (resetTimer) {
  6.     prevTime = millis();
  7.     resetTimer = false;
  8.   }
  9.   unsigned long currTime = millis();
  10.   if (unitTime <= (currTime - prevTime)) {
  11.     timeUp = true;
  12.     prevTime = millis();
  13.   }
  14.   return timeUp;
  15. }

1 分ごとにタイマーのフラグを出力します。

本来ならこれも割り込み処理したいところですね。でも、複数の割り込み処理はいろいろ不都合もあるらしいので、millis() を利用しています。25 分間での誤差はほとんど問題にならない程度です。

  1. // Detect buttons status and behavior
  2. void detectButtons() {
  3.   static const int START_BUTTON = 0;
  4.   static const int STOP_BUTTON = 1;
  5.   if (BUTTON_ON == digitalRead(buttonPins[START_BUTTON])) isRun = true;
  6.   if (BUTTON_ON == digitalRead(buttonPins[STOP_BUTTON])) isRun = false;
  7. }

ボタン入力の検知です。難しいアクションはやりません。
赤いボタンがスタートで isRun フラグを true にセット、青がストップで isRun フラグを false にします。

  1. // Start the period
  2. void startPeriod(int periods) {
  3.   numbersToDisplay = periods;
  4.   resetTimer = true;
  5.   beep();
  6. }

isRun フラグが false から true に変化すると、期間を開始します。

  1. // Run the period
  2. void runPeriod() {
  3.   if (unitTimer()) {
  4.     numbersToDisplay--;
  5.     if (0 >= numbersToDisplay) {
  6.       isRun = false;
  7.       endOfPeriodMelody();
  8.     }
  9.   }
  10. }

isRun フラグが true の間は、表示する数字を 1 分ごとにカウントダウンしていきます。0 になるとメロディを演奏して終了です。

  1. // Stop the period
  2. void stopPeriod() {
  3.   if (numbersToDisplay) {
  4.     numbersToDisplay = 0;
  5.     beep();
  6.   }
  7.   currPeriod++;
  8.   if (numberOfPeriods <= currPeriod) currPeriod = 0;
  9. }

isRun フラグが true から false に変化すると終了で、次の期間へ移行します。
numberToDisplay が 0 ではないときはストップボタンが押されたということなので、強制的に終了にします。

  1. // LEDs control and behavior
  2. void ledsControl() {
  3.   static const int FORCUS = 0;
  4.   static const int REST = 1;
  5.   int pos = currPeriod % numberOfLedPins;
  6.   if (isRun) {
  7.     digitalWrite(ledPins[pos], ledBlinker() ? LED_ON : LED_OFF);
  8.   } else {
  9.     digitalWrite(ledPins[FORCUS], FORCUS == pos ? LED_ON : LED_OFF);
  10.     digitalWrite(ledPins[REST], REST == pos ? LED_ON : LED_OFF);
  11.   }
  12. }

LED 表示のコントロールです。
期間に応じて対応する LED を点灯します。期間が進行している間は点滅します。

  1. // LED blinker
  2. bool ledBlinker() {
  3.   static const int interval = 500; // mS
  4.   static bool state = true;
  5.   static unsigned long prev = millis();
  6.   unsigned long curr = millis();
  7.   if (interval <= (curr - prev)) {
  8.     state = !state;
  9.     prev = millis();
  10.   }
  11.   return state;
  12. }

LED の点滅のコントロール。

  1. // Turn off all LEDs
  2. void turnOffLeds() {
  3.   for (int i = 0; i < numberOfLedPins; i++) {
  4.     digitalWrite(ledPins[i], LED_OFF);
  5.   }
  6. }

LED をすべて消灯します。

  1. //End of period melody
  2. void endOfPeriodMelody() {
  3.   static const int STOP_BUTTON = 1;
  4.   Timer1.stop();
  5.   turnOffDigits();
  6.   turnOffLeds();
  7.   attachInterrupt(digitalPinToInterrupt(buttonPins[STOP_BUTTON]), stopMelody, LOW);
  8.   toneMelody();
  9.   detachInterrupt(digitalPinToInterrupt(buttonPins[STOP_BUTTON]));
  10.   stopNote = false;
  11.   Timer1.start();
  12. }

集中期間の終了時のメロディ演奏です。

演奏中にダイナミック点灯の割込みがあると音が途切れてしまうので、割込みを止め、表示を消しています。代わりにストップボタンの割込みで演奏を中止できるようにしてあります。

  1. // Play melody
  2. void toneMelody() {
  3.   for (int thisNote = 0; thisNote < numberOfMelody; thisNote++) {
  4.     if (stopNote) break;
  5.     int noteDuration = noteTempo / noteDurations[thisNote];
  6.     int pauseBetweenNotes = noteDuration * notePause;
  7.     tone(piezoPin, melody[thisNote], noteDuration);
  8.     delay(pauseBetweenNotes);
  9.   }
  10. }

演奏の本体は、スケッチ例の toneMelody をそのまま利用させてもらいました。
メロディを自由に設定できるのでこうしたのですが、外部にメロディ IC を置いて演奏させる、なんてのがベターじゃないかとも思っています。

  1. // Stop melody interrupt
  2. void stopMelody() {
  3.   stopNote = true;
  4. }

メロディ演奏を中止させるボタン割込みのハンドラです。

  1. // Beep
  2. void beep() {
  3.   tone(piezoPin, NOTE_A7, 50);
  4. }

ボタン操作時のビープ音。

  1. // Put into sleep mode
  2. void sleepMode() {
  3.   static const int WAKE_UP_BUTTON = 0;
  4.   static unsigned long prev = millis();
  5.   if (!isRun) {
  6.     unsigned long curr = millis();
  7.     if (sleepTime <= (curr - prev)) {
  8.       turnOffDigits();
  9.       attachInterrupt(digitalPinToInterrupt(buttonPins[WAKE_UP_BUTTON]), wakeUp, LOW);
  10.       sleep_mode();
  11.       detachInterrupt(digitalPinToInterrupt(buttonPins[WAKE_UP_BUTTON]));
  12.       prev = millis();
  13.     }
  14.   } else {
  15.     prev = millis();
  16.   }
  17. }

タイマーが停止して 3 分経過すると、AVR をパワーダウンモードにします。
パワーダウンモード中にスタートボタンを押すと、パワーダウンを解除して、タイマーがスタートします。

パワーダウンと言っても Arduino 自体は 10 数 mA 消費していますので、スイッチの切り忘れのないように LED は点灯したままにしています。
ちなみに Arduino NANO 本体の電源 LED は点灯しないようにしました。こいつは 3mA 程度消費しますが、動作表示用の LED は 0.9mA 流れるだけです。

  1. // Wake up interrupt
  2. void wakeUp() {
  3.   beep();
  4. }

パワーダウンモードの解除ボタンの割込みハンドラです。

  1. void setup() {
  2.   for (int i = 0; i < numberOfDigitPins; i++) {
  3.     pinMode(digitPins[i], OUTPUT);
  4.     digitalWrite(digitPins[i], DIGIT_OFF);
  5.   }
  6.   for (int i = 0; i < numberOfSegmentPins; i++) {
  7.     pinMode(segmentPins[i], OUTPUT);
  8.   }
  9.   for (int i = 0; i < numberOfButtonPins; i++) {
  10.     pinMode(buttonPins[i], INPUT_PULLUP);
  11.   }
  12.   for (int i = 0; i < numberOfLedPins; i++) {
  13.     pinMode(ledPins[i], OUTPUT);
  14.   }
  15.   pinMode(LED_BUILTIN, OUTPUT);
  16.   digitalWrite(LED_BUILTIN, LED_OFF);
  17.   const int interruptInterval = 1000; // uS
  18.   Timer1.initialize(interruptInterval);
  19.   Timer1.attachInterrupt(displayInterrupt);
  20.   set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  21. }

setup() では、各ピンの動作モードを設定しています。
D13 につながっている内蔵 LED (LED_BUILDIN) は、放置すると昼行灯のように点灯してしまうので、意図的に消灯させます。

ダイナミック点灯用のタイマー TimerOne の設定、パワーダウンモードの設定も行ないます。

  1. void loop() {
  2.   static bool wasRun = false;
  3.   int periods = periodsTable[currPeriod];
  4.   detectButtons();
  5.   if (isRun && !wasRun) startPeriod(periods);
  6.   if (isRun) runPeriod();
  7.   if (!isRun && wasRun) stopPeriod();
  8.   wasRun = isRun;
  9.   ledsControl();
  10.   sleepMode();
  11. }

loop() では、現在の期間値の取り出し、ボタン状態の検知、動作分岐、LED 制御、スリープ制御を繰り返します。

まだまだエクセレントとは言い難いと思うけど、一生懸命作ったぜぃ (^_^;)

ユニバーサル基板

ユニバーサル基板 表面

基板表面です。

きれいに作ろうと思うけど、なかなかねぇ (^_^;)

ユニバーサル基板 裏面

基板裏面です。

Arduino と 7 セグメント LED まわりはポリウレタン銅線を使っています。
今回初めて使ってみたのですが、なかなか便利ですね。ハンダ付けと引き回しは、もっと慣れないとうまくなれないです。

電源ラインとアースラインは裸銅線です。ポリウレタン銅線と色が同じなので区別がつきません (^_^;)

NANO の電源 LED を消す

NANO 電源LEDを消す

スケッチの解説の中で書いていますが、Arduino NANO 本体の電源表示用 LED を消しています。
こいつ、意外と明るいので邪魔になることがありますでしょ?

写真のように、電源 LED と制限抵抗の間のパターンを切っています。下手くそなので大きく切っちゃいましたが、カッターでちょこっと切れば消えます。

もしまた点灯させたくなったら、切った部分にハンダを落とせばつながるんじゃないでしょーか、たぶん (^_^;)

ポモドーロタイマーを使ってみると

べつにこのタイマーだからって話じゃないですが。

ポモドーロタイマーを利用して作業していると、集中期間の 25 分間がものすごく短く感じます。えーっ、もう 25 分経ったの? って感じ。集中できちゃってるんでしょうねぇ。
対して休憩時間の 5 分間は、集中期間に匹敵するほど長く感じたり (^_^;)

休憩時間はできるだけパソコンもスマホも触らない。一日そうして作業すると、終わったあとの疲労度がずいぶん違うように思います。
ポモドーロテクニックは、とってもお薦めです。

タイトルとURLをコピーしました