Using the PCF8583 as a clock component on the I2C bus of the Raspberry Pi

Clock and Calendar

As I said at the start, the PCF8583 chip functions primarily as a clock and calendar component. The example program I2C8583Timer sets the clock time and the alarm register and then reads out the stored time value (Listing 2).

Listing 2

I2C8583Timer (In Part)

01 public static void getTime() {
02   try {
03     I2CBus bus = I2CFactory.getInstance(I2CBus.BUS_1);
04     I2CDevice pcf8583Address = bus.getDevice(address);
05     byte status = 0x04;
06     if (isAlarmSet()){ status = 0x06; }
07     pcf8583Address.write((byte)0x00,(byte)0xc0);
08     int sec   = BCDtoInt (((Integer)pcf8583Address.read(0x02)).byteValue());
09     int min   = BCDtoInt (((Integer)pcf8583Address.read(0x03)).byteValue());
10     int hour  = BCDtoInt (((Integer)pcf8583Address.read(0x04)).byteValue());
11     int day   = BCDtoInt (((Integer)(pcf8583Address.read(0x05) & 0x3f)).byteValue() );
12     int year  = ((Integer)((pcf8583Address.read(0x05)>>6) & 0x03)).byteValue();
13     int month = BCDtoInt (((Integer)(pcf8583Address.read(0x06) & 0x1f)).byteValue() );
14     pcf8583Address.write((byte)0x00,(byte)0x10);
15     int yearBase = 0;
16     yearBase = pcf8583Address.read(0xfe);
17     yearBase = yearBase << 8;
18     yearBase = yearBase | pcf8583Address.read(0xff);
19     year = year + yearBase;
20     pcf8583Address.write((byte)0x00,(byte)status);
21     System.out.printf("%02d:%02d:%02d %02d.%02d.%4d",hour,min,sec,day,month,year);
22     System.out.println(" Alarm:"+isAlarmSet());
23   }
24   catch(Exception e) { System.out.println("Get Time"+e); }
25 }
26
27 public static void setTime(byte day,byte month,int year,byte hour,byte min,byte sec) {
28   try {
29     I2CBus bus=I2CFactory.getInstance(I2CBus.BUS_1);
30     I2CDevice pcf8583Address = bus.getDevice(address);
31     pcf8583Address.write((byte)0x00,(byte)0xc0);
32     pcf8583Address.write((byte)0x01,(byte)(0x00));
33     pcf8583Address.write((byte)0x02,(byte)(DecToBCDArray (sec)[0]));
34     pcf8583Address.write((byte)0x03,(byte)(DecToBCDArray (min)[0]));
35     pcf8583Address.write((byte)0x04,(byte)(DecToBCDArray(hour)[0]));
36     pcf8583Address.write((byte)0x05,(byte)(((byte)(year % 4) << 6) | DecToBCDArray(day)[0] ));
37     pcf8583Address.write((byte)0x06,(byte)(DecToBCDArray(month)[0]));
38     pcf8583Address.write((byte)0x00,(byte)(0x10));
39     int yearBase = (year - year % 4);
40     pcf8583Address.write((byte)0xfe,(byte)(yearBase >>8));
41     pcf8583Address.write((byte)0xff,(byte)(yearBase & 0x00ff));
42     pcf8583Address.write((byte)0x00,(byte)0x04);
43   }
44   catch(Exception e) { System.out.println("Set Time"+e); }
45 }
46
47 public static void setAlarm(byte day,byte month,byte hour,byte min,byte sec) {
48   try {
49     I2CBus bus=I2CFactory.getInstance(I2CBus.BUS_1);
50     I2CDevice pcf8583Address = bus.getDevice(address);
51     pcf8583Address.write((byte)0x00,(byte)0xc0);
52     pcf8583Address.write((byte)0x09,(byte)(0x00));
53     pcf8583Address.write((byte)0x0A,(byte)(DecToBCDArray(sec)[0]));
54     pcf8583Address.write((byte)0x0B,(byte)(DecToBCDArray(min)[0]));
55     pcf8583Address.write((byte)0x0C,(byte)(DecToBCDArray(hour)[0]));
56     pcf8583Address.write((byte)0x0D,(byte)(DecToBCDArray(day)[0] ));
57     pcf8583Address.write((byte)0x0E,(byte)(DecToBCDArray(month)[0]));
58     pcf8583Address.write((byte)0x08,(byte)0xb0);
59     pcf8583Address.write((byte)0x00,(byte)0x04);
60   }
61   catch(Exception e) { System.out.println("Set Alarm"+e); }
62 }

The first thing that stands out is that the program contains a fair number of type casts, because only the type byte can be used for communication with the chip. As discussed before, all of the numbers are stored in BCD format. See the box titled "Details of BCD." The only real magic occurs when the addresses 0x00 (Table 3) and 0x08 (Table 4) are accessed. For example, when writing the value 0xc0 into register 0x00, the current values (e.g., time and date or the counter values) are retained in the latches of the chip.

