Как сделать снимок изменяющейся переменной (например, таймера) в C? - PullRequest
0 голосов
/ 02 июня 2018

Я сейчас программирую микроконтроллер 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 в качестве глобальной переменной и сделать его равным time , думая, что это можетдинамически захватывать статическую переменную, но, к сожалению, это не так.Кто-нибудь может предложить способ сделать это?

1 Ответ

0 голосов
/ 03 июня 2018

В вашем вопросе скрыто много аспектов.

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

Кстати, пропускная способность может быть значительно улучшена, если избегать использования плавающей запятой там, где это не нужно.

...