Timing operazioni arduino

Ciao, sto cercando di districarmi con un RTC della Maxim, si tratta del modello DS3231.

In sostanza riesco a leggerlo e scriverlo senza problemi, ma siccome la sua precisione è di 1 secondo, mentre a me serve di 1mS, sto pianificando di usare un contatore esterno da collegare al suo output da 32.768 Hz, e da resettare una volta al secondo tramitela sua uscita INT, settata opportunamente a un interrupt al secondo.

Per il momento, fintanto che il contatore è in viaggio, sto sperimentando con arduino per fare delle prove.

Ho messo due interrupt, uno gestisce l’ingresso a 32Khz e l’altro quello a 1Hz.

Funziona tutto, ma i timing non mi tornano; ho fatto cosi:

#include <Wire.h>
#include <DS3231.h>

#define MAX 30

DS3231 Clock;

unsigned long counter, valori[MAX];
byte i;

void setup()
{
  Serial.begin(115200);
  Clock.enable32kHz(true);
  // Turn on oscillator pin, frequency j
  Clock.enableOscillator(true, false, 0);
  attachInterrupt(digitalPinToInterrupt(2), stampa, RISING);
  attachInterrupt(digitalPinToInterrupt(3), increment, RISING);
}


void loop()
{
  if(i == MAX - 1)
  {
    detachInterrupt(digitalPinToInterrupt(3));
    detachInterrupt(digitalPinToInterrupt(2));
    for (i = 1; i < MAX - 1; i++)
    {
      Serial.println(valori[i] - valori[i - 1]);
    }
  }
}


void stampa()
{
  valori[i] = counter;
i++;
}


void increment()
{
  counter++;
}

Io credo (e spero) che i risultati non tornino perchè ci sono delle operazioni di mezzo, come la copia dei registri del LONG e l’incremento del BYTE che fa da indice.

I valori mi vengono tutti da 32.648Hz, cioè 120 parti in meno rispetto ai famosi 32.768Hz.

In realtà non vengono nemmeno sempre uguali, ma a volte viene 32.649.

secondo voi dove stà il problema ?

Non credo che sia questo il problema, ma counter e valori, essendo usati negli interruput, devono essere dichiarati come 'volatile'.

Ciao, Ale.

>dukeluca86: ... prova a leggere da QUI in poi, perché assegnare così le variabili NON va bene ed inoltre, come ti ha detto ilguargua, tutte le variabile usate nelle ISR devono essere dichiarate "volatile".

Guglielmo

Guglielmo, mi devi scusare ma non ho capito.

Il mio codice a parte le volatile che non ho messo, nelle routine delle ISR fa solo una copia di una variabile, un incremento in un'altra, cioè se non è "atomico" cosi cosa devo fare ?

RIPETO: Sicuramente non ho capito io. Ho guardato pure il link che mi hai postato, ma non ho capito bene dove vuoi parare, mi puoi indirizzare un po di piu ? Grazie in anticipo.

PS Ok ho cercato il reference per il termine volatile, ed ora mi è chiaro come agisce, cioè piu o meno, non ho capito perchè a quelle il problema non succede e alle altre si, sempre di ram si parla, però mi fido e vado avanti.

dukeluca86:
Guglielmo, mi devi scusare ma non ho capito.

... scusa tu ... hai ragione :smiley: ... non avevo notato che prima di usare la variabie, disconnetti gli interrupt e quindi essi non possono interferire. :wink:

Guglielmo

Io credo (e spero) che i risultati non tornino perchè ci sono delle operazioni di mezzo, come la copia dei registri del LONG e l'incremento del BYTE che fa da indice.

I valori mi vengono tutti da 32.648Hz, cioè 120 parti in meno rispetto ai famosi 32.768Hz.

mmmmhhh... non sono sicuro, ma mi pare di capire che hai un generatore di segnali per le due frequenze??

Do per scontato che il generatore c'è ed è affidabile e pertanto non rimane che trovare la causa nel codice.

In teoria ogni 1/32768 = 0,000030517578125s (30,517578125us) si presenta un fronte di salita su INT1 (pin 3), ok.
La variabile counter viene incrementato di 1, prima che si presenti l'altro fronte passano altri 30.51us e questo tempo mi sembra sufficientemente ampio per incrementare la variabile counter anche se questa è grande 32bit. Quindi escludiamo che il problema sia qui e cerchiamo altrove.

