Unixtime Incorrect + Timer vs ISR?

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

Hello,

 

This thread references another one I previously made: http://www.avrfreaks.net/forum/avr-atxmega32a4-time-question.

After having some time off from working on this project, I came back and attempted to progress with the knowledge I received from that thread.

 

My goal is still the same: with an ATxMEGA32A4, record the time in unixtime and display it in a serial logging program (PuTTY) with USART.

To be more specific, the program I am running is expected to test at least a week, recording current measurements (filtered measurement every X amount of seconds) and time.

 

As a test, I used June 1, 2017 12:00PM inputted by the user as a test for how my program is converting the date. Using asctime() to display the date on the device's LCD screen works.

When trying to display the data serially through PuTTY, as a unixtime timestamp, it shows '377889856' instead of the expected '1496318400' for the date. What could be causing this change?

 

Here is the relevant code:

int16_t month = 6;
int16_t day = 1;
int16_t year = 2017;
int16_t hour = 12;
int16_t minute = 0;

...

//Store date/time variables into a struct for use in the system time
time_t unixtime;
struct tm * start_date;

start_date->tm_year = year;
start_date->tm_mon = month - 1;
start_date->tm_mday = day;
start_date->tm_hour = hour;
start_date->tm_min = minute;

//Use the date for mk_gmtime
unixtime = mk_gmtime(start_date);

//clear the lcd screen to display the date
lcdClear();
lcdPrintData(asctime(start_date), 16);

//set the system time to user-defined time
set_system_time(unixtime);    

//TEST
//print the unixtime to show on the putty
printUint32((uint32_t)unixtime, 10);

...

void printUint32(uint32_t n, uint8_t base) {
	char buf[8 * sizeof(uint32_t) + 1]; // Assumes 8-bit chars plus zero byte.
	char *str = &buf[sizeof(buf) - 1];

	*str = '\0';

	// prevent crash if called with base == 1
	if (base < 2) base = 10;

	do
	{
		uint32_t m = n;
		n /= base;
		char c = m - base * n;
		*--str = c < 10 ? c + '0' : c + 'A' - 10;
	} while(n);

	int i = 0;
	while (*(str+i) != '\0')
	{
		do{} while(!USART_IsTXDataRegisterEmpty(&USARTC0));
		USART_PutChar(&USARTC0, *(str+i));
		i++;
	}
}

void printUint16(uint16_t n, uint8_t base) {
	char buf[8 * sizeof(uint16_t) + 1]; // Assumes 8-bit chars plus zero byte.
	char *str = &buf[sizeof(buf) - 1];

	*str = '\0';

	// prevent crash if called with base == 1
	if (base < 2) base = 10;

	do
	{
		uint16_t m = n;
		n /= base;
		char c = m - base * n;
		*--str = c < 10 ? c + '0' : c + 'A' - 10;
	} while(n);

	int i = 0;
	while (*(str+i) != '\0')
	{
		do{} while(!USART_IsTXDataRegisterEmpty(&USARTC0));
		USART_PutChar(&USARTC0, *(str+i));
		i++;
	}
}

From what I know from this link: http://www.nongnu.org/avr-libc/user-manual/group__avr__time.html, unixtime would be of type uint32_t.

 

---

 

Also related to this question is whether I could alternatively use a timer to increment the unixtime instead of interrupts. Is that doable?

My attempts at integrating ISR into my program doesn't seem to work out for me as a novice at AVR programming. The ISR function would be separate from the main function, right?

 

Here is a piece of code within the program that uses the timer from the ATxMega32A4. It loops and measures for data two times for every second that it must account for (user sets 4 seconds, it will loop 8 times).

//initialize timer
TIMER.CTRLA = TC_CLKSEL_DIV64_gc;
TIMER.CNT = 0;
//ADC value
//Acquire sensor current measurement, using a 12-bit ADC
ADC_Ch_Conversion_Start(&ADCA.CH1);	//start the ADC measurement
while(!ADC_Ch_Conversion_Complete(&ADCA.CH1)) {}
value_ADC = ADC_ResultCh_GetWord_Signed(&ADCA.CH1,ADC_OFFSET);	//obtain the data in counts
//wait for 500 milliseconds; if the user holds or pushes the controller's stick left, it will terminate the ongoing test and exit to menu
while(TIMER.CNT<15625)
{
	if(bit_is_set(PORTA.IN,6))	//will complete this function and return to menu if the stick is moved left
	{
		skipLine();
		//will scan for the left button being held; if held long enough, stop the test, else continue
		for(int j = 0; j < 10; j++)
		{
			_delay_ms(100);
			if(bit_is_set(PORTA.IN,6))
			{
				is_dir_left = true;
				send_string("holding...");
				printChar(13);
			} else
			{
				is_dir_left = false;
				break;
			}
		}
		if(is_dir_left)
		{
			printExitMsg();
			return 0;
		}
	}
}
//reset timer
TIMER.CNT = 0;

 

