[RISOLTO] Jitter mostruoso!

Ciao a tutti

Su una scheda Arduino Uno sto provando un programma per generare 9 segnali a frequenze diverse. Il problema è che i segnali sono affetti da un jitter mostruoso! Il segnale a 207Hz, per esempio, (4,8ms di periodo) ha un jitter di 200us sul ciclo intero e di 400us (8%!) sul semiperiodo. 400us sono 6480 cicli di clock! Ho cercato un po' in rete, ma non ho trovato una soluzione. Si può fare qualcosa?

void setup()
{
DDRD =0xFF; // Porta D: tutte uscite
DDRB|=0x01; // Porta B: PB0 uscita
}

void loop()
{
if(micros()%7408<3704)PORTD|= (1<<PD0); // 135Hz = 7408us
else                  PORTD&=~(1<<PD0);
if(micros()%5882<2941)PORTD|= (1<<PD1); // 170Hz
else                  PORTD&=~(1<<PD1);
if(micros()%4830<2415)PORTD|= (1<<PD2); // 207Hz
else                  PORTD&=~(1<<PD2);
if(micros()%4064<2032)PORTD|= (1<<PD3); // 246Hz
else                  PORTD&=~(1<<PD3);
if(micros()%3484<1242)PORTD|= (1<<PD4); // 287Hz
else                  PORTD&=~(1<<PD4);
if(micros()%3030<1515)PORTD|= (1<<PD5); // 330Hz
else                  PORTD&=~(1<<PD5);
if(micros()%2666<1333)PORTD|= (1<<PD6); // 375Hz
else                  PORTD&=~(1<<PD6);
if(micros()%2370<1185)PORTD|= (1<<PD7); // 422Hz
else                  PORTD&=~(1<<PD7);
if(micros()%2122<1061)PORTB|= (1<<PB0); // 471Hz
else                  PORTB&=~(1<<PB0);
}

Grazie
Gianluca

ciao

premetto che non so aiutarti, però ti allego questo sopratutto dove Guglielmo fa elenco dei Timer, possono centrare qualcosa con il tuo problema??

MD

Ciao, Matteo
Ti ringrazio, ma è un problema di jitter, cioè di instabilità delle frequenze dei segnali generati, prodotto da interrupt e simili.

Difatti pensavo che non era quel tipo di problema, però tentar non nuoce :slight_smile: :slight_smile:

Spero che qualcuno possa darti una mano ahaha

MD

Uhmm... Mi viene il sospetto che il jitter sia prodotto dalla catena di "if"! Probabilmente, generando un segnale solo sarebbe minore.

Sì, è vero: lasciando un segnale solo il jitter si riduce moltissimo, eliminando gli enormi salti che si vedono ogni tanto e lasciando solo un po' di vero jitter. Per risolvere il problema dovrei almeno rendere i ritardi costanti, aggiustando poi le frequenze...

Prova cosi':

//-------- generazione 9 frequenze -----------------------------------------------

uint16_t Tempo;
uint8_t Mask;

uint16_t Previous_F0 = 0;
uint16_t Previous_F1 = 0;
uint16_t Previous_F2 = 0;
uint16_t Previous_F3 = 0;
uint16_t Previous_F4 = 0;
uint16_t Previous_F5 = 0;
uint16_t Previous_F6 = 0;
uint16_t Previous_F7 = 0;
uint16_t Previous_F8 = 0;

#define PERIOD_F0 7408                // periodo in uS delle frequenze da generare
#define PERIOD_F1 5882                //
#define PERIOD_F2 4830                //
#define PERIOD_F3 4064                //
#define PERIOD_F4 3484                //
#define PERIOD_F5 3030                //
#define PERIOD_F6 2666                //
#define PERIOD_F7 2370                //  
#define PERIOD_F8 2122                //  


//-------------------------------------------------------------------------------------------

void setup()
{
  DDRD =0xFF;                         // Porta D: tutte uscite
  DDRB|=0x01;                         // Porta B: PB0 uscita

  TCCR1A = B00000000;                 // timer 1 base tempi 500nS (prescaler/8) 
  TCCR1B = B00000010;                 //
  TCNT1 = 0x0000;                     // 
  
  noInterrupts();                     // tutti gli interrupt disabilitati
}


