Введение
DS1302 — модуль часов реального времени (RTC) с собственной батарейкой, продолжающий отслеживать текущую дату и время даже когда Arduino полностью обесточен — что отличает его от простого программного счётчика времени на основе millis(), сбрасывающегося при каждой перезагрузке платы. В статье разберём установку и считывание текущего времени, что станет основой для журналирования событий в итоговом мини-проекте цикла.
Концепция
DS1302 хранит время в специальных регистрах (секунды, минуты, часы, день, месяц, год), доступных через простой трёхпроводной интерфейс (CLK, DATA, RST, отдельный от стандартного SPI/I2C). Библиотека RtcDS1302 (или аналогичная, например Rtc by Makuna) предоставляет удобный объектный интерфейс для установки и считывания времени, а также позволяет проверить, было ли время уже когда-то установлено (важно для первого включения с разряженной или впервые установленной батарейкой) или модуль работает с «обнулённым» состоянием.
Пример кода
// firmware/src/main.cpp — DS1302: установка времени при первом запуске, периодическая отправка
#include <ThreeWire.h>
#include <RtcDS1302.h>
ThreeWire myWire(6, 7, 8); // DAT, CLK, RST
RtcDS1302<ThreeWire> rtc(myWire);
void setup()
{
Serial.begin(9600);
rtc.Begin();
// Время компиляции скетча — удобный способ единоразовой установки времени при первой загрузке
RtcDateTime compiledTime = RtcDateTime(__DATE__, __TIME__);
if (!rtc.IsDateTimeValid()) {
Serial.println("RTC:NOT_SET_USING_COMPILE_TIME");
rtc.SetDateTime(compiledTime);
}
if (!rtc.GetIsRunning()) {
rtc.SetIsRunning(true); // модуль может быть остановлен, например, после полной замены батарейки
}
delay(100);
Serial.println("READY");
}
void loop()
{
static unsigned long lastTimeSend = 0;
if (millis() - lastTimeSend >= 1000) {
const RtcDateTime now = rtc.GetDateTime();
// Формат YYYY-MM-DD HH:MM:SS — однозначный, легко разбираемый на стороне Qt
char buffer[20];
snprintf(buffer, sizeof(buffer), "%04u-%02u-%02u %02u:%02u:%02u",
now.Year(), now.Month(), now.Day(),
now.Hour(), now.Minute(), now.Second());
Serial.print("RTC:");
Serial.println(buffer);
lastTimeSend = millis();
}
static String inputBuffer = "";
while (Serial.available() > 0) {
char c = Serial.read();
if (c == 'n') {
processCommand(inputBuffer);
inputBuffer = "";
} else if (inputBuffer.length() < 64) {
inputBuffer += c;
}
}
}
void processCommand(String command)
{
command.trim();
if (command.startsWith("RTC:SET:")) {
// Установка времени от Qt-приложения — формат: RTC:SET:2026,6,23,14,30,0
int y, mo, d, h, mi, s;
sscanf(command.c_str() + 8, "%d,%d,%d,%d,%d,%d", &y, &mo, &d, &h, &mi, &s);
rtc.SetDateTime(RtcDateTime(y, mo, d, h, mi, s));
Serial.println("ACK:RTC:SET");
}
}
Пояснения к коду
rtc.IsDateTimeValid() проверяет, действительно ли время, хранящееся в модуле, корректно — это важно при самом первом использовании нового модуля или после полной разрядки его резервной батарейки, когда хранящееся значение может быть произвольным «мусором»; в этом случае прошивка автоматически устанавливает время компиляции самого скетча (RtcDateTime(__DATE__, __TIME__)) как разумное начальное приближение, что удобнее, чем оставлять время полностью неустановленным.
RTC:SET: — команда, позволяющая Qt-приложению явно установить точное текущее время на модуле (например, синхронизировав его с системным временем компьютера) — это более точный способ установки времени по сравнению с фиксированным временем компиляции скетча, которое отражает момент компиляции, а не момент реальной установки часов на конкретном физическом устройстве, и может отличаться на минуты или часы от реального текущего момента, особенно если прошивка собиралась заранее.
Подводные камни
- Разряженная резервная батарейка модуля DS1302 (обычно небольшая батарейка типа CR2032 или аналогичная) приводит к сбросу хранимого времени при каждом отключении основного питания — для устройств, развёрнутых на продолжительный срок без постоянного питания, состояние батарейки RTC-модуля нужно периодически проверять, поскольку без неё сам смысл использования RTC (сохранение времени между включениями) теряется, и модуль фактически вырождается в более сложную альтернативу простому
millis(). - Время компиляции скетча (
__DATE__,__TIME__) фиксируется в момент именно компиляции исходного кода, а не момент его реальной загрузки на конкретное устройство — если прошивка собрана заранее и загружена на устройство значительно позже (или одна и та же скомпилированная прошивка используется для нескольких устройств, прошиваемых в разное время), автоматически подставленное время окажется неточным, и для продакшен-устройств обязательна последующая точная установка времени черезRTC:SET, а не только полагание на время компиляции. - Различие часовых поясов между временем, установленным на RTC-модуле, и тем, что отображается в Qt-интерфейсе пользователю, если они не явно синхронизированы относительно одной согласованной системы отсчёта (UTC или конкретный локальный часовой пояс) — для проектов, где это важно (журналирование событий с точными временными метками для последующего анализа), стоит явно зафиксировать, в каком часовом поясе хранится время на устройстве, и выполнять соответствующее преобразование на стороне Qt при необходимости отображения в другом поясе.
- Прямой разбор строки времени через
sscanf()без проверки количества успешно разобранных полей (аналогично подводному камню про RGB) — при получении искажённой или неполной команды установки времени часть переменных останется неинициализированной, что может установить на RTC-модуле произвольное, некорректное значение времени без какой-либо явной индикации ошибки.