If I'm being too insistent with my threads, I apologize in advance.

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

breadsticks wrote:
What could be causing this change?

 

Your printUint32() code?  If you make some test runs in the simulator, what do you get?

 

 

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

​I haven't figured out how to run the simulator properly on Atmel Studio 7. Do you think there might be something wrong with how that function is implemented?

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

Avoid doing the conversion yourself, if possible. While not standard, many C toolchains support itoa(...).

 

breadsticks wrote:
​Do you think there might be something wrong with how that function is implemented?

It's about ruling things in or out and thus reducing the problem space.

 

breadsticks wrote:
​I haven't figured out how to run the simulator properly on Atmel Studio 7.

Preparations:

  1. Isolate your conversion-and-print function into a separate minimal test project. Remove the actual UART code from it  (since simulating UARTs is not a smooth simple process) - possibly replacing it with outputting consecutive characters to a port instead (this will e.g. stop the optimizer from just throwing everything out as "meaningless code"). This is IMPORANT ADVICE! Always try to debug as small a code mass as possible - that's what the pro's do all the time. Don't think you can get out of trouble faster by not doing this preparatory step - because you can't. All the non-relevant stuff around the problem proper will just disturb the process and cost a lot of time and grief.
  2. In the project Properties, select Tool to the left, and then under Select debugger/programmer to the right select Simulator.

Running in/with the simulator:

  1. In the debug menu, select Start debugging and break.
  2. The Debug menu is full of commands to step into, over and out of functions. You get a view showing local variables, and you can watch any variable in a watch window - as long as the optimizer has not thrown them out. You can set breakpoints and free-run until you hit them. Etc..

