在中断例程中使用millis()和micros()


13

文档attachInterrupt()说明:

... millis()依靠中断来计数,因此它在ISR内永远不会增加。由于delay()需要中断才能起作用,因此如果在ISR内部调用,它将不会起作用。micros()最初可以正常工作,但将在1-2 ms之后开始异常运行。...

如何micros()从不同millis()(当然除了他们的精确度)?以上警告是否表示micros()在中断例程中使用始终是个坏主意?

上下文-我想测量低脉冲占用率,因此需要在输入信号发生变化时触发例程并记录当前时间。

Answers:


16

其他答案都很好,但是我想详细说明如何micros()工作。它总是读取当前的硬件计时器(可能是TCNT0),该计时器由硬件不断更新(实际上,由于预分频器为64,所以每4 µs)。然后,它添加了定时器0溢出计数,该计数由定时器溢出中断(乘以256)更新。

因此,即使在ISR内部,您也可以依靠micros()更新。但是,如果等待时间太长,则会错过溢出更新,然后返回的结果将下降(即,您将获得253、254、255、0、1、2、3等)。

这是micros()-略微简化的,以除去限定用于其它处理器:

unsigned long micros() {
    unsigned long m;
    uint8_t oldSREG = SREG, t;
    cli();
    m = timer0_overflow_count;
    t = TCNT0;
    if ((TIFR0 & _BV(TOV0)) && (t < 255))
        m++;
    SREG = oldSREG;
    return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

上面的代码允许溢出(它检查TOV0位),因此它可以在中断关闭时处理溢出,但只能处理一次 -没有处理两次溢出的规定。


TLDR;

  • 不要在ISR内部进行延迟
  • 如果必须执行这些操作,则可以选择时间,micros()但不可以millis()。也delayMicroseconds()有可能。
  • 延迟不要超过500 µs左右,否则会错过定时器溢出。
  • 即使是短暂的延迟,也可能导致您错过传入的串行数据(在115200波特下,每87 µs您将获得一个新字符)。

无法理解下面给出的在micros()中使用的语句。您能详细说明一下吗?由于写了ISR,因此一旦输入ISR,TOV0标志将被清除,因此以下条件可能不成立!如果((TIFR0&_BV(TOV0))&&(t <255))m ++;
Rajesh

micros()不是ISR。这是正常功能。TOV0标志使您可以测试硬件,以查看计时器是否发生了溢出(但尚未处理)。
尼克·加蒙

由于测试是在中断关闭的情况下完成的,因此您知道该标志在测试期间不会改变。
尼克·加蒙

因此,您的意思是说在micros()函数中的cli()之后,如果发生任何溢出,是否需要检查?有道理。否则,即使micros不是函数,TIMER0_OVF_vect ISR也会清除TIFR0中的TOV0,这是我的疑问。
Rajesh

还有为什么将TCNT0与255进行比较以查看其是否小于255?在cli()之后,如果TCNT0达到255,会发生什么?
Rajesh

8

在中断程序中使用或在其中都没错millis()micros()

错误的不正确使用它们。

最主要的是,当您处于中断程序中时,“时钟不会滴答”。 millis()并且micros()不会改变(嗯,micros()最初会,但是一旦它超过了需要毫秒刻度的魔术毫秒点,它就会崩溃。)

因此,您当然可以在ISR中调用millis()micros()查找当前时间,但是不要期望该时间会改变。

您提供的报价中会警告您时间上没有变化。 delay()依靠millis()改变来了解已经过去了多少时间。由于它没有改变,delay()所以永远无法完成。

因此,从本质上讲,无论您何时在ISR中使用它们millis()micros()都将告诉您调用ISR的时间。


3
不,micros()更新。它总是读取硬件定时器寄存器。
尼克·加蒙

4

引用的短语不是警告,仅是有关事物工作方式的说明。

使用millis()micros()在正确编写的中断例程中没有本质上的错误。

另一方面,在定义不正确的中断例程中做任何事情都是错误的。

一个中断例程花了几微秒的时间来完成其工作,很可能是不正确的。

总之:一个正确编写的中断程序不会造成或遇到问题millis()micros()

编辑: 关于“为什么micros()“开始行为异常””,如“ 检查Arduino micros函数 ”网页中所述, micros()普通Uno上的代码在功能上等同于

unsigned long micros() {
  return((timer0_overflow_count << 8) + TCNT0)*(64/16);
}

这将返回一个四字节无符号长整数,其中包括timer0_overflow_count来自Timer-0计数寄存器的三个最低字节和一个字节。

arduino millis函数网页的检查timer0_overflow_count所述,TIMER0_OVF_vect中断处理程序每毫秒将约增加一次。

在中断处理程序开始之前,AVR硬件会禁用中断。如果(例如)一个中断处理程序在中断仍被禁用的情况下运行了五毫秒,则将至少错过四个定时器0溢出。[在Arduino系统中,用C代码编写的中断不是可重入的(能够正确处理同一处理程序中的多个重叠执行),但是可以编写可重入的汇编语言处理程序,以便在开始耗时的过程之前重新启用中断。]

换句话说,定时器溢出不会“堆积”;每当在处理来自上一个溢出的中断之前发生了溢出时,millis()计数器就会丢失一毫秒,而差异timer0_overflow_count又会导致micros()毫秒的错误。

关于“小于500μs”作为中断处理的上限,“为了防止定时器中断阻塞太长时间”,您可以提高到1024μs以下(例如1020μs),并且millis()仍然可以工作,大多数时间。但是,我将中断处理程序视为耗时超过5 s,将懒惰的花费超过10 s,将类似蜗牛的花费超过20 s。


您能否详细说明“事情是如何工作的”,尤其是为什么要micros()“开始行为异常”?您所说的“正确编写的中断例程”是什么意思?我认为它的意思是“短于500us”(以防止阻止定时器中断时间太长),“尽可能使用易失性变量进行通信”和“不调用库代码”,还有其他吗?
PetrPudlák16年

@PetrPudlák,请参见编辑
James Waldby-jwpat7 2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.