I am building a small art project to display my current age with an accuracy of 1/100th of a second. I am using a XIAO SAMD21, a DS3231 RTC, and a 128x32 OLED screen. Here is what it looks like right now without the battery:
My aim is to fit this and a battery into a locket / necklace. I want to make as low-power as possible so that it can last at least an hour or two on a 75 mAh Li-po. I can't use a bigger battery since it wouldn't fit in the necklace.
I wrote the code to the best of my ability and it looks like it works really well. I just want a sanity check to make sure there isn't a simpler way of doing things. In particular I feel like the calculation of the age in the main loop, the syncing with RTC, and maybe the display updates, could be done in a better way. Maybe there are some things I do with interrupts and the 'alarms' in the RTC? Just power on the display every other second maybe. I am also considering using an epaper display and removing the power LED.
The code is attached here. I use the Adafruit libraries to drive the SSD1306 on the OLED screen, and one of the DS3231 libraries to interface with the RTC. Parts of it are adapted from examples, so there may be some extraneous imports or declarations I don't need.
// TUNIO 2024
// Age Pendant
// TO DO:
// current loop is 17 to 18 milliseconds long - need less than 5 for accurate centi-second counting
// Libraries
#include <SPI.h>
#include <Wire.h>
#include <DS3231.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// OLED Setup ~ copied from example
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// RTC Setup
RTClib myRTC;
// Initializations for periodic sync with RTC
uint32_t remote_time = 0;
uint32_t sync_interval_ms = 1800000; // sync with RTC every sync_interval_ms milliseconds // about an hour is good
uint32_t last_millis_synced = 0; // the last time we synced with RTC
uint32_t system_time = 0; // the local system time
// Initializations for age calcs
// QUESTION us 32 bit enough to store the age of someone? What if they live to 100?
uint32_t born_utime = 788888888; // date of birth in unixtime // 788888888
uint32_t total_age_secs = 0;
uint32_t sec_remainder = 0;
uint8_t age_years = 0;
uint8_t age_months = 0;
uint8_t age_days = 0;
uint8_t age_hours = 0;
uint8_t age_mins = 0;
uint8_t age_secs = 0;
// initializations for centi-second counting
uint8_t oldsec = -1;
uint8_t newsec = -1;
uint32_t offset = 0;
uint8_t age_cs = 0;
// screen saving vars
bool invert = false;
//// debug
//uint32_t start_millis=0;
//uint32_t end_millis=0;
void setup() {
Serial.begin(57600);
Wire.begin();
delay(250);
// display setup
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.dim(true);
display.setTextColor(SSD1306_WHITE);
display.clearDisplay();
display.setTextSize(2); // 1 default
display.setRotation(3); //0 default, 1, 2, 3
// turn off built in LED
pinMode(LED_BUILTIN , OUTPUT);
digitalWrite(LED_BUILTIN , HIGH);
// do first sync
sync_time_with_rtc();
}
void loop() {
// //debug
// start_millis = millis();
// sync every sync_interval_ms with RTC
if (millis() - last_millis_synced >= sync_interval_ms) {
sync_time_with_rtc();
}
// Calculate time elapsed since birth in years, months, etc.
system_time = local_time_now();
total_age_secs = system_time - born_utime; // age in seconds
age_years = num_time_units(total_age_secs, 31558149.756); // second arg is num of: secs in a year
age_months = num_time_units(sec_remainder, 2629745.568); // ... secs in a month
age_days = num_time_units(sec_remainder, 86401.505); // ... secs in day
age_hours = num_time_units(sec_remainder, 3600); // ... in an hour
age_mins = num_time_units(sec_remainder, 60); // ... in an minute
age_secs = sec_remainder;
// display the age calculated
show_age_on_display(5, 3); // input top left coordinates of where the age will be displayed
// //debug
// end_millis = millis();
// Serial.println(end_millis - start_millis,DEC);
}
// Prints the provided age to the LCD with the correct formatting
void show_age_on_display(int col, int line) {
newsec = age_secs;
if (newsec != oldsec) {
offset = -1 * (millis() % 1000);
if (age_secs % 11 == 0) {invert = ! invert;} // invert display to save pixels burning every 11 secs
oldsec = age_secs;
}
display.clearDisplay();
display_at_location(age_years, col, line);
display_at_location(age_months, col, line + (13 + 5) * 1);
display_at_location(age_days, col, line + (13 + 5) * 2);
display_at_location(age_hours, col, line + (13 + 5) * 3);
display_at_location(age_mins, col, line + (13 + 5) * 4);
display_at_location(age_secs, col, line + (13 + 5) * 5);
age_cs = floor(((millis() + offset) % 1000) / 10);
display_at_location(age_cs, col, line + (13 + 5) * 6);
display.fillRect(0, display.height() - 18, display.width(), 18 - ((18 * age_cs) / 100), SSD1306_INVERSE); // progress bar completes every second
display.invertDisplay(invert);
display.display();
}
// display integer value <100 at given location with "0" padding
void display_at_location(uint8_t value, uint8_t this_col, uint8_t this_line){
display.setCursor(this_col, this_line);
if (value < 10) display.print(F("0"));
display.print(value, DEC);
}
// input seconds and returns number of whole time unit, sets remainder variable
uint32_t num_time_units( uint32_t secs, float time_unit) {
int num_units = floor(secs / time_unit);
sec_remainder = secs - round(num_units * time_unit);
return num_units;
}
// syncs time with the RTC: call on sync_interval, power ons, AND when wake from sleep
void sync_time_with_rtc() {
digitalWrite(LED_BUILTIN , LOW); // briefly flash the LED every sync with the RTC
remote_time = myRTC.now().unixtime();
last_millis_synced = millis();
system_time = local_time_now();
digitalWrite(LED_BUILTIN , HIGH);
}
// fetches the unixtime according to Arduino
uint32_t local_time_now() {
return remote_time + floor((millis() - last_millis_synced) / 1000);
}