rocketnumbernine

Andrew's Project Blog: Hardware, Software, Stuff I find Interesting

This is a follow on to "Using SPI on an AVR (1)" and illustrates using SPI to communicate with a real time clock chip, it also illustrates use of external interrupts to initiate code on the microcontroller.

Maxim DS1305 Real Time Clock

The DS1305 Serial Alarm Real-Time Clock is an 18 pin DIP clock chip. When setup with a 32.768kHz crystal and optional backup battery, it provides a real time clock, 2 programmable alarms that drive I/O pins (which can be used to trigger interrupts on the MCU), 96 bytes of RAM and a rechargeable battery charger accessible from SPI or 3-wire interconnect.

The datasheet contains a simple circuit, without a battery attached VCC2 and VBAT should be connected to ground and VCC1 and VCCIF to 5V, for SPI operation SERMODE pin should be connected to 5V, then SDO is MISO, SDI is MOSI, SCK clock, and CE chip enable.

SPI Configuration

From the datasheet we read that the device samples the state of the clock when the chip enable pin goes high to decide whether the SPI master is using a high or low idle clock level allowing either CPOL of 0 or 1 to be used. Data is sampled on the trailing edge of the clock (CPHA=1) so SPI mode 1 or 3 can be used. Data is read and written MSB first. Unlike some SPI devices the chip select pin is driven high to select the chip.

All DS1305 functionality is accessed through 127 byte-wide data registers. All registers have one address for reading (0x00-0x7F) and one for writing (0x80-0xFF) as shown in Figure 2 of the datasheet and copied below: DS1305 data registers

Using the DS1305

The register structure of the DS1305 makes the communication protocol to the device very simple. The MCU transfers a byte containing the address of the data to be read or written. If writing to a register it then writes the byte to MOSI and ignores the data on MISO, if reading it writes a dummy byte to MOSI and reads the register value from MISO. The DS1305 SPI Slave uses the address transferred in the first byte to determine whether to 'read' or 'write' the next byte.

The device also supports burst mode - if the chip select is kept high after a read or write, further data can be sent or received with an implicit address increment. So the current date/time can be sent in a stream of bytes to set the current time.:

1
2
3
4
5
6
7
8
9
10
11
12
// set current date/time to 23/04/09 20:32:47 
setup_spi(SPI_MODE_1, SPI_MSB, SPI_NO_INTERRUPT, SPI_MSTR_CLK16);
select_ds1305();
send_spi(0x80); // write address of first Date/Time register (for seconds)
send_spi(0x47); // next byte holds seconds = 47
send_spi(0x32); // next byte holds minutes = 32
send_spi(0x20); // next byte holds hours in 24hour mode = 20
send_spi(0x05); // next byte holds day of week = 5 (thursday)
send_spi(0x23); // next byte holds date = 23
send_spi(0x04); // next byte holds month = 04
send_spi(0x09); // next byte holds 2 digit year = 09
deselect_ds1305();

Note, that the DS1305 uses Binary Coded Decimal with each decimal digit being represented in 4 binary bits, so 47 in decimal is represented as 0x47 in hex. When only a single digit is required (for example to represent the most significant digit in the 12 hour clock time (0 or 1) only 1 bit is used.

A simple library of helper functions, macros, and structures simplifies usage further, for example:

1
2
3
4
5
6
// enable the oscillator in the control register to
// make the device "tick" every second
set_control(DS1305_EOSC);
// set current date/time to 23/04/09 20:32:47
DS1305_DATETIME current = {0x47, 0x32, 0x20, 0x05, 0x23, 0x04, 0x09};
set_time(&current);

After enabling the oscillator and setting the current time, get_time() can be used to fetch the value of the current time, which will increment every second.

The DS1305 also has two programmable alarms which can be configured to trigger either at a specific time of the week, or once every second, minute, or hour. The status register can be polled to see when the alarm has triggered or, more usefully, one of the interrupt lines (INT0 and INT1) can be made to go low when the alarm trigters (by setting the INTCN and AIE0 and AIE1 flags in the control register). The 7th bit of the alarm time registers (second, minute, hour, and day) are used to choose the granularity of the alarm shown on table 1, page 7 of the datasheet and repeated below:

DS1305 timer mask values For example, to set alarm 0 to trigger at 10:33:03 every day and bring the INT0 pin low would require:

1
2
set_control(DS1305_EOSC | DS1305_INTCN | DS1305_AIE0);
setAlarm(0x03,0x33,0x10,0x00 | DS1305_ALARM_SET);

Invoking an Interrupt on Microcontroller

If the INT0 pin of the DS1305 is connected to a pin on the microcontroller we can initiate an interrupt to execute some code when the alarm triggers.

On the Microcontroller we enable external interrupts on the INT0 pin (which differs across different AVR Microcontrollers) so that when the line goes from high to low.

1
2
3
4
5
// raise interrupt when INT0 pin falls (pin PD0 at AT90usbXXX, pin PD4 on ATmegaXXX)
EICRA = (1<<ISC01) | (0<<ISC00);
// enable interrupts
EIMSK = (1<<INT0);
sei();
More details of the options available to set up interrupts (for example whether the interrupt should be triggered on rising or falling edges) is included the "External Interrupts" Chapter in all AVR datasheets.

After configuration, when an interrupt occurs the following Interrupt Service Routine will be called, the code fetches the alarm time to clear the interrupt flag on the DS1305

1
2
3
4
5
6
7
8
// called when pin INT0 changes
ISR(INT0_vect)
{
  // get alarm value to clear alarm interrupt flag on DS1305
  DS1305_DATETIME alarm;
  get_alarm0(&alarm);
  flash_led();
}

The example code is included in the SPI helper library below.

Updated 2010/02/24:

SPI library code moved to Google Code

References/Resources

  1. AVR SPI helper library
  2. Serial Peripheral Interface
  3. AVR AT90USB82/162 Datasheet
  4. AVR ATmega48/88/168/328 Datasheet
  5. AVR151: Setup And Use of The SPI
  6. DS1305 datasheet

It looks like your browser doesn't support javascript so comments won't work

Tags/Categories: arduino, AVR, SPI, at90usb162, atmega328, DS1305, RTC, interrupts, howto