I need some help figuring out a problem with RTC code
Don M
Posts: 1,652
I have built a serial sniffer. It records data to an sd card. So that it doesn't hold a file open forever and risk losing data I close the file after each receive/write operation for either the TX or RX loop and open the next time with append.
But that's not my problem as that part works fine. Also in order to not have a huge file I close the file and open a new file at the top of every hour. So for example assuming the current time and date my file name for this hour would be 0211_18.TXT.
This works up until it is midnight. I get all the other date/hour file labels in order from 1 - 23 but at midnight tonight it will write it as 0212_12.TXT instead of 0212_00.TXT.
I set the RTC in 24 hour mode and if I set the time to say 23:58 and invoke the Clock.GetHour method it will return 23. If I invoke the Clock.IsPM method it will return 1. Both as expected. So I let it run and the time rolls over from 23:59:59 to 00:00:00 and it produces the correct filename: 0212_00.TXT
The kicker is if I let it run overnight it does not produce the correct date/time file name. At midnight it shows 0212_12.TXT. So through the night it would produce the file names in this order:
(starting at 10:00 PM on Feb 10th)
0210_22.TXT
0210_23.TXT (11 PM)
0211_12.TXT (Midnight)
0211_01.TXT (1 AM)
0211_02.TXT
and so on till it get to 11 AM
0211_11.TXT
0212_12.TXT (file already exists so it just "appends" to it)
0212_13.TXT
on so on...
So I don't get why I can set the clock to 23:58 (11:58 PM) and let it run and it works correctly but if I let it run from say now (18:49 {6:49 PM}) till tomorrow morning it will be wrong again.
Here's my code that's pertinent to the date/time file name methods:
And here's Roy's RTC code. It's the same one used on the Spinneret. I modified a couple items as noted.
Any help trying to figure this out would be greatly appreciated.
Thanks
Don
But that's not my problem as that part works fine. Also in order to not have a huge file I close the file and open a new file at the top of every hour. So for example assuming the current time and date my file name for this hour would be 0211_18.TXT.
This works up until it is midnight. I get all the other date/hour file labels in order from 1 - 23 but at midnight tonight it will write it as 0212_12.TXT instead of 0212_00.TXT.
I set the RTC in 24 hour mode and if I set the time to say 23:58 and invoke the Clock.GetHour method it will return 23. If I invoke the Clock.IsPM method it will return 1. Both as expected. So I let it run and the time rolls over from 23:59:59 to 00:00:00 and it produces the correct filename: 0212_00.TXT
The kicker is if I let it run overnight it does not produce the correct date/time file name. At midnight it shows 0212_12.TXT. So through the night it would produce the file names in this order:
(starting at 10:00 PM on Feb 10th)
0210_22.TXT
0210_23.TXT (11 PM)
0211_12.TXT (Midnight)
0211_01.TXT (1 AM)
0211_02.TXT
and so on till it get to 11 AM
0211_11.TXT
0212_12.TXT (file already exists so it just "appends" to it)
0212_13.TXT
on so on...
So I don't get why I can set the clock to 23:58 (11:58 PM) and let it run and it works correctly but if I let it run from say now (18:49 {6:49 PM}) till tomorrow morning it will be wrong again.
Here's my code that's pertinent to the date/time file name methods:
repeat ' other code in here... Minute := clock.GetMinutes ' Get minutes from RTC if Minute == 59 ' Check to see if we are in last minute of the hour old := 1 ' Set flag for last minute of hour... if Minute == 0 ' If it is top of the hour... if old == 1 ' and it was previously 59 minutes... sd.pclose ' close any open file open_write ' start a new file with new name sd.SDStr(clock.FmtDateTime) ' then write the new date and time to SD card sd.pputc(13) sd.pclose ' and close file. old := 0 ' Reset flag pub open_write | l make_filename l := \sd.popen(@FileName, "r") if l == -1 ' If file does not exist then start a new file clock.Update sd.setdate(2000 + clock.GetYear, clock.GetMonth, clock.GetDay, clock.GetHour, clock.GetMinutes, clock.GetSeconds / 2) ' Write Date / Time to SD card l := \sd.popen(@FileName,"w") if l == -1 ser.str(DEBUG,string("SD Error 2", 13)) else ser.str(DEBUG,string("Ready... ", 13)) else ' Else if file already exists then just append file l := \sd.popen(@FileName,"a") if l == -1 ser.str(DEBUG,string("SD Error 2A", 13)) else ser.str(DEBUG,string("Ready (A)... ", 13)) pub make_filename '' Makes file name using date and hour in this format: mmdd_hh.txt make_nfilename(clock.GetMonth, @FileName, 0) make_nfilename(clock.GetDay, @FileName, 2) make_nfilename(clock.GetHour, @FileName, 5) dat FileName byte "0000_00.TXT", 0
And here's Roy's RTC code. It's the same one used on the Spinneret. I modified a couple items as noted.
'' S-35390A RTC Driver Version 1.0 '' Copyright (c) 2010 Roy ELtham '' November 13, 2010 '' See end of file for terms of use '' This uses the basic i2c driver available on the obex site '' '' This is the RTC chip used on the Spinneret Web Server. '' '' 01-23-2011 - (Beau Schwabe added 'FmtDateTime') to PUB operations '' 02-08-2014 - Added Update to Hours / Minutes / Seconds methods 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 with the DeviceID and commands to ' indicate reading or writing of the data Read = 1 Write = 0 OBJ i2c : "basic_i2c_driver_1" 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 byte DataBuffer[30] PUB start i2c.Initialize(SCL) ' disable the alarms (int pins) SetStatus2(%0000_0000) Update PUB FmtDateTime|p,r Update ' Update RTC values p := string("SunMonTueWedThuFriSat") ' changed order 10/24/2012. Mon was first. r := DateTime[3] ' GetDayOfWeek bytemove(@DataBuffer,p+r*3,3) DataBuffer[3] := " " p := string("JanFebMarAprMayJunJulAugSepOctNovDec") r := DateTime[1]-1 ' GetMonth bytemove(@DataBuffer+4,p+r*3,3) DataBuffer[7] := " " r := DateTime[2] ' GetDay DataBuffer[8] := (r/10)+48 DataBuffer[9] := r-((r/10)*10)+48 DataBuffer[10] := "," DataBuffer[11] := " " DataBuffer[12] := "2" DataBuffer[13] := "0" r := DateTime[0] ' GetYear DataBuffer[14] := (r/10)+48 DataBuffer[15] := r-((r/10)*10)+48 DataBuffer[16] := " " r := DateTime[4] ' GetHour DataBuffer[17] := (r/10)+48 DataBuffer[18] := r-((r/10)*10)+48 DataBuffer[19] := ":" r := DateTime[5] ' GetMinutes DataBuffer[20] := (r/10)+48 DataBuffer[21] := r-((r/10)*10)+48 DataBuffer[22] := ":" r := DateTime[6] ' GetSeconds DataBuffer[23] := (r/10)+48 DataBuffer[24] := r-((r/10)*10)+48 DataBuffer[25] := " " 'If Mode24Hour == 0 ' This routine doesn't work right. ' If PM == 0 ' DataBuffer[26]:="A" ' else ' DataBuffer[26]:="P" ' DataBuffer[27]:="M" 'else DataBuffer[28]:=0 return @DataBuffer PUB GetYear Update ' Added 2/8/2014 so correct time is shown when requested return DateTime[0] PUB GetMonth Update ' Added 2/8/2014 so correct time is shown when requested return DateTime[1] PUB GetDay Update ' Added 2/8/2014 so correct time is shown when requested return DateTime[2] PUB GetDayOfWeek Update ' Added 2/8/2014 so correct time is shown when requested return DateTime[3] PUB GetHour Update ' Added 2/8/2014 so correct time is shown when requested return DateTime[4] PUB GetMinutes Update ' Added 2/8/2014 so correct time is shown when requested return DateTime[5] PUB GetSeconds Update ' Added 2/8/2014 so correct time is shown when requested return DateTime[6] PUB Is24HourMode Update ' Added 2/8/2014 so correct time is shown when requested return Mode24Hour PUB IsPM Update ' Added 2/8/2014 so correct time is shown when requested return PM '' This functions tells you if the indicated alarm occurred 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) 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 occurred ' 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 to 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 '' 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 {{ 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. }}
Any help trying to figure this out would be greatly appreciated.
Thanks
Don
Comments
Are you doing this with by calling Set24HourMode?
What do you get when you call Clock.Is24HourMode?
Sorry if you've already checked these things. Just throwing out wild guesses.
This problem may be due to how the RTC changes the hour in 24hr mode. I had one RTC chip that went from 23:59:59 to 12:00:00, then very quickly to 00:00:00. Only reason I caught it was that time data was latched and sent to the PC using the 1PPS pulse from the RTC.
My code does that now. I thought about putting in some code to check but I shouldn't have to.
@Duane - I'll look into your suggestions.
Keep in mind they're just wild guesses.
Yes.
This is interesting... I get 0. So I'll have to see why. The current time even shows as 22:24 so I don't know why it displays the time that way but thinks its not in the 24 Hr mode.
Like you say there must be a bug in the clock object code.