ESP32 RTC DS3231

Pokud potřebujete na ESP32 spouštět naplánované úlohy, určitě se nemůžete spoléhat na vnitřní časování. Budete potřebovat nějaký externí modul reálného času (RTC), který vám na vyžádání vrátí přesný čas.

V bastlířské oblasti se hodně často používá DS1307. Vyniká hlavně v tom, že je extrémně levný. Na Aliexpressu se dá koupit za necelých 14 CZK. Cena kompenzuje určitou nepřesnost. Udává se zpoždění cca 5 minut za měsíc.

Sice několika násobně dražší, ale pořád směšně levný je DS3231. Na Aliexpressu se dá pořídit za 60 CZK.

U Lásky 128 CZK plus poštovné.

Tento modul je daleko přesnější a obsahuje tepelně kompenzovaný krystalový oscilátor (TCXO), který odolává změnám teplot. Pro náš testovací projekt použijeme DS3231.

DS3231

Modul umí sledovat roky, měsíce, dny, hodiny, minuty a sekundy. Umí dokonce přestupné roky do roku 2100. Dokáže pracovat v 12 hodinovém nebo 24 hodinovém formátu. Obsahuje dva programovatelné alarmy.

Modul má piny pro připojení přes sběrnici I2C. Má také pin INT, který umí produkovat signál přerušení a pin SQW pro generování "square wave" signálu na frekvencích 1Hz, 4kHz, 8kHz nebo 32kHz.

Přesnost modulu je +-2 minuty za rok.

Modul se napájí pomocí knoflíkové baterie CR2032, LIR2032 nebo ekvivalentní o průměru 20mm. Při použití 220mAh baterie je teoretická životnost 8 let.

Modul obsahuje 32 bytový AT24C32 EEPROM paměťový čip s milionem zapisovacích cyklů. Neslouží pro běh hodin, může být použit pro zápis logů nebo libovolných dat.

Vyčerpávající popis modulu najdete na lastminuteengineers.com.

Zapojení

Pomocí I2C připojíme k ESP modulu.

Program

Pokud nejsou hodiny nastaveny nebo mají starší čas, než je čas při kompilaci kódu, přenastaví se na aktuální čas. Po inicializaci modulu se vytvoří naplánovaná úloha pomocí modulu TaskScheduler a každých 10 sekund vypíše aktuální čas modulu do sériové konzole. Zbytek v komentovaném kódu.

main.cpp

/**
 * @file main.cpp
 * @author Hard Wired
 * @brief ESP32 I2C RTC test project.
 * @details
 *      Based on https://github.com/Makuna/Rtc/blob/master/examples/DS3231_Simple/DS3231_Simple.ino example.
 *      Modul tutorial https://lastminuteengineers.com/ds3231-rtc-arduino-tutorial/
 * @version 0.1
 * @date 2023-02-08
 */

#include <Arduino.h>

#include <Wire.h>
#include <RtcDS3231.h>
#include <TaskScheduler.h>

const int SECOND_IN_MILS = 1000;
const int TEN_SECONDS_IN_MILS = SECOND_IN_MILS * 10;

/**
 * @brief Vytvoření plánovače
 *
 */
Scheduler runner;

/**
 * @brief Vytvoření RTC objektu a připojení pomocí I2C sběrnice.
 *
 * @return RtcDS3231<TwoWire>
 */
RtcDS3231<TwoWire> Rtc(Wire);

/**
 * @brief Vytáhne z objektu "Rtc" poslední chybový stav a vypíše chybovou hlášku¨
 *        a dá vědět pomocí true/false.
 * @details see https://www.arduino.cc/reference/en/language/functions/communication/wire/endtransmission/
 *
 * @param errorTopic
 * @return true
 * @return false
 */
bool wasError(const char *errorTopic = "")
{
    uint8_t error = Rtc.LastError();
    if (error != 0)
    {
        Serial.print("[");
        Serial.print(errorTopic);
        Serial.print("] WIRE communications error (");
        Serial.print(error);
        Serial.print(") : ");

        switch (error)
        {
        case Rtc_Wire_Error_None:
            Serial.println("(none?!)");
            break;
        case Rtc_Wire_Error_TxBufferOverflow:
            Serial.println("transmit buffer overflow");
            break;
        case Rtc_Wire_Error_NoAddressableDevice:
            Serial.println("no device responded");
            break;
        case Rtc_Wire_Error_UnsupportedRequest:
            Serial.println("device doesn't support request");
            break;
        case Rtc_Wire_Error_Unspecific:
            Serial.println("unspecified error");
            break;
        case Rtc_Wire_Error_CommunicationTimeout:
            Serial.println("communications timed out");
            break;
        }
        return true;
    }
    return false;
}

