Shop OBEX P1 Docs P2 Docs Learn Events
Dallas/Maxim DS3234-based Clock with Propellor — Parallax Forums

Dallas/Maxim DS3234-based Clock with Propellor

tomcrawfordtomcrawford Posts: 1,126
edited 2015-03-06 21:02 in Robotics
Introduction
The Dallas/Maxim DS3234 is an updated replacement for the DS1302/1307 Timekeeping chip. The main additions are internal crystal providing +/- 2 ppm accuracy at room temperature, an on-chip thermometer, and on-chip TOD alarm registers. The DS3234 comes in a 20-pin SO package, requiring a surface mount PCB/assembly or a breakout board. Here is a pointer to the data sheet. http://www.maximintegrated.com/datasheet/index.mvp/id/4051

This note discusses a clock based on the DS3234, Parallax Propeller, and a Parallax 4 x 20 serial LCD display. The programming is 100% spin and includes examples of SPI Bus interfacing and a serial interface to the display. The code is attached.

Breadboard and Breakout Boards
The attached photo shows the solderless breadboard with the Prop chip and its standard accoutrements, power conditioning, connectors for the display and the DS3234, and a speaker. I used a 5-volt regulator as well as 3.3-volt regulator because I didn’t have a 5-volt wall-wart with banana plugs. The Prop pin assignments are clear from the program listing.

The DS3234 is available only in a SO package. This means some sort of adapter is needful to use it on a DIP-oriented breadboard. One can use a SOIC-to-DIP adapter, available from people like digikey, etc. There are also DS3234-specific breakout boards available from a number of sources; Google “ds3234 breakout”. The one I used came from cutedigi. It has a socket for the backup battery and a reset switch. All the DS3234 pins are brought to a SIP connector. For reasons I don’t understand, the SIP connector is mounted on the component side of the board. I connected it my breadboard with a cable but would have preferred mounting it directly to the breadboard component side up.

TimeKeeping
The DS3234 timing chain begins with a 32kHz (two to the fifteenth Hz) oscillator controlled by a crystal in the SO package. Having an on-chip crystal is extremely cool because you don’t have to worry about finding the right crystal with the right capacitors and the right layout. Even better, it allows Maxim to guarantee the accuracy because they have control over the crystal, capacitors, and layout. They specify +/- 2 ppm, which works out to about 1 second per week.

The 32 kHz is divided to 1 Hz and then in a chain of registers containing seconds, minutes, date-of-month, month, year, and day-of week. These registers are directly accessible for reading or writing. The monthly roll-over is corrected for months of less than 31 days, including corrections for leap years. It is possible to set dates that make no sense such as April 31. Whether ones includes logic to prevent this is, I suppose, a personal choice.

It ought to be noted that the values in the time and date registers are BCD rather than pure binary. I chose to convert immediately on reading and last thing before writing.

The attached spin program allows the time, day, and date to be set. A CASE statement runs once a second to increment or decrement whichever value is currently being modified. If we are just keeping time, the date and time are displayed once a second.
Daylight Saving Time is corrected for automatically unless the clock is programmed for Arizona (or Hawai’i) time.
There is a bit in a status register that “…may be used to judge the validity of the timekeeping data”. I check that bit on power-up to determine whether to program the chip to default values and whether to assume the status information in the on-chip RAM is valid. Status kept in the on-chip RAM includes Daylight Saving status and alarm status.

The DS3234 includes an on-chip thermometer. The temperature is in one-quarter degrees centigrade and can be read at any time. I round the integer part up if the fraction is one-half or three-quarters, round down otherwise. The accuracy is +/- 3 degrees, which renders the precision a little bogus.
Alarms
The DS3234 includes two alarms. One can program hours, minutes, and seconds or hours and minutes or hours, minutes, seconds, and date or hours, minutes, seconds and day. The chip can be programmed to drive a pin when an alarm matches. Since I use that pin for a 1 Hz square wave, I interrogate a status register once a second to see if an alarm is sounding.

