Der PCF8583 ist ein so genannter Real-Time-Clock (kurz RTC, Echtzeituhr) Baustein. RTC Bausteine sind besonders geeignet, wenn eine Zeitinformation immer präsent sein und auch bei Verlust der Versorgungsspannung erhalten bleiben muss. Damit die Uhrzeit nicht verloren geht, wird eine Pufferbatterie (man verwendet hier Knopfzellen, da diese eine sehr konstante Spannungskurve aufweisen) an die RTC angeschlossen.
Sollte nun die Versorgungsspannung ausfallen, wird die RTC, und nur die RTC, weiterhin versorgt. Es ist wichtig, dass die Knopfzelle wirklich nur für die RTC vorgesehen wird, da Knopfzellen in der Regel eine eher geringe Kapazität haben und außerdem nicht für Ströme jenseits von 1 mA ausgelegt sind! Der PCF8583 wird über den I2C Bus (auch TWI, IIC genannt) angesteuert. Er besteht aus einem 256 Byte großem RAM, welches sich in verschiedene Funktionalität aufgliedert. Den PCF8583 gibt es für 1,40€ (Stand 2013) bei Reichelt .
Aufbau des PCF8583
Wie ich in der Einleitung schon erwähnt habe besteht die RTC im wesentlichen aus einem 256 Byte großem RAM in dem das Steuerregister der RTC und die Daten abgelegt sind. Der Adressbereich des RAMs reicht von 0x00 bis 0xFF. Das Steuerregister ist unter der Adresse 0x00 erreichbar. Wie beim I2C Bus üblich hat jeder angeschlossene Teilnehmer eine (einmalige) Adresse. Der Freiraum der Adresse des PCF8583 ist leider etwas begrenzt, was in den meisten Fällen aber nicht stört. Man kann sich als Anwender zwischen zwei Adressen entscheiden: 0xA0 oder 0xA2. Dabei ist das Bit 1 hardwaremäßig festzulegen! Der Anschlusspin A0 entspricht dem Bit 1 der Slave Adresse. Wird A0 auf Masse gelegt, so ist die Adresse der RTC 0xA0. Wird A0 hingegen auf das positive Potential gelegt, so lautet die Adresse 0xA2. Des Weiteren verfügt der PCF8583 über einen INT Ausgang, welcher low aktiv ist. An diesem Pin kann die RTC zum Beispiel ein 1 Hz Signal ausgeben. Das 1 Hz Signal nutze ich in meinem Mini Anzeige Modul um den PIC aus dem Sleep Mode zu wecken. So erspare ich mir das Erzeugen einer 1s Zeitbasis innerhalb des PIC.
Ansteuerung
Nachfolgend stelle ich meine Routinen zur Ansteuerung des PCF8583 zur Verfügung. Da dabei selbstverständlich auch I2C Routinen notwendig sind, sind diese gleich mit dabei. Ich habe für die Routinen eine Struktur vorgesehen, welche sämtliche Zeitinformationen enthält. Diese können mit der Funktion setTime in die PCF8583 geladen werden (zum Beispiel um bei aller erste Inbetriebnahme der RTC die Uhrzeit einzustellen). Außerdem kann die Zeit aus der RTC mit der Funktion getTime aus der RTC in die Struktur geladen werden.
time.c
Die nun folgenden Programmcodes werden erfolgreich in meinem Projekt: Mini Anzeige Modul eingesetzt.
/************************************************ * File: time.c * Project: -- * Author: Nicolas Meyertöns * Version: -- * Web: PIC-Projekte.de * ************************************************/ #include <xc.h> #include "time.h" #include "i2c.h" tTime time = { 0,0,2013,0,0,0,0,MEZ }; void initPCF8583(void) { startI2C(); sendI2C(PCF_ADDR_W); sendI2C(PCF_CONTROL); sendI2C(PCF_SET_CONTROL); stopI2C(); /*Direkt die Uhrzeit aus der RTC beziehen*/ getTime(); } void setTime(void) { startI2C(); sendI2C(PCF_ADDR_W); sendI2C(PCF_SECONDS); /*Auto Inkrement..*/ sendI2C(ITOBCD(time.Sec)); sendI2C(ITOBCD(time.Min)); sendI2C(ITOBCD(time.Hour)); /*Jahres ID# ermitteln (0..3 wobei 0: Schaltjahr*/ sendI2C((((time.Year-YEAR_ID)%4)<<6) | ITOBCD(time.Day)); sendI2C((time.wDay <<5) | ITOBCD(time.Month)); stopI2C(); } void getTime(void) { uint8_t tmp; /*Setzten des Adresspointers der RTC*/ startI2C(); sendI2C(PCF_ADDR_W); sendI2C(PCF_SECONDS); startI2C(); sendI2C(PCF_ADDR_R); tmp = reciveI2C_ack(); time.Sec = BCDTOI(tmp); tmp = reciveI2C_ack(); time.Min = BCDTOI(tmp); tmp = reciveI2C_ack(); time.Hour = BCDTOI(tmp); tmp = reciveI2C_ack(); time.Day = BCDTOI((0x3F & tmp)); tmp = reciveI2C_nack(); time.Month = BCDTOI((0x1F & tmp)); time.wDay = BCDTOI(((0xE0 & tmp))>>5); stopI2C(); }
time.h
/************************************************ * File: time.h * Project: -- * Author: Nicolas Meyertöns * Version: -- * Web: PIC-Projekte.de * ************************************************/ #ifndef TIME_H #define TIME_H #include <stdint.h> #include <stdbool.h> /*Makros*/ #define ITOBCD(x) ( ( ( x / 10 ) << 4 ) + (x % 10) ) #define BCDTOI(x) ( ( ( x >> 4 ) * 10 ) + ( x & 0x0F ) ) #define PCF_ADDR_W 0xA0 #define PCF_ADDR_R 0xA1 #define PCF_CONTROL 0x00 #define PCF_SET_CONTROL 0x00 #define PCF_SECONDS 0x02 #define PCF_MINUTES 0x03 #define PCF_HOURS 0x04 #define PCF_YEAR_DAY 0x05 #define PCF_WEEKDAY_MONTH 0x06 #define PCF_CLEAR_SEC 0x00 /*2012 war ID#0 nun folgt alle 4 Jahre die ID#0*/ #define YEAR_ID 12 #define MESZ true #define MEZ false /*Strukturen*/ enum eDay { Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag, Sonntag }; struct sTime { uint8_t Day; uint8_t Month; uint16_t Year; uint8_t Hour; uint8_t Min; uint8_t Sec; enum eDay wDay; bool stime; }; typedef struct sTime tTime; /*Variablen aknündigen*/ extern tTime time; /*Prototypen*/ void initPCF8583(void); void setTime(void); void getTime(void); #endif /* TIME_H */
i2c.c
/********************************************* * File: i2c.c * Project: -- * Author: Nicolas Meyertöns * Version: -- * Web: PIC-Projekte.de * ************************************************/ #include <xc.h> #include "i2c.h" /* * Initialisierung des I2C Bus * Anmerkung: Hier am PIC18LF45K22 */ void initI2C(void) { /*Zugehörige Tris-Bits auf Input schalten*/ /*Master-Mode - Clock = Fosc / (4*(SSPxADD+1)) */ /*SSPxADD values of 0, 1 or 2 are not supported for I2C Mode*/ SSP2CON1bits.SSPM3 = 1; SSP2CON1bits.SSPM2 = 0; SSP2CON1bits.SSPM1 = 0; SSP2CON1bits.SSPM0 = 0; SSP2CON1bits.SSPEN = 1; SSP2ADD = 9; // Für einen Takt von 100kHz SSP2CON2bits.ACKDT = 0; } void idleI2C(void) { /*Läuft eine Übertragung?*/ while( SSP2STATbits.R_W ); /*Ist irgendwas anderes aktiv?*/ while( SSP2CON2 & 0x1F ); } uint8_t sendI2C(uint8_t byte) { idleI2C(); PIR3bits.SSP2IF = 0; SSP2BUF = byte; /*Warten bis Übertragung abgeschlossen ist*/ waitI2C(); return ~SSP2CON2bits.ACKSTAT; } uint8_t reciveI2C_nack(void) { uint8_t incomming = 0; idleI2C(); PIR3bits.SSP2IF = 0; SSP2CON2bits.RCEN = 1; waitI2C(); SSP2CON2bits.ACKDT = 1; SSP2CON2bits.ACKEN = 1; while( SSP2CON2bits.ACKEN ); incomming = SSP2BUF; /*Nur empfangen wenn rw true*/ return incomming; } uint8_t reciveI2C_ack(void) { uint8_t incomming = 0; idleI2C(); PIR3bits.SSP2IF = 0; SSP2CON2bits.RCEN = 1; waitI2C(); SSP2CON2bits.ACKDT = 0; SSP2CON2bits.ACKEN = 1; while( SSP2CON2bits.ACKEN ); incomming = SSP2BUF; /*Nur empfangen wenn rw true*/ return incomming; } void startI2C(void) { idleI2C(); SSP2CON2bits.SEN = 1; while( SSP2CON2bits.SEN ); } void restartI2C(void) { idleI2C(); SSP2CON2bits.RSEN=1; while( SSP2CON2bits.RSEN ); } void stopI2C(void) { idleI2C(); SSP2CON2bits.PEN = 1; while( SSP2CON2bits.PEN ); } void waitI2C(void) { while(!PIR3bits.SSP2IF); PIR3bits.SSP2IF = 0; }
i2c.h
/********************************************* * File: i2c.h * Project: -- * Author: Nicolas Meyertöns * Version: -- * Web: PIC-Projekte.de * ************************************************/ #ifndef I2C_H #define I2C_H #include <stdint.h> #include <stdbool.h> /*Makros*/ /*I2C Makros*/ #define IIC_READ 1 #define IIC_WRITE 0 #define true 1 #define false 0 /*Prototypen*/ void initI2C(void); uint8_t sendI2C(uint8_t byte); void startI2C(void); void restartI2C(void); void stopI2C(void); uint8_t reciveI2C_ack(void); uint8_t reciveI2C_nack(void); void waitI2C(void); void idleI2C(void); #endif /* I2C_H */
Zeitinformationen als BCD Code
Die Zeitinformationen in der RTC liegen als BCD Code vor. Was bedeutet das? Ich zeige es am Besten an einem Beispiel: Die dezimale Zahl 16 ist im BCD Format 0x16. Aber Hexadezimal 0x16 ist natürlich nicht dezimal 16 sondern 1*16 + 6*1 = 22. Eine Dezimalzahl wird im BCD Format durch 4 Bit repräsentiert, welche den Wert 0..9 annehmen kann. Da man mit 4 Bit aber von 0 bis 15 Zahlen darstellen kann, sind die Werte 10-15 oder xA bis xF nicht erlaubt. Man nennt diese Werte auch Pseudotetraden (siehe auch Wikipedia ).
Ein weiteres Beispiel anhand des Registers Nummer 0x05 aus dem RAM des PCF8583. Die RAM Zelle mit der Adresse 0x05 beinhaltet die Information über das Jahr sowie den Tag. Der Aufbau dieser Zelle ist wie folgt:
Speicheradresse 0x05 - Zustand nach Reset: 0x01 MSB LSB 0 6 5 4 3 2 1 0 | | | | | | | | +-+-+ +-+-+ | | | | Tage in BCD: | | +---+---+---+----> Einer Stelle | +----------------------> Zehner(0..3) +------------------------------> Jahr(0..3)
Wenn diese Zelle zum Beispiel den Inhalt 0x71 hat, so würde das bedeuten, dass es der Tag 31 ist. Denn das gesamte untere Nibble (Bit 0..3) sind für die “Einerstelle” des Tages. Und da steht nun mal 0001 (0x_1). Somit steht die 1 schon fest. Im oberen Nibble (Bit 4..7) sind nur die Bits 4 und 5 für die Zehnerstelle des Tages verantwortlich, denn mehr als 3_ Tage hat ein Monat ja ohnehin nicht. Somit können in den Bits 6 und 7 noch weitere Informationen gespeichert werden und das ist in diesem Fall die Angabe des Jahres. Aber nun zurück zum Tag. Wir hatten für die erste Ziffer bereits die 1 festgestellt. Wenn wir nun unter Berücksichtigung, dass nur Bit 4 und 5 für die nächste Ziffer des Tages zuständig sind, dann erhalten wir wir _ _ 11. Und das ist eine 3. Somit kommen wir zu dem Schluss, dass in dieser Situation der Tag der 31. ist. Und weiterhin ist die Information für das Jahr der Wert 1.
Warum geht das Jahr nur von 0 bis 3?
Das ist recht schnell zu beantworten: Die RTC hat den genauen Wert für das Jahr nicht gespeichert! Es wird jedoch angegeben um “was für ein Jahr” es sich handelt. Wir unterscheiden zwischen normalem und Schaltjahr. In einem Schaltjahr hat der Februar 29 anstatt sonst 28 Tage. Die PCF8583 signalisiert ein Schaltjahr mit einem “Jahreswert” von 0. Sprich, ist der Wert in den Bits 6 und 7 im Register 0x05 00, so hat der Februar in diesem Jahr 29 Tage. Ich habe das so gelöst in dem ich das aktuelle Jahr in meinem Programm mitführe und eben bei einem Jahreswechsel um 1 erhöhe. Damit ich eine Referenz habe (für die Jahreswerte 0…3) definiere ich das Jahr (20)12 als 0, denn dies war ein Schaltjahr. Der Wert für die beiden Jahresbits kann nun wie folgt ermittelt werden:
(((time.Year-YEAR_ID)%4)<<6)
Wenn das time.Year (also das aktuelle Jahr) zum Beispiel (20)13 wäre, dann würden die Jahresbits 01 lauten. Mit anderen Worten “noch 3 Jahre bis zum nächsten Schaltjahr” oder “Ein Jahr seit dem letzten Schaltjahr vergangen”.