NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Tracking how long a pin is low in Microseconds

March 25, 2010
by Solorbob
Solorbob's Avatar

I'm trying to make my own, specialized version of the arduino pulseIn() function. I'm needing it to keep track of how long in microseconds a pin is low, and exit out when it goes back high, or when a timeout value is reached.

This is my first crack at it, so if anything looks out of whack, please let me know. ~ Code follows:

//************ //************ //This function will return the time in microseconds, uS, for how long //the IR Rx receive receives a pulse.
// We are using the IR Rx pin of PB1. // The IR Rx will be high when not receiveing, and will go low during receiving. // We want to capture the time of while it is low. If the pin does not go, and the // timeout value, in microseconds, is reached, the function will return 0;

unsigned long IRPulseIn(int timeout){ unsigned long uSCnt = 0; // To capture the pulse count. unsigned long i = 0; // Timeout counter.

  for (i=0; i <= timeout; i++) {
    //Check to see if we have stopped getting IR data, if so, exit for loop
    if( (PINB & (1<<PB1)) && uSCnt >=1)
    {
        i = timeout+1; //Set i to > than timeout so we will break out of for loop.
    } else
    {
        if (!(PINB & (1<<PB1))) uSCnt++; //If the input pin is low, add to uS counter.
        delay_us(1);            
    }
   }

   return uSCnt; //return our microsecond count.

} //*************

Thanks, Shawn

March 26, 2010
by bretm
bretm's Avatar

Do you need it to be very accurate? delay_us(1) isn't very accurate to start with, and then you're doing a bunch of other stuff during each loop. At 14.7456Mhz clock speed, it only takes 14 single-cycle instructions to eat up 1 μs.

I don't know what data type you're using for timeout, but let's say it's uint16_t. Let's approximate how many instructions you have:

Load timeout into two registers (2)

Compare i against it (2)

Branch instruction (1)

Read a bit from PINB (1)

Load "1" (2)

Compare against uSCnt (2)

&& (1)

Read PINB again (1)

Compare and branch (1)

Probably increment uSCnt (2)

That's roughly 15 clock cycles for the code you're running, or over 1 μs even before you call delay_us(1).

A better way to do this is to let the MCU count for you using 16-bit Timer/Counter1's Input Capture Unit if you can use the ICP1 pin (PB0) instead of PB1. See section 15.6 of the datasheet. This shouldn't interfere with the bootloader's use of PB0 because that's only grounded during programming.

Enable a pin-change interrupt for PB0. When it goes low, reset Counter1. When it goes high, Counter1 value will be copied to the Input Capture register, and then your interrupt will be called. You then just read the value (as quickly as possible, before the pin changes again) and do whatever you want with it.

March 26, 2010
by Solorbob
Solorbob's Avatar

Thank you for your information. It doesn't need to be too accurate. I'm just using IR to send 2 bytes from one nerdkit to another nerdkit. I was going to use some timing to identify a one as a 1200uS pulse and a zero as a 600uS pulse. I'll look into what you have suggested to see if I can figure it out.

thanks, Shawn

March 26, 2010
by bretm
bretm's Avatar

Oh, ok, that's a lot easier. You don't need to mess with Input Capture. Your method will work fine if all you need to know is "long time" or "short time". But I would simplify it:

while ((++uSCnt < timeout) && !(PINB & _BV(PB1)))
    delay_us(1);

If you're battery-powered I still prefer to use pin change interrupts, though. It's more complicated, but that way I can put the chip to sleep while it's waiting and save a lot on power. 600μs is forever!

March 26, 2010
by bretm
bretm's Avatar

Oops, I missed that you also time out if PB1 doesn't ever go low. But it also looks like you return after timeout even if you're in the middle of recieving a pulse. Is that the desired behavior?

March 26, 2010
by Solorbob
Solorbob's Avatar

I thought that is what I wanted, but I may have to have another look at it. So far, my values are all over the place from 150uS - 700uS for what should be my 2400uS header pulse. My others are all over the place too. Maybe I'm timing out in mid capture, I don't know, but it is really random.

I'll simplify my code as you have suggested, but I might need to look into the other method also. I don't think I can use Timer1 because I'm using it for PWM to send out the 38kHz IR pulses. unless you think there is a better way to send out the IR pulses, and use timer1 for the pin changes. I'm open for suggestions. thanks for your help thus far. Shawn

March 27, 2010
by bretm
bretm's Avatar

That sounds like a problem I was having trying to decode pulses from a CMMR-6P decoder. It was supposed to provide 200ms, 500ms, or 800ms pulses, but they were really all over the place because of noise such as momentary drop-outs of the signal.

If it's the Sony protocol, you should see low pulses of 2400us, 1200us, 600us, and high spacers of 300us. Those are all multiples of 300us. So to filter out the noise you might try something like this:

Wait for the 2400us to start. Then take 30 samples 10us apart for a total of 300us. Count the lows. You should get 30, but treat anything above 20 as good. Anything less than 10 count as a high. Anything between 10 and 20 would be an error.

So for a 2400us pulse you should get 8 lows (8 x 300us), then one high (the 300us space), then one or two lows (600us or 1200us), then one high, etc. Quit after you get your 13 pulses.

Post a Reply

Please log in to post a reply.

Did you know that first-order systems have a exponentially decaying response to step inputs? Learn more...