Posticipo segnale d'ingresso al variare della frequenza dello stesso

Ciao a tutti,
sono incappato in uno scoglio: sto realizzando un sistema di variazione di anticipo della scintilla in un motore due tempi.
Praticamente faccio leggere la frequenza del segnale d'ingresso con arduino UNO tramite una routine trovata in rete che si chiama FreqPeriodCounter e funziona tramite interrupt CHANGE sul pin3. Dopo di che ho una configurazione salvata mi dice il ritardo da impostare sul segnale d'ingresso (onda quadra) in modo che sul pin4 in uscita abbia lo stesso segnale ma ritardato di quanto desidero.
Al variare della freq del segnale quindi ho diversi tipi di ritardo che posso applicare. Il segnale d'ingresso varia da un minimo di 20 ad un massimo di 250Hz.

il problema è che il segnale viene ritardato ma non è stabile, ovvero guardando i due con l'oscilloscopio, quello di uscita balla, cioè sembra che istante per istante il ritardo applicato vari in continuazione anche se la freq del segnale d'ingresso è fissa e quindi lo è anche il ritardo applicato.

Aiutatemi per favore!

Grazie

il codice è questo:

#include "FreqPeriodCounter.h"


#define PUIN 3
#define PUOUT 4

float m[6]={0, 0, 0, 0, 0, 0}; // y=m*x+q
float q[6]={0, 0, 0, 0, 0, 0}; 
int i=0;

unsigned long prec=0;
int delta_t; // in microseconds
int freq; // hertz



const byte counterPin=PUIN; 
const byte counterInterrupt=1;
FreqPeriodCounter counter(counterPin, micros, 0);

void setup()
{
 
  
  attachInterrupt(counterInterrupt, counterISR, CHANGE);

  
  pinMode(PUIN,INPUT); // input signal
  pinMode(PUOUT,OUTPUT); // output signal
  digitalWrite(PUOUT, LOW);

   
  //  configuration 2000rpm 0°, 4000rpm 3°, 6000rpm 5.5°, 8000rpm 7.5°, 10000rpm 10° 
    delta_t_opt[0]=0;delta_t_opt[1]=125;delta_t_opt[2]=152;delta_t_opt[3]=156;delta_t_opt[4]=160; 
    freq_max[0]=33;freq_max[1]=67;freq_max[2]=101;freq_max[3]=133;freq_max[4]=167; 
    
    for(i=0; i<5; i++)
    {
      m[i+1]=(float)(((delta_t_opt[i+1]-delta_t_opt[i])*1.0)/((freq_max[i+1]-freq_max[i])*1.0)); 
    }
    m[0]=(float)((delta_t_opt[0]*1.0)/(1.0*freq_max[0])); 
   
    for(i=0; i<5; i++)
    {
      q[i]=(float)((delta_t_opt[i]*1.0)-m[i+1]*freq_max[i]); 
    }
    
    
}

void loop(void)
{
       
          val=digitalRead(PUIN);
          
         if(counter.ready()) {freq=counter.hertz();}
           
          if(freq<freq_max[0]) //make the semi-lines in every frq range and y=m*x+q -> delta_t=m[]*freq+q[]
          {
            delta_t=(int)(m[0]*freq+0.5);
          }
          if(freq>=freq_max[0] && freq<freq_max[1])
          {
            delta_t=(int)((m[1]*freq+q[0])+0.5);    
          }
          if(freq>=freq_max[1] && freq<freq_max[2])
          {
            delta_t=(int)((m[2]*freq+q[1])+0.5); 
          }          
          if(freq>=freq_max[2] && freq<freq_max[3])
          {
            delta_t=(int)((m[3]*freq+q[2])+0.5);
          }          
          if(freq>=freq_max[3] && freq<freq_max[4])
          {
            delta_t=(int)((m[4]*freq+q[3])+0.5);
          }          
          if(freq>=freq_max[4] && freq<freq_max[5])
          {
            delta_t=(int)((m[5]*freq+q[4])+0.5);
          }
          
          
          if(micros()-prec>=(unsigned long)(delta_t)) //wait until the delay and then make the out signal
            {
              digitalWrite(PUOUT,val); 
              prec=micros();
            }    
             
      
	
}

void counterISR() // for the freq counter
{ 
  counter.poll();
}

