3.16. Commande de servomoteur

Un servomoteur de modélisme (RC) est un petit moteur à engrenages dans un boîtier scellé, doté d’une commande de position en boucle fermée intégrée. À l’intérieur du boîtier se trouvent un moteur à courant continu, un réducteur, un potentiomètre relié à l’arbre de sortie, et une petite carte de commande qui compare la lecture du potentiomètre à une consigne provenant de l’extérieur. La commande fait tourner le moteur dans le sens qui réduit l’erreur, et s’arrête lorsque la position correspond. Du côté de la caméra, rien de tout cela n’est visible – vous indiquez simplement au servomoteur où aller.

3.16.1. Le signal PWM

Un servomoteur reçoit sa consigne sous forme d’un signal PWM à une cadence d’image fixe de 50 Hz, où la largeur d’impulsion sélectionne la position :

  • Une impulsion de 1,0 ms amène l’arbre à une extrémité de sa course.

  • Une impulsion de 1,5 ms positionne l’arbre au centre.

  • Une impulsion de 2,0 ms amène l’arbre à l’autre extrémité.

Tout ce qui se situe entre les deux correspond à une position intermédiaire.

Trois rangées de tracés en onde carrée empilées verticalement. Chaque rangée montre une période de 20 ms d'une PWM à 50 Hz avec une impulsion haute étroite au début : 1,0 ms dans la rangée du haut, 1,5 ms au milieu, 2,0 ms dans la rangée du bas.

La trame PWM du servomoteur dure 20 ms ; la largeur d’impulsion (1,0 – 2,0 ms) sélectionne la position.

Contrairement aux LED et aux moteurs, le servomoteur ne fait pas la moyenne de la PWM. La largeur d’impulsion est elle-même la commande : la logique interne du servomoteur mesure chaque impulsion, fixe sa cible en conséquence, et fait tourner le moteur jusqu’à ce que la sortie corresponde. Le rapport cyclique exprimé en fraction (entre 5 % et 10 % sur toute la plage) est accessoire – c’est la largeur d’impulsion absolue qui compte, et c’est ce que le logiciel doit contrôler.

3.16.2. Câblage

Les servomoteurs de modélisme utilisent un connecteur à trois fils :

  • Alimentation (généralement rouge) : l’alimentation propre du servomoteur, typiquement de 4,8 V à 6 V. N’alimentez pas le servomoteur depuis le rail 3,3 V de la caméra – il ne peut pas fournir le courant de calage, et le rail s’effondrera.

  • Masse (généralement noir ou marron) : le chemin de retour de l’alimentation du servomoteur, reliée à la masse de la caméra pour que le signal dispose aussi d’une référence commune.

  • Signal (généralement blanc, jaune ou orange) : la ligne PWM issue du GPIO de la caméra.

3.16.3. Code

duty_u16() fonctionnerait, mais elle fixe le rapport cyclique comme fraction de la période – peu commode pour un signal où c’est la largeur d’impulsion absolue qui compte et où la période est fixe. duty_ns() fixe la largeur d’impulsion directement en nanosecondes :

from machine import PWM, Pin

servo = PWM(Pin("P7"), freq=50, duty_ns=1_500_000)  # centre

La porteuse est à 50 Hz (période de 20 ms) ; le temps haut de chaque cycle est exactement de 1500 µs. Une petite fonction utilitaire rend explicite le mappage position-vers-impulsion :

def set_position(angle):
    # angle: 0..180 degrees mapped to 1.0..2.0 ms
    pulse_us = 1000 + (angle * 1000) // 180
    servo.duty_ns(pulse_us * 1000)

set_position(0)      # full one way
set_position(90)     # centre
set_position(180)    # full the other way

Un balayage lent sur toute la plage :

import time

for angle in range(0, 181, 5):
    set_position(angle)
    time.sleep_ms(20)
for angle in range(180, -1, -5):
    set_position(angle)
    time.sleep_ms(20)

La plage 1,0 – 2,0 ms est la norme, mais de nombreux servomoteurs acceptent une plage plus large (souvent de 500 µs à 2500 µs) pour la course complète. La fiche technique du servomoteur indique les limites exactes de largeur d’impulsion ; des valeurs hors de cette plage peuvent envoyer le moteur cogner contre ses butées mécaniques.

Pour un servomoteur ayant une plage non standard, sortez les limites dans des constantes et paramétrez le mappage :

PULSE_MIN_US = 500     # full one way (from the data sheet)
PULSE_MAX_US = 2500    # full the other way

def set_position(angle):
    span_us = PULSE_MAX_US - PULSE_MIN_US
    pulse_us = PULSE_MIN_US + (angle * span_us) // 180
    servo.duty_ns(pulse_us * 1000)