Details of BCD

BCD stands for "binary-coded decimal," a class of binary encodings for decimal numbers. Simply put, this means that each half byte or "nibble" stores a decimal position of a number from 0 to 9. Why would anyone do this when everybody knows that a byte represents the values of numbers from 0 to 255?

The answer is that simple decoders can be set up in hardware, such as seven-segment displays, when 4 bits are allocated to one decimal position. Good old mainframes also store numbers in BCD format.

Theoretically, this gives the mainframe an unlimited capacity to perform exact computations. Table 2 shows how this kind of coding looks. It is fairly easy to figure out how to read BCD-encoded numbers, because only 4 bits need to be decoded each time.

Table 2

Decimal vs BCD

Decimal Number

BCD Equivalent

0

0000

1

0001

2

0010

3

0011

4

0100

5

0101

6

0110

7

0111

8

1000

9

1001

42

0100 0010

8196

1000 0001 1001 0110

65535

0110 0101 0101 0011 0101

Table 3

PCF8583 – Address 0x00

Bit

Function

0

50% duty cycle, seconds flag; 0 when alarm is set.

1

50% duty cycle, minutes flag; 0 when alarm is set.

2

0: Alarm disabled, 0x08h to 0x0Fh function as RAM; 1: Alarm set, 0x08h up to 0x0Fh function as alarm control registers.

3

0: 0x05h and 0x06h without mask; 1: 0x05h and 0x06h contain date and month, directly readable.

Bits 4 and 5

00: Clock with 32,768KHz; 01: Clock with 50Hz; 10: Counter mode; 11: Test mode.

6

0: "Count" mode; 1: Save most recent counter value in latches.

7

0: "Count impulses" mode; 1: Finish counting, erase divider.

Table 4

PCF8583 – Address 0x08 (Clock)

Bit

Function

0 to 2

000: No counter; 001: Hundredths of a second; 010: Seconds; 011: Minutes; 100: Hours; 101: Days; 110: Unused; 111: Test mode.

3

0: Counter does not trigger interrupts; 1: Counter triggers interrupts.

4 and 5

00: No alarm; 01: Daily alarm; 10: Weekly alarm; 11: Date alarm.

6

0: No counter; 1: Counter active.

7

0: No alarm; 1: Alarm active.

Writing 0x04 into the 0x00 register continuously updates the temporary storage and activates the alarm register. Writing 0xB0 into 0x08 activates the alarm for a specific time. When the alarm goes off, the chip sets the second bit in the status control register, which in turn sets the interrupt output, thus causing the LED to light up.

As a result, the method getTime() initially retrieves via getAlarmStatus() the value of this bit and restores the value at the end of the read operation. Otherwise, each read operation would reset the alarm. Additionally, the PCF8583 chip can optionally set the alarm bit at the occurrence of an event.

This alarm would always have to be reset with the setTime() and setAlarm() methods. Table 4 shows several capabilities for programming the alarm timer (Figure 7).

Figure 7: Example program for programming the clock and the alarm.

If the clock doesn't run correctly, it is probably because of the ratio between the crystal quartz and the capacitance on the OSCI and OSCO pins on the chip. The resonant frequency of the crystal should be exactly 32,768kHz. Adjusting the capacitance allows you to set the resonance at a precise value. The accuracy of the resonance is perhaps too advanced for the user who wants to understand the basics of the PCF8583. However, building an exact real-time clock means that the user will need to be able to tune the resonant frequency with the assistance of an oscilloscope or other tool for measuring frequencies.

Counter

The PCF8583 is also suitable for use as an event counter. To utilize this function, you need to set status bits 4 and 5 in the 0x00h control status register to 10. This transforms the OSCI pin into a counter input, whereas the OSCO pin no longer has any function. If you want to set the INT output for a particular counter reading, you should set the "enable alarm" bit in the 0x00 register.

When the chip is in counter mode, it will use registers 0x01 to 0x03 for storing the current values. The values are stored in this operating mode in hexadecimal format and not in BCD encoding. Registers 0x09 to 0x0B act as storage for a counter alarm. In all other respects, the I2C8583 counter program looks very similar to the example of the clock program (Listing 3).

Listing 3

I2C8583 Counter Program

