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.
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.
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.
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.
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
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
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
Example of Setting the time in code
status = i2c_start(DeviceAddress+I2C_WRITE);
if ( !status )
{
i2c_write(wa);
i2c_write(time->hundreds);
i2c_write(time->seconds);
i2c_write(time->minutes);
i2c_write(time->hours);
}
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
Example of Reading the time in code
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.
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.
#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();
io_init();
StopCounter();
pci_init();
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?