Go Down

Topic: How to get microsecond from registers? (Read 980 times) previous topic - next topic

liudr

I did some google search but it too often points to the "login or register"  :D

My question is, how to read the microsecond result from directly accessing registers. One project I'm doing needs a bit or performance boost. I was doing while (micros()-current<wait) but it was not as accurately as I wanted. From the other end of the project, a fluctuation indicates the loop wanders about +- 12us, which is translating to +- 2mm distance measurement error. I wonder if using register can make this drop down. I have heard of the micros only returns multiple of 4us boundary too so will register access make its accuracy any better? Thanks!

BTW, I would be comfortable with inserting assembly code too if you tell me how to write the C syntax to include them.

AWOL

For clues, you could look at the source of "micros()"
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

Jack Christensen

What range of time are you needing to measure? A simpler approach might just be to configure a timer and use the timer values. Input capture is another cool option if it fits whatever it is you're trying to do.
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

liudr

#3
Jun 08, 2012, 06:11 pm Last Edit: Jun 08, 2012, 06:24 pm by liudr Reason: 1

For clues, you could look at the source of "micros()"


I am either dumb or had a bad breakfast (could be both). Thank you!!!

Found the code: been searching outside of my computer on google and didn't know the answer is within:

Code: [Select]
unsigned long micros() {
unsigned long m;
uint8_t oldSREG = SREG, t;

cli();
m = timer0_overflow_count;
#if defined(TCNT0)
t = TCNT0;
#elif defined(TCNT0L)
t = TCNT0L;
#else
#error TIMER 0 not defined
#endif

 
#ifdef TIFR0
if ((TIFR0 & _BV(TOV0)) && (t < 255))
m++;
#else
if ((TIFR & _BV(TOV0)) && (t < 255))
m++;
#endif

SREG = oldSREG;

return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

liudr


What range of time are you needing to measure? A simpler approach might just be to configure a timer and use the timer values. Input capture is another cool option if it fits whatever it is you're trying to do.


Jack,

Quick version: I want to detect an input to swing high and then wait for a period of time and set an output to high. The delay between detection and activating output is between 5.7ms and 11.4ms (round trip time for sound to travel between 1m distance and 2m distance), with as much accuracy as possible, or relative error of 0.1%, i.e. 1mm to 2mm, so right on the edge of 4us to 8us if possible.

Complete story here: I wanted to emulate a simple sonic ranger that works like the following (not like ping))))

1.   The data acquisition system pulls the INIT digital line to HIGH to initiate the sonic ranger to start measurement.
2.   The ranger immediately sends out an ultrasonic.
3.   The ranger receives a reflected pulse from the nearest object and pulls the ECHO digital line to HIGH to indicate it has detected an object.
4.   The data acquisition system measures the delay between the start and the end of the measurement.
5.   It then uses the speed of sound to calculate round trip distance and only outputs the one-way distance to the user.

I emulated steps 3 and 4 with a different distance measurement device, my smart track. So every time my arduino is probed by INIT going from low to high, it measures distance and emulates a delay like the above-described sonic ranger, then gives a high on ECHO so the data acquisition system treats it like a sonic ranger and does its things happily.

But I noticed a fluction of the measurement that translates into +-12us fluctuation, could very well be the DAQ's timing being inaccurate, but I wanted to trust their +- 1mm accuracy which is translated into about 6us fluctuation (maybe I'm too picky). This makes me think my arduino C code is not time-accurate.

So what is an input capture?

Jack Christensen

#5
Jun 08, 2012, 06:35 pm Last Edit: Jun 08, 2012, 06:44 pm by Jack Christensen Reason: 1
Timer/Counter1 has an input capture unit, that causes the timer value to be stored in the input capture register, ICR1. Actually it sounds like this could work well for you. I'd configure the timer just to run free at whatever frequency is needed. For sake of example, assuming a 16MHz system clock, set the prescaler to clk/8 and then each count would be 0.5µs. So save the timer value when pulling INIT high, and configure the input capture interrupt so the code knows when the return happened, i.e. wire the ECHO signal to the ICP1 pin. Subtract the ICR1 value from the previously saved value and that's it. Edit: Ummm, no. The other way 'round. Subtract the previously saved value from the ICR1 value. :smiley-red:

That seems about as straightforward and clean as it could be, should be minimal error caused by processing, calculations, etc.

Oh section 16.6 in the ATmega328 datasheet gives all the gory details on input capture ;)

PS: With the timer running at 2MHz, the counter would overflow about every 32mS if I did the maths right, so that would be the upper limit to the interval that could be measured.
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

liudr

Jack,

Thank you! Sounds like quite a few tasks for me with not much timer experience. :smiley-red: I'll read the section to get some understanding. So just to reiterate, I need the following skills to make it work:

1) Know how to set the prescalar of the timer1
2) Know how to read timer value so I can save it
3) Know how to configure the input capture interrupt and which pin is the ICP1 pin (this sounds really restricting since my system is already built and few pins are available for this thing)
4) Subtracting? I'm really clueless around timers.

