LED - LCD - Temperature - Time

In this example, in addition to the LCD, LED, and temperature sensor DS18B20 peripherals, I have added a real-time clock (RTC - Real Time Clock) PCF8583, which is very useful when it is desired to record the time independently of the power supply/operation of the micro-controller.
The project will have a higher degree of difficulty than the previous ones by adding the serial communication necessary for setting the real-time clock and using a real terminal in the serial communications.

The electrical schematic of the assembly is presented below:

The source code used is as follows:

/*
Conectarea LCD la Sanguino
 * LCD RS la pinul digital pin 22
 * LCD Enable la pinul digital pin 23
 * LCD D4 la pinul digital pin 24
 * LCD D5 la pinul digital pin 25
 * LCD D6 la pinul digital pin 26
 * LCD D7 la pinul digital pin 28
 * LCD R/W la masa
Conectare LED
* LED K la pinul digital 13
* LED A la +5V printr-o rezistenta de 1k
Conectare DS18B20 - senzor de temperatura capsula TO92
* DS18B20 DQ la pinul digital 2
* DS18B20 Vdd la +5V
* DS18B20 GND la masa
* DS18B20 o rezistenta pull-up de 4k7 intre Vdd si pinul DQ  
Conectare RTC - PCF8583
* RTC A0 la +V adresa hardware 0xA2
* RTC SCL la pinul D16/SCL
* RTC SDA la pinul D17/SDA
* RTC OSC1 la oscilator quartz de 32678Hz
* RTC OSC0 la oscilator quartz de 32678HZ
* RTC GND la masa
* RTC OSC1 la +V printr-un condensator de 22pF
*/
// Librariile utilizate
#include <Arduino.h>
#include <LiquidCrystal.h> // load the library for the LCD display
#include <OneWireNg_CurrentPlatform.h> // load the library for 1-Wire communication (DS18B20)
#include <Wire.h> // load the library necessary for the RTC sensor
#include <PCF8583.h> // load the library for the RTC sensor
#include <stdio.h> // load the library necessary to create a string using sprintf

 // constantele sunt variabile cu valoare fixa
const int ledPin =  13; // pinul unde este conectat LED-ul
const long intervalBlink = 500; // intervalul de timp in care se va aprinde ledul (in milliseconde)
int correct_address = 0; // necesar pentru PCF8583

// variabile a caror valoare se va modifica
int ledState = HIGH;  // ledState utilzat pentru a seta LED-ul ON sau OFF (HiGH => LED=OFF)
unsigned long previousMillisBlink = 0; // variabila care va stoca timpul cand starea LED-ului a fost actualizata

// intializez libraria si asociez pinii Sanguino la LCD
const int rs = 22, en = 23, d4 = 24, d5 = 25, d6 = 26, d7 = 27;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

// ------------------------Declaratiile necesare pentru senzorul DS18----------------------------------

#define OW_PIN  2 // the pin where the DS18 sensor is connected 
// #define PARASITE_POWER // uncomment for parasite power support 

#ifdef PARASITE_POWER
/* sterge comentarea pentru alimentarea tranzistorului
si controlul acestuia prin pinul 9 sau altul */
// # define PWR_CTRL_PIN   9
#endif

/* Comenzile pentru circuitele DS */
#define CMD_CONVERT_T           0x44
#define CMD_COPY_SCRATCHPAD     0x48
#define CMD_WRITE_SCRATCHPAD    0x4e
#define CMD_RECALL_EEPROM       0xb8
#define CMD_READ_POW_SUPPLY     0xb4
#define CMD_READ_SCRATCHPAD     0xbe

/* Tipurile de circuite DS suportate */
#define DS18S20     0x10
#define DS1822      0x22
#define DS18B20     0x28
#define DS1825      0x3b
#define DS28EA00    0x42

#define ARRSZ(t) (sizeof(t)/sizeof((t)[0]))

static struct {
  uint8_t code;
  const char *name;
  }
  DSTH_CODES[] = {
    { DS18S20, "DS18S20" },
    { DS1822, "DS1822" },
    { DS18B20, "DS18B20" },
    { DS1825, "DS1825" },
    { DS28EA00,"DS28EA00" }
    };

static OneWireNg *ow = NULL;

/* returneaza NULL daca nu este suportat */
static const char *dsthName(const OneWireNg::Id& id) {
  for (size_t i=0; i < ARRSZ(DSTH_CODES); i++) {
    if (id[0] == DSTH_CODES[i].code)
    return DSTH_CODES[i].name;
    }
    return NULL;
}

// ---------------------------sfarsitul declaratiilor necesare pentru circuitele DS-------------------------------

// construiesc un obiect p 
//   PCF8583 p(0xA0); // adresa pentru cazul in care A0 la GND
   PCF8583 p(0xA2); // adresa pentru cazul in care A0 la +V

