Проверка кода таймера с использованием часов реального времени и OLED-экрана

power code-review rtc oled seeeduino-xiao

Я создаю небольшой арт-проект, который будет отображать мой текущий возраст с точностью до 1/100 секунды. Я использую XIAO SAMD21, DS3231 RTC и OLED-экран 128x32. Вот как это выглядит сейчас без аккумулятора:

Моя цель — поместить это и батарейку в медальон/ожерелье. Хочу сделать максимально маломощным, чтобы он мог проработать хотя бы час-два на Li-po 75 мАч. Я не могу использовать батарею большего размера, потому что она не помещается в ожерелье.

Я написал код в меру своих возможностей, и похоже, что он работает очень хорошо. Я просто хочу проверить здравомыслие, чтобы убедиться, что нет более простого способа сделать что-то. В частности, мне кажется, что расчет возраста в основном цикле, синхронизация с RTC и, возможно, обновления дисплея можно было бы выполнить лучше. Может быть, я что-то делаю с прерываниями и «сигнализациями» в RTC? Просто включайте дисплей каждую секунду. Я также подумываю об использовании дисплея на электронной бумаге и удалении светодиода питания.

Код прилагается здесь. Я использую библиотеки Adafruit для управления SSD1306 на OLED-экране и одну из библиотек DS3231 для взаимодействия с RTC. Частично он адаптирован из примеров, поэтому могут быть некоторые посторонние импорты или объявления, которые мне не нужны.

// TUNIO 2024
// Подвеска возраста

// ДЕЛАТЬ:
// длина текущего цикла составляет от 17 до 18 миллисекунд — для точного подсчета сантисекунд требуется менее 5

// Библиотеки
#include <SPI.h>
#include <Wire.h>
#include <DS3231.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// Настройка OLED ~ скопировано из примера
#define SCREEN_WIDTH 128 // Ширина OLED-дисплея в пикселях
#define SCREEN_HEIGHT 32 // Высота OLED-дисплея в пикселях
#define OLED_RESET     -1 // Номер контакта сброса (или -1, если используется общий контакт сброса Arduino)
#define SCREEN_ADDRESS 0x3C // См. таблицу данных для адреса; 0x3D для 128x64, 0x3C для 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Настройка RTC
RTClib myRTC;

// Инициализация для периодической синхронизации с RTC
uint32_t remote_time = 0;
uint32_t sync_interval_ms = 1800000; // синхронизация с RTC каждые sync_interval_ms миллисекунды // около часа — это хорошо
uint32_t last_millis_synced = 0; // когда мы в последний раз синхронизировались с RTC
uint32_t system_time = 0; // локальное системное время

// Инициализация для расчета возраста
// ВОПРОС: достаточно ли 32 бит, чтобы хранить чей-то возраст? Что, если они доживут до 100 лет?
uint32_t born_utime = 788888888; // дата рождения в 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;

// инициализация для отсчета долей секунды
uint8_t oldsec = -1;
uint8_t newsec = -1;
uint32_t offset = 0;
uint8_t age_cs = 0;

// переменные сохранения экрана
bool invert = false;

//// отладка
//uint32_t start_millis=0;
//uint32_t end_millis=0;

void setup() {
  Serial.begin(57600);
  Wire.begin();
  delay(250);

  // настройка дисплея
  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 по умолчанию
  display.setRotation(3); //0 по умолчанию, 1, 2, 3

  // выключаем встроенный светодиод
  pinMode(LED_BUILTIN , OUTPUT);
  digitalWrite(LED_BUILTIN , HIGH);

  // делаем первую синхронизацию
  sync_time_with_rtc();
}