liudr

So if I try to use the micros code instead, can someone explain the following features?

1) uint8_t oldSREG = SREG, t; What is the SREG?  must be a register, right?
2) What are the defs TIFR0, TCNT0 and TCNT0L? If I'm using Uno, which one is defined?

Thanks.

Jack Christensen

SREG is the status register, it has the global interrupt enable bit in it. Without digging into the micros() code, I imagine that the interrupt bit is what's being manipulated there.

The other registers are for Timer0.

I haven't played with input capture myself, so I'd like to give it a try. The cool thing about it is once the setup is done (and that shouldn't be all that bad), then there's not much to do, like I said, very clean implementation.

I'd be game collaborating a bit if you would be. Should be able to hammer out a demo very quickly.
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Jack Christensen


3) Know how to configure the input capture interrupt and which pin is the ICP1 pin (this sounds really restricting since my system is already built and few pins are available for this thing)


Opps, that'd be the only potential show-stopper. ICP1 is the same as the Arduino Uno D8 pin.
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Jack Christensen

Actually a pin-change interrupt should work if the ICP1 pin is unavailable. A little more code but almost as good. Any pin can do a pin-change interrupt. Overall, no additional pins are required, just one for the INIT signal and one for the ECHO signal.
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Jack Christensen

#11
Jun 08, 2012, 11:16 pm Last Edit: Jun 08, 2012, 11:20 pm by Jack Christensen Reason: 1
Well I couldn't help myself. Here's an input capture demo. One odd thing, I had to make sure I cleared the input capture flag in the Timer/Counter1 interrupt flag register [TIFR1 |= _BV(ICF1);]. Not sure why, might have had some switch bounce sneaking in there. That probably wouldn't be a problem with clean digital signals. Obviously the timer clock speed is not appropriate for your application, but that is easily altered.

Code: [Select]
//Input capture demo: Reaction timer.
//Wire a tactile button switch from the Arduino D8 pin to ground.
//To start, press and release the button when prompted to do so.
//After a random 2-5 second delay, the LED will illuminate.
//Press the button again as soon as the LED illuminates.
//The reaction time between the LED illuminating and the second button press is displayed.
//Reaction times over about four seconds will not be measured correctly.
//Jack Christensen 08Jun2012 CC BY-SA

#include <avr/interrupt.h>
#include <util/atomic.h>
#include <Button.h>                      //https://github.com/JChristensen/Button
#include <Streaming.h>                   //http://arduiniana.org/libraries/streaming/

#define ICP_PIN 8                        //the input capture pin
#define LED 13                           //the LED
Button btn(ICP_PIN, true, true, 25);     //instantiate the button
unsigned int startTime;                  //timer value when the LED was turned on
unsigned int stopTime;                   //timer value when the button was pressed
volatile boolean fCapture;               //flag indicating that input capture occurred
float reactionTime;                      //time to react to the LED coming on and pushing the button

void setup(void)
{
   Serial.begin(115200);
   Serial << endl << "** Reaction Timer **" << endl << endl;
   pinMode(LED, OUTPUT);
   
   //Set up Timer/Counter1
   ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
   {
       TCCR1B = 0;                       //stop the timer, input capture on falling edge
       TCCR1A = 0;                       //normal mode
       TCCR1C = 0;                       //no force output compare
       TIMSK1 = 0;                       //disable timer interrupts
       TCCR1B = _BV(CS12) | _BV(CS10);   //start the timer, clk/1024
                                         //(each timer count is 64µs, timer overflows about every 4.194 sec)
   }
}

void loop(void)
{
   Serial << "Press and release the button to start." << endl;
   
   do { btn.read(); } while ( !btn.wasReleased() );    //wait for button press & release
   delay( random(2000, 5000) );          //random delay
   ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
   {
       fCapture = false;                 //clear our input capture flag
       startTime = TCNT1;                //get the start time from the counter
       TIFR1 |= _BV(ICF1);               //clear the AVR's input capture flag
       TIMSK1 = _BV(ICIE1);              //enable input capture interrupts
   }
   digitalWrite(LED, HIGH);              //turn on the LED
   while ( !fCapture );                  //wait for the user to react

   ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
   {
       stopTime = ICR1;                  //record the stop time from the input capture register
   }
   digitalWrite(LED, LOW);               //turn off the LED

   reactionTime = (stopTime - startTime) * 64e-6;   //calculate & display the results
   Serial << "Reaction time = " << _FLOAT(reactionTime, 3) << " seconds." << endl << endl;
   do { btn.read(); } while ( btn.isPressed() );    //wait for user to release button
}

//Timer/Counter1 Input Capture Interrupt Handler
ISR(TIMER1_CAPT_vect)
{
   TIMSK1 = 0;                        //disable further interrupts
   fCapture = true;                   //signal that input capture has occurred
}
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

liudr



3) Know how to configure the input capture interrupt and which pin is the ICP1 pin (this sounds really restricting since my system is already built and few pins are available for this thing)


