GPIO – Servos moteurs, joypad et WiFi

IMG_5494

Nous allons voir dans cet article comment réaliser une station de pilotage à distance pour commander des servos moteurs, à l’aide d’un Raspberry Pi, d’un joypad et d’un lien WiFi. Dans cet exemple j’uitiliserai un eeePC 901 pour la station de pilotage, mais il est tout à fait possible d’utiliser un deuxième Raspberry Pi.

Voici un schéma global :

pi joystick servoLa construction de ce dispositif se fait en trois étapes :

  1. Contrôle des servos moteurs par le Pi
  2. Utilisation du joypad
  3. Envoi des commandes à distance par wifi

Liste du matériel utilisé dans cet article

 

1 – Contrôle des servos moteurs par le Pi

Pour piloter plusieurs servos moteurs avec un Pi, nous n’avons d’autre choix que de passer par une carte contrôleur. Le site Adafruit propose justement un circuit permettant de contrôler jusqu’à 16 servos moteurs en utilisant le bus I²C (disponible sur le GPIO du Pi comme expliqué dans cet article GPIO – Entrée en matière).

imageVous pouvez acheter ce circuit directement sur le site d’Adafruit : Adafruit 16-Channel 12-bit PWM/Servo Driver – I2C interface – PCA9685

imageVous devrez sortir votre fer et souder les broches sur le circuit imprimé 🙂 Un bornier est prévu pour connecter une source d’alimentation. J’utilise un bloc coupleur de pile avec 4 piles AA pour fournir une tension de 6V, parfait pour alimenter mes servos moteurs.

Côté servos, je dispose de deux gros FUTABA S3010 et d’un minuscule FUTABA S3114.

Le branchement de la carte sur le Pi peut se faire simplement à l’aide de wire jumpers. Il suffit de connecter l’alimentation (3,3V et la masse/ground) ainsi que les broches SCL et SDA comme sur le schéma ci-dessous :

branchement_16cUne fois les branchements effectués, il convient d’activer le support de l’I²C sur le Raspberry Pi. Ceci se fait en deux étapes :

1 – Editez le fichier /etc/modules et ajoutez les deux lignes suivantes :

i2c-bcm2708
i2c-dev

2 – Editez ensuite le fichier /etc/modprobe.d/raspi-blacklist.conf et commentez les deux lignes suivantes (en ajoutant # au début de ces deux lignes) :

# blacklist spi-bcm2708
# blacklist i2c-bcm2708

Pour finir, installez l’outil i2c-tools et redémarrez le Pi :

apt-get install i2c-tools
shutdown -r now

Pour vérifer que la carte est bien reconnue par le Pi, lancez la commande suivante :

i2cdetect -y 1 # pour la révision 2 du Pi

ou

i2cdetect -y 0 # pour la première version du Pi

Vous devriez obtenir ce résultat :

root@raspberrypi:~# i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: 70 -- -- -- -- -- -- --

Bien ! Nous allons maintenant écrire le programme en Python qui va nous permettre de piloter ces servos moteurs. Python est installé par défaut sur Raspbian, il ne manque que certains paquets :

apt-get install python-dev python-rpi.gpio python-smbus

Adafruit propose une librairie Python qui permet de dialoguer facilement avec les différents composants distribués par son site. Nous n’allons pas nous en priver 🙂 Pour télécharger la dernière version disponible de cette librairie, il suffit d’en récupérer les sources sur le dépôt GIT :

git clone https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code.git

La partie qui nous intéresse ici se trouve dans le répertoire Adafruit-Raspberry-Pi-Python-Code/Adafruit_PWM_Servo_Driver.

Dans un premier temps, voici un petit script qui permet de tester les servos test_servo.py :

#!/usr/bin/python

from Adafruit_PWM_Servo_Driver import PWM
import sys

pwm = PWM(0x40, debug=True)
pwm.setPWMFreq(60)

pwm.setPWM(int(sys.argv[1]), 0, int(sys.argv[2]))

Pensez à rendre ce script exécutable grâce à la commande chmod 755 test_servo.py

Ce script prend deux paramètres :

  • l’id du port contrôlant un servo de 0 à 15
  • une valeur, généralement entre 150 et 600 qui détermine la position que doit prendre le servo

Exemples pour commander le servo branché sur le premier port (ayant pour id 0) :

./test_servo.py 0 480
./test_servo.py 0 240

Et voilà, si tout s’est bien passé, vous devriez pouvoir piloter vos servos de cette manière. Nous verrons plus loin dans cet article comment les piloter avec un joypad et à distance 🙂

Note : Je ne sais absolument pas pourquoi le troisième paramètre de la fonction setPWM() doit être entre 150 et 600… De même, je ne sais pas à quoi correspond le deuxième paramètre attendu par cette fonction. Si quelqu’un connaît la réponse, merci de nous expliquer cela en commentaire de cet article 🙂

2 – Utilisation du joypad

26-127-505-02Je dispose d’un magnifique joypad USB, modèle Saitek P990 munis de deux mini-joysticks ou « chapeaux chinois ». Nous allons utiliser ces deux joysticks pour piloter nos servos.

Un joystick qu’est ce que c’est ? En fait, c’est simplement un potentiomètre dont la valeur (la résistance) est interprétée et traduite en nombre. Sur ce modèle de joypad, les valeurs sont échantillonnées de 0 à 1023 (soit 1024 valeurs possibles). Pour obtenir deux axes, il y a deux potentiomètres par joystick. Avec deux joysticks, nous avons donc de quoi piloter indépendamment 4 servos 🙂

La librairie Python evdev permet d’interagir facilement avec les événements déclenchés par des périphériques USB. Pour l’installer :

apt-get install python-pip python-dev
pip install evdev

Avant d’écrire un script Python qui sache interpréter les actions de ces joysticks, vous devez connaître l’identifiant « event » attribué par le système au moment où vous branchez votre joypad USB.

lsusb
Bus 001 Device 003: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 005: ID 05e3:0505 Genesys Logic, Inc.
Bus 002 Device 003: ID 06a3:040b Saitek PLC P990 Dual Analog Pad
Bus 005 Device 002: ID 0b05:b700 ASUSTek Computer, Inc. Broadcom Bluetooth 2.1
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 006: ID 11b0:6148 ATECH FLASH TECHNOLOGY
Bus 001 Device 007: ID 05af:3062 Jing-Mold Enterprise Co., Ltd Cordless Keyboard
Bus 001 Device 008: ID 046d:c016 Logitech, Inc. Optical Wheel Mouse

Mon joypad est bien reconnu par le système avec la dénomination : « Saitek PLC P990 Dual Analog Pad« .

cat /proc/bus/input/devices

Cherchez la référence à votre joypad et repérez l’identifiant « event », ici nous avons l’identifiant « event6« .

[...]

I: Bus=0003 Vendor=06a3 Product=040b Version=0100
N: Name="Saitek P990 Dual Analog Pad"
P: Phys=usb-0000:00:1d.0-2/input0
S: Sysfs=/devices/pci0000:00/0000:00:1d.0/usb2/2-2/2-2:1.0/input/input15
U: Uniq=
H: Handlers=event6 js0
B: PROP=0
B: EV=1b
B: KEY=3fff 0 0 0 0 0 0 0 0 0
B: ABS=30027
B: MSC=10

[...]

A chaque fois que l’on va presser un bouton sur le joypad, ou actionner un de ses joysticks, un événement sera déclenché. Il s’agit donc de surveiller en permanence les événements déclenchés et d’agir en fonction. Voici maintenant un petit script Python qui permet de tester votre joypad et d’identifier les différents événements liés à chaque joystick et chaque bouton : test_joypad.py

#!/usr/bin/python

from evdev import InputDevice, categorize, ecodes
from time import sleep
from datetime import date
import os, sys, socket

dev = InputDevice('/dev/input/event6') # reprendre le même identifiant "event"
print(dev)

for event in dev.read_loop(): # boucle qui surveille l'arrivée d'un événement
  e_code=event.code
  e_type=event.type
  e_value=event.value
  print(str(e_type)+' - '+str(e_code)+' - '+str(e_value))

A chaque fois que vous toucherez quelque chose sur votre joypad, ce script affichera trois variables, e_code, e_type et e_value :

./test_joystick.py
device /dev/input/event6, name "Saitek P990 Dual Analog Pad", phys "usb-0000:00:1d.0-2/input0"
3 - 0 - 508
3 - 0 - 412
3 - 0 - 347
3 - 0 - 391
3 - 0 - 457
3 - 0 - 511
3 - 1 - 602
3 - 1 - 701
3 - 1 - 771
3 - 1 - 659
3 - 1 - 561
3 - 1 - 511

Voilà ce que j’obtiens en actionnant le joystick gauche de mon joypad, identifié par l’e_code 3. Dans l’axes des X, j’obtiens un e_type 0, et un e_type 1 pour l’axe des Y. Lorsque je relâche le joystick en position centrale, les valeurs sont proches de 512 (valeur médiane entre 0 et 1023). Ces caractéristiques sont propres à ce modèle de joypad, mais le principe de fonctionnement est le même pour tous 🙂 Ce petit script vous permettra de noter l’e_code et l’e_type de chaque bouton et joystick que vous voulez utiliser.

Pour ma part, j’ai trois servos à piloter, et je dispose de deux joysticks avec chacun deux axes. J’utiliserai l’axe X du joystick gauche (e_code 3, e_type 0) pour piloter le servo 1. L’axe Y du joystick gauche (e_code 3, e_type 1) pour piloter le servo 2. Et enfin l’axe X joystick droit (e_code 3, e_type 5) pour piloter le servo 3 🙂

A partir de là, vous pouvez facilement imaginer associer le servo 1 au roulis d’un avion, le servo 2 au tangage et le servo 3 au lacet par exemple 🙂

La dernière étape consiste à envoyer les commandes du joypad, branché sur la station de pilotage, au Raspberry Pi qui répercutera les ordres sur ses servos 🙂

3 – Envoi des commandes à distance par wifi

Nous avons d’un côté un script Python qui tourne sur un Raspberry Pi qui a pour rôle de piloter des servos. De l’autre, nous avons un eeePC (ou autre machine…), qui exécute un second script Python dont le but est d’interpréter les commandes envoyées par un joypad. Il s’agit maintenant de faire communiquer ces deux scripts entre eux, ce que nous pouvons facilement faire grâce aux sockets 🙂

Sans trop rentrer dans les détails, il s’agit d’ouvrir une socket au niveau du script qui s’exécute sur le Pi afin qu’il soit à l’écoute de messages envoyés par le script côté « station de pilotage ». Le script côté « station de pilotage » se connecte à cette socket en passant par le réseau IP (via WiFi), et envoie les commandes déclenchées par le joypad.

Côté Raspberry Pi :

#!/usr/bin/python

from Adafruit_PWM_Servo_Driver import PWM
import sys, os, socket

listen_address = ('0.0.0.0', 12800) # écoute sur toutes les interface sur le port 12800

srv_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # protocole UDP
srv_socket.bind(listen_address)
print "server enabled"

pwm = PWM(0x40, debug=True) # initialisation de la carte contrôleur
pwm.setPWMFreq(60)

while True:
  query, clt_address = srv_socket.recvfrom(1024) # écoute les message
  order=query.split('_') # on découpe les messages reçus
  servo=order[0]
  pos=order[1]
  print str(query)+' - '+str(servo)+' - '+str(pos) # on affiche les infos
  pwm.setPWM(int(servo), 0, int(pos)) # on envoie la commande au servo
sys.exit(0)

Côté Station de pilotage :

#!/usr/bin/python

from evdev import InputDevice, categorize, ecodes
from time import sleep
from datetime import date
import os, sys, socket

now = date.today()
print(now)

clt_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # protocole UDP
srv_address = ('192.168.101.27', 12800) # on vise l'adresse IP du Pi sur le port 12800

dev = InputDevice('/dev/input/event6') # initialisation du joypad
print(dev)

def convertAxis (value, axis_max): # fonction qui convertie les valeurs des joystick
  facteur=axis_max/490
  new_value=int(round((value/facteur)+150, 0))
  return new_value

for event in dev.read_loop(): # pour chaque événement détecté
  e_code=event.code
  e_type=event.type
  e_value=event.value
  print(str(e_type)+' - '+str(e_code)+' - '+str(e_value))
  if e_type == 3:
    if e_code == 0: # à destination du servo 1
      servo_pos=str(convertAxis (e_value, 1024))
      clt_socket.sendto("0_"+servo_pos, srv_address)
    elif e_code == 1: # à destination du servo 2
      servo_pos=str(convertAxis (e_value, 1024))
      clt_socket.sendto("1_"+servo_pos, srv_address)
    elif e_code == 5: # à destination du servo 3
      servo_pos=str(convertAxis (e_value, 512))
      clt_socket.sendto("2_"+servo_pos, srv_address)

Conclusions

En déclinant ce principe, vous pouvez piloter de nombreux servos moteurs à distance et commencer à envisager la construction systèmes complexes comme des robots ou des drones 🙂

En parlant de drone, ces articles relatifs au GPIO me serviront à construire un drone avion, comme expliqué ici : Raspberry Pi BOA Drone 🙂

Cependant, certaines zones d’ombre persistes, notamment sur le pilotage de servos. L’utilisation de la lib distribuée par Adafruit évite de se poser de question, cela fonctionne immédiatement. La contre partie, c’est qu’on ne comprend pas forcément tout ce qui se passe. Aussi, j’invite les lecteurs de ce blog à intervenir en commentaire de cet article afin d’apporter des informations complémentaires, des idées ou simplement des remarques 🙂

Merci à vous 🙂

24 réflexions au sujet de « GPIO – Servos moteurs, joypad et WiFi »

  1. rdep

    bonjour,
    super article, je veux faire la même chose mais sans le wifi.

    J’ai un soucis cependant, quand je lance le script pour tester le joypad, ça ce met a défiler alors que je ne touche à rien. Du coup, difficile de savoir quels sont les variables de mon joypad.

    Que faire ?

    Merci beaucoup.

    Répondre
    1. Olivier Auteur de l’article

      Bonjour,
      A première vue, si sans rien toucher vous avez des choses qui défilent, c’est soit :

      • que le joystick est fatigué et qu’il ne reste pas au neutre
      • que le joystick est équipé d’un bouton permettant d’envoyer des commandes à répétition et qu’il est enclenché
      Répondre
      1. rdep

        Bonjour,

        Tout d’abord, merci pour votre réponse.

        J’ai essayé avec 2 joypads, celui de la PS3 et celui de la freebox. Ils fonctionnent tout les 2 normalement lorsqu’ils sont branchés sur la console ou la freebox.
        Le résultat est le même et j’ai essayé d’appuyer sur tout les boutons, rien n’y fait.

        Je vais réessayer en passant par un HUB qui a sa propre alimentation, ça peut peut être aider.

        Merci beaucoup, je vous tiens au courant.

        Répondre
        1. Olivier

          Bonjour,
          J’ai eu le même pb que ci-dessus, a savoir l’affichage de la réponse en boucle sans action sur le joypad.
          Après correction du script, j’ai réglé le pb. A savoir de modifier la boucle de la façon suivante :
          for event in dev.read_loop():
          if event.type == ecodes.EV_KEY:
          e_code=event.code
          e_type=event.type
          e_value=event.value
          print(str(e_type)+’ – ‘+str(e_code)+’ – ‘+str(e_value))

          Répondre
  2. JD

    Bonjour,

    Je construis un robot à trois roues pour me familiariser avec l’électronique. Votre tuto m’aide beaucoup.

    Pourririez-vous m’envoyer un schéma complet des branchements s’il vous plait?

    JD

    Répondre
    1. Olivier Auteur de l’article

      Bonjour JD,
      Tous les schémas sont dans l’article (connexion du circuit sur le GPIO du Pi en I2C). De quoi avez vous besoin ?

      Répondre
      1. JD

        Bonjour Olivier,

        J’ai fait la première partie, l’alimentation est par batterie et la connetion au raspberry pi est par wifi. Je n’ai pas besoin de la partie 2 Utilisation du joypad.

        J’ai une question :
        quand je lance
        ./test_servo.py 0 480
        puis
        ./test_servo.py 1 480
        le premier moteur s’arrête et le second démarre.

        Savez vous comment je peux faire pour avoir les deux servos qui fonctionnent de façon indépendante et en même temps, tout en exécutant des lignes de commandes de ce type (./test_servo.py 1 480).

        Merci!

        Répondre
  3. Commin Benoit

    Bonjour

    Pour avoir réaliser ce type d’implementation, l’utilisation de la carte ADAFruit n’est pas indispensable. Vous pouvez installer le driver Servoblaster sur le linux du raspberry : de cette façon, un port GPIO peut piloter directement le servomoteur.

    J’ai réalisé une voiture télécommandé sur ce principe piloté par un mobile Android ( avec retour Video ).

    Répondre
    1. John Dossin

      Bonjour

      Je réussi a piloter mon servo avec servoblaster pouvez-vous m’aidé a corriger mon code afin de rendre aussi fluide MERCI

      je n’utilise que la fonction gauche et droite avec mon servo
      #import evdev
      import time
      import os
      from evdev import InputDevice, categorize, ecodes
      os.system(‘sudo /home/pi/scripts/c/ServoBlaster/user/servod’)
      f = open(‘/dev/servoblaster’, ‘w’)
      f.write(‘0=%s’%os.system)
      #creates object ‘gamepad’ to store the data
      #you can call it whatever you like
      gamepad = InputDevice(‘/dev/input/event4′)
      servopath= »/dev/servoblaster f=open(servopath,’w’) »

      #prints out device info at start
      print(gamepad)

      #button code variables (change to suit your device)
      aBtn = 34
      bBtn = 36
      xBtn = 35
      yBtn = 37

      up = 46
      down = 32
      left = 307
      right = 305

      start = 24
      select = 49

      lTrig = 37
      rTrig = 50

      #loop and filter by event code and print the mapped label
      for event in gamepad.read_loop():
      if event.type == ecodes.EV_KEY:
      if event.value == 1:
      if event.code == yBtn:
      print(« Y »)
      elif event.code == bBtn:
      print(« B »)
      elif event.code == aBtn:
      print(« A »)
      elif event.code == xBtn:
      print(« X »)
      elif event.code == up:
      print(« up »)
      elif event.code == down:
      print(« down »)
      elif event.code == left:
      os.system(« echo 5=+60 > /dev/servoblaster »)
      print(« left »)
      elif event.code == right:
      os.system(« echo 5=-60 > /dev/servoblaster »)
      print(« right »)
      elif event.code == start:
      print(« start »)
      elif event.code == select:
      print(« select »)
      elif event.code == lTrig:
      print(« left bumper »)
      elif event.code == rTrig:
      print(« right bumper »)

      Répondre
  4. paul N.

    Bonjour, comment lier un mouvement d’un servomoteur gauche droite avec un joypad lorsque l’on joue avec retropie svp si quelqu’un à une piste je suis preneur merci d’avance.

    Répondre
    1. Olivier Auteur de l’article

      Bonjour Paul,
      Je ne connais pas bien retropi mais en principe cela devrait fonctionner de la même manière

      Répondre
  5. gi67-white

    Bonjour,

    Merci Beaucoup pour le tuto ! Ca marche impeccable.

    J’aimerai avoir une info (si possible),

    J’ai acheté sur aliexpress http://fr.aliexpress.com/item/New-320A-Brushed-Brush-Speed-Controller-ESC-w-Reverse-for-RC-Car-Truck-Boat-1-8/32488081316.html

    Et en le branchant comme un servo classique sur la « plaque 16 servos » je n’ai aucun résultats.

    J’ai branché sur le « ESC » une batterie 7.2v et un moteur 540 (classique lui aussi).

    Merci de vos réponses ! Bon week 😉

    Répondre
    1. anonymous

      Bonjour,
      2 pistes :
      – débrancher le fil rouge de l’ESC car il fournit une tension contrairement à un servo classique
      – il est possible qu’il faille mettre le moteur au minimum avant que l’ESC accepte de le piloter.C’est une protection courante.

      Bon courage…

      Répondre
  6. Ping : Rob O Tic | Pearltrees

  7. Ping : Pi BOAt - description des modules - MagdiBlog

  8. Ping : Pi BOAt - Radar de surface - MagdiBlog

  9. Ping : Groupe Manette | Pearltrees

  10. BTS MEI

    bonjour à tous je suis étudiant en BTS maintenance des systèmes et j’ai pour mission de devoir piloter un ou plusieurs moteur avec une manette de ps4 (joystick) et dont si je pousse le joystick à fond le moteur doit tourner à sa vitesse maximale mais si je pousse le joystick légèrement le moteur doit tourner légèrement. Le pire dans tout sa c’est que je n’ai absolument aucune mais alors aucune connaissance en programmation. c’est pour quoi je viens solliciter votre aide.
    PS : pour réaliser cela je dispose d’un raspberry, d’une mannette de ps4 et d’une platine d’essai.
    merci d’avance a celui qui sera capable de m’aidé.

    Répondre
  11. Titouan

    Bravo pour ce tuto j’admire votre travaille.Mais est ce-que cette page (GPIO- servo,joypad et wifi) est la dernière page du tutoriel? Car l’auto pilot m’aurais plus.

    Répondre
  12. John Dossin

    Bonjour

    Je réussi a piloter mon servo avec servoblaster pouvez-vous m’aidé a corriger mon code afin de rendre aussi fluide MERCI

    je n’utilise que la fonction gauche et droite avec mon servo
    #import evdev
    import time
    import os
    from evdev import InputDevice, categorize, ecodes
    os.system(‘sudo /home/pi/scripts/c/ServoBlaster/user/servod’)
    f = open(‘/dev/servoblaster’, ‘w’)
    f.write(‘0=%s’%os.system)
    #creates object ‘gamepad’ to store the data
    #you can call it whatever you like
    gamepad = InputDevice(‘/dev/input/event4′)
    servopath= »/dev/servoblaster f=open(servopath,’w’) »

    #prints out device info at start
    print(gamepad)

    #button code variables (change to suit your device)
    aBtn = 34
    bBtn = 36
    xBtn = 35
    yBtn = 37

    up = 46
    down = 32
    left = 307
    right = 305

    start = 24
    select = 49

    lTrig = 37
    rTrig = 50

    #loop and filter by event code and print the mapped label
    for event in gamepad.read_loop():
    if event.type == ecodes.EV_KEY:
    if event.value == 1:
    if event.code == yBtn:
    print(« Y »)
    elif event.code == bBtn:
    print(« B »)
    elif event.code == aBtn:
    print(« A »)
    elif event.code == xBtn:
    print(« X »)
    elif event.code == up:
    print(« up »)
    elif event.code == down:
    print(« down »)
    elif event.code == left:
    os.system(« echo 5=+60 > /dev/servoblaster »)
    print(« left »)
    elif event.code == right:
    os.system(« echo 5=-60 > /dev/servoblaster »)
    print(« right »)
    elif event.code == start:
    print(« start »)
    elif event.code == select:
    print(« select »)
    elif event.code == lTrig:
    print(« left bumper »)
    elif event.code == rTrig:
    print(« right bumper »)

    Répondre

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.