Ciao :slight_smile:
Scusa ma 250Hz sono la bellezza di 250*60==> 15000 giri al minuto ??? sono tantini :smiley:
Le mia moto è da pista (yz 125 preparata) e dubito fortemente che superi i 12000...
Comunque secondo me a prima vista il problema sono gli IF, devi farli più veloci o comunque devi ottimizzare il codice..

Anche io avevo un yz125 2009 :slight_smile:

Comunque, a parte gli if il problema persiste anche se li togli.
Provare per credere: basta impostare che dopo una digitalread dell'ingresso si fa l'if con la micros all'interno e con un valore di 200 us per esempio si fa generare il segnale di uscita. sull'oscilloscopio il segnale di uscita si sposta sempre!

Quoto Ratto93, in effetti il ritardo calcolato tramite quella sequenza di "if" potrebbe portare a una sorta di "battimento" tra trigger e il successivo ritardo. Una soluzione da provare potrebbe consistere nell'utilizzo di un timer in configurazione "overflow" o CTC, usato come "monostabile", ovvero fermato ad ogni "ciclo" di delay, che preleva il ritardo (espresso in questo caso come valori prima dell'overflow), direttamente da una tabella lookup; questo dovrebbe consentirti una maggiore velocità di esecuzione.
Certo, bisogna lavorarci un po' su e fare molte prove.

Mi puoi aiutare per le soluzioni che hai detto?

Io avevo pensato che quando il segnale di ingresso diventa alto allora si fa partire un timer che conta il valore di delay da applicare e quando va in overflow genera il segnale in uscita..

Come si può agire?

Sui timer mi sono letto della roba anche tramite altre discussioni ma non ho trovato ciò che mi serve.

Io avevo pensato che quando il segnale di ingresso diventa alto allora si fa partire un timer che conta il valore di delay da applicare e quando va in overflow genera il segnale in uscita..

Si, infatti pensavo a qualcosa di questo genere.
Puoi prendere "spunto", idee e punto di partenza da questo codice:

void setup() {
  
  /* un fronte di salita su INT0 genera un interrupt - pin 2 */
  EICRA |= (1<<ISC01) | (1<<ISC00);  
  
  /* Abilita interrupt esterni sul piedino. */
  EIMSK |= (1<<INT0);

  /* INT0 come INPUT */
  DDRD  &= ~(1<<DDD2);  // PD2 input (INT0) (pin 2)  
  
  /* Gestione Timer1 in Overflow per generare il delay sull'impulso */
  TIMSK1 &= ~(1<<TOIE1);    // Timer disattivato
  TCCR1A &= ~((1<<WGM11) | (1<<WGM10));  
  TCCR1B &= ~((1<<WGM12) | (1<<WGM13));      
  TIMSK1 &= ~(1<<OCIE1A);  
  
  /* PRESCALER (secondo l'uso)
    ad esempio qui 1024
  */  
  TCCR1B |= (1<<CS12)  | (1<<CS10); 
  TCCR1B &= ~(1<<CS11);        
  
  // Imposta delay desiderato...da prelevare da una tabella di valori pronta (lookup)
  TCNT1H = 0xXX;  
  TCNT1L = 0xYY;
 
}

/* Fronte di salita ricevuto...Fa partire Timer per delay */
ISR(INT0_vect) {
    TIMSK1 |= (1<<TOIE1);      
}

/* Overflow timer */
ISR(TIMER1_OVF_vect) {  
  TIMSK1 &= ~(1<<TOIE1);  // ferma Timer1  
  TCNT1H = 0xXX;          // ricarica timer per il prossimo impulso
  TCNT1L = 0xYY;
  /*
    Qui sono eseguite le operazioni da svolgere dopo il delay
    (partenza impulso)

  */
}

In pratica ogni volta che si riceve un fronte alto sul pin 2 parte Timer1 che dovrà essere opportunamente caricato con un valore che rappresenta il "delay" desiderato in base alla circostanze (preso da una tabella precompilata), quando il timer va in overflow esso viene "bloccato" (modalità monostabile), ricaricato col prossimo delay e in questa circostanza si possono eseguire le operazioni richieste (partenza impulso).
Ovviamente nel "loop" si dovranno svolgere le operazioni relative alla scelta del valore di delay da inserire tramite le istruzioni

 TCNT1H = 0xXX;          // ricarica timer per il prossimo impulso
  TCNT1L = 0xYY;

E' solo un modo diverso di affrontare il problema, non è detto che sia quello più efficace, ma in mancanza di altre soluzioni vale la pena tentare; bisognerà studiarci un po' su ma, mal che vada, ti sarai fatta una cultura sui timer :slight_smile:

