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 :
- http://docs.micropython.org/en/latest/pyboard/library/pyb.Pin.html#pyb-pin
-
http://docs.micropython.org/en/latest/pyboard/pyboard/quickref.html#pwm-pulse-width-modulation
-
http://docs.micropython.org/en/latest/pyboard/library/pyb.Timer.html#pyb-timer
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