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 :

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émentdiode 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éthodeLe 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.

1
from microbit import *
2
3
pin2.set_analog_period_microseconds(300)
4
speed=0 # Vitesse du moteur de 0 a 5
5
6
while True:
7
    if button_a.was_pressed():
8
        speed = (speed+1)%6
9
        display.show(speed
10
        pin2.write_analog(1023*speed//5)

ComplémentFabrication 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éthodePilote 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éthodeLe 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éthodeLe 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().

1
def motor_speed():
2
    t=ticks_ms()
3
    while pin2.read_digital()==1 and ticks_ms()-t<TIMEOUT_MS:
4
        pass
5
    if ticks_ms()-t>TIMEOUT_MS:
6
        return -1
7
    t1=time_pulse_us(pin2,1,TIMEOUT_MS*1000)
8
    t=ticks_ms()
9
    while pin2.read_digital()==0 and ticks_ms()-t<TIMEOUT_MS:
10
        pass
11
    if ticks_ms()-t>TIMEOUT_MS:
12
        return -1
13
    t2=time_pulse_us(pin2,0,TIMEOUT_MS*1000)
14
    return int(60*1000000/((t1+t2)*2))
15

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.

1
def read_speed():
2
    liste = []
3
    for i in range(10):
4
        m=motor_speed()
5
        if m != -1:
6
            liste.append(m)
7
    if len(liste)==0:
8
        return 0
9
10
    liste.sort()
11
    liste_filtree=[liste[0]]
12
    i=1
13
    while i<len(liste) and liste[i]/liste[i-1]<1.1:
14
        liste_filtree.append(liste[i])
15
        i+=1
16
    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éthodeLe programme complet

1
from microbit import *
2
from machine import time_pulse_us
3
from time import ticks_ms
4
from ht16k33_seg import seg_7x4
5
6
aff7seg = seg_7x4()
7
8
TIMEOUT_MS = 500
9
speed=0
10
11
pin2.set_analog_period_microseconds(300)
12
13
def motor_speed():
14
    # realise une unique mesure de vitesse
15
    t=ticks_ms()
16
    while pin2.read_digital()==1 and ticks_ms()-t<TIMEOUT_MS:
17
        pass
18
    if ticks_ms()-t>TIMEOUT_MS:
19
        return -1
20
    t1=time_pulse_us(pin2,1,TIMEOUT_MS*1000)
21
    t=ticks_ms()
22
    while pin2.read_digital()==0 and ticks_ms()-t<TIMEOUT_MS:
23
        pass
24
    if ticks_ms()-t>TIMEOUT_MS:
25
        return -1
26
    t2=time_pulse_us(pin2,0,TIMEOUT_MS*1000)
27
    return int(60*1000000/((t1+t2)*2))
28
29
def read_speed():
30
    # Fait la moyenne de 10 mesures
31
    # en eliminant les valeurs aberrantes
32
    liste = []
33
    for i in range(10):
34
        m=motor_speed()
35
        if m != -1:
36
            liste.append(m)
37
    if len(liste)==0:
38
        return 0
39
40
    liste.sort()
41
    liste_filtree=[liste[0]]
42
    i=1
43
    while i<len(liste) and liste[i]/liste[i-1]<1.1:
44
        liste_filtree.append(liste[i])
45
        i+=1
46
    return sum(liste_filtree)//len(liste_filtree)
47
    
48
while True:
49
    if button_a.was_pressed():
50
        speed = (speed+1)%6
51
        display.show(speed)
52
        pin1.write_analog(1023*speed//5)
53
54
    if speed != 0:
55
        aff7seg.print(read_speed())
56
        aff7seg.update_display()
57
    else:
58
        aff7seg.print(0)
59
        aff7seg.update_display()
60

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 :

  1. On allume la LED dès qu'une impulsion est détectée

  2. 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éthodeLe programme
1
from microbit import *
2
3
speed = 0
4
strob = False
5
6
pin2.set_analog_period_microseconds(300)
7
8
while True:
9
    if button_a.was_pressed():
10
        speed = (speed+1)%6
11
        display.show(speed)
12
        pin1.write_analog(1023*speed//5)
13
    
14
    if button_b.was_pressed():
15
        strob = not strob
16
        pin0.write_digital(0)
17
        
18
    if strob :
19
        if pin2.read_digital()==0:
20
            pin0.write_digital(1)
21
        else:
22
            pin0.write_digital(0)
23
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éthodeLe 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 :

1
rotation_speed = read_speed()
2
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.

1
if ticks_us() - last_flash > interval:
2
    last_flash = ticks_us()
3
    pin0.write_digital(1)
4
    sleep_us(interval//40)
5
else:
6
    pin0.write_digital(0)

Voici pour finir le programme complet

1
from microbit import *
2
from machine import time_pulse_us
3
from time import ticks_ms, ticks_us, sleep_us
4
from ht16k33_seg import seg_7x4
5
6
aff7seg = seg_7x4()
7
8
TIMEOUT_MS = 500
9
speed = 0
10
rotation_speed = 0
11
interval = 0
12
last_flash = 0
13
strob = False
14
15
pin2.set_analog_period_microseconds(300)
16
17
def motor_speed():
18
    # realise une unique mesure de vitesse
19
    t=ticks_ms()
20
    while pin2.read_digital()==1 and ticks_ms()-t<TIMEOUT_MS:
21
        pass
22
    if ticks_ms()-t>TIMEOUT_MS:
23
        return -1
24
    t1=time_pulse_us(pin2,1,TIMEOUT_MS*1000)
25
    t=ticks_ms()
26
    while pin2.read_digital()==0 and ticks_ms()-t<TIMEOUT_MS:
27
        pass
28
    if ticks_ms()-t>TIMEOUT_MS:
29
        return -1
30
    t2=time_pulse_us(pin2,0,TIMEOUT_MS*1000)
31
    return int(60*1000000/((t1+t2)*2))
32
33
def read_speed():
34
    # Fait la moyenne de 10 mesures
35
    # en eliminant les valeurs aberrantes
36
    liste = []
37
    for i in range(10):
38
        m=motor_speed()
39
        if m != -1:
40
            liste.append(m)
41
    if len(liste)==0:
42
        return 0
43
44
    liste.sort()
45
    liste_filtree=[liste[0]]
46
    i=1
47
    while i<len(liste) and liste[i]/liste[i-1]<1.1:
48
        liste_filtree.append(liste[i])
49
        i+=1
50
    return sum(liste_filtree)//len(liste_filtree)
51
    
52
while True:
53
    if button_a.was_pressed():
54
        speed = (speed+1)%6
55
        display.show(speed)
56
        pin1.write_analog(1023*speed//5)
57
    
58
    if button_b.was_pressed():
59
        strob = not strob
60
        pin0.write_digital(0)
61
        
62
    if strob :
63
        if ticks_us() - last_flash > interval:
64
            last_flash = ticks_us()
65
            pin0.write_digital(1)
66
            sleep_us(interval//40)
67
        else:
68
            pin0.write_digital(0)
69
    else:
70
        if speed != 0:
71
            rotation_speed = read_speed()
72
            interval = 60 * 1000000 // rotation_speed 
73
            aff7seg.print(rotation_speed)
74
            aff7seg.update_display()
75
        else:
76
            interval = 0
77
            aff7seg.print(0)
78
            aff7seg.update_display()
79