Dies ist eine Art "klassisches" Problem, und ich glaube, ich habe eine Lösung, aber ich möchte es mit dieser Community überprüfen. Ich baue ein Projekt mit dem Mikrocontroller ATtiny88 und programmiere in avr-gcc. Ich brauche es, um die folgenden Interrupts zu verarbeiten:
Ich möchte den Timer0-Überlauf verwenden, um einen 32-Bit-Millisekunden-Zeitstempel beizubehalten (ähnlich dem millis()
Konzept von Arduino). Für meine Anwendung kann dem TWI-Interrupt nichts im Wege stehen, da mein ATtiny88 als TWI-Slave fungiert und ich als solcher niemals Interrupts löschen kann. Die Timer ISRs müssen beide deklariert werden NO_BLOCK
.
Da der ATtiny88 ein 8-Bit-Prozessor ist, dauert der Zugriff auf Variablen, die breiter als 8-Bit sind, mit Sicherheit mehrere Zyklen. Die Art und Weise, wie der Arduino-Kern dieses Dilemma handhabt, besteht darin, den Zugriff auf seine interne 32-Bit- timer0_millis
Variable mit einem cli()
innerhalb der millis()
Funktion zu schützen. Dieser Ansatz ist für mich unangenehm, da er bedeutet, dass ich möglicherweise einen TWI-Interrupt verpasse, weil ich anrufe millis()
. Es ist mir egal, ob gelegentlich ein "Tick" der Zeitbasis fehlt, weil ein TWI-Interrupt verarbeitet wird.
Also schrieb ich timebase.h / timebase.c in der Hoffnung, dieses Problem auf Kosten von etwas zusätzlichem Speicherplatz durch doppelte Pufferung zu umgehen .
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
}
Meine Frage ist, löst der Code, den ich hier geschrieben habe, effektiv das Problem, das ich beschrieben habe? Habe ich den atomaren Zugriff auf die Zeitbasis erfolgreich implementiert, ohne Interrupts zu löschen? Wenn nein, warum nicht und wie kann ich meine Ziele erreichen?
Nach einem kurzen Blick darauf zu urteilen, sieht die doppelte Pufferung nach einem guten Ansatz aus. Ich glaube jedoch, dass es immer noch passieren kann, dass Sie einen "ungültigen" Wert zurückgegeben bekommen. Theoretisch könnte der T0-Interrupt mehrmals ausgelöst werden, während Sie auf den Zeitstempel in der timebase_now()
Funktion zugreifen (wenn die Ausführung durch einen anderen ISR > 1 ms verzögert wird) und Ihre "doppelte" Pufferung unbrauchbar machen würde.
Sind Sie sicher, dass es überhaupt erforderlich ist, Ihren Timer0 ISR nicht blockierend zu machen? Da die Hardware alle Low-Level-TWI-Funktionen verarbeitet und die maximale Datenübertragungsgeschwindigkeit 400 kHz beträgt, sollte genügend Zeit für die Verarbeitung von TWI-Daten vorhanden sein. Die Aktualisierung der 4-Byte-Zeitstempelvariablen dauert nur wenige Taktzyklen. Unter welcher Annahme erwarten Sie, TWI-Interrupts zu verlieren?
Sie sagen, dass die Genauigkeit des Zeitstempels nicht kritisch ist. Eine andere Lösung könnte also darin bestehen, einfach ein Flag (oder einen 1-Byte-Zähler) in der T0-ISR zu setzen und die Aktualisierung des Zeitstempels in der Hauptschleife zu handhaben. Dies funktioniert jedoch nur, wenn timebase_now()
von keiner ISR aus aufrufbar sein soll.
Vicatcu
Rev
Vicatcu
Rev
Vicatcu
Vicatcu
Rev