DS1307 pro začátečníky

Zápisník experimentátora

Hierarchy: DS1307 Hodiny reálneho času

V dnešním článku si vysvětlíme základy používání integrovaného obvodu DS1307. Naučíme se nastavit čas a naučíme se také číst čas z obvodu. Vysvětlíme si, jak využívat celou RAM, kterou nám integrovaný obvod poskytuje. Součástí vysvětlení bude i drobný výlet do hlubin protokolu I2C.

Použité součástky

Použil jsem tyto součástky:

  • Arduino Mega 2560 ((link)
  • DS1307 (link)
  • Breadboard (link)
  • 2x 10k rezistor
  • Baterie CR2032 (link)
  • Battery Socket Holder (link)

Programy

Vytvořil jsem tři vzorové příklady:

  • set_time - Tento příklad ukazuje nastavení času v integrovaném obvodu pomocí makra s časovým razítkem v době kompilace. Program je jen mírně upraven vůči původnímu příkladu přímo z knihovny.
  • get_time - Čtení času z integrovaného obvodu. Program je jen mírně upraven vůči původnímu příkladu přímo z knihovny.
  • get_ram - DS1307 má celkově 64 bajtů RAM paměti. Z toho je pro potřeby počítání času využitých pouze 8 bajtů. Ostatní můžete používat na ukládání svého nastavení. V tomto příkladu ukazuji čtení celé RAM a naplnění RAM vzorovými daty.

Před samotnou prací s obvodem je vhodné, pokud otestujete, zda je viditelný na sběrnici I2C. Můžete mít omylem přehozené připojovací vodiče, nebo jste udělali nějakou jinou chybu. Na otestování je vhodný program i2c_tester, pomocí kterého si zobrazíte všechny I2C slave zařízení na sběrnici. Pokud jste si poskládali zapojení na breadboardu pomocí méjho návodu, uvidíte pouze jedno zařízení. Pokud použijete hotový modul, uvidíte dvě zařízení, protože na modulu se obvykle nachází také​ externí EEPROM.

set_time

První činnost, kterou musíte po zakoupení integrovaného obvodu udělat je nastavení času. Nejjednodušší je nastavit si stejný čas, jako máte v počítači. Mohli byste to udělat i přímo zadáním hodnot pro hodiny, minuty a sekundy, ale existuje pohodlnější způsob. Využijí se makra __TIME__ a __DATE__. Ty obsahují textový řetězec s aktuálním časem v okamžiku kompilace programu. Protože kompilace a nahrání programu do Arduina se děje těsně po sobě, bude takto nastavený čas v integrovaném obvodu jen o pár sekund rozdílný vůči času, který máte v počítači.

V programu se o převod času z textové podoby starají dvě funkce. Funkce getDate získá datum a funkce getTime získá čas. Využívají k tomu funkci sscanf. Textové řetězce mají v každém kompilátoru stejnou formu a proto se nemusíte obávat, že by program nefungoval, protože máte jinou jazykovou mutaci v operačním počítači, než je angličtina.

Při takovém nastavení času je třeba si uvědomit, jaký čas zadáte do integrovaného obvodu. Je to takzvaný lokální čas. Čili je to čas, který podléhá pravidelným změnám v létě, kdy se posouvá o jednu hodinu. Ale na vyzkoušení nastavení času nám to bude stačit. Později si ukážeme, jak se s letním časem vypořádat.

#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>

const char *monthName[12] = {
  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

tmElements_t tm;
bool parse=false;
bool config=false;

void setup() {

  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor
  delay(200);

  Serial.print("Sketch compile time __TIME__='");
  Serial.print(__TIME__);
  Serial.print("', __DATE__='");
  Serial.print(__DATE__);
  Serial.println("'");

  // get the date and time the compiler was run
  if (getDate(__DATE__) && getTime(__TIME__)) {
    parse = true;
    // and configure the RTC with this info
    if (RTC.write(tm)) {
      config = true;
    }
  }

  if (parse && config) {
    Serial.println("Parse and write OK.");
  } else if (parse) {
    Serial.println("DS1307 Communication Error");
    Serial.println("Please check your circuitry");
  } else {
    Serial.println("Could not parse info from the compiler");
  }
}

void loop() {
if(parse && config) {
  time_t t=RTC.get();
  Serial.print("RTC.get()=");
  Serial.println(t);
  }
delay(1000);
}

bool getTime(const char *str)
{
  int Hour, Min, Sec;

  if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false;
  tm.Hour = Hour;
  tm.Minute = Min;
  tm.Second = Sec;
  return true;
}

bool getDate(const char *str)
{
  char Month[12];
  int Day, Year;
  uint8_t monthIndex;

  if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false;
  for (monthIndex = 0; monthIndex < 12; monthIndex++) {
    if (strcmp(Month, monthName[monthIndex]) == 0) break;
  }
  if (monthIndex >= 12) return false;
  tm.Day = Day;
  tm.Month = monthIndex + 1;
  tm.Year = CalendarYrToTm(Year);
  return true;
}

get_time

Když máme nastavený čas, můžeme ho z integrovaného obvodu číst. Máme dvě možnosti, v jaké podobě čas získáme. V tomto příkladu je získán čas rozparsovaný do struktury tmElements_t. K tomu slouží funkce read. Čas se ale může získat i jako time_t. K tomu slouží funkce get. Vše závisí na dalšího zpracování času v programu, jakou formu na to zvolíme. V tomto případě se nám více hodila rozparsovaná forma do tmElements_t. Do příkladu jsem doplnil i převod mezi oběma formami pomocí funkce makeTime, abyste viděli, že změna je možná kdykoliv.

#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>

void setup() {
  Serial.begin(9600);
  while (!Serial) ; // wait for serial
  delay(200);
  Serial.println("DS1307RTC Get Time");
  Serial.println("-------------------");
}

void loop() {
  tmElements_t tm;
  time_t t;

  if (RTC.read(tm)) {
    t=makeTime(tm);
    Serial.print("Ok, Time=");
    print2digits(tm.Hour);
    Serial.write(':');
    print2digits(tm.Minute);
    Serial.write(':');
    print2digits(tm.Second);
    Serial.print(", Date(D/M/Y)=");
    Serial.print(tm.Day);
    Serial.write('/');
    Serial.print(tm.Month);
    Serial.write('/');
    Serial.print(tmYearToCalendar(tm.Year));
    Serial.print(", time_t=");
    Serial.print(t);
    Serial.println();
  } else {
    if (RTC.chipPresent()) {
      Serial.println("The DS1307 is stopped. Please run the set_time");
      Serial.println("sketch to initialize the time and begin running.");
      Serial.println();
    } else {
      Serial.println("DS1307 read error! Please check the circuitry.");
      Serial.println();
    }
    delay(9000);
  }
  delay(1000);
}

void print2digits(int number) {
  if (number >= 0 && number < 10) {
    Serial.write('0');
  }
  Serial.print(number);
}

get_ram

V posledním příkladu budeme přímo číst obsah RAM v integrovaném obvodu. Protože použitá knihovna na to neposkytuje žádné funkce, musíme si je doplnit. Abychom se trochu procvičili v objektovém programování, odvodil jsem od třídy DS1307RTC novou třídu DS1307RTCex. Do ní jsem doplnil proměnnou ram, abychom měli kam uložit obsah celé RAM v integrovaném obvodu. Doplnil jsem i tři funkce, které udělají celou práci.

  • readRam - Pomocí této funkce přečteme obsah RAM. Ve funkci vidíte cyklus, který se 4x opakuje a čte po 16 bajtů. Musíme to udělat takto, protože implementace I2C v Arduine nedovoluje najednou přečíst 64 bajtů. V cyklu si všimněte, že pokud chceme pomocí I2C číst, musíme nejprve poslat adresu a až pak můžeme z ní přečíst 16 bajtů. Adresu stačí nastavit pouze pro začátek bloku, protože integrovaný obvod si ji při čtení sám posouvá. Ve funkci mohou být pro vás dvě podivné c ++ konstrukce.
    • *ptr=ram - Do proměnné ptr mohu nastavit začátek pole ram. V c ++ je i ptr i ram pointer a proto to můžu zapsat takto. Pomocná proměnná ptr mi slouží k tomu, abych se postupně přesouval přes jednotlivé prvky pole a zapisoval do něj přečtené hodnoty.
    • *ptr++=Wire.read() - Tento zápis znamená to, že uloží do místa, kam aktuálně ukazuje pointer (*ptr) a potom posune pointer o jedno místo nahoru (ptr++). C++ nám umožňuje provádět i takovéto operace v jednom kroku.
  • dump - Tato funkce slouží k vypsání určeného místa v paměti RAM (v mikrokontroléru) na sériový port. Na první pohled se může zdát složitá. Ale ve skutečnosti pouze vypíše čtyři řádky se šestnácti hodnotami v hexadecimálním tvaru a pak ještě v podobě alfanumerických znaků.
  • demoRam - Funkci jsem doplnil proto, abychom měli v RAM integrovaného obvodu i nějaké snadno kontrolovatelné znaky. Funkce zapisuje až od devátého místa, protože na nižších místech jsou uloženy hodnoty samotného času, se kterým integrovaný obvod pracuje. Protože tu jen zapisujeme, v I2C nejprve nastavíme adresu a potom pošleme samotnou hodnotu. Funkce není nijak optimalizována, šlo mi jen o to, aby byl příklad každému čtenáři hned jasný.
#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>

#define DS1307_CTRL_ID 0x68

class DS1307RTCex : public DS1307RTC {
    uint8_t ram[64];

  public:
    DS1307RTCex() : DS1307RTC() {}

    bool readRam() {
      uint8_t *ptr = ram;

      for (uint8_t x = 0; x <= 4; x++) {
        Wire.beginTransmission(DS1307_CTRL_ID);
        Wire.write((uint8_t)x * 16);
        if (Wire.endTransmission() != 0)
          return false;

        uint8_t sz = 16;
        // block
        Wire.requestFrom(DS1307_CTRL_ID, 16);
        if (Wire.available() < 16) return false;
        while (sz) {
          *ptr++ = Wire.read();
          sz--;
        }
      }

      return true;
    }

    void dump() {
      uint8_t nRows = 4;
      char    asciiDump[17];
      int     value;

      for (uint8_t r = 0; r < nRows; r++) {
        uint8_t a = 16 * r;

        Serial.print(F("0x"));
        if ( a < 16 * 16 * 16 ) Serial.print(F("0"));
        if ( a < 16 * 16 ) Serial.print(F("0"));
        if ( a < 16 ) Serial.print(F("0"));
        Serial.print(a, HEX); Serial.print(F(" "));

        for (int c = 0; c < 16; c++) {
          value = ram[a + c];
          if (value < 16)
            Serial.print(F("0"));
          Serial.print(value, HEX);
          Serial.print(c == 7 ? "  " : " ");
          if ((value >= 0x20) && (value < 0x7f))
            asciiDump[c]  = value;
          else
            asciiDump[c]  = '.';
        }
        asciiDump[16] = 0;
        Serial.println(asciiDump);
      }
    }

    void demoRam() {
      uint8_t data = 0;
      for (uint8_t i = 8; i < 64; i++)  {
        Wire.beginTransmission(DS1307_CTRL_ID);
        Wire.write(i);
        Wire.write(data++);
        Wire.endTransmission();
      }
    }

};

DS1307RTCex ex;

void setup() {
  Serial.begin(9600);
  while (!Serial) ; // wait for serial
  delay(200);
  Serial.println("DS1307RTC Get RAM");
  Serial.println("-------------------");

  //ex.demoRam();

  if (ex.readRam()) {
    ex.dump();
  }
  else
    Serial.println("DS1307RTC Communication error");
}

void loop() {
  delay(1000);
}

Zdrojový kód

Zdrojový kód se nachází na serveru GitHub.



Download
  • DS1307 - Datasheet DS1307 - 64 x 8 Serial Real-Time Clock

26.09.2017


Menu