Now, the quality of your life has just increased by a substantial amount. (-:

Happy 75th anniversary to one of the best movies ever made! Rick Blane [Bogart]: "Of all the gin joints, in all the towns, in all the world, she walks into mine."

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

Well I didn't bother with any AVR malarky I used my PC and Geany to debug your code and found two mistakes:

1. Confusion over  struct tm * start_date; versus  struct tm start_date;

2. The 1900 year offset was missing.

 

Here's my corrected version:

 

void main (void)
{
	int16_t month = 6;
	int16_t day = 1;
	int16_t year = 2017;
	int16_t hour = 12;
	int16_t minute = 0;

	//Store date/time variables into a struct for use in the system time
	time_t unixtime;
	struct tm start_date;

	start_date.tm_year = year-1900;
	start_date.tm_mon = month - 1;
	start_date.tm_mday = day;
	start_date.tm_hour = hour;
	start_date.tm_min = minute;

	//Use the date for mk_gmtime
	unixtime = mktime(&start_date);

	//clear the lcd screen to display the date
	//~ lcdClear();
	//~ lcdPrintData(asctime(start_date), 16);

	//set the system time to user-defined time
	//~ set_system_time(unixtime);

	//TEST
	//print the unixtime to show on the putty
	printUint32((uint32_t)unixtime, 10);
}

nigel@E6420:~/Project/AVRfreaks/randomPC$ /bin/sh ./geany_run_script.sh
1496314801

------------------
(program exited with code: 0)

 

Obviously I hacked printUint32 to print characters to stdout and replaced mk_gmtime with mktime for the PC.

 

I haven’t a clue why I get a leap second though.

 

Edit: I Should have clarified that mktime uses local time and my timezone is BST i.e. GMT+1.

 

Last Edited: Tue. Jul 18, 2017 - 08:04 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quick sanity check:  Take the same 32-bit value that you use as input to your function -- "unixtime".

 

Give it to sprintf() to construct a character string, and send that.

Give it to ultoa() to construct a character string, and send that.

 

How do those strings from "trusted" conversion sources compare to yours?

 

 

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

breadsticks wrote:
When trying to display the data serially through PuTTY, as a unixtime timestamp, it shows '377889856' instead of the expected '1496318400' for the date.
You did expect 1496318400? Have a look at the documentation. Epoch of the AVR-Libc implementation is Jan 1 2000, not 1970.

Stefan Ernst

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

sternst wrote:
You did expect 1496318400? Have a look at the documentation. Epoch of the AVR-Libc implementation is Jan 1 2000, not 1970.

LOL -- never thought to "look" there.  I plugged OP's 149... number into an online converter I've used in my own work and it came up with OP's test date.

 

#define UNIX_OFFSET   946684800

Difference between the Y2K and the UNIX epochs, in seconds. To convert a Y2K timestamp to UNIX...

1 long unix;

2 time_t y2k;

4 y2k = time(NULL);

5 unix = y2k + UNIX_OFFSET;

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.

Last Edited: Tue. Jul 18, 2017 - 08:06 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the responses!

 

JohanEkdahl wrote:

Avoid doing the conversion yourself, if possible. While not standard, many C toolchains support itoa(...).

Would using sprintf() be a suitable alternative to itoa()?

 

The part about the simulator is very true. I've been figuratively pulling my hair out with every bug I found since starting this project.

Thank you for the rundown on using the simulator.

 

N.Winterbottom wrote:

Well I didn't bother with any AVR malarky I used my PC and Geany to debug your code and found two mistakes:

1. Confusion over  struct tm * start_date; versus  struct tm start_date;

2. The 1900 year offset was missing.

 

When building the code on AS7, it noted the use of a pointer, so I included that.

I forgot about the offset! I knew I was missing something.

That is strange how you got a leap second in your implementation. My timezone is PST.

 

sternst wrote:

You did expect 1496318400? Have a look at the documentation. Epoch of the AVR-Libc implementation is Jan 1 2000, not 1970.

I did not think it would be a problem of the epoch implementation. Thank you for pointing it out!

 

theusch wrote:

sternst wrote:
You did expect 1496318400? Have a look at the documentation. Epoch of the AVR-Libc implementation is Jan 1 2000, not 1970.

LOL -- never thought to "look" there.  I plugged OP's 149... number into an online converter I've used in my own work and it came up with OP's test date.

 

#define UNIX_OFFSET   946684800

Difference between the Y2K and the UNIX epochs, in seconds. To convert a Y2K timestamp to UNIX...

1 long unix;

2 time_t y2k;

4 y2k = time(NULL);

5 unix = y2k + UNIX_OFFSET;

 

This makes sense! Though, I wonder what implementation between yours and N.Winterbottom's should I use?

 

 

And going back to the other question I had, would using my timer counter be fine to use to 'tick' the system time? Or would it lose accuracy over the course of a day or more?

Last Edited: Tue. Jul 18, 2017 - 08:52 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

breadsticks wrote:
And going back to the other question I had, would using my timer counter be fine to use to 'tick' the system time? Or would it lose accuracy over the course of a day or more?

You will always have some inaccuracy, regardless of technique. E.g. your time source (e.g crystal) will not be perfect. With that said, the jitter or drift introduced using a crystal and interrupt driven "ticking" will be better than most other approaches. If you use a separate RTC chip they often have a tweaking function to compensate for inaccuracies from temperature and/or voltage differences, but even they will not be perfect.

 

A reasonable setup should give you a drift of a few seconds per day: Lets say crystal accuracy is 50 ppm. 86400 seconds per day. 86400 / (50 / 1000000) = 4.32 seconds.

 

A compensating algorithm or a separate RTC chip could reduce that further. 

Happy 75th anniversary to one of the best movies ever made! Rick Blane [Bogart]: "Of all the gin joints, in all the towns, in all the world, she walks into mine."

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

Specifically on the pointer issue, I'm talking about this extract:

//Store date/time variables into a struct for use in the system time
time_t unixtime;
struct tm * start_date;

start_date->tm_year = year;
start_date->tm_mon = month - 1;
start_date->tm_mday = day;
start_date->tm_hour = hour;
start_date->tm_min = minute;

Here you define the pointer start_date but fail to initialise it; the 5 members you write end up in random memory somewhere.

Actually I'm sure avr-gcc would have issued a warning for this one.

 

Last Edited: Wed. Jul 19, 2017 - 02:54 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

N.Winterbottom wrote:
Actually I'm sure avr-gcc would have issued a warning for this one.
But apparently only with -Wall....

C:\SysGCC\avr\bin>avr-gcc -mmcu=atmega16 -Os avr.c -o avr.elf

C:\SysGCC\avr\bin>avr-gcc -mmcu=atmega16 -Os avr.c -o avr.elf -Wall
avr.c: In function 'main':
avr.c:7:9: warning: unused variable 'unixtime' [-Wunused-variable]
  time_t unixtime;
         ^
avr.c:10:22: warning: 'start_date' is used uninitialized in this function [-Wuninitialized]
  start_date->tm_year = year;
                      ^

 

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

clawson wrote:
But apparently only with -Wall...

Apologies to Pink Floyd.

We don't need no -Wunused-variable
We don't need no -Wuninitialized
No dark sarcasm in the forum
Posters leave them lines alone
Hey! Posters! Leave them lines alone
All in all it's just another brick in the -Wall
All in all you're just another brick in the -Wall

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: 1

Thanks, I needed that!yesLOL!

 

Edit: Now, if I can only get that out of my head so I can do "real" work...

David (aka frog_jr)

Last Edited: Wed. Jul 19, 2017 - 03:18 PM