Go Down

Topic: Another way to do periodic timing (Timer2) instead of millis() (Read 2239 times) previous topic - next topic

mk3

hi all,

This is not a project, but rather a bit of working code you might find useful.  I am building data acquisition modules and needed only a relatively slow data rate.  I have been bothered by serial data getting lost and decided to look for a more efficient way to trigger my 1 time per second reporting.  I could be wrong, but I just had the idea that the millis() function would be some overhead I could dump.

Timer2 has a phase-correct PWM mode in which the timer counts UP and then DOWN before overflowing so it slows things down a bit - just what I wanted!  It also turned out to be not so difficult to get the timing super-close to true 1second intervals with the following formula

1024 prescaler
252 count to interrupt
2 Timer2 counts UP then DOWN before triggering overflow
Count 31 overflows to get ~1 second
(1024*252*2*31)/16000000 = .999936 seconds - less than .01% error

This is so close to the Arduino millis() function that I decided to test it and I think just by chance this substitute was closer to real time than millis() over a 20 minute period... but both are close enough for my purposes.

Here is the sample code - it puts out the Binary values of the registers upon reboot and then it sends out both time values one time per second so one can see how they compare.  From what I have read, Timer2 is used in some of the PWM (analog output) but this does not matter for my projects which are only data acquisition.  Here's the code.
Code: [Select]
//=========================================================================!
/*
  filename Timer2_999_94ms
  //1024 prescaler
  //252 count to interrupt
  //2 Timer2 counts UP then DOWN before triggering overflow
  //Count 31 overflows to get ~1 second
  //(1024*252*2*31)/16000000 = .999936 seconds
 
  Reference of other Arduino items that use Timer2
   Tone
   PWM
   ?
*/

//macros for setting bits to make setting bits more clear

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

//Count to this number of interrupts, then DO the action
# define howManyInterrupts  31 

#define bps 9600

/*
to allow resetting the millis() timer to 0
This is only an example as this code is intended
as a possible substitute for millis() in some
specific cases
*/
extern volatile unsigned long timer0_millis;

int int_counter = 0;
unsigned long time_ms=903;  //Can start at 0, but starting at 903 matches millis()

#define increment 1000  //The timer inc is 999.92 but 1000 is close enough
#define ledPin 13       // hard code the pin number for the LED

boolean timetoprint = false;


void setup() {               
  // initialize the digital pin as an output.
  // Pin 13 has an LED connected on most Arduino boards:
  pinMode(ledPin, OUTPUT);   

  Serial.begin(bps);

  setupTimer2();

  //Prints out the setup for your curiosity
  Serial.println();
  Serial.print("TCCR2A  B");
  Serial.println(TCCR2A,BIN);
  Serial.print("TCCR2B  B");
  Serial.println(TCCR2B,BIN);
  Serial.print("TIMSK2  B");
  Serial.println(TIMSK2,BIN);
  Serial.print("OCR1A  B");
  Serial.println(OCR1A,BIN);
  Serial.print("OCR2A  B");
  Serial.println(OCR2A,BIN);
  Serial.print("ASSR  B");
  Serial.println(ASSR,BIN);
  Serial.println();
  Serial.println("millis(),Timer2 millis");

}

void loop() {

  if (timetoprint){

    timetoprint=false;

    Serial.print(millis());
    Serial.print(',');
    Serial.println (time_ms);
    time_ms=time_ms+increment;

  }
  else{

    if (Serial.available()>0){

      unsigned char in = Serial.read();

      switch(in)  //just an example of reading some serial commands
      {
      case 'r': //reset the time to 0 ( for data acquisition it's nice to reset time)
        time_ms=0;
        timer0_millis=0;
        break;
      default:
        Serial.println("not recognized");
      }//switch
    }
  }
}

/*
TCCR2A  B00000001
TCCR2B  B00001111
TIMSK2  B00000001
OCR1A   B00000000
OCR2A   B11111100
ASSR    B00000000
*/

void setupTimer2 (){

  //set up timer2

    TIMSK2 = 0 ;  //We do NOT Generate interrupts

  cbi(ASSR, AS2);   //// use clock, not T2 pin .. probably defaulted anyway

  /*When the value of AS2 is changed, the contents of TCNT2, OCR2A,
   OCR2B, TCCR2A and TCCR2B might be corrupted. :8271C-AVR-08/10 p165*/

  //Clear the Timer Registers - now we know what we have
  TCCR2B = 0; 
  TCCR2A = 0;
  TCNT2 = 0;

  /*TCCR2A contains the compare match mode and part of the wave generation
   mode bits
   */
  sbi(TCCR2A, WGM20);   //WGM20 is bit 1

  /*TCCR2B contains the prescaler and the remainer of the wave generation
   mode bits.  Results in Waveform generation mode 5 on Table 17-8 of the
   datasheet.  However, we are not actually generating a waveform.
   */

  sbi(TCCR2B, WGM22);   //WGM22 is bit 1

  //Timer2 Settings:  Timer Prescaler /1024
  sbi(TCCR2B,CS22); //set this bit
  sbi(TCCR2B,CS21); //set this bit
  sbi(TCCR2B,CS20); //set this bit

  //Timer2 Overflow Interrupt Enable
  /*
   Bit 0 - TOIE2: Timer/Counter2 Overflow Interrupt Enable
   When the TOIE2 bit is written to one and the I-bit in the Status Register
   is set (one), the Timer/Counter2 Overflow interrupt is enabled. The
   corresponding interrupt is executed if an overflow in Timer/Counter2
   occurs, i.e., when the TOV2 bit is set in the Timer/Counter2 Interrupt
   Flag Register - TIFR2.
   */

  OCR2A = 252;       //252 results in a 1000ms period. 63 results in 250ms
 
 
  sbi(TIMSK2,TOIE2); //enable the timer to raise overflow interrupts

}


ISR(TIMER2_OVF_vect)
{

  /*This interrupt fires once every
   32.64 milliseconds
   The counter has to count UP and then it counts back down due to the
   way the registers are set.
   16MHz / (1024*(255*2)) = 30.63725 interrupts per second ==>(1012 ms period)
   16MHz / (1024*(252*2)) = 31.00198413 interrupts per second
   */
  int_counter += 1;
  if (int_counter == howManyInterrupts)
  {
    int_counter = 0;
    timetoprint=true;

  }
};


I tested this on a Nano and a Mega
of course I got some help to start this from other posts on this forum and others so I am sorry if I have repeated something
obvious.  The code isn't cut/paste so I couldn't figure out if anyone else should be cited... if so just let me know.

UnaClocker

Great information.. But where did you get the 16000000 that you divide by?
Brian from Tacoma, WA
Arduino evangelist - since Dec, 2010.


UnaClocker

Brian from Tacoma, WA
Arduino evangelist - since Dec, 2010.

Go Up