Go Down

Topic: Output in PPMsum, è possibile? (Read 2792 times) previous topic - next topic

Stefanoxjx



Mi viene il dubbio e chiedo una conferma a voi che il radiocomando invii il segnale solo quando ci sono delle variazioni sui valori
(movimento stick) mentre quando non ci sono variazioni, non invia nulla o invia solo un segnale di conferma che secondo me
potrebbe essere un segnale alto che dura 22.1ms + una pausa di 0.4ms.


Il segnale viene trasmesso di continuo, il tremolio dei servi vuol dire che il tuo segnale non è stabile nel tempo, ovvero non vengono generate sequenze di impulsi perfettamente identiche a parità di condizioni.
Come generi il ppm  e in base a cosa vari le durate dei singoli impulsi ?


Eccomi, scusate il ritardo nella risposta ma nel frattempo ho provato a vedere se ne capivo qualcosa di più e riuscivo a cavarmela da solo............. ovviamente non ci sono riuscito :(
Questo è lo sketch che genera il segnale PPM:
Code: [Select]

#define  Durata         22000
#define  Pausa          400
#define  PPMPin         8 


void setup()
{               
  Serial.begin(9600);
  pinMode(PPMPin, OUTPUT);     
}


void loop()
{
  int  ch[8] = {720, 1120, 1120, 1120, 1120, 1120, 1120, 1120};
  long timeframe;
  timeframe=0;

  //1
  digitalWrite(PPMPin, HIGH);
  delayMicroseconds(ch[0]);
  digitalWrite(PPMPin, LOW);
  delayMicroseconds(Pausa);
  timeframe=timeframe+ch[0]+Pausa;
 
  //2
  digitalWrite(PPMPin, HIGH);
  delayMicroseconds(ch[1]);
  digitalWrite(PPMPin, LOW);
  delayMicroseconds(Pausa);
  timeframe=timeframe+ch[1]+Pausa;
 
  //3
  digitalWrite(PPMPin, HIGH);
  delayMicroseconds(ch[2]);
  digitalWrite(PPMPin, LOW);
  delayMicroseconds(Pausa);
  timeframe=timeframe+ch[2]+Pausa;

  //4
  digitalWrite(PPMPin, HIGH);
  delayMicroseconds(ch[3]);
  digitalWrite(PPMPin, LOW);
  delayMicroseconds(Pausa);
  timeframe=timeframe+ch[3]+Pausa;

  //5
  digitalWrite(PPMPin, HIGH);
  delayMicroseconds(ch[4]);
  digitalWrite(PPMPin, LOW);
  delayMicroseconds(Pausa);
  timeframe=timeframe+ch[4]+Pausa;
 
  //6
  digitalWrite(PPMPin, HIGH);
  delayMicroseconds(ch[5]);
  digitalWrite(PPMPin, LOW);
  delayMicroseconds(Pausa);
  timeframe=timeframe+ch[5]+Pausa;

  //7
  digitalWrite(PPMPin, HIGH);
  delayMicroseconds(ch[6]);
  digitalWrite(PPMPin, LOW);
  delayMicroseconds(Pausa);
  timeframe=timeframe+ch[6]+Pausa;

  //8
  digitalWrite(PPMPin, HIGH);
  delayMicroseconds(ch[7]);
  digitalWrite(PPMPin, LOW);
  delayMicroseconds(Pausa);
  timeframe=timeframe+ch[7]+Pausa;


  //Completa il frame 20ms
  digitalWrite(PPMPin, HIGH);
  delayMicroseconds(Durata -timeframe);
  digitalWrite(PPMPin, LOW);
  delayMicroseconds(Pausa);
}


Questo sketch genera un segnale PPM che dovrebbe lasciare tutti i servocomandi fermi nella posizione impostata.
Provando a commentare le righe inerenti a tutti i canali e lasciando solo quella del primo canale, con l'oscilloscopio ho effettivamente notato una cosa che
non riuscivo a vedere analizzando l'onda di tutti gli 8 canali.
Il segnale non è precisissimo, ha delle piccole oscillazioni che sono effettivamente quelle che generano il tremolio dei servocomandi.
Nel frattempo ho fatto qualche ricerca per capire se magari dipendesse dal fatto che la serie di istruzioni faceva perdere del tempo prezioso di elaborazione
ma non credo sia quello il problema.
Infatti ho provato a scrivere un piccolo sketch che genera un'onda quadra direttamente tramite il timer ma anche con questo vedo che la lettura non è sempre
uguale, ci sono delle piccole variazioni:
Code: [Select]

void  setup()
{
  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);
  TCCR2A = _BV (COM2A1) | _BV (COM2B1) |_BV (WGM21) | _BV (WGM20);
  TCCR2B = _BV (CS22);
  OCR2A = 200;
  OCR2B = 100;
}

