Mechanics of RTC date keeping?

Go To Last Post
27 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Greetings -

I fully realize that there is no one best way to do this but I am curious about the methods that folks use. The question is about when to roll to the next month. I can see several ways:

    1) An array of day counts for each month, modifying the February value according to whether or not it is a leap year.

    2) A lookup table, which is essentially an array

    3) An if - else if - chain such as

    if (month == 1) 
        do something;
    else if (month == 2)
        do something;
    else if ....

    4) Maybe there is some other scheme not yet divined?

The if/else if chain would seem to be easier to document and is arguably easier for some mythical second programmer to recognize. But, I suspect that it would lead to more code, though that is not a huge issue in my application.

Thoughts?

Thanks
Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My thoughts are that the way to do time was worked out by K&R in the 70s, and the c source to just about any c run time library will have it. Just have to find it. I betcha clawson can post the link from memory.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ahhh, had forgotten about time.h! Thanks, Bob

Perhaps I should add:

I have a seconds "ticker" 32KHz crystal plus Timer2 on Mega168P. Through the serial port, I am going to set the current hh:mm:ss, dd/mm/yyyy. It is necessary to update those values as the ticker ticks away.

This is to generate time stamps in a data logger record.

There are also "alarm" times when the logging is to begin and end, so date comparisons are needed. But, I do not have to deal with unixtime, sqltime, utc, time zones, and all that other stuff.

I am quite comfortable doing the time/date arithmetic myself. Having a library would be frosting.

Thanks
Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Only thing about time.h and the associated lib functionality is that it's going to depend on which C compiler you use. I'm guessing IAR comes with it but I don't think the other compilers do. It will be in the next release of avr-gcc as the files have already been added to AVR-LibC but no current issue yet has it (and Atmel being separate from the main development might mean they cannot use it in their releases).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Last time I did this, I used a lookup table, with an if to account for leap years.

//
// Return the number of days of the requested month. Also takes in account leap years
//
U8 get_days_in_month(char month, U16 year)
{
	static U8 __flash days_in_month[12]=
	{	31,28,31,30,31,30,31,31,30,31,30,31};
	U8 d;

	month -= 1;
	d = days_in_month[month];
	if (month == 1 && !(year & 3))
	{
		d += 1;
	}

	return (d);
}

(january=1, december=12)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jayjay -

In that scheme, how were you counting years? Was it uint16_t 2014 for year 2014?

thanks
Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Jim,

I guess you decided against using a Ds1307 which includes ALL of your requirements including alarms?

Cheers,

Ross

Ross McKenzie ValuSoft Melbourne Australia

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

:: Morten

 

(yes, I work for Microchip, yes, I do this in my spare time, now stop sending PMs)

 

The postings on this site are my own and do not represent Microchip’s positions, strategies, or opinions.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Don't have the board space, now. To be fair, I never really looked at it as it seemed like the 168, etc,had enough to handle the hard part.

Morten - thank you for that entertaining and enlightening reference. Fortunately, this is a logger and does not have to deal with time zones or time shifts or historical time. That can all be managed in the GUI that interfaces with it. It mostly has to be self-consistent, and accurate within a few 10s of seconds on intervals of less than 12 months.

Thanks,

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Jayjay -

I see what you are doing now - the (year & 3) as a substitute for "divisible by 4".

Thanks
Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

But, I do not have to deal with unixtime, sqltime, utc, time zones, and all that other stuff.

Consider the situation if you >>do<< consider using unixtime as your "least common denominator"...

-- Timekeeping in normal operation is merely incrementing a 32-bit value each second
-- Event logging is merely storing the current 32-bit value
-- Alarms can be n seconds "from now"

I find that the 32-bit form is more compact and generally easier to work with than some kind of bcd or binary representation of yymmddhhmm as when starting with a real RTC. In any case, at some point there are transformation routines needed. Those routines are well known. For your logging app, you could perhaps sidestep that entirely and just work with the seconds and seconds "from now".

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
For your logging app, you could perhaps sidestep that entirely and just work with the seconds and seconds "from now".
That's the way I have done my (few) logging apps, but none of those required on-board display or any day/date awareness. They perform the same task day or night, January or July.

