DATA E ORA CON DS3231 E REGOLAZIONE

Schema con Microcontrollore PIC per Realizzare un Orologio con Data e Ora Con il Modulo DS3231 e la Possibilità di Regolazione…

 

 

 

 




 

INTRO

Il seguente schema va ad eseguire tutte le funzioni viste nel tutorial sul modulo DS3231 per realizzare un orologio con data e ora precise al secondo con la possibilità di regolare data e ora grazie a due pulsanti. Si utilizza un microcontrollore PIC16F877 per controllare il modulo RTC (Real Time Clock) e LCD che viene utilizzato per mostrare data e ora. La scelta del PIC16f877 è stata quasi obbligata visto che utilizzando un PIC18F252 quando su utilizzava il programma “FixHex” prima di andare a programmare il PIC il programma va a cambiare alcuni parti del codici creando problemi di funzionamento. Il codice funziona con tutti i microcontrollori che hanno internamente il modulo SPI, l’importante è usare un PIC16 se si ha a disposizione solo il programmatore K150.

Oltre a contare il tempo e visualizzarlo su display LCD è possibile regolare data e ora, funzione utile alla prima accensione oppure in caso di mancanza di tensione di alimentazione del modulo. In pratica vengono utilizzati due pulsanti, uno seleziona se regolare l’anno, il mese, il giorno, l’ora, i minuti o i secondi, mentre l’altro pulsante incrementa il valore di queste variabili , portandole al valore iniziale  automaticamente quando  si raggiunge il valore massimo per gli anni ad esempio 2099.

Il giorno (Lunedì, Martedì….) non viene visualizzato in quanto non è proprio necessario, ma con una piccola modifica è possibile visualizzare anche questa informazione. La regolazione massima per il giorno del mese è 31, il che non considera i mesi con meno giorni. Bisogna stare attenti a caricare il giorno giusto per il mese giusto, altrimenti il modulo DS3231 ritorna al valore 1 di giorno. Con una piccola modifica si può dare una intelligenza maggiore al sistema di regolazione.
 





 

CODICE

Il codice è il seguente:

sbit LCD_RS at RB2_bit;                        //Connessioni LCD
sbit LCD_EN at RB3_bit;
sbit LCD_D4 at RB4_bit;
sbit LCD_D5 at RB5_bit;
sbit LCD_D6 at RB6_bit;
sbit LCD_D7 at RB7_bit;
sbit LCD_RS_Direction at TRISB2_bit;
sbit LCD_EN_Direction at TRISB3_bit;
sbit LCD_D4_Direction at TRISB4_bit;
sbit LCD_D5_Direction at TRISB5_bit;
sbit LCD_D6_Direction at TRISB6_bit;
sbit LCD_D7_Direction at TRISB7_bit;

unsigned short sec     absolute 0x0020;       //Variabili data e ora
unsigned short minute  absolute 0x0021;
unsigned short hour    absolute 0x0022;
unsigned short day     absolute 0x0024;
unsigned short month   absolute 0x0025;
unsigned short year    absolute 0x0026;
unsigned short dato    absolute 0x0023;
unsigned char regolazione = 0;                //Variabile regolazione
unsigned char time[9];                        //Stringhe di caratteri
unsigned char ddate[11];
char text[4];
const  unsigned char  SECONDS_ADDRES=0x00;    //Indirizzi modulo DS3231
const  unsigned char  MINUTES_ADDRES=0x01;
const  unsigned char  HOURS_ADDRES  =0x02;
const  unsigned char  WEEKDAY_ADDRES=0x03;
const  unsigned char  DAY_ADDRES    =0x04;
const  unsigned char  MONTHS_ADDRES =0x05;
const  unsigned char  YEAR_ADDRES   =0x06;

