Rustre's Corner

Piloter jusqu'à 20 servos

Dans le cours précédent nous avons étudié une méthode simple pour piloter jusqu'à 10 servos en enchainant les impulsions de chacun des servos l'un après l'autre. Seulement cela nous limite à seulement 10 servos par timer. Vous me répondrez fort justement qu'il y a quatre timers, et que nous pourrions donc piloter jusqu'à 40 servos, ce qui, sur un PIC18F252 de seulement 28 pattes, est déjà pas mal :P

Bon, on à déjà tout ce qu'il nous faut, alors pourquoi essayer de faire mieux? Je vous répondrais PARCE-QUE ! Et si je développe, je pourrais argumenter qu'il peut être nécéssaire de piloter par exemple 20 servos, en ayant qu'un seul timer de libre! D'accord, ce cas ne doit pas être courant, mais voyons tout de même comment on pourrait faire celà. Si vous vous rapellez du timeline du cours précédent, nous enchainions les impulsions les unes derrière les autres. Celà nous amenait à un maximum de 10 servos, en sachant que chaque servo peut avoir une consigne de 2ms maximum, et que le temps inter-pulse doit être de 20ms maximum. Nous nous sommes basés sur cette limite des 2ms de temps d'impulsion pour optimiser notre gestion des servos. Pour rappel voici le timeline du cours précédent :

timeline du cours précédent

Utilisons maintenant une autre limite qui est la limite des 1ms minimum. Nous savons que chaque servo aura une impulsion de 1ms minimum, donc voyons ce que le timeline pourrait nous donner en déclenchant une impulsion toutes les 1ms :

premiere optimisation

Les traits pleins représentent un début d'impulsion, les pointillés étant les fins possible d'impulsion (entre 1 et 2ms plus tard). Nous avons ici un procédé d'entrelacement : on commence l'impulsion suivante (2) avant que la précédente (1) soit terminée. Il faudra donc se redéclencher à la fin de l'impulsion précédente (1), puis à nouveau 1ms après l'impulsion en cours (2) pour commencer l'impulsion qui suivra (3), puis à nouveau à la fin de l'impulsion en cours (2) etc...

Etant donné qu'on déclenche une impulsion toutes les 1ms, on va donc pouvoir piloter en théorie jusqu'à 20 servos, avec un seul timer. Il faudra néanmoins faire un programme plus complexe, calculant à chaque déclenchement d'interruption si il faut arrêter un pulse, lancer un autre pulse, au bout de combien de temps il faudra redéclencher etc...

Mais rien n'est impossible à nous programmeurs n'est-ce pas? Un tableau comme celui du cours précédent pour stocker l'état du pulse de chaque servo, une variable contenant le servo actif, et un peu de calcul :

  • pour l'état du pulse, nous mettrons 1 si le pulse est dans sa première partie (la première milliseconde)
  • si le pulse est dans sa deuxième partie (temps restant entre 1 et 2ms), nous mettrons 2
  • pour la consigne nous ne stockerons que la durée "dépassant" la première milliseconde : pour une impulsion de 1,5ms, nous stockerons 5000 (0,5ms * 10000 cycles)
  • Après l'initialisation de la procédure (départ de l'impulsion du premier servo, et déclenchement au bout de 1ms) nous ferons le traitement suivant :
    • stocker 2 dans l'état du pulse à la ligne du servo actif
    • régler le timer pour un déclenchement dans tab[ s_actif ][ 1 ] millisecondes (on termine le pulse)
    • on stocke 1 dans l'état du pulse à la ligne du servo suivant
    • on apelle la fonction maj_servo modifiée (qui met PULSE_ON si tab[ i ][ 0 ] est égale à 1 ou 2)
    • quand le timer se déclenche, on met 0 dans la case 0 du tableau à la ligne du servo actif
    • on règle le timer pour un déclenchement dans 10000 - tab[ s_actif ][ 1 ] (le temps restant pour la première partie de l'impulsion du servo suivant)
    • on met à jours la variable s_actif (on lui fait prendre la valeur du numéro du servo suivant)
    • on recommence

Vu que vous commencez à vous faire la main, je vous laisse faire ce programme! Si vous n'y arrivez pas ou que vous avez des questions, c'est sûrement que je me serai mal expliqué dans ce cours, donc n'hésitez pas à me mailer. Il ne devrait pas y avoir beaucoup de modifs à apporter au programme du cours précédent.


Encore plus de servos avec un seul timer?

Mais est-ce seulement possible? Mais évidemment ;-) Poussons juste un peu plus loin notre technique d'entrelacement. Pourquoi ne pas démarrer toutes les impulsions en même temps? Puis déclencher après chaque fin d'impulsion? Ensuite attendre un peu (quelques millisecondes par exemple, ou plus, ou moins, dans la limite des 20ms) puis recommencer. Voici à quoi pourrait ressembler le timeline :