void  loop()
{}


Quindi a questo punto la domanda è: qual'è il trucco per avere un segnale pulito e stabile?
Grazie dell'aiuto.
Ciao.




www.multiwii.it
www.sdmodel.it

Stefanoxjx


Sorry for the english, but if you guys are still looking to output PPM signals, I have a low level library which does this.

Its a variation of the code here - http://rcarduino.blogspot.com/2012/08/arduino-serial-servos.html, much faster than using digitalwrite and delay.

The clock signal is basically PPM, the variation allows you to set the number of channels and removes the reset pulse.

Duane B

rcarduino.blogspot.com


Thanks for your help.
I will study it as soon as possible :)
Bye.
www.multiwii.it
www.sdmodel.it

astrobeed



Eccomi, scusate il ritardo nella risposta ma nel frattempo ho provato a vedere se ne capivo qualcosa di più e riuscivo a cavarmela da solo............. ovviamente non ci sono riuscito :(


Se non mi ricordo male la delayMicroseconds() non è molto precisa come ripetibilità dei tempi, ovvero soffre di jitter, dopo pranzo provo il tuo sketch e ti dico di quanto oscillano esattamente i vari impulsi (misura con DSO) e vediamo come fare per risolvere il problema.

Stefanoxjx




Eccomi, scusate il ritardo nella risposta ma nel frattempo ho provato a vedere se ne capivo qualcosa di più e riuscivo a cavarmela da solo............. ovviamente non ci sono riuscito :(


Se non mi ricordo male la delayMicroseconds() non è molto precisa come ripetibilità dei tempi, ovvero soffre di jitter, dopo pranzo provo il tuo sketch e ti dico di quanto oscillano esattamente i vari impulsi (misura con DSO) e vediamo come fare per risolvere il problema.



Grazie :)
www.multiwii.it
www.sdmodel.it

astrobeed

Ho fatto un primo test col tuo sketch e ti confermo quanto avevo sospettato, e tu che tu avevi già visto con l'oscilloscopio, i segnali dei servo soffrono tutti di un jitter compreso tra 5 e 20 us e questo causa il tremolio dei servo che lavorano in continuazione per aggiornare la posizione.
Per me la soluzione può essere solo una, prima di tutto eliminare la digitalwrite che è lentissima di suo e poi usare direttamente un timer per le temporizzazioni invece della delaymicroseconds che per questo genere di applicazioni non è abbastanza precisa/stabile.
Adesso faccio una modifica al volo allo sketch per renderlo preciso e tra non molto te lo posto.

Stefanoxjx


Ho fatto un primo test col tuo sketch e ti confermo quanto avevo sospettato, e tu che tu avevi già visto con l'oscilloscopio, i segnali dei servo soffrono tutti di un jitter compreso tra 5 e 20 us e questo causa il tremolio dei servo che lavorano in continuazione per aggiornare la posizione.
Per me la soluzione può essere solo una, prima di tutto eliminare la digitalwrite che è lentissima di suo e poi usare direttamente un timer per le temporizzazioni invece della delaymicroseconds che per questo genere di applicazioni non è abbastanza precisa/stabile.
Adesso faccio una modifica al volo allo sketch per renderlo preciso e tra non molto te lo posto.


Ti ringrazio.
Effettivamente stavo pensando anch'io di usare un timer ma è da questa mattina che ci penso ma non riesco a tirare fuori un'idea su come implementare lo stesso sketch con l'uso del timer.
www.multiwii.it
www.sdmodel.it

astrobeed

Adesso dovrebbe andare bene, è scritto in fretta e sicuramente si possono fare varie migliorie al codice, uso il timer1 in modo normale caricando di volta in volta il conteggio per raggiungere il tempo previsto dall'impulso, quando si verifica l'interrupt di overflow del timer1 resetto il pin 8 e il ciclo continua con il prossimo evento.
Volendo è possibile rendere il tutto indipendente dalle attese semplicemente utilizzando uno scheduler su evento, basta incrementare all'interno dell'interrupt una variabile che dice su quale canale si deve operare e usare il flag per la while d'attesa come flag per dare il via al prossimo ciclo, il loop gira normalmente permettendo di svolgere altri compiti nel frattempo.

Code: [Select]

#define  Durata         20000
#define  Pausa          400
#define  PPMPin         8 

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

volatile char attesa;

ISR(TIMER1_OVF_vect)
{
cbi(TCCR1B,CS10);  // stop timer 1
cbi (PORTB,0);     // pin 8 a 0
attesa = 0;
}

void setup()
{               
  Serial.begin(9600);
  pinMode(PPMPin, OUTPUT);
  pinMode(13, OUTPUT);
  digitalWrite(13,HIGH);
  // init timer 1
  TCCR1A = 0x00;  // modo normale
  TCCR1B = 0x00;
  // sbi(TCCR1B,CS12); 
  // sbi(TCCR1B,CS11);
  // sbi(TCCR1B,CS10);
  TIMSK1=0x01; // enabled timer overflow interrupt;
}

void loop()
{
  int  ch[8] = {720, 1000, 1200, 1500, 1700, 1900, 2000, 900};
  long timeframe;
  timeframe=0;
 
  //1
  TCNT1 = 65536- ch[0] * 16;  // calcola valore da caricare nel timer
  sbi(TCCR1B,CS10);           // avvia il timer (16 MHz)
  sbi (PORTB,0);              // setta il pin 8 a 1

  attesa = 1;                 // setta la variabile per il ciclo d'attesa dell'interrupt.
  while (attesa == 1);        // attesa interrupt

  TCNT1 = 65536- Pausa * 16;  // pausa tra gli impulsi.
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[0]+Pausa;

  //2
  TCNT1 = 65536- ch[1] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);
 
  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[1]+Pausa;
 
  //3
  TCNT1 = 65536- ch[2] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);

  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[2]+Pausa;

  //4
  TCNT1 = 65536- ch[3] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);
 
  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[3]+Pausa;

  //5
  TCNT1 = 65536- ch[4] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);
 
  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[4]+Pausa;
   
  //6
  TCNT1 = 65536- ch[5] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);

  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[5]+Pausa;

  //7
  TCNT1 = 65536- ch[6] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);

  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[6]+Pausa;
 
  //8
  TCNT1 = 65536- ch[7] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);
 
  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[7]+Pausa;
 
  //Completa il frame 20ms
  digitalWrite(PPMPin, HIGH);
  delayMicroseconds(Durata -timeframe);
  digitalWrite(PPMPin, LOW);

  delayMicroseconds(Pausa);
}