void Write_CMD_DS3231(unsigned char address,unsigned char dato){      //Sottoprogramma scrittura comandi
   if(address == 0x0E ||  address == 0x0F){                           //Se l'indirizzo è 0x0E o 0x0F
      I2C1_Start();                                                   //Inizializza I2C Scrivi 0xD0
      I2C1_Wr(0xD0);                                                  //Scrivi indirizzo
      I2C1_Wr(address);                                               //Scrivi il dato
      I2C1_Wr(dato);                                                  //Ferma I2C
      I2C1_Stop();
   }
}

unsigned char Read_TIMER_DS3231(unsigned char address){              //Sottoprogramma lettura data e ora
   unsigned char value;
   if((address>=0x00)&&(address<=0x06)){                             //Se l'indirizzo è compreso
      I2C1_Start();                                                  //tra quelli di data e ora inizializza I2C
      I2C1_Wr(0xD0);                                                 //Scrivi 0xD0
      I2C1_Wr(address);                                              //Scrivi indirizzo
      I2C1_Repeated_Start();                                         //Se vi sono problemi ripeti lo start
      I2C1_Wr(0xD1);                                                 //Scrivi 0xD1
      value = I2C1_Rd(0);                                            //Leggi I2C
      I2C1_Stop();                                                   //Stop I2C
      return(value);
   }
}

unsigned char BCDUpperPart(unsigned char bcd_var){                   //Sottoprogramma BCD to decimale
   return (((bcd_var & 0xF0)>>4)|0x30);
}

unsigned char BCDLowerPart(unsigned char bcd_var){                   //Sottoprogramma BCD to Decimale
   return ((bcd_var & 0x0F)|0x30);
}

unsigned char IntToBCD (unsigned char int_var ){                     //Sottoprogramma da decimale a BCD
   return Dec2Bcd(int_var);
}

void Write_TIMER_DS3231(unsigned char address,unsigned char dato){   //Sottoprogramma scrittura data e ora
   if((address>=0x00)&&(address<=0x06)){                             //Se l'indirizzo è quello di data e ora
      I2C1_Start();                                                  //Scrivi indirizzo e dato
      I2C1_Wr(0xD0);
      I2C1_Wr(address);
      I2C1_Wr(dato);
      I2C1_Stop();
    }
}

void Write_RAM_DS3231(unsigned char address,unsigned char dato){    //Sottoprogramma scrittura EEprom
  if((address>=0x14)&&(address<=0xFF)){                             //Se l'indirizzo è quello della EEProm
     I2C1_Start();                                                  //Avvia scrittura
     I2C1_Wr(0xD0);
     I2C1_Wr(address);
     I2C1_Wr(dato);
     I2C1_Stop();
  }
}

unsigned char Read_RAM_DS3231(unsigned char address){              //Sottoprogramma lettura EEprom
  unsigned char value;
  if((address>=0x14)&&(address<=0xFF)){                            //Se l'indirizzo è quello della EEProm
    I2C1_Start();                                                  //Avvia lettura
    I2C1_Wr(0xD0);
    I2C1_Wr(address);
    I2C1_Repeated_Start();
    I2C1_Wr(0xD1);
    value = I2C1_Rd(0);
    I2C1_Stop();
    return(value);
  }
}

void Display_Lcd_Time(unsigned char time_vect){                   //Scrittura ora su LCD
    time[0] = BCDUpperPart(hour);                                 //Scrivi ora
    time[1] = BCDLowerPart(hour);
    time[2] = ':';                                                //scrivi :
    time[3] = BCDUpperPart(minute);                               //Scrivi minuti
    time[4] = BCDLowerPart(minute);
    time[5] = ':';                                                //Scrivi :
    time[6] = BCDUpperPart(sec);                                  //Scrivi secondi
    time[7] = BCDLowerPart(sec);
    time[8] = '\0';
    Lcd_Out(1,7,time);
}

