Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

Keeping time with the PCF8583 Real Time Clock and AVR

5.00/5 (6 votes)
13 Feb 2012CPOL11 min read 76.5K   795  
A Real Time Clock development board.

Real Time Clock Development Extension Board

This is my second in a series of Development Extension Board (DEB) articles, if you missed the first one AVR LED/Level shifter Board be sure to check it out. These DEB boards came about as a way to mount frequenty used components on a board external to the MPU, be easy to use in the development cycle and have the ability to be plugged into a bread board where it may be wired easily. In cases such as with the RTC we will be using the PCF8583 Clock/Calendar with 256 X 8-bit RAM device where communication with the device is accomplished using the I2C Bus protocol. I haven't had any experience with this device or for that matter any device using the I2C Bus but wanted to learn so here we are. Although I used an AVR processor this board can be used with any MPU that supports GPIO and interrupts if notification required.

Credits: This library uses Peter Fluery's I2C Library for the I2c Communications. This other libraries by Peter Fleury are an excellent resource and are open source under the GNU GPL license.

I held off learning the I2C bus as I thought it would be difficult to learn and it was a little trickey but using Peter Fleury's I2C library made it fairly easy and provided the basic building blocks I needed to communicate with the device. I had the communications up and running in short time with the aid of a Logic Analyzer I just recently purchased from Saleae Logic. This wonderful device saved me hours of time debugging and looking at the data actually being sent. Not having some kind of tool such as this has kept me from attempting to learn I2C in the past. The figure that follows is a snapshot from the Logic Analyzer that was configured to interpret I2C data.

RTCDevelExtBrd_7.jpg

The Analyzer is monitoring the Clock and Data lines and the packet that's being sent is shown with the green circle marking the start byte and the red square is the stop byte. We'll go into more detail about the packet structure in the sections that follow.

There is a lot of information that will be presented in this article so the material is divided into two major sections being the Hardware and Software sections and where the Software section is further divided into sections that describe the the chips functionality, how to configure, set and read the registers and the high level software to control it's usage.

Hardware

The finished board mounted on the bread board is shown in the image below and along with the DEB board AVR LED/Level shifter Board that we created in the last article. I have set a timer alarm by setting the current time to 00:00:00.00 and the Alarm Time for 00:00:2.50 and when the alarm fires the interrupt it activates one of the lights on the LED board. I realize that this is very simplistic but we will be developing DEB boards in the future that will accept input and display results, but we're not at that point yet so we just want to make sure that our board is funtioning properly and that we understand how to control it.

RTCDevelExtBrd_8.jpg

As can be seen from the schematic below there really isn't much to this board much of the functionality is built into the chip and we are just controlling it. I have included the data sheet for the device with the source for this article and it explains most of the inner workings fairly well but gets real vague when describing the different timer modes and alarms. At least it was difficult for me to understand so I will attempt to fill in some of the gaps in this article and provide the tools needed to talk to the device at a high level.

RTCDevelExtBrd_0b.jpg

The board requires 5V to power it and 2/3 GPIO ports to interface to it, 2 being if you are not interested in receiving notifications by way of alarms. The two 4.7K pull up resistors are required by the I2C communications and assumes that no other pull up resistors are introduced onto the bus.

In addition the alarm interrupt pin on the chip is routed to a pin on the board so that alarm notification can be configured to do something. In this simple example I am setting the current and alarm times in code and using a Pin Change interrupt to intercept the alarm then turning on a light on the LED DEB board when the alarm is triggered. In the future I may expand on this theme when I get both input and output boards completed. I've been working on a EADOGm126 display board and have made good progress and intend on finishing in the near future but I received the 32.768 Khz crystals in the mail the other day and wanted to get the RTC working. I've had the RTC chip for quite a while but for some reason never ordered the crystals until recently. So in the process of unwinding my stack I will finish the display board.

The main design criteria when developing this board was that the board should be provided with both power and control pins located on the board that when plugged into the bread board provide access to the boards communication bus and interrupt port. This minimalistic approach mimics the plug and play architecture with obvious differences and limitations but in my opinion it will make the prototyping and design/proof-of-concept phases much easier and a lot faster as most of the wiring has already been done. This plug and play architecture is extended to the solftware also so that if a particular requirement is needed for a device the appropriate board may be added, wired up and the software module included in your source.

PCF8583 RTC Chip Description

The device has 256 X 8-bits of RAM with an auto-increment adress register that allows data to be sequentially transferred in either direction. The first 8 bytes or registers of the adress space contain information pertaining to the clock. The second 8 bytes from 0x08 to 0x0f are only valid when alarms are enabled and available as free space if alarms are not used. I've included the datasheet for the chip and it shows a further break down of the hours, year/date and weekday/month registers. These fields may also be accessed with the mask flag bit in the Control/Status register which allows the registers to be read with all but the actual value masked out.

RTCDevelExtBrd_1.jpg

