見出し画像

プログラムと電子工作・目覚まし時計

M5StickC Plus で時刻や日付を管理したいアプリケーションがあります。
例えば、歩数計。一日あたり何歩歩いたのか。例えば、自転車のスピードメーター。時速やペダルの回転数(ケイデンス)をリアルタイムに表示する。

最もシンプルなのは、目覚まし時計ですね。
M5StickC Plus のアラーム音は音量が小さいので実際には役者不足です。


目標

  • M5StickC Plus の時刻は、Network Time Protocol(NTP)で取得します。

  • 日付、時刻を M5StickC Plus の液晶ディスプレイにリアルタイム表示します。

  • アラーム時刻が来たら、ブザーを鳴らします。ブザーの ON/OFF は上ボタンで、アラーム時刻は横ボタンで設定します。

部品・機材

使用する部品は次のとおりです。「Hello, World!」と同じですが、Wi-Fi ルータが必要です。

電子部品

  • M5StickC Plus 1台

開発用機材

  • PC(Windows10 または 11)、開発環境 Arduino-IDE 導入ずみ

  • USB-A・USB-C ケーブル

  • Wi-Fi ルータ

開発手順

  1. PC と M5StickC Plus を USBケーブルで接続する。

  2. Arduino-IDE でスケッチ alarmclock.ino を開く。

  3. 検証・コンパイルする。

  4. M5StickC Plus に書き込む。

スケッチ

alarmclock.ino

#include <M5StickCPlus.h>
#include <WiFi.h>

#define LHEIGHT 15  /*1行の高さ(px)TextSize(3):25、TextSize(2):15*/
#define BEEP_FREQ 661  /*アラーム音の周波数(Hz)*/
#define BEEP_DURATION (3*60*1000)  /*アラーム音の時間(ms)、上ボタンを押さなくても自動的に切れる*/

//  それぞれのWi-Fi環境を設定する。
const char* ssid = "xxxxxxxx";  /*SSID*/
const char* password = "xxxxxxxx";  /*PASSWORD*/

struct tm tmnow;    /*現在のローカルタイム*/
struct tm tmprev;   /*前回のローカルタイム*/
struct tm tmalarm;  /*アラーム時刻のローカルタイム*/

bool alarm_set = false;  /*アラーム:true on(時刻が来たらブザーが鳴る)、false off*/