/**
 * @brief Vypíše naformátované datum do sériové konzole.
 *
 * @param dt
 */
void printDateTime(const RtcDateTime &dt)
{
    char buffer[20];

    sprintf(buffer, "%d-%02d-%02d %02d:%02d:%02d", dt.Year(), dt.Month(), dt.Day(), dt.Hour(), dt.Minute(), dt.Second());

    Serial.println(buffer);
}

void printDateTimeAndTemperature()
{
    if (!Rtc.IsDateTimeValid())
    {
        if (!wasError("loop IsDateTimeValid"))
        {
            // Common Causes:
            //    1) the battery on the device is low or even missing and the power line was disconnected
            Serial.println("RTC lost confidence in the DateTime!");
        }
    }

    RtcDateTime now = Rtc.GetDateTime();
    if (!wasError("loop GetDateTime"))
    {
        printDateTime(now);
    }

    RtcTemperature temp = Rtc.GetTemperature();
    if (!wasError("loop GetTemperature"))
    {
        temp.Print(Serial);
        // you may also get the temperature as a float and print it
        // Serial.print(temp.AsFloatDegC());
        Serial.println("C");
    }
}

/**
 * @brief Vytvoření naplánované úlohy. Poběží navždy a spustí se každých 10 sekund.
 *
 * @return Task
 */
Task PrintDateTimeAndTemperatureTask(TEN_SECONDS_IN_MILS, TASK_FOREVER, &printDateTimeAndTemperature);

void setup()
{
    Serial.begin(115200);

    Serial.print("compiled: ");
    // makro které expanduje datum při kompilaci kódu na "mmm dd yyyy" řetězec
    Serial.print(__DATE__);
    Serial.print(' ');
    // makro které expanduje čas při kompilaci kódu na "hh:MM:ss" řetězec
    Serial.println(__TIME__);

    Rtc.Begin();

#if defined(WIRE_HAS_TIMEOUT)
    Wire.setWireTimeout(3000 /* us */, true /* reset_on_timeout */);
#endif

    /**
     * @brief Vytvoří objekt RtcDateTime a nastaví ho podle času kompilace kódu.
     *
     */
    RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);
    printDateTime(compiled);
    Serial.println();

    /**
     * @brief Pokud neni RTC nastaveno nastaví datum a čas.
     *
     */
    if (!Rtc.IsDateTimeValid())
    {
        if (!wasError("setup IsDateTimeValid"))
        {
            // Common Causes:
            //    1) first time you ran and the device wasn't running yet
            //    2) the battery on the device is low or even missing

            Serial.println("RTC lost confidence in the DateTime!");

            // following line sets the RTC to the date & time this sketch was compiled
            // it will also reset the valid flag internally unless the Rtc device is
            // having an issue

            Rtc.SetDateTime(compiled);
        }
    }

    /**
     * @brief V případě že není RTC spuštěno spustíme ho.
     *
     */
    if (!Rtc.GetIsRunning())
    {
        if (!wasError("setup GetIsRunning"))
        {
            Serial.println("RTC was not actively running, starting now");
            Rtc.SetIsRunning(true);
        }
    }

    /**
     * @brief V případě, že je čas hodin starší než čas kompilace, nastaví se nový čas. Vypíše do sériové konzole informaci o nastavení RTC.
     *
     */
    RtcDateTime now = Rtc.GetDateTime();
    if (!wasError("setup GetDateTime"))
    {
        if (now < compiled)
        {
            Serial.println("RTC is older than compile time, updating DateTime");
            Rtc.SetDateTime(compiled);
        }
        else if (now > compiled)
        {
            Serial.println("RTC is newer than compile time, this is expected");
        }
        else if (now == compiled)
        {
            Serial.println("RTC is the same as compile time, while not expected all is still fine");
        }
    }

    /*
     * Nastaví RTC modul do požadovaného stavu.
     */
    Rtc.Enable32kHzPin(false);
    wasError("setup Enable32kHzPin");
    // The INT/SQW pin on the DS3231 provides either an interrupt signal (due to alarm conditions) or a nice square wave at 1Hz, 4kHz, 8kHz, or 32kHz.
    Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone);
    wasError("setup SetSquareWavePin");

    /**
     * @brief Přiřazení naplánované úlohy do plánovače.
     *
     */
    runner.addTask(PrintDateTimeAndTemperatureTask);

    /**
     * @brief Povolení naplánované úlohy.
     *
     */
    PrintDateTimeAndTemperatureTask.enable();
}

void loop()
{
    runner.execute(); // spouštění plánovače
}

platformio.ini

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200

lib_deps =
  makuna/RTC @ ^2.3.6
  arkhipenko/TaskScheduler @ ^3.7.0

Loading