Referència | Trucs | Perifèrics | Recursos CITCEA | |
Tutorial | Exemples | Projectes | Inici |
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.