lesto

qualcosa non mi torna, tu dai una pausa fissa di 400 tra un canale e l'altro, ma non dovrebbe essere di 2000-ch[ i ], ovvero ogni canale occupare sempre 2000 e il segnale totale 20000, fissando quindi il limite di canali massimi a 10?

comuqnue ho rielaborato il codice per essere un poco più comprensibile (odio le ripetizioni :) ), c'è un salto a funzione in più, verò che aggiunge un (microscopico) delay ma il tempo è fisso, quindi anche se fosse un delay sensibile sarebbe aggirabile sottraendo al valore wait il "tempo di salto"

Code: [Select]
#define  Durata        20000
#define  Pausa          400
#define  PPMPin         8  
#define  NUMERO_CANALI  8

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))



volatile char attesa;

int ch[NUMERO_CANALI] = {720, 1000, 1200, 1500, 1700, 1900, 2000, 900};

ISR(TIMER1_OVF_vect)
{
cbi(TCCR1B,CS10);  // stop timer 1
cbi (PORTB,0);     // pin 8 a 0
attesa = 0;
}

void setup()
{                
 Serial.begin(9600);
 pinMode(PPMPin, OUTPUT);
 pinMode(13, OUTPUT);
 digitalWrite(13,HIGH);
 // init timer 1
 TCCR1A = 0x00;  // modo normale
 TCCR1B = 0x00;
 TIMSK1=0x01; // enabled timer overflow interrupt;
}