void Display_Lcd_Date(unsigned char ddate_vect){                  //Scrittura Data su LCD
    ddate[0] = BCDUpperPart(day);                                 //Scrivi giorno
    ddate[1] = BCDLowerPart(day);
    ddate[2] ='.';                                                //Scrivi .
    ddate[3] = BCDUpperPart(month);                               //Scrivi mese
    ddate[4] = BCDLowerPart(month);
    ddate[5] ='.';                                                //Scrivi .
    ddate[6] = '2';                                               //Scrivi 20
    ddate[7] = '0';
    ddate[8] = BCDUpperPart(year);                                //Scrivi anno
    ddate[9] = BCDLowerPart(year);
    ddate[10] = '\0';
    Lcd_Out(2,7,ddate);
}

void EXT_INT() iv 0x0004 ics ICS_AUTO {                           //Interrupt
  Delay_ms(300);
  regolazione ++;                                                //Cambia regolazione
  INTCON.INTF = 0;                                               //reset del flag
}

void main(){                                                     //PORTB0 e 1 Ingressi
  TRISB.F0 = 1;
  TRISB.F1 = 1;

  INTCON.GIE = 1;                                                //attivo gli interrupt
  OPTION_REG.INTEDG = 1;                                         //interrupt sul fronte di salita
  INTCON.INTE = 1;

  Lcd_Init();                                                    // Initializza LCD
  Lcd_Cmd(_LCD_CLEAR);                                           //Puliscilo
  Lcd_Cmd(_LCD_CURSOR_OFF);                                      //Spegni cursore
  Lcd_Out(1,3," NE555.IT");                                      //Scrivi sulla prima riga
  Delay_ms(1000);

  I2C1_Init(100000);                                             //I2C a 100KHz

  Write_CMD_DS3231(0x0E,0x00);                                   //Configura modulo ds3231
  Write_CMD_DS3231(0x0F,0xF8);

  sec    =0x00;                                                  //Inizializza data e ora
  minute =0x00;
  hour   =0x00;
  day    =0x01;
  month  =0x01;
  year   =0x17;

  Write_TIMER_DS3231(SECONDS_ADDRES,sec);                        //Scrivi nel modulod data e ora
  Write_TIMER_DS3231(MINUTES_ADDRES,minute);
  Write_TIMER_DS3231(HOURS_ADDRES,hour);
  Write_TIMER_DS3231(DAY_ADDRES,day);
  Write_TIMER_DS3231(MONTHS_ADDRES,month);
  Write_TIMER_DS3231(YEAR_ADDRES,year);

  while(1){                                                      //Ciclo infinito
    Lcd_Cmd(_LCD_CLEAR);                                         //Scrivi data e ora
    Lcd_Out (1, 1, "TIME:");
    Lcd_Out (2, 1, "DATE:");
    sec    = Read_TIMER_DS3231(SECONDS_ADDRES);                 //Leggi data e ora e aggiorna
    minute = Read_TIMER_DS3231(MINUTES_ADDRES);
    hour   = Read_TIMER_DS3231(HOURS_ADDRES);
    day    = Read_TIMER_DS3231(DAY_ADDRES);
    month  = Read_TIMER_DS3231(MONTHS_ADDRES);
    year   = Read_TIMER_DS3231(YEAR_ADDRES);
    Display_Lcd_Time(time[9]);                                 //Visualizza data e ora
    Display_Lcd_Date(ddate[11]);
    Delay_ms(200);                                             //Aggiorna ogni 0.2secondi

    while (regolazione == 1){                                  //Se regolazione = 1
      Lcd_Out (1, 1, "ANNO:");                                 //mostra valore anno corrente anno
      Lcd_Out_Cp("20");
      ByteToStr(year, text);
      Lcd_Out_Cp(text);
      Lcd_Out_Cp("    ");                                      //usa stringhe vuote per pulire spazi
      Lcd_Out (2, 1, "                ");
      if(PORTB.F1 == 1){                                       //Se il tasto di incremento = 1
        Lcd_Cmd(_LCD_CLEAR);                                   //Pulisci LCD
        year ++;                                               //Incrementa anno
        if(year == 100) year = 0;                              //Se anno = 100 ritorna il valore a 0
        Delay_ms(200);                                         //ritardo anti rimbalzo
      }
    }

    while (regolazione == 2){                                  //Se regolazione = 2
      Lcd_Out (1, 1, "MESE: ");                                //Regola mese
      ByteToStr(month, text);
      Lcd_Out_Cp(text);
      Lcd_Out_Cp("   ");
      if(PORTB.F1 == 1){                                       //Se incremento premuto
        Lcd_Cmd(_LCD_CLEAR);                                   //incrementa mese fino a 12
        month ++;
        if(month == 13) month = 1;                             //ritorna a 1 se >12
        Delay_ms(200);
      }
    }

  while (regolazione == 3){                                     //Se regolazione = 3
    Lcd_Out (1, 1, "GIORNO: ");                                 //Regola giorno
    ByteToStr(day, text);
    Lcd_Out_Cp(text);
    Lcd_Out_Cp("   ");
    if(PORTB.F1 == 1){                                          //se incremento premuto
      Lcd_Cmd(_LCD_CLEAR);                                      //Incrementa giorno
      day ++;
      if(day == 32) day = 1;                                    //Attenzione, limite 31 per tutti i mesi
      Delay_ms(200);
    }
  }

  while (regolazione == 4){                                    //Se regolazione = 4
    Lcd_Out (1, 1, "ORA:  ");                                  //Regola ora
    ByteToStr(hour, text);
    Lcd_Out_Cp(text);
    Lcd_Out_Cp("   ");
    if(PORTB.F1 == 1){
      Lcd_Cmd(_LCD_CLEAR);
      hour ++;
      if(hour == 25) hour = 0;
      Delay_ms(200);
    }
  }

  while (regolazione == 5){                                    //Regolazione = 5 regola minuti
    Lcd_Out (1, 1, "MINUTI: ");
    ByteToStr(minute, text);
    Lcd_Out_Cp(text);
    Lcd_Out_Cp("   ");
    if(PORTB.F1 == 1){
      Lcd_Cmd(_LCD_CLEAR);
      minute ++;
      if(minute == 60) minute = 0;
      Delay_ms(200);
    }
  }

  while (regolazione == 6){                                  //Regolazione = 6 regola secondi
    Lcd_Out (1, 1, "SECONDI: ");
    ByteToStr(sec, text);
    Lcd_Out_Cp(text);
    Lcd_Out_Cp("   ");
    if(PORTB.F1 == 1){
      Lcd_Cmd(_LCD_CLEAR);
      sec ++;
      if(sec == 60) sec = 0;
      Delay_ms(200);
    }
  }

  if(regolazione == 7){                                     //Se regolazione = 7
    regolazione = 0;                                        //Resetta regolazione
    Lcd_Cmd(_LCD_CLEAR);
    sec    = IntToBCD (sec);                                //Converti da decimali a BCD
    minute = IntToBCD (minute);
    hour   = IntToBCD (hour);
    day    = IntToBCD (day);
    month  = IntToBCD (month);
    year   = IntToBCD (year);
    Write_TIMER_DS3231(SECONDS_ADDRES,sec);                 //Inserisci i valori nel modulo DS3231
    Write_TIMER_DS3231(MINUTES_ADDRES,minute);
    Write_TIMER_DS3231(HOURS_ADDRES,hour);
    Write_TIMER_DS3231(DAY_ADDRES,day);
    Write_TIMER_DS3231(MONTHS_ADDRES,month);
    Write_TIMER_DS3231(YEAR_ADDRES,year);
  }
 }
}

