DS1307 pre začiatočníkov

Zápisník experimentátora

Hierarchy: DS1307 Hodiny reálneho času

V dnešom článku si vysvetlíme základy používania integrovaného obvodu DS1307. Naučíme sa nastaviť čas a naučíme sa aj čítať čas z obvodu. Vysvetlíme si, ako využívať celú RAM, ktorú nám integrovaný obvod poskytuje. Súčasťou vysvetlenia bude aj drobný výlet do hlbín protokolu I2C.

Použité súčiastky

Použil som tieto súčiastky:

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

Programy

Vytvoril som tri vzorové príklady:

  • set_time - Tento príklad ukazuje nastavenie času v integrovanom obvode pomocou makra s časovou pečiatkou v dobe kompilácie. Program je len mierne upravený voči pôvodnému príkladu priamo z knižnice.
  • get_time - Čítanie času z integrovaného obvodu. Program je len mierne upravený voči pôvodnému príkladu priamo z knižnice.
  • get_ram - DS1307 má celkovo 64 bajtov RAM pamäte. Z toho je pre potreby počítania času využitých iba 8 bajtov. Ostatné môžete používať na ukladanie svojho nastavenia. V tomto príklade ukazujem čítanie celej RAM a naplnenie RAM vzorovými údajmi.

Pred samotnou prácou s obvodom je vhodné, ak otestujete, či je viditeľný na zbernici I2C. Môžete mať omylom prehodené pripojovacie vodiče, alebo ste urobili nejakú inú chybu. Na otestovanie je vhodný program i2c_tester, pomocou ktorého si zobrazíte všetky I2C slave zariadenia na zbernici. Ak ste si poskladali zapojenie na breadboarde pomocou môjho návodu, uvidíte iba jedno zariadenie. Ak použijete hotový modul, uvidíte dve zariadenia, pretože na module sa obvykle nachádza aj externá EEPROM.

set_time

Prvú činnosť, ktorú musíte po zakúpení integrovaného obvodu urobiť je nastavenie času. Najjednoduchšie je nastaviť si rovnaký čas, ako máte v počítači. Mohli by ste to urobiť aj priamo zadaním hodnôt pre hodiny, minúty a sekundy, ale existuje pohodlnejší spôsob. Využijú sa makrá __TIME__ a __DATE__. Tie obsahujú textový reťazec s aktuálnym časom v okamihu kompilácie programu. Pretože kompilácia a nahratie programu do Arduina sa deje tesne po sebe, bude takto nastavený čas v integrovanom obvode iba o pár sekund rozdielny voči času, ktorý máte v počítači.

V programe sa o prevod času z textovej podoby starajú dve funkcie. Funkcia getDate získa dátum a funkcia getTime získa čas. Využívajú na to funkciu sscanf. Textové reťazce majú v každom kompilátore rovnakú formu a preto sa nemusíte obávať, že by program nefungoval preto, lebo máte inú jazykovú mutáciu v operačnom počítači, než je angličtina.

Pri takomto nastavení času si treba uvedomiť, aký čas zadáte do integrovaného obvodu. Je to takzvaný lokálny čas. Čiže je to čas, ktorý podlieha pravidelným zmenám v lete, kedy sa posúva o jednu hodinu. Ale na vyskúšanie nastavenia času nám to bude stačiť. Neskôr si ukážeme, ako sa s letným časom vysporiadať.

#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

Keď máme nastavený čas, môžeme ho z integrovaného obvodu čítať. Máme dve možnosti, v akej podobe čas získame. V tomto príklade je získaný čas rozparsovaný do štruktúry tmElements_t. Na to slúži funkcia read. Čas ale môže získať aj ako time_t. Na to slúži funkcia get. Všetko závisí od ďalšieho spracovania času v programe, akú formu na to zvolíme. V tomto prípade sa nám viac hodila rozparsovaná forma do tmElements_t. Do príkladu som doplnil aj prevod medzi oboma formami pomocou funkcie makeTime, aby ste videli, že zmena je možná kedykoľvek.

#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 poslednom príklade budeme priamo čítať obsah RAM v integrovanom obvode. Pretože použitá knižnica na to neposkytuje žiadne funkcie, musíme si ich doplniť. Aby sme sa trochu precvičili v objektovom programovaní, odvodil som od triedy DS1307RTC novú triedu DS1307RTCex. Do nej som doplnil premennú ram, aby sme mali kam uložiť obsah celej RAM v integrovanom obvode. Doplnil som aj tri funkcie, ktoré urobia celú prácu.

  • readRam - Pomocou tejto funkcie prečítame obsah RAM. Vo funkcii vidíte cyklus, ktorý sa 4x opakuje a číta po 16 bajtov. Musíme to urobiť takto, pretože implementácia I2C v Arduine nedovoľuje naraz prečítať 64 bajtov. V cykle si všimnite, že ak chceme pomocou I2C čítať, musíme najprv poslať adresu a až potom môžeme z nej prečítať 16 bajtov. Adresu stačí nastaviť iba pre začiatok bloku, pretože integrovaný obvod si ju pri čítaní sám posúva. Vo funkcii môžu byť pre vás dve podivné c++ konštrukcie.
    • *ptr=ram - Do premennej ptr priradím začiatok poľa ram. V c++ je aj ptr aj ram pointer a preto to môžem zapísať takto. Pomocná premenná ptr mi slúži na to, aby som sa postupne presúval cez jednotlivé prvky poľa a zapisoval do neho prečítané hodnoty.
    • *ptr++=Wire.read() - Tento zápis znamená to, že uloží do miesta, kam aktuálne ukazuje pointer (*ptr) a potom posunie pointer o jedno miesto nahor (ptr++). C++ nám umožňuje robiť aj takéto operácie v jednom kroku.
  • dump - Táto funkcia slúži na vypísanie určeného miesta v pamäti RAM (v mikrokontroléri) na sériový port. Na prvý pohľad sa môže zdať zložitá. Ale v skutočnosti iba vypíše štyri riadky so šestnástimi hodnotami v hexadecimálnom tvare a potom ešte aj v podobe alfanumerických znakov.
  • demoRam - Funkciu som doplnil preto, aby sme mali v RAM integrovaného obvodu aj nejaké ľahko skontrolovateľné znaky. Funkcia zapisuje až od deviateho miesta, pretože na nižších miestach sú uložené hodnoty samotného času, s ktorým integrovaný obvod pracuje. Pretože tu len zapisujeme, v I2C najprv nastavíme adresu a potom pošleme samotnú hodnotu. Funkcia nie je nijako optimalizovaná, šlo mi len o to, aby bol príklad každému čitateľovi hneď 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 sa nachádza na serveri GitHub.



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

25.09.2017


Menu