Generatore Onda Quadra 0-1 kHz a Due Canali

----EDIT---- Vedere --> Pag.3 Post #22

Buongiorno a tutti,
sono Stefano, piacere.
le mie conoscenze di arduino sono per lo più didattiche e non avendo mai affrontato il problema dei Timer sono di fronte a un problema con la costruzione di un sistema di simulazione di una ruota fonica e due sensori.

lo scopo del progetto è questo:

  • tramite il nostro Arduino simulare un Motore che possa variare la sua velocità dalla frequenza di 1 Hz a 4 kHz. per la lettura della velocità del motore si utilizza (nella pratica) una ruota fonica e dei sensori(vedi foto allegata) il tutto poi dovrà essere mostrato in un display LCD 20x4 dove tramite un menù si potranno variare anche alcuni parametri come n° denti, diametro ruota.
  • le variabili in gioco sono quindi:
  • frequenza (1 Hz - 4 kHz)
  • n° denti
  • diametro ruota
  • numero sensori (tengo fisso a due)
  • duty-cycle (l'ideale è compreso tra il 60% e l'80%)
  • sfasamento tra i sensori

fin'ora ho scritto questo codice:

#include <PWM.h>

int out_1 = 3;                // il pin frequenza 1
int out_2 = 9;                // il pin frequenza 2
int in_1=2;                   // il pin per passare tra la regolazione fine e i 4 kHz
int k=100;                    // numero di passi

float period;                 // periodo in microsecondi
float passo=0;                // valore in microsecondi per il passo

int cnt_1=0;                  // contatore per la prima forma d'onda
int cnt_2=0;                  // contatore per la seconda forma d'onda

int dtyValue;                 // valore del dutycycle in percentuale
 
int frqLowValue, frqLowValue_new; // valore frequenza da potenziometro
int dly, dly_new;                 // valore di sfasamento tra i due segnali  
int aggCount = 0;                 // contatore per rallentare il ricalcolaParametri
int frequency, frequency2;        // le due frequenze dei segnali a 4 kHz

void setup() 
{
 InitTimersSafe();                                    //inizializza il valore dei timer
 pinMode(2,INPUT);                                    // imposta il PIN 2 come ingresso
 pinMode(3,OUTPUT);                                   // imposta il PIN 3 come uscita
 pinMode(9, OUTPUT);                                  // imposta il PIN 9 come uscita
 dly =  map(analogRead(A3),0,1023,0,99)*k/100;        // valore dello sfasamento tra i due segnali 
 frqLowValue= map(analogRead(A2),0,1023, 4000, 1);    // valore della frequenza bassa
 cnt_2=100-dly*k/100;                                 // imposta il contatore 2 allo stato         
}

void ricalcolaParam()
{
    dtyValue= (map(analogRead(A1), 0, 1023, 1, 99))*k/100;    //valore duty-cycle
    frqLowValue_new= map(analogRead(A2),0,1023, 4000, 1);     //valore della frequenza bassa
    dly_new =  map(analogRead(A3),0,1023,0,99)*k/100;         //valore del delay di sfasamento
    
  if (dly_new!=dly)
  {
    dly=dly_new;
    cnt_1=0;
    cnt_2=100-dly;
    digitalWrite(out_2, LOW);
  }
  if (frqLowValue_new!= frqLowValue)
  {
    frqLowValue=frqLowValue_new;  
    period= (float)(1000000/frqLowValue);               //periodo in microsecondi
    passo= (float)period/k;                             //passo in microsecondi
  }
}




void loop() 
{
   if(digitalRead(in_1)==HIGH)
   {
      frequency = SetPinFrequencySafe(out_1, 4000);      //  frequenza 1 impostata a 4 kHz
      pwmWrite(out_1 , 240);                             //   imposto il dutycycle all'95 % circa 
      
      frequency2 = SetPinFrequencySafe(out_2, 4000);     //  frequenza 2 impostata a 4 kHz
      pwmWrite(out_2 , 240);                             //  imposto il dutycycle all'95 % circa
   }
   else
   {
    
       if(cnt_1==0)
       {
         aggCount++;                                      //aggCount: contatore per rallentare l'acquisizione dei parametri
       }
       if (aggCount == 50)                                //ogni periodi ricalcola il parametro 
       {
         ricalcolaParam();
         aggCount = 0;
       }

      
       if (cnt_1<dtyValue)
       {
        digitalWrite(out_1, HIGH); 
       }
       if (cnt_1>=dtyValue)
       {
        digitalWrite(out_1,LOW);
       }
       if (cnt_2<dtyValue)
       {
        digitalWrite(out_2,HIGH);
       }
       if (cnt_2>=dtyValue )
       {
        digitalWrite(out_2, LOW);
       }
       
      cnt_1=(cnt_1+1)%k;
      cnt_2=(cnt_2+1)%k;
      delayMicroseconds(passo);
   }
}

