1ms accuracy timer

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

Hi Guys, using a mega328p and trying to setup a timer to record 6 timings cumulatively.
Below is my code, but its seriously either wrong or just extremely inaccurate - I am using internal 8Mhz timer1 and internal 8Mhz RC osciallator + 65ms:

ISR(TIMER1_COMPA_vect) 
{ 
   timer_tick++;      //stopwatch timer 
} 


void timerInitialize(void){ 
    // set up timer with prescaler = 64 and CTC mode 
    TCCR1B |= (1 << WGM12)|(1 << CS11)|(1 << CS10);  

    // initialize counter 
    TCNT1 = 0; 

    // initialize compare value 
    OCR1A = 124;//1000Hz w/ 64 prescaler 

    // enable compare interrupt 
    TIMSK1 |= (1 << OCIE1A); 

    // enable global interrupts 
   sei(); 
} 

void timingFunction (void) 
{ 
uint8_t j; 
float timeArray[6]; 
ToggleLED(); 

 while (!(~PINB&(1<<button)));  // loop here until first button press 
   _delay_ms(50);  // delay for 50 ms to debounce button press 
   timer_tick=0; // here is where we start timing 
    while (~PINB&(1<<button));  // loop here until button is released 
    _delay_ms(50);  // delay for 50 ms to debounce button release 

   for (j=0;j<6;j++){ 

   ToggleLED(); 
    while (!(~PINB&(1<<button)));  
    _delay_ms(50);  
   cli(); 
   timeArray[j] = (float)timer_tick/1000;      //get the value of the millisecond counter 
    sei(); 

 while (~PINB&(1<<button));  // loop here until button is released 
 _delay_ms(50);  
    } 
   cli(); 
   send(&timeArray[0], 6); //pointer to array and array size
   sei(); 
} 

Could it just be that it is really inacurate due to the internal osciallator? I was trying to time 30 seconds, taking a reading every 5 seconds, checking against my watch.
Thanks

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
    TCCR1B |= (1 << WGM12)|(1 << CS11)|(1 << CS10); 
    // initialize counter
    TCNT1 = 0;
    // initialize compare value
    OCR1A = F_CPU/64/1000 - 1; //1000Hz w/ 64 prescaler

Yes. Your sums are correct. (assuming that you don't have CKDIV8 fuse)
The interrupt should be reasonably accurate. Yes, the factory calibration is normally very good.

However, you disable interrupts during the send() function. And (I know) that your send() function waits for an ACK from your RX device.

Why use sei() ?

David.

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

May I ask why you complicate things by using floats?
Instead of counting 0.001sec to 1sec you can just use 16bit unsigned int (uint16_t) and count 1ms to 1000ms.

A uint16_t would allow a count up to 65535 ms , if you want more than that you can always use a uint32_t that can count up to 4294967295 ms

Alex

"For every effect there is a root cause. Find and address the root cause rather than try to fix the effect, as there is no end to the latter."
Author Unknown

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

Yea thats a good point about the float, I can just send it as ms, and convert back (for maths) the other side.

David, Im not sure what your question relates to, I disable interrupts during sending, because I didnt want the interupts interupting the send routine. Im figuring however you think I should just not disbale and renable interupts around the send routine.

BTW last time we spoke you weren't impressed with my timing routine - i.e how Im collecting the timings. I am now starting the time keeping variable at -50 to account for the button debounce delay.

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

There is seldom any need to disable interrupts. Generally only while you access a 16-bit or 32-bit variable. e.g. reading timer_tick

Then you only need

    cli();
    var16 = timer_tick;
    sei();
    ...
    // perform math on var16

A more general solution is:

    uint8_t sreg = SREG;
    cli();
    // perform atomic operation(s)
    ...
    SREG = sreg;

avr-gcc even has a special header file to cope with atomic operations.

Why don't you say 'what' you want to send?
I really can't believe that you intend to sit pressing a button slowly for seven times as a realistic application.

David.

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

Need to read about atomic stuff then, I dont quite get that.
Im just trying to build a stopwatch with a lap timer feature, it was originally to time a pendulum arm swinging with light gates (not me pressing buttons, thats just my development method, rather than having to have a pendulum on my desk), but I decided I wanted to use the opportunity to learn a few more things along the way, like wireless communication and writing to sd cards...

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

In addition to using atomic access as David wrote above, you should declare timer_tick volatile.

Sid

Life... is a state of mind

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

Quote:

Need to read about atomic stuff

In the tutorial forum:

The traps when using interrupts

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

Yeah it is volatile, thanks.
Right I get it, its similiar to calling cli(), doing your stuff, then calling sei(0 to turn interrupts back on (but I assume more efficient or something)
This is a simple example of using it:

#include 
#include 
#include 
#include 

volatile uint16_t ctr;

ISR(TIMER1_OVF_vect)
{
  ctr--;
}

...
int
main(void)
{
   ...
   ctr = 0x200;
   start_timer();
   sei();
   uint16_t ctr_copy;
   do
   {
     ATOMIC_BLOCK(ATOMIC_FORCEON)
     {
       ctr_copy = ctr;
     }
   }
   while (ctr_copy != 0);
   ...
}

But would you put other code inside that do loop awsell?, seems strange to use a do loop.

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

Why have you used a loop?
The only purpose of the atomic is to ensure that you get the correct result when your ctr goes from 255 to 256. It is possible that the interrupt occurs between the lo-byte going to zero and before the high byte is incremented.

In your case, you read your 'system timer' atomicaly, and copy to your working variable. Anyway, the odds of an interrupt occurring at the 'inconvenient' time are pretty low. e.g. 1 in 1024000.

David.

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

david.prentice wrote:
The only purpose of the atomic is to ensure that you get the correct result when your ctr goes from 255 to 256.

In general, there are more cases that are taken care of with this. Decrementing from 0, incrementing from 65535, decrementing from 256, incrementing from 255, doing any other sort of arithmetic on the variable, assigning to the variable, reading from the variable.

In the "simple example" case at hand, it deals with reading the counter when it goes from 0 to 65535 and from 256 to 255.

Sid

Life... is a state of mind

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

a.mlw.walker wrote:
But would you put other code inside that do loop awsell?, seems strange to use a do loop.

You need to understand what the example intends to do. It is intended to loop until the counter has been decremented 200 times. Then it exits the loop.

It's a bad design as it may loop forever if the timer frequency is high enough. There is no guarantee that a counter value of 0 is ever read - it could be that only 1 and 65535 is read, for instance.

Sid

Life... is a state of mind