01 public static void getCounter() {
02   try {
03     I2CBus bus = I2CFactory.getInstance(I2CBus.BUS_1);
04     I2CDevice pcf8583Address = bus.getDevice(address);
05     byte status = 0x24;
06     if (isAlarmSet()){ status = 0x26;}
07     pcf8583Address.write((byte)0x00,(byte)0xe0);
08     int h01   = ((Integer)pcf8583Address.read(0x01)).byteValue();
09     int h02   = ((Integer)pcf8583Address.read(0x02)).byteValue();
10     int h03   = ((Integer)pcf8583Address.read(0x03)).byteValue();
11     pcf8583Address.write((byte)0x00,(byte)status);
12     System.out.printf("%05d:%05d:%05d",h03,h02,h01);
13     System.out.println();
14     System.out.println("Alarm:"+isAlarmSet());
15   }
16   catch(Exception e) { System.out.println("Get Counter"+e); }
17 }
18
19 public static void getAlarm() {
20   try{
21     I2CBus bus = I2CFactory.getInstance(I2CBus.BUS_1);
22     I2CDevice pcf8583Address = bus.getDevice(address);
23     byte status = 0x24;
24     if (isAlarmSet()){ status = 0x26;}
25     pcf8583Address.write((byte)0x00,(byte)0xe0);
26     int h01   = ((Integer)pcf8583Address.read(0x09)).byteValue();
27     int h02   = ((Integer)pcf8583Address.read(0x0A)).byteValue();
28     int h03   = ((Integer)pcf8583Address.read(0x0B)).byteValue();
29     pcf8583Address.write((byte)0x00,(byte)status);
30     System.out.printf("%05d:%05d:%05d",h03,h02,h01);
31     System.out.println();
32     System.out.println("Alarm:"+isAlarmSet());
33   }
34   catch(Exception e) { System.out.println("Get Counter"+e); }
35 }
36
37 public static void setCounter(byte h03,byte h02,byte h01) {
38   try {
39     I2CBus bus = I2CFactory.getInstance(I2CBus.BUS_1);
40     I2CDevice pcf8583Address = bus.getDevice(address);
41     pcf8583Address.write((byte)0x00,(byte)0xE0);
42     pcf8583Address.write((byte)0x01,(byte)h01);
43     pcf8583Address.write((byte)0x02,(byte)h02);
44     pcf8583Address.write((byte)0x03,(byte)h03);
45     pcf8583Address.write((byte)0x00,(byte)0x24);
46   }
47   catch(Exception e) { System.out.println("Set Counter"+e); }
48 }
49
50 public static void setCounterAlarm(byte h03,byte h02,byte h01) {
51   try {
52     I2CBus bus = I2CFactory.getInstance(I2CBus.BUS_1);
53     I2CDevice pcf8583Address = bus.getDevice(address);
54     pcf8583Address.write((byte)0x00,(byte)0xE0);
55     pcf8583Address.write((byte)0x08,(byte)0x90);
56     pcf8583Address.write((byte)0x09,(byte)h01);
57     pcf8583Address.write((byte)0x0A,(byte)h02);
58     pcf8583Address.write((byte)0x0B,(byte)h03);
59     pcf8583Address.write((byte)0x00,(byte)0x24);
60   }
61   catch(Exception e) { System.out.println("Set Counter Alarm"+e); }
62 }
63
64 public static boolean isAlarmSet() {
65   boolean alarm = false;
66   try{
67     I2CBus bus = I2CFactory.getInstance(I2CBus.BUS_1);
68     I2CDevice pcf8583Address = bus.getDevice(address);
69     if (((byte)0x02&((Integer)pcf8583Address.read(0x00)).byteValue())==(byte)2) {
70       alarm = true;
71     }
72   }
73   catch(Exception e) { System.out.println("Set Alarm"+e);}
74   return alarm;
75 }

The counter input reacts to the positive edge, so it counts transitions from 0 to 1. During the test, I didn't encounter any problems when the switches bounced. Bouncing in this situation means that the switch provides contact several times even though it is only supposed to be triggered once. If this happens, you can put a simple non-bouncing switch together with the help of a flip-flop (Figure 8 and online [3]). When the chip operates as a counter, the contents of the 0x08 alarm register are interpreted differently than when it operates as a clock (Table 5).

Figure 8: Schematic for a non-bouncing switch.

Table 5

PCF8583 as a Counter

Bit

Function

0 to 2

000: No counter; 001: Units; 010: Hundreds; 011:Ten thousands; 100: Millions; 101: Not permitted; 110: Not permitted; 111: Test mode.

3

0: Counter does not trigger interrupt; 1: Counter triggers interrupt.

4, 5

00: No alarm; 01: Alarm; 10: Not permitted; 11: Not permitted.

6

0: No counter, alarm inactive; 1: Counter, alarm active.

7

0: No alarm; 1: Alarm active.

Figure 9 shows the counter test program in action. It sets the counter alarm to 5. The counter itself is initialized to 1. Once the switch has been pressed four times, the LED lights up and the alarm status bit changes the value to true.

Figure 9: The output of the test program running in "counter" mode.

Buy this article as PDF

Express-Checkout as PDF

Pages: 8

Price $2.95
(incl. VAT)

Buy Raspberry Pi Geek

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content