Memory address 0x00 is the Control/Status register that is used to define the overall behavior of the chip. The Control/Status register and a brief description for each bit in the register is shown in the figure.

Control/Status Register
RTCDevelExtBrd_5.jpg

The timer and alarm flags are set according to the rules for the mode you are currently running and they must be cleared in order for subsequent alarms to be recognized.

When the Alarm enable bit is set in the Control/Status register the register from 0x08 to 0x0f become active and the Alarm functionality is determined by the Alarm Control byte at register location 0x08.

The function mode defines 4 modes of operation but in this article we are only concerned with the clock mode 32.768 kHz mode since we have included a 32.768 crystal on the board for this purpose.

If the alarm enable bit of the Control/Status register is set then the alarm control register becomes active and is used to define the behavior of alarms.

Alarm Register
RTCDevelExtBrd_6.jpg

The timer modes we will be looking at here are the Clock, Timer and Alarm timer modes where the two timer modes are very simlar, the Timer starts at zero or a predefined value and counts to 99 the resets to zero and the Alarm Timer mode starts at zero and counts until a predefined values is reached at which point an interrupt is trigger if configured to do so. The following sections describe how the timers are configured, what registers are involved and pseudocode to demonstrating usage.

Clock Mode

Using the Clock mode is basically what you would think of with an Alarm Clock in that you set the current time, set the alarm time and enable the alarm and when the alarm goes off disable the alarm. This mode uses the alarm registers 0x09-0x0e as the target alarm time and if the alarm interrupt enable bit is set in the Alarm Register then an interrupt will be fired when the current time equals the alarm time with the frequency specified using the clock alarm function bits 4-5 in the Alarm Control register.

This mode mode requires that;
  Control/Status Register;
     bit 2 set to enable alarms.
  Alarm Register;
     bits 4-5 set to one of the clock alarm modes.
     bit  7   alarm interrupt enable.

Example pseudocode: Setting the chip for a dated alarm
  Stop counter - Control/Status register bit 7 = 0
  Set Clock Time - Clock time/date/ registers 0x01 - 0x06
  Set Alarm time - Alarm time/date registers 0x09 - 0x0e
  Enable Alarm - Control/Status register bit 2 = 1
  Start counter Control/Status register bit 7 = 0

Timer Mode

This mode reminds me of stopwatch in that register 0x07 is used as a counter and starts at either 0x00 or a predefined value to 99 at which point the counter goes back to zero and if configured triggers an interrupt.

This mode mode requires that;
  Control/Status Register;
     bit  2   enable alarms.
  Alarm Register;
     bits 0-3 count interval the timer uses.
     bit  3   timer interrupt enable
     bit  7   alarm interrupt enable
  Register 0x07 - Start value for timer as needed.

Example pseudocode: Setting timer to run
  Stop counter Control/Status register bit 7 = 1
  Set Alarm Time - register 0x07    [optional]
  Set count interval - Alarm register bit3 0-2
  Enable Alarm - Alarm register bit 3
  Start counter Control/Status register bit 7 = 0

Timer Alarm Mode

Think of this mode as resembling an egg timer where a time interval is set, the timer enable and when the counter and the interval time match an interrupt is triggered if configured for interrupts.

This mode mode requires that;
  Control/Status Register;
     bit  2   be set to enable alarms.
  Alarm Register;
     bits 0-3 count interval the timer uses.
     bit  6   alarm timer interrupt enable
     bit  7   alarm interrupt enable
  Register 0x0f - Compare timer value.

Example pseudocode: Setting timer to run
  Stop counter Control/Status register bit 7 = 1
  Set Alarm Timer register (0x0f)
  Set count interval - Alarm register bit3 0-2
  Enable Alarm Timer Alarm register bit 6
  Start counter Control/Status register bit 7 = 0

The timer mode you use depends on your needs but the library provided in the source should handle most of your needs and if not can be easily adapted to suit your needs.

Software

To better understand this section we are going to break it down into more managable chunks as there's quite a bit going on. In the following sections we will be covering the topics listed here. The software was written for the ATMega328p processor but can be adapted by modifying the appropriate defines in the Clock.h module.

  • Using the I2C Bus - as it pertains to communicating with the device.
  • Ineracting with the chip
  • High Level Control - Clock library
Using the I2C Bus

As the section name implies we are going to discover how to use the I2C bus without going into a lot of the mechanics. For a good read on the subject here is a good I2C Tutorial.

In a nutshell I2C communications uses two wires; one for clock and one for data. Fortunatly the ATMega328p chip handles most of the I2C bus protocol in hardware and the Peter Fluery library handles all low level interaction with the chip. A very simplistic description of the packet scheme used in the communications protocol would be that for each transmission a start byte, which includes the slave address followed by any data required, restart and finally a stop byte to signify the end of packet. This protocol and the I2C library assume that this is a network with only one master node. Although the chip says it can handle multiple masters, implementation turns out to be quite a chore because you have to start considering collison detection and how to deal with them. I akin it to Asynchronus processing where you have to be thread aware. For most of my work a single master network will work just fine.

