Files
M5Unit-RTC/examples/UnitUnified/UnitRTC/PlotToSerial/main/PlotToSerial.cpp
T
2026-03-04 20:55:31 +09:00

303 lines
9.8 KiB
C++

/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Example using M5UnitUnified for UnitRTC (PCF8563/BM8563/HYM8563)
- Sync time from NTP via WiFi, then set RTC
*/
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedRTC.h>
#include <M5HAL.hpp> // For NessoN1
#include <WiFi.h>
#include <esp_sntp.h>
#include <esp_random.h>
#include <algorithm> // std::shuffle
#include <cstdint>
namespace {
// ==============================================================
// NOTICE: Set your WiFi credentials and timezone before building
// ==============================================================
const char* WIFI_SSID = ""; // Your WiFi SSID
const char* WIFI_PASS = ""; // Your WiFi password
// POSIX timezone string (default: JST-9 = UTC+9, no DST)
const char* YOUR_TIMEZONE = "JST-9";
// const char* YOUR_TIMEZONE = "EST5"; // US Eastern (UTC-5, no DST)
// const char* YOUR_TIMEZONE = "CET-1"; // Central Europe (UTC+1, no DST)
// const char* YOUR_TIMEZONE = "CST-8"; // China (UTC+8)
// See: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
// NTP server list
// China
constexpr char ntp_cn0[] = "cn.pool.ntp.org";
constexpr char ntp_cn1[] = "ntp.aliyun.com";
constexpr char ntp_cn2[] = "time.cloud.tencent.com";
// Japan
constexpr char ntp_jp0[] = "ntp.nict.jp";
constexpr char ntp_jp1[] = "ntp.jst.mfeed.ad.jp";
constexpr char ntp_jp2[] = "time.google.com";
// United States
constexpr char ntp_us0[] = "us.pool.ntp.org";
constexpr char ntp_us1[] = "time.nist.gov";
constexpr char ntp_us2[] = "time.cloudflare.com";
// Europe
constexpr char ntp_eu0[] = "europe.pool.ntp.org";
constexpr char ntp_eu1[] = "ntp.ripe.net";
constexpr char ntp_eu2[] = "ptbtime1.ptb.de";
// Select region: jp / us / eu / cn
const char* ntpURLTable[] = {ntp_jp0, ntp_jp1, ntp_jp2};
// const char* ntpURLTable[] = {ntp_us0, ntp_us1, ntp_us2};
// const char* ntpURLTable[] = {ntp_eu0, ntp_eu1, ntp_eu2};
// const char* ntpURLTable[] = {ntp_cn0, ntp_cn1, ntp_cn2};
// UniformRandomBitGenerator using ESP32 hardware RNG
struct ESP32RNG {
using result_type = uint32_t;
static constexpr result_type min()
{
return 0;
}
static constexpr result_type max()
{
return UINT32_MAX;
}
result_type operator()()
{
return esp_random();
}
};
constexpr char WDAY_NAMES[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
auto& lcd = M5.Display;
m5::unit::UnitUnified Units;
m5::unit::UnitPCF8563 unit;
void display_printf(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
void display_printf(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
lcd.vprintf(fmt, args);
va_end(args);
}
// Sync NTP time and set RTC (shows progress on Display)
bool sync_ntp_to_rtc()
{
M5_LOGI("Connecting to WiFi: %s", WIFI_SSID);
display_printf("WiFi connecting");
// Tab5 (ESP32-P4) uses ESP32-C6 co-processor for WiFi via SDIO; set pins before WiFi.begin()
#if defined(CONFIG_IDF_TARGET_ESP32P4)
if (M5.getBoard() == m5::board_t::board_M5Tab5) {
constexpr int SDIO2_CLK = 12;
constexpr int SDIO2_CMD = 13;
constexpr int SDIO2_D0 = 11;
constexpr int SDIO2_D1 = 10;
constexpr int SDIO2_D2 = 9;
constexpr int SDIO2_D3 = 8;
constexpr int SDIO2_RST = 15;
WiFi.setPins(SDIO2_CLK, SDIO2_CMD, SDIO2_D0, SDIO2_D1, SDIO2_D2, SDIO2_D3, SDIO2_RST);
}
#endif
WiFi.mode(WIFI_STA);
if (WIFI_SSID[0] && WIFI_PASS[0]) {
WiFi.begin(WIFI_SSID, WIFI_PASS);
} else {
WiFi.begin();
}
int retry = 20;
while (retry-- > 0 && WiFi.status() != WL_CONNECTED) {
display_printf(".");
m5::utility::delay(500);
}
if (WiFi.status() != WL_CONNECTED) {
M5_LOGE("WiFi connection failed");
display_printf("\nWiFi FAILED\n");
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
return false;
}
M5_LOGI("WiFi connected");
display_printf("\nWiFi OK\n");
// Shuffle NTP servers
std::shuffle(std::begin(ntpURLTable), std::end(ntpURLTable), ESP32RNG{});
M5_LOGI("NTP: %s / %s / %s", ntpURLTable[0], ntpURLTable[1], ntpURLTable[2]);
configTzTime(YOUR_TIMEZONE, ntpURLTable[0], ntpURLTable[1], ntpURLTable[2]);
// Wait for sync
display_printf("NTP syncing");
retry = 10;
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && --retry >= 0) {
M5_LOGI(" NTP sync in progress...");
display_printf(".");
m5::utility::delay(1000);
}
// Done with WiFi
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
struct tm t{};
if (!getLocalTime(&t, 10000)) {
M5_LOGE("Failed to get NTP time");
display_printf("\nNTP FAILED\n");
return false;
}
M5_LOGI("NTP time: %04d-%02d-%02d %02d:%02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min,
t.tm_sec);
display_printf("\nNTP OK\n");
// Set RTC from NTP (use GMT for RTC storage)
// Stop RTC clock for precise time setting (prescaler held in reset)
unit.writeStop(true);
// Prepare next second's time
time_t now = time(nullptr) + 1;
struct tm gmt = *gmtime(&now);
// Write time while clock is stopped (no carry race condition)
if (!unit.writeDateTime(gmt)) {
unit.writeStop(false);
M5_LOGE("Failed to set RTC");
display_printf("RTC set FAILED\n");
return false;
}
// Wait for exact second boundary, then release STOP
// First 1Hz tick occurs ~0.508s after STOP is released
while (now > time(nullptr)) {
;
}
unit.writeStop(false);
M5_LOGI("RTC set to (GMT): %04d-%02d-%02d %02d:%02d:%02d", gmt.tm_year + 1900, gmt.tm_mon + 1, gmt.tm_mday,
gmt.tm_hour, gmt.tm_min, gmt.tm_sec);
display_printf("RTC set OK\n");
return true;
}
} // namespace
void setup()
{
M5.begin();
M5.setTouchButtonHeightByRatio(100);
// The screen shall be in landscape mode
if (lcd.height() > lcd.width()) {
lcd.setRotation(1);
}
auto board = M5.getBoard();
// ESP32-C6 boards have only 1 hardware I2C (Wire), used by M5Unified internally.
// Use SoftwareI2C via M5HAL for the GROVE port to avoid bus conflict.
if (board == m5::board_t::board_ArduinoNessoN1 || board == m5::board_t::board_M5NanoC6) {
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
if (board == m5::board_t::board_ArduinoNessoN1) {
pin_num_sda = M5.getPin(m5::pin_name_t::port_b_out);
pin_num_scl = M5.getPin(m5::pin_name_t::port_b_in);
}
M5_LOGI("getPin(M5HAL): SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
m5::hal::bus::I2CBusConfig i2c_cfg;
i2c_cfg.pin_sda = m5::hal::gpio::getPin(pin_num_sda);
i2c_cfg.pin_scl = m5::hal::gpio::getPin(pin_num_scl);
auto i2c_bus = m5::hal::bus::i2c::getBus(i2c_cfg);
M5_LOGI("Bus:%d", i2c_bus.has_value());
if (!Units.add(unit, i2c_bus ? i2c_bus.value() : nullptr) || !Units.begin()) {
M5_LOGE("Failed to begin");
while (true) {
m5::utility::delay(10000);
}
}
} else {
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
Wire.begin(pin_num_sda, pin_num_scl, 100 * 1000U);
if (!Units.add(unit, Wire) || !Units.begin()) {
M5_LOGE("Failed to begin");
while (true) {
m5::utility::delay(10000);
}
}
}
M5_LOGI("M5UnitUnified has been begun");
M5_LOGI("%s", Units.debugInfo().c_str());
// Check voltage low (battery backup lost)
if (unit.getVoltLow()) {
M5_LOGW("RTC voltage low - time may be invalid");
}
// Sync from NTP if WiFi credentials are configured
if (!sync_ntp_to_rtc()) {
M5_LOGW("NTP sync skipped or failed - using current RTC time");
}
lcd.startWrite();
lcd.fillScreen(0);
lcd.endWrite();
}
void loop()
{
static time_t prev{};
M5.update();
Units.update();
// Show local time on Serial and Display every second
time_t now = time(nullptr);
if (now != prev) {
struct tm local = *localtime(&now);
// Serial
M5.Log.printf("%04d-%02d-%02d(%s) %02d:%02d:%02d\n", local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
WDAY_NAMES[local.tm_wday], local.tm_hour, local.tm_min, local.tm_sec);
// Display
lcd.setCursor(0, 0);
lcd.startWrite();
display_printf("%04d-%02d-%02d(%s)\n%02d:%02d:%02d\n", local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
WDAY_NAMES[local.tm_wday], local.tm_hour, local.tm_min, local.tm_sec);
lcd.endWrite();
prev = now;
}
if (M5.BtnA.wasClicked()) {
// Read and show RTC (GMT)
auto dt = unit.getDateTime();
M5.Log.printf("RTC(GMT) %04d-%02d-%02d(%s) %02d:%02d:%02d\n", dt.date.year, dt.date.month, dt.date.date,
WDAY_NAMES[dt.date.weekDay % 7], dt.time.hours, dt.time.minutes, dt.time.seconds);
lcd.startWrite();
display_printf("RTC(GMT)\n%04d-%02d-%02d(%s)\n%02d:%02d:%02d\n", dt.date.year, dt.date.month, dt.date.date,
WDAY_NAMES[dt.date.weekDay % 7], dt.time.hours, dt.time.minutes, dt.time.seconds);
lcd.endWrite();
} else if (M5.BtnA.wasHold()) {
// Re-sync RTC from NTP (progress shown on Display)
M5_LOGI("Re-syncing NTP...");
lcd.startWrite();
lcd.fillScreen(0);
lcd.endWrite();
lcd.setCursor(0, 0);
if (!sync_ntp_to_rtc()) {
M5_LOGW("NTP sync failed");
}
lcd.startWrite();
lcd.fillScreen(0);
lcd.endWrite();
}
}