This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Timer as frequency measurement: issue

Hello everyone,

I am using the STM32F407ZET6, amongst other things, for a frequency measurement.

The incoming sine signal is converted to a (positive only) square wave signal via hardware, and is then fed to a GPIO port, configured as external interrupt on rising edge.
To measure its cycle duration, I am starting a microsecond-increment timer on the first rising edge and disable its counter after, let’s say, 5 rising edges. On every timer interrupt a variable is incremented to keep count of the passed microseconds. Eventually I am using the mean value over those 5 cycles to determine the signal frequency
(I can’t use the input capture function of the timers because the square-signal lies on an unfitting port.)

Now this works very well. I get exact results that fit their purpose by all means.
But only up to the point where I add several delays (to control a LCD display) before the frequency measurement. For some reason this seems to interfere with the correct timer execution. The more or larger delay functions have been placed, the greater the error during the frequency measurement.

I’ve done the delays with the same timer I use for the frequency measurement (timer 3) and also by simple usage of NOP loops. Both create said error. I feel like, for some reason, it messes with the system ticks.

Any inputs on this topic would be greatly appreciated.

Kind regards,
Alain

Parents
  • You should clear the external interrupt flag before you start measuring 5 periods.

    Else you'll get a new period start while you are busy with LCD delay.
    And this period start sets the external interrupt flag.
    When you then decides to start your measurement you instantly get an interrupt even if it was a long time since the last pulse.

    Note that it's often nice to keep the timer free-running forever.

    The reason for this is that you can then use an interrupt-drive delay for longer delays. But you can also busy-loop short delays with little overhead by quickly sample the current timer value and then repeatedly sample again and again until the difference between the two samples represents your delay. This means you could have a timer tick with 100ns period and busy-loop a 1.2us delay (with a 0.1us jitter error and a bit of setup error) by waiting 12 ticks. Or you could have the main loop order a 500ms delay by requesting an interrupt (that sets a flag) after 5000000 ticks. Or the main loop could poll multiple concurrent software timers with:

    for (;;) {
        if ((t0 - sw_timer0_start) >= TIMER0_DELAY) {
            ...
        }
        if ((t0 - sw_timer1_start) >= TIMER1_DELAY) {
            ...
        }
        if (hw_timer_flag) {
            hw_timer_flag = false;
            ...
        }
        if (uptime >= uptime_last + 30) {
            ...
        }
    }
    


    And if that free-running timer generates an interrupt on every overflow, then it could update an "uptime" counter.

    So a single free-running timer can really solve lots of different problems all at the same time. Short polled delays. Multiple concurrently polled long delays. System uptime.

Reply
  • You should clear the external interrupt flag before you start measuring 5 periods.

    Else you'll get a new period start while you are busy with LCD delay.
    And this period start sets the external interrupt flag.
    When you then decides to start your measurement you instantly get an interrupt even if it was a long time since the last pulse.

    Note that it's often nice to keep the timer free-running forever.

    The reason for this is that you can then use an interrupt-drive delay for longer delays. But you can also busy-loop short delays with little overhead by quickly sample the current timer value and then repeatedly sample again and again until the difference between the two samples represents your delay. This means you could have a timer tick with 100ns period and busy-loop a 1.2us delay (with a 0.1us jitter error and a bit of setup error) by waiting 12 ticks. Or you could have the main loop order a 500ms delay by requesting an interrupt (that sets a flag) after 5000000 ticks. Or the main loop could poll multiple concurrent software timers with:

    for (;;) {
        if ((t0 - sw_timer0_start) >= TIMER0_DELAY) {
            ...
        }
        if ((t0 - sw_timer1_start) >= TIMER1_DELAY) {
            ...
        }
        if (hw_timer_flag) {
            hw_timer_flag = false;
            ...
        }
        if (uptime >= uptime_last + 30) {
            ...
        }
    }
    


    And if that free-running timer generates an interrupt on every overflow, then it could update an "uptime" counter.

    So a single free-running timer can really solve lots of different problems all at the same time. Short polled delays. Multiple concurrently polled long delays. System uptime.

Children
  • A 1us interrupt isn't a good plan (1 MHz!)

    TIM3->SR &=~TIM_SR_UIF; // clear pending interrupt flag

    No need to mask it on, just write it to the register

    TIM3->SR =~TIM_SR_UIF;

    Also try using DWT_CYCCNT as a sub-microsecond time stamp, it doesn't need interrupts, and has 32-bit precision, and multi-second rollover

    There's the SYSTICK counter too, but it's only 24-bit wide as I recall

  • Even if it's not connected to a pin you could use any timer (ideally a 32-bit one) to count at 1 MHz, and use the HW count, and not a SW count. If you had it wrap at 1000 ticks your interrupt would be 1 KHz, and not 1 MHz. Remember a 1 MHz interrupt will divert the processor every 168 cycles (at 168 MHz), which is rather excessive.

    16-bit (TIM3 and TIM4) or 32-bit (TIM2 and TIM5)

  • Thank you very much for your help, everyone. The issue is solved.

    Per, it was indeed a pending external interrupt which was set during the LCD Setup time. I wasn't aware of the fact that the microcontroller sets interrupt flags as soon as the port is accordingly configured, and not only after the interrupt service routine itself was activated.
    However i do not seem to be able to reset the interrupt flag outside the service routine, so i simply ignore the first call of the ISR. It's not pretty but it works.

    In this application a free running usec timer is out of question due to time sensitive operations following the frequency measurement. To keep system performance high and the time measurement increments small, i am forced to toggle a usec interrupt timer. But i will keep this in the back of my head for later use. Thanks a lot for the input!

    Pier, i am aware that a 1 usec timer is very demanding for the uC, you are right in your pleads to avoid the likes. Unfortunately i am forced to use such small time increments to get an exact frequency value since all following calculations and measurements are based around the signal frequency. But to your reassurance: i only activate the timer on a few occasions when there is nothing much else to do for the processor. Also the measurement interrupts have a higher priority than the timer interrupt, which should secure its the proper execution.

    I used the SysTick counter before, subtracting an end and a start value and thus calculating the passed time. But i later on changed it to the toggling usec timer because i felt it is cleaner coding.

    I will definitely look into DWT_CYCCNT and HW Counter. Thank you very much for your advice.

    Best regards,
    Alain