Проверка кода таймера с использованием часов реального времени и OLED-экрана
Я создаю небольшой арт-проект, который будет отображать мой текущий возраст с точностью до 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);
}
@Mir, 👍1
Обсуждение1 ответ
Лучший ответ:
Я вижу здесь довольно много вычислений с плавающей запятой. Начиная с 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
- Как установить управляющий регистр в модуле RTC DS3231 для Arduino UNO R3?
- Установить регистр управления на низкий уровень в ds3231 rtc
- Программа Arduino, использующая i2c, перестает работать после нескольких раз печати на OLED
- Arduino pow() делает девятки
- Помогите в личном проекте будильника, не могу записать время на семисегментный дисплей
- Платы Arduino не работают с powerbanks!
- Функции, задерживающие распознавание датчика жестов
- Какое максимальное энергопотребление Arduino Nano 3.0?
используйте функцию для замены повторяющегося кода, @jsotola
Я обнаружил одну вещь: millis() будет перезапускаться каждые ~ 49 дней, если по какой-то причине Arduino не выполняет цикл включения и выключения. Итак, я следую этому и обновляю код https://arduinoprosto.ru/q/12587/how-can-i-handle-the-millis-rollover., @Mir