Sunday, February 9, 2014

Arduino micros() function with 0.5us precision - using my Timer2_Counter Library

Advertisement

Subscribe by Email! (via FeedBurner)
...merging the world of Arduino and Radio Control, one tool at a time...
...CODE FOR A PRECISE MICROS() FUNCTION IS POSTED BELOW...

"I wrote a libary to get 0.5us precision on a "micros()" replacement function, so that I can get repeatable results reading a PWM or PPM signal, to within 1us.  I searched all around the internet and could not find something comparable (or that was easy to use, and maintained the Arduino's ability to write PWM signals via the Servo Libary), so I think this is my first real contribution to the world of Arduino and Radio Control."  

(font above is type "standard," from here)

Download this library:

Donate & download via the link just above. Install the library into the Arduino IDE (using Sketch --> Include Library --> Add .ZIP Library), then run the examples to see how to use the library.

By Gabriel Staples
Written: 9 Feb. 2014
Last Updated: 4 Aug. 2015

History (newest on top)
-slight re-arrangement of a few bits of info - 8 May 2015
-changed license from GPL to LGPL V3 or later; added a new example demonstrating reading in ANY pulsing signal (to measure high pulse width and frequency), INCLUDING RC PWM signals, using Pin Change Interrupts! The example may be fundamentally quite complicated, but I assure you it is very easy to use. Download the latest version of code to see this new example. - 21 March 2015
-added PPM/PWM links at very bottom of post - 26 Feb. 2014
-moved code from Google Drive to GitHub, & updated download links - 12 April 2014
-I have now turned this into a full-blown, true Arduino library!  Now you can actually install it like a normal Arduino library, and use the #include <eRCaGuy_Timer2_Counter.h> line and everything!  Download the latest code by clicking the download link - 17 May 2014
-26 July 2014 - minor changes to function names (removed the "T2" parts since I made that change in my library several months back)

========================================================================
Please support my work and contributions by using my code and sharing links to my website! Also, *subscribe* to receive immediate notification of new articles, by using the links at the top-right!
========================================================================

Related Articles:

Timer2_Counter Library:

In order to interface Arduino with the world of Radio Control, I need to be able to interpret and even duplicate the signals which Radio Control vehicles use.  These signals include Pulse Position Modulation (PPM) and Pulse Width Modulation (PWM).  I will not go into the details of how these signals work, but I will say that I have spent a great deal of time learning about them to a very detailed level.  This way, I can have the knowledge to read and manipulate them.

For those of you who don't know: the signal coming out of the trainer port, or going into the trainer port, on the back of an RC transmitter (Tx), is a PPM signal.  The signal coming out of a receiver (Rx) and to a servo or Electronic Speed Conroller (ESC) is a PWM signal.

If an Arduino can read the PPM signal coming out of the back of a Tx, this is EXTREMELY useful in debugging your radio and setup by allowing you to see what pulse widths your Tx is outputting for each channel.  Additionally, it allows you to use your RC Tx as an INPUT DEVICE to control an Arduino!

You now have access to all these analog and digital inputs (sticks, scroll wheels, switches, etc), to manipulate your Arduino!  And, since many RC hobbyists (like me) have many cheap radios from RTF (Ready to Fly) kits and things lying around, this is very useful so that we can finally put them to use doing something else.  For instance, I plan on using one of my old radios, with an Arduino nano and a Leonardo-compatible device, to act as a wireless mouse for controlling my wife's computer when connected to the TV.  Then, I can sit on the couch and control the screen with an RC Tx!