//-----------------------------------------------------------------------------------------

void loop()
{
  Mask = 0;
  Tempo = TCNT1;                                // lettura timer 1

  if((Tempo - Previous_F0) >= PERIOD_F0)
  {
    Mask |= (1<<PD0);
    Previous_F0 = Tempo;
  }

  if((Tempo - Previous_F1) >= PERIOD_F1)
  {
    Mask |= (1<<PD1);    
    Previous_F1 = Tempo;
  }

  if((Tempo - Previous_F2) >= PERIOD_F2)
  {
    Mask |= (1<<PD2);    
    Previous_F2 = Tempo;
  }

  if((Tempo - Previous_F3) >= PERIOD_F3)
  {
    Mask |= (1<<PD3);    
    Previous_F3 = Tempo;
  }

  if((Tempo - Previous_F4) >= PERIOD_F4)
  {
    Mask |= (1<<PD4);    
    Previous_F4 = Tempo;
  }

  if((Tempo - Previous_F5) >= PERIOD_F5)
  {
    Mask |= (1<<PD5);    
    Previous_F5 = Tempo;
  }

  if((Tempo - Previous_F6) >= PERIOD_F6)
  {
    Mask |= (1<<PD6);    
    Previous_F6 = Tempo;
  }

  if((Tempo - Previous_F7) >= PERIOD_F7)
  {
    Mask |= (1<<PD7);    
    Previous_F7 = Tempo;
  }

  if((Tempo - Previous_F8) >= PERIOD_F8)
  {
    PORTB ^= (1<<PB0);                              // attuazione frequenza su porta B   
    Previous_F8 = Tempo;
  }

  PORTD ^= Mask;                                    // attuazione frequenze su porta D
}

Ciao
Marco

Ooohh... Funziona!
Grazie, Marco!

Capisco che sommi tutto e poi fai un solo PORTD alla fine (più il PORTB), ma perché fai XOR?
Ah! Semplicemente perché ogni volta che finisce un semiperiodo deve commutare!
Inoltre non riesco a capire perché consideri il periodo completo, anziché il semiperiodo... Ah! E' perché hai impostato una base dei tempi a 500ns anziché 1us!
Però perché nonostante tutti gli if qui va tutto bene, mentre nel mio avevo tutto quel jitter? Ho provato anche a disattivare il noInterrupts(), ma funziona ugualmente bene! E' perché usi TNCT1 anziché micros()?
Come sei arrivato a questo programma? Avevi già sperimentato sulla generazione di segnali?

Mi piacerebbe capire bene come sei arrivato a questo che, seppure semplicissimo, funziona molto bene. Ad esempio, non avevo pensato a commutare tutte le uscite contemporaneamente, dato che le frequenze sono completamente scorrelate tra loro e non ha senso parlare di sincronia.

Grazie
Gianluca

