Signal servo généré en hard par un timer (pas de librairie ici)

Salut,

Suite à ta demande, je te mets mes configs servo ici...

Pour commencer, pour les servos, j'utilise malheureusement le timer1, car il faut impérativement un timer 16 bits en hard pour avoir un minimum de précision (l'unité que j'ai trouvé donne 1 <=> 500ns). Dans ton cas, c'est pas mal, puisque ça correspond à la précision de la mesure de ton signal PPM, le transfert de la mesure dans les servos se fait donc sans conversion de temps, c'est la bonne nouvelle.

Qui dit bonne nouvelle dit donc mauvaise, il faudra que tu orientes ta détection PPM sur le timer2, mais là, je te retourne ton idée (que j'ai pas encore testé mais qui devrait marcher) : étendre le timer2 en 16 bits logiciel.
Problème non solvable : seul le timer1 possède une ICP, mais je ne pense pas que ça mettra en doute la précision (il suffit de mettre moins de 500ns entre l'INT déclenchée par le PPM et le relevé de TCNT2, sans traîner, c'est jouable, ça devrait ne prendre que 6 cycles d'horloge, soit 375ns, on a 8 cycles pour basculer de l'INT à la lecture de TCNT2)

La config servo :

Servo_setup(){
  TCCR1A = 0x02;        // (WGM1 = Fast PWM)
  TCCR1A += 0x80;  // COM1A = 2, servo1 sur OC1A
  TCCR1A += 0x60;  // COM1B = 2, servo2 sur OC1B
  TCCR1B = 0x18;   // (WGM1 = Fast PWM)

  TCCR1B += 0x02;  // démarrage du timer 1 : Prescaler = 1/8, Fclk = 2MHz, T = 0.5µs
  ICR1 = 35000;    // période servo à ajuster à souhait : (30000 = 15ms, 40000 = 20ms)
}

L'"écriture" des commandes servo se fait par OCR1A = value1 et OCR1B = value2 à n'importequel moment, elle sera matériellement prise en compte dès que TCNT1 = 0 (càd lorse-que timer1 aura fini son tour).

La période servo (ICR1) est importante, j'ai remarqué que l'asservissement interne des servos (de base) se fait au rythme de la période du signal de commande, si cette période est trop lente, alors le servo atteindra sa position en "escalier" cadencé au rythme du signal.

Pour l'extension du timer2 :

volatile byte T2_MSB;  // extention logicielle de timer2
volatile word TCNT2_New, TCNT2_Old; // nouvelle et ancienne mesure de timer2

Void Timer2_setup(){
  TCCR2A = 0x00; // normal port operation
  TCCR2B = 0x00; // Clock = 0Hz
  TIMSK2 = 0x01; // int sur OverFlow

  TCCR2B = 0x02; // démarrage T2 : Clock = 2MHz, T = 0.5µs
  DDRD &= 0xFB; // pin PPM en entrée (port D2, pin N°2)
  EICRA = 0x02;  // autoriser INT0 pour lecture PPM   <= à vérifier, je crois qu'il faut ICS0=1 dans ton cas, voir page 72 du datasheet
}

ISR(T2_OVF){   // extention logicielle de T2 en 16 bits
  T2_MSB++;
  if (T2_MSB = 0) { // débordement, le compteur 16 bits vient de faire un tour complet!
    // a définir
  }
}

ISR(INT0_VECT){   // réception impulsion PPM
  TCNT2_New = TCNT2 + (T2_MSB << 8);    // lecture Timer2

/*          code de RAZ timer2 :
  TCNT2 = 0;  // mise à zéro timer2
  T2_MSB = 0;  // mise à zéro timer2
*/
// ici le traitement de TCNT2_New

}

En fait, c'est rigolo, car ces codes, je les ai écrits hier pour mon multiplicateur d'impulsions, et je n'ai eu qu'à changer deux lignes pour que ça corresponde à ton projet...

Merci,

C'est très interressant, le mode fast PWM va grandement améliorer mon projet sur les points suivants :

  • Il permet de fiabiliser le sens du signal (il monte sur TCNT1=0 et il déscend sur compare match), avant je gérais ça "à la main"
  • Les mise à jour des valeurs OCR1A&OCR1B est bufferisée, jusqu'à TCNT1=0, donc plus besoin de les écrire à un moment précis

En revanche, j'ai l'impression qu'un erreur c'est glissée dans ton code :

Servo_setup(){
  TCCR1A = 0x02;        // (WGM1 = Fast PWM)
  TCCR1A += 0x80;  // COM1A = 2, servo1 sur OC1A
  TCCR1A += 0x60;  // COM1B = 2, servo2 sur OC1B
  TCCR1B = 0x18;   // (WGM1 = Fast PWM)

  TCCR1B += 0x02;  // démarrage du timer 1 : Prescaler = 1/8, Fclk = 2MHz, T = 0.5µs
  ICR1 = 35000;    // période servo à ajuster à souhait : (30000 = 15ms, 40000 = 20ms)
}

TCCR1A += 0x60; // COM1B = 2, servo2 sur OC1B
Ca serait pas plutôt :
TCCR1A += 0x20;
??

Moi je les écris comme ça :

TCCR1A = B10100010; // COM1A & COM1B en mode non-inverted, Timer1 en mode fast PWM
TCCR1B = B00011010; // Timer1 en mode fast PWM, Prescaler = 1/8, Fclk = 2MHz, T = 0.5µs

Super_Cinci:
Qui dit bonne nouvelle dit donc mauvaise, il faudra que tu orientes ta détection PPM sur le timer2, mais là, je te retourne ton idée (que j'ai pas encore testé mais qui devrait marcher) : étendre le timer2 en 16 bits logiciel.
Problème non solvable : seul le timer1 possède une ICP, mais je ne pense pas que ça mettra en doute la précision (il suffit de mettre moins de 500ns entre l'INT déclenchée par le PPM et le relevé de TCNT2, sans traîner, c'est jouable, ça devrait ne prendre que 6 cycles d'horloge, soit 375ns, on a 8 cycles pour basculer de l'INT à la lecture de TCNT2)

La période servo (ICR1) est importante, j'ai remarqué que l'asservissement interne des servos (de base) se fait au rythme de la période du signal de commande, si cette période est trop lente, alors le servo atteindra sa position en "escalier" cadencé au rythme du signal.

Je pense qu'on peut coupler les 2 fonctions sur le timer1 (lecture PPM & écriture des 2 servos) le seul impact est le suivant :

Je ne peux pas utiliser ICR1, ce qui n'est pas grave puisque dans mon projet, le compteur est remis à zéro en fin de trame PPM, cette trame boucle à 22.5ms, ce qui colle tout à fait à mes servos :smiley: (Ca fonctionnait déjà comme ça).

La seule véritable question est : Si on force le compteur à 0, les valeurs OCR1A & OCR1B sont-elle bien mises à jour ?

Sev

Pour TCCR1A, tu as raison, je vais corriger mon code. merci!

Ensuite, concernant la lecture PPM, j'ai un doute, car je n'aime pas trop mélanger les fonctions sur un "sujet" aussi délicat que les servos. Mais tu peux toujours essayer, il te restera le timer 2 étendu en plan B si ça cafouille trop...

Je ne vois aucune raison pour que ça cafouille :roll_eyes:, l'ICP & les OCR1x sont toutes les 2 des fonctions hard, je n'ai qu'à les laisser tourner.

Si ça se trouve en mutualisant de la sorte les fonctions sur les compteurs, ton mega te suffirait peut-être pour ton compte-tour ?

Sev

Effectivement ya un problème...

Je ne peux pas utiliser ICR1 & OCR1A en mode fast PWM parce que en mode 14 ou 15, ils définissent le plafond du compteur... :~

Et je ne comprends pas bien c'est quoi les mode 8 ,9 & 10 bits en fast PWM... :frowning:

Sev

UniseV:
Effectivement ya un problème...

Je ne peux pas utiliser ICR1 & OCR1A en mode fast PWM parce que en mode 14 ou 15, ils définissent le plafond du compteur... :~

Et je ne comprends pas bien c'est quoi les mode 8 ,9 & 10 bits en fast PWM... :frowning:

Sev

Dans mon code, le timer boucle à 15 ou 20ms, ce qui n'est pas compatible avec la durée de la trame que tu veux lire...

Les modes 8, 9 et 10 bits définissent le "TOP" du timer : 8 bits : FF, 9 Bits : 1FF, 10 bits: 3FF... c'est tout... un peu inutile car il suffit de tourner sur ICRn pour définir le TOP, mais en même temps, on pourrait générer une PWM en CTC sur OCRnA et utiliser IPCn pour un temps de réponse... l'arduino est très polyvalent...

C'est quoi ces fréquences de OUF ??? Un servo c'est du 50 Hz...

sur un "sujet" aussi délicat que les servos.

C'est la première fois que j'entends que les servos sont un sujet délicat...

La raison d'être du 16 bits pour les servos c'est d'avoir une résolution de 65536 pas pour la période entière au lieu de 256 avec un compteur 8 bits.

JLB

Le processeur a juste à gérer une interruption toutes les 20 mS pour habiller le compteur avec la valeur correspondant à la largeur de pulses demandée.

Ca doit bien aller chercher 3 lignes de code...

JLB

Super_Cinci:
Dans mon code, le timer boucle à 15 ou 20ms, ce qui n'est pas compatible avec la durée de la trame que tu veux lire...

Les modes 8, 9 et 10 bits définissent le "TOP" du timer : 8 bits : FF, 9 Bits : 1FF, 10 bits: 3FF... c'est tout... un peu inutile car il suffit de tourner sur ICRn pour définir le TOP, mais en même temps, on pourrait générer une PWM en CTC sur OCRnA et utiliser IPCn pour un temps de réponse... l'arduino est très polyvalent...

Dans ton code, il suffirait de pousser ICR1 à 45000 pour avoir une période de 22.5ms mais le problème c'est que je ne peux pas le synchroniser avec mon entrée PPM

Sinon une autre idée pour utiliser un timer 8bit pour 2 servo :

  • Pas de prescaler (donc un timer d'environ un 1ms)
  • 1 période haute, 1 période "réglable", 18 périodes basses

Qu'en penses-tu Super_Sinci ?

jihelbi:
Ca doit bien aller chercher 3 lignes de code...

Les trois lignes de code en question sont dans le premier post.

UniseV:
Dans ton code, il suffirait de pousser ICR1 à 45000 pour avoir une période de 22.5ms mais le problème c'est que je ne peux pas le synchroniser avec mon entrée PPM

le servo saurait peut-être se contenter d'une période de 22.5ms, mais ça doit pas être facile de synchroniser le timer1 sur le signal PPM...

UniseV:
Sinon une autre idée pour utiliser un timer 8bit pour 2 servo :

  • Pas de prescaler (donc un timer d'environ un 1ms)
  • 1 période haute, 1 période "réglable", 18 périodes basses

Qu'en penses-tu Super_Sinci ?

Un timer 8 bits va compter de 0 à 255 en 16µs@16MHz, 128µS@2MHz, 512µs@500KHz, 1.024ms@250KHz, 2.048ms@125KHz, 4.096ms@62.5KHz, 16.384ms@15.625KHz. Si on prend le prescaler à 1/1024, on a une période acceptable pour le servo (16ms), mais côté précision PWM, on est à un pas de 64µs, je doute que ce soit assez précis pour les servos de ton application. A tester quand même, car si ça passe, alors c'est tout bénéf : le timer2 s'occupe des servos, et le timer1 du PPM...

Solution 2 : utiliser le timer2 qui génère une int à une fréquence élevée pour incrémenter un timer3 logiciel en 16 bits et comparer à chaque fois ce timer3 avec la valeur de servo demandée pour faire de la PWM logicielle. Mais ça va peut-être bouffer trop de temps d'exécution, en même temps, tu conserves l'avantage du ICP1 qui fait une "photo" de TCNT1 au moment de l'INT physique donc tu gardes la précision de mesure si l'int ICP1 est mise en file d'attente pendant l'int du timer2 pour les servos.

Je n'oublie pas la constatation que j'avais faite sur un servo HITEC de base : le servo met plus de temps à atteindre sa position si le signal de commande a une trop longue période...

Personnellement j'ai déja réalisé un contrôle de n servos (limité au nombre de pins en sortie) sans timer. Juste avec une interruption périodique à 64000 Hz. Avec les servos courant de modélisme (genre Futaba) cela donne environ 128 pas de positionnement ce qui suffit amplement compte tenu de la qualité de ces servos.

Bien sur cela ne suffira avec des servos de haut de gamme et très cher. Mais à ce niveau de prix autant prendre du servo numérique.

JLB

Super_Cinci:

UniseV:
Sinon une autre idée pour utiliser un timer 8bit pour 2 servo :

  • Pas de prescaler (donc un timer d'environ un 1ms)
  • 1 période haute, 1 période "réglable", 18 périodes basses

Qu'en penses-tu Super_Sinci ?

Un timer 8 bits va compter de 0 à 255 en 16µs@16MHz, 128µS@2MHz, 512µs@500KHz, 1.024ms@250KHz, 2.048ms@125KHz, 4.096ms@62.5KHz, 16.384ms@15.625KHz. Si on prend le prescaler à 1/1024...

Em'suis trompé, prennons un prescaler à 1/64, on a une période de 1,024ms (@250KHz).

Mettons bout-à-bout 20 périodes complètes du timer :
Pendant la première période le signal est haut, pendant la seconde on règle le signal pour "tomber" au bon moment (OCR2x en mode clear on compare match), les 18 restantes le signal reste bas...

On a donc une précision de 4µs (les 8bits de timer sont bien utilisé !) et on gère la montée déscente du signal dans l'interruption d'overflow par parametrage des registres.

Est-ce plus clair ?

Sev

Ayéééééééé! j'ai compris! ben je dirais "à tester"... en espérant que 1.028ms soit assez court pour mettre le servo au minimum, et 2.044ms assez long... sinon, il faudra jongler avec un ICR2 < 255 et si le "temps" servo voulu > ICR2, laisser passer une seconde période TCNT2 avant de mettre en route OCR2x...

C'est aussi une approche intéressante!