The Figures 17-19 show the various packet formats for exchanging data with the device. In this stage we are building another layer in the communcations stack whose methods call the I2C Library primitive methods. This level of abstraction is used to Send/Receive information to/from the device in the form of packets without regard to content.

Interacting with the chip

There are three packet formats that the chip recognizes and are described in the following sections.

Master transmits to slave receiver (WRITE) mode.

Figure 17 shows a packet where data is sent from the master to the slave and is most communly used to set a register value or a sequential set of register values. This is a one-way transfer of dat from the master to the slave.

Pseudocode used to Set the timer registers

Send Start character (includes slave address)
Send Word Address (Address to set or start at)
Send Data(s)
Send Stop character
RTCDevelExtBrd_2.jpg

Example of Setting the time in code

//send start byte with device address and write mode
status = i2c_start(DeviceAddress+I2C_WRITE);
//If device is responding
if ( !status )
{
    //Write the register address to start writting.
    //For clock time wa = 0x01, for alarm time wa = 0x09
    i2c_write(wa);

    //Write the values sequentially to the appropriate registers.
    i2c_write(time->hundreds);
    i2c_write(time->seconds);
    i2c_write(time->minutes);
    i2c_write(time->hours);
}
//Send the stop byte
i2c_stop();
Master reads after setting work address (with word address, READ data).

Figure 18 shows a master sending a request to read a predetermined number of bytes of data from the slave given the starting register.

Pseudocode used to Read the Time registers

Send Start character (includes slave address)
Send Word Address (Register address to start at)
Send a Restart character
Read Data with ACK
Read Data with NAK (last item)
Send Stop character
RTCDevelExtBrd_3.jpg

Example of Reading the time in code

//send start byte with device address and write mode
status = i2c_start(DeviceAddress+I2C_WRITE);
if ( !status )
{
    i2c_write(wa);
    status = i2c_rep_start(DeviceAddress+I2C_READ);
    if (!status)
    {
        _current_time.hundreds = i2c_readAck();
        _current_time.seconds = i2c_readAck();
        _current_time.minutes = i2c_readAck();
        _current_time.hours = i2c_readNak();
    }
}
i2c_stop();
Master reads slave immediately after first byte(READ mode).

Figure 19 shows a master sending a request for slave to receive data starting at address 0x00 and read until ReadNAK is issued. Takes advatage of the auto-increment feature of the chip to read sequential registers.

RTCDevelExtBrd_4.jpg
Example of how the library may be used

In this example we are defineing a clock alarm that triggers an interrupt 2.5 seconds after enabling the alarm.

    /*
 * RealTimeClock.c
 *
 * Created: 2/10/2012 11:17:26 PM
 *  Author: Mike
 */
#ifndef FCPU
#define F_CPU 8000000L
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#include "i2cmaster.h"
#include "clock.h"

void pci_init();
void io_init();

void SetClockModeAlarm();
void SetTimeModeAlarm();
void SetAlarmTimeNodeAlarm();

ISR(PCINT1_vect)
{
    PORTB = (1<<PB5);   
}

int main(void)
{
    unsigned char dummy;
    bool line = false;
       
    i2c_init();   // init I2C interface
   
    io_init();
   
    StopCounter();
   
    //Pin change interrupt init
    pci_init();
   
    //uncomment the one of the following
    SetClockModeAlarm();
               
    StartCounter(0x04);
   
    sei();
   
    while(1)
    {
          ReadTime(false);
       
        _delay_ms(100);
    } 
}

void io_init()
{
    DDRB = (1<<PB5);
    DDRC |= (1<<PC4) | (1<<PC5);
}

void pci_init()
{
    PCIFR = (1<<PCIF1);
    PCMSK1 = (1<<PCINT8);
    PCICR = (1<<PCIE1);
}

void SetClockModeAlarm()
{
    Time now;
    Date date;
   
    WriteRegister(ALARM_REG, 0xb0);
   
    date.year = 0x12;
    date.month = 0x02;
    SetDate(&date, false);
    SetDate(&date, true);
   
    now.hundreds = 0x00;
    now.seconds = 0x00;
    now.minutes = 0x13;
    now.hours = 0x08;
    now.r4.h_1224 = 1;
    SetTime(&now, false);
   
    now.hundreds = 0x32;
    now.seconds = 0x02;
    SetTime(&now, true);
}

Summary

Creating the PCF8583 device library and this article taught me a lot about the inner workings of the device. The work went a lot quicker with the help of my new Logic Analyzer which quite frankly if I hadn't had it I probably would have kept putting off learning the I2C Bus protocol as I had it in my head that it would be a pain to learn.

I'll be creating more DEB boards in the near future and intend to bring this board back out when I get an input and display board finished. Maybe we'll try a programmable bench timer?

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)