Prima di tutto si inizializzano le connessioni con il display LCD, indicando a quali pin esso è connesso. Successivamente vengono inizializzati i registri che contengono le variabili utili e le costanti dei registri per i comandi del modulo DS3231.

Il primo sottoprogramma “Write_CMD_DS3231” serve per scrivere un comando nel modulo DS3231 e si va ad inizializzare la comunicazione I2C, poi a scrivere l’indirizzo e successivamente il dato spegnendo in fine la comunicazione.

Il secondo sottoprogramma “Read_TIMER_DS3231” viene usato per leggere i dati dal modulo DS3231 e si va a scrivere l’indirizzo del registro da leggere e poi si legge la risposta.

I tre sottoprogrammi successivi vengono usati per convertire i dati da interi a BCD e da BCD a interi.

“Write_TIMER_DS3231” viene usato per scrivere nel registro ore, minuti, secondi, anno, mese e giorno e si va a inizializzare la comunicazione, scrivere il valore del registro da modificare e si scrive il dato.

“Write_RAM_DS3231” e “Read_RAM_DS3231” servono per scrivere e leggere dati dalla memoria sul modulo memoria.

Il sottoprogramma “Display_Lcd_Time” è utilizzato per mostrare a schermo l’ora e va a scomporre i tre elementi dell’orario in decine e unità e li mostra uno per volta con i due punti come separatore. Il programma successivo funziona alla stesso modo ma viene usato per la data utilizzando punto e virgola come separatore.