void loop()
{
 long timeframe;
 timeframe=0;

 //All
 int i;
 for (i=0;i<NUMERO_CANALI;i++){
   sbi (PORTB,0);
   wait(ch[i]);
   wait(pausa);
   timeframe=timeframe+ch[i]+Pausa;
 }
 
 //Completa il frame 20ms
 digitalWrite(PPMPin, HIGH);
 delayMicroseconds(Durata -timeframe);
 digitalWrite(PPMPin, LOW);

 delayMicroseconds(Pausa);
}

void wait(int tempo){
 TCNT1 = 65536- tempo * 16;
 sbi(TCCR1B,CS10);
 attesa = 1;
 while (attesa == 1);
}
sei nuovo? non sai da dove partire? leggi qui: http://playground.arduino.cc/Italiano/Newbie

Stefanoxjx


Adesso dovrebbe andare bene, è scritto in fretta e sicuramente si possono fare varie migliorie al codice, uso il timer1 in modo normale caricando di volta in volta il conteggio per raggiungere il tempo previsto dall'impulso, quando si verifica l'interrupt di overflow del timer1 resetto il pin 8 e il ciclo continua con il prossimo evento.
Volendo è possibile rendere il tutto indipendente dalle attese semplicemente utilizzando uno scheduler su evento, basta incrementare all'interno dell'interrupt una variabile che dice su quale canale si deve operare e usare il flag per la while d'attesa come flag per dare il via al prossimo ciclo, il loop gira normalmente permettendo di svolgere altri compiti nel frattempo.

Code: [Select]

#define  Durata         20000
#define  Pausa          400
#define  PPMPin         8 

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

volatile char attesa;

ISR(TIMER1_OVF_vect)
{
cbi(TCCR1B,CS10);  // stop timer 1
cbi (PORTB,0);     // pin 8 a 0
attesa = 0;
}

void setup()
{               
  Serial.begin(9600);
  pinMode(PPMPin, OUTPUT);
  pinMode(13, OUTPUT);
  digitalWrite(13,HIGH);
  // init timer 1
  TCCR1A = 0x00;  // modo normale
  TCCR1B = 0x00;
  // sbi(TCCR1B,CS12); 
  // sbi(TCCR1B,CS11);
  // sbi(TCCR1B,CS10);
  TIMSK1=0x01; // enabled timer overflow interrupt;
}