Much easier that way. Setting the time from a host is a simple matter of sending the number of seconds since the Epoch as a 32-bit integer. Logging to an SD card (or wherever) is just as easy:

TIME TEMP BATT
1388939376 -22.7 3096
.
.
.

The log file isn't really human-readable, but after the data logging run is complete, host software can do the heavy lifting.

If you need to set alarms, that can be done by the host, so no need to include calendar arithmetic in the AVR.

On Linux, a simple command will convert any date to seconds since Epoch:

$ date +%s -d "2014-10-01 13:00:00"
1412182800
$ 

As Lee says, your AVR code then just needs to compare 'now' to a single 32-bit integer for each alarm.

The 'date' command above can also easily handle time zones and DST.

JJ

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hmmm, that is worth considering, especially with respect to memory space for timestamps.

Have to consider the details. I will be writing the high level host app, so epoch time is not an issue. I am using xplat XOJO and date.totalseconds returns exactly that.

What do I need to include in my avr program to allow 32-bit integer arithmetic (adds, increments, compares)?

Thanks
Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ka7ehk wrote:
What do I need to include in my avr program to allow 32-bit integer arithmetic (adds, increments, compares)?
Is this a trick question?

In avr-gcc, long is a 32-bit signed integer, same as unix time. Synonyms are long int and int32_t.

You could use unsigned 32-bit integers, but I don't see an advantage since unix time is signed. Negative values represent times before the Epoch (1970-01-01 00:00:00 GMT+0).

JJ

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Not a trick question. Call it an uninformed question. I did not realize that avr-gcc has 32 bit basic math support.

And, I see now that there is eeprom_write_dword, and so forth.

OK, all current questions have been dealt with. I think.

Thanks, everyone.

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I did not realize that avr-gcc has 32 bit basic math support.

It actually has 64bit basic math suport too ("long long" but better used as uint64_t/int64_t). For "Unixtime" however 32bits ((u)int32_t) will suffice.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks, Cliff

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You might not need full awareness of calendar time, but in case you want such features, there are nice tricks to calculate day-of-week from a given date, or rather, days since some reference day which is known.

In this case it was also easier to split year 2014 into two bytes, the other saying 20 centuries and other saying 14 years, to calculate stuff. Most likely it helped with calculating how many 365-day and 366-day years there have been.

Another point is, all monts are either long or short. Long months have 31 days, while short months have 30, except short month february which is as we know a bit special that it can have 28 or 29 days only, but anyway a single array of bits can represent days in month. Also it is a simple algorithm for counting that, just use the LSB, but it is different if month is <8 or >7.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've decided to only track (and uses as timestamp) total seconds in my logger. That turns out to be much easier.

The host will do the conversion of total seconds (unix epoch seconds) into human readable date and time. Makes it easier in the logger. Further, only the write event to logger NVRAM (once per 24 hours) will have a full time stamp. The intermediate values will only have minutes since last store event (will save about 250 bytes per day).

Thanks for all the great input. I has really helped me to clarify my thinking and choose a good scheme for this project.

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As a diversion, I have a current application for a series of controllers that has an RTC, as well as logging to SD card as an integral part of the application. That uses FATFS -- which needs a "DOS" timestamp!

So, twice a second I get the time from the RTC, in a typical BCD format. Because I know further manipulations are needed, I make a "bin[ary] time" in the same routine. Then, I convert to Unix timestamp.

