Add UnitRTC using M5UnitUnified

This commit is contained in:
GOB
2026-02-26 19:54:07 +09:00
parent 7e0f0ac76b
commit 103e0a932b
22 changed files with 6393 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
{
"build": {
"arduino": {
"memory_type": "qio_opi",
"ldscript": "esp32s3_out.ld",
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_M5STACK_ATOMS3R",
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "dio",
"mcu": "esp32s3",
"variant": "m5stack_atoms3"
},
"connectivity": [
"bluetooth",
"wifi"
],
"frameworks": [
"arduino",
"espidf"
],
"name": "M5Stack AtomS3R",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"require_upload_port": true,
"speed": 460800
},
"url": "https://docs.m5stack.com/en/core/AtomS3R",
"vendor": "M5Stack"
}
+33
View File
@@ -0,0 +1,33 @@
{
"build": {
"core": "esp32",
"extra_flags": [
"-DARDUINO_M5STACK_NANOC6"
],
"f_cpu": "160000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"mcu": "esp32c6",
"variant": "esp32c6"
},
"connectivity": [
"wifi"
],
"debug": {
"openocd_target": "esp32c6.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "M5Stack NanoC6",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194384,
"require_upload_port": true,
"speed": 460800
},
"url": "https://docs.m5stack.com/en/core/M5NanoC6",
"vendor": "M5Stack"
}
+40
View File
@@ -0,0 +1,40 @@
{
"build": {
"arduino":{
"ldscript": "esp32_out.ld",
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DM5STACK_M5STICK_CPLUS2",
"-DBOARD_HAS_PSRAM",
"-mfix-esp32-psram-cache-issue",
"-mfix-esp32-psram-cache-strategy=memw",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "dio",
"mcu": "esp32",
"variant": "m5stick_c"
},
"connectivity": [
"wifi",
"bluetooth"
],
"frameworks": [
"arduino",
"espidf"
],
"name": "M5Stick-CPlus2",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"require_upload_port": true,
"speed": 1500000
},
"url": "https://docs.m5stack.com/en/core/M5StickC%20PLUS2",
"vendor": "M5Stack"
}
+2600
View File
File diff suppressed because it is too large Load Diff
Executable
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# Please execute on repositry root
## Get version from library.properties
## Get git rev of HEAD
LIB_VERSION="$(pcregrep -o1 "^\s*version\s*=\s*(\*|\d+(\.\d+){0,3}(\.\*)?)" library.properties)"
#echo ${DOXYGEN_PROJECT_NUMBER}
DOXYGEN_PROJECT_NUMBER="${LIB_VERSION} git rev:$(git rev-parse --short HEAD)" doxygen docs/Doxyfile
@@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Example using M5UnitUnified for UnitRTC (PCF8563/BM8563/HYM8563)
- Alarm notification demo
*/
#include "main/AlarmNotify.cpp"
@@ -0,0 +1,242 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Example using M5UnitUnified for UnitRTC (PCF8563/BM8563/HYM8563)
- Set alarm to 1 minute ahead, show countdown, notify when alarm fires
- BtnA click: re-set alarm to +1 minute from current RTC time
*/
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedRTC.h>
#include <M5HAL.hpp> // For NessoN1
#include <sys/time.h>
#include <cstdint>
namespace {
// 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
constexpr char WDAY_NAMES[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
auto& lcd = M5.Display;
LGFX_Sprite canvas(&lcd);
m5::unit::UnitUnified Units;
m5::unit::UnitPCF8563 unit;
volatile bool alarm_fired{};
bool indicator_lit{};
void on_alarm()
{
alarm_fired = true;
}
void canvas_printf(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
void canvas_printf(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
canvas.vprintf(fmt, args);
va_end(args);
}
// Draw indicator circle at top-right corner of canvas
void draw_indicator(bool lit)
{
int32_t r = canvas.height() / 20;
if (r < 4) {
r = 4;
}
int32_t x = canvas.width() - r - 4;
int32_t y = r + 4;
canvas.fillCircle(x, y, r, lit ? 2 : 3); // palette: 2=TFT_BLUE, 3=TFT_DARKGREY
}
// Set alarm to current RTC time + 1 minute
bool set_alarm_plus1()
{
using namespace m5::unit::pcf8563;
rtc_time_t now_t{};
rtc_date_t now_d{};
if (!unit.readTime(now_t) || !unit.readDate(now_d)) {
M5_LOGE("Failed to read current time");
return false;
}
int8_t alarm_min = now_t.minutes + 1;
int8_t alarm_hour = now_t.hours;
if (alarm_min >= 60) {
alarm_min -= 60;
alarm_hour++;
if (alarm_hour >= 24) {
alarm_hour = 0;
}
}
// Set alarm (minutes + hours enabled, date/weekday disabled)
// writeAlarm clears flag and enables interrupt automatically
rtc_time_t at{alarm_hour, alarm_min, -1};
rtc_date_t ad{2000, 1, -1, -1};
if (!unit.writeAlarm(at, ad)) {
return false;
}
M5.Log.printf("Alarm set to %02d:%02d (current %02d:%02d:%02d)\n", alarm_hour, alarm_min, now_t.hours,
now_t.minutes, now_t.seconds);
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();
// Configure alarm callback (polling mode)
auto cfg = unit.config();
cfg.on_alarm = on_alarm;
unit.config(cfg);
// 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 system clock from RTC (stored as GMT) and set timezone
{
m5::unit::pcf8563::rtc_datetime_t dt{};
unit.readDateTime(dt);
struct tm gmt = dt.to_tm();
time_t t = mktime(&gmt);
struct timeval tv{t, 0};
settimeofday(&tv, nullptr);
setenv("TZ", YOUR_TIMEZONE, 1);
tzset();
M5_LOGI("System clock set from RTC (GMT), TZ=%s", YOUR_TIMEZONE);
}
// Create sprite for flicker-free drawing (2-bit palette, internal RAM)
canvas.setPsram(false);
canvas.setColorDepth(2);
int32_t sw = lcd.width() < 320 ? lcd.width() : 320;
int32_t sh = lcd.height() < 240 ? lcd.height() : 240;
canvas.createSprite(sw, sh);
canvas.setFont(lcd.getFont());
canvas.setTextSize(lcd.getTextSizeX(), lcd.getTextSizeY());
canvas.setPaletteColor(0, TFT_BLACK);
canvas.setPaletteColor(1, TFT_WHITE);
canvas.setPaletteColor(2, TFT_BLUE);
canvas.setPaletteColor(3, TFT_DARKGREY);
// Set alarm to +1 minute
set_alarm_plus1();
}
void loop()
{
M5.update();
Units.update();
static time_t prev{};
time_t now = time(nullptr);
// Handle alarm notification
if (alarm_fired) {
alarm_fired = false;
indicator_lit = true;
M5.Log.printf("*** ALARM FIRED! ***\n");
M5.Speaker.tone(2000, 20);
// Re-set alarm to +1 minute
set_alarm_plus1();
prev = 0; // force redraw
}
// Update display every second
if (now != prev) {
struct tm local = *localtime(&now);
// Read alarm setting
m5::unit::pcf8563::rtc_time_t at{};
m5::unit::pcf8563::rtc_date_t ad{};
unit.readAlarm(at, ad);
// Serial
M5.Log.printf("%04d-%02d-%02d(%s) %02d:%02d:%02d Alarm:%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, at.hours,
at.minutes);
// Draw to canvas (off-screen), then push to display
canvas.fillScreen(TFT_BLACK);
canvas.setCursor(0, 0);
canvas_printf("%04d-%02d-%02d(%s)\n%02d:%02d:%02d\n\nAlarm: %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, at.hours, at.minutes);
draw_indicator(indicator_lit);
indicator_lit = false;
canvas.pushSprite(0, 0);
prev = now;
}
// BtnA click: re-set alarm to +1 minute
if (M5.BtnA.wasClicked()) {
M5.Log.printf("Re-setting alarm...\n");
set_alarm_plus1();
prev = 0; // force redraw
}
}
@@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Example using M5UnitUnified for UnitRTC (PCF8563/BM8563/HYM8563)
- Periodic timer demo
*/
#include "main/PeriodicTimer.cpp"
@@ -0,0 +1,235 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Example using M5UnitUnified for UnitRTC (PCF8563/BM8563/HYM8563)
- Countdown timer using writeTimer() convenience API
- BtnA click: cycle interval and repeat mode
5s repeat -> 10s repeat -> 30s repeat -> 5s oneshot -> 10s oneshot -> stop -> ...
*/
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedRTC.h>
#include <M5HAL.hpp> // For NessoN1
#include <sys/time.h>
#include <cstdint>
namespace {
// 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
constexpr char WDAY_NAMES[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
auto& lcd = M5.Display;
LGFX_Sprite canvas(&lcd);
m5::unit::UnitUnified Units;
m5::unit::UnitPCF8563 unit;
volatile uint32_t timer_count{};
volatile bool timer_fired{};
bool indicator_lit{};
// Mode table: {seconds, repeat}
struct TimerMode {
uint8_t seconds;
bool repeat;
};
constexpr TimerMode MODES[] = {
{5, true}, {10, true}, {30, true}, {5, false}, {10, false}, {0, false},
};
size_t mode_idx{};
void on_timer()
{
timer_count = timer_count + 1;
timer_fired = true;
}
void canvas_printf(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
void canvas_printf(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
canvas.vprintf(fmt, args);
va_end(args);
}
// Draw indicator circle at top-right corner of canvas
void draw_indicator(bool lit)
{
int32_t r = canvas.height() / 20;
if (r < 4) {
r = 4;
}
int32_t x = canvas.width() - r - 4;
int32_t y = r + 4;
canvas.fillCircle(x, y, r, lit ? 2 : 3); // palette: 2=TFT_BLUE, 3=TFT_DARKGREY
}
// Start timer with current mode
void start_timer()
{
auto& m = MODES[mode_idx];
if (m.seconds == 0) {
unit.writeTimer(0);
M5.Log.printf("Timer stopped\n");
return;
}
uint32_t actual = unit.writeTimer(static_cast<uint32_t>(m.seconds) * 1000, m.repeat);
M5.Log.printf("Timer: %lu ms, %s\n", (unsigned long)actual, m.repeat ? "repeat" : "oneshot");
}
} // 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();
// Configure timer callback (polling mode)
auto cfg = unit.config();
cfg.on_timer = on_timer;
unit.config(cfg);
// 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 system clock from RTC (stored as GMT) and set timezone
{
m5::unit::pcf8563::rtc_datetime_t dt{};
unit.readDateTime(dt);
struct tm gmt = dt.to_tm();
time_t t = mktime(&gmt);
struct timeval tv{t, 0};
settimeofday(&tv, nullptr);
setenv("TZ", YOUR_TIMEZONE, 1);
tzset();
M5_LOGI("System clock set from RTC (GMT), TZ=%s", YOUR_TIMEZONE);
}
// Create sprite for flicker-free drawing (2-bit palette, internal RAM)
canvas.setPsram(false);
canvas.setColorDepth(2);
int32_t sw = lcd.width() < 320 ? lcd.width() : 320;
int32_t sh = lcd.height() < 240 ? lcd.height() : 240;
canvas.createSprite(sw, sh);
canvas.setFont(lcd.getFont());
canvas.setTextSize(lcd.getTextSizeX(), lcd.getTextSizeY());
canvas.setPaletteColor(0, TFT_BLACK);
canvas.setPaletteColor(1, TFT_WHITE);
canvas.setPaletteColor(2, TFT_BLUE);
canvas.setPaletteColor(3, TFT_DARKGREY);
// Start with first mode
start_timer();
}
void loop()
{
M5.update();
Units.update();
static time_t prev{};
time_t now = time(nullptr);
// Timer fired: light up indicator + beep
if (timer_fired) {
timer_fired = false;
indicator_lit = true;
M5.Log.printf("Timer fired! count=%lu\n", (unsigned long)timer_count);
M5.Speaker.tone(2000, 20);
prev = 0; // force redraw
}
// BtnA click: cycle mode
if (M5.BtnA.wasClicked()) {
mode_idx = (mode_idx + 1) % m5::stl::size(MODES);
timer_count = 0;
start_timer();
prev = 0; // force redraw
}
// Update display every second
if (now != prev) {
struct tm local = *localtime(&now);
auto& m = MODES[mode_idx];
uint32_t count = timer_count;
// Serial
M5.Log.printf("%04d-%02d-%02d(%s) %02d:%02d:%02d Timer:%us %s Count:%lu\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, m.seconds, m.repeat ? "repeat" : "oneshot", (unsigned long)count);
// Draw to canvas (off-screen), then push to display
canvas.fillScreen(TFT_BLACK);
canvas.setCursor(0, 0);
canvas_printf("%04d-%02d-%02d(%s)\n%02d:%02d:%02d\n\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);
if (m.seconds == 0) {
canvas_printf("Timer: OFF\nCount: %lu\n\n[BtnA] Start\n", (unsigned long)count);
} else {
canvas_printf("Timer: %u sec\nMode: %s\nCount: %lu\n\n[BtnA] Change\n", m.seconds,
m.repeat ? "repeat" : "oneshot", (unsigned long)count);
}
draw_indicator(indicator_lit);
indicator_lit = false;
canvas.pushSprite(0, 0);
prev = now;
}
}
@@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Example using M5UnitUnified for UnitRTC (PCF8563/BM8563/HYM8563)
*/
#include "main/PlotToSerial.cpp"
@@ -0,0 +1,303 @@
/*
* 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();
}
}
+17
View File
@@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5UnitUnifiedRTC.h
@brief Main header of M5UnitRTC using M5UnitUnified
*/
#ifndef M5_UNIT_UNIFIED_RTC_H
#define M5_UNIT_UNIFIED_RTC_H
#ifdef __cplusplus
#include "M5UnitUnifiedRTC.hpp"
#else
#error M5UnitUnifiedRTC requires a C++ compiler, please change file extension to .cc or .cpp
#endif
#endif
+27
View File
@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5UnitUnifiedRTC.hpp
@brief Main header of M5UnitRTC using M5UnitUnified
@mainpage M5Unit-RTC
Library for Unit-RTC using M5UnitUnified.
*/
#if defined(_Unit_RTC_H__)
#error "DO NOT USE it at the same time as conventional libraries"
#endif
#ifndef M5_UNIT_UNIFIED_RTC_HPP
#define M5_UNIT_UNIFIED_RTC_HPP
#include "unit/unit_PCF8563.hpp"
//! @brief Alias
using UnitBM8563 = m5::unit::UnitPCF8563;
using UnitHYM8563 = m5::unit::UnitPCF8563;
using UnitRTC = m5::unit::UnitPCF8563;
#endif
+4
View File
@@ -6,6 +6,10 @@
* @version V0.0.2
* @date 2022-07-29
*/
#if defined(M5_UNIT_UNIFIED_RTC_HPP)
#error "DO NOT USE it at the same time as M5UnitUnified libraries"
#endif
#ifndef _Unit_RTC_H__
#define _Unit_RTC_H__
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+256
View File
@@ -0,0 +1,256 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file unit_PCF8563_types.hpp
@brief Type definitions for PCF8563 Unit
*/
#ifndef M5_UNIT_RTC_UNIT_PCF8563_TYPES_HPP
#define M5_UNIT_RTC_UNIT_PCF8563_TYPES_HPP
#include <cstdint>
#include <tuple>
#include <time.h>
namespace m5 {
namespace unit {
namespace pcf8563 {
/*!
@struct rtc_time_t
@brief Time of day (hours, minutes, seconds)
*/
struct __attribute__((packed)) rtc_time_t {
int8_t hours{};
int8_t minutes{};
int8_t seconds{};
rtc_time_t(int8_t hours_ = -1, int8_t minutes_ = -1, int8_t seconds_ = -1)
: hours{hours_}, minutes{minutes_}, seconds{seconds_}
{
}
rtc_time_t(const tm& t) : hours{(int8_t)t.tm_hour}, minutes{(int8_t)t.tm_min}, seconds{(int8_t)t.tm_sec}
{
}
//! @brief Construct from struct tm (time fields only)
static inline rtc_time_t from_tm(const tm& t)
{
return rtc_time_t(t);
}
//! @brief Convert to struct tm (only time fields are set)
inline tm to_tm() const
{
tm t{};
t.tm_hour = hours;
t.tm_min = minutes;
t.tm_sec = seconds;
return t;
}
};
///@name rtc_time_t comparison operators
///@{
inline bool operator==(const rtc_time_t& lhs, const rtc_time_t& rhs)
{
return lhs.hours == rhs.hours && lhs.minutes == rhs.minutes && lhs.seconds == rhs.seconds;
}
inline bool operator!=(const rtc_time_t& lhs, const rtc_time_t& rhs)
{
return !(lhs == rhs);
}
inline bool operator<(const rtc_time_t& lhs, const rtc_time_t& rhs)
{
return std::tie(lhs.hours, lhs.minutes, lhs.seconds) < std::tie(rhs.hours, rhs.minutes, rhs.seconds);
}
inline bool operator<=(const rtc_time_t& lhs, const rtc_time_t& rhs)
{
return !(rhs < lhs);
}
inline bool operator>(const rtc_time_t& lhs, const rtc_time_t& rhs)
{
return rhs < lhs;
}
inline bool operator>=(const rtc_time_t& lhs, const rtc_time_t& rhs)
{
return !(lhs < rhs);
}
///@}
/*!
@struct rtc_date_t
@brief Calendar date (year, month, day, weekday)
*/
struct __attribute__((packed)) rtc_date_t {
//! year 1900-2099
int16_t year{};
//! month 1-12
int8_t month{};
//! date 1-31
int8_t date{};
//! weekDay 0:sun / 1:mon / 2:tue / 3:wed / 4:thu / 5:fri / 6:sat
int8_t weekDay{};
rtc_date_t(int16_t year_ = 2000, int8_t month_ = 1, int8_t date_ = -1, int8_t weekDay_ = -1)
: year{year_}, month{month_}, date{date_}, weekDay{weekDay_}
{
}
rtc_date_t(const tm& t)
: year{(int16_t)(t.tm_year + 1900)},
month{(int8_t)(t.tm_mon + 1)},
date{(int8_t)t.tm_mday},
weekDay{(int8_t)t.tm_wday}
{
}
//! @brief Construct from struct tm (date fields only)
static inline rtc_date_t from_tm(const tm& t)
{
return rtc_date_t(t);
}
//! @brief Convert to struct tm (only date fields are set)
inline tm to_tm() const
{
tm t{};
t.tm_year = year - 1900;
t.tm_mon = month - 1;
t.tm_mday = date;
t.tm_wday = weekDay;
return t;
}
};
///@name rtc_date_t comparison operators (weekDay is excluded)
///@{
inline bool operator==(const rtc_date_t& lhs, const rtc_date_t& rhs)
{
return lhs.year == rhs.year && lhs.month == rhs.month && lhs.date == rhs.date;
}
inline bool operator!=(const rtc_date_t& lhs, const rtc_date_t& rhs)
{
return !(lhs == rhs);
}
inline bool operator<(const rtc_date_t& lhs, const rtc_date_t& rhs)
{
return std::tie(lhs.year, lhs.month, lhs.date) < std::tie(rhs.year, rhs.month, rhs.date);
}
inline bool operator<=(const rtc_date_t& lhs, const rtc_date_t& rhs)
{
return !(rhs < lhs);
}
inline bool operator>(const rtc_date_t& lhs, const rtc_date_t& rhs)
{
return rhs < lhs;
}
inline bool operator>=(const rtc_date_t& lhs, const rtc_date_t& rhs)
{
return !(lhs < rhs);
}
///@}
/*!
@struct rtc_datetime_t
@brief Combined date and time
*/
struct __attribute__((packed)) rtc_datetime_t {
rtc_date_t date{};
rtc_time_t time{};
rtc_datetime_t() = default;
rtc_datetime_t(const rtc_date_t& d, const rtc_time_t& t) : date{d}, time{t}
{
}
rtc_datetime_t(const tm& t) : date{t}, time{t}
{
}
//! @brief Assign from struct tm
inline rtc_datetime_t& operator=(const tm& t)
{
date = rtc_date_t(t);
time = rtc_time_t(t);
return *this;
}
//! @brief Convert to struct tm (explicit)
explicit inline operator tm() const
{
return to_tm();
}
//! @brief Construct from struct tm
static inline rtc_datetime_t from_tm(const tm& t)
{
return rtc_datetime_t(t);
}
//! @brief Convert to struct tm
inline tm to_tm() const
{
tm t{};
t.tm_year = date.year - 1900;
t.tm_mon = date.month - 1;
t.tm_mday = date.date;
t.tm_wday = date.weekDay;
t.tm_hour = time.hours;
t.tm_min = time.minutes;
t.tm_sec = time.seconds;
t.tm_isdst = -1;
return t;
}
};
///@name rtc_datetime_t comparison operators (weekDay is excluded)
///@{
inline bool operator==(const rtc_datetime_t& lhs, const rtc_datetime_t& rhs)
{
return lhs.date == rhs.date && lhs.time == rhs.time;
}
inline bool operator!=(const rtc_datetime_t& lhs, const rtc_datetime_t& rhs)
{
return !(lhs == rhs);
}
inline bool operator<(const rtc_datetime_t& lhs, const rtc_datetime_t& rhs)
{
if (lhs.date != rhs.date) {
return lhs.date < rhs.date;
}
return lhs.time < rhs.time;
}
inline bool operator<=(const rtc_datetime_t& lhs, const rtc_datetime_t& rhs)
{
return !(rhs < lhs);
}
inline bool operator>(const rtc_datetime_t& lhs, const rtc_datetime_t& rhs)
{
return rhs < lhs;
}
inline bool operator>=(const rtc_datetime_t& lhs, const rtc_datetime_t& rhs)
{
return !(lhs < rhs);
}
///@}
/*!
@enum TimerClock
@brief Timer clock source for PCF8563 countdown timer
*/
enum class TimerClock : uint8_t {
Hz4096 = 0, //!< 4.096 kHz (~244us resolution)
Hz64 = 1, //!< 64 Hz (~15.6ms resolution)
Hz1 = 2, //!< 1 Hz (1 second resolution)
HzPM = 3, //!< 1/60 Hz (1 minute resolution)
};
/*!
@enum ClockOutput
@brief CLKOUT pin output frequency (register 0x0D)
*/
enum class ClockOutput : uint8_t {
None = 0x00, //!< CLKOUT disabled (high-impedance)
Hz32768 = 0x80, //!< 32.768 kHz (FE=1, FD=00)
Hz1024 = 0x81, //!< 1.024 kHz (FE=1, FD=01)
Hz32 = 0x82, //!< 32 Hz (FE=1, FD=10)
Hz1 = 0x83, //!< 1 Hz (FE=1, FD=11)
};
} // namespace pcf8563
} // namespace unit
} // namespace m5
#endif
+60
View File
@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
main for UnitTest on native
*/
#include <gtest/gtest.h>
// C++ version
#if __cplusplus >= 202002L
#pragma message "C++20 or later"
#elif __cplusplus >= 201703L
#pragma message "C++17 or later"
#elif __cplusplus >= 201402L
#pragma message "C++14 or later"
#elif __cplusplus >= 201103L
#pragma message "C++11 or later"
#else
#error "Need C++11 or later"
#endif
// Compiler
#if defined(__clang__)
#pragma message "Clang"
#elif defined(_MSC_VER)
#pragma message "MSVC"
#elif defined(__BORLANDC__)
#pragma message "BORLANDC"
#elif defined(__MINGW32__) || defined(__MINGW64__)
#pragma message "MINGW"
#elif defined(__INTEL_COMPILER)
#pragma message "ICC"
#elif defined(__GNUG__)
#pragma message "GCC"
#else
#pragma message "Unknown compiler"
#endif
/*
For native test, this main() is used.
If the Arduino framework is used, the framework library main is used.
*/
#if !defined(ARDUINO)
int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
#ifdef GTEST_FILTER
::testing::GTEST_FLAG(filter) = GTEST_FILTER;
#endif
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
RUN_ALL_TESTS();
#pragma GCC diagnostic pop
// Always return zero-code and allow PlatformIO to parse results
return 0;
}
#endif
+63
View File
@@ -0,0 +1,63 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
main for UnitTest on embedded
*/
#include <gtest/gtest.h>
#include <M5Unified.h>
#include <esp_system.h>
#pragma message "Embedded setup/loop"
#if __has_include(<esp_idf_version.h>)
#include <esp_idf_version.h>
#else // esp_idf_version.h has been introduced in Arduino 1.0.5 (ESP-IDF3.3)
#define ESP_IDF_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(3, 2, 0)
#endif
namespace {
auto& lcd = M5.Display;
} // namespace
void test()
{
lcd.fillRect(0, 0, lcd.width() >> 1, lcd.height(), RUN_ALL_TESTS() ? TFT_RED : TFT_GREEN);
}
void setup()
{
auto m5cfg = M5.config();
m5cfg.pmic_button = false; // Disable BtnPWR
m5cfg.internal_imu = false; // Disable internal IMU
m5cfg.internal_rtc = false; // Disable internal RTC
M5.begin(m5cfg);
M5_LOGI("CPP %ld", __cplusplus);
M5_LOGI("ESP-IDF Version %d.%d.%d", (ESP_IDF_VERSION >> 16) & 0xFF, (ESP_IDF_VERSION >> 8) & 0xFF,
ESP_IDF_VERSION & 0xFF);
M5_LOGI("BOARD:%X", M5.getBoard());
M5_LOGI("Heap: %u", esp_get_free_heap_size());
lcd.clear(TFT_DARKGRAY);
::testing::InitGoogleTest();
#ifdef GTEST_FILTER
::testing::GTEST_FLAG(filter) = GTEST_FILTER;
#endif
}
void loop()
{
test();
#if 0
delay(1000);
esp_restart();
#endif
while (true) {
delay(10000);
}
}
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More