Club robotique de Sophia-Antipolis

Accueil > Essais > Cartes électroniques > La carte ServoPi de ABElectronics

La carte ServoPi de ABElectronics

dimanche 30 novembre 2014, par Eric P.

Présentation

La carte ServoPi est une extension pour la Raspeberry [1] qui est architecturée autour du NXP PCA9685. Ce composant est un générateur de PWM sur 16 canaux et avec une résolution de 12 bits (soit 4096 pas), qui communique en I2C.

A la base conçu pour piloter des LEDs, il peut être utilisé partout où une modulation en largeur d’impulsion est utile, et en particulier pour contrôler des servos de radio-modélisme conventionnels [2].

La carte en détails

ABElectronics a rendu le PCA9685 facile à exploiter en construisant une carte qui intègre également un level shifter 3.3/5V du bus I2C, ainsi que diverses protections (ex : résistances de limitation sur les sorties) et bien entendu la connectique adaptée.

Le PCA9685 est interfacé en I2C, ce qui veut dire au passage que, bien que conçue pour être utilisée avec une Raspberry, la carte peut être connectée à tout ce qui sait causer sur un bus I2C (ou TWI ou SMBus), comme par exemple une Arduino ou même un PC équipé d’une interface USB/I2C.

On y trouve en premier lieu le connecteur 26 points stackable fidèle au principe utilisé par ABElectonics, qui permet d’empiler à volonté diverses extensions sans se retrouver avec la moitié de la table couverte de cartes et de câbles en nappes. Sur la photo ci-dessous, on peut par exemple voir un assemblage d’une Raspberry avec :

  • une IOpi (32 I/O contrôlées par deux MCP23017)
  • une ADCPi (8 ADC gérées par deux MCP3424)
  • et bien entendu la ServoPi dont nous parlons ici (qu’il aurait mieux valu mettre en bas de la pile eu égard à ses dimensions , mais nous ne l’aurions pas vue sur la photo)

Remarque : les liens HTTP ci-dessus ont été mis à jour pour pointer vers les versions actuelles des cartes citées, légèrement différentes de celles visibles sur les photos. Les nouvelles versions ont été modifiées afin d’être compatibles mécaniquement avec les RasPi récentes, mais sont totalement identiques sur le plan fonctionnel.

La ServoPi empilée avec quelques copines sur une pauvre RasPi

Le bord adjacent (sur la droite de la photo) porte la rangée des 16 connecteurs 3 points pour les servos. Par défaut, leur ligne 5V est séparée de celle de la Raspberry (pour des raisons évidentes de consommation) et cette alimentation est à fournir sur un connecteur 2 points situé sur le 3ème côté de la carte (en haut sur la photo).

Il est possible de relier le 5V des servos à celui du connecteur 26 points de la Raspberry, de manière à alimenter celle-ci avec la même source que les servos. Cette opération se fait en connectant par soudure deux pads situés juste à côté du connecteur d’alimentation. Personnellement, je préfère l’éviter, car rien ne viendra filtrer les éventuelles perturbations électriques générées par les servos. Ce n’est pas pour rien que les créateurs de la carte ont laissé la connexion ouverte par défaut. Si vous voulez n’utiliser qu’une seule source d’énergie, il vaut mieux créer deux branches 5V distinctes à partir d’elle, dont une correctement filtrée pour la Raspberry.

A la différence des autres cartes de ABElectonics, l’adresse I2C assignée au chip n’est pas configurée par des jumpers sur header, mais par une série de pads à connecter par soudure. C’est certainement par souci d’économie, et en partant du principe qu’on ne change pas l’adresse tous les 4 matins. Dans un contexte d’expérimentation ce n’est cependant pas forcément l’option la plus pratique. Heureusement pour nous, ABElectronics fait en sorte que ses produits utilisent des adresses par défaut n’entrant pas en conflit les unes avec les autres, donc on peut dans un premier temps d’en accommoder... tant que nous n’avons pas 32 servos à contrôler.

Principe de fonctionnement

Il est relativement simple. Je ne saurais que trop vous conseiller d’étudier la documentation du PCA9685 gentiment jointe à et article et qui aide à tout comprendre.

Datasheet du PCA9685 de NXP

Ce conseil est d’autant plus pressant dans la mesure où l’étude du module Python de ABElectronics n’est pas très informative sur les mécanismes internes. Et de plus tout l’intérêt de notre hobby c’est bien d’apprendre comment fonctionnent les choses, et de ne pas se joindre au troupeau des moutons qui utilisent passivement ce qui leur tombe entre les mains 😉

Les impulsions sont générées selon trois paramètres :

  • la fréquence des signaux, commune à toutes les sorties
  • le délai entre le début de la période et l’activation de la sortie, propre à chaque sortie et configurée par la paire de registres LEDx_ON_H/LEDx_ON_L
  • le délai entre le début de la période et la désactivation de la sortie, propre à chaque sortie et configurée par la paire de registres LEDx_OFF_H/LEDx_OFF_L

On constate qu’on peut non seulement gérer la durée de l’impulsion, mais également son déphasage. Ceci permet entre autres de répartir l’activation des sorties sur l’ensemble de la période du signal afin de réduire le nombre de celle qui sont actives simultanément (par exemple si les courants cumulés dépassaient les capacités de dissipation du chip). La figure 7 de la page 16 du document NXP illustre ce point de manière très explicite (c’est d’ailleurs elle qui m’a permis de comprendre ce mécanisme).

Mise en oeuvre

Vous pouvez utiliser le module fourni par ABElectronics sur leur GitHub. Il fonctionne très bien.

Je lui reproche cependant d’avoir une interface trop fonctionnelle, et qui fleure trop l’approche bibliothèque de fonctions C. Je n’ai rien contre cela, mais puisque nous utilisons Python, autant écrire du code Python, et pas du C transcrit en Python (sinon, autant écrire du C ;)).

J’ai donc commis ma version, car faire soi-même est aussi une très bonne manière d’aller au fond des choses. Vous trouverez cela sur notre GitHub à nous.

Le module servopi du package pybot.abelectronics propose plusieurs classes qui permettent de manipuler tout cela d’une manière plus orientée objet :

  • Servo qui comme son nom l’indique représenter un servo, et permet de le paramétrer et le contrôler
  • ServoPiBoard qui représente la carte elle-même

Cette manière de présenter les choses au développeur de l’application utilisatrice n’est pas sans rappeler celle que propose les librairies Arduino. Ainsi les Arduinistes découvrant la framboise serpentiforme ne seront pas en territoire totalement inconnu 🙂

On commence par créer une instance de ServoPiBoard pour en configurer entre autres l’adresse I2C, mais également quelques aspects qui sont très bien détaillés dans les commentaires du code (j’ai pris la peine de les rédiger, alors je ne vais pas en plus les recopier ici).

Une particularité ici par rapport aux versons initiales des modules de ABElectronics (qui les a fait évoluer sur mes suggestions) : l’objet qui donne accès au bus I2C est fourni comme paramètre du constructeur, au lieu que ce soit lui qui s’occupe de le créer. Ceci est un design pattern très connu, désigné par Dependencies Injection ou Inversion of Control (IoC) dans la littérature. La philosophie est de fournir à un objet ce dont il a besoin pour faire son boulot, plutôt que de lui laisser la responsabilité d’aller le chercher (éventuellement mal). C’est une forme d’assistanat [3] mais c’est ici pour la bonne cause. En effet cela permet au contexte externe de gérer la manière d’accéder à la ressource concernée, et notamment en environnement de test unitaire, de fournir des simulations des processus réels (mock objects dans la littérature). Ici, cela permet par exemple de pouvoir tourner sur votre PC préféré pour tester le code avant d’aller l’implanter dans la RasPi, grâce à ce qui est fait dans le module i2c du package pybot. Je vous engage à aller y jeter un coup d’oeil car il montre comment tirer parti des exceptions ImportError pour commuter de manière automatique et transparente vers des implémentations de simulation dès lors qu’on n’est pas sur la cible réelle.

En pratique, ça donne ce qui est disponible dans le script de démo test_servopi.py du sous-répertoire test du package pybot.abelectronics, dont voici l’extrait correspondant :

try:
    from pybot.raspi import i2c_bus
except ImportError:
    from pybot.i2c import SimulatedSMBus
    i2c_bus = SimulatedSMBus()

board = ServoPiBoard(i2c_bus)

Puis on crée autant d’instances de la classe Servo associés à cette carte que nécessaire, en donnant au passage non seulement le numéro du canal de la carte sur lequel ils sont connectés, mais optionnellement des paramètres permettant de définir la manière dont l’application va manipuler les positions.

Paramétrage avancé de la classe Servo

Deux paramètres optionnels peuvent être fournis pour définir les positions extrêmes de la course du servo. Chaque position est spécifiée par deux attributs :

  • la durée du signal, en millisecondes
  • la valeur donnée à cette position, exprimée dans le domaine applicatif

