You've already forked M5Unit-RTC
mirror of
https://github.com/m5stack/M5Unit-RTC.git
synced 2026-05-20 10:33:20 -07:00
243 lines
7.4 KiB
C++
243 lines
7.4 KiB
C++
/*
|
|
* 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
|
|
}
|
|
}
|