deuxième optimisation

Pour cette méthode, il va nous falloir encore un peu plus de calcul. En effet, nous allons devoir changer à la volée (durant l'exécution du programme) toutes les consignes, et a chaque fois déclencher au bout du plus court temps : en effet dans cette méthode, les impulsions ne se déclenchent plus "à la suite" : l'impulsion bleue qui est "avant" dans les numéros de servo (c'est le servo0) a une impulsion qui se termine après celle du servo1, mais avant celle du servo2. Nous aurions pu prendre un autre exemple où c'est le servo2 qui se serait déclencher avant le servo0 etc... imaginez le méli-mélo si on a par exemple 30 servos... procédons par ordre :

  • Déclencher tous les pulses (mettre tous les états de pulse à 1 puis appeller maj_servo)
  • Trouver le servo qui a la consigne la plus courte, et le déclarer comme actif
  • Déclencher le timer au bout de cette consigne la plus courte
  • Tant qu'il reste des états de pulse à 1 répeter ce qui suit :
    • Lorsque le timer se déclenche, mettre l'état du pulse du servo actif à 0
    • Appeler maj_servo pour refléter l'état du tableau sur les servos
    • Soustraire la consigne du servo actif à toutes les consignes
    • Trouver le servo qui a la consigne la plus courte, et le déclarer comme actif
    • Déclencher le timer au bout de cette consigne la plus courte

Ca à l'air simple non? Mais alors, me direz-vous, on peut piloter un nombre infini de servos comme ca! Et je vous répondrais que "en théorie, oui", mais en pratique, faut arrêter de rêver :-P

Déjà il faut penser à modifier légèrement le code afin de prendre en compte le cas ou il y aurait deux consignes (ou plus) égales. Déjà l'affaire se corse : il n'est plus possible d'utiliser la variable s_actif qui ne peut contenir au plus qu'un seul servo. "Eh bien, on va coder le(s) servo(s) actif(s) en mettant un 2 dans l'état du pulse, un peu comme nous avions fait auparavant, et en désactivant tous ces pulses (en remettant l'état du pulse à 0) en même temps"... soit ;-)

Mais d'où vient le réel problème qui va nous empecher de piloter par exemple 200 servos avec un seul timer? Déjà c'est sûr on a pas assez de ports/pins sur un PIC, mais la vraie raison est autre : le temps n'est pas extensible, et chaque calcul que nous faisons prends du temps.

A 40Mhz, une instruction (une fraction d'un calcul) prends 100nanosecondes. Ce qu'il faut savoir, c'est que lorsqu'on rentre dans une interruption, aucune autre interruption de la même priorité (cf cours sur les interruptions) ne peut se déclencher. Donc pendant ce temps, si un timer lève une interruption, elle ne sera exécutée qu'une fois que l'interruption en cours sera terminée.

Imaginons maintenant deux servos dont la consigne se suit à quelques centièmes de milliseconde près : le premier aurait une consigne de 1.25ms, et le deuxième de 1,26ms. Il y a donc 100 cycles qui séparent l'un de l'autre, et il va donc falloir que le traitement que l'on fait dans l'interruption pour le premier servo soit inférieur à 100 cycles.

Pour avoir une idée du nombre de cycles nécéssaires pour faire un calcul, reportez vous au fichier .lst créé par MPLAB lorsque vous lancez une compilation : ce fichier met en relation chaque ligne de code C et les instructions en assembleur associées. En comptant le nombre d'instructions en assembleur, vous aurez une idée du nombre de cycles nécéssaires, en sachant qu'une instruction assembleur prends 1 cycle, sauf pour les sauts qui en prennent 2, et pour la lecture/écriture dans la mémoire EEPROM c'est encore plus lent.

Si notre calcul prends par exemple 1000 instructions, nous allons avoir un décalage de temps de presque 0,1ms, ce qui est largement perceptible au final sur l'angle du servo. Il y a alors moyen de s'arranger par exemple en imposant d'autres limites qui nous permettront d'économiser du temps de calcul et d'optimiser les traitements : il est par exemple possible de n'autoriser que 3 consignes par exemple. Nous aurions 1ms, 1,5ms (pour le neutre) et 2ms : tous nos servos pourront alors soit être à fond dans le sens inverse des aiguilles d'une montre, soit à fond dans le sens des aiguilles d'une montre, soit au centre.

Dans ce cas le calcul est beaucoup plus simple : Au bout de 1ms on arrête l'impulsion de tous les servos qui ont la consigne 1ms, puis au bout de 0,5ms (5000 cycles plus tard, ce qui nous laisse de la marge), tous ceux qui doivent être au centre, puis encore 0,5ms plus tard tous ceux qui doivent être à fond dans le sens des aiguilles d'une montre. Ensuite on attends quelques millisecondes et on recommence. Dans ce cas nous pourrons avoir un très grand nombre de servos pilotables très précisément, sans craindre d'avoir un décalage de la consigne.

Plus on augmente le nombre de consignes possibles, plus on augmente le nombre de servos à piloter, et moins on se laisse de marge pour le code dans l'interruption. Il faut par ailleurs faire attention au fait que nous voulons peut-être que notre PIC puisse faire (pendant le temps entre les interruptions) d'autres calculs/gestions de capteurs etc... or si le PIC passe son temps dans l'interruption de pilotage des servos, il ne fera rien d'autre. De plus il faut garder à l'esprit qu'il y a trois autres timers que l'on pourrai vouloir utiliser, et même d'autres périphériques qui peuvent eux aussi lever des interruptions.

Voilà au bout de quelques cours, on se heurte déjà aux limites du PIC ;-) mais n'exagérons rien, un µC cadencé à 40Mhz nous laisse tout de même pas mal de liberté! Il n'y a qu'à voir les exemples d'utilisations dans des robots que vous trouverez sur la page des Fribottes, en particulier les excellents Cnossos de Julien (vainqueur de la coupe HISPABOT cette année) et Picobot de Lionel (arrivé troisième). Tout ça avec nos petits PICs, ça fait rêver hein?


Voilà c'en est fini de ce cours un peu rébarbatif je vous l'accorde, j'espère qu'il ne vous aura pas découragé! Vous n'êtes par contre pas encore débarassés des servos, nous en utilisons encore dans le cours suivant, mais cette fois-ci nous allons inverser sa consigne à la volée, grâce à un bouton poussoir!


Schéma de montage avec servo-moteur

Pour rappel, voici le schéma de montage avec un servo-moteur. Si l'ont veut piloter n servos, il suffit de les connecter aux pins libres de votre choix, puis de refléter ce choix dans les #define au début du programme. Voilà le schéma eagle.

Montage du PIC18F252 avec un servo-moteur


Valid XHTML 1.0! Valid CSS! Copyright 2004 © M. AGOPIAN. Ce document et tous ceux de ce site sont sous licence Creative Commons License Creative Commons License.