Opps, that'd be the only potential show-stopper. ICP1 is the same as the Arduino Uno D8 pin.


Darn, I used D8 already. All I have left are D9,10, 14-19. I guess this teaches me a lesson not to think I can do everything in software. So for my next hardware design iteration, which pins should I reserve for fancy things like ext interrupts and these timer driven functions? Now I know D0 and D1 are serial, D2 and D3 are for external interrupt and will keep them around. Also D9 for timer1. Anything else you would suggest me to keep? The arduino ADC isn't really awesome so I would not be devastated to use an external ADC (faster I2C 12bit or more) use A0-3 for digital pins, such as LCD data lines and reserve A4-5 for I2C bus. So what other fancy pins should I reserve? I'm not using SPI though.

liudr


Well I couldn't help myself. Here's an input capture demo. One odd thing, I had to make sure I cleared the input capture flag in the Timer/Counter1 interrupt flag register [TIFR1 |= _BV(ICF1);]. Not sure why, might have had some switch bounce sneaking in there. That probably wouldn't be a problem with clean digital signals. Obviously the timer clock speed is not appropriate for your application, but that is easily altered.

Code: [Select]
//Input capture demo: Reaction timer.
//Wire a tactile button switch from the Arduino D8 pin to ground.
//To start, press and release the button when prompted to do so.
//After a random 2-5 second delay, the LED will illuminate.
//Press the button again as soon as the LED illuminates.
//The reaction time between the LED illuminating and the second button press is displayed.
//Reaction times over about four seconds will not be measured correctly.
//Jack Christensen 08Jun2012 CC BY-SA

#include <avr/interrupt.h>
#include <util/atomic.h>
#include <Button.h>                      //https://github.com/JChristensen/Button
#include <Streaming.h>                   //http://arduiniana.org/libraries/streaming/

#define ICP_PIN 8                        //the input capture pin
#define LED 13                           //the LED
Button btn(ICP_PIN, true, true, 25);     //instantiate the button
unsigned int startTime;                  //timer value when the LED was turned on
unsigned int stopTime;                   //timer value when the button was pressed
volatile boolean fCapture;               //flag indicating that input capture occurred
float reactionTime;                      //time to react to the LED coming on and pushing the button

void setup(void)
{
   Serial.begin(115200);
   Serial << endl << "** Reaction Timer **" << endl << endl;
   pinMode(LED, OUTPUT);
   
   //Set up Timer/Counter1
   ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
   {
       TCCR1B = 0;                       //stop the timer, input capture on falling edge
       TCCR1A = 0;                       //normal mode
       TCCR1C = 0;                       //no force output compare
       TIMSK1 = 0;                       //disable timer interrupts
       TCCR1B = _BV(CS12) | _BV(CS10);   //start the timer, clk/1024
                                         //(each timer count is 64µs, timer overflows about every 4.194 sec)
   }
}

void loop(void)
{
   Serial << "Press and release the button to start." << endl;
   
   do { btn.read(); } while ( !btn.wasReleased() );    //wait for button press & release
   delay( random(2000, 5000) );          //random delay
   ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
   {
       fCapture = false;                 //clear our input capture flag
       startTime = TCNT1;                //get the start time from the counter
       TIFR1 |= _BV(ICF1);               //clear the AVR's input capture flag
       TIMSK1 = _BV(ICIE1);              //enable input capture interrupts
   }
   digitalWrite(LED, HIGH);              //turn on the LED
   while ( !fCapture );                  //wait for the user to react

   ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
   {
       stopTime = ICR1;                  //record the stop time from the input capture register
   }
   digitalWrite(LED, LOW);               //turn off the LED

   reactionTime = (stopTime - startTime) * 64e-6;   //calculate & display the results
   Serial << "Reaction time = " << _FLOAT(reactionTime, 3) << " seconds." << endl << endl;
   do { btn.read(); } while ( btn.isPressed() );    //wait for user to release button
}

//Timer/Counter1 Input Capture Interrupt Handler
ISR(TIMER1_CAPT_vect)
{
   TIMSK1 = 0;                        //disable further interrupts
   fCapture = true;                   //signal that input capture has occurred
}



Jack,

Thanks for not being able to help yourself! I'll use this code on a regular Arduino dev board to see how it looks on that other end of the project.

At the moment I'm able to machine something to secure my sensors and the fluctuating readings have gone down a bit but this is still a very nice solution I'm going to try. I've been procrastinating on timers and interrupts.  :smiley-red:

Jack Christensen

#14
Jun 08, 2012, 11:36 pm Last Edit: Jun 08, 2012, 11:39 pm by Jack Christensen Reason: 1
@liudr, keep me in the loop as to how it works out. As for other "fancy pins", I keep the attached pinned to my bulletin board for easy reference. Basically just taken from the datasheet, but it's a good cross-reference between Arduino pin numbers, DIP package pin numbers, and the various functions of the pins.

Nick Gammon has good stuff on his site regarding timers and interrupts, as does Dean Camera.
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Go Up