Condición de carrera en los temporizadores AVR

4

Estoy intentando implementar un temporizador global, para poder llamar a time_us () en cualquier parte del programa y la función devolverá microsegundos desde el inicio del programa. He hecho esto usando TIMER2 (reloj de 8 bits) e interrupción en el desbordamiento, y contando los desbordamientos:

volatile uint64_t _time_overflow_cnt;

void time_reset(){
    TCCR2A=0; // no pin output,
    TCCR2B=(1<<CS21); // prescaler=8
    TIMSK2=1<<TOIE2; // interrupt on overflow (256 cycles)
    sei();
    TCNT2=0; // reset timer to 0
    _time_overflow_cnt=0;
}

ISR(TIMER2_OVF_vect){
    _time_overflow_cnt++;
}

uint64_t time_us(){
    return (_time_overflow_cnt*256 + TCNT2)/(F_CPU/8/1000000ULL);
}

Sin embargo, este código tiene una condición de carrera, que en mi caso es bastante notable: si reescribimos este código en ensamblador, vemos que el time_us es bastante largo (los cálculos de 64 bits tardan un tiempo) y si TCNT2 se desborda durante Esta vez, los cálculos están sesgados por 256 ciclos completos. Intenté desactivar las interrupciones antes y volver a encenderlas después del cálculo, pero esto no ayuda, solo protege el contador de desbordamiento, mientras que el TCNT2 sigue aumentando. También intenté guardar el contador de desbordamiento, luego guardar TCNT2, verificar si el anterior cambió en el medio, y algunas otras 'soluciones' similares a esta, pero todas ellas tenían algunos problemas de TOCTOU (Tiempo de verificación al Tiempo de uso). ¿Tienes una mejor idea para la implementación?

    
pregunta akrasuski1

2 respuestas

3

Solo haga la recuperación atómica .

#include <util/atomic.h>

 ...

uint64_t time_us(){
    register uint8_t tcnt2
    register uint64_t ovcnt;
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        tcnt2 = TCNT2;
        ovcnt = _time_overflow_cnt;
    }
    return (ovcnt*256 + tcnt2)/(F_CPU/8/1000000ULL);
}
    
respondido por el Ignacio Vazquez-Abrams
2

Yo mismo no pude pensar en una solución que funcionara, así que busqué mucho en Internet, y después de un tiempo, me topé con esta publicación del blog: enlace

Reimplementa la función micros () de Arduino en la plataforma Arduino, porque el escritor quería lograr una mayor precisión del temporizador. Aunque el código estaba escrito en lenguaje Arduino, utilizando llamadas a funciones que no eran AVR, lo traduje a C puro y parecía funcionar bien. A continuación se muestra el código que funciona para mí (los comentarios son del autor; según ellos, él tenía los mismos problemas que antes: TCNT2 desbordado en la función):

uint64_t time_us(){

    cli();
    uint8_t tcnt2_save = TCNT2; //grab the counter value from Timer2
    uint8_t flag_save = IS_HIGH(TIFR2,0); //grab the timer2 overflow flag value
    if (flag_save) { //if the overflow flag is set
        tcnt2_save = TCNT2; //update variable just saved since the overflow flag could have just tripped between previously saving the TCNT2 value and reading bit 0 of TIFR2.
                            //If this is the case, TCNT2 might have just changed from 255 to 0, and so we need to grab the new value of TCNT2 to prevent an error of up
                            //to 127.5us in any time obtained using the T2 counter (ex: T2_micros). (Note: 255 counts / 2 counts/us = 127.5us)
                            //Note: this line of code DID in fact fix the error just described, in which I periodically saw an error of ~127.5us in some values read in
                            //by some PWM read code I wrote.
        _time_overflow_cnt++; //force the overflow count to increment
        TIFR2 |= 0b00000001; //reset Timer2 overflow flag since we just manually incremented above; see datasheet pg. 160; this prevents execution of Timer2's overflow ISR
    }
    uint64_t ticks = _time_overflow_cnt*256 + tcnt2_save; //get total Timer2 count
    sei(); //allow interrupts again
    return ticks/(F_CPU/8/1000000ULL);
}
    
respondido por el akrasuski1

Lea otras preguntas en las etiquetas