I need some help figuring out a problem with RTC code
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.
PUB Set24HourMode(state) ' clamp incoming value Mode24Hour := 0 #> state <# 1 ' read the current Status1 value Status1 := GetStatus1 ' update the 12/24 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)