When needed (creating a file on the SD card, the "bintime" is transformed to a DOS timestamp. Code below...

As a final kick in the pants, on the PC side when the log files are processed, one of the options is to create comma-delimited files suitable for Excel import. So there is another routine to turn Unix time into Excel time.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
//
// **************************************************************************
// "Time" structure as returned by DS1305/DS1306 RTC:
// **************************************************************************
//	BCD format time
//	Byte	0:		seconds			(00-59)
//			1:		minutes			(00-59)
//			2:		hours			(01-12 or 00-23, plus 12-24/am-pm flag)
//			3:		day of week 	(1-7)	[1=Sunday or 1=Monday?]
//			4:		date in month	(01-31)
//			5:		month			(01-12)
//			6:		year			(00-99)
//
//	Internal "time" structure used to move & store timestamps in BCD.
//	The "dayofweek" field is combined with the 5 bits of "month"
//		in a single byte.  6-byte total timestamp.
//	REVISION:  day-of-week is dropped
//
//	Byte	0:		seconds			(00-59)
//			1:		minutes			(00-59)
//			2:		hours			(01-12 or 00-23, plus 12-24/am-pm flag)
//
//			3:		month			(01-12)
//			4:		date in month	(01-31)
//			5:		year			(00-99)

struct	bcdtime
{
	unsigned char		seconds;
	unsigned char		minutes;
	unsigned char		hours;
	unsigned char		month;
	unsigned char		date;
	unsigned char		year;
};
struct	bintime
{
	unsigned char		seconds;
	unsigned char		minutes;
	unsigned char		hours;
	unsigned char		month;
	unsigned char		date;
	unsigned char		year;
};

typedef	unsigned long	utime_t;	// 32-bit UNIX time

// **************************************************************************
//	32-bit value union for remapping/"generic" data
// **************************************************************************
union	longmap
{
unsigned char			u8[4];			// 8-bit mapping
unsigned int			u16[2];			// 16-bit mapping
unsigned long int		u32;			// 32-bit mapping
};
typedef	union longmap				Longmap;
typedef	eeprom Longmap*					eeLongmapptr;

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
// ***********
// RTC Timestamp
// ***********
//		1/2 second:  RTC servicing

		SPCR = SPCR_DS1305;

		scratch = get_time(&bcdtimestamp, &bintimestamp);
		SPCR = SPCR_SD_RUN;	// return to SD card setup as the default

		// Check for a bad return value (a negative number in "scratch")

		// Create the 32-bit Unix timestamp used for operations
		unixtimestamp = bintime2unixtime (&bintimestamp);


You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
// ***********
// RTC Timestamp
// ***********
//		1/2 second:  RTC servicing

		SPCR = SPCR_DS1305;

		scratch = get_time(&bcdtimestamp, &bintimestamp);
		SPCR = SPCR_SD_RUN;	// return to SD card setup as the default

		// Check for a bad return value (a negative number in "scratch")

		// Create the 32-bit Unix timestamp used for operations
		unixtimestamp = bintime2unixtime (&bintimestamp);


...
//
// **************************************************************************
// *
// *	G E T _ T I M E
// *
// **************************************************************************
//
//	Set up and do a read from address 0 of the RTC.
//	Grab seven bytes of time stamp and place at bufptr.
//
//	Modified to produce both the BCD (directly from the DS1305/6) and the binary
//	(for further calculations) structures.
//
//	Returned value is the year, and -1 for invalid timestamp.
//
signed char						get_time			(struct bcdtime * timeptr, struct bintime * btimeptr)
{
unsigned char	hold_dayofweek;		// temporary holding value
unsigned char	bcdwork;			// temporary holding value
unsigned char	binwork;
signed char		retval;				// The (year-2000) for success; negative values for falure


    SELECT_RTC = 1;				// select RTC

	retval = 0;					// starting point; not a valid return value

    spi(0x00);					// send command "read starting at 0"

// Seconds
	bcdwork = spi(0x00);

	binwork = ((bcdwork >> 4)*10) + (bcdwork & 0x0f);

	if (binwork > 59)
		{
		retval -= 1;

		binwork = 0;
		bcdwork = 0;
		}
	timeptr->seconds = bcdwork;
	btimeptr->seconds = binwork;

// Minutes
	bcdwork = spi(0x00);

	binwork = ((bcdwork >> 4)*10) + (bcdwork & 0x0f);

	if (binwork > 59)
		{
		retval -= 2;

		binwork = 0;
		bcdwork = 0;
		}
	timeptr->minutes = bcdwork;
	btimeptr->minutes = binwork;

// Hours (24-hour format)
	bcdwork = spi(0x00);

	binwork = ((bcdwork >> 4)*10) + (bcdwork & 0x0f);

	if (binwork > 23)
		{
		retval -= 4;

		binwork = 0;
		bcdwork = 0;
		}
	timeptr->hours = bcdwork;
	btimeptr->hours = binwork;

// Day of Week
//	hold_dayofweek = spi(0x00) << 5;				// day of weeek 1-7 into high 3 bits
	hold_dayofweek = spi(0x00);		// day of weeek no longer used

// Date (Day of Month)
	bcdwork = spi(0x00);

	binwork = ((bcdwork >> 4)*10) + (bcdwork & 0x0f);

	if (binwork > 31)
		{
		retval -= 8;

		binwork = 1;
		bcdwork = 1;
		}
	timeptr->date = bcdwork;
	btimeptr->date = binwork;

// Month
	bcdwork = spi(0x00);

	binwork = ((bcdwork >> 4)*10) + (bcdwork & 0x0f);

	if ((binwork > 12) || (bcdwork < 1))
		{
		retval -= 16;

		binwork = 1;
		bcdwork = 1;
		}
	timeptr->month = bcdwork;
	btimeptr->month = binwork;

// Year
//	Accept somewhat arbitrary value of 2009-20025

	bcdwork = spi(0x00);

	binwork = ((bcdwork >> 4)*10) + (bcdwork & 0x0f);

	if ((binwork > 25) || (bcdwork < 9))
		{
		retval -= 32;

		binwork = 1;
		bcdwork = 1;
		}
	else
		{
		if (retval >= 0)
			{
			// No errors
			retval = binwork;
			}
		}
	timeptr->year = bcdwork;
	btimeptr->year = binwork;


    SELECT_RTC = 0;			// de-select RTC

	return retval;
}

/*---------------------------------------------------------*/
/* User Provided Timer Function for FatFs module           */
/*---------------------------------------------------------*/
/* This is a real time clock service to be called from     */
/* FatFs module. Any valid time must be returned even if   */
/* the system does not support a real time clock.          */
/* This is not required in read-only configuration.        */


unsigned long get_fattime (void)
{
// Assume that the ElmChan function with the shifts as shown below
//	is correct, and ape it to transform the DS1305 time structure
//	which is in BCD.
//
//
/*
MS-DOS Date & Time (4 bytes)

The lower word determines the time, the upper word the date. Used by several DOS function calls and by all FAT file systems.

Bits Contents
0-4 Second divided by 2
5-10 Minute (0-59)
11-15 Hour (0-23 on a 24-hour clock)
16-20 Day of the month (1-31)
21-24 Month (1 = January, 2 = February, etc.)
25-31 Year offset from 1980

*/
//	The 32-bit version will have lots of ugly shifts.  Let's see if a union would work.
union longmap		dostimestamp;

	// Build the low 16 bits
	dostimestamp.u16[0] =
				((unsigned int)bintimestamp.hours << 11)
			|	((unsigned int)bintimestamp.minutes << 5)
			|	((unsigned int)bintimestamp.seconds >> 1);

	// Build the high 16 bits
	dostimestamp.u16[1] =
				((unsigned int)(bintimestamp.year + (2000 - 1980)) << 9)
			|	((unsigned int)bintimestamp.month << 5)
			|	((unsigned int)bintimestamp.date);

	return dostimestamp.u32;
}

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

//
// **************************************************************************
// *
// *	B I N T I M E 2 U N I X T I M E
// *
// **************************************************************************
//
utime_t 				bintime2unixtime	(struct bintime * btimeptr)
{
// Convert the BCD time From DS1305/6 to an unsigned long UNIX timestamp
//	of seconds since the epoch-- Jan. 1, 1970.
//
// As this app never needs to go back in time or specify arbitrary dates,
//	simplifications can be done by simply calculating the whole-year
//	seconds through an arbitrary year such as 2000, 2007, or 2008.
//
// Do a quick window check on the year.  It must be > 2008, and must be less than
//	an arbitrary year--2020?
utime_t			work;	// accumulator of seconds
signed int		mult;	// working

// Base seconds, 1970 through 2008
	work = RTC_SEC_TILL_2009;

// How many years since?
	mult = btimeptr->year - RTC_YEAR_MIN;
	if ((mult < 0) || (mult > (RTC_YEAR_MAX - RTC_YEAR_MIN + 1)))
		{
		mult = 0;
		}
// Add the whole years since start of 2009
	work += (unsigned long)mult * RTC_SEC_PER_YEAR;

// Add a day for each leap year in the prior years
	work += ((unsigned long)mult/4) * RTC_SEC_PER_DAY;

// Add a day for this year, if leap year and past Feb.
	if ((mult+1) % 4 == 0)
		{
		// Leap year
		if (btimeptr->month > 2)
			{
			// Past February
			work += RTC_SEC_PER_DAY;
			}
		}

// Add the days up to this month.  Use a table
	mult = btimeptr->month;
	if ((mult < 1) || (mult > 12))
		{
		mult = 1;
		}

	work += ((unsigned long)rtc_days[mult-1]) * RTC_SEC_PER_DAY;

// Add the days this month, up to today
	mult = btimeptr->date;
	if ((mult < 1) || (mult > 31))
		{
		mult = 1;
		}

	work += ((unsigned long)(mult-1)) * RTC_SEC_PER_DAY;

// Add the seconds up to this hour
	mult = btimeptr->hours;
	if ((mult < 0) || (mult > 23))
		{
		mult = 1;
		}

	work += ((unsigned long)(mult)) * RTC_SEC_PER_HOUR;

// Add the seconds this hour
	mult = (unsigned int)60 * (unsigned int)btimeptr->minutes + (unsigned int)btimeptr->seconds;

	work += mult;

	return work;
}


You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
//	RTC and TimeStamp Handling
//	==========================
//
#define	RTC_SEC_PER_HOUR	(60l * 60l)
#define	RTC_SEC_PER_DAY		(RTC_SEC_PER_HOUR * 24l)
#define	RTC_SEC_PER_YEAR	(RTC_SEC_PER_DAY * 365l)

// Seconds from Jan.1, 1970 through Dec. 31, 2008.
//	-- 39 years
//	-- Leap years: 72, 76, 80, 84, 88, 92, 96, 00, 04, 08
//
//	According to Excel, there are 14245 days between those two dates.
//		365 * 39 = 14235 so that would be 10 leap years.
//
#define	RTC_SEC_TILL_2009	(RTC_SEC_PER_DAY * 14245L)

#define	RTC_YEAR_MIN	9	// 2009, as an 8-bit binary year
#define	RTC_YEAR_MAX	25	// 2025, as an 8-bit binary year

// Elapsed days at the end of each month shown (ignoring leap years)
flash unsigned int		rtc_days[13] =
{
0,
31,		// J
59,		// F
90,		// M
120,	// A
151,	// M
181,	// J
212,	// J
243,	// A
273,	// S
304,	// O
334,	// N
365		// D
};

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here are some of the relevant bits of the AVR-LibC (added but u unused so far) implementation:

http://svn.savannah.nongnu.org/v...

(the month/mday stuff towards the end of that looks particularly clever ;-))

And the other direction:

http://svn.savannah.nongnu.org/v...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Interesting. In my real app, I had the luxury of only worrying about current dates and not historical. So I used a baseline of RTC_SEC_TILL_2009 and went from there.

There are calculations for days of the various months in the libc code. I ended up just making a table and indexing, figuring it would be less cycles and in the end less code space.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.