Programació en C del PIC 16F690

Referència Trucs Perifèrics   Recursos CITCEA
Tutorial Exemples Projectes   Inici

Rellotge de temps real

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).

Rellotge de temps real

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
}

 

 

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