Did I break two Seiko S-35390A RTC devices?
MJHanagan
Posts: 189
Have been using a Propeller DNA board for a home automation project just getting underway. One of the reasons for picking this platform is the on-board RTC (Seiko S-35390A). Using the code below I managed to get the RTC running without any troubles. I have been working on the main code over a period of weeks with modest success. A few days ago I made some edits to the code that talks to a weather station and mangle the code on and off as I worked out the details of storing some data in the upper region of the Propeller's RAM (from $9600 to about $9700). After tweaking a few debugging lines in the code I downloaded the code but it would not function beyond the start routine for the RTC. After reviewing the latests edits looking for a reason why the code failed to get beyond the initialization of the RTC I decided to try a previous version of the code which work well in the past. This too choked at the start of the RTC routine. So I went even further back to the beginning where all I have is pretty much the RTC code, and this too failed at the start of the RTC. I was now convinced the RTC device got whacked so I got out the second DNA board and plugged it in and downloaded the code below. Voila, it seemed to work so I edited the code to reflect the current time and downloaded it again. As it was downloading I was unwiring the first DNA board when I noticed on the serial terminal that the code stopped working at the RTC start routine. What the heck?
Can you break two RTCs by running bad code? Is this bad code or just bad luck?
Can you break two RTCs by running bad code? Is this bad code or just bad luck?
'' Seiko S-35390A RTC Driver Version 1.0 '' Original code from OBEX ("S-35390A RTC Driver Version 1.0 ", by Roy Eltham dated November 13, 2010) '' '' Code modifications by MJH: '' * includes DST features (Daylight Savings Time) - DST status '' is stored in the RTC's "user byte": '' 0=standard time currently in effect '' 1=daylight saving time currently in effect '' the SetDST routine checks the current date and hour to determine if a change '' is needed and if so make the adjustment in the hour and updates the "user byte". '' * access to sunrise and sunset times (previously stored values in EEPROM) '' * RTC runs in fixed 24 hour mode. Use GetHour12 for AM/PM hour '' '' This uses the basic i2c driver available on the obex site '' See end of file for terms of use. CON _clkmode = xtal1 + pll16x _clkfreq = 80_000_000 SCL = 28 ' SCL pin of the s-35390A, SDA is assumed to be ' one pin higher by the i2c driver DeviceID = %0110_0000 ' S-35390A device id ' S-35390A commands, these are combined with DeviceID to read or write the chip registers Command_Status1 = %0000_0000 Command_Status2 = %0000_0010 Command_DateTime = %0000_0100 Command_Time = %0000_0110 Command_Alarm1 = %0000_1000 Command_Alarm2 = %0000_1010 Command_ClockCorrection = %0000_1100 Command_UserData = %0000_1110 ' these are combined (ORed) with the DeviceID and commands to ' indicate reading or writing of the data Read = 1 Write = 0 CON MemSunrise = $8000 'Location of the first long: Sunrise for day of the year 1 (1-Jan) MemSunset = MemSunrise + 366 * 4 'Location of the first long: Sunset for day of the year 1 (1-Jan) LightsPin = 1 RFpin = 16 OBJ i2c : "basic_i2c_driver" pst : "Parallax Serial Terminal" VAR long Status1, Status2 long DateTime[7] ' year(0 to 99), month, day, day of week (0 to 6), hour, minute, seconds long Mode24Hour ' 0 = 12 hour mode, 1 = 24 hour mode long PM ' 0 = AM, 1 = PM long AlarmOccurred[2] ' status of the 2 alarms, 0 = did not occur, 1 = did occur long DSTenabled ' Daylight savings time flag (0=disabled, 1=enabled) long DSTadjust ' Daylight savings adjustment hour (0=not in DST, 1=in DST) long DayOfYear long KeyIn Long LOhh, LOmm, LOss, dd, mm, yy, MemAddr long LightsON, LightsOFF long i, j, k, l, m ' LONG LightsPin PUB Main DIRA[ LightsPin ] := 1 DIRA[ 2 ] := 1 DIRA[ RFpin ] := 0 pst.Start( 115_200 ) ' Start the terminal at 115,200 baud pst.HOME ' Clear screen and place cursor at home position pst.Beep pst.CLEAR pst.str(string("Seiko S-35390A Demo Program")) ' Start the RTC i2C function and update the time. start ' Set mode for 24 hours Set24HourMode(1) pst.str(string(pst#NL,pst#NL, "The current RTC user data value is: ")) pst.dec( GetUserData ) ' Use the RTC's "user byte" to store the DST setting (0=Standard time in effect, 1=daylight saving time in effect)Set the DST SetUserData( 1 ) pst.str(string(pst#NL,pst#NL, "The current user data value is: ")) pst.dec( GetUserData ) ' mm, dd, yy, dow, hh, mm, ss 'SetDateTime(6,22,15,1,12+6,57,0) 'SetTime( 19, 17, 0 ) 'Update 'Read the current time from the RTC device pst.str(string(pst#NL,pst#NL, "The current clock time is: ")) pst.rjdec(GetHour, 2, "0") pst.Char(":") pst.rjdec(GetMinutes, 2, "0") pst.Char(":") pst.rjdec(GetSeconds, 2, "0") pst.Char(" ") IF IsPM == 0 pst.str(string("AM ")) ELSE pst.str(string("PM ")) pst.str(@DayNames[GetDayOfWeek*4]) pst.Char(" ") pst.rjdec(GetDay, 2, "0") pst.Char("-") pst.str(@MonthNames[GetMonth*4]) pst.Char("-") pst.dec( GetYear ) pst.str(string(pst#NL, "Today's sun rise time is: ")) pst.dec( SunriseSS( GetDayOfYear ) ) pst.str(string(" and sunset at: ")) pst.dec( SunsetSS( GetDayOfYear ) ) ' The 90*60 corrects for 30 minutes for dusk plus 60 minutes for DST LightsON := SunsetSS( GetDayOfYear ) + 30 * 60 + GetUserData * 3600 pst.str(string(pst#NL, "Lights on at: ")) LOhh := LightsON / 60 /60 LOmm := ( LightsON - LOhh*60*60 ) / 60 LOss := LightsON - LOhh*60*60 - LOmm * 60 pst.rjdec( LOhh, 2, "0" ) pst.Char(":") pst.rjdec( LOmm, 2, "0" ) pst.Char(":") pst.rjdec( LOss, 2, "0" ) 'LightsOFF := (10+12)*60*60 'Lights off at 10:00 PM LightsOFF := (9+12)*60*60 + 55*60 + 0 'Lights off at 10:00 PM IF ElapsedTime > LightsON AND ElapsedTime < LightsOFF OUTA[ LightsPin ] := 1 ELSE OUTA[ LightsPin ] := 0 repeat KeyIn := pst.rxcount IF KeyIn > 0 KeyIn := pst.CharIn IF KeyIn == 27 QUIT ELSE Update pst.str(string( pst#NL, "The time is: ")) pst.rjdec(GetHour12, 2, "0") pst.Char(":") pst.rjdec(GetMinutes, 2, "0") pst.Char(":") pst.rjdec(GetSeconds, 2, "0") pst.Char(" ") IF IsPM == 0 pst.str(string("AM")) ELSE pst.str(string("PM")) pst.str(string( pst#NL, "Elapsed seconds since midnight: ")) pst.dec( ElapsedTime ) IF ElapsedTime > LightsON and ElapsedTime < LightsOFF pst.str(string( " Lights are ON")) ELSE pst.str(string( " Lights are OFF")) !OUTA[ 2 ] 'UserData := GetUserData waitcnt(cnt+clkfreq/10) 'IF INA[ RFpin ] := 1 ' !OUTA[ LightsPin ] ' !OUTA[ 2 ] ' waitcnt(cnt+clkfreq) Update IF OUTA[ LightsPin ] == 0 AND ElapsedTime > LightsON AND ElapsedTime < LightsOFF OUTA[ LightsPin ] := 1 pst.str(string( pst#NL, "Lights turned on at ")) pst.str( GetTimeString ) ELSEIF OUTA[ LightsPin ] == 1 AND ( ElapsedTime < LightsON OR ElapsedTime > LightsOFF ) OUTA[ LightsPin ] := 0 pst.str(string( pst#NL,"Lights turned off at ")) pst.str( GetTimeString ) pst.beep pst.str(string(pst#NL,pst#NL, "All done, good-bye.")) PUB start i2c.Initialize(SCL) ' disable the alarms (int pins) SetStatus2(%0000_0000) Update PUB GetYear return DateTime[0] PUB GetMonth return DateTime[1] PUB GetDay return DateTime[2] PUB GetDayOfWeek return DateTime[3] PUB GetHour return DateTime[4] PUB GetHour12 IF DateTime[4] == 0 RESULT := 12 ELSEIF DateTime[4] >12 RESULT := DateTime[4] - 12 ELSE RESULT := DateTime[4] return RESULT PUB GetMinutes return DateTime[5] PUB GetSeconds return DateTime[6] PUB Is24HourMode return Mode24Hour PUB IsPM return PM '' This functions tells you if the indicated alarm occured since the last time you called '' this function. These flags are potentially set by calls to GetStatus1, Update, SetTime, or SetDateTime. '' The flag for the indicated alarm is cleared by this function so it can be triggered again later. '' whichAlarm should be 0 or 1 PUB DidAlarmOccur(whichAlarm) ' clamp incoming value whichAlarm := 0 #> whichAlarm <# 1 result := AlarmOccurred[whichAlarm] AlarmOccurred[whichAlarm] := 0 '' state should be 0 for 12 hour mode or 1 for 24 hour mode PUB Set24HourMode(state) ' clamp incoming value Mode24Hour := 0 #> state <# 1 ' read the current Status1 value Status1 := GetStatus1 ' update the 12/14 bit based on state if Mode24Hour == 1 Status1 |= %0100_0000 ' or in the bit ' fix up our stored hour if PM == 1 AND DateTime[4] < 12 DateTime[4] += 12 else Status1 &= %1011_1111 ' mask off the bit ' fix up our stored hour if PM == 1 DateTime[4] -= 12 ' in 12 hour mode change hour 0 to 12 if DateTime[4] == 0 DateTime[4] := 12 SetStatus1(Status1) '' The hour is expected in 24 hour mode, it will be converted into 12 hour mode if you have set that mode. '' I did this to save having another parameter passed in for the AM/PM flag which would be ignored in '' 24 hour mode. '' dayOfWeek is a value from 0 to 6, you can set it to whatever you want for whatever day, and the clock will just '' increment it each day, wrapping at 6 back to 0. You probably want to use 0 to either Sunday or Monday. '' It only matters if you set an alarm with a dayOfWeek enabled. PUB SetDateTime(month, day, year, dayOfWeek, hour, minutes, seconds) | index DateTime[0] := ConvertToBCD(year) DateTime[1] := ConvertToBCD(month) DateTime[2] := ConvertToBCD(day) DateTime[3] := ConvertToBCD(dayOfWeek) ' adjust hour based on 12/24 hour mode if Mode24Hour == 0 AND hour > 11 hour -= 12 DateTime[4] := ConvertToBCD(hour) | %0100_0000 else DateTime[4] := ConvertToBCD(hour) DateTime[5] := ConvertToBCD(minutes) DateTime[6] := ConvertToBCD(seconds) ' write out the full date and time to the chip i2c.Start(SCL) i2c.Write(SCL, DeviceID | Command_DateTime | Write) repeat index from 0 to 6 i2c.Write(SCL, DateTime[index] >< 8) 'The ><8 reverses the order of the lower 8 bits and clears all bits above 8 to 0 i2c.Stop(SCL) ' reread the date & time to fix our stored values Update '' The hour is expected in 24 hour mode, it will be converted into 12 hour mode if you have set that mode. '' I did this to save having another parameter passed in for the AM/PM flag which would be ignored in '' 24 hour mode. PUB SetTime(hour, minutes, seconds) | index ' adjust hour based on 24/12 hour mode if Is24HourMode == 0 AND hour > 11 hour -= 12 DateTime[4] := ConvertToBCD(hour) | %0100_0000 ' or in the am/pm flag (high = pm) else DateTime[4] := ConvertToBCD(hour) DateTime[5] := ConvertToBCD(minutes) DateTime[6] := ConvertToBCD(seconds) ' write out the time to the chip i2c.Start(SCL) i2c.Write(SCL, DeviceID | Command_Time | Write) repeat index from 4 to 6 i2c.Write(SCL, DateTime[index] >< 8) i2c.Stop(SCL) ' reread the date & time to fix our stored values Update '' Sets the indicated alarm. The hour value is expected to be in 24 hour mode (like SetTime) '' if dayOfWeek, hour, or minute are negative values then the alarm will not use that portion '' of the setting. For example, SetAlarm(0, -1, 10, 30) will have the alarm go off every morning '' at 10:30am, SetAlarm(0, -1, -1, 15) will have the alarm go off 15 minutes after every hour of '' every day, SetAlarm(0, -1, -1, -1) will disable the alarm. '' dayOfWeek is a value from 0 to 6, and corresponds with whatever you set in SetDateTime. If, when you called '' SetDateTime, you set dayOfWeek to be 0 for Sunday, then a value of 3 here would mean Wednesday. PUB SetAlarm(alarmIndex, dayOfWeek, hour, minutes) | Alarm[3], index ' Clamp to valid range alarmIndex := 0 #> alarmIndex <# 1 ' Set the indicated alarm into alarm mode Status2 := GetStatus2 if alarmIndex == 0 Status2 := Status2 & %0001_1111 ' clear the alarm 1 state flags Status2 := %0010_0000 | Status2 ' set the alarm 1 state flags to be in alarm mode else Status2 := Status2 & %1111_0001 ' clear the alarm 2 state flags Status2 := %0000_0010 | Status2 ' set the alarm 2 state flags to be in alarm mode SetStatus2(Status2) ' setup what we are going to write to the alarm registers based on the input params ' if dayOfWeek > 0 Alarm[0] := ConvertToBCD(dayOfWeek) | %1000_0000 else Alarm[0] := 0 if hour > 0 if Is24HourMode == 0 AND hour > 11 hour -= 12 Alarm[1] := ConvertToBCD(hour) | %0100_0000 ' or in the am/pm flag (high = pm) else Alarm[1] := ConvertToBCD(hour) | %1000_0000 else Alarm[1] := 0 if minutes > 0 Alarm[2] := ConvertToBCD(minutes) | %1000_0000 else Alarm[2] := 0 ' write out the cooked alarm info to the chip i2c.Start(SCL) if alarmIndex == 0 i2c.Write(SCL, DeviceID | Command_Alarm1 | Write) else i2c.Write(SCL, DeviceID | Command_Alarm2 | Write) repeat index from 0 to 2 i2c.Write(SCL, Alarm[index] >< 8) i2c.Stop(SCL) '' These 2 functions allow you to read and write a byte of data to the clock chip that is saved across '' power cycling the Spinneret. It will be saved as long as the SuperCap keeps the RTC chip going (days). PUB GetUserData : result i2c.Start(SCL) i2c.Write(SCL, DeviceID | Command_UserData | Read) result := i2c.Read(SCL, i2c#NAK) i2c.Stop(SCL) return result PUB SetUserData(value) i2c.Start(SCL) i2c.Write(SCL, DeviceID | Command_UserData | Write) i2c.Write(SCL, value) i2c.Stop(SCL) PUB GetStatus1 : result i2c.Start(SCL) i2c.Write(SCL, DeviceID | Command_Status1 | Read) result := i2c.Read(SCL, i2c#NAK) i2c.Stop(SCL) ' check to see if either alarm occured ' we need to do this any time Status1 is read since reading it clears the alarm flags if result & %0000_1000 <> 0 AlarmOccurred[0] := 1 if result & %0000_0100 <> 0 AlarmOccurred[1] := 1 ' Clear off the alarm flags and the reset bit (high bit). ' We clear the reset bit because we don't want to make it easy to accidentally reset the ' chip. Which could happen if we read Status1 and then use that value modified to write ' back to Status1. result &= %01110011 return result PUB SetStatus1(value) i2c.Start(SCL) i2c.Write(SCL, DeviceID | Command_Status1 | Write) i2c.Write(SCL, value) i2c.Stop(SCL) PUB GetStatus2 : result i2c.Start(SCL) i2c.Write(SCL, DeviceID | Command_Status2 | Read) result := i2c.Read(SCL, i2c#NAK) i2c.Stop(SCL) ' Clear off the low bit, this si the test bit and should always be zero. result &= %1111_1110 return result PUB SetStatus2(value) ' Clear off the low bit, this si the test bit and should always be zero. value &= %1111_1110 i2c.Start(SCL) i2c.Write(SCL, DeviceID | Command_Status2 | Write) i2c.Write(SCL, value) i2c.Stop(SCL) '' Read the full date and time from the chip. Also, updates our PM flag appropriately, and '' updates the cached Status variables as well as the AlarmOccurred variable. PUB Update | index, temp i2c.Start(SCL) i2c.Write(SCL, DeviceID | Command_DateTime | Read) ' read first 6 bytes repeat index from 0 to 5 temp := i2c.Read(SCL, i2c#ACK) >< 8 ' the am/pm flag is valid in both 12 and 24 hour mode if index == 4 ' index 4 is the hour if temp & %0100_0000 ' check the am/pm flag bit PM := 1 temp &= %1011_1111 ' mask off the am/pm flag bit else PM := 0 ' it's AM so clear the PM flag DateTime[index] := ConvertFromBCD(temp) ' read last byte DateTime[6] := ConvertFromBCD(i2c.Read(SCL, i2c#NAK) >< 8) i2c.Stop(SCL) ' in 12 hour mode change hour 0 to 12 'if Mode24Hour == 0 AND DateTime[4] == 0 ' DateTime[4] := 12 ' update cached status variables by reading the chip status ' this will also update the Alarm1_Occurred and Alarm2_Occurred variables Status1 := GetStatus1 Status2 := GetStatus2 ' Update the DST values UpdateDST '' read the detailed information about clock correction in the S-35390A datasheet '' before using these functions PUB SetClockCorrection(value) i2c.Start(SCL) i2c.Write(SCL, DeviceID | Command_ClockCorrection | Write) i2c.Write(SCL, value) i2c.Stop(SCL) PUB GetClockCorrection : result i2c.Start(SCL) i2c.Write(SCL, DeviceID | Command_ClockCorrection | Read) result := i2c.Read(SCL, i2c#NAK) i2c.Stop(SCL) return result PRI ConvertToBCD(value) : result ' convert a long to Binary Coded Decimal result := ((value / 10) * 16) + (value // 10) return result PRI ConvertFromBCD(value) : result ' convert from Binary Coded Decimal to a long result := ((value / 16) * 10) + (value // 16) return result PUB UpdateDST {{ see http://www.nist.gov/pml/div688/dst.cfm As of 2007, daylight saving time in the United States begins at 2:00 a.m. on the second Sunday of March, and ends at 2:00 a.m. on the first Sunday of November Note: in order for this algorithm to function properly you must use the following day code sequence in the S35390A RTC: 0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat }} CASE GetMonth 4..10 : DSTadjust := +1 'If the month is between April (4) and October (10) then it is DST 1..2 : DSTadjust := 0 'If the month is between December (12) and February (2) then it is not DST 3 : 'DST begins at 2:00 AM on the second Sunday of March IF ( ( GetDay + GetDayOfWeek ) >8 ) OR ( ( GetDay + GetDayOfWeek ) == 8 AND GetHour => 2 ) DSTadjust := +1 ELSE DSTadjust := 0 11 : 'DST ends at 2:00 a.m. on the first Sunday of November IF ( GetDay + GetDayOfWeek ) <7 OR ( ( GetDay + GetDayOfWeek ) ==7 AND GetHour <2 ) '<-- needs work to address 1 hour transition time DSTadjust := +1 ELSE DSTadjust := 0 ' Compare the DST value with the user byte value stored in the RTC. IF DSTadjust <> GetUserData 'If values are not the same then a DST transition has just occured. IF DSTadjust == 1 'We have just transisitoned to DST so adjust clock ahead by +1 hours (2 AM --> 3 AM) SetTime( GetHour+1, GetMinutes, GetSeconds ) SetUserData( 1 ) 'Update the user data byte to 1 to signify the can was made to the RTC's time 'DSTadjust = 0 so the transition was out of DST and we need adjust the clock by -1 hours 'but when we do this the next DST check will say we are back to DST time so we need 'to use a temporary user value of 2 for the next hour before setting it to 0. ELSEIF GetUserData == 2 AND GetHour =>2 'If we were in the transition hour and just now passed out of the 1 AM to 2 AM hour then SetUserData(0) 'set the user byte value to 0 (now DST=User value=0) ELSE 'This is the first occurance of leaving DST so adjust the RTC hour by -1 SetTime( GetHour-1, GetMinutes, GetSeconds ) SetUserData( 2 ) 'Update the user data byte to 2 signifying we are now in that special 1 hour transition time from 1 AM to 2 AM. RETURN DSTadjust Pub SunriseSS( _DayOfYear ) MemAddr := MemSunrise + ( _DayOfYear-1 ) * 4 RESULT := i2c.ReadLong(i2c#BootPin, i2c#EEPROM, MemAddr ) RETURN Pub SunsetSS( _DayOfYear ) ' The stored sunset times (seconds past midnight) are stored as longs (4 bytes) in EEPROM memory. ' This function returns the Sunset time for the specified date. MemAddr := MemSunset + ( _DayOfYear-1 ) * 4 RESULT := i2c.ReadLong(i2c#BootPin, i2c#EEPROM, MemAddr ) RETURN PUB GetDayOfYear | iC DayOfYear := 0 iC := 0 REPEAT GetMonth DayOfYear += DaysInMonth[iC++] DayOfYear += GetDay RETURN DayOfYear PUB ElapsedTime RETURN GetHour*60*60+GetMinutes*60+GetSeconds PUB GetTimeString TimeStr[0] := GetHour12 / 10 + "0" TimeStr[1] := GetHour12 // 10 + "0" TimeStr[3] := GetMinutes / 10 + "0" TimeStr[4] := GetMinutes // 10 + "0" TimeStr[6] := GetSeconds / 10 + "0" TimeStr[7] := GetSeconds // 10 + "0" IF IsPM == 1 TimeStr[9] := "P" ELSE TimeStr[9] := "A" RETURN @TimeStr DAT ' Sun=0, Mon=1, Tue=2, Wed=3, Thu=4, Fri=5, Sat=6 DayNames byte "Sun", 0, "Mon", 0, "Tue", 0, "Wed", 0, "Thu", 0, "Fri", 0, "Sat", 0 ' mmm=0, Jan=1, Feb=2, Mar=3, Apr=4, May=5, Jun=6, Jul=7, Aug=8, Sep=9, Oct=10, Nov=11, Dec=12 MonthNames byte "mmm", 0, "Jan", 0, "Feb", 0, "Mar", 0, "Apr", 0, "May", 0, "Jun", 0, "Jul", 0, "Aug", 0, "Sep", 0, "Oct", 0, "Nov", 0, "Dec", 0 DaysInMonth byte 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 TimeStr byte "hh:mm:ss xM", 0 {{ TERMS OF USE: MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. }}
Comments
It is difficult for me to believe that it is possible to permanently damage something like a clock chip with bogus programming (although I am sure there are plenty of counter-examples).
The data sheet is REV 4.1, which suggests the part has been in production for some time (but perhaps you got two parts from the same flakey run (or even forgeries)).
Of course, if you have a logic analyzer, you can determine exactly how it is getting stuck.
Page 36 of the data sheet talks about "Reset After Communication Interruption".
I would be more inclined to look for noisy power, etc.
Just my opinion.