// declar functiile utilizate
void blinkLed(); // fuctia care aprinde un LED
long getTemp(); // functia pentru determinarea temperaturii
void printLcdTemp(); //functia pentru afisarea temperaturii
void printLcdTime(); //functia pentru afisarea ceasului 
void setTime(); // functia pentru setarea datei si a orei
void clear_row_1(); // functia pentru stergerea randului 1 si pozitionare la inceputul liniei
void clear_row_2(); // functia pentru stergerea randului 2 si pozitionare la inceputul liniei

// aceasta functie va fi rulata o data
void setup() {

  // -----------------configurez DS18B20------------------------------------ 
  #ifdef PWR_CTRL_PIN
  ow = new OneWireNg_CurrentPlatform(OW_PIN, PWR_CTRL_PIN, false);
  #else
  ow = new OneWireNg_CurrentPlatform(OW_PIN, false);
  #endif

  delay(500);

  #if (CONFIG_MAX_SRCH_FILTERS > 0)
  /* if filtering is enabled - filter to supported devices only;
  CONFIG_MAX_SRCH_FILTERS must be large enough to embrace all code ids */
  for (size_t i=0; i < ARRSZ(DSTH_CODES); i++)
  ow->searchFilterAdd(DSTH_CODES[i].code);
  #endif
  // ---------------------sfarsit configurare DS18B20----------------------

  Serial.begin(9600); // intializez comunicatia seriala
  lcd.begin(16, 2); // setez nr. de coloane si de linii ale LCD-ului
  lcd.print("Test..."); // Afisez un mesaj la LCD
  Serial.print("Test...");
  Serial.println(" gata");

  delay(2000); // astept pentru ca mesajul de intampinare sa fie afisat
  pinMode(ledPin, OUTPUT); // setez pinul digital aferent ledPin ca iesire 
  lcd.clear(); // sterg ecranul afisorului

}

//acesta functie va fi rulata la infinit
void loop() { 

  blinkLed(); // apelez functia ce aprinde LED-ul
  setTime(); // apelarea functiei pentru setarea datei si a orei
  printLcdTime(); //functia pentru afisarea ceasului 
  printLcdTemp(); // apelez functia care afiseaza temperatura

}

// functia ce va aprinde un led intermitent
// in acest moment intervalul de timp este afectat de toate delay-urile din program
// in viitor se va folosi task-uri pentru ca elimina acest neajuns
void blinkLed() { 
  // verifica daca e trecut timpul pentru a schimba satrea LED-ului (ON sau OFF)
  // daca diferenta dintre timpul curent (currentMillis) si ultimul timp (previosMillis) salvat
  // este mai mare decat timpul impus prin variabila (intervalBlink) 
  unsigned long currentMillisBlink = millis(); // stochez in curentMillis timpul curent
  if (currentMillisBlink - previousMillisBlink >= intervalBlink) {
    // verific daca s-a scurs intervalul de timp dorit
    // daca da, atunci,
    previousMillisBlink = currentMillisBlink; //salvez timpul current
    if (ledState == HIGH) {
      // daca LED-ul este OFF atunci schimb starea lui in ON si invers
      ledState = LOW;
      }
      else {
        ledState = HIGH;
        }
  digitalWrite(ledPin, ledState); // setez starea pinului cu variabila ledState stabilita anterior
  }

}

//functia pentru citirea temperaturii
long getTemp() {
    OneWireNg::Id id;
  OneWireNg::ErrorCode ec;

  ow->searchReset();
  do {
    ec = ow->search(id);
    if (!(ec == OneWireNg::EC_MORE || ec == OneWireNg::EC_DONE))
    break;

    /* porneste conversia temperaturii */
    ow->addressSingle(id);
    ow->writeByte(CMD_CONVERT_T);

    #ifdef PARASITE_POWER
    /* alimenteaza magistrala */
    ow->powerBus(true);
    #endif

    delay(500); // asteptam pentru realizarea conversiei

    uint8_t touchScrpd[] = {
      CMD_READ_SCRATCHPAD,
      /* datele citite scratchpad sunt plasate aici (9 bytes) */
      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
      };

    ow->addressSingle(id);
    ow->touchBytes(touchScrpd, sizeof(touchScrpd));
    uint8_t *scrpd = &touchScrpd[1];  /* datele scratchpad */

    if (OneWireNg::crc8(scrpd, 8) != scrpd[8]) {
      Serial.println("  Invalid CRC!");
      continue;
      }

    long temp = ((long)(int8_t)scrpd[1] << 8) | scrpd[0];
    if (id[0] != DS18S20) {
      unsigned res = (scrpd[4] >> 5) & 3;
      temp = (temp >> (3-res)) << (3-res);  /* bitii nedefiniti devin zero */
      temp = (temp*1000)/16;
      }

    else if (scrpd[7]) {
      temp = 1000*(temp >> 1) - 250;
      temp += 1000*(scrpd[7] - scrpd[6]) / scrpd[7];
      }
      else {
        /* ar trebui sa nu se intample */
        temp = (temp*1000)/2;
        Serial.println("  Zeroed COUNT_PER_C detected!");
        }
  return temp;
  } while (ec == OneWireNg::EC_MORE);
}

