LED - LCD - Temperatura - Timpul

În acest exemplu pe lângă perifericele LCD, LED și senzorul de temperatură DS18B20, am adăugat un circuit de timp real (RTC - Real Time Clock) PCF8583 foarte folositor atunci când se dorește a se înregistra timpul independent de alimentarea/funcționarea microcontrolerului.
Proiectul va avea un grad de dificultate mai ridicat ca cele anterioare prin adăugarea comunicației seriale necesare setării ceasului real și folosirea unui terminal real în comunicațiile seriale.

Schema electrica a montajului este prezentată mai jos:

Codul sursa folosit este următorul:

/*
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> // incarc libraria pentru afisorul LCD
#include <OneWireNg_CurrentPlatform.h> // incarc libraria pentru comunicatia 1-Wire (DS18B20)
#include <Wire.h> // incarc libraria necesara pentru senzorul RTC
#include <PCF8583.h> // incarc libraria pentru senzorul RTC
#include <stdio.h> // incarc libraria necesara pentru a crea un string 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 // pinul unde este conectat senzorul DS18 
// #define PARASITE_POWER // sterge comentarea pentru suport parasite 

#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);

}

Librăriile folosite sunt:

Funcționarea montajului în Proteus se poate vedea în videoclipul de mai jos:

După cum se poate observa din videoclip pentru setarea ceasului am folosit comunicația serială. Pentru acesta a fost necesar să realizez o pereche de porturi seriale com3 și com4 conecate între ele (null modem).
La portul com3 am conectat microcontrolerul prin portul virtual COMPIM iar portul com4 am conectat terminalul CoolTerm folosit în cadrul comunicației seriale.

Programul folosit pentru rezlizarea porturilor virtuale com3 și com4 este com0com, acesta se descarcă de aici.
Pentru comunicația serială am folosit terminalul CoolTerm.

ATENȚIE: Pinii RX/TX ai microcontrolerului nu se vor conecta direct la un port serial în viața reală. Citește mai jos.

Conectarea portului virtual din Proteus COMPIM am realizat-o direct, pentru a simplifica schema și a reduce încărcarea memoriei respectiv a microprocesorului calculatorului, dar în viața reală se va conecta cu ajutorul unui circuit de adaptare a nivelurilor TTL-RS232 (de exemplu MAX232 sau altele ce folosesc și comunicația USB ca de exemplu FT232R).

Întreg proiectul (codul sursă și fișierul pentru Proteus ) se poate descărca de aici LCD_Blink_LED_DS18B20_PCF8583.zip