You've already forked M5Unit-RTC
mirror of
https://github.com/m5stack/M5Unit-RTC.git
synced 2026-05-20 10:33:20 -07:00
Add UnitRTC using M5UnitUnified
This commit is contained in:
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
Executable
+11
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user