// functia pentru afisare la LCD a temperaturii
void printLcdTemp() {

  long temperatura = getTemp();

// afisarea temperaturii ca variabila de tip float (cast)
  lcd.setCursor(9,1); // pozitionez cusorul pentru afisarea temperaturii
  lcd.print("       "); // sterg temperatura anterioara

  if (temperatura < 0) {
    temperatura = -temperatura;
    lcd.setCursor(9,1);
    lcd.print('-');
    }

  lcd.setCursor(10,1); // pozitionez cursorul pentru afisarea valorii temperaturii
  lcd.print((float)temperatura / 1000);
  lcd.print('C');

/*
// afisarea temperaturii ca variabila de tip long
  lcd.setCursor(9,1); // pozitionez cusorul pentru afisarea temperaturii
  lcd.print("       "); // sterg temperatura anterioara
  if (temperatura < 0) {
    temperatura = -temperatura;
    lcd.setCursor(9,1);
    lcd.print('-');
    }
    lcd.setCursor(10,1);
    lcd.print(temperatura / 1000);
    lcd.print('.');
    lcd.print(temperatura % 1000);
   // lcd.print('C');
*/

}

// functia pentru setarea datei si a orei prin comunatia seriala
// se va transmite de forma: ZZLLAAAAhhmmss;
void setTime() {

  if(Serial.available() > 0){ // daca a fost stabilta comunicatie seriala, atunci citesc caracterele primite
    p.day = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
    p.month = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
    p.year= (int) ((Serial.read() - 48) *1000 +  (Serial.read() - 48) *100 + (Serial.read() - 48) *10 + (Serial.read() - 48)) ;
    p.hour  = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
    p.minute = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
    p.second = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));

    if(Serial.read() == ';'){ // daca intalnesc caracterul ; atunci setez ceasul
      Serial.println("ceasul a fost setat");
      clear_row_1(); // curat randul 1 al LCD-ului
      clear_row_2(); // curat randul 2 al LCD-ului
      p.set_time(); // setz ceasul
      }

  p.get_time(); // citesc ceasul
  char time[50];
  sprintf(time, "%02d/%02d/%04d %02d:%02d:%02d",
   p.day, p.month, p.year, p.hour, p.minute, p.second);
  Serial.println(time);
  delay(1000);

  }

}

// functia pentru afisarea datei si a orei la LCD 
// se va afisa de forma: ZZ/LL/AAAA respectiv hh:mm:ss
void printLcdTime() {

  p.get_time(); // citesc ceasul
  char date[50];
  char time[50];
  sprintf(date, "%02d/%02d/%04d",
     p.day, p.month, p.year);
  sprintf(time, "%02d:%02d:%02d",
     p.hour, p.minute, p.second);

  lcd.setCursor(0,0);
  lcd.print(date);
  lcd.setCursor(0,1);
  lcd.print(time);

  delay(250);
}

// functia sterge randul 1 si pozitionez cursorul la inceputul liniei
void clear_row_1() {

  lcd.setCursor(0, 0);
  lcd.print("                ");  
  lcd.setCursor(0, 0);

}

// functia sterge randul 2 si pozitionez cursorul la inceputul liniei
void clear_row_2() {

  lcd.setCursor(0, 1);
  lcd.print("                ");  
  lcd.setCursor(0, 1);

}

The libraries used are:

The operation of the assembly in Proteus can be seen in the video below:

As can be seen from the video, I used serial communication to set the clock. For this, it was necessary to create a pair of serial ports com3 and com4 connected to each other (null modem).
I connected the microcontroller to port com3 via the virtual port COMPIM, and I connected the terminal CoolTerm to port com4 used in the serial communication.

The program used for creating the virtual ports com3 and com4 is com0com , which can be downloaded here .
For serial communication, I used the terminal CoolTerm .

WARNING: The RX/TX pins of the microcontroller should not be connected directly to a serial port in real life. Read below.

I connected the virtual port in Proteus COMPIM directly, to simplify the schematic and reduce the memory load on the computer's microprocessor, but in real life, it should be connected using a level-shifting circuit TTL-RS232 (for example, MAX232 or others that also use USB communication, such as FT232R).

The entire project (source code and the file for Proteus) can be downloaded from here LCD_Blink_LED_DS18B20_PCF8583.zip