Grazie mille per l'aiuto lo apprezzo molto!

Adesso provo a guardare ed implementare il codice!

Una domanda: come posso creare una tabella pronta per valori già caricati?
Perchè dovrei creare un delay per ogni hertz di frequenza rilevato e metterlo in una tabella.

Beh, qui la cosa si può trattare in un sacco di modi diversi...bisognerebbe scoprire se ci sono dei legami tra freq e delay per vedere se si possono utilizzare formule al posto della tabella...diciamo che non ho dati sufficienti per poterti dare consigli utili.

Volendo, ad esempio, sfruttare l'idea della tabella (metodo "brute-force"), credo ti convenga preparare un array con tutti i valori di delay che servono, indicizzato con i valori delle possibili frequenze. Ovviamente i valori di delay per il timer devono essere calcolati prima, uno per ogni frequenza, ma con un foglio di calcolo qualsiasi (tipo Excel) è un'operazione da pochi minuti.

uint16_t  lookupTable[] = {0xVAL1, 0xVAL2, ...., 0xVALn};

dove l'indice '0' corrisponderà alla minima frequenza gestita dal programma e 0xVAL1 sarà la costante di ritardo calcolata per quella frequenza.
Se FREQ_MIN è la minima frequenza e f è la frequenza letta, puoi prelevare il valore di delay semplicemente con:

uint16_t  delay = lookupTable[f - FREQ_MIN];

e spedirlo direttamente ai registri con un'istruzione tipo:

TCNT1H = (delay & 0xFF00) >> 8;
TCNT1L = (delay &0x00FF);

Di più non saprei dirti...

Grazie ancora!

Ho notato però una cosa con l'oscilloscopio. Se nel codice che ho scritto togliessi tutti i conti sul delta t da applicare, ovvero togliessi tutto quel blocco di if in uscita avrei comunque sempre un ritardo rispetto il fronte d'ingresso di 24us, con o senza i calcoli di mezzo!

E questo non so da cosa deriva ma comunque non deriva dal fatto che faccio tutti qui calcoli o sbaglio?
quindi non è necessario creare una tabella a priori se i valori me li posso comunque tirar fuori run time come già faccio no?

C'è qualcuno?

ciao,
avevo in mente lo stesso progetto l'anno scorso. ci ho studiato su per un.. po, poi abbandonato per mancanza di tempo. possiamo riprendermi un po insieme se vuoi.

Si certo va bene...sto incontrando problemi nell'implementazione del codice di dalubar.

nella funzione chiamata a causa dell'interrupt mi viene fuori questo errore:

core.a(WInterrupts.c.o): In function __vector_1': C:\Users\Michele\Desktop\arduino-1.0-windows\arduino-1.0\hardware\arduino\cores\arduino/WInterrupts.c:230: multiple definition of __vector_1'

come conseguenza di questo codice

ISR(INT0_vect) {
    TIMSK1 |= (1<<TOIE1);      
}

Dovrebbe far partire il timer1 in presenza dell'interrupt.

Sapete dirmi cosa significa?

Ciao, scusa se non ho risposto prima ma non mi sono accorto dei messaggi.
Fai una cosa, prova questo sketch, so per certo che funziona perchè l'ho appena provato. E' identico a quello che ti ho postato, solo che fa accendere il led sul pin 13 come prova dell'avvio e successivo stop del timer.
Collega il pin 2 con una pullup da 10K (ma anche 1K va bene). Se tutto funziona correttamente, collegando il pin a massa (non importa quanto a lungo) dovrebbe accendersi il led 13 e spegnersi dopo circa 1 secondo. In questo modo, il sistema funziona come un debounce per pulsanti o un monostabile.
Se ti dà problemi di compilazione o non fa quel che deve allora le cause sono da ricercare altrove.

volatile boolean isTimerOn;

void setup() {
  pinMode(13, OUTPUT);
  
  /* un fronte di salita su INT0 genera un interrupt - pin 2 */
  EICRA |= (1<<ISC01) | (1<<ISC00);  
  
  /* Abilita interrupt esterni sul piedino. */
  EIMSK |= (1<<INT0);

  /* INT0 come INPUT */
  DDRD  &= ~(1<<DDD2);  // PD2 input (INT0) (pin 2)  
  
  /* Gestione Timer1 in Overflow per generare il delay sull'impulso */
  TIMSK1 &= ~(1<<TOIE1);    // Timer disattivato
  TCCR1A &= ~((1<<WGM11) | (1<<WGM10));  
  TCCR1B &= ~((1<<WGM12) | (1<<WGM13));      
  TIMSK1 &= ~(1<<OCIE1A);  
  
  /* PRESCALER (secondo l'uso)
    ad esempio qui 1024
  */  
  TCCR1B |= (1<<CS12)  | (1<<CS10); 
  TCCR1B &= ~(1<<CS11);        
  
}