Bellissimo esempio, da tenere sempre presente (è che proprio non ho voglia di andarmi a ristudiare i registri, l'ho già fatto con st6 e PIC, ma si potrebbero fare tante cosine interessanti :confused:)

Secondo me dovrebbe andare ancora meglio cambiando l'aggiornamento delle variabili tempo in questo modo:

if ((Tempo - Previous_F0) >= PERIOD_F0)
{
    Mask |= (1 << PD0);
    Previous_F0 += PERIOD_F0;
}

E anche sostituire tutti i 1 << PDx con costanti precalcolate.

Dataman:
ma perché fai XOR?

Perche` i bittarelli di Mask vanno a uno solo in corrispondenza dei fronti di commutazione.

Sì, appunto: non fa una volta |=1 e un'altra &=0 come facevo io, ma ogni volta che scade il tempo (che è un semiperiodo, per i 500ns al posto di 1us) inverte lo stato del bit.

Facendo += PERIOD_F0; è più preciso, perché non risente del ritardo dovuto a if e Mask|=. Se n'era parlato tempo fa. Ho verificato che non c'è l'errore del 2~3 per mille che rilevo nella versione di Marco (ad esempio leggo 2369us contro 2375us).

Sostituendo PORTD^=(1<<PD0...7); con PORTD^=0b00000001...10000000; non ho rilevato differenze.

Vorrei capire, però, perché con il mio programma avevo quei salti...

Ciao Gianluca,

provo a soddisfare le tue curiosita'.

Però perché nonostante tutti gli if qui va tutto bene, mentre nel mio avevo tutto quel jitter?

Perche' non ti sei preoccupato di considerare i tempi di esecuzione di quello che hai scritto.
Per ridurre il jitter e' fondamentale che il loop giri il piu' velocemente possibile.

Gli if non c'entrano, ci vogliono e non prendono molto tempo, ma scrivere if(micros()%4830<2415) puo' essere comodo per il programmatore , ma non per un povero micro.

Capisco che sommi tutto e poi fai un solo PORTD alla fine (più il PORTB), ma perché fai XOR?

L'hai gia' capito: se tu vedi i due bit in ingresso di uno XOR, uno come un segnale e l'altro come un comando, quando il comando e' =1 il segnale viene invertito e quando e' =0 il segnale resta invariato.

Inoltre non riesco a capire perché consideri il periodo completo, anziché il semiperiodo... Ah! E' perché hai impostato una base dei tempi a 500ns anziché 1us!

Anche questo l'hai gia' capito, inoltre l'AtMega con clock a 16MHz non consente di usare 1uS come base tempi di Timer 1. Se vuoi approfondire devi leggere il datasheet.(Se sei interessato dimmi e spiego meglio).

Ho provato anche a disattivare il noInterrupts(), ma funziona ugualmente bene!

noInterrupts() ci deve essere per due motivi:

  1. Evita che l'interrupt di Timer 0, quello che in Arduino gira sempre per gestire millis() micros() delay() e forse qualcos'altro, interrompa il nostro loop e quindi introduca jitter.

  2. E' indispensablile che ci siano gli interrupt disabilitati per leggere Timer 1, avendo la garanzia di leggerlo sempre correttamente.Se vuoi approfondire devi leggere il datasheet.(Se sei interessato dimmi e spiego meglio).

E' perché usi TNCT1 anziché micros()?

Perche' micros() e' molto piu' pesante, nel senso che prende molto piu' tempo per l'esecuzione, specie se lo chiami 9 volte come hai fatto tu.
Leggere Timer1 e' una cosa velocissima.
Inoltre micros() da' un valore su 32 bit che in questo caso non servono a niente, se non a rendere il tutto piu' pesante.

Come sei arrivato a questo programma? Avevi già sperimentato sulla generazione di segnali?

Mi piacerebbe capire bene come sei arrivato a questo che, seppure semplicissimo, funziona molto bene.

Sono piuttosto anziano ed ho iniziato tanti anni fa come progettista HW digitale.
Ho poi scritto per molti anni in assembler, e quindi a contatto con quello che succede dentro ad un micro.
Di conseguenza, anche oggi che scribacchio in C, mi porto dietro quello che ho fatto quando ero giovane.

@Claudio_FF

Secondo me dovrebbe andare ancora meglio cambiando l'aggiornamento delle variabili tempo in questo modo:

Onestamente non vedo vantaggi ...

E anche sostituire tutti i 1 << PDx con costanti precalcolate.

i 1 << PDx sono delle costanti calcolate dal preprocessore

Ciao
Marco

Ohhh, finalmente qualcosa su cui giocare col mio oscilloscopio FLUKE 97....

:smiley:
Mi piacerebbe se ci fosse un'area del forum in cui condividere i propri esperimenti su piccoli gioielli che si possono realizzare, in contrapposizione a mostri degli anni '80 con schede zeppe di integrati...

@Claudio_FF

Secondo me dovrebbe andare ancora meglio cambiando l'aggiornamento delle variabili tempo in questo modo:

Hai ragione, cosi e’ piu’ preciso.

Sulimarco:
@Claudio_FF

Hai ragione, cosi e’ piu’ preciso.

Paragrafo ottenere intervalli periodici precisi :wink:

Datman:
in contrapposizione a mostri degli anni '80 con schede zeppe di integrati...

Come il mio primo Z80 :slight_smile:
tsp-z1.jpg