The alarm “day” function is interesting. One would like to program an alarm for one or more days of the week, say Monday though Friday. But the register contains a value, not a bit mask. So I programmed the alarm hardware to ignore the day and date and implemented the function in software. The alarm functions ended up taking as much code as the time setting and keeping functions.
SPI Driver
The low level driver for the DS3234 is shown here. I implemented one function to write a register given the register address and value, and another that returns the contents of the specified register. The following code fragment shows the drivers.
Pub Write3234Reg(RegAd, WData)               'write to a register in the 3234
    outa[DS3234CLK]~~                        'make sure clock is high
    outa[DS3234CS]~                          'when CS goes active
    outa[DS3234CLK]~                         'clock goes low ready for first bit of address
    RegAd := RegAd| $80                      'set write flag
    repeat 8                                 'write flag plus 7 bits of address
       if (RegAd & $80) == 0
          outa[DS3234DI]~                      'set a zero
       else
          outa[DS3234DI]~~                       'or else a one
       outa[DS3234CLK]~~                       'clock high
       outa[DS3234CLK]~                        'then low
       RegAd := RegAd << 1                     'prepare next bit
    repeat 8                                   '8 bits of data
       if (WData & $80) == 0
          outa[DS3234DI]~                      'set a zero
       else
          outa[DS3234DI]~~                       'or else a one
       outa[DS3234CLK]~~                       'clock high
       outa[DS3234CLK]~                        'then low
       WData := WData << 1
    outa[DS3234CS]~~                           'leave chip select high               
    outa[DS3234CLK]~~                          'and clock high 

Pub Read3234Reg(RegAd)                       'read a register in the 3234
    outa[DS3234CLK]~~                        'make sure clock is high
    outa[DS3234CS]~                          'when CS goes active
    outa[DS3234CLK]~                         'clock goes low ready for first bit of address
    repeat 8                                 '(read flag) plus 7 bits of address
       if (RegAd & $80) == 0
          outa[DS3234DI]~                      'set a zero
       else
          outa[DS3234DI]~~                       'or else a one
       outa[DS3234CLK]~~                       'clock high
       outa[DS3234CLK]~                        'then low
       RegAd := RegAd << 1                     'prepare next bit
       outa[DS3234DI]~                         'leave DI Low
    repeat 8                                   '8 bits of data
       result := result << 1                   'scale what we have 
       outa[DS3234CLK]~~                       'clock data bit out of DS3234
       if ina[DS3234DO] <> 0                     'clock to data valid is < 200 nsec
         Result := Result | 1                  'set a one
       outa[DS3234CLK]~                        'low for next bit
    outa[DS3234CS]~~                           'leaving CS high
    outa[DS3234CLK]~~



Serial Driver
The serial driver for the display is shown here. The elegant little asynchronous serializer (which I cribbed from whoever wrote it first, Thank you very much) transmits one eight-bit character; the other function shown here sends a text string beginning at the current cursor position.
PUB TextString(TextPoint)       'put a string of text on display at current cursor
    CharPoint := TextPoint
    repeat
        Ascii := byte[CharPoint] 
        if (Ascii ==0) or (Ascii == $A) or (Ascii == $D) 
           return                   'end of string
        SerialChar(Ascii)          'onto display
        CharPoint ++                'on to next character

Pub SerialChar(TheByte)             'Serialize one character
    SerializerCnt := cnt            'initialize timing 
    outa[SerOut]~                    'start bit
    waitcnt(SerializerCnt+=SerBitTime)    'wait for start bit to complete
    repeat 8                        'going to do eight data bits
       outa[SerOut] := TheByte & %1    'set the bit to zero or one
       TheByte := TheByte >> 1         'align the next bit
       waitcnt(SerializerCnt+=SerBitTime)  'one bit time (Pretty much dead on)     '
    outa[SerOut]~~                     'stop bit
    waitcnt(SerializerCnt+=SerBitTime)   'one last bit time


Program Notes
The program is attached as a spin file. It is divided into four major sections: time keeping and time setting, alarm functions, DS3234 functions, and display functions. Each section has its own CON and VAR definitions.

The “main” starts up the DS3234 and the display. In starting the DS3234, I select a 1 HZ square wave used to run the code once a second. I check SWMode to traverse among functions, and then dispatch in the CASE Mode statement. Normally I just display the time, date, temperature, and alarm status, check to see if it’s time to adjust for DayLight Saving, check for pending alarms, and check for bells and whistles. If we are not in normal Mode, we execute the code corresponding to the variable being varied (such as hours, alarm control, etc).

The alarm logic is next; it has its own set of constants and variables. There are two alarms, so most of the functions take whichalarm as an argument. This results in a lot of LOOKUPs to choose register addresses. There are four vectors, each with one entry for each alarm. These are Alarm[3], AlarmMin[3], etc. The reason these are defined as three entries instead of two is because the alarms are named 1 and 2 rather than 0 and 1 and I didn’t want to be constantly writing WhichAlarm-1. AlarmDay(WhichAlarm) contains a mask of one bit for each day of the week; this provides a solution to the Monday-through-Friday issue.

