Need advice on keeping track of time

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

I'm working on some code that has what I call a 'system tick' that increments via an interrupt every 10ms. I built my code around this concept, resetting it to 0 in the main loop every time I needed to keep track of time without stopping code execution.

I've been told that using a volatile variable that gets reset to 0 by everyone and their mother is not the best way to do things. I can't come up with a better way to do it. Can anyone point me in the right direction?

Here's a peek at the code with most of the extra stuff removed:

//System
volatile unsigned char systick = 0;


/*--------------------------------------------------------------------------
	FUNC: 7/23/10 - Delay for n milliseconds
	PARAMS: Delay length in milliseconds
	RETURNS: NONE
	NOTES: This is a blocking function.  Why in the delay, the only other code
		that can execute is in interrupt handlers.
--------------------------------------------------------------------------*/
void delay_ms(unsigned int n)
{
  systick = 0;
  while (systick < n/10);
  systick = 0;
}

/*--------------------------------------------------------------------------
	FUNC: 7/23/10 - Blinks LED in the doorbell
	PARAMS
		1: # of times to blink
		2: Duration in milliseconds
	RETURNS: NONE
	NOTES: This is a blocking function.  When the LED blinks, the only other code
		that can execute is in interrupt handlers.
--------------------------------------------------------------------------*/
void blink_LED(unsigned char times, unsigned int duration_ms)
{
  //while (times--)
  for (unsigned char i=times; i>0; i--)
  {
    LED_PORT |= 1<<LED0;
    delay_ms(duration_ms);
    LED_PORT &= ~(1<<LED0);
    if (i >= 1) delay_ms(duration_ms);
    else systick=0;
  }
}

//--------------------------------------------------------------------------
int main(void)
{
  init_timers();
  init_io();
  unsigned char state = STATE_REST;

  for (;;)
  {
		switch(state){
			case STATE_REST :
        if( get_key_press( 1<<KEY0 ))
        {
					//Set entry index and array data to zero
          initialize_entry_code();
	  			//Set state to STATE_ENTRY
          state = STATE_ENTRY;
          //blink LED once (also resets systick)
					blink_LED(1,600);
          systick=0;
        }
        else if (get_key_press( 1<<KEY1 ))
        {
          //Programming button has been pushed
          state = STATE_PROGRAM_WAIT;
          system_timeout = 0;
          initialize_entry_code();
        }
			break;

			case STATE_PROGRAM_WAIT:
	      //Waiting for code entry.
	      if( get_key_press( 1<<KEY0 ))
	      {
	        state = STATE_PROGRAM;
	        blink_LED(1,600);	//signal that we're ready for first entry
	        systick=0;
	      }
	      //Flash until user button is pushed
	      else if ( systick > 50 )
	      {
	        LED_PORT ^= 1<<LED0;
	        systick = 0;
	        if (++system_timeout > 240) state = system_reset(state);	//We've been idle for 2 minutes so reset the system.
	      }

			case STATE_PROGRAM:
			case STATE_ENTRY:
      	//Has entry timed out?
      	if (systick > 150)
      	{
        	//Make sure a number was entered 
        	if(entry_code[entry_index])
          	{      
          		//check if that was the final digit 
            	if(++entry_index>3)
            	{
            	  check_code(state);
            	  state = STATE_REST;
            	} 
            	//Confirm with a blink (resets systick)
            	else blink_LED(1,600);
          	}
        	//current code is 0 which is not allowed, reset the system
        	else
        	{
          	state = system_reset(state);
        	}
      	}
      	else if( get_key_press( 1<<KEY0 ))
      	{
        	//increment digit to array
        	++entry_code[entry_index];
        	//reset systick because button was just pushed
        	systick = 0;
      	}
    	break;
		}
  }
}

//--------------------------------------------------------------------------
ISR(TIM0_OVF_vect)           // every 10ms
{
  //other stuff goes here

  ++systick;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I've been told that using a volatile variable that gets reset to 0 by everyone and their mother is not the best way to do things.

Was that discussion here at 'freaks? If so, could you point to that thread so that we don't do the same exercise again?

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"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

What exactly do you mean "keep track of time"?

There are (at least) a couple of ways you can approach this. First, let's specify a 10ms tick and a 16-bit counter (giving 65 seconds max duration - if you need more you'll have to extend the concept).

Method 1 just increments the 16-bit counter every tick, and every tick interrupt you also check for equality with one or more "timeout" variables, and set flags accordingly (in an RTOS you'd potentially make tasks runnable, but that's a whole 'nother level).

Method 2 uses the same tick ISR but this time decrements "timeout" variables until they get to 0 and then sets the flags. The amount of code is about the same as method 1.

To have repetitive events you would just add the time value to the variable in method 1 (ignoring any rollover), or re-store the time value in method 2. In this case method 1 is more bullet-proof since even if one or more additional ticks pass before you get around to reloading the timeout variable your timing will not slip in method 1, but it will slip in method 2. For that reason I'd use method 1.

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

@JohanEkdahl: Nope, separate conversation with a person who went way out of their way to help... I didn't want to tread on his good will by asking him for even more help.

@kk6gm: Your comment about "ignoring any rollover" was interesting. I've been avoiding rollover by resetting the global variable to zero whenever I need to start keeping track of time. How can I use variable overflow to my advantage?

In my mind if I'm waiting for a variable to reach or exceed a certain value I could get a false positive when the variable overflows because I'm using the greater than operator:

//don't execute until 1 second has passed
unsigned char my_time = systick + 100;
if (systick >= my_time) {
  //this will execute after 1 second
}

ISR() {
  //This interrupt fires every 10ms
  ++systick;
}

Am I missing something?

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

No, you're not missing anything. If you don't check the timers (the 16-bit software variables that count up or down in the ISR) in the ISR itself, you could miss an == condition. You will, of course, never miss that condition in the ISR itself. So you cannot rely on a >= (or ==) test of the software timer value in mainline code if your timers are rolling over.

But what you can do is set a flag in the ISR upon match, and the test for that flag in mainline code will always yield a correct result, no matter how many additional ticks may pass before you get around to checking the flag.

Note that what you are essentially doing here is mimicking a hardware timer, which will capture a match condition and set a corresponding hardware flag (it may also be configured to generate an interrupt, but we'll ignore that for this discussion). Again, the benefit here is in being able to have repetitive events without worrying about any time slipping. Here's a sample for 10ms hardware tick and 1 second software "tick":

// 10ms ISR
...
if (++systick == timer_var)
  timer_flag = 1;

// mainline code
...
if (timer_flag)
{
  timer_flag = 0;
  timer_var += 100;  // set next second timer, rollover is OK
  ... execute 1-second code
}

Keep in mind that any variables shared between ISR and mainline code should be declared volatile to make sure the optimizer doesn't foul up the multiple accesses.