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!!!