Sorties PWM

Ce que l'on va faire ici

Ici, nous allons voir comment utiliser les broches GPIO en sortie en mode PWM (=largeur d'impulsion modulée) avec MicroPython

Note

Je rappelle à toutes fins utiles que la PWM consiste à utiliser une fréquence de base fixe (500 à 1000 Hz typiquement) dont on va contrôler la proportion de niveau HAUT/BAS au sein de la période. Le résultat obtenu est une "émulation" analogique, la tension étant en moyenne égale à 3.3V x % niveau HAUT.

Liens utiles :

Le principe de la PWM ("Pulse Width Modulation" = Largeur d'Impulsion Modulée)

Le principe de la PWM consiste à jouer que la largeur de l'impulsion haute d'une onde de fréquence fixe, en général de l'ordre de 500 Hz. La conséquence est d'obtenir un comportement "pseudo-analogique", à savoir une tension moyenne qui est fonction de la largeur du niveau HAUT.

La même chose exprimée en terme de tension :

Instructions Micropython utiles pour la carte Pi Pico

Le principe général de la PWM avec Micropython pour la carte Pi Pico est la définition d'un objet PWM à partir d'un objet Pin (=représentant une broche) existant.

Définition de la broche utilisée

On définit la broche PWM en la passant au constructeur PWM() selon :

pwm=PWM(Pin(25)) # crée un objet PWM sur la broche 25 (Led onboard)

Fixer la fréquence utilisée pour la PWM

On fixe ensuite la fréquence PWM à utiliser (la fréquence de la "porteuse") : mettre une valeur de 500 Hz ou plus.

pwm.freq(1000) # fixe la fréquence de l'onde PWM

Fixer la largeur du cyle Haut (appelé "duty cycle" )

On dispose pour cela de la fonction .duty() de l'objet PWM qui reçoit un paramètre en 16 bits (càd la valeur 65535 correspond à 100% de niveau HAUT)

pwm.duty_u16(duty) # fixe largeur cycle HAUT PWM en 16 bits (max=65535)

ATTENTION : type int obligatoire !

La fonction .duty_u16(duty) reçoit un type int de façon obligatoire ! Sinon, erreur. C'est un comportement plus C que Python, mais bon, sachez-le !

Attention : valeur en 16 bits !

Retenez bien que la valeur à passer est en 16 bits (maxi = 100% = 65535) Si on veut exprimer la valeur en 256 niveaux, comme avec Arduino, faire :

duty_u16=(duty_u8+1)*256-1

Générer un PWM symétrique (50%) une broche

Le circuit à réaliser est le suivant :

Le code est le suivant :

from machine import Pin, PWM

pwm=PWM(Pin(16)) # impulsion PWM sur la broche 16

pwm.freq(1000) # frequence base PWM

pwm.duty_u16(32767)

value=(65536/2)-1
print(value)

pwm.duty_u16(int(value)) # fixe duty à 50% (max/2)-1 Attention : type int obligatoire 
#pwm.duty_u16(65536-1) # fixe duty à 100% (max/2)-1
#pwm.duty_u16(0) # fixe duty à 0%

print("Test PWM 50%")

La LED s'allume avec une luminosité intermédiaire. L'oscilloscope mis sur la broche confirme un signal symétrique :

Exemple : Faire varier la luminosité de la LED "onboard"

from machine import Pin, PWM, Timer 
import utime

pwm=PWM(Pin(25)) # crée un objet PWM sur la broche 25 (Led onboard)
pwm.freq(1000) # fixe la fréquence de l'onde PWM

timerPwm=Timer() # objet Timer pour appeler la boucle variation PWM

dir=1 # variable de sens
duty=0 # variable largeur impulsion

def loopPwm(timerPwm): # fonction de variation de la PWM

        global duty, dir # variable globale

        # duty est exprimé en 16 bits - pour 255, chaque cran=255 car 65536 = 256x256
        duty=duty+(255*dir)  # variation de la largeur d'impulsion - selon sens ajoute ou retranche
        pwm.duty_u16(duty) # fixe largeur cycle HAUT PWM en 16 bits (max=65535)

        if duty>= 65535 : dir=-1 # inversion sens
        if duty<= 0 : dir=1 # inversion sens


timerPwm.init(freq=200, callback=loopPwm) # forme minimale - par frequence
#timer.init(period=1/255, callback=loop) # forme minimale - par periode

Faire varier la PWM en fonction de la valeur d'une résistance variable

Nous allons ici faire varier la luminosité d'une LED à partir d'une résistance variable.

Le montage à réaliser est le suivant :

Le code est le suivant :

from machine import Pin, ADC, PWM, Timer 

pwm=PWM(Pin(16)) # crée un objet PWM sur la broche 25 (Led onboard)
pwm.freq(1000) # fixe la fréquence de l'onde PWM

sensor=ADC(0) # la broche analogique ADC0

timerPwm=Timer() # objet Timer pour appeler la boucle variation PWM

def loopPwm(timerPwm): # fonction de variation de la PWM

        pwm.duty_u16(sensor.read_u16()) # fixe largeur cycle HAUT PWM en 16 bits (max=65535)
        # la mesure est aussi en 16 bits

timerPwm.init(freq=200, callback=loopPwm) # forme minimale - par frequence

Note

Remarquer la simplicité avec laquelle on réalise le couplage entre la fonction de lecture analogique et la fonction de PWM.

PWM et servomoteurs :

On peut générer l'impulsion utile pour contrôler un servomoteur à l'aide de la PWM en utilisant une fréquence de 50Hz et en jouant sur la largeur d'impuslion.

from machine import Pin, ADC, PWM, Timer 

pwm=PWM(Pin(16)) # crée un objet PWM sur la broche 25 (Led onboard)
pwm.freq(50) # fixe la fréquence de l'onde PWM pour servomoteurs - 50 Hz

#sensor=ADC(0) # la broche analogique ADC0

timerPwm=Timer() # objet Timer pour appeler la boucle variation PWM

# fonction pour changement d'échelle
def rescale(valeur, in_min, in_max, out_min, out_max):
    return (valeur - in_min) * (out_max - out_min) / (in_max - in_min) + out_min


def loopPwm(timerPwm): # fonction de variation de la PWM

        angle=rescale(90, 0,180, 1638,8192) # calcule duty en fonction angle en degrés
        pwm.duty_u16(int(angle)) # fixe largeur cycle HAUT PWM en 16 bits (max=65535)
        # la mesure est aussi en 16 bits

timerPwm.init(freq=200, callback=loopPwm) # forme minimale - par frequence

# Todo

# à 50 hz = 20ms de période = impulsion servomoteurs : le pulse 100% est à 65536, 50% à 32767
# 20ms = 20000µs du coup, le pulse 500µs est à 1638 de duty cycle(0°) est à , le pulse de 2500 µs(180°) est à 8192
# d'une manière générale : le pulse servo pour duree_µs est 65536*duree_µs/20000

Warning

A noter la fonction duty_ns() plus intéressante dans le cas d'un servomoteur :

# la fonction pwm.duty_ns() fixe largeur en ns... utile pour servo
pwm.duty_ns(1500*1000) # neutre