//------------------------------------------------------------------------------
//  setup()
void setup() {
    //  電源ON時に 1回だけ実行する処理をここに書く。
    //  LCDを初期化し、アプリ名を書く。
    M5.begin();               /*M5を初期化する*/
    M5.Axp.ScreenBreath(20);  /*画面の輝度を少し下げる*/
    M5.Lcd.setTextSize(2);    /*文字サイズはちょっと小さめ*/
    M5.Lcd.setRotation(3);    /*上スイッチが左になる向き*/

    M5.Lcd.print("alarmclock");
    delay(500);

    Serial.begin(115200);

    //  WiFiアクセスポイントに接続する。
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {  /*Wi-Fi AP接続待ち*/
        delay(500);
        M5.Lcd.print(".");
    }
    M5.Lcd.fillScreen(BLACK);  /*背景を黒にする*/
    M5.Lcd.setCursor(0, LHEIGHT*0);  /*1行目*/
    M5.Lcd.println("WiFi OK");
    M5.Lcd.println(WiFi.localIP());
    Serial.print("WiFi connected\r\nIP address: ");
    Serial.println(WiFi.localIP());

    //  NTP でローカルタイムを時刻合わせする。
    configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");

    //  現在の時刻を求める。
    getLocalTime(&tmnow);
    tmprev = tmnow;

    //  アラーム時刻をセットする。仮に 07:00:00とする。
    tmalarm.tm_hour = 7;
    tmalarm.tm_min  = 0;
    tmalarm.tm_sec  = 0;

    M5.Beep.begin();               /*アラーム音を初期化する*/
    M5.Beep.tone(BEEP_FREQ, 500);  /*アラーム音をテストする*/
}
//------------------------------------------------------------------------------
//  loop()
void loop() {
    //  自動的に繰り返し実行する処理をここに書く。

    //  アラーム音の状態を更新する。
    M5.Beep.update();

    //  ボタンの状態を更新する。
    M5.update();

    getLocalTime(&tmnow);
    if (tmnow.tm_sec != tmprev.tm_sec) {
        //  --- 秒が変化した。
        //  1秒ごとに時刻表示する。
        if (0 == tmnow.tm_sec % 1) {
            M5.Lcd.setTextSize(3);
            M5.Lcd.setCursor(0, LHEIGHT*3);  /*4行め*/
            M5.Lcd.printf("%04u-%02u-%02u",
                          tmnow.tm_year + 1900, tmnow.tm_mon + 1, tmnow.tm_mday);
            M5.Lcd.setCursor(0, LHEIGHT*5);  /*6行め*/
            M5.Lcd.printf("%02u:%02u:%02u",
                          tmnow.tm_hour, tmnow.tm_min, tmnow.tm_sec);
        }

        //  10日に 1回、3:03:03に NTP で時刻合わせする。
        //  ※  頻度、時刻に特別な意味はない。あまり頻繁だとサーバに負荷をかける。
        if (0 == tmnow.tm_mday % 10 && 
            3 == tmnow.tm_hour && 3 == tmnow.tm_min && 3 == tmnow.tm_sec) {
            //  --- 指定時刻が来た。
            //  NTP でローカルタイムを時刻合わせする。
            configTime(9 * 3600L, 0, "ntp.nict.jp", "time.gooDDgle.com", "ntp.jst.mfeed.ad.jp");
            getLocalTime(&tmnow);
        }

        //  アラーム音を ON/OFF する。
        if (alarm_set) {
            if (tmalarm.tm_hour == tmnow.tm_hour && tmalarm.tm_min == tmnow.tm_min && 
                0 == tmnow.tm_sec) {
                //  --- アラーム時刻になった。
                //  アラームを鳴らす。
                M5.Beep.tone(BEEP_FREQ, BEEP_DURATION);
            }
        }
        else {
            //  アラームを切る。
            M5.Beep.mute();
        }

        //  前回のローカルタイムを更新する。
        tmprev = tmnow;
    }

    //  押しボタンを確認する。
    if (M5.BtnA.wasPressed()) {
        //  アラーム ON/OFF を切り替える。
        alarm_set = !alarm_set;
    }
    if (M5.BtnB.wasPressed()) {
        //  アラーム時刻を +1時する。
        tmalarm.tm_hour = (tmalarm.tm_hour + 1) % 24;
    }

    //  アラーム時刻を表示する。
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(0, LHEIGHT*7);  /*8行め*/

    if (alarm_set) {
        M5.Lcd.printf("ALARM ON  %02u:%02u",
                      tmalarm.tm_hour, tmalarm.tm_min);
    }
    else {
        M5.Lcd.printf("ALARM OFF %02u:%02u",
                      tmalarm.tm_hour, tmalarm.tm_min);
    }

    delay(1);
}
  • Network Time Protocol(NTP)で現在時刻を読み出し、ローカルタイムをセットします。

  • アラーム時刻を仮設定し、アラーム音の準備をします。仮設定に近い時刻(例えば 2分後)を設定すれば、アラーム音のテストがすぐにできます。

  • loop() 関数内の if 文で秒の変化を捕まえます。秒が変化した時 1回だけ処理を行うことができます。

  • 処理頻度は剰余演算(%)を用いて実現します。例えば、10日に 1回行う場合は、N == tmnow.tm_mday % 10 のように条件を指定します。Nは 10で割った余りです。N=0なら 10日、20日、30日に処理されます。N=1なら 1日、11日、21日、31日に処理されます。年、月、時、分、秒も同様です。年、月は暦と異なりますので注意してください。

  • 例えば tm_mday(日)の条件を指定する場合、tm_hour(時)、tm_min(分)、tm_sec(秒)の条件を指定しないと、24×60×60回処理を繰り返してしまいます。上記のスケッチでは、3時 3分 3秒を指定しています。

  • 1秒以上かかる処理を行う場合は、開始タイミングが重ならないように条件を分散する方がいいでしょう。

結果

  • 電源オンで、WiFi接続し、Network Time Protocol(NTP)で現在時刻を読み出し、ローカルタイムをセットします。

  • 1秒経つごとに、日付と時刻を更新します。(写真1)

写真1 alarmclock.ino の実行結果
  •  上ボタン(押しボタンA)でアラーム ON/OFF切り替え、横ボタン(押しボタンB)でアラーム時刻(時のみ)を設定します。

  • アラーム時刻が来たら、アラーム音が鳴ります。放置すれば 3分間鳴り続けて自動的に切れます。上ボタン(押しボタンA)でアラーム OFFすれば鳴り止みます。

練習問題

  1. 「時」が変わるとき(59分から00分になったとき)に「プッ」と鳴るようにしてください。

  2. アラーム時刻の「分」も設定できるように機能拡張してください。

参考

struct tm {
    int  tm_sec;    // seconds after the minute [0-60]
    int  tm_min;    // minutes after the hour [0-59]
    int  tm_hour;   // hours since midnight [0-23]
    int  tm_mday;   // day of the month [1-31]
    int  tm_mon;    // months since January [0-11]
    int  tm_year;   // years since 1900
    int  tm_wday;   // days since Sunday [0-6]
    int  tm_yday;   // days since January 1 [0-365]
    int  tm_isdst;  // Daylight Savings Time flag
    long tm_gmtoff; // offset from CUT in seconds
    char *tm_zone;  // timezone abbreviation
};

ライセンス

このページのソースコードは、複製・改変・配布が自由です。営利目的にも使用してかまいませんが、何ら責任を負いません。


この記事が気に入ったらサポートをしてみませんか?