If an Arduino can create a PPM signal to go into the trainer port on the back of a Tx, this is EXTREMELY useful because it allows you to use the Arduino to drive your RC vehicle via Feed-Forward loops (ie: you can preprogram commands in the Arduino, like "go forward 20 feet, then turn left 45 deg," or "do continuous figure 8's."

Similarly, putting your Arduino in at the Rx end is also very useful, because you can create your own custom mixes to manipulate your servos or speed controllers, or you can create or modify an autopilot.  Also, knowing how to read the PPM and PWM signals from a receiver also enables you to use a standard RC Tx/Rx combination as a wireless communication device for your Arduino!  You can even send data from one Arduino to another, over open-air distances up to 1km, using this technique, again, if you understand the signals well enough.  This makes RC and Arduino a very powerful combination, and to the Arduino user: I should note that many older 72MHz RC Tx/Rx combos exist out there that are being sold for ~$20 used by Radio Control hobbyists who are transitioning to the newer 2.4GHz spread-spectrum systems.  If you see a 72MHz system for only a few bucks, grab it!  It's a great tool for your Arduino.

Now...on to the title of this post.  In learning to manipulate and read PWM and PPM signals, it quickly became apparent to me that timing is critical.  I originally used the built-in Arduino micros() function for my timing, but it only has a resolution of 4us.  This is very poor, as it means that I get errors up to +/- 8us, over time (determined experimentally).  PWM and PPM signals usually range from 900~2100us max, but are more commonly 1100~1900us (this is default on a Spektrum brand Tx).  In your standard "computerized" (ie: microcontroller-run) RC Tx, when you change your "%" range or endpoint settings, each percent corresponds to 4us.  (This is calculated as follows: 1900-1100 = 800us, which is +/-100% of the standard travel, so 1500 to 1900us is the +100% portion, which covers 400us, and 1100 to 1500 is the -100% portion, which covers 400us.  Therefore, 400us/100% = 4us per 1%.)  Therefore, assuming an error span of 12us, that's a fluctuation in simply reading or writing a fixed channel value, of up to 3% as seen in the radio settings.

I didn't like that, so I wrote a "libary" to get 0.5us precision on a "micros()" replacement function, so that I can get repeatable results reading a PWM or PPM signal, to within 1us.  I searched all around the internet and could not find something comparable (or that was easy to use, and maintained the Arduino's ability to write PWM signals via the Servo Libary), so I think this is my first major contribution to the world of Arduino and Radio Control.

First, I will post an example code which shows how to use my Timer2_Counter "Library."  I have thoroughly commented all of my code.  You will see that I use micros() as a comparison, to show that my code does in fact work.

-Due to line wrap when I pasted the code on my blog, the formatting of the code below is not perfect.  When you view it in the Arduino IDE (Integrated Development Environment), however, it looks good.
Advertisement
Download:
Donate & download using the link at the top of this article.

Additional References to Get Started Learning about Radio Control PPM (Pulse Position Modulation) and PWM (Pulse Width Modulation) Signals:
  1. http://www.camelsoftware.com/firetail/blog/radio/reading-pwm-signals-from-a-remote-control-receiver-with-arduino/ - Reading PWM Signals from a Remote Control Receiver, with Arduino
  2. http://www.mftech.de/ppm_en.htm - PPM Encoding
  3. http://www.mftech.de/buchsen_en.htm - R/C buddy box plugs/ pin assignment

========================================================================
Please support my work and contributions by subscribing and sharing using the buttons at the top-right of my website!  Also be sure to share links to my articles with your friends.
========================================================================


The Code:

(Update: 27 March 2015: the below code is outdated. Download the latest version of the code using the links already mentioned above, and reference the included files, including the examples, to see what the code and functions look like from the latest version of this library!)

Function Definitions of Timer2_Counter:
-This is a list of the available functions, for a full description of each function, refer to the comments at the top of the "Timer2_Counter.ino" file.

  • setup();
  • get_count();
  • get_micros();
  • reset();
  • revert_to_normal();
  • unsetup();
  • overflow_interrupt_off();
  • overflow_interrupt_on();

1) Example Code


/*
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(1000);
  
  //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("elapsed time using micros() = ");
  Serial.print(t_elapsed1);
  Serial.println("us");
  Serial.print("elapsed time using get_T2_count() with unsigned long final data type = ");
  Serial.print(t_elapsed2_ul);
  Serial.println("us");
  Serial.print("elapsed time using get_T2_count() with float final data type = ");
  Serial.print(t_elapsed2_fl);
  Serial.println("us");
  Serial.print("elapsed time using get_T2_micros() = ");
  Serial.print(t_elapsed3);
  Serial.println("us");
  
  //Wait a second before repeating
  delay(1000); 
}


2) Timer2_Counter main "Library" file:
/*
Timer2 Counter
-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: 7 Dec. 2013
Last Updated: 11 Dec. 2013
*/

