Skip to content

Instantly share code, notes, and snippets.

@simonjgreen
Created March 20, 2023 09:02
Show Gist options
  • Save simonjgreen/ddfb79169a28beeb83827a35590e3475 to your computer and use it in GitHub Desktop.
Save simonjgreen/ddfb79169a28beeb83827a35590e3475 to your computer and use it in GitHub Desktop.
M5Stick C Plus World Clock with M5 Digiclocks
#include <M5StickCPlus.h>
#include <M5UNIT_DIGI_CLOCK.h>
#include <Wire.h>
#include <WiFi.h>
#include "time.h"
#include <NTPClient.h>
// Wireless details
const char* ssid = "SSID";
const char* password = "PSK";
// Screen settings
uint8_t clockBrightness = 5; // 3 is good
uint8_t tftBrightness = 11; // 8 is good
// Define time parameters
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 3600;
const int ntpSyncPeriod_min = 120;
unsigned long lastNtpSyncTime = 0;
unsigned long now = 0;
// Create UDP instance for NTP
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, ntpServer, gmtOffset_sec, daylightOffset_sec);
RTC_TimeTypeDef RTC_TimeStruct;
RTC_DateTypeDef RTC_DateStruct;
// Pins for SPI
#define SDA 32
#define SCL 33
// Clock addresses and timezones
const uint8_t clockAddresses[] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35};
const int32_t timezoneOffsets[] = {-7, -6, -5, 0, 1, 10};
const size_t clockCount = sizeof(clockAddresses) / sizeof(clockAddresses[0]);
static_assert(clockCount == sizeof(timezoneOffsets) / sizeof(timezoneOffsets[0]), "Timezone offsets count must match clock count");
M5UNIT_DIGI_CLOCK Digiclocks[clockCount];
void setup() {
M5.begin();
M5.Lcd.setRotation(3);
M5.Lcd.fillScreen(BLACK);
M5.Axp.EnableCoulombcounter();
M5.Lcd.println("Entering boot");
// Initial sync
ntpsync();
now = millis();
lastNtpSyncTime = now;
// Init the SPI
delay(2000);
Wire.begin(SDA, SCL);
// Clocks init
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0,0);
M5.Lcd.println("Initialising Clocks");
for (size_t i = 0; i < clockCount; i++) {
if (Digiclocks[i].begin(&Wire, SDA, SCL, clockAddresses[i])) {
M5.Lcd.printf("Clock %d init successful\n", i + 1);
} else {
M5.Lcd.printf("Clock %d init error\n", i + 1);
while (1);
}
char offsetString[6];
sprintf(offsetString, " T:%d", timezoneOffsets[i]);
Digiclocks[i].setString(offsetString);
Digiclocks[i].setBrightness(clockBrightness);
}
M5.Lcd.fillScreen(BLACK);
}
void ntpsync() {
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0);
// Connect to WiFi
M5.Lcd.printf("\nConnecting to %s", ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password); // Connect wifi and return connection status.
while (WiFi.status() != WL_CONNECTED) { // If the wifi connection fails.
delay(500); // delay 0.5s.
M5.Lcd.print(".");
}
M5.Lcd.println("\nCONNECTED!");
// Get time from NTP
M5.Lcd.print("Synchronizing time...");
timeClient.begin();
while (timeClient.getEpochTime() < 1000) {
M5.Lcd.print(".");
timeClient.update();
}
time_t epoch_time = timeClient.getEpochTime();
M5.Lcd.printf("\nTime fetched: %u\n", epoch_time);
delay(1000);
// Set RTC to NTP
struct tm timeinfo;
gmtime_r(&epoch_time, &timeinfo);
RTC_TimeTypeDef rtc_time = {
timeinfo.tm_hour,
timeinfo.tm_min,
timeinfo.tm_sec
};
M5.Rtc.SetTime(&rtc_time);
M5.Lcd.println("Time synchronized");
RTC_DateTypeDef rtc_date = {
timeinfo.tm_year + 1901, // tm_year is years since 1900
timeinfo.tm_mon + 1, // tm_mon is months since January (0 to 11)
timeinfo.tm_mday,
timeinfo.tm_wday + 1 // tm_wday is days since Sunday (0 to 6)
};
M5.Rtc.SetData(&rtc_date);
M5.Lcd.println("Date synchronized");
// Drop WiFi once in sync
WiFi.disconnect(true); // Disconnect wifi.
WiFi.mode(WIFI_OFF); // Set the wifi mode to off.
M5.Lcd.println("Wifi Disconnected");
// Pause a moment so the user can see feedback
delay (2000);
M5.Lcd.fillScreen(BLACK);
}
void cycleBrightness() {
clockBrightness = (clockBrightness % 8) + 1;
for (size_t i = 0; i < clockCount; i++) {
Digiclocks[i].setBrightness(clockBrightness);
}
}
void showDebug() {
// debug display
M5.Lcd.setTextSize(1);
M5.Lcd.setCursor(65, 0, 2);
M5.Axp.ScreenBreath(tftBrightness);
M5.Lcd.println("RTC World Clock");
M5.Rtc.GetTime(&RTC_TimeStruct);
M5.Rtc.GetData(&RTC_DateStruct);
float voltage = M5.Axp.GetBatVoltage();
float percentage = ((voltage - 3.3) / 0.9) * 100; // assuming a 3.3V minimum and 4.2V maximum voltage
float chargingCurrent = M5.Axp.GetBatChargeCurrent();
bool charging = chargingCurrent > 0;
M5.Lcd.setCursor(0, 15);
M5.Lcd.printf("Date: %04d-%02d-%02d\n",RTC_DateStruct.Year, RTC_DateStruct.Month,RTC_DateStruct.Date);
M5.Lcd.printf("Time: %02d : %02d : %02d\n",RTC_TimeStruct.Hours, RTC_TimeStruct.Minutes, RTC_TimeStruct.Seconds);
M5.Lcd.printf("Battery: %.2fV %.2f%% \n", voltage, percentage);
M5.Lcd.println(charging ? "Charging: Yes" : "Charging: No ");
M5.Lcd.printf("Clock Brightness: %u\n", clockBrightness);
M5.Lcd.print("Offsets: ");
for (size_t i = 0; i < clockCount; i++) {
M5.Lcd.printf("%+d", timezoneOffsets[i]);
if (i < clockCount - 1) {
M5.Lcd.print(" ");
}
}
}
void loop() {
static unsigned long lastUpdateTime = 0;
static char clockBuffers[clockCount][6] = {"88:88"};
static char prevClockBuffers[clockCount][6] = {"88:88"};
now = millis();
M5.Rtc.GetTime(&RTC_TimeStruct);
// Actions on buttons
M5.update();
M5.BtnA.wasPressed() ? cycleBrightness() : (void)0;
M5.BtnB.wasPressed() ? ntpsync() : (void)0;
// If the requisite number of minutes have passed resync NTP
(now - lastNtpSyncTime >= ntpSyncPeriod_min * 60000) ? ntpsync() : (void)0;
if (now - lastUpdateTime >= 1000) {
lastUpdateTime = now;
showDebug();
// Set each clock buffer against the RTC and push if they have changed
for (size_t i = 0; i < clockCount; i++) {
int adjusted_hour = (RTC_TimeStruct.Hours + timezoneOffsets[i]) % 24;
int adjusted_min = RTC_TimeStruct.Minutes;
sprintf(clockBuffers[i], "%02d:%02d", adjusted_hour, adjusted_min);
if (strcmp(clockBuffers[i], prevClockBuffers[i]) != 0) {
Digiclocks[i].setString(clockBuffers[i]);
strcpy(prevClockBuffers[i], clockBuffers[i]);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment