Digital alarm clock using DS3231 RTC
In this project i will present you a digital clock i made, using an extremely accurate Real Time Clock chip (RTC) which is the DS3231 from
maxim intergrated.
The DS3231 is a low cost real time clock, with an intergrated temperature compensated crystal
oscillator (TCXO) and a crystal of 32KHz.
This chip uses a built-in temperature sensor to periodically measure the crystal's temperature (which is approx~ every 64secs), and by
switching different
internal capacitors in and out of the crystal circuit, it can precisely adjust its frequency to remain constant.
Maxim claims an accuracy within 2ppm from 0°C to +40°C, and 3.5ppm from -40°C to +85°C, which this is translated as a clock drift of +-2 mins per year.
This chip also incorporates a battery input, so it can maintain accurate timekeeping when mains power to the device is absent. This is done through
a comparator circuit, where monitors the status of VCC to detect power failures, so to automatically switch to the backup supply when it is necessary.
The RTC counts seconds, minutes, hours, day, date, month, and year information.
The date at the end of the month is automatically adjusted for months with fewer than 31 days, and includes corrections for leap years.
It can be operated in either 24-hour or 12-hour format with an AM/PM indicator. ( In which the 24Hour format is more preferable to me...)
It has also two programmable time-of-day alarms, and one programmable square-wave output.
In addition to all of the characteristics we talked before it has also a major advantage that communication with the chip is accomplished through the I2C bus,
which minimizes the need of multiple I/O ports. Awesome!!!
Showing the parts i removed (Read below) |
(Click to enlarge.) |
This board as you can clearly see from (Fig1) has a pinout header with 6 pins. Lets look together what each pin does in detail
32kHz Output. This open-drain pin requires an external pullup resistor. When enabled, the output operates on
either power supply. It may be left open if not used. |
||
Active-Low Interrupt or Square-Wave Output. This open-drain pin requires an external pullup resistor connected to a supply at 5.5V or less. This multifunction pin is determined by the state of the INTCN bit in the Control Register (0Eh). When INTCN is set to logic 0, this pin outputs a square wave and its frequency is determined by RS2 and RS1 bits. When INTCN is set to logic 1, then a match between the timekeeping registers and either of the alarm registers activates the INT/SQWpin (if the alarm is enabled). Because the INTCN bit is set to logic 1 when power is first applied, the pin defaults to an interrupt output with alarms disabled. The pullup voltage can be up to 5.5V, regardless of the voltage on VCC. If not used, this pin can be left unconnected. |
||
Serial Clock Input. This pin is the clock input for the I2C serial interface and is used to synchronize data
movement on the serial interface. Up to 5.5V can be used for this pin, regardless of the voltage on VCC. |
||
Serial Data Input/Output. This pin is the data input/output for the I2C serial interface. This open-drain pin
requires an external pullup resistor. The pullup voltage can be up to 5.5V, regardless of the voltage on VCC. |
||
Power pin for primary supply of the RTC module. This pin should be decoupled using a 0.1µF to 1.0µF capacitor. If it is not used, then you should connect it to ground. Datasheet specifies that it has a range from 2.3v to 5.5vdc, with best stability oscillation results on 3.3vdc |
||
This pin is the power supply ground. |
  Resistors | |||
R1 | Smd (0805) Resistor 200 Ohm | ||
R2 | Smd (0805) Resistor 1K | ||
R1_A | Smd (0805) Network Resistor 4.7K | ||
R2_A | Smd (0805) Network Resistor 4.7K | ||
  Capacitors | |||
C1 | Ceramic Capacitor 1uf | ||
C2 | Ceramic Capacitor 0.1uf | ||
  IC's | |||
IC2 | DS3231  I2C Real Time Clock Chip (TCXO) | ||
IC1 | AT24C32  I2C 32K Serial Eeprom (32,768 bits) | ||
  MISC | |||
