2
\$\begingroup\$

I am using an ATmega328P MCU with an 8 MHz internal oscillator. I need to measure the pulse width of an incoming pulse. The frequency of this pulse is approximately 500 Hz. Once every 2 ms, one pulse appears with a pulse length of at least 14 μs. I need to measure the length of this pulse.

Can anybody give me some direction how to do it? I know that I have to use an input capture interrupt. The thing I don't know is exactly how many microseconds it takes for the MCU to enter and exit the ISR. And also as I know the input capture interrupt occurs in every rising edge. But I don't need to measure the duty cycle nor frequency. I have to measure the time between the rising and falling edges.


Will this code work properly for pulse with 14 μs width?

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdbool.h>

uint16_t rising_capture = 0;
uint16_t falling_capture = 0;
uint16_t pulse_width = 0;
#define rising_edge true

ISR(TIMER1_CAPT_vect)
{
    switch(rising_edge){
        case true:
            TCNT1 = 0;               // reset the timer counter 
            rising_capture = ICR1;   // take the input capture value and save it in rising_capture 
            TCCR1B &= ~(1 << ICES1); // set input capture for falling edge
            rising_edge = false;     // make falling_edge variable false so next time it will react to case false
            break;
        
        case false:
            falling_capture = ICR1; // take input capture value and save it in falling edge
            TCCR1B |= (1 << ICES1); // set input capture for rising edge back
            rising_edge = true;     // make rising edge true for next pulse
            break;
    }
} 

int main(void)
{
    TCCR1B |= (1 << ICES1); // input capture set for rising edge
    TCCR1B |= (1 << CS10);  // no prescalar
    TIMSK1 |= (1 << ICIE1); // input capture interrupt enable
    while (1) 
    {
        // the difference of falling_capture and rising_capture will give the clock cycle past
        //between rising and falling edges
        pulse_width = falling_capture - rising_capture;                                          
    }
}

i changed my ISR() like this:

ISR(TIMER1_CAPT_vect){
if(TCCR1B & (1 << ICES1)){
    TCNT1 = 0;
    uart_send_int(TCNT1);
    rising_time = ICR1;
    TCCR1B &= ~(1 << ICES1);
}
else{
    TCCR1B |= (1 << ICES1);
    falling_time = TCNT1;
    uart_send_int(ICR1);
    pulse_width = falling_time - rising_time;
    uart_send_int(pulse_width );

}

}

F_CPU 8Mhz , no prescalar;

Now I am getting some values but I am not sure that they are exactly what I need. Because while pulse width increases, the pulse_width value should increase too but instead it gives some random numbers.

\$\endgroup\$
2
  • \$\begingroup\$ I'm pretty sure this code doesn't compile. rising_edge is a constant, however you're using it as a variable. \$\endgroup\$
    – Velvet
    Commented Jan 30, 2023 at 10:45
  • \$\begingroup\$ oh, it was a terrible mistake. i corrected it like bool rising_edge = true; \$\endgroup\$ Commented Jan 30, 2023 at 10:47

2 Answers 2

2
\$\begingroup\$

I haven't used AVR much but generally microcontrollers work like this:

  • In the timer hardware peripheral, you configure one timer pin as "input capture".
  • The input capture pin can be configured to give an interrupt, either on rising or falling edge (or in some cases both).
  • Every timer channel has its own individual counter value. Upon interrupt, this value should be preserved by hardware as it was at the point an edge was detected.
  • From there on the MCU interrupt latency + software delays will take some time, but it doesn't matter because you grab the individual timer counter from inside the ISR. You should not grab the main freerunning timer value because that one will have inaccuracy due to interrupt latency etc.
  • Most timer hardware should support changing the interrupt source from rising to falling from inside the ISR.
  • From the C programming point of view, make sure that all variables involved are uint16_t in case of 16 bit timers or uint32_t in case of 32 bit timers. You'll be calculating the difference between two time stamps and in some hardware, timer counters wrap-around from for example 65535 to 0, but that's not a problem. If you use unsigned arithmetic in C, then that's not a problem. For example if read 1 gives 65000 and read 2 gives 1000 then you can do read2 - read1 and get the result in timer cycles = 1536.
  • (Not applicable to AVR: In case you are using a 32 bit MCU but a 16 bit timer, do keep in mind that a 32 bit C compiler will implicitly promote the uint16_t to int, so you'll need to convert back to uint16_t then.)
  • It's a common beginner mistake to think that you have to reset the timer all the time. The actual timer values read are irrelevant here, only the difference between them matters. So the timer channel can just as well be freerunning with wrap-around.
\$\endgroup\$
3
  • \$\begingroup\$ i reset the timer for avoiding overflow. Because i didnt know that "For example if read 1 gives 65000 and read 2 gives 1000 then you can do read2 - read1 and get the result in timer cycles = 1536." \$\endgroup\$ Commented Jan 30, 2023 at 10:53
  • 1
    \$\begingroup\$ @turqaymammadov You have to check the manual how this particular part works, I don't know - this is a generic answer. However, in C programming there's a difference between unsigned wrap-around (well-defined) and signed overflow (undefined). \$\endgroup\$
    – Lundin
    Commented Jan 30, 2023 at 11:03
  • \$\begingroup\$ The interrupt is optional and secondary to the capture of the timer value. You can simply poll the capture flag and not use interrupts. If the concern is with isr processing time, run the code in a simulator and measure it. It might be something like 70 clocks on entry and exit depending on what the compiler decides. \$\endgroup\$
    – Kartman
    Commented Jan 30, 2023 at 12:56
1
\$\begingroup\$

Since the captures save the timer value in hardware registers, you have more than enough time to copy these values, in particular the period time of 2ms.

Start by implementing the capture of one edge and its ISR. You might want to instrument the ISR with outputs to a port bit to measure its latency and duration. In a second step, do the same for the other edge.

The measurement results will make you confident in your approach.

Remember to keep the ISRs short, no heavy lifting in them. Consider a queue for the subsequent processing.

\$\endgroup\$
4
  • \$\begingroup\$ please take a look the code i added down below. i tried to implement your advice. will this code work for 14 microsecond pulse length? \$\endgroup\$ Commented Jan 30, 2023 at 10:16
  • 1
    \$\begingroup\$ Go ahead and try it. \$\endgroup\$ Commented Jan 30, 2023 at 12:27
  • \$\begingroup\$ Interrupt latency is not the issue as the capture hardware negates this. The issue is getting into and out of the isr and still having enough time to process. \$\endgroup\$
    – Kartman
    Commented Jan 30, 2023 at 12:58
  • \$\begingroup\$ @Kartman With the port bit change inside the ISR, at least the latency introduced by the ISR entry is measured. This is not an exact method, I know, but the OP needs to see some real numbers to get a feeling. With a period time of 2 ms, the few clocks of ISR entry and exit are negligible. -- Anyway, your proposal to drop the idea of interrupts is good. At least the OP can consider this. The busy loops waiting for the edges take some clocks, too, by the way. \$\endgroup\$ Commented Jan 30, 2023 at 14:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.