Con questo codice sono riuscito a creare questa situazione:

  • variazione da circa 1 Hz a 200 Hz tramite regolazione Fine con variazione del dutycycle e dello sfasamento tra le due onde quadre.
  • tramite uno Ingresso abilito la regolazione fine oppure la frequenza fissa di 4 kHz.

ora io vorrei invece riuscire a sfasare i due segnali ad alta frequenza e poter variare cosicchè da poter variare il duty dal 60 all'80%(ora fisso al 95%). inoltre inserire la dipendenza dal diametro della ruota e numero di denti.
tramite la funzione SetPinFrequencySafe ho visto che posso impostare una frequenza e questa farla variabile però se le vado a variare durante il Loop i due segnali si sfasano in modo variabile e mai fisso.
tramite invece pwmWrite si varia il duty (che accetta valori da 0 a 255 )

ho provato a leggere e capire il funzionamento dei Timer per farlo tutto tramite i prescaler ecc ecc..ma da quel poco che ho capito è che se utilizzo uno timer esempio Timer1 le due forme d'onda sono in quadratura di fase. io ora vedo con l'oscilloscopio che a 4 kHz le due onde sono sfasate di 19 microsecondi.

Come vedete ci sono molte idee ma confuse.
qualcuno riesce a darmi una manina?

grazie

Simulatore_ ruota.JPG

Simulatore_ ruota.JPG

Benvenuto. Essendo il tuo primo post, nel rispetto del regolamento, ti chiediamo cortesemente di presentarti QUI (spiegando bene quali conoscenze hai di elettronica e di programmazione ... possibilmente evitando di scrivere solo una riga di saluto) e di leggere con attenzione il su citato REGOLAMENTO... Grazie.
Qui una serie di link utili, non inerenti al tuo problema:

Il cross-posting è vietato.

la ruota fonica è stata inventata per misurare la velocità angolare di un albero motore cioe le sue lievi accelerazioni/decelerazioni che fa in un giro da questi dati si possono ricavare vitali informazioni
se per caso vuoi fare una centralina per controllo motori
ti anticipo che una ecu ha circa 100 ingressi/uscite, e quasi tutte servono a qualcosa :o

io non devo fare un centralina per controllo motori ma una sorta di generatore di onde quadre variabili in sfasamento e duty-cycle.

>stevepizz: prima di tutto e prima di continuare, come ha già fatto Nid69ita, nel rispetto del regolamento (… punto 13, primo capoverso), ti chiedo cortesemente di presentarti QUI (spiegando bene quali conoscenze hai di elettronica e di programmazione ... possibilmente evitando di scrivere solo una riga di saluto) e di leggere con MOLTA attenzione il su citato REGOLAMENTO ...

... dopo di che, in conformità al suddetto REGOLAMENTO, punto 13, il cross-posting è proibito (anche tra lingue diverse), per cui, il tuo thread duplicato, nell'area di lingua Inglese, è stato rimosso e ti prego di NON duplicare ulteriormente i tuoi post in varie aree del forum. Grazie,

Guglielmo

@gpb01, @nid69ita presentazione fatta. chiedo perdono per il cross-posting. speravo che dall'altra parte del mondo ci fosse qualcuno che dava una mano...
ad ogni modo ripropongo il problema..

chiarimenti su Timer arduino alla fine della fiera. (ho già letto il loro funzionamento su vari siti e documenti).
quello che devo fare io è una sorta di generatore di onde quadre.

2 segnali che possono variale la loro frequenza indipendentemente oppure insieme,
regolazione del duty dei due segnali
sfasamento tra i due segnali di uscita

sono riuscito a generare due segnali ma con la stessa frequenza e duty variabile.
ora ho visto che se lo faccio tramite i delayMicroseconds() posso arrivare a 200 Hz circa.
se lo faccio con i Timer allora arrivo anche a 4 kHz che è dove voglio arrivare.

il problema sta nel fatto che tramite i Timer e in particolare la libreria <PWM.h> posso variare la frequenza in maniera lineare ma i due segnali non restano in fase (vedo i due segnali che continuano a cambiare lo sfasamento tra loro).

se invece imposto 4 kHz oppure divido le frequenze a metà ogni volta (31 Hz, 62 Hz, 125 Hz, 250 Hz...) vedo che i segnali non si sfasano ma cambiando la frequenza ottengo uno sfasamento fisso random mentre io invece vorrei ottenere un determinato sfasamento fisso (50% tra i due segnali).

qualcuno riesce a darmi una mano?

grazie

Quello che vuoi fare non è possibile con un AVR 8 bit, al massimo puoi ottenere due onde rettangolari con la stessa frequenza, duty cycle variabile e fase variabile, oppure due segnali con frequenza diversa e duty variabile, ma non con fase regolabile a piacere.
Per fare quello che chiedi devi andare su processori molto più complessi, p.e. gli STM32, e c'è un bel po di lavoro software per gestire correttamente le risorse hardware.
In alternativa potresti usare un ic sintetizzatore DDS a doppio canale, in questo modo puoi fare quello che chiedi però i costi sono relativamente alti e c'è sempre un discreto lavoro software.

volendo potrei fare a meno dello sfasamento tra i due segnali e magari mettere uno sfasamento fisso impostato ad esempio al 40-50%.

magari provando con un Arduino 2 riesco ad ottenere risultati migliori?

@astrobeed: si riesce a fare tramite i delayMicro oppure i timer secondo te?

vi inserisco parte del codice aggiornato che ho scritto fino adesso:

#include <PWM.h>
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>



LiquidCrystal_I2C lcd(0x27, 20, 4);

#define pinFrqHigh A1
#define pinDelay A2
#define pinDuty A3
#define pinFrqLow A0
#define pinSwitch 2
#define pinSx 10
#define pinDx 11 
#define pinUp 12
#define pinDwn 13




// funzione per assegnare la frequenza in base al trimmer
int assegnaFrq(int frq)
{ 

  if (frq<60){return frqHigh=15;}
  if (frq>=60 && frq<200){return frqHigh=62;}
  if (frq>=200 && frq<300){return frqHigh=125;}
  if (frq>=300 && frq<400){return frqHigh=250;}
  if (frq>=400 && frq<500){return frqHigh=500;}
  if (frq>=500 && frq<620){return frqHigh=1000;}
  if (frq>=620 && frq<750){return frqHigh=2000;}
  if (frq>=750){return frqHigh=4000;}
   
}

void stampaLCD()
{
  lcd.clear();
  if (hiLo==LOW)
  {
      
      lcd.setCursor(0,1);
      lcd.print("Frequenza: ");
      lcd.print(frqLCD);
      lcd.print(" Hz");
    
      lcd.setCursor(0,2);
      lcd.print("Duty-cycle: ");
      lcd.print(dtyStamp);
      lcd.print(" %"); 
      
      lcd.setCursor(0,3);
      lcd.print("Sfasamento: ");
      lcd.print(dly);
      lcd.print(" %");   
    
      lcd.setCursor(0,0);
      lcd.print("Giri/min: ");
      lcd.print(giriMin);  
  }
  else
  {
    lcd.setCursor(0,1);
      lcd.print("Frequenza: ");
      lcd.print(frqLCD);
      lcd.print(" Hz");
    
      lcd.setCursor(0,2);
      lcd.print("Duty-cycle: ");
      lcd.print(dtyStamp); 
      lcd.print(" %"); 
      
      lcd.setCursor(0,3);
      lcd.print("N Denti: ");
      lcd.print(nDenti);   
    
      lcd.setCursor(0, 0);
      lcd.print("Giri/min: ");
      lcd.print(giriMin);     
  }

}

void letKeypad()
{
     //lettura Keypad
    sx=digitalRead(pinSx);
    dx=digitalRead(pinDx);
    up=digitalRead(pinUp);
    dwn=digitalRead(pinDwn); 
    
    if (dx==HIGH){nDenti++;
    stampaLCD();}
    if (sx==HIGH){
      if(nDenti>=1)
        {
          nDenti--;
          stampaLCD();
        }
    }

    if (up==HIGH)
    { 
      if(row>=0){
        row--;
      }
      else{
        row=0;
      }
    }
    if (dwn==HIGH)
    {
      if (row<=3){
       row++;
      }
      else{
        row=3;
      }
      
    }
  // lcd.setCursor(19,row);
  // lcd.cursor();
  
}