Next is the code specific to the DS3234. The constants are register names, bit names within registers, and pin names. There are low-level access to registers, both read and write, access to on-chip RAM, code to access the DS3234 both when it is coming out of battery-backup and when it is being programmed initially. The BCD2Bin and Bin2BCD functions are here.

Last is the logic for the Parallax Serial Display. The constants are mostly special character for setting the cursor, backlight on/off, etc. Two special characters are defined, a degree sign and a plus/minus sign. The serializer and textstring functions are already described.

Comments

  • PublisonPublison Posts: 12,366
    edited 2014-03-02 12:51
    Very nice write-up Tom.

    I recently purchased a NXP PCF2127A that has almost the same specs. I really like the on board xtal and the large SRAM.

    I may have to get one of these to play with.

    Jim
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2014-05-24 08:59
    Quick note. Since I started this clock up ten weeks ago, it has kept perfect time. I set it two seconds slow (according to a WWV receiver) and it is still two seconds slow. Of course, this is a sample of one and at room temperature. Still, I'm pretty pleased.
  • RS_JimRS_Jim Posts: 1,751
    edited 2014-07-09 09:38
    Hi Tom,
    I down loaded your code, and then walked away from it when I saw it was spi. I have the ds3231 which I have been trying to code modifying Kye's ds1302 code from obex. The 3231 is I2C. I need to revisit your code and check out your bcd conversion routines. My biggest problem with the code has come with writing the 12/not24 mode bit and the pm/not am flags in reg 02H. Looking at the notes I have on my debugging in binary, I may have a problem with the string decoding and recoding. I set the 12 hr and pm flags and I get back 23hours! Currently the only access I have to the forum is my iPad and that doesn't allow me to view your archived code. When you read back hours in 12 hour mode, are you masking off the upper 3bits?
    Jim
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2014-07-09 15:17
    Short answer is: I didn't use and didn't test 12 hour mode. Longer answer is: Yes, I'm pretty sure you would have to mask off the three high bits to isolate the 10hr bit. Of course, then you need to look at the am/pm bit to disambiguate that.
    The BCD conversions are pretty simple with the following notes: BCD2Bin requires valid BCD inputs and BIn2BCD assumes positive integers in the range 00-99.
    Um, I can look at the code on my iPad; just tap the main.spin at the bottom of the original note. The formatting is pretty broken (most every line occupies two iPad lines), but you *can* see it. Actually, if I print it from my iPad (wifi to hp whatever), it looks pretty good.
    I would also point out that the readReg and writeReg methods are really well isolated. Changing them out for i2c ought to be a straight-foorward exercise.
    Please let me know if I can provide any further help
  • JLockeJLocke Posts: 354
    edited 2014-07-09 18:47
    I had a project in which I was using a DS1307 for timekeeping and in a later revision I eliminated the 5V regulator, so I switch out the DS1307 for a DS3231. I don't recall making any code changes to use the new chip, but I'm using DS1307_RTCEngine for the time-keeping. Have you tried that one?

    By the way, here's a breakout board that I made for the DS3231...
    DS3231.jpg
    436 x 401 - 110K
  • RS_JimRS_Jim Posts: 1,751
    edited 2014-07-10 10:53
    Tom,You are correct, I can open main.spin in the iPad. I will be able study the code while I am away from my computers.
    JLocke wrote: »
    I had a project in which I was using a DS1307 for timekeeping and in a later revision I eliminated the 5V regulator, so I switch out the DS1307 for a DS3231. I don't recall making any code changes to use the new chip, but I'm using DS1307_RTCEngine for the time-keeping. Have you tried that one?By the way, here's a breakout board that I made for the DS3231...
    DS3231.jpg
    I have the chip running under DS1307_RTCEngine modified for the additional registers. I was hoping to get the 12 hour function running so I could conserve some code space. I will change the com obj to use pst as my serial engine and use the bcd routines there. John, now that I know that you have the 3231 operational, I will zip up my code and pm it to you to see if the am/pm works. I am currently in Canada without computers, so will have to do that when I get home.Jim
  • SavageCircuitsSavageCircuits Posts: 199
    edited 2014-07-10 14:30
    JLocke wrote: »
    I had a project in which I was using a DS1307 for timekeeping and in a later revision I eliminated the 5V regulator, so I switch out the DS1307 for a DS3231. I don't recall making any code changes to use the new chip, but I'm using DS1307_RTCEngine for the time-keeping. Have you tried that one?

    By the way, here's a breakout board that I made for the DS3231...
    DS3231.jpg

    Do you have these for sale?
  • JLockeJLocke Posts: 354
    edited 2014-07-10 16:26
    Chris: No, but I've given out probably 4-5 of them in the Traveling Parts Box! They seem to go quickly. I think I have 3 or 4 of them around here... I'd be happy to send you one; drop me a PM!

    I drew the boards in Eagle, had them made at OSH Park (3 bds. for $4.50), and soldered them up in my cheap convection toaster oven (callback to discussion on Savage Circuits).

    RS_Jim: I use the DS1307_RTCEngine.spin object from the OBEX for conversing with the DS3231. I've been using another object, unix_time.spin, to deal with handling the date and time. Phil Pilgrim wrote the code, and this is from the file: This object provides methods that convert back and forth between Unix time and year.month.day hour:minute:second values. Unix time is a signed, 32-bit integer representing the number of seconds elapsed since 01 Jan 1970 at 00:00:00 UTC, ignoring leap seconds. The object also provides date and time from Unix time in ASCII string format.

    I modified the original to add two new public functions to return the date and the time in the format I wanted (mm/dd/yy and hh:mm).

    I make a call to the DS1307 object ReadTime method to get the current time
     rtc.readTime                                   ' get time from clock chip
      hour   := rtc.clockHour                        ' update the time values
      minute := rtc.clockMinute                      ' ... needed before starting data logging cog
      second := rtc.clockSecond
      day    := rtc.clockDate
      month  := rtc.clockMonth
      year   := rtc.clockYear
      DateTime := Tyme.unix_time(year, month, day, hour, minute, second, 0)
      Dow    := rtc.clockDay
    

    Another routine displays the date and time on the LCD
    PUB updateLCDTime | locTime
      ''* updates the LCD time display
      ''
      locTime := DateTime
      repeat until not lockset(LockLCD)              ' lock comms to LCD
      Comms.tx(LCD, $FE)                             ' home the cursor on 1st line
      Comms.tx(LCD, 128)
    
      Comms.str(LCD, Tyme.jl_date_str(locTime, 0))
      Comms.str(LCD, string(" "))                    ' add some separation
      Comms.str(LCD, Tyme.time12_str(locTime, 0))
      Comms.txflush(LCD)
      lockClr(LockLCD)                               ' release the lock
    

    Here's the code (in unix_time.spin) that builds the time string for display
    {
    ===============================================================================
    }
    PUB time12_str(unixtime, timezone) | hhmmss, hr  
    
      {{ Given Unix time and a local timezone, return a zero-terminated ASCII string in the format:
         "hh:mm:ss am" or "hh:mm:ss pm" (12-hour am/pm format).
      ''
      '' `Parameters:
      ''
      ''     `unixtime: Unix time in whole seconds.
      ''     `timezone: Timezone for time returned: -12 .. 12, e.g. 0 == UTC, -5 == EST, -7 == PDT, etc.
      ''
      '' `Return: Address of the resulting time string.
      ''          `NOTE: The contents of this string are transient and must be either copied or output before another
      '-                 time string method in this object is called.
      ''
      '' `Example: sio.str(udt.time12_str(1_000_000_000, 0))
      ''
      ''     Output a string representing the time given by 1_000_000_000 (in Unix time), UTC
      '-     (in this case the string "01:46:40 am").
      }}
    
      bytemove(@tm_str, string("xx:xx:xx AM"), 12)
      hhmmss := time(unixtime, timezone)
      
      if ((hr := hhmmss.byte[2]) > 12)
        tm_str[9] := "P"
        hr -= 12
    
      if (hr == 12)
        tm_str[9] := "P"
        
      if (hr == 0)
        hr := 12
        
      dec(@tm_str, hr, 2)
      dec(@tm_str[3], hhmmss.byte[1], 2)
      dec(@tm_str[6], hhmmss.byte[0], 2)
    
      if tm_str[0] == "0"     ' replace leading zero with space
        tm_str[0] := " "
        
      return @tm_str
    
  • SavageCircuitsSavageCircuits Posts: 199
    edited 2014-07-10 16:39
    Sent you a PM on Savage///Circuits. :cool:
  • SavageCircuitsSavageCircuits Posts: 199
    edited 2014-07-14 13:27
    I received the package today, but will follow-up on the Savage///Circuits forums.
  • RS_JimRS_Jim Posts: 1,751
    edited 2014-07-22 08:44
    jlocke,
    I am attaching an archive of my ds3231 code to have you run on your chip is you would. There is some debug code in it that shows what is being sent to the clock chip in binary and what is being returned from the chip.
    DS3231_RTCDemo_072114 - Archive [Date 2014.07.22 Time 08.14].zip
    _clockdatapin,_clockClockpin are couuently set at 1&0, you can edit in constants to match your setup.
    Thanks
    Jim
  • JLockeJLocke Posts: 354
    edited 2014-07-22 21:57
    I'll see if I can run this probably tomorrow night and let you know.
  • JLockeJLocke Posts: 354
    edited 2014-07-23 21:03
    Wired it up on the breadboard tonight, and at first look it works, but needs some work on the 'date' display output. Haven't checked all the functions, but may let you do that for yourself.
    The only thing I changed in the code was setting _clockClockPin = 28 and _clockDataPin = 29 to share the I2C line already established for the EEPROM. If you use some other pins, don't
    forget to add a 10K pull-up to the clock and data lines.

    Here's the PST session when setting the time. Notice that the time is displayed as 23:58:nn AM, although I entered the time as 10 PM.
    The first capture is from your program. The second is from the original DS1307 RTCEngine Demo dated 7/27/2010 by Kye.

    Capture.PNG
    CaptureOrig.PNG
    731 x 670 - 67K
    730 x 668 - 63K
  • RS_JimRS_Jim Posts: 1,751
    edited 2014-07-24 06:59
    John,
    Ok there is nothing wrong with my clock chip. You get the exact same results that I get. I wonder what the issue is with setting the 12 hour bit and the pm bit. I am sure I have gone over the data sheet thoroughly and %01000000 sets the hours to 12 & %00100000 sets the pm flag. As you can see the chip is scrambling the bits. Kye just uses 24 hour and subtracts 12 and sets the pm flag in software, I was trying to use the chips own capability.
    Thanks for checking on your chip. If you get a chance, look at the routine in time to see if you spot aby problems.
    Thanks again for checking.
    Jim
  • doggiedocdoggiedoc Posts: 2,239
    edited 2014-07-24 08:01
    JLocke wrote: »
    I had a project in which I was using a DS1307 for timekeeping and in a later revision I eliminated the 5V regulator, so I switch out the DS1307 for a DS3231. I don't recall making any code changes to use the new chip, but I'm using DS1307_RTCEngine for the time-keeping. Have you tried that one?

    By the way, here's a breakout board that I made for the DS3231...
    DS3231.jpg

    I'm interested in buying a couple of those from you. Any chance you have some for sale?
    Paul
  • RS_JimRS_Jim Posts: 1,751
    edited 2014-08-02 07:27
    Tom,
    Is there a chance you could mod your code to check the 12/24 hour feature un that chip. Everyone seems to be running 24 hour and correcting for 12.
    Jim
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2014-08-03 04:54
    I'm away from my computer until late August. I can have a look when I get back.
  • RS_JimRS_Jim Posts: 1,751
    edited 2014-08-05 06:18
    Thanks Tom, next time I have a chance to do major work with this is early Sept.
    Jim
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2014-08-24 10:21
    RS_Jim,

    I tried the am/pm mode and it seems to work fine (wouldn't have expected anything else). I prefer to use 24-hour mode because it is much simpler, even with correcting. Setting the hour is just too messy in am/pm.

    If you want to use am/pm, be sure and do NOT go through BCD2BIn when reading back the hour register. Also do NOT use Bin2BCD when writing it. That bit me for a while.

    Google "Maxim Tutorial 5413" for a really good description of how 12-hour am/pm works.

    tc
  • RS_JimRS_Jim Posts: 1,751
    edited 2014-08-26 07:05
    RS_Jim,

    I tried the am/pm mode and it seems to work fine (wouldn't have expected anything else). I prefer to use 24-hour mode because it is much simpler, even with correcting. Setting the hour is just too messy in am/pm.

    If you want to use am/pm, be sure and do NOT go through BCD2BIn when reading back the hour register. Also do NOT use Bin2BCD when writing it. That bit me for a while.

    Google "Maxim Tutorial 5413" for a really good description of how 12-hour am/pm works.

    tc
    Tom,
    I suspect that thebcd2bin bin2bcd is where my problems are originating.
    Jim
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2015-03-06 08:44
    This clock has gained two seconds over the last year. It started out two seconds slow and is now dead on(according to a WWVB clock) (as near as I can eyeball it). I'm gonna put in another five clicks of retardation in the aging register and see what it does over the next year.
  • kwinnkwinn Posts: 8,697
    edited 2015-03-06 21:02
    Now that's a long term project. Nice work Tom.
Sign In or Register to comment.