Il ciclo di interrupt “EXT_INT” è utilizzato per rilevare la pressione del pulsante di regolazione e va a incrementare una variabile chiamata regolazione che indica quale variabile andare ad aggiustare.

Nel programma principale si vanno ad inizializzare gli I/O prima di tutto, poi gli interrupt e successivamente il display e la comunicazione I2C con velocità 100KHz. Infine prima del ciclo infinito si va ad inizializzare il modulo DS3231.

Nel ciclo infinito si pulisce il display, si scrive data e ora e poi si leggono questi valori mostrandoli a schermo ogni 200ms. Per la regolazione si usa la variabile regolazione, se 1 si regola l’anno, 2 il mese, 3 il giorno, 4 l’ora, 5 i minuti, 6 i secondi e infine se regolazione=7  si va a memorizzare nel modulo data e ora regolata. Se regolazione = 7 si resetta la regolazione, si convertono i dati da interi a BCD e si vanno a scrivere con la funzione “Write_TIMER_DS3231”.

In ogni singolo “while” che controlla la regolazione viene visualizzata su LCD cosa si va a regolare e si va a visualizzare anche il valore. Se l’altro tasto è a livello logico alto allora si incrementa il valore da regolare ogni 200ms e se supera il massimo del valore consentito viene portato a livello iniziale. Tutti e sei i while svolgono le stesse funzioni.

La configurazione dei fuses è la seguente:

 

SCHEMA

Lo schema è il seguente:

La tensione di alimentazione è 5V continui e stabilizzati. Sia il modulo DS3231 che il microcontrollore sono alimentati da questa tensione a 5V inoltre il modulo ha la batteria al litio da 3V a bottone per far si che non si stoppi il conteggio anche se la tensione di alimentazione non è presente. Il PIC16F877A è alimentato tramite i pin 11 e 32 connessi alla tensione positiva e i pin 12 e 31 connessi alla massa dell’alimentazione. Anche il display LCD è connesso alla tensione di 5V con la retroilluminazione del display connessa tra il positivo e massa tramite una resistenza da 470 Ohm. C5 serve per filtrare la tensione.

Il microcontrollore viene mantenuto in stato di funzionamento grazie alla resistenza di pull-up R1 mentre il circuito di oscillazione da 8MHz è formato dal cristallo di quarzo X1 da 8MHz e da C1 e C2.

Il pulsante “REG” porta una tensione alta sul pin 33 quando premuto mentre R3 lo mantiene a livello basso. C4 viene usato come condensatore antirimbalzo. La stessa cosa vale per il pulsante “INC”, R2 e C3.

Il modulo DS3231 è connesso al microcontrollore tramite le linee SCL e SDA. Teoricamente servirebbe una resistenza di pull-up su queste linee ma esse sono già presenti nel modulo DS3231.

Lo schema montato su breadboard è il seguente:

 

DOWNLOAD

Potete scaricare la simulazione del circuito con PROTEUS e il codice compilato con MIKROC al seguente LINK!!!




 


[Voti Totali: 0 Media Voti: 0]
Segui la Nostra Pagina Facebook: Facebook

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *