Effet stroboscopique - Niveau intermédiaire
Dans ce tutoriel, nous allons faire tourner un moteur et faire clignoter une LED. Rien de bien passionnant ? bien au contraire si les deux activités sont synchronisées : on peut alors figer la rotation de la pale du moteur en allumant la LED au bon moment !
Nous verrons dans ce tutoriel
comment contrôler la vitesse d'un moteur à courant continu avec PWM
comment mesurer la vitesse de rotation avec un capteur à effet hall
comment faire clignoter une LED rapidement à une fréquence bien précise
et au passage, nous découvrirons un afficheur LED à 4 chiffres i2c bien pratique pour afficher la vitesse de rotation du moteur
Matériel nécessaire
Pour la réalisation de ce projet, nous aurons besoin :
d'une carte micro:bit
d'un moteur à courant continu
de 2 aimants
d'un capteur à effet hall
d'un afficheur LED 4 chiffres i2c ht16k33 (optionnel)
d'une LED et sa résistance de 100 ohms.
une diode
un transistor NPN type 2N2222 et une résistance de 100 ohms
une résistance de 1kOhm
Contrôler la vitesse de rotation du moteur
Un moteur à courant continu a un fonctionnement très basique : on lui fourni du courant et il tourne ... Afin de contrôler sa vitesse de rotation, l'idée est de lui fournir du courant par intermittence : on appelle ce procédé PWM (Pulse Width Modulation ou modulation par largeur d'impulsion).
La modulation à largeur d'impulsions (Pulse Width Modulation) permet de générer un signal carré de fréquence donnée. Le rapport entre le temps où le signal est haut sur une période est appelé rapport cyclique (duty cycle). Plus ce rapport est élevé, plus la charge connectée sur la broche reçoit d'énergie. En jouant sur ce rapport cyclique, le PWM permet de faire varier l'intensité d'une LED, la vitesse d'un moteur ou bien de contrôler l'angle de rotation d'un servo moteur.
Pour utiliser PWM sur micro:bit, je vous renvoie à cette page du kit de survie.
Commander une charge consommant du courant
Notre moteur - que j'ai récupéré dans un vieux lecteur CD de PC - fonctionne sous 5V et consomme autour de 100 mA. C'est bien au delà des capacités des broches d'entrée sorties de la micro:bit qui sont limitées à 3V avec des charges de 20mA max. L'idée va être d'utiliser un transistor (ici un 2N2222) pour commander notre charge. Nous utiliserons le schéma suivant :
La carte micro:bit commande en PWM via sa broche 1 la base du transistor qui est utilisé comme une sorte d’interrupteur. Lorsque la broche 1 est à l'état haut, le transistor permet au courant de passer ce qui ferme le circuit et fait tourner le moteur. Lorsque la broche passe à l"état 0, le transistor se bloque et le courant le passe plus dans le moteur.
Complément : diode de roue libre
Lorsque l'on coupe brutalement une charge inductive (ici un moteur principalement composé de bobines de fils), il se crée un pic de tension important pouvant endommager les composants. La diode placée en parallèle du moteur permet d'éliminer ce phénomène et de protéger notre circuit.
Méthode : Le programme
Il ne reste plus qu'à programmer la carte afin de délivrer un signal PWM de rapport cyclique variable. On va prévoir6 niveaux de vitesse (de 0 à 5). On change la vitesse avec le bouton A et on l'affiche sur la matrice LED. A ce stade, le programme est très simple et se passe de plus de commentaires.
from microbit import *
pin2.set_analog_period_microseconds(300)
speed=0 # Vitesse du moteur de 0 a 5
while True:
if button_a.was_pressed():
speed = (speed+1)%6
display.show(speed
pin2.write_analog(1023*speed//5)
Complément : Fabrication de la pale du moteur
Afin de voir le moteur tourner, j'ai modélisé une pale pour le moteur [zip] très basique mais qui permet de fixer au bout les aimants dont nous aurons besoin par la suite. Le fichier ZIP contient le fichier blend et STL prêt à l'impression.
Mesurer la vitesse de rotation du moteur
Pour mesurer la vitesse de rotation, l'idée est d'utiliser un capteur à effet hall. Le principe est le même que pour un compteur de vélo : lorsque qu'un aiment passe devant le capteur, il génère une impulsion électrique. Cette impulsion sera détectée par la carte qui en mesurant l'écart de temps entre deux impulsions en déduira la vitesse de rotation et l'affichera.
Complément : Écran LED 4 chiffres
J'utilise pour l'affichage de la vitesse cet écran. Il se pilote via le bus i2c et utilise le circuit HT16K33 pour contrôler les segments des 4 chiffres. Tout écran possédant ces caractéristiques doit pouvoir fonctionner avec le code que je fournis. Je trouve cet écran très pratique pour afficher des données numériques depuis la carte micro:bit.
Si vous ne possédez pas d'écran, vous pouvez tout à fait vous en passer. Il suffit d'utiliser la fonction graphique intégrée à Mu.
Méthode : Pilote de l'écran
Pour piloter d'écran LED 4 chiffres, vous aurez besoin d'un module à copier vur la micro:bit. Vous pouvez le télécharger ici [zip].
Méthode : Le montage
Pour le montage, nous reprendrons le précédant et y ajoutons le capteur à effet hall. Pour fonctionner, celui-ci à besoin d'une alimentation de 3V que nous prendrons sur la micro:bit ainsi que d'une résistance de PULL-UP de 1kOhms.
L'écran se connecte sur l'alimentation 3V de la carte ainsi que sur les broches SDA et SCL.
Il faudra physiquement placer le capteur à effet hall à proximité de la pale de manière à ce que les aimants le frôlent à chaque passage. Le scotch est votre ami !
Méthode : Le programme
Le code à mettre sur la carte est plus long que le précédent car il faut déterminer la vitesse du moteur. Pour cela, nous utiliserons une fonction motor_speed().
def motor_speed():
t=ticks_ms()
while pin2.read_digital()==1 and ticks_ms()-t<TIMEOUT_MS:
pass
if ticks_ms()-t>TIMEOUT_MS:
return -1
t1=time_pulse_us(pin2,1,TIMEOUT_MS*1000)
t=ticks_ms()
while pin2.read_digital()==0 and ticks_ms()-t<TIMEOUT_MS:
pass
if ticks_ms()-t>TIMEOUT_MS:
return -1
t2=time_pulse_us(pin2,0,TIMEOUT_MS*1000)
return int(60*1000000/((t1+t2)*2))
L'idée est ici de déterminer la longueur de l'impulsion haute (t1) puis celle de l'impulsion basse (t2). En les ajoutant (t1+t2), on obtient la longueur d"une période soit une demi rotation puisqu'il y a deux aimants sur la pale. Avant de lancer la fonction time_pulse_us, on s'assure que la broche pin2 est dans l'état opposé. De cette manière, time_pulse_us renvoie la longueur totale de l'impulsion demandée.
Le problème est que parfois, des valeurs aberrantes apparaissent et faussent la mesure. Afin de les éliminer, j'ai créé une seconde fonction qui va faire 10 mesures de vitesse, les trier par ordre croissant et détecter les valeurs aberrante. Une fois éliminées, la vitesse retournée sera la moyenne des mesures cohérentes faites. Cela permet d'avoir une relative précision dans la mesure. C'est la fonction read_speed() qui se charge de ce travail.
def read_speed():
liste = []
for i in range(10):
m=motor_speed()
if m != -1:
liste.append(m)
if len(liste)==0:
return 0
liste.sort()
liste_filtree=[liste[0]]
i=1
while i<len(liste) and liste[i]/liste[i-1]<1.1:
liste_filtree.append(liste[i])
i+=1
return sum(liste_filtree)//len(liste_filtree)
L'idée ici est de trier la liste par ordre croissant. Dès qu'une mesure est significativement plus grande que la précédente, on s'arrête et on ignore le reste de la liste. On renvoie la moyenne de la liste ainsi nettoyée.
Il est possible d'obtenir une meilleure précision en augmentant le nombre de mesures, mais cela se fait au détriment de la rapidité de mesure.
Méthode : Le programme complet
from microbit import *
from machine import time_pulse_us
from time import ticks_ms
from ht16k33_seg import seg_7x4
aff7seg = seg_7x4()
TIMEOUT_MS = 500
speed=0
pin2.set_analog_period_microseconds(300)
def motor_speed():
# realise une unique mesure de vitesse
t=ticks_ms()
while pin2.read_digital()==1 and ticks_ms()-t<TIMEOUT_MS:
pass
if ticks_ms()-t>TIMEOUT_MS:
return -1
t1=time_pulse_us(pin2,1,TIMEOUT_MS*1000)
t=ticks_ms()
while pin2.read_digital()==0 and ticks_ms()-t<TIMEOUT_MS:
pass
if ticks_ms()-t>TIMEOUT_MS:
return -1
t2=time_pulse_us(pin2,0,TIMEOUT_MS*1000)
return int(60*1000000/((t1+t2)*2))
def read_speed():
# Fait la moyenne de 10 mesures
# en eliminant les valeurs aberrantes
liste = []
for i in range(10):
m=motor_speed()
if m != -1:
liste.append(m)
if len(liste)==0:
return 0
liste.sort()
liste_filtree=[liste[0]]
i=1
while i<len(liste) and liste[i]/liste[i-1]<1.1:
liste_filtree.append(liste[i])
i+=1
return sum(liste_filtree)//len(liste_filtree)
while True:
if button_a.was_pressed():
speed = (speed+1)%6
display.show(speed)
pin1.write_analog(1023*speed//5)
if speed != 0:
aff7seg.print(read_speed())
aff7seg.update_display()
else:
aff7seg.print(0)
aff7seg.update_display()
Ce programme affiche en continu la vitesse du moteur sur l'écran 4 chiffres. Un appui su r le bouton A permet de modifier la vitesse du moteur.
Le stroboscope
Nous avons à présent tous les ingrédients pour construire notre stroboscope. Nous allons voir deux versions :
On allume la LED dès qu'une impulsion est détectée
On allume la LED à fréquence fixe calculée en fonction de la vitesse
version 1
Cette première version est très simple puisque aucun calcul de vitesse n'est nécessaire. Dès qu'on appuie sur B, on allume la LED au passage de l'aimant. L'écran 4 chiffres ne sert a rien et peut être enlevé. On va juste ajouter une LED en série avec une résistance de 100 ohms, pilotée par la broche 0.
Méthode : Le programme
from microbit import *
speed = 0
strob = False
pin2.set_analog_period_microseconds(300)
while True:
if button_a.was_pressed():
speed = (speed+1)%6
display.show(speed)
pin1.write_analog(1023*speed//5)
if button_b.was_pressed():
strob = not strob
pin0.write_digital(0)
if strob :
if pin2.read_digital()==0:
pin0.write_digital(1)
else:
pin0.write_digital(0)
Version 2
Dans cette partie, nous allons reprendre le montage complet avec l'afficheur de vitesse, et ajouter la LED. Nous allumerons la LED à intervalle régulier basé sur la vitesse mesurée. Cela permet de mettre en évidence les éventuelles imprécisions de la mesure : la vitesse est exacte lorsque le mouvement apparent est nul. On va en pratique constater un léger mouvement de la pale ce qui est plus sympa à regarder ! En divisant la période de clignotement par 4, on peut même afficher une croix à la place de la pale.
Méthode : Le programme
Tout d'abord, tant que le bouton B n'est pas pressé, on détermine la période basée sur la fréquence affichée :
rotation_speed = read_speed()
interval = 60 * 1000000 // rotation_speed
Lorsqu'on appuie sur le bouton B, la période précédemment calculée est utilisée pour activer la LED à intervalle de temps régulier. On peut ainsi jouer sur cet intervalle pour afficher la pale à plusieurs endroits simultanément.
if ticks_us() - last_flash > interval:
last_flash = ticks_us()
pin0.write_digital(1)
sleep_us(interval//40)
else:
pin0.write_digital(0)
Voici pour finir le programme complet
from microbit import *
from machine import time_pulse_us
from time import ticks_ms, ticks_us, sleep_us
from ht16k33_seg import seg_7x4
aff7seg = seg_7x4()
TIMEOUT_MS = 500
speed = 0
rotation_speed = 0
interval = 0
last_flash = 0
strob = False
pin2.set_analog_period_microseconds(300)
def motor_speed():
# realise une unique mesure de vitesse
t=ticks_ms()
while pin2.read_digital()==1 and ticks_ms()-t<TIMEOUT_MS:
pass
if ticks_ms()-t>TIMEOUT_MS:
return -1
t1=time_pulse_us(pin2,1,TIMEOUT_MS*1000)
t=ticks_ms()
while pin2.read_digital()==0 and ticks_ms()-t<TIMEOUT_MS:
pass
if ticks_ms()-t>TIMEOUT_MS:
return -1
t2=time_pulse_us(pin2,0,TIMEOUT_MS*1000)
return int(60*1000000/((t1+t2)*2))
def read_speed():
# Fait la moyenne de 10 mesures
# en eliminant les valeurs aberrantes
liste = []
for i in range(10):
m=motor_speed()
if m != -1:
liste.append(m)
if len(liste)==0:
return 0
liste.sort()
liste_filtree=[liste[0]]
i=1
while i<len(liste) and liste[i]/liste[i-1]<1.1:
liste_filtree.append(liste[i])
i+=1
return sum(liste_filtree)//len(liste_filtree)
while True:
if button_a.was_pressed():
speed = (speed+1)%6
display.show(speed)
pin1.write_analog(1023*speed//5)
if button_b.was_pressed():
strob = not strob
pin0.write_digital(0)
if strob :
if ticks_us() - last_flash > interval:
last_flash = ticks_us()
pin0.write_digital(1)
sleep_us(interval//40)
else:
pin0.write_digital(0)
else:
if speed != 0:
rotation_speed = read_speed()
interval = 60 * 1000000 // rotation_speed
aff7seg.print(rotation_speed)
aff7seg.update_display()
else:
interval = 0
aff7seg.print(0)
aff7seg.update_display()