/*
Timer2 Counter Basic Example
-A timer function with 0.5us precision, rather than 4us precision like the built-in Arduino micros() function has.
By Gabriel Staples
Visit my blog at http://electricrcaircraftguy.blogspot.com/
-My contact info is available by clicking the "Contact Me" tab at the top of my blog.
-Please support my work & contributions by buying something here: https://sites.google.com/site/ercaguystore1/
My original post containing this code can be found here: http://electricrcaircraftguy.blogspot.com/2014/02/Timer2Counter-more-precise-Arduino-micros-function.html
Written: 8 Feb. 2014
Updated: 9 Feb. 2014
*/
/*
===================================================================================================
LICENSE & DISCLAIMER
Copyright (C) 2014 Gabriel Staples. All right reserved.
This code was written entirely at home, during my own personal time, and is neither a product of work nor my employer.
Furthermore, unless otherwise stated, it is owned entirely by myself.
------------------------------------------------------------------------------------------------
License: GNU General Public License Version 3 (GPLv3) - http://www.gnu.org/licenses/gpl.html
------------------------------------------------------------------------------------------------
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/
===================================================================================================
*/
//CODE DESCRIPTION:
//This code demonstrates the use of my Timer2, which provides a more precise timer than micros().
//micros() has a precision of only 4us. However, Timer2 keeps track of time to a precision of 0.5us.
//This is especially important in my code which reads an RC receiver PWM signal, which varies from 900~2100us.
//Though this code demonstrates the use of the Timer_2 functions I have written, it does not adequately demonstrate the
//real utility of the code, so I will state the following:
//By using my Timer2 timer to measure the PWM high time interval on an RC receiver, in place of using micros(), I can get repeatable
//pulse width reads with a fluctuation of ~1us, rather than having a read-in range fluctuating by as much as +/- 4~8 us when I use micros().
//This is an increase in precision of ~8x.
void setup() {
//configure Timer2
setup_T2(); //this MUST be done before the other functions work; Note: since this messes up PWM outputs on pins 11 & 3,
//you can always revert Timer2 back to normal by calling unsetup_T2()
//prepare serial
Serial.begin(115200);
//Output a header of info:
Serial.println("Notes:");
Serial.println("micros() has a precision of 4us");
Serial.println("get_T2_count() with unsigned long final data type has a final precision of 1us, and is fast");
Serial.println("get_T2_count() with float final data type has a final precision of 0.5us, and is not quite as fast");
Serial.println("get_T2_micros() has a precision of 0.5us, and is slower than the above 2 methods, so one of the above 2 methods is preferred");
Serial.println("==============================================");
}
void loop() {
//Grab Start Times
unsigned long t_start1 = micros(); //us; get the current time using the built-in Arduino function micros(), to a precision of 4us
unsigned long t_start2 = get_T2_count(); //count units of 0.5us each; get my Timer2 count, where each count represents 0.5us; PREFERRED METHOD
float t_start3 = get_T2_micros(); //us; get the current time using my Timer2; Note: THE METHOD ONE LINE ABOVE IS PREFERRED OVER THIS METHOD
//since using floats is slower than using unsigned longs
//Wait a bit
delayMicroseconds(2000);
//Grab End Times
unsigned long t_end1 = micros(); //us; using built-in Arduino function that has a precision of 4us
unsigned long t_end2 = get_T2_count(); //count units of 0.5us each; using my Timer2 count, where each count represents 0.5us
float t_end3 = get_T2_micros(); //us; using my Timer2 micros, which has a precision of 0.5us
//Calculate elapsed times
unsigned int t_elapsed1 = t_end1 - t_start1; //us; using micros()
unsigned int t_elapsed2_ul = (t_end2 - t_start2)/2; //us; to a precision of 1us, due to using unsigned long data type truncation, using Timer2 count
float t_elapsed2_fl = (t_end2 - t_start2)/2.0; //us; to a precision of 0.5us, due to using float data type for final time difference calc; note that I divide by 2.0, NOT 2
float t_elapsed3 = t_end3 - t_start3; //us; to a precision of 0.5us
//Display the results
Serial.println(""); //insert a space
Serial.print(get_T2_micros());
Serial.println("micros ->"); //insert a space
Serial.println(micros());
//Wait a second before repeating
delay(1000);
}
//Set up Global Variables
//volatile (used in ISRs)
volatile unsigned long T2_overflow_count = 0; //initialize Timer2 overflow counter
volatile unsigned long T2_total_count = 0; //initialize Timer2 total counter
//normal variables
byte tccr2a_save; //initialize; will be used to backup default settings
byte tccr2b_save; //initialize; will be used to backup default settings
//Interrupt Service Routine (ISR) for when Timer2's counter overflows; this will occur every 128us
ISR(TIMER2_OVF_vect) //Timer2's counter has overflowed
{
T2_overflow_count++; //increment the timer2 overflow counter
}
//Configure Timer2
void setup_T2()
{
//backup variables
tccr2a_save = TCCR2A; //first, backup some values
tccr2b_save = TCCR2B; //backup some more values
//increase the speed of timer2; see below link, as well as the datasheet pg 158-159.
TCCR2B = TCCR2B & 0b11111000 | 0x02; //Timer2 is now faster than default; see here for more info: http://playground.arduino.cc/Main/TimerPWMCheatsheet
//Note: don't forget that when you speed up Timer2 like this you are also affecting any PWM output (using analogWrite) on Pins 3 & 11.
//Refer to the link just above, as well as to this source here: http://www.oreilly.de/catalog/arduinockbkger/Arduino_Kochbuch_englKap_18.pdf
//Enable Timer2 overflow interrupt; see datasheet pg. 159-160
TIMSK2 |= 0b00000001; //enable Timer2 overflow interrupt. (by making the right-most bit in TIMSK2 a 1)
//TIMSK2 &= 0b11111110; //use this code to DISABLE the Timer2 overflow interrupt, if you ever wish to do so later. (see datasheet pg. 159-160)
//set timer2 to "normal" operation mode. See datasheet pg. 147, 155, & 157-158 (incl. Table 18-8).
//-This is important so that the timer2 counter, TCNT2, counts only UP and not also down.
//-To do this we must make WGM22, WGM21, & WGM20, within TCCR2A & TCCR2B, all have values of 0.
TCCR2A &= 0b11111100; //set WGM21 & WGM20 to 0 (see datasheet pg. 155).
TCCR2B &= 0b11110111; //set WGM22 to 0 (see pg. 158).
}
//get total count for Timer2
unsigned long get_T2_count()
{
// REM noInterrupts(); //prepare for critical section of code
cli();
uint8_t tcnt2_save = TCNT2; //grab the counter value from Timer2
boolean flag_save = bitRead(TIFR2,0); //grab the timer2 overflow flag value
if (flag_save) { //if the overflow flag is set
tcnt2_save = TCNT2; //update variable just saved since the overflow flag could have just tripped between previously saving the TCNT2 value and reading bit 0 of TIFR2.
//If this is the case, TCNT2 might have just changed from 255 to 0, and so we need to grab the new value of TCNT2 to prevent an error of up
//to 127.5us in any time obtained using the T2 counter (ex: T2_micros). (Note: 255 counts / 2 counts/us = 127.5us)
//Note: this line of code DID in fact fix the error just described, in which I periodically saw an error of ~127.5us in some values read in
//by some PWM read code I wrote.
T2_overflow_count++; //force the overflow count to increment
TIFR2 |= 0b00000001; //reset Timer2 overflow flag since we just manually incremented above; see datasheet pg. 160; this prevents execution of Timer2's overflow ISR
}
T2_total_count = T2_overflow_count*256 + tcnt2_save; //get total Timer2 count
// REM interrupts(); //allow interrupts again
sei();
return T2_total_count;
}
//get the time in microseconds, as determined by Timer2; the precision will be 0.5 microseconds instead of the 4 microsecond precision of micros()
float get_T2_micros()
{
float T2_micros = get_T2_count()/2.0;
return T2_micros;
}
//reset Timer2's counters
void reset_T2()
{
T2_overflow_count = 0; //reset overflow counter
T2_total_count = 0; //reset total counter
TCNT2 = 0; //reset Timer2 counter
TIFR2 |= 0b00000001; //reset Timer2 overflow flag; see datasheet pg. 160; this prevents an immediate execution of Timer2's overflow ISR
}
//undo configuration changes for Timer2
void revert_T2_to_normal()
{
T2_overflow_interrupt_off(); //turn off Timer2 overflow interrupts
TCCR2A = tccr2a_save; //restore default settings
TCCR2B = tccr2b_save; //restore default settings
}
//same as revert_T2_to_normal()
void unsetup_T2()
{
revert_T2_to_normal();
}
//Turn off the Timer2 Overflow Interrupt
void T2_overflow_interrupt_off()
{
// TIMSK2 &= 0b11111110; //use this code to DISABLE the Timer2 overflow interrupt; see datasheet pg. 159-160
TIMSK2 &= ~(_BV(TOIE2)); //alternate code to do the above; see here for use of _BV: http://194.81.104.27/~brian/microprocessor/BVMacro.pdf
}
//Turn the Timer2 Overflow Interrupt Back On
void T2_overflow_interrupt_on()
{
// TIMSK2 |= 0b00000001; //enable Timer2 overflow interrupt. (by making the right-most bit in TIMSK2 a 1); see datasheet pg. 159-160
TIMSK2 |= _BV(TOIE2); //alternate code to do the above; see here for use of _BV: http://194.81.104.27/~brian/microprocessor/BVMacro.pdf
}