I am currently programming a Teensy micro-controller and want to setup a pause function for a game. I have been able to create a timer from ISR counter overflows, however I haven't been able to work out how to pause this counter. I have tried:
ISR(TIMER1_OVF_vect) {
if (paused == 0){
overflow_counter ++;
}
}
This seems to make no difference to the counter, the counter just keeps going no matter what instructions I put inside the function. I have tried a variety of if statements and they are ignored - the function just increases the counter for some reason (even if I put overflow_counter --)!
So I have tried to setup another variable that takes a snapshot of the time when the pause button is pressed, then when the game is un-paused it would take another snapshot and calculate the difference. This would then be taken away from the overall time.
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;
}
I have tried setting snapshot_time as a global variable and making it equal to time thinking this may dynamically capture a static variable but unfortunately it doesn't. Can anyone offer a way to do this?
There are many aspects hidden in your question.
1. First of all, the counter variable should be marked volatile
. The compiler is applying different optimizations to variables, so it may, say, load a variable into a register and continue to work with register, assuming it is only the place, where content of the variable is stored. If a variable is declared with the keyword volatile
, then the compiler knows that it could be changed occasionally in any time, therefore the compiler will reload and/or rewrite the variable each time it is accessed. So, it may be declared like this
volatile uint16_t overflow_counter;
The same goes for the paused
variable.
2. You should remember that, if interrupts are not disabled, then timer interrupt can occur between any two processor's instructions. Since processor is 8bit, it access the memory using 8-bit wide bus. That means, to read 16-bit data, it requires 2 instructions. Let's say we copy the counter value into a local variable:
uint16_t counter_snapshot = overflow_counter;
The local variable will allocate two registers and two memory read operations will be performed. Let's imagine the interrupt is happened after first of them, but before second. So, on output you'll have half of the number copied from it's previous value, while the second half from it's new. I.e. the value will be damaged. It will not be happened, if variable is 8-bit and copied by one instruction. But if it is wider, or if it is read-modified-written, then precautions should be taken:
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. As said above, interrupt can happen in any time. That means if you try to read overflow_counter and TCNT1 both, the interrupt can be happened in between, so, result will be not as expected. Especially if reading of those two values is separated by such a long operation as floating-point multiplication. So, workaround may be as follows:
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...
By the way, throughput can be much improved, if avoid using floating point where it is not necessary.