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