void ricalcolaParam()
{
      dtyValue_new= (map(analogRead(pinDuty), 0, 1023, 1, 99))*k/100;    //valore duty-cycle
      frqLowValue_new= map(analogRead(pinFrqLow),0,1023, 1, low);     //valore della frequenza bassa
      dly_new =  map(analogRead(pinDelay),0,1023,0,99)*k/100;         //valore del delay di sfasamento
      dtyLim= map(dtyValue,0,99,140,229);
      
      count = map(frqLowValue,low,1,50,1);
        //lettura dei valori della pulsantiera
        letKeypad();
        // 
      if(dtyValue_new!=dtyValue)
      {
          dtyValue=dtyValue_new;
          stampaLCD();
      }

      if (dly_new!=dly)                                             // aggiorna solo se i parametri sono cambiati
      {
          dly=dly_new;
          cnt_1=0;                                                  // azzera il contatore 1
          cnt_2=100-dly;                                            // setta il contatore 2 allo sfasamento corretto
          digitalWrite(out_2, LOW);                                 // porta il secondo segnale basso
          stampaLCD();
     
      }
      if (hiLo==LOW)
      {
        if (frqLowValue_new!= frqLowValue)                            // aggiorna solo se i parametri sono cambiati
        {
            frqLowValue=frqLowValue_new;
            frqLowStamp=map(frqLowValue, 1,low, 1, 52);
            frqLCD=frqLowStamp;  
            period= (float)(1000000/frqLowValue);                     // periodo in microsecondi
            passo= (float)period/k;                                   // passo in microsecondi
             //calcolo dei giri al minuto
            giriMin=(frqLCD*60.0)/nDenti;
            dtyStamp=map(dtyLim, 140,229, 1, 99);
            stampaLCD();
        }
      }
      if (hiLo==HIGH)
      {
          frqHighValue_new=assegnaFrq(analogRead(pinFrqHigh));         
          if (frqHighValue_new!= frqHighValue)                            // aggiorna solo se i parametri sono cambiati
          { 
             frqHighValue=frqHighValue_new; 
             frqHigh=frqHighValue;
             frqLCD=frqHigh; 
             //calcolo dei giri al minuto
             giriMin=(float)(frqLCD*60.0)/nDenti;      
             dtyStamp=map(dtyLim, 140,229, 55, 90);   
             stampaLCD();  
          } 
      }

}


void loop() 
{
  hiLo= digitalRead(pinSwitch);
     if(hiLo==HIGH)                                            // se hiLo è alta allora ho il range di frequenze alto  
     { 
         aggCount_1++;                                           // aggCount: contatore per rallentare l'acquisizione dei parametri
         if (aggCount_1 == 500)                                  // ogni 500 periodi di clock ricalcola il parametro 
         {
           ricalcolaParam();
           aggCount_1 = 0;
         }
        frequency = SetPinFrequencySafe(out_1, frqHigh);          //  frequenza 1 impostata secondo lettura trimmer
        pwmWrite(out_1 , dtyLim);                                 //  imposto il dutycycle per la frquenza 1
        frequency2 = SetPinFrequencySafe(out_2, frqHigh);         //  frequenza 2 impostata secondo lettura trimmer
        pwmWrite(out_2 , dtyLim);                                 //  imposto il dutycycle per la frequenza 2
        
     }
     else                                                      // se hiLo è bassa passa alla modalità di regolazione fine frequenza
     { 
         if(cnt_1==0) { aggCount_2++; }                                     //aggCount: contatore per rallentare l'acquisizione dei parametri
         if (aggCount_2 == count)                               //ogni periodi ricalcola il parametro 
         {
            ricalcolaParam();
            aggCount_2 = 0;
         }     
         if (cnt_1<dtyValue)                                 // se contatore 1 è minore del dutycycle allora tiene alto il segnale 1
         {
            digitalWrite(out_1, HIGH); 
         }
         if (cnt_1>=dtyValue)                                // se contatore 1 è maggiore del dutycycle allora abbassa il segnale 1
         { 
            digitalWrite(out_1,LOW);
         }
         if (cnt_2<dtyValue)                                 // se contatore 1 è minore del dutycycle allora tiene alto il segnale 2
         {
            digitalWrite(out_2,HIGH);                         
         }
         if (cnt_2>=dtyValue )                               
         { 
            digitalWrite(out_2, LOW);
         }
         
        cnt_1=(cnt_1+1)%k;                                   
        cnt_2=(cnt_2+1)%k;                                   
        delayMicroseconds(passo);                           
     }
     
}

astrobeed:
... ic sintetizzatore DDS a doppio canale ...

AD9958 ? ... pero' ti pelano 34 Euro piu iva per il solo chip ... :astonished:

con Arduino proprio niente vero?

stevepizz:
volendo potrei fare a meno dello sfasamento tra i due segnali e magari mettere uno sfasamento fisso impostato ad esempio al 40-50%.