/* Fronte di salita ricevuto...Fa partire Timer per delay */
ISR(INT0_vect) {
   if (!isTimerOn) {
      // Imposta delay desiderato...da prelevare da una tabella di valori pronta (lookup)
      TCNT1H = 0xC2;  
      TCNT1L = 0xF7;
      TIMSK1 |= (1<<TOIE1); 
      digitalWrite(13, HIGH);
      isTimerOn = true;
   }
}

/* Overflow timer */
ISR(TIMER1_OVF_vect) {  
  TIMSK1 &= ~(1<<TOIE1);  // ferma Timer1  
  TCNT1H = 0xC2;  
  TCNT1L = 0xF7;
  isTimerOn = false;
  /*
    Qui sono eseguite le operazioni da svolgere dopo il delay
    (partenza impulso)

  */
  digitalWrite(13, LOW);
}

void loop() {
  
}

EDIT: già che c'ero ho fatto qualche piccola miglioria che stabilizza molto i tempi di output del timer...

mamma mia, mi usate i registri per impostare l'interrupt (quando attachInterrupt anadava più che bene) è poi mi usate la digitalRead(lentissima) quando potreste usare le macro.

multiple definition of `__vector_1'

ti sei dimenticato di togliere gli include a llibrerie esterne? a quanto pare una di esse sta già usando quella ISR... magari i problemi che avevi erano proprio due librerie che cozzavano.

Ciao, ho provato, ma non funziona proprio benissimo.
Vorrei proporre un idea: se utilizzo il timer2 per contare fino al valore di delay desiderato e poi viene generato un interrupt quando ha finito e nella funzione che gestisce l'interrupt si genera il segnale di uscita e si carica un nuovo valore di delay?

ho trovato questo codice in rete e funziona con il timer1: ho provato a modificarlo per usarlo con il timer2 ma non funziona, ho settato il prescaler a 32 ma niente. questo dovrebbe far cambiare di stato un led quando il conteggio è raggiunto.

#define ledPin 13

void setup()
{
  pinMode(ledPin, OUTPUT);
  
  // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 31250;            // compare match register 16MHz/256/2Hz
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  interrupts();             // enable all interrupts
}

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);   // toggle LED pin
}

void loop()
{
  // your program here...
}

Provate a dare un occhio per modificarlo per questa applicazione. Le ho provate tutte ma forse mi sfugge qualcosa!

Scusate avevo fatto un errore: il programma di dalubar funziona correttamente. Nello sketch c'è scritto di impostare il valore di delay nei registri però bisogna impostare il valore di partenza in modo che quello di overflow - partenza =delay, giusto?

esatto, anche se non è consigliatissimo da fare, devi ricordarti di farlo ad ogni overflow. se non erro puoi invece impostare il valore di overflow: questa tecnica è più pulita. Mettiamo il caso in cui perdi un interrupt dal timer perchè facevi altro, dato che non risetti il valore di partenza, che ripartirà da 0, non solo avrai perso un interrupt ma anche il prossimo verrà generato in modo erroneo

L'uso di un valore iniziale del contatore si fa quando l'intervallo da misurare non può essere ottenuto usando solo il prescaler.

Vorrei usare il timer2 perchè mi permette di ottenere con prescaler più piccoli, valori più piccoli di ritardo. dato che non ho bisogno di generare ritardi superiori a 200us, come si imposta l'overflow per ottenere il valore di delay richiesto con il timer2?

Altra cosa: per il timer 1 faccio TCNT1H e TCNT1L per settare il valore di partenza dal quale contare...ma con il timer 2 come si fa?
Perchè il compilatore mi da errore se faccio TCNT2H

dal sito atmel scarica il manuale (la "bibbia") dell'atmega 328, e da lì vedi i registri di un timer a quale timer corrispondono, vedi i registri dl prescaler, quali valori prescaler puoi settare, e mille altre cose. credimi, se già lavori con i registri troverai sì tante cose in arabo (es. i2c, spi), ma anche delle ottime spiegazioni su TUTTI i registri