Concurrencia de ISR de base de tiempo

6

Este es un tipo de problema "clásico", y creo que tengo una solución, pero quiero examinarla con esta comunidad. Estoy creando un proyecto con el microcontrolador ATtiny88 y estoy programando en avr-gcc. Lo necesito para manejar las siguientes interrupciones:

  • TWI (I2C)
  • Timer0 Overflow
  • Captura del temporizador1

Quiero usar el Desbordamiento de Timer0 para mantener una marca de tiempo de milisegundos de 32 bits (similar al concepto millis() de Arduino). Para mi aplicación, no puedo hacer que nada se interponga en la interrupción TWI porque mi ATtiny88 está actuando como esclavo TWI y, como tal, nunca puedo borrar las interrupciones. Los ISR del temporizador deben declararse NO_BLOCK .

Dado que el ATtiny88 es un procesador de 8 bits, el acceso a las variables que son más anchas que 8 bits es seguro que tomará varios ciclos para completar. La forma en que el núcleo de Arduino maneja este dilema es mediante la protección del acceso a su variable interna de 32 bits timer0_millis con un cli() dentro de la función millis() . Ese enfoque me resulta desagradable porque significa que podría pasar por alto una interrupción de TWI porque llamo a millis() . No me importa que de vez en cuando falte un "tic" de la base de tiempo debido a una interrupción TWI que se está manejando.

Así que escribí timebase.h / timebase.c con la esperanza de evitar este problema a costa de un poco de almacenamiento adicional, mediante doble búfer .

timebase.h

/*
 * timebase.h
 *
 *  Created on: Dec 3, 2012
 *      Author: vic
 */

#ifndef TIMEBASE_H_
#define TIMEBASE_H_

// timer 0 is set up as a 1ms time base
#define TIMER0_1MS_OVERFOW_PRESCALER 3     // 8MHz / 64 = 125 kHz
#define TIMER0_1MS_OVERFLOW_TCNT     131   // 255 - 131 + 1 = 125 ticks

void timebase_init();
uint32_t timebase_now();

#endif /* TIMEBASE_H_ */

timebase.c

/*
 * timebase.c
 *
 *  Created on: Dec 3, 2012
 *      Author: vic
 */
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include "timebase.h"

// double buffered timestamp
volatile uint32_t timestamp_ms_buf[2] = {0, 1};
volatile uint8_t  timestamp_ms_buf_volatile_idx = 0;

void timebase_init(){
    // set up the timer0 overflow interrupt for 1ms
    TCNT0  = TIMER0_1MS_OVERFLOW_TCNT;
    TIMSK0 = _BV(TOIE0);

    // start timer0
    TCCR0A = TIMER0_1MS_OVERFOW_PRESCALER;
}

// fires once per millisecond, don't block
// can't miss TWI interrupts for anything
ISR(TIMER0_OVF_vect, ISR_NOBLOCK){
    TCNT0 = TIMER0_1MS_OVERFLOW_TCNT;

    // modify the volatile index value
    timestamp_ms_buf[timestamp_ms_buf_volatile_idx] += 2;

    // change the volatile index
    timestamp_ms_buf_volatile_idx = 1 - timestamp_ms_buf_volatile_idx; // always 0 or 1
}

uint32_t timebase_now(){
    uint8_t idx = timestamp_ms_buf_volatile_idx; // copy the current volatile index
    return timestamp_ms_buf[1 - idx];            // return the value from the non-volatile index
}

Mi pregunta es, ¿el código que he escrito aquí resuelve de manera efectiva el problema que he descrito? ¿He implementado exitosamente el acceso atómico a la base de tiempo sin borrar las interrupciones? Si no es así, ¿por qué no y cómo puedo lograr mis objetivos?

    
pregunta vicatcu

1 respuesta

4

A juzgar por un vistazo rápido, el doble búfer parece un buen enfoque. Sin embargo, creo que todavía puede suceder que obtenga un valor "no válido" devuelto. Teóricamente, la interrupción T0 podría dispararse varias veces mientras accede a la marca de tiempo en la función timebase_now() (si la ejecución se retrasa > 1 ms por otro ISR) y haría que su búfer "doble" sea inútil.

¿Está seguro de que incluso es necesario que su Timer0 ISR no se bloquee? Dado que el hardware está manejando todas las funciones TWI de bajo nivel y la velocidad máxima de transferencia de datos es de 400 kHz, debería haber suficiente tiempo para manejar los datos TWI. Actualizar la variable de marca de tiempo de 4 bytes solo toma unos pocos ciclos de reloj. ¿En qué supuesto espera perder las interrupciones TWI?

Dice que la precisión de la marca de tiempo no es crítica, por lo que otra solución podría ser simplemente establecer un indicador (o contador de 1 byte) en el ISR T0 y manejar la actualización de la marca de tiempo en el bucle principal. Sin embargo, esto solo funciona si se supone que timebase_now() no se puede llamar desde cualquier ISR.

    
respondido por el Rev1.0

Lea otras preguntas en las etiquetas