void loop()
{
  int  ch[8] = {720, 1000, 1200, 1500, 1700, 1900, 2000, 900};
  long timeframe;
  timeframe=0;
 
  //1
  TCNT1 = 65536- ch[0] * 16;  // calcola valore da caricare nel timer
  sbi(TCCR1B,CS10);           // avvia il timer (16 MHz)
  sbi (PORTB,0);              // setta il pin 8 a 1

  attesa = 1;                 // setta la variabile per il ciclo d'attesa dell'interrupt.
  while (attesa == 1);        // attesa interrupt

  TCNT1 = 65536- Pausa * 16;  // pausa tra gli impulsi.
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[0]+Pausa;

  //2
  TCNT1 = 65536- ch[1] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);
 
  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[1]+Pausa;
 
  //3
  TCNT1 = 65536- ch[2] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);

  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[2]+Pausa;

  //4
  TCNT1 = 65536- ch[3] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);
 
  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[3]+Pausa;

  //5
  TCNT1 = 65536- ch[4] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);
 
  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[4]+Pausa;
   
  //6
  TCNT1 = 65536- ch[5] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);

  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[5]+Pausa;

  //7
  TCNT1 = 65536- ch[6] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);

  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[6]+Pausa;
 
  //8
  TCNT1 = 65536- ch[7] * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  sbi (PORTB,0);
  while (attesa == 1);
 
  TCNT1 = 65536- Pausa * 16;
  sbi(TCCR1B,CS10);
  attesa = 1;
  while (attesa == 1);
  timeframe=timeframe+ch[7]+Pausa;
 
  //Completa il frame 20ms
  digitalWrite(PPMPin, HIGH);
  delayMicroseconds(Durata -timeframe);
  digitalWrite(PPMPin, LOW);

  delayMicroseconds(Pausa);
}



Perfetto, funziona!
Astro: SANTO SUBITO :D
Ora i servi stanno belli fermi solo che non riesco più a passare i dati da pc ad arduino attraverso la seriale probabilmente perchè softwareserial usa lo stesso timer per funzionare.
Comunque avevo già intenzione di escogitare qualche altro sistema  perchè la seriale o la faccio lavorare a bassa velocità oppure si perde dati per strada.
Il dubbio è se trovare qualche interfaccia i2c per pc o usare la parallela.
www.multiwii.it
www.sdmodel.it

Stefanoxjx

#24
Sep 22, 2012, 06:59 pm Last Edit: Sep 23, 2012, 01:36 am by Stefanoxjx Reason: 1

qualcosa non mi torna, tu dai una pausa fissa di 400 tra un canale e l'altro, ma non dovrebbe essere di 2000-ch[ i ], ovvero ogni canale occupare sempre 2000 e il segnale totale 20000, fissando quindi il limite di canali massimi a 10?

comuqnue ho rielaborato il codice per essere un poco più comprensibile (odio le ripetizioni :) ), c'è un salto a funzione in più, verò che aggiunge un (microscopico) delay ma il tempo è fisso, quindi anche se fosse un delay sensibile sarebbe aggirabile sottraendo al valore wait il "tempo di salto"

Code: [Select]
#define  Durata        20000
#define  Pausa          400
#define  PPMPin         8  
#define  NUMERO_CANALI  8

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))



volatile char attesa;

int ch[NUMERO_CANALI] = {720, 1000, 1200, 1500, 1700, 1900, 2000, 900};

ISR(TIMER1_OVF_vect)
{
cbi(TCCR1B,CS10);  // stop timer 1
cbi (PORTB,0);     // pin 8 a 0
attesa = 0;
}

void setup()
{                
 Serial.begin(9600);
 pinMode(PPMPin, OUTPUT);
 pinMode(13, OUTPUT);
 digitalWrite(13,HIGH);
 // init timer 1
 TCCR1A = 0x00;  // modo normale
 TCCR1B = 0x00;
 TIMSK1=0x01; // enabled timer overflow interrupt;
}

void loop()
{
 long timeframe;
 timeframe=0;

 //All
 int i;
 for (i=0;i<NUMERO_CANALI;i++){
   sbi (PORTB,0);
   wait(ch[i]);
   wait(pausa);
   timeframe=timeframe+ch[i]+Pausa;
 }
 
 //Completa il frame 20ms
 digitalWrite(PPMPin, HIGH);
 delayMicroseconds(Durata -timeframe);
 digitalWrite(PPMPin, LOW);

 delayMicroseconds(Pausa);
}

void wait(int tempo){
 TCNT1 = 65536- tempo * 16;
 sbi(TCCR1B,CS10);
 attesa = 1;
 while (attesa == 1);
}



No, la pausa tra canale e canale è sempre uguale, è il segnale di start che va a completare la lunghezza del pacchetto.
www.multiwii.it
www.sdmodel.it

Go Up