Il costo in termini di cicli di clock necessari per "onorare" la IRQ è fisso (non ricordo) e dipende dal prologo ed epilogo presenti in ogni ISR (in più qui non c'è la ISR pura ma una funzione e quindi un costo di chiamata a funzione). Prologo ed epilogo rispettivamente, salvataggio di registri (es SREG) e ripristino degli stessi prima della uscita. Per il costo di prologo ed epilogo basta disassemblare il file .hex prodotto dal compilatore.

L'altra funzione (stampa) fa capo a INT0 (pin 2), stessa considerazione per il costo di chiamata, ma qui c'è un secondo di tempo che considero più che sufficiente.

Quindi dove sta il problema? non lo so, però la Serial.println un poco di tempo se lo prende, non solo per la chiamata, ma anche perché c'è la ISR di millis che avanza e parte della Serial.println è interrompibile e parte è resa atomica. La ISR di millis sul timer0 (OVF_vect) dovrebbe essere attiva e se lo è rompe :slight_smile: e interrompe :smiley:

Tocca guardare il disassemblato, domani lo posto.

Ciao.

Se non sbaglio a fare i conti tra prologo ed epilogo si spendono circa 70 cicli CPU esclusi RET che richiedono 4 cicli.
Ogni ciclo 0.0625us.

La ISR OVF_vect (<__vector_16> )è presente e il codice viene eseguito ogni 1ms. INT0 e INT1 hanno maggiore priorità rispetto a vector_16, tuttavia terminata la ISR INT0 o INT1 se c’è un evento salvato dopo avere eseguito almeno 1 istruzione nel loop si salta al vector_16.

Secondo me la vector_16 rompe troppo e non è in sincro con il segnale INT1 a 32768Hz. Da questo può dipendere il fatto che:

In realtà non vengono nemmeno sempre uguali, ma a volte viene 32.649.

Un altro errore potrebbe essere imputabile al generatore di segnali, che forse non c’è ma è il DS che viene usato per generare questi due segnali.

Ciao.

dukeluca86TimingELF.txt (120 KB)

dukeluca86Timing.txt (30.6 KB)

Grazie mille per l’aiuto. Allora, fornisco dei chiarimenti:

  • Non ho un generatore di segnali, potrei costruirlo con un DDS ma porta via tempo e quindi lo metto come ultima risorsa.

  • Si, uso il DS3231 come generatore, dovrebbe essere molto stabile visto che è pure TCXO, se no che me ne faccio di un RTC se mi perde colpi.

  • A me deve mantenere una stabilità del millisecondo per qualche ora, il tempo di usarlo per delle misurazioni. Posso tollerare un offset di programmazione che poi potrei calcolare, ma almeno il tempo me lo deve scandire sempre nello stesso modo.

  • Nella realtà non userò arduino per calcolare i milliSec ma un contatore a 16 bit (sn74lv8154) che mi da in uscita il dato su una porta parallela a 8 bit che devo leggere in due volte e ricomporre (a 16 bit parallela non l’ho trovata e comunque il micro è a 8 bit). Il dispositivo sarà svuotato al raggiungimento del famoso 1 Hz, che messo sul pin di reset azzererà il contatore.

Tutto da provare ovviamente, meno uso il micro in questo ambito meglio mi sento, perchè è poco sotto il mio controllo con tutti gli interrupt che ci viaggiano sopra.

120 parti su 32.768 sono tante, anche per un rtc impreciso… sono 3,66mS… cioè ogni 300 secondi perde un secondo, cioè circa 5 minuti al giorno, mica da ridere.

Grazie per lo sforzo fatto a smontare il codice, non avrei osato chiedertelo.

Per spegnere il timer0 basta assegnare 0 (zero) al registro TCCRB0 nel setup, sempre qui puoi stampare il valore di millis() 2 volte con in mezzo un delay(1000), i due valori stampati devono essere identici se il timer0 è spento.

Riprovare con il codice attuale e verificare se cambia qualcosa.

120 parti su 32.768 sono tante, anche per un rtc impreciso… sono 3,66mS… cioè ogni 300 secondi perde un secondo, cioè circa 5 minuti al giorno, mica da ridere.

Si certo, però non vedo nel datasheet garanzia che la frequenza sia di 32768Hz, cioè potrebbe oscillare più o meno rapidamente ma stabile nel tempo (e ovviamente al variare della temperatura). C’è da dire che non ho dedicato il giusto tempo al datasheet, e infatti penso che ci sia specificato la tolleranza.

Tutto da provare ovviamente, meno uso il micro in questo ambito meglio mi sento, perchè è poco sotto il mio controllo con tutti gli interrupt che ci viaggiano sopra.

Con il codice attuale l’unico evento che può rompere è il timer0. Durante l’esecuzione di ogni ISR gli interrupt sono disabilitati, quindi il problema non si pone.

Questo a seguire è il codice della ISR OVF di arduino:

#if defined(TIM0_OVF_vect)
ISR(TIM0_OVF_vect)
#else
ISR(TIMER0_OVF_vect)
#endif
{
	// copy these to local variables so they can be stored in registers
	// (volatile variables must be read from memory on every access)
	unsigned long m = timer0_millis;
	unsigned char f = timer0_fract;

	m += MILLIS_INC;
	f += FRACT_INC;
	if (f >= FRACT_MAX) {
		f -= FRACT_MAX;
		m += 1;
	}

	timer0_fract = f;
	timer0_millis = m;
	timer0_overflow_count++;
}

Questo codice viene eseguito ogni 1ms e si prende il suo tempo, se durante l’esecuzione si presenta un fronte su INT1 la relativa ISR viene eseguita con uno slittamento temporale se va bene, se va male durante questa routine si presentano due fronti e uno viene perso.

Ciao.

Ok, proverò a disabilitare il timer0, non ho capito quali sono le conseguenze globalmente, cioè quanti e quali funzioni utilizzano questo timer... spero non vada a rompere le scatole ad altro.

Seconda cosa, ma quindi si può mettere del codeice eseguibile nella ISR dell'interrupt senza fargli chiamare una funzione per gestirla ? Cioè dichiaro semplicemente l'attach dell'interrupt con per esempio dentro un "(...,counter++, RISING)" ? O ho capito male io ?

Terza cosa, ho dato un occhio alla precisione ed è garantito un +-2ppm su un range da 0 a 40 gradi centigradi con alimentazione di 3.3v. 2 ppm non sono malaccio, dovrebbero essere 0,065536 parti su 32.768.

AGGIORNAMENTO:
Ho provato a dare un TCCRB0 = 0; nel setup, ma ho avuto in cambio un errore:

'TCCRB0' was not declared in this scope

Ho sbagliato a scrivere TCCR0B.

TCCR TIMER COUNTER CONTROL REGISTER.
Il numero 0, 1, 2 per i 3 timer
Mentre A, B, C per i 3 registri di ogni timer.

Seconda cosa, ma quindi si può mettere del codeice eseguibile nella ISR dell'interrupt senza fargli chiamare una funzione per gestirla ? Cioè dichiaro semplicemente l'attach dell'interrupt con per esempio dentro un "(...,counter++, RISING)" ? O ho capito male io ?

Si più o meno, il problema e che bisogna leggere il datasheet e accendere e spegnere dei bit di vari registri e quindi non si usa proprio attach ecc.

I vettori di interrupt di INT0 e INT1 sono

ISR(INT0_vect) {
   // qui il codice della routine, quello che hai messo nella funzione

}

Per INT1 duplichi il codice sopra e cambi INT0 con INT1

Per abilitare o meno gli interrupt e configurarli ci sono i registri:
EICRA per configurazione rising o falling
EIMSK per abilitarli

Es:

EICRA = 15; // rinsing edge per INT0 e INT1
EIMSK = 3; // INT0 e INT abilitati

// EIMSK = 0; // per spegnerli al posto di deattach

Ciao.

dukeluca86:
Ok, proverò a disabilitare il timer0, non ho capito quali sono le conseguenze globalmente

Il core Arduino utilizza Timer0 per:

  • la funzione millis()
  • la funzione delay()
  • PWM sui pin 5 e 6
  • per la funzione di "pulse counting" sul pin 5
  • per la funzione di "input capture" sul pin 8

... quando viene fermato ... tutto ciò non funziona più.

Guglielmo