我目前正在编写Teensy微控制器并希望为游戏设置暂停功能。我已经能够从ISR计数器溢出创建一个计时器,但是我还没有能够弄清楚如何暂停这个计数器。我试过了:
ISR(TIMER1_OVF_vect) {
if (paused == 0){
overflow_counter ++;
}
}
这似乎对计数器没有任何影响,无论我在函数中放入什么指令,计数器都会继续运行。我已经尝试了各种if语句并且它们被忽略了 - 函数只是因为某些原因增加了计数器(即使我把overflow_counter - )!
因此,我尝试设置另一个变量,该变量拍摄按下暂停按钮时的快照,然后当游戏取消暂停时,将需要另一个快照并计算差异。这将从总体时间中消失。
double snapshot_time = 0;
double get_current_time(void){
double time = ( overflow_counter * 65536.0 + TCNT1 ) * PRESCALE / FREQ;
if (paused == 0 && switch_state[5] == 1){
snapshot_time = time;
}
return time;
}
我尝试将 snapshot_time 设置为全局变量,并使其等于时间,认为这可以动态捕获静态变量,但不幸的是它并不是。任何人都可以提供这样做的方法吗?
答案 0 :(得分:4)
您的问题中隐藏着许多方面。
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...
顺便说一下,如果避免在没有必要的地方使用浮点数,那么吞吐量可以大大提高。