Forse non sono stato abbastanza chiaro, se vuoi due segnali con frequenza diversa scordati di controllare in un qualsiasi modo la loro fase, sarà sempre quasi random, non puoi in nessun modo impostarla ad un valore prefissato.
Per farla breve, se i due segnali hanno la stessa frequenza, perché generati dallo stesso timer tramite i due contatori OCx, puoi variarne, sebbene con dei limiti, fase e duty.
In tutti i casi scordati di farlo con delay() o micros(), devi agire direttamente su i timer.

Qui puoi trovare qualcosa di simile a quello che ti serve, giusto come esempio su come agire su i Timer, leggiti tutto il topic, a pagina 3, post 39, trovi anche il codice e la relativa misura con DSO.

grazie mille dei chiarimenti.

astrobeed:
Per farla breve, se i due segnali hanno la stessa frequenza, perché generati dallo stesso timer tramite i due contatori OCx, puoi variarne, sebbene con dei limiti, fase e duty.
In tutti i casi scordati di farlo con delay() o micros(), devi agire direttamente su i timer.

io son riuscito ad arrivare a 200 Hz utilizzando i delayMicroseconds() e digitalWrite().
volevo provare con un Arduino 2 che ha una frequenza di clock di 84 Mhz e quindi riesco ad arrivare già un bel po' più su di velocità.

astrobeed:
Qui puoi trovare qualcosa di simile a quello che ti serve, giusto come esempio su come agire su i Timer, leggiti tutto il topic, a pagina 3, post 39, trovi anche il codice e la relativa misura con DSO.

ho letto tutto il topic e guardato anche i sorgenti però non è quello che devo fare io in quanto questo genera forme d'onda con la stessa frequenza e stessa fase. a me serve una sfasatura preimpostata e frequenza variabile (uguale tra i due segnali). es. frequenza che varia da 1 a 4 kHz (lineare o a step non importa) su tutti e due i segnali. cioè segnale 1 a 1 kHz e segnale 2 a 1 kHz ma sfasato di un tot percento del periodo.

Il link che ti ho postata era chiaramente solo come esempio di cosa si può fare agendo direttamente su i timer, infatti ho scritto "puoi trovare qualcosa di simile a quello che ti serve, giusto come esempio su come agire su i Timer", non ho detto che era esattamente quello che ti serve.
Nel codice specifico era necessario simulare due encoder incrementali, che alla fine è una cosa molto simile alla ruota fonica, il problema di fondo è che con gli AVR a 8 bit non puoi regolare la fase a piacere tra due segnali diversi, non credo sia nemmeno possibile sulla DUE, intendo lavorando con i timer perché non mi risulta che siano sincronizzabili tra loro, mentre è sicuramente possibile su gli STM32 F3 e F4 in quanto i loro flextimer sono sincronizzabili.
Forse con la DUE riesci a farlo sintetizzando i segnali da software, però non usare la delay/micros, usa sempre un timer per gestire le temporizzazioni e usa l'accesso diretto ai GPIO, tramite registri macchina, per commutare lo stato dei pin, la digitawrite è molto lenta.

grazie Astro.
ricominciare il progetto con un STM32 non ci penso proprio in quanto non conoscendo nemmeno l'architettura e il linguaggio di programmazione perderei troppo tempo.

ho provato a fare la commutazione dei pin tramite registri con l'utilizzo del PORTB ... però con questa funzione si imposta il valore di tutti i pin (B dal 8 al 13) io vorrei invece cambiarlo solo di un pin (es 9 oppure 10).

tra l'altro non trovo da nessuna parte un tutorial Serio di come si usano i Timer, la spiegazione di OCRX ecc ecc. Tanti siti ma tutti che non fanno una spiegazione dettagliata ma soprattutto che faccia capire a uno che non li ha mai usati.

ciao

prova a vedere qui.

ciao
pippo72

Dove l'hai letto che accedendo direttamente ai registri macchina si cambia per forza tutto un port ?
Ovvio che se scrivi un intero byte nel registro di un PORT cambi tutti gli otto bit, però l'assembly degli AVR prevede istruzioni specifiche per cambiare i singoli bit di un registro, devi usare le macro bit_is_set e bit_is_clear, vengono tradotte dal compilatore nell'equivalente assembly, solo due cicli macchina per l'esecuzione, ovvero 125 ns.
Le due macro fanno parte delle avrlibc, quindi sono già integrate nel nel compilatore, basta che includi avr/io.h nel codice.

p.s.
Sotto il cofano di Arduino c'è molto di più di quello che si vede :slight_smile:

pippo72:
prova a vedere qui.

Tutto corretto quello che dice Leo però è obsoleto, ora c'è il supporto diretto del compilatore per queste operazioni e si fa tramite le apposite macro predefinite nelle avrlibc.