Per fer les funcions de rellotge de temps real, hem triat un mòdul amb el DS1307 RTC que és un rellotge en temps real (real time clock, RTC).

El mòdul incorpora una petita pila que manté el rellotge en funcionament quan no hi ha alimentació externa. A més de la funció RTC, aquest mòdul proporciona a l'usuari 56 bytes de memòria RAM que no s'esborra quan es perd l'alimentació jaque hi ha la pila de reserva.
El connexionat d'aquest mòdul és molt senzill ja que només cal connectar dues potes d'alimentació i dues de comunicació. Encara que el PIC16F690 incorpora la comunicació I2C, s'ha preferit implementar des de zero les funcions de comunicació. En la configuració triada, les connexions són les de la taula següent:
| Mòdul RTC | PIC16F690 | Funció |
| 5V | Vdd | Positiu de l'alimentació |
| GND | Vss | Negatiu de l'alimentació |
| SDA | RC7 | Canal de comunicació I2C |
| SCL | RC6 | Rellotge I2C |
El mòdul RTC també pot generar una sortida (SQW) que, en principi, s'activa un cop cada segon però que també pot fer-ho amb altres periodicitats.
Les dades corresponents a la data i l'hora estan guardades en la memòria del mòdul RTC. En aquesta memòria també hi podem escriure per posar la data i l'hora correctes. La comunicació I2C ens permet accedir a adreces concretes de memòria. La funció de cada una s'indica a la següent taula:
| Adreça (decimal) |
Funció | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | Rang de valors |
| 0 | Segons | CH | Desenes segons | Unitats segons | 00 - 59 | |||||
| 1 | Minuts | 0 | Desenes minuts | Unitats minuts | 00 - 59 | |||||
| 2 | Hores | 0 | 1 → 12 h | 1 → PM 0 → AM |
Desenes hores | Unitats hores | 01 - 12 | |||
| 0 → 24 h | Desenes hores | Unitats hores | 00 - 23 | |||||||
| 3 | Dia de la setmana | 0 | 0 | 0 | 0 | 0 | Dia de la setmana | 1 - 7 | ||
| 4 | Dia del mes | 0 | 0 | Desenes dia del mes | Unitats dia del mes | 00 - 31 | ||||
| 5 | Mes | 0 | 0 | 0 | Desenes mes | Unitats mes | 01 - 12 | |||
| 6 | Any | Desenes any | Unitats any | 00 - 99 | ||||||
| 7 | Polsos | Estat actual SQW | 0 | 0 | 1 → activa sortida polsos 0 → desactiva sortida polsos |
0 | 0 | Freqüència polsos | ||
| 8 - 63 | RAM | Memòria lliure | ||||||||
Quan el bit CH és 1 el rellotge s'atura i permet estalviar energia.
El dia del mes es gestiona a partir del calendari i, per tant, es té en compte el nombre de dies del mes actual (inclosos anys de traspàs). En canvi, el dia de la setmana es gestiona en forma independent de la resta, per tant, podem considerar que 1 és dilluns i 7 diumenge o qualsevol altra opció.
A l'hora de llegir i escriure dades, la comunicació I2C contempla diverses opcions. En aquest cas només contemplarem la lectura i l'escriptura d'un únic byte. Els elements ques es comuniquen amb I2C han de tenir una adreça ja que hi pot haver diversos elements connectats simultàniament. El nostre mòdul RTC té una adreça I2C de set bits 1101000 que haurem d'indicar quan ens hi connectem.
A continuació tenim un programa de mostra que envia al microcontrolador una data (que podem canviar, si ho desitgem) i porta un bucle que la va llegint cada un segon (aproximadament). S'ha triat una data i hora properes al moment de canvi de dia i de mes per poder veure com ho fa.
#pragma config FOSC = INTRCIO, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = OFF #pragma config CPD = OFF, BOREN = OFF, IESO = OFF, FCMEN = OFF #include <xc.h> // Carrega el fitxer de funcions necessari per al compilador XC8 #define _XTAL_FREQ 4000000 // La freqüència del rellotge és 4 MHz
char AdreI2C = 0b11010000; // Adreça del dispositiu I2C (1101000) // Rodada cap a l'esquerra (preparada per afegir el bit RW) char Posicio = 0; // Posició a la pantalla char Lectura; // Aquí guardarem el resultat de la lectura char Port = 0; // Valor a escriure al port C // RC7 és SDA // RC6 és SCL
// Definició de les funcions que farem servir char LlegirI2C(char AdreMem); // Llegeix un byte I2C void EscriuI2C(char AdreMem, char Dades); // Escriu un byte I2C void SDAentrada(void); // Posa SDA com a entrada void SDAsortida(void); // Posa SDA com a sortida void StartI2C(void); // Envia el bit d'inici I2C void StopI2C(void); // Envia el bit d'aturada I2C void ACK_S(void); // Reb un ACK des de l'esclau void NACK_M(void); // Envia un NACK a l'esclau void EnvByteI2C(char Buffer); // Funció interna que envia un byte a I2C char RebByteI2C(void); // Funció interna que rep un byte a I2C void EnviaL(char Caracter); // Envia un caràcter void EnviaBCD2(char Caracter); // Mostrem a la pantalla un valor BCD // a partir de dues xifres hexadecimals void EnviaBCD1(char Caracter); // Mostrem a la pantalla un valor BCD // a partir d'una xifra hexadecimal
void main (void) {
ANSEL = 0b00000101; // Configura AN0 i AN2 com entrada analògica
ANSELH = 0; // Desactiva les altres entrades analògiques
TRISA = 0xFF; // Tot el port A és d'entrada
TRISB = 0; // Tot el port B és de sortida
TRISC = 0; // Tot el port C és de sortida
TXSTAbits.BRGH = 1; // Configuració de velocitat
BAUDCTLbits.BRG16 = 0; // Paràmetre de velocitat de 8 bits
SPBRG = 25; // Velocitat de 9600 baud
TXSTAbits.SYNC = 0; // Comunicació asíncrona
TXSTAbits.TX9 = 0; // Comunicació de 8 bits
RCSTAbits.SPEN = 1; // Activa comunicació sèrie
TXSTAbits.TXEN = 1; // Activa comunicació
PORTB = 0; // Inicialitza a 0 el port B
PORTC = 0; // Inicialitza a 0 el port C
// Com a mostra, es posen les 23.58.34
// del dijous 31-3-2016
// per poder veure un canvi de dia i de mes
EscriuI2C(0, 0x34); // Segons
EscriuI2C(1, 0x58); // Minuts
EscriuI2C(2, 0x23); // Hores
EscriuI2C(3, 0x04); // Dia setmana (dijous)
EscriuI2C(4, 0x31); // Dia mes
EscriuI2C(5, 0x03); // Mes (març)
EscriuI2C(6, 0x16); // Any (2016)
EscriuI2C(7, 0x00); // Polsos desactivats
while (1) {
__delay_ms(1000); // Retard d'1 s
Posicio = 64; // La primera columna de la segona fila és 64
Posicio = Posicio + 128; // Posa el bit de posicionat a 1
EnviaL(254); // Control de la posició del cursor
EnviaL(Posicio); // Canvia el cursor de lloc
Lectura = LlegirI2C(2); // Hores
EnviaBCD2(Lectura); // Mostrem a la pantalla el valor
EnviaL('.'); // Separador
Lectura = LlegirI2C(1); // Minuts
EnviaBCD2(Lectura); // Mostrem a la pantalla el valor
EnviaL('.'); // Separador
Lectura = LlegirI2C(0); // Segons
EnviaBCD2(Lectura); // Mostrem a la pantalla el valor
Posicio = 0; // La primera columna de la primera fila és 0
Posicio = Posicio + 128; // Posa el bit de posicionat a 1
EnviaL(254); // Control de la posició del cursor
EnviaL(Posicio); // Canvia el cursor de lloc
Lectura = LlegirI2C(4); // Dia
EnviaBCD2(Lectura); // Mostrem a la pantalla el valor
EnviaL('-'); // Separador
Lectura = LlegirI2C(5); // Mes
EnviaBCD2(Lectura); // Mostrem a la pantalla el valor
EnviaL('-'); // Separador
EnviaL('2'); // Any primer dígit
EnviaL('0'); // Any segon dígit
Lectura = LlegirI2C(6); // Dia
EnviaBCD2(Lectura); // Mostrem a la pantalla el valor
EnviaL(' '); // Espai
EnviaL(' '); // Espai
Lectura = LlegirI2C(3); // Dia
EnviaBCD1(Lectura); // Mostrem a la pantalla el valor
}
}
char LlegirI2C(char AdreMem) {
char Rebut; // Valor rebut
SDAsortida(); // Posa SDA com a sortida
StartI2C(); // Envia el bit d'inici I2C
EnvByteI2C(AdreI2C); // Enviem l'adreça del dispositiu
// Amb un zero al final per indicar escriptura
SDAentrada(); // Posa SDA com a entrada
ACK_S(); // Reb un ACK des de l'esclau
SDAsortida(); // Posa SDA com a sortida
EnvByteI2C(AdreMem); // Adreça a enviar
SDAentrada(); // Posa SDA com a entrada
ACK_S(); // Reb un ACK des de l'esclau
SDAsortida(); // Posa SDA com a sortida
StartI2C(); // Envia el bit d'inici I2C
EnvByteI2C(AdreI2C+1); // Enviem l'adreça del dispositiu
// Amb un 1 al final per indicar lectura
SDAentrada(); // Posa SDA com a entrada
ACK_S(); // Reb un ACK des de l'esclau
Rebut = RebByteI2C(); // Rep el byte
SDAsortida(); // Posa SDA com a sortida
NACK_M(); // Envia un NACK a l'esclau
StopI2C(); // Envia el bit d'aturada I2C
return Rebut; // Retorna el valor
}
void EscriuI2C(char AdreMem, char Dades) {
SDAsortida(); // Posa SDA com a sortida
StartI2C(); // Envia el bit d'inici I2C
EnvByteI2C(AdreI2C); // Enviem l'adreça del dispositiu
// Amb un zero al final per indicar escriptura
SDAentrada(); // Posa SDA com a entrada
ACK_S(); // Reb un ACK des de l'esclau
SDAsortida(); // Posa SDA com a sortida
EnvByteI2C(AdreMem); // Adreça a enviar
SDAentrada(); // Posa SDA com a entrada
ACK_S(); // Reb un ACK des de l'esclau
SDAsortida(); // Posa SDA com a sortida
EnvByteI2C(Dades); // Dades a enviar
SDAentrada(); // Posa SDA com a entrada
ACK_S(); // Reb un ACK des de l'esclau
SDAsortida(); // Posa SDA com a sortida
StopI2C(); // Envia el bit d'aturada I2C
}
void SDAentrada() {
TRISCbits.TRISC7 = 1; // Posem SDA com a entrada
}
void SDAsortida() {
TRISCbits.TRISC7 = 0; // Posem SDA com a sortida
}
void StartI2C() {
Port = Port | 0b11000000; // Abans de començar, SDA ha d'estar activat
// i SCL també
PORTC = Port;
__delay_us(2); // Allarguem el pols
Port = Port & 0b01111111; // Bit d'inici, posem SDA a zero
PORTC = Port;
__delay_us(2); // Allarguem el pols
Port = Port & 0b10111111; // Fi del bit d'inici, posem SCL a zero
PORTC = Port;
}
void StopI2C() {
Port = Port & 0b01111111; // Desactivem SDA
Port = Port | 0b01000000; // i activem SCL
PORTC = Port;
__delay_us(2); // Allarguem el pols
Port = Port | 0b10000000; // Posem SDA a 1
PORTC = Port;
}
void ACK_S() {
Port = Port | 0b01000000; // Posem SCL a 1
PORTC = Port;
__delay_us(2); // Allarguem el pols
Port = Port & 0b10111111; // Posem SCL a zero
PORTC = Port;
Port = Port | 0b10000000; // Posem SDA a 1
PORTC = Port;
}
void NACK_M() {
Port = Port | 0b10000000; // Posem SDA a 1
PORTC = Port;
Port = Port | 0b01000000; // Posem SCL a 1
PORTC = Port;
__delay_us(2); // Allarguem el pols
Port = Port & 0b10111111; // Posem SCL a zero
PORTC = Port;
}
void EnvByteI2C(char Buffer) {
char Temp; // Variable temporal
for (signed char k = 1; k < 9; k++){
Port = Port & 0b10111111; // Posem SCL a zero per modificar SDA
PORTC = Port;
Temp = Buffer & 0b10000000; // Agafa el bit de més a l'esquerra
// Temp només podrà valer 0 o 128
if (Temp == 0) { // Si val 0
Port = Port & 0b01111111; // Desactivem SDA
} else { // Si val 128
Port = Port | 0b10000000; // Posem SDA a 1
}
Buffer = Buffer << 1; // Rodem els bits per situar el següent
PORTC = Port;
Port = Port | 0b01000000; // Activem SCL perquè el receptor llegeixi el bit
PORTC = Port;
}
Port = Port & 0b10111111; // Posem SCL a zero per modificar SDA
PORTC = Port;
}
char RebByteI2C() {
char Buffer; // Valor rebut
Port = Port & 0b10111111; // Posem SCL a zero
PORTC = Port;
for (signed char k = 1; k < 9; k++){
Port = Port | 0b01000000; // Posem SCL a 1
PORTC = Port;
Buffer = Buffer << 1; // Rodem els bits per situar el següent
// a la dreta hi quedarà un zero
if (RC7 == 1) {
Buffer = Buffer | 0b00000001; // Si SDA està activat, posem un 1
}
Port = Port & 0b10111111; // Posem SCL a zero
PORTC = Port;
}
return Buffer; // Retorna el valor
}
void EnviaL(char Caracter) {
TXREG = Caracter; // Agafa el caràcter i l'envia
__delay_ms(1); // Donem temps
while (PIR1bits.TXIF == 0) // Esperem que s'acabi d'enviar
; // No fem res
}
void EnviaBCD2(char Caracter) {
char Temp; // Variable temporal
Temp = Caracter & 0b11110000; // Agafem el primer dígit
Temp = Temp >> 4; // Rodem els bits per situar-los a la dreta
Temp = Temp + '0'; // Ho convertim a ASCII
EnviaL(Temp); // Envia el primer dígit
Temp = Caracter & 0b00001111; // Agafem el segon dígit
Temp = Temp + '0'; // Ho convertim a ASCII
EnviaL(Temp); // Envia el segon dígit
}
void EnviaBCD1(char Caracter) {
char Temp; // Variable temporal
Temp = Caracter & 0b00001111; // Agafem el segon dígit
Temp = Temp + '0'; // Ho convertim a ASCII
EnviaL(Temp); // Envia el segon dígit
}

Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.