0

我目前正在编写一个 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设置为全局变量并使其等于时间,认为这可能会动态捕获静态变量,但不幸的是它没有。谁能提供一种方法来做到这一点?

4

1 回答 1

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...

顺便说一句,如果在不必要的地方避免使用浮点,吞吐量可以大大提高。

于 2018-06-02T23:30:25.957 回答