您的问题中隐藏着许多方面。
1.首先,计数器变量应该被标记volatile
。编译器正在对变量进行不同的优化,因此它可以将变量加载到寄存器中并继续使用寄存器,假设它只是存储变量内容的地方。如果使用关键字声明变量volatile
,则编译器知道它可以随时更改,因此编译器将在每次访问时重新加载和/或重写该变量。所以,可以这样声明
volatile uint16_t overflow_counter;
paused
变量也是如此。
2.你应该记住,如果中断没有被禁止,那么定时器中断可以发生在任何两个处理器的指令之间。由于处理器是 8 位的,它使用 8 位宽的总线访问内存。这意味着,要读取 16 位数据,它需要 2 条指令。假设我们将计数器值复制到局部变量中:
uint16_t counter_snapshot = overflow_counter;
局部变量将分配两个寄存器并执行两次内存读取操作。让我们假设中断发生在第一个之后,但在第二个之前。因此,在输出时,您将从之前的值复制一半的数字,而从它的新值复制另一半。即价值将被损坏。如果变量是 8 位并由一条指令复制,则不会发生这种情况。但如果它更宽,或者如果它是读取-修改-写入,则应采取预防措施:
uint8_t old_sreg = SREG; // SREG i/o register contains processor state flags, including "interrupt flag", which allows interrupt
cli(); // clear the "interrupt flag", preventing interrupts from happening
uint16_t counter_snapshot = overflow_counter; // safely taking the snapshot
SREG = old_sreg; // restoring the "interrupt flag" to it's previous state. Other flags also restored but we do not care about them.
3.如上所述,中断可以随时发生。这意味着如果您尝试同时读取overflow_counter 和TCNT1,中断可能会发生在两者之间,因此结果将与预期不符。特别是如果这两个值的读取被浮点乘法这样的长操作分开。因此,解决方法可能如下:
uint8_t old_sreg = SREG; // saving state
cli(); // locking interrupts
// latching values we are interested in
uint16_t latch_overflow_counter = overflow_counter;
uint16_t latch_tcnt1 = TCNT1;
uint8_t latch_tifr1 = TIFR1;
SREG = old_sreg; // restoring interrupts
/* We are disabling interrupts, but it do not stop the timer from counting,
therefore TCNT1 value continue changing, and timer could overflow in any time
within that block above. But which moment exactly? Before we read TCNT1 or just after?
Let's assume if TCNT1 have high values then we got it's value just before the timer overflow;
otherwise, overflow happened before that */
if ((latch_tifr1 & (1 << TOV1)) && // we got the overflow flag set
(latch_tcnt < 32768) { // we are within the low values
latch_overflow_counter++; // increasing the latched value
}
double time = ( latch_overflow_counter * 65536.0 + latch_tcnt1 ) * PRESCALE / FREQ; // now using latched values to calculate...
顺便说一句,如果在不必要的地方避免使用浮点,吞吐量可以大大提高。