時計を作る (NTP Clock ESP32 DevKit V1 + MAX7219)
最初に
このWebを大変参考にさせていただきました!!
ESP32マイコンボードとMAX7219 LEDディスプレイ4連x2でNTPクロックを作ってみた
思い立ったいきさつ
Arduinoで何か作ろうと思っていたところに、リモートワークで見やすい時計、それも電波時計のような時刻調整不要なものが欲しくなり、「じゃあ自分で作ってみるか!!」と思ったのがきっかけです。
GPSでもよかったのですが、そのためだけにGPS受信機を占有するのもなぁ、ということでNTPを使った時計に行きつきました。
上記Webをはじめ、Arduinoを使ったNTP時計は比較的簡単に見つかったので、助かりました。
NTPクロックの構想
機能的にはそう難しくはないのですが、詳細設計しようとすると色々悩みます。これは仕事でも同じです。パッと見で簡単そうに見えても、仕上げるには妥協を含めて決めねばならぬ事、調べて設計に織り込まねばならない制約を全て入れていかなければなりません。これはハード設計を伴う場合の醍醐味でもあります。
表示エリア
一番悩んだのはここです。
私の作ろうとしたのは、「時分秒」のみでありLEDマトリックスモジュールは1つのみ、と決めていました。縦8x横32ドットの表示範囲に入れようとすると、5x8ドットのフォントだったとして、数字だけで表示しようとしても5ドットx6文字に加え、文字間のスペースが5ドット必要なので、35ドットとなり横方向に3ドット不足します。コロン(:)を2つ入れるにはさらにスペースを含めて最低4ドット横方向に必要です。
不足している表示エリアを上記Webではフォントの横方向を4ドットにして対処されています。私の用途では、自宅でのリモートワーク時に(ほぼ)正確な時分秒が表示することが重要(特に秒の把握が重要)なので、しっかり見やすい配置を考えなきゃいけない。(Windowsのタスクバーの時計が秒を表示しないのが原因!!)
私が使っている会社PCはWin10なのでレジストリを変更すれば表示されるようだが、Win11では完全に削除されているらしい。
Now that computers have more than 4MB of memory, can we get seconds on the taskbar?
いろいろ考えた結果、時分は5x8ドットフォント、秒は3x5ドットフォントを作成して、左側3モジュール(24ドット)で時分、右端1モジュール(8ドット)で秒を少し小さく表示することとした。時と分の間もコロンなし。結果として、12時34分だと「1234」と表示される。秒との間は少しスペースあり、小さな秒表示は上寄せにしてわかりやすくした。
↓こんな表示です。これだと13時29分20秒です。
NTPの同期
どれぐらいの時間間隔でNTP同期するかを決めねばならない。作ってから確認すべきだが、それほど精度が悪くないなら1日に1度、悪いようなら数時間に1度NTP同期することにする。いずれにせよ、NTPもネットワークも限られた資源の中で運営されているわけで、むやみにNTP同期を乱発すべきではない。
毎日仕事を始めるときに時計をONにするので、表示開始する前にNTP同期がされる。また、仕事を終えるとPC共々この時計の電源をOFFにすることになる。結果論だが、夜の遅い時間で確認しても1秒ずれていない(ラッキー!!)なので、基本12時間に1回同期することにする。
立ち上げ時点でNTP同期してから表示が開始されるので、RTCも特に必要ない(ESP32打と内蔵しているようだが)ので、バックアップ電源も不要。
電源
PCのType-A出力、PCの電源(Type-Cアダプタ)の横に付属のUSB Type-Aや、ディスプレイにあるUSB Type-Aを使うことで、追加のコンセントを占有しないようにしたかったのですが、ESP32を使う時点で概ねUSBの電源を使うことになるので、特に問題なし。
ハードウエア設定と、SPI通信の設定
使ったハードウエア
ESP32 DevKit V1
VKLSVAN ESP32 ESP-32S NodeMCUマイクロUSBデュアルコア開発ボードモジュール 2.4GHz ワイヤレスWiFi + Bluetoothデュアルモード ESP-WROOM-32モジュール内臓 マイクロコントローラ
MAX7219 LED Matrix
F Fityle MAX7219 5ピンケーブル付きArduinoラズベリー用ドットLEDマトリックスMCU制御LEDディスプレイモジュール
両方合わせて1500円以下でした。
これから購入される場合は、配送料に気を付けましょう。
接続とSPI通信設定
Connection: ()内のピン番号ではなく、GPIOの番号で指示する。
MAX7219 | ESP32 |
---|---|
VCC (1) | VIN (40) |
GND (2) | GND (39) |
DIN (3) | MOSI (15) GPIO 23 |
CS (4) | SS (8) GPIO 5 |
CLK (5) | SCL (14) GPIO 22 |
Exampleにあるプログラム(C++)内では以下のように設定する。
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CLK_PIN 18 // or SCK
#define DATA_PIN 23 // or MOSI
#define CS_PIN 5 // or SS
スケッチ(Arduino Script)
SSID, WIFIKEY, ntpServer(ドメイン or IPアドレス)は実行前に皆さんの都合の良いように設定してください。
尚、使用しているフォントファイル"GF3x8p.h"と”GF5x8p.h"は最後にリンクを付けているGitHub上に置いてあります。
// This software uses several open software,
// with each of different license declaration.
// Please use this software with understanding of
// each included software license
// Script by kinoene.jp
#include <NTPClient.h> //For NTP Time Sync
#include <WiFi.h> //For WiFi Network Connection
#include <WiFiUdp.h> //For WiFi UDP
#include <MD_Parola.h> //For MAX7219 Matrix LED
#include <MD_MAX72xx.h> //For MAX7219 SPI LED Driver
#include <SPI.h> //For SPI Communication
#include <time.h> //For Time Based Application
#include "GF3x8p.h" // 3x8 font
#include "GF5x8p.h" // 5x8 font
//Wi-Fi
#define SSID "********" //Use your own WiFi SSID
#define WIFIKEY "********" //Ise your own WiFi Key
//NTP client
#define ntpServer "***.***.***.***" //NTP IP Address; Set your local NTP
#define tzOffset 32400 // JST = 3600 * 9; Set your local offset to UTC in second
//MAX7219
#define MAX_DEVICES 4 // four modules
#define CLK_PIN 18
#define DATA_PIN 23
#define CS_PIN 5
#define SPEED_TIME 25 //Small numbers are faster. Zero is the fastest.
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
// Hardware SPI connection
MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// Define NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, ntpServer, tzOffset, 43200000);
//12 hours : 12(hour) x 60(min) x 60(sec) x 1000(ms) = 43200000 for NTP Sync renewal
void setup() {
//WiFi Connection Setting
WiFi.begin(SSID, WIFIKEY);
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
}
timeClient.begin();
// Matrix Display Setting
P.begin(2); // 2 zones
P.setZone(0, 1, 3); //00011111 <- Zone 0 for HM display
P.setZone(1, 0, 0); //11100000 <- Zone 1 for Sec display
P.setFont(0,GF5x8p);
P.setFont(1,GF3x8p);
P.setIntensity(0); //Darkest
}
void loop() {
time_t epTime = timeClient.getEpochTime();
struct tm ts;
char bufS[3], bufT[5]; // bufS = Sec, 'ss' + 1 = 3, bufT = HourMinute, 'hhmm" + 1 = 5
ts = *localtime(&epTime);
timeClient.update();
P.displayAnimate();
//Zone 0 Time
if (P.getZoneStatus(0)) {
strftime(bufT, sizeof(bufT), "%H%M", &ts); // %T: hh:mm:ss
P.displayZoneText(0, bufT, PA_LEFT, SPEED_TIME, 0, PA_PRINT, PA_NO_EFFECT);
P.displayReset(0);
}
//Zone 1 Time
if (P.getZoneStatus(1)) {
strftime(bufS, sizeof(bufS), "%S", &ts); // %T: hh:mm:ss
P.displayZoneText(1, bufS, PA_RIGHT, SPEED_TIME, 0, PA_PRINT, PA_NO_EFFECT);
P.displayReset(1);
}
delay(50); //Don't remove this delay, and don't make it too small
}
まとめ・今回の製作で私が学んだこと
GitHub
スケッチはこちら
8x5 8x3ドットのフォントなど全てのファイルはこちら動作のわかる動画はこちら
今回の私の学びのまとめ
- Arduinoで、ライブラリのインストール含めて開発の手順一通り
- GitHubへの成果物の登録
- SPI通信の基本
- ESP32とMAX7219ドライバの理解
Discussion