構成
プロジェクト名:M5-Rtc
デバイス:M5STACK
技術選定の理由(ここを書くと読者が喜びます)
- BM8563 (RTC): 「M5Stack本体のタイマーは再起動でリセットされるが、RTCがあれば電源オフでも時刻を維持できる」点。
- M5FontRender: 「標準フォントやバイナリフォントに比べ、アンチエイリアスが効いた美しい文字表示が可能」な点。
- DualCore (Task): 「Core 1でセンサー通信やロジック、Core 0で重い描画処理(FontRender)を分担させることで、UIの引っ掛かりをなくす」点。
DualCore(マルチタスク)の実装解説
詰まりポイント
Core 0の役割: Core 0はWi-Fi/Bluetooth通信も担当しているため、delay(1) または vTaskDelay を入れないとウォッチドッグタイマーでリセットがかかる点。
プログラム
変数
// RTC(BM8563)用の構造体
struct RTC_Time {
uint8_t hour, min, sec;
};
struct RTC_Date {
uint16_t year;
uint8_t month, day;
};
RTC_Date now_date;
RTC_Time now_time;
RTC_Date old_date;
RTC_Time old_time;
// DualCore
TaskHandle_t task_handl; //マルチタスクハンドル定義
boolean isDisp_ok = false; //ディスプレイ表示フラグ
#define MODE_BOOT 0
#define MODE_HOME 1
#define MODE_RTC 2
int8_t task_no=MODE_BOOT;
int8_t last_task_no=0;
初期化処理
void setup() {
M5.begin();
Wire.begin(); // I2Cの開始(RTC通信用)
// --- デバッグ用:RTC時刻初期化 (2026/01/12 00:00:00) ---
setRTC(2026, 1, 12, 0, 0, 0);
//
setup_wifi();
myIP = WiFi.softAPIP();
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setCursor(0, 8);
M5.Lcd.setTextFont(2);
M5.Lcd.setTextSize(2);
//--------------------------------------------------------
//SDファイルチェック
// 本システムではSDカードが挿入されていることを必須とする。
//--------------------------------------------------------
SD_Exitst=SD.begin(); // SDカードと通信できるか確認
if(!SD_Exitst){
#ifdef DEBUG_MODE
Serial.println("SD Card is not installed.\n");
#endif
M5.Lcd.println("SD Card is not installed.\n"); //通信できなければエラーを表示
M5.Lcd.println("please confirm."); //通信できなければエラーを表示
// SDカードがないと困るので、ここで無限ループさせて先に進ませない
while (1) {
delay(1000);
}
}
//--------------------------------------------------------
//FontFile notexist
//--------------------------------------------------------
if (!render.loadFont("/ombre.ttf")) {
#ifdef DEBUG_MODE
Serial.println("FontFile not Exist");
#endif
M5.Lcd.println("FontFile Not Exist.");
while (1) {
delay(1000);
}
}
//
xTaskCreatePinnedToCore(&taskDisplay, "taskDisplay", 8192, NULL, 10, &task_handl, 0);
delay(500);
task_no=MODE_HOME;
isDisp_ok=true;
}
メイン
void loop() {
M5.update(); // ボタン状態の更新
button_action(); // ボタンが押されたら task_no を切り替え、isDisp_ok = true にする
// --- 時刻のリアルタイム更新処理 ---
static uint8_t last_sec = 99; // 前回の秒を記憶する変数
getRTC(&now_date, &now_time); // RTCから最新時刻を取得
// 現在のモードが RTC の時だけ、秒が変わった瞬間に更新フラグを立てる
if (task_no == MODE_RTC) {
if (now_time.sec != last_sec) {
isDisp_ok = true; // 描画タスクを動かす
last_sec = now_time.sec; // 秒を更新
}
}
delay(10); // 10ms待機(ボタン反応と更新精度のバランス)
}
RTC関連
// RTCに時刻を書き込む (2028/01/18 00:00:00 など)
void setRTC(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec) {
Wire.beginTransmission(0x51);
Wire.write(0x02); // 秒レジスタから開始
Wire.write(decToBcd(sec));
Wire.write(decToBcd(min));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(day));
Wire.write(0x00); // 曜日(無視)
Wire.write(decToBcd(month)); // 月(ここに含まれるセンチュリービットは通常0で2000年〜2099年)
Wire.write(decToBcd(year % 100)); // 年の下2桁
Wire.endTransmission();
}
// RTCから読み出す
void getRTC(RTC_Date* d, RTC_Time* t) {
Wire.beginTransmission(0x51);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(0x51, 7);
t->sec = bcdToDec(Wire.read() & 0x7F);
t->min = bcdToDec(Wire.read() & 0x7F);
t->hour = bcdToDec(Wire.read() & 0x3F);
d->day = bcdToDec(Wire.read() & 0x3F);
Wire.read(); // 曜日スキップ
d->month = bcdToDec(Wire.read() & 0x1F);
d->year = bcdToDec(Wire.read()) + 2000;
}
表示タスク
void taskDisplay(void *pvParameters) {
while (true) {
if (isDisp_ok) {
// モードが切り替わった瞬間だけ画面全体を黒塗りする
if (task_no != last_task_no) {
M5.Lcd.fillScreen(BLACK);
last_task_no = task_no;
}
switch (task_no) {
case MODE_BOOT:
break;
case MODE_HOME: { // ★波括弧を追加
// ★ 描画先を本体ディスプレイのポインタに戻す
render.setTextColor(WHITE);
render.setTextSize(24);
render.setTextColor(GREEN);
render.setCursor(65, 20);
render.printf("Wifi Router\n");
render.setTextSize(20);
render.setTextColor(WHITE);
render.setCursor(30, 80);
// IPアドレス取得
String str = String(myIP[0]) + '.' + String(myIP[1]) + '.' + String(myIP[2]) + '.' + String(myIP[3]);
render.printf("IP:%s\n", str.c_str());
render.setCursor(30, 110);
render.printf("ID=ombre_wifi");
render.setCursor(30, 140);
render.printf("Pass=pass1234");
break;
}
case MODE_RTC:
// --- 日付エリアの更新 ---
// 日付が表示される範囲(x=30, y=80)だけをピンポイントで消去
// 2. 日付に変化があるかチェック
if (!isDateSame(now_date, old_date)) {
#ifdef DEBUG_MODE
Serial.println("renew date\n");
#endif
M5.Lcd.fillRect(30, 80, 260, 26, BLACK);
render.setCursor(30, 80);
render.setTextColor(WHITE);
render.printf("Date: %04d/%02d/%02d", now_date.year, now_date.month, now_date.day);
// 3. 次回比較のために保存
old_date = now_date;
}
// --- 時刻エリアの更新 ---
// 時刻が表示される範囲(x=30, y=110)だけをピンポイントで消去
// 4. 時刻に変化があるかチェック
if (!isTimeSame(now_time, old_time)) {
#ifdef DEBUG_MODE
Serial.println("renew time\n");
#endif
M5.Lcd.fillRect(30, 110, 260, 26, BLACK);
render.setCursor(30, 110);
render.printf("Time: %02d:%02d:%02d", now_time.hour, now_time.min, now_time.sec);
// 3. 次回比較のために保存
old_time = now_time;
}
break;
}
if (isDisp_ok) {
isDisp_ok = false;
}
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
※一部作成した関数は記載省略
今後
日次の設定方法については今回WifiRooter機能も搭載したので現在、UDP、Socket通信にするか検討中
日時設定における通信方式の比較
| 特徴 | UDP (User Datagram Protocol) | Socket (TCP) |
| 信頼性 | 送りっぱなし(届かない可能性あり) | 確実に届く(ハンドシェイクあり) |
| 速度 | 速い、オーバーヘッドが少ない | UDPよりは遅い(接続処理が必要) |
| 実装難易度 | 非常に簡単 | 少し複雑 |
| 向いている用途 | 時刻同期(NTPなど)、一斉配信 | 確実な設定変更、双方向通信 |
補足
なぜ今回DualCoreにしたのか?
今後WiFi通信(Socket)を追加した際に、通信処理による描画の遅延を防ぐため。



コメント