void loop() {
// //отладка
// start_millis = millis();

  // синхронизируем каждый sync_interval_ms с RTC
  if (millis() - last_millis_synced >= sync_interval_ms) {
    sync_time_with_rtc();
  }

  // Вычисляем время, прошедшее с момента рождения, в годах, месяцах и т. д.
  system_time = local_time_now();
  total_age_secs = system_time - born_utime; // возраст в секундах
  age_years = num_time_units(total_age_secs, 31558149.756); // второй аргумент — число: секунд в году
  age_months = num_time_units(sec_remainder, 2629745.568); // ... секунд в месяц
  age_days = num_time_units(sec_remainder, 86401.505); // ... секунд в день
  age_hours = num_time_units(sec_remainder, 3600); // ... через час
  age_mins = num_time_units(sec_remainder, 60); // ... через минуту
  age_secs = sec_remainder;

  // отображаем вычисленный возраст
  show_age_on_display(5, 3); //вводим верхние левые координаты места, где будет отображаться возраст

// //отладка
// end_millis = millis();
// Serial.println(end_millis - start_millis,DEC);
}


// Печатает указанный возраст на ЖК-дисплее в правильном формате
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;} // инвертируем отображение, чтобы пиксели не горели каждые 11 секунд
    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); // индикатор выполнения завершается каждую секунду

  display.invertDisplay(invert);
  display.display();
}

// отображаем целое значение <100 в заданном месте с "0" прокладка
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);
}


// вводим секунды и возвращаем количество целых единиц времени, устанавливаем переменную остатка
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;
}


// синхронизирует время с RTC: вызов sync_interval, включения питания И при выходе из спящего режима
void sync_time_with_rtc() {
  digitalWrite(LED_BUILTIN , LOW); // кратковременно мигаем светодиодом при каждой синхронизации с RTC
  remote_time = myRTC.now().unixtime();
  last_millis_synced = millis();
  system_time = local_time_now();
  digitalWrite(LED_BUILTIN , HIGH);
}


// извлекает unixtime согласно Arduino
uint32_t local_time_now() {
  return remote_time + floor((millis() - last_millis_synced) / 1000);
}

, 👍1

Обсуждение

используйте функцию для замены повторяющегося кода, @jsotola

Я обнаружил одну вещь: millis() будет перезапускаться каждые ~ 49 дней, если по какой-то причине Arduino не выполняет цикл включения и выключения. Итак, я следую этому и обновляю код https://arduinoprosto.ru/q/12587/how-can-i-handle-the-millis-rollover., @Mir


1 ответ


Лучший ответ:

0

Я вижу здесь довольно много вычислений с плавающей запятой. Начиная с SAMD21 не имеет модуля с плавающей запятой, все операции с плавающей запятой реализованы в программном обеспечении, которое может быть довольно дорогим. В качестве оптимизации предлагаю оставаться как можно дольше в целочисленной сфере. Например:

uint32_t total_minutes = total_age_secs / 60;
age_secs = total_seconds - total_minutes * 60;
uint32_t total_hours = total_minutes / 60;
age_mins = total_minutes - total_hours * 60;
uint16_t total_days = total_hours / 24;
age_hours = total_hours - total_days * 24;
age_years = total_days / 365;
uint16_t remaining_days = total_days - age_years * 365;
age_months = remaining_days * 12 / 365;
age_days = remaining_days - age_months * 365 / 12;

Обратите внимание, что здесь имеется некоторое округление: день считается ровно 86400 секунд вместо 86399,9999 (не знаю, где вы нашли номер 86401.505), в году предполагается 365 дней, а все месяцы предполагается равным. Если вы нашли эти приближения слишком грубыми (в основном в году), вы можете использовать 365,25 = 1461/4 дня в году и 1461/48 дней в месяц. Это можно сделать, заменив последние четыре строки с:

age_years = total_days * 4 / 1461;
uint16_t remaining_days = total_days - age_years * 1461 / 4;
age_months = remaining_days * 48 / 1461;
age_days = remaining_days - age_months * 1461 / 48;
,

Я почти закончил обновление кода, возможно, вам будет интересно его увидеть. Я удаляю все операции с плавающей запятой и пытаюсь максимально увеличить разрешение, используя только 32-битные целые числа и несколько поправочных коэффициентов. Кажется, я получил «86401,505», наивно разделив длину звездного года в секундах на 365,25 дней, я исправлю это в следующей версии. Думаю, я немного запутался в нескольких разных определениях года., @Mir