LED1 | Red Led Smd (0805) | ||
D1 | 1N4148 Smd Diode | ||
BATT | CR2032 + Horizontal Battery Holder | ||
|
The TCXO circuitry consists of the parts that are circled on the left block diagram.
These are the temperature sensor the oscillator with the capacitor array and the control logic unit.
The control logic unit reads the temperature sensor's output and by using a predefined lookup table, (which
determines the capacitance that it is required on each temperarute), then adds the aging correction (which this value is taken from the age register
0z10 position on the address register map) and the final result of these two parameters sets accordinally the capacitance selection register, where
capacitors are added or substracted from the array,
in order to keep stable oscillation in ambient temperature variations.
New values (including changes to the age register) are loaded, when temperarure change is detected by the sensor or when a user-initiated temperature
conversion is completed. Temperature conversion occurs on initial application of VCC and once every 64 seconds afterwards.
As we talked earlier this module communicates through the I2C bus. We should also mention that this module acts as a slave device only!!!.
Once again i realized why every bench, where microcontrollers are present, should have a logic analyzer...
Besides some problems i had, which we will discuss later, it is more easier to hook up your analyzer and watch if you follow-up the proper
sequences for read-write operations according to the datasheet.
Below are both modes (read-write operations) though the I2C bus.
Let's analyze both of them one by one, but first let's have a look at the registers address map of the chip.
In order to write data on the DS3231, master initiates a start condition on the I2C bus.
After that master sends the slave's address which is the 0xD0 or '11000000', where the last bit of the address, indicates whether a read or write operation
will be performed. (In our case 0 since we want to write on the DS3231)
Then you wait for an acknowledge from slave, and when it is received, master sends a pointer byte (word address) which 'points' the beginning
address that you want to start writing to. When an acknowledge is received once more, master starts transmitting data to the slave, waiting for an acknowledge
on every byte that was received successfully by the slave. You can either transmit a single byte or a byte array.
Keep also in mind that the slave's pointer increases automatically by one on every successfull writing, and wraps around to the beginning when it reaches
at the end of the address register map. (Register position 12h)
And finally master initiates a stop condition, as a result to terminate the writing procedure and release the bus.
In summary :
1) Initiate a start condition
2) Send the slave address followed by the operation 0xD0 (Write operation)
3) Wait for slave to acknowledge
4) Send the pointer (word address) where to start writing to
5) Wait for slave to acknowledge
6) Send (one or multiple bytes) to slave waiting for each byte transmition, the slave to acknowledge.
7) Initiate a stop condition
*** One clarification about single and multiple byte transmittion ***.
If you want to write for example two values, the hour and the year, these registers are not in a row. (Look the address table above.)
So you will have to do the transmition in two parts.
This will be as it follows :
1) Initiate a start condition.
2) Send the slave address followed by the operation 0xD0 (Write operation).
3) Wait for slave to acknowledge.
4) Send the pointer (word address) where to start writing to 0x02(Hour).
5) Wait for slave to acknowledge.
6) Send the hour data ex 0x21 nine o clock at night and wait the slave to acknowledge.
7) Initiate a stop condition.
1) Initiate a start condition.
2) Send the slave address followed by the operation 0xD0 (Write operation).
3) Wait for slave to acknowledge.
4) Send the pointer (word address) where to start writing to 0x06 (Year).
5) Wait for slave to acknowledge.
6) Send the hour data ex 0x19 (2019) and wait the slave to acknowledge.
7) Initiate a stop condition.
And as for writing a byte array will be as it follows :
1) Initiate a start condition.
2) Send the slave address followed by the operation 0xD0 (Write operation).
3) Wait for slave to acknowledge.
4) Send the pointer (word address) where to start writing to 0x00 (Secs).
5) Wait for slave to acknowledge.
6) Send the secs data ex 0x00 and wait the slave to acknowledge.
6a) Send the mins data ex 0x00 and wait the slave to acknowledge.
6b) Send the hours data ex 0x15 and wait the slave to acknowledge.
6c) Send the day data ex 0x01 and wait the slave to acknowledge.
6d) Send the date data ex 0x19 and wait the slave to acknowledge.
6e) ........ As many as you want until 0x12 position.
7) Initiate a stop condition.
|
In summary :
1) Initiate a start condition.
2) Send the slave address followed by the operation 0xD0 (Write operation).
3) Wait for slave to acknowledge.
5) Send the pointer (word address) from where to start reading.
5) Wait for slave to acknowledge.
6) Initiate a restart condition.
7) Send the slave address followed by the operation 0xD1 (Reading operation).
8) Wait for slave to acknowledge.
9) Then the ssp module generates clocks to receive data. Check if the sspbuf is full.
10) When sspbuf is full send an acknowledge to slave that data successully received.(Repeat from step9 for as many bytes as you want to read)
11) Send a NACK (not acknowledge) to "tell" slave that the reading will stop.
12) Initiate a stop condition.
*** A reading example of hour and minutes. ***.
If you want to read for example two values, the minutes and the hour
This will be as it follows :
1) Initiate a start condition.
2) Send the slave address followed by the operation 0xD0 (Write operation).
3) Wait for slave to acknowledge.
5) Send the pointer (word address) from where to start reading. (0x01 minutes)
5) Wait for slave to acknowledge.
6) Initiate a restart condition.
7) Send the slave address followed by the operation 0xD1 (Reading operation).
8) Wait for slave to acknowledge.
9) Then the ssp module generates clocks to receive data. Check if the sspbuf is full.
10) When sspbuf is full (Minutes received.) send an acknowledge to slave that data successully received.
11) Then the ssp module generates clocks to receive data. Check if the sspbuf is full.
12) When sspbuf is full (Hour received.) send an acknowledge to slave that data successully received.
13) Send a NACK (not acknowledge) to "tell" slave that the reading will stop.
14) Initiate a stop condition.
This module has 2 built-in alarms but i haven't done any test yet to upload any results. When i have, i will update this page with all of my experimentations i did.
Right now i am using one alarm which was created by me on my programm.
The concept is that on every second, the programm checks if the values that have been stored by the user on eeprom, match with the timekeeping registers of the DS3231.
And when there is a match it activates an output, which in our case is a led.
Of course more info can be found on my assembly listing programm.
|
Zooming on Saleae logic programm i noticed that when the pic initializes the I2C hardware module, the logic analyzer captured these transitions.
I still don't know why this is happening and of course i don't know, if this is the right thing to do by the module when it initializes.
If anyone has expierienced something similar please let me khow by sending me an e-mail at info@electronicsgate.com or by using the Contact form page.
Meanwhile i will keep searching for an explanation to post.
I found an interesting article from Maxim that explains the differences between several RTCs ( DS3231, DS3232, DS3234, DS32B35, DS32C35) to help us
in a proper selection of a real time clock chip, for our application. It also discusses the DS3231M, which has a built-in microelectronic mechanical systems (MEMS)
resonator instead of a crystal.