Il y a plusieurs intérêts à ce paramétrage :

  • limiter la course du servo en donnant des durées définissant un intervalle plus réduit que celui qui est supporté par le servo
  • manipuler des positions avec une signification en cohérence avec l’application.

Ainsi on ne force pas à donner une position sous forme par exemple d’un pourcentage de la course totale, et ce dans un sens défini, mais on laisse à l’utilisateur de la classe la liberté de l’exprimer en ce qu’il veut (un angle en degré, un déplacement en millimètres, si par exemple la sortie du servo est un pignon qui attaque une crémaillère,...), voire d’inverser la convention de signe en fournissant des bornes inversées.

Le plus simple pour bien comprendre est de regarder les déclarations des 3 servos contrôlés par la démo :

servos = {
    # servo 1 set to normal move extent, the horn angle being specified in the [-90, 90] range
    board.get_servo(
        1, stop_min=StopSpecification(-90, Servo.DEFAULT_MS_MIN), stop_max=StopSpecification(90, Servo.DEFAULT_MS_MAX)
    ),
    # servo 2 set to a reduced move extent, the horn angle being restricted to the [-45, 45] range. The servo will
    # not move when the requested position is outside these bounds
    board.get_servo(
        2, stop_min=StopSpecification(-45, 1), stop_max=StopSpecification(45, 2)
    ),
    # servo 3 set as servo 1, but with direction reversed (note the logical position signs)
    board.get_servo(
        3, stop_min=StopSpecification(90, Servo.DEFAULT_MS_MIN), stop_max=StopSpecification(-90, Servo.DEFAULT_MS_MAX)
    )
}

Pour terminer, voici un aperçu du dispositif de test et une petite vidéo de la démo :

Dispositif de démo

Les servos sont disposés dans l’ordre de leur numéro, de gauche à droite. Les trois servos sont pilotés par la même loi de contrôle, l’interprétation de sa variable étant personnalisée en fonction des paramètres de configuration du servo, comme le montre le code de la boucle principale de la démo :

    a, d_a = 0, math.radians(10)

    while True:
        # make the position span the [-90, 90] range, using a sinusoidal control law to get smooth direction changes
        # by decelerating and accelerating at inversion points
        position = math.cos(a) * 90.0
        for servo in servos:
            servo.set_position(position)

        a += d_a
        time.sleep(0.05)

On peut noter que :

  • le servo de droite (3) fonctionne en opposition de phase par rapport à celui de gauche (1), ce qui traduit bien l’effet de l’inversion des positions logiques exprimées pour ses limites de course
  • celui du milieu évolue sur une course moitié, du fait qu’on ait associé aux valeurs logiques des durées d’impulsions correspondant à des positions moitié moins éloignées (1ms et 2ms au lieu de 0.5ms et 2.5ms) de la valeur médiane
  • le servo de gauche a un mouvement souple, grâce au fait que la loi de contrôle de sa position est basée sur une fonction cosinus
  • celui de droite a des changements de sens plus brutaux, très probablement parce que l’étendue des durées d’impulsion qu’il accepte est plus réduite [4]
  • celui du milieu a des périodes d’immobilité, correspondant à la fraction de temps pendant laquelle la position demandée sort de l’intervalle [-45, 45] qui définit son domaine de mouvement

Et voilà. A vous d’imaginer quoi faire de tout cela... et de me signaler les bugs aussi ;)

Conclusion

Comme toutes les autres cartes de ABElectronics que j’ai pu tester, celle-ci est un produit que je conseille, car pour un prix très raisonnable vous avez une carte de qualité et fabriquée avec soin, plutôt bien conçue et qui prend en charge très simplement une fonction très souvent accaparante lorsqu’on doit générer soi-même des impulsions de commande pour des servos (ou des ESC). Pensez-y pour votre prochaine création animée par une RasPi.


[1compatible avec les modèles B+

[2par opposition aux modèles commandés en numérique, comme les Dynamixel AX12, déjà présentés dans nos articles

[3dont l’effet pervers est parfois d’engendrer une certaine déresponsabilisation au niveau des assistés, tentés de sombrer dans la passivité ("quelqu’un s’en occupera bien pour moi")

[4et puis c’est une sombre m$$$$ vendue par Conrad

Un message, un commentaire ?

modération a priori

Attention, votre message n’apparaîtra qu’après avoir été relu et approuvé.

Qui êtes-vous ?

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici

Ce champ accepte les raccourcis SPIP {{gras}} {italique} -*liste [texte->url] <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.