/*
===================================================================================================
  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:
BACKGROUND:
-This code uses Timer2 to create a more precise timer than micros().  Micros() updates only every 4us.  However, I want something that will update every 0.5us, and that's 
what this provides. 
-The downside is that it changes the behavior of PWM output (using analogWrite) on Pins 3 & 11.
-The upside is that I have managed to get a precise timer using the 8-bit Timer2, rather than the 16-bit Timer1, so that I can keep the Timer1 unmodified so I can continue
 to use the servo library as desired.  Note here that the servo library relies on the Atmega328 16-bit Timer1.  I have deciphered this by knowing that
 A) using the servo library at all disables PWM output on pins 9 & 10; see here: http://www.arduino.cc/en/Reference/Servo
 and B) PWM on pins 9 & 10 is controlled by Timer1; see here: http://playground.arduino.cc/Main/TimerPWMCheatsheet
-Note: every now and then, but not very often, you will see that the value returned by get_T2_count or get_T2_micros is late by ~4us.  This is because Timer2 is only
 an 8-bit timer, so every 128us the overflow interrupt is called to increment the overflow counter, and stepping into and out of the interrupt takes 4~5us
 according to Nick Gammon. Source: Nick Gammon; "Interrupts" article; "How long does it take to execute an ISR?" section, found here: http://www.gammon.com.au/forum/?id=11488
IMPLEMENTATION:
-To use this code, simply copy this file (I have it named "Timer2_Counter.ino") into your directory (folder) where you are already working on a specific Arduino file.  
Next time you open up your main file you are working on, this code will automatically open up as an additional tab in the Arduino IDE.  
-I have tested this code ONLY on Arduinos using the Atmega328 microcontroller; more specifically, the Arduino Nano.

I heavily reference the 660 pg. Atmega328 datasheet, which can be found here:  http://www.atmel.com/Images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet.pdf


Version History:
(most recent event last; format year-month-day, ex: 20131211 is Dec. 11, 2013):
20131211 - completed first iteration of code

============================================================================================================================================================================
Function Definitions
 ___  _   _  _  _   ___  _____  ___  ___   _  _      ___   ___  ___  ___  _  _  ___  _____  ___  ___   _  _  ___ 
| __|| | | || \| | / __||_   _||_ _|/ _ \ | \| |    |   \ | __|| __||_ _|| \| ||_ _||_   _||_ _|/ _ \ | \| |/ __|
| _| | |_| || .` || (__   | |   | || (_) || .` |    | |) || _| | _|  | | | .` | | |   | |   | || (_) || .` |\__ \
|_|   \___/ |_|\_| \___|  |_|  |___|\___/ |_|\_|    |___/ |___||_|  |___||_|\_||___|  |_|  |___|\___/ |_|\_||___/
                                                     
setup_T2();  //This function MUST be called before any of the other Timer2 functions will work.  This function will generally only be called one time in your setup() loop.
             //"setup_T2()" prepares Timer2 and speeds it up to provide greater precision than micros() can give.
get_T2_count();  //gets the counter from Timer 2. Returns the Timer2 counter value as an unsigned long.  Each count represents a time interval of 0.5us.
                 //note that the time returned WILL update even in Interrupt Service Routines (ISRs), so if you call this function in an ISR, and you want the time to be
                 //as close to possible as a certain event that occured which called the ISR you are in, make sure to call "get_T2_count()" first thing when you enter
                 //the ISR.
                 //Also, note that calling "get_T2_count()" is faster than calling "get_T2_micros()," and is therefore the preferable way to measure a time interval.
                 //For example: call "get_T2_count()" at the beginning of some event, then at the end.  Take the difference and divide it by 2 to get the time interval
                 //in microseconds.
get_T2_micros(); //returns the Timer2 microsecond time, with a precision of 0.5us, as a float.  This function is slower than calling "get_T2_count()," and therefore is not
                 //the preferred way of getting time.  It is better to get the time by calling "get_T2_count()" then dividing the value by 2.  By choosing whether or not
                 //you want to call "get_T2_count()" or "get_T2_micros()," you can decide if you need the extra precision of a float, or not, at the cost of having 
                 //slightly slower code.
reset_T2(); //resets the Timer2 counters back to 0.  Very useful if you want to count up from a specific moment in time, or obtain an "elapsed time."
revert_T2_to_normal(); //this function might also be called "unsetup_T2".  It simply returns Timer2 to its normal state that Arduino had it in prior to calling "setup_T2"
unsetup_T2(); //the exact same as "revert_T2_to_normal()"
T2_overflow_interrupt_off(); //turns off the Timer 2 overflow interrrupt so that you no longer interrupt your code every 128us in order to increment your overflow counter. 
                       //This may be desirable when you are no longer referencing the T2 counter or timer and want your main code to run a touch faster, but you don't want
                       //to call unsetup_T2() in order to change all of Timer2's settings back to default.
                       //Since an interrupt takes ~5us to execute, and my Timer2 will overflow every 128us, disabling the Timer2 overflow interrupt will prevent 
                       //you from losing that amount of time (~5us) every 128us.
                       //Source: Nick Gammon; "Interrupts" article; "How long does it take to execute an ISR?" section, found here: http://www.gammon.com.au/forum/?id=11488
                       //Note: If you diable the Timer 2 overflow interrupt but still call get_TC_count() or get_TC_micros() at least every 128us, you will notice no 
                       //difference in the counter, since calling get_TC_count() or get_TC_micros() also checks the interrupt flag and increments the overflow counter 
                       //automatically.  You have to wait > 128us before you see any missed overflow counts.
T2_overflow_interrupt_on(); //turns Timer 2's overflow interrupt back on, so that the overflow counter will start to increment again; see "T2_overflow_interrupt_off()"
                            //explanation for more details.
============================================================================================================================================================================
References:
-cool font, type "small", from: http://www.network-science.de/ascii/

Additional Resources:
-Nick Gammon's "Timers & Counters" article - http://www.gammon.com.au/forum/?id=11504
-Nick Gammon's "Interrupts" article - http://www.gammon.com.au/forum/?id=11488
*/


//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()
{
  noInterrupts(); //prepare for critical section of code
  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
  interrupts(); //allow interrupts again
  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 
}


========================================================================
Please support my work and contributions by subscribing and sharing using the buttons at the top-right of my website!  Also be sure to share links to my articles with your friends.
========================================================================

THE END



***Subscribe by Email! (via FeedBurner)***

38 comments:

  1. Gabriel, I'm potentially interested in using your library but the GPL3 license is probably going to be too restrictive for me. As such I haven't even looked through what you have to see if it will work for me. Can you contact me so we can discuss what the license actually means and limits me to and so I can let you know what I have in mind for your library. My work is "mostly" for charity so maybe we can come to an arrangement. thanks. --Dave (contact@racecoordinator.net)

    ReplyDelete
    Replies
    1. Dave, thanks for the note! I emailed you. We can discuss more there.

      Delete
  2. can i generate a pulse width of 0.5 microsecond ??

    ReplyDelete
    Replies
    1. Using my code? At the moment, no, but I can add that in I believe pretty easily. Would you like this feature?

      Delete
    2. Or, if you want to learn on your own, study this. This is a fantastic resource: http://www.righto.com/2009/07/secrets-of-arduino-pwm.html

      Delete
  3. Dude, your lib is really very useful and precise.
    I just have one question, one your example code read_PWM_pulses_on_ANY_pin_via_pin_change_interrupt, why you do this???
    pulseTime = pulseCountsCopy/2.0;

    I actually had to do this (pulseCountsCopy * 10) / 2 to get the value the motor is expecting, but I really have no idea why you have to divide by 2 the original pulse time.

    ReplyDelete
    Replies
    1. Hi thanks for the question. The pulse counts have units of 0.5 microseconds (us). To convert from units of 0.5us to us you must divide by two. Ex: a 1000us wide pulse will be read as 2000 counts on the microcontroller's Timer2. Divide by two to get the time value of 1000us.

      Delete
    2. Units, NOT actual time......Damn!!! It was on my face the whole time :P
      Thanks.

      Gabriel, would you mind if I annoy you with other stuff related to RC and Arduinos?
      I'm a computer engineer, so coding is kind of easy.....but the rest is almost everything new.

      Delete
    3. Sure, it doesn't hurt to ask questions. I'm a pretty busy guy, but the worst that will happen is I simply won't have time to respond.....or I won't know the answer. Never hurts to ask though. If you're new to Arduino, also be sure to check out the links at the bottom of this article here, to learn and reference as you go: http://www.electricrcaircraftguy.com/2014/01/the-power-of-arduino.html.

      Delete
    4. Thanks, I'm not skipping steps here, really trying to read everything I can.

      Just one question than.....the damn jitter on the input signal (from the receiver to the arduino) where is that coming from?????
      I already checked everything, even my soldering and wires. Once I connect to the arduino, the signal goes everywhere .
      Yes yes, I already checked the examples in your library and did some research on the low level stuff you talked about. The only difference to what I'm doing is that I use the high level interrupts on pins 2 and 3 (it's just a 3 channels receiver and I don't care for the third).

      Delete
    5. Can you copy and paste some data from the serial monitor showing this jitter? I'm not sure what you're talking about exactly.

      Delete
    6. Also, I wrote my example code to read in only a single channel at a time. Unless you wrote your code to read in more than one channel, it's probably simply your code.

      Delete
    7. Line 5 at the top of my example says, "-this code only reads in a single channel at a time, though it could be expanded to read in signals on every Arduino pin, digital and analog, simultaneously."

      Delete
    8. Yes, I'm aware of all that :D
      As soon as I stable this thing, I'll work on the code to read more than one at a time.

      Just confirm me one thing, the input MUST also be a PWM pin?????
      I understood that only output had to be exclusively PWM to work properly and this is probably the cause of the interference.

      I'll be out of town for some days, when I come back I test if this is the case.

      Delete
    9. Hi André, no, the input does not have to be a PWM pin. It can literally be any pin on the Arduino (digital or analog pins, except A6 and A7)...assuming you're using an Uno, Duemilanove, Nano, Pro Mini, etc, but I recommend not using pins 0 or 1 since those are the serial communication pins.

      To add another couple pins to read in, look into the Arduino function "attachInterrupt" (http://www.arduino.cc/en/Reference/attachInterrupt). This is how external interrupts are used, which are easier to use than pin change interrupts. External interrupts are ONLY available on pins 2 and 3. So....if you want to read in 3 channels on a receiver, use my code to read in one receiver channel on any pin that isn't 2 or 3, then attach a couple external interrupts to read in the other two channels. Copy my code in the ISR that is already reading in the one channel, and duplicate that functionality in the ISRs you create for the two external interrupts. That will be the easiest way to read in more channels, and using that method you can pretty easily read in 3 channels. Reading in more than that will require a skillful use of pin change interrupts.

      Delete
    10. This comment has been removed by the author.

      Delete
    11. Ok......
      Making progress.....all power sources must have the same ground!!! :D
      And there is such a thing as Servo.writeMicroseconds () (why reading the lib documentation first, right????)
      Now the step motor works as expected.

      But the interruption output is still a mess.
      I'm using the rc steering channel on pin 2 whit attachInterrupt (). This is the output I'm geting:

      steering = 1507
      ----------------------------
      steering = 1507
      ----------------------------
      steering = 1507
      ----------------------------
      steering = 1507
      ----------------------------
      steering = 171
      ----------------------------
      steering = 893
      ----------------------------
      steering = 1507
      ----------------------------
      steering = 4093
      ----------------------------
      steering = 1504
      ----------------------------
      steering = 9076
      ----------------------------
      steering = 1507
      ----------------------------
      steering = 1507
      ----------------------------
      steering = 243
      ----------------------------
      steering = 966

      Delete
    12. Hi, yes always remember a common ground among devices. Otherwise you get a "floating" voltage on any device without a common ground, and it can randomly jump from low to high, or anything in between, simply from static electricity, or nearby devices....including your own hand, which will store some charge.

      Let's back up a bit. To help you someone needs to know the following:
      1) What are you trying to accomplish?
      2) What does your circuit look like?-- (feel free to describe thoroughly, take a picture, or post a schematic). Be specific with devices and parts. Also, I think you are using a *servo* motor, not a stepper motor?
      3) What does your code look like?

      To get project trouble-shooting help like this I think you should post on the Arduino Forum here, so we can see your code easier. You are welcome to post all the info in my comments here too, but I think the forum is better for this purpose, as your problems I think are generically code-related, not specific to my library: Arduino Forum: Project Guidance. <--be sure when you start a new topic to answer the above 3 questions so people can help you out.

      Post a link on your Arduino post back to here, so people can see what library you are using and where you got it, and post a link in the comments here, on my website, back to your Arduino forum post you will make (if you decide to do so), so people who read my article here (including myself), can be aware of your Arduino forum post to help you. I'll see what I can do to help, but I'll need more info. I'm sure we can get this resolved.

      Delete
    13. Ok! I fixed all the problems and you may be interested in the things I found.

      When using the high level API for interrupts on pins 2 and 3, it does make a difference if it's a PWM pin or not even for input. Pin 2 is not PWM and the signal goes everywhere as I shown you, but pin 3 is PWM and the signal from the RC receiver gets just perfectly stable.

      Someone already did a more interesting API to handle interrupts in all pins at the same time using Pin Change and it's available from Arduino web site. Check it out: http://playground.arduino.cc/Main/PinChangeInt

      I don't know if its just as efficient as doing all the dirty work ourselves, but the documentation says it uses the same method as you do.
      Don't mind the content on the Arduino website, it seams to be quite outdated. Go straight to the Git Hub repository for the latest release, its very simple and smooth.

      Thanks for all your help, I hope to have contributed a little to your efforts.

      Delete
  4. Thanks for posting this code, it saved me from a couple more hours of debugging - I see that you had the exact same problem as me (with TCNT2 overflowing while returning microseconds) ;)

    ReplyDelete
    Replies
    1. Yessir! It was tricky. Glad to have helped! If you share or write anything up, any links back to my site here are appreciated.

      Delete
  5. your blog describe a nice view of the things. It is very effective for the reader. Please post more blog related to this.
    Visit :- radio Arduino

    ReplyDelete
  6. Hi all.

    Gabriel, I am working on a project involving the use of the ATMega328P-PU chip's limited number of timers. In researching a problem, I fortunately came across your .05 microsecond PWM measurement article. Bravo!

    I thoroughly read through your code, and I have a quick question for you. I noticed that, in the loop() section of your test code, you do not call reset_T2() at the top of the loop before t_start1, t_start_2, and t_start3 are set by the respective function calls. Is there a reason for this?

    If I understand your code correctly, without calling reset_T2() before these variables are assigned values, the unsigned long (uint32_t) T2_overflow_count variable will wrap around in value. If this happens, the end number would be smaller than the start number, and the elapsed time calculation (t_end(x) - tstart(x)) would be negative.


    Thoughts?

    ReplyDelete
    Replies
    1. Hi zx11, great question. I never use the reset_T2() function I wrote. It's not required. Resetting the timer means it cannot be used for timing more than one thing at a time, and I'm usually timing multiple things, or keeping a long-term timer for other purposes or something. Unlike languages like MATLAB, where every variable is automatically a double, the beauty of unsigned integer types (ex: byte, unsigned int, unsigned long) is that they cannot hold negative values. Therefore, when they hit the top, they roll over, or "overflow". Similarly, when they hit the bottom, they "underflow."

      Try it out in a test sketch on an Arduino, and you'll see the following. Let's assume a byte data type. 255+1 = 0. Similarly, 0-1 = 255. 10-20 = 246, etc. So, if your start time is 100, and your end time is 99, it means 255 counts have passed by. Sure enough, 99-100 = 255.

      Delete
    2. Let me add this too. You can have repeated (multiple) over/underflows. Ex: 100 + 513 = 101, since it overflows twice, coming right back to where it started, + 1. (100+513 is the same as 100+256+256+1, and each time you add or subtract 256, you end up where you started). <--again, assuming a byte (uint8_t/char) data type.

      Delete
    3. Run this sketch:

      /*
      overflow_and_underflow_test.ino
      -prove that unsigned data types cannot hold negative values, and have both overflow and "underflow" traits
      By Gabriel Staples
      26 Aug. 2015
      */

      void setup()
      {
      Serial.begin(115200);

      byte b1, b2, b3;

      b1 = 255;
      b2 = 1;
      b3 = b1 + b2; //0
      Serial.println(b3); //0

      b1 = 0;
      b2 = 1;
      b3 = b1 - b2; //255
      Serial.println(b3); //255

      b1 = 10;
      b2 = 20;
      b3 = b1 - b2; //246
      Serial.println(b3); //246

      b1 = 100;
      b2 = 99;
      b3 = b2 - b1; //255
      Serial.println(b3); //255

      b1 = 100;
      b2 = 513; //1
      b3 = b1 + b2; //101
      Serial.println(b2); //1
      Serial.println(b3); //101
      }

      void loop()
      {
      }

      Delete
    4. Hi all.

      Thanks Gabriel. I understand now. With unsigned numbers, the subtrahend's being bigger or smaller than the minuend will always result in a positive difference, and end minus start will always yield the distance (clock ticks) between them.

      Thanks again.


      Take care.

      -Z

      Delete
  7. Hi all.

    Gabriel, I have another question for you, and it deals with the checking of the flag_save variable in the get_T2_count() function.

    In the get_T2_count() function, you use the following statement to retrieve Timer 2's current overflow status:

    boolean flag_save = bitRead(TIFR2,0);

    Then, you check the value with the following:

    if (flag_save) ...


    It is possible that Timer 2 could overflow after the bitRead(TIFR2, 0) and before the if(flag_save) statements. Instead, I think it would be better to use direct register manipulation to minimize the chance of missing an overflow during the check for it. For example, you could probably do the following:

    uint8_t tcnt2_save = TCNT2; //grab the counter value from Timer2
    if(TIFR2 & 0x01)
    {
    tcnt2_save = TCNT2;
    T2_overflow_count++;
    TIFR2 |= 0b00000001;
    }


    In the above code, you directly derive Timer 2's overflow status in minimal time and without calling a slower function to do so.


    Thoughts?


    Take care.

    -Z

    ReplyDelete
  8. Z, you're just filled with curiosity! :) Another good question.

    bitRead(TIFR2,0) is actually identical to (TIFR2 & 0x01) in this case. bitRead() isn't a function, it's actually a macro, processed by the precompiler, not at runtime, so it has no overhead like a function would. It is defined here as:
    "#define bitRead(value, bit) (((value) >> (bit)) & 0x01)"

    You have a point about getting rid of my overflowFlag boolean however, to speed things up, though the compiler might optimize that out anyways since I don't use it elsewhere...so maybe it wouldnt change anything afterall. You'd have to check the assembly code that comes out of it.

    As for the possibility that Timer2 could overflow after the bitRead and before the if(flag_save), yes, it's possible, but it's irrelevant how I've written the code. No overflow are ever gone unaccounted for. Let's look at some cases.

    Case 1:
    Let's assume it's about to overflow, so we read TCNT2 at 255, and we read that the overflow flag is *not* set. Now, after reading the overflow flag and before checking the if statement, it overflows. Oh well, that overflow will be captured then and accounted for in the overflowISR. No problem there. The difference between someOverFlowCount*256 + 255 [the result of the count returned in the former case] and (someOverFlowCount+1)*256 + 0 [the result of the count returned in the latter case] is still only one time count.

    Case 2:
    Now, Let's assume it's about to overflow, so we read TCNT2 as 255. Next, we actually do read there was an overflow between storing the value from TCNT2 and checking the overflow flag. Oh well, notice that if there's an overflow we read TCNT2 again, so now we'd read it as 0 or 1 perhaps. The overflow count gets incremented, we read TCNT2 again....all is well, no significant error occurs. If I were to *not* read TCNT2 again, however, errors up to 255 counts or so would occur, which is a problem. Therefore, I have to read TCNT2 again in the event the overflow flag is true.

    Case 3:
    What happens if you were to reverse the order, and read the overflow bit and *then* read TCNT2? Well, that's where we'd have problems, so I was careful *not* to do it in that order. If I did it that way, I could read no overflow (while TCNT2 is actually at 255, for instance), but then the overflow occurs between reading the overflow flag and reading TCNT2, and by the time I read TCNT2 it's back at 0 or 1. In other words, this case would mean I missed an overflow event essentially by reading TCNT2 as 255 or 254 when in fact I should have read it as 0 or 1. This would introduce an error of up to 255 counts or so, so I was careful to do it the way I did it.

    The way I wrote it, no overflow are ever missed, no counts are ever missed. It works. I have worked on this for many hours, and I see no alternative way, nor order, to do this. Any other order of reading and doing things could mess it up.

    What I'm doing is essentially using software to turn an 8-bit counter into a 32-bit counter, by accounting for overflows. The only problem with this technique at all is that running the overflow ISR has overhead of a few microseconds. Therefore, every 128us, when an overflow interrupt occurs, a few microseconds is taken up, and timestamps cannot be grabbed during this time. Consequently, about 4/128 = 3% of the time you grab timestamps, you will see that even though their resolution is 0.5us, they will be off by 3~4 us.

    So, one out of 30 reads or so will have some error of a few us. Using Timer1 with my library would reduce this to only one out of every 8000 reads or so, since timer overflows would occur 65536/256 = 256 times less frequently.

    ReplyDelete
  9. MAJOR UPDATE TO LIBRARY COMING SOON!
    Since you seem quite interested in this library, I'll add this too:
    I'm in the middle of a major update to this library. My new library will be called eRCaGuy_TimerCounter, and includes the ability to use Timer0, Timer1, or Timer2, with*out* losing PWM outputs while using the timer! If you use Timer0 or 2, you can continue using 8-bit PWM (command 0 to 255) on the two respective pins, except at an increased frequency of 7.8kHz instead of Arduino's normal 977Hz for PWM on Timer0 and 490Hz for Timer2. As for Timer1, my new library will allow 16-bit PWM (command 0 to 65535) on the two respective pins, except at a freq. of only 30.5Hz instead of Arduino's normal 490Hz.

    I'm debating other additions, such as software interrupts like the Timer1 Library allows (http://playground.arduino.cc/Code/Timer1), but we'll see.

    ReplyDelete
  10. Hi all.

    Gabriel, again, thank you for your thorough explanation.

    I asked these questions because the project on which I'm working requires precise timing (just like the reason for which you wrote this library). I wanted to use Timer 1 (16-bit), but it was already in use by another library. Therefore, I had to write extra code to use Timer 2 (8-bit).

    I commend your efforts, as they have helped me to solve a logic issue into which I ran while coding my project.

    I look forward to the next major library update.


    Take care.

    -Z

    ReplyDelete
    Replies
    1. Hi Z, I'm curious, what are your timing requirements?
      -resolution?
      -max time span?

      What is the library taking up Timer1?
      -I ask because sometimes you can port libraries to other timers, combine libraries or features, or add features to an existing library
      -these are generally advanced skills, but with enough ingenuity and trickery, pretty much *any* library on a 16-bit timer can be made to work on an 8-bit timer, with some tradeoffs and understanding of performance.

      Delete
    2. Hi all.


      Gabriel, I am using the RGBmatrixPanel library from Adafruit (https://learn.adafruit.com/32x16-32x32-rgb-led-matrix?view=all#test-example-code). On an Arduino Uno, this library uses Timer 1, PORTB, and PORTD.

      Because I need to take full advantage of the ATMega328P-PU chip in the Uno, I have decided to use it stand-alone to make use of all of the chip's I/O pins. Using the chip in the Uno prevents this. The stock chip is set to use the internal 8 MHz oscillator with a pre-scale of CLK/8. I have set the chip's fuses to use a 16 MHz clock signal via external crystal with 22 pF capacitors and no pre-scale.

      In my project, I am using Timer 2 in concert with timer and pin-change interrupts. The output of the project is sent to the RGB matrix panel. The RGB panel uses Timer1. millis(), micros(), and delay() use Timer 0. Timer 2 is used by tone(). Since my application does not use tone(), I decided it was safe to use Timer 2. Microsecond resolution is sufficient. The time span needs to be as long as possible, so the variable that tracks timer overflows may need to be uint64_t.

      I thought about trying to change the RGB matrix panel library to use a timer other than the 16-bit Timer 1. However, I did not want to deal with issues in a future release/upgrade of the library due to my modifications of the present release.

      Please let me know if you have further questions.


      Take care.

      -Z

      Delete
    3. Z, makes sense, except for one thing: "Because I need to take full advantage of the ATMega328P-PU chip in the Uno, I have decided to use it stand-alone to make use of all of the chip's I/O pins. Using the chip in the Uno prevents this."

      What does using a bare chip allow you to do that you can't do on an Uno directly?
      -That's the part I don't see at the moment.

      Delete
    4. If you want to change the bootloader, manually set fuses, use the internal oscillator vs external, etc etc, that's all doable on an Uno......and I don't see what IO pins on an Uno you don't have access to (https://www.arduino.cc/en/Hacking/PinMapping168).

      Delete
    5. Hi all.

      Gabriel, for example, the Arduino Uno R3 does not expose the PCINT6 and PCINT7 pins on the ATmega328P-PU. Besides interrupts, those pins are used by the external crystal. The ATmega328P-PU can be driven to a 20 MHz maximum. However, in order to do so, you need a 20 MHz crystal attached across those pins along with the proper capacitors.

      More importantly, when I work on a project, I use my Uno only for the initial phase of the project's logic coding. Once that is done, I typically then move to a stand-alone ATmega328P-PU chip. I can't afford to dedicate an Uno to a single project, as that would be come expensive over time. An Uno R3 is $24.95. An ATmega328P-PU is less than $4.00. With the supporting elements (l.e.d.'s, resistors, etc.), the cost is typically no more than $15, if that.


      Take care.

      -Z

      Delete
  11. This comment has been removed by the author.

    ReplyDelete

Thanks for your comment or question! If it is a question, I will try to get back to you quickly.

Note: some HTML tags are allowed in your comments. To learn how to add bold (<b>...</b>), italics (<i>...</i>), or hyperlinks (<a href="URL">NAME</a>) to your comments, read here.

~Sincerely, Gabriel Staples.

P.S. Yo hablo español también. Je parle français aussi. (I speak Spanish & French too).