rob_hot : le robot guidé à la chaleur - niveau intermédiaire

Dans ce projet, nous allons fabriquer un robot qui se guide tout seul vers une source de chaleur. Celui-ci s'appuiera sur une plate-forme maqueen

La détection de chaleur se fera via le capteur AMG8833 pour lequel adafruit propose un module i2c simple à mettre en œuvre. Il est disponible en France, par exemple chez mouser.

Découverte du capteur AMG8833

Le capteur AMG8833 se connecte à la carte microbit via une interface i2C. Celle-ci ne nécessite que 2 fils : SDA et SCL en plus l'alimentation (entre 3 et 5v). Il est compatible arduino, raspberry pi, microbit ainsi que bien sûr les cartes adafruit fonctionnant avec circuitpython. Adafruit fournit d'ailleurs une librairie pour prendre en charge ce capteur.

MéthodePrise en charge sous Microbit

La librairie Adafruit n'est pas compatible avec la carte microbit. Je l'ai donc adaptée à cette dernière. Le fonctionnement du capteur est assez simple : il renvoie une matrice 8x8 de nombres flottants correspondant aux températures lues dans la zone de vision du capteur.

Pour le branchement, connectez

  • VIN sur le 3V de la carte microbit

  • GND sur GND

  • SCL et SDA sur les broches SCL (19) et SDA (20) de la microbit.

Cela nécessite une carte d'extension. On peut aussi utiliser le robot maqueen qui offre une connectique i2c (juste derrière le capteur ultrasons).

Voici le code permettant de tester le bon fonctionnement du dispositif.

1
import microbit
2
import ustruct
3
4
class Amg8833():
5
    def __init__(self, addr=0x69):
6
        """Initiaisation capteur
7
        addr : adresse i2c. 0x69 par defaut"""
8
        self.addr = addr
9
        self.register = 0x80
10
        self.size = 8
11
        self.offset = 0
12
        self.rate = 1
13
14
        # PCTL NORMAL
15
        microbit.i2c.write(self.addr, b'0x00')
16
        microbit.i2c.write(self.addr, b'0x00')
17
        # INITIAL RESET
18
        microbit.i2c.write(self.addr, b'0x01')
19
        microbit.i2c.write(self.addr, b'0x3F')
20
        # FPS 10
21
        microbit.i2c.write(self.addr, b'0x02')
22
        microbit.i2c.write(self.addr, b'0x00')
23
24
    def getval(self, val):
25
        absval = (val & 0x7FF)
26
        if val & 0x800:
27
            return - float(0x800 - absval) * 0.25
28
        else:
29
            return float(absval) * 0.25
30
31
    def pixels(self):
32
        pixs = [[0]*self.size for _ in range(self.size)]
33
        reg = self.register
34
        for ir in range(0, self.size):
35
            for ic in range(0, self.size):
36
                microbit.i2c.write(self.addr, bytes([reg]))
37
                val = ustruct.unpack('<h', 
38
                                     microbit.i2c.read(self.addr, reg, 2))[0]
39
                tmp = (self.getval(val) - self.offset) * self.rate
40
                pixs[ir][ic] = tmp
41
                reg += 2
42
        return pixs
43
44
amg = Amg8833()
45
46
while True:
47
    print(amg.pixels())
48

On obtient en retour quelque chose ressemblant à cela : une matrice 8x8 de températures. difficile d'imaginer plus simple !

[

[20.25, 20.5, 22.0, 23.75, 22.75, 22.25, 23.5, 23.25],

[19.75, 20.75, 22.25, 24.25, 23.25, 22.0, 21.75, 20.0],

[18.75, 20.25, 21.25, 21.5, 20.75, 20.5, 20.0, 18.5],

[19.75, 17.25, 18.75, 18.75, 18.5, 18.0, 18.5, 17.5],

[18.25, 17.25, 17.75, 18.0, 17.25, 17.0, 17.25, 17.5],

[19.0, 18.0, 18.5, 18.0, 16.75, 16.75, 17.75, 17.25],

[19.0, 18.25, 18.75, 18.5, 17.5, 17.5, 17.75, 17.75],

[19.5, 17.75, 17.5, 18.25, 17.25, 17.25, 17.25, 17.25]

]

Détection de la source de chaleur

Dans cette partie, nous allons rechercher la source de chaleur. Pour cela, nous allons parcourir la matrice de températures à la recherche du plus grand nombre et récupérer la colonne dans laquelle il se trouve.

  • Si l'indice de cette colonne est entre 0 et 2, on considérera que la source de chaleur est à gauche face au robot. On allumera alors sa LED droite.

  • Si l'indice de cette colonne est supérieure à 5, on considérera que la source est à droite face au robot. On allumera alors sa LED gauche.

  • Sinon, la source de chaleur est à peu près centrée.

Dans le même temps, nous allumerons sur la matrice LED les 25 pixels au centre du capteur, avec une luminosité d'autant plus grande que la source de chaleur est importante.

détection source de chaleur

MéthodeProgramme Python

Voici le programme correspondant. J'ai ajouté une méthode hotspot_x à la classe Amg8833. Celle-ci parcourt les pixels en mémorisant l'indice de colonne du pixel le plus chaud. C'est cet indice qui est renvoyé.

Dans le même temps, cette méthode affiche sur la matrice LED les pixels du centre du capteur, en exploitant les 9 niveaux de luminosité possible en fonction de la température.

1
import microbit as mb
2
import ustruct
3
4
class Amg8833():
5
    def __init__(self, addr=0x69):
6
        """Initiaisation capteur
7
        addr : adresse i2c. 0x69 par defaut"""
8
        self.addr = addr
9
        self.register = 0x80
10
        self.size = 8
11
        self.offset = 0
12
        self.rate = 1
13
14
        # PCTL NORMAL
15
        mb.i2c.write(self.addr, b'0x00')
16
        mb.i2c.write(self.addr, b'0x00')
17
        # INITIAL RESET
18
        mb.i2c.write(self.addr, b'0x01')
19
        mb.i2c.write(self.addr, b'0x3F')
20
        # FPS 10
21
        mb.i2c.write(self.addr, b'0x02')
22
        mb.i2c.write(self.addr, b'0x00')
23
24
    def getval(self, val):
25
        absval = (val & 0x7FF)
26
        if val & 0x800:
27
            return - float(0x800 - absval) * 0.25
28
        else:
29
            return float(absval) * 0.25
30
31
    def convpix(self, val, m1, m2):
32
        r = int((val-m1)/(m2-m1)*9)
33
        r = min(9, r)
34
        r = max(0, r)
35
        return r
36
37
    def pixels(self):
38
        pixs = [[0]*self.size for _ in range(self.size)]
39
        reg = self.register
40
        for ir in range(0, self.size):
41
            for ic in range(0, self.size):
42
                mb.i2c.write(self.addr, bytes([reg]))
43
                val = ustruct.unpack('<h', mb.i2c.read(self.addr, reg, 2))[0]
44
                tmp = (self.getval(val) - self.offset) * self.rate
45
                pixs[ir][ic] = tmp
46
                reg += 2
47
        return pixs
48
49
    def hotspot_x(self):
50
        max, indmax = 0, 0
51
        reg = self.register
52
        for ic in range(self.size):
53
            for ir in range(self.size):
54
                mb.i2c.write(self.addr, bytes([reg]))
55
                val = ustruct.unpack('<h', mb.i2c.read(self.addr, reg, 2))[0]
56
                tmp = (self.getval(val) - self.offset) * self.rate
57
                if tmp > max:
58
                    max, indmax = tmp, ic
59
                if 2 <= ir < 7 and 2 <= ic < 7:
60
                    mb.display.set_pixel(6-ic, 6-ir, self.convpix(tmp, 15, 40))
61
                reg += 2
62
        return indmax
63
64
65
amg = Amg8833()
66
67
def hotsearch():
68
    hs = amg.hotspot_x()
69
    # mb.display.show(hs)
70
71
    if hs < 3:
72
        mb.pin8.write_digital(1)
73
        mb.pin12.write_digital(0)
74
    elif hs > 4:
75
        mb.pin8.write_digital(0)
76
        mb.pin12.write_digital(1)
77
    else:
78
        mb.pin8.write_digital(0)
79
        mb.pin12.write_digital(0)
80
81
while True:
82
    hotsearch()

Mise en route de rob_hot

Dans cette partie, nous allons simplement mettre en commun la partie sur le robot maqueen, en particulier le code sur la détection d'obstacles, et la détection d'une source de chaleur.

AttentionMémoire limitée

La RAM de la microbit est très limitée et on atteint ici les limites de ce que la carte peut accepter en python. Pour des projets plus conséquents, on sera obligé de revenir au langage C en utilisant par exemple l'environnement arduino.

MéthodeLe programme rob_hot

Pour faire fonctionner le robot, j'ai élagué au maximum les classes maqueen et amg8833 en ne gardant que le strict minimum. Voici le robot en situation. Observez la manière dont il me suit, puisque je suis la source la plus chaude en face du capteur.

1
import microbit as mb
2
import ustruct
3
import machine
4
from random import randint
5
6
class Maqueen():
7
    def __init__(self, addr=0x10):
8
        """Initiaisation robot
9
        addr : adresse i2c. 0x10 par defaut"""
10
        self.addr = addr
11
        self._vitesse = 0  # vitesse entre 0 et 100
12
13
    def getVitesse(self):
14
        return self._vitesse
15
16
    def setVitesse(self, v):
17
        self._vitesse = v
18
19
    def moteurDroit(self, v=None):
20
        if v is None:
21
            v = self._vitesse
22
        sens = 0 if v >= 0 else 1  # sens moteur
23
        vit = abs(v)*255//100   # vitesse moteur 0..255
24
        mb.i2c.write(self.addr, bytearray([2, sens, vit]))
25
26
    def moteurGauche(self, v=None):
27
        if v is None:
28
            v = self._vitesse
29
        sens = 0 if v >= 0 else 1  # sens moteur
30
        vit = abs(v)*255//100   # vitesse moteur 0..255
31
        mb.i2c.write(self.addr, bytearray([0, sens, vit]))
32
33
    def avance(self, v=None):
34
        if v is not None:
35
            self._vitesse = v
36
        self.moteurDroit()
37
        self.moteurGauche()
38
39
    def recule(self):
40
        self.moteurDroit(-self._vitesse)
41
        self.moteurGauche(-self._vitesse)
42
43
    def stop(self):
44
        mb.i2c.write(self.addr, bytearray([0, 0, 0]))
45
        mb.sleep(1)
46
        mb.i2c.write(self.addr, bytearray([2, 0, 0]))
47
48
    def distance(self):
49
        """Calcule la distance à l'obstacle en cm
50
        pin1 : Trig
51
        pin2 : Echo"""
52
        mb.pin1.write_digital(1)
53
        mb.sleep(10)
54
        mb.pin1.write_digital(0)
55
56
        mb.pin2.read_digital()
57
        t2 = machine.time_pulse_us(mb.pin2, 1)
58
59
        d = 340 * t2 / 20000
60
        return d
61
62
class Amg8833():
63
    def __init__(self, addr=0x69):
64
        """Initiaisation capteur
65
        addr : adresse i2c. 0x69 par defaut"""
66
        self.addr = addr
67
        self.register = 0x80
68
        self.size = 8
69
        self.offset = 0
70
        self.rate = 1
71
72
        # PCTL NORMAL
73
        mb.i2c.write(self.addr, b'0x00')
74
        mb.i2c.write(self.addr, b'0x00')
75
        # INITIAL RESET
76
        mb.i2c.write(self.addr, b'0x01')
77
        mb.i2c.write(self.addr, b'0x3F')
78
        # FPS 10
79
        mb.i2c.write(self.addr, b'0x02')
80
        mb.i2c.write(self.addr, b'0x00')
81
82
    def getval(self, val):
83
        absval = (val & 0x7FF)
84
        if val & 0x800:
85
            return - float(0x800 - absval) * 0.25
86
        else:
87
            return float(absval) * 0.25
88
89
    def hotspot_x(self):
90
        def convpix(val, m1, m2):
91
            r = int((val-m1)/(m2-m1)*9)
92
            r = min(9, r)
93
            r = max(0, r)
94
            return r
95
96
        mx, indmax = 0, 0
97
        reg = self.register
98
        for ic in range(self.size):
99
            for ir in range(self.size):
100
                mb.i2c.write(self.addr, bytes([reg]))
101
                val = ustruct.unpack('<h', mb.i2c.read(self.addr, reg, 2))[0]
102
                tmp = (self.getval(val) - self.offset) * self.rate
103
                if tmp > mx:
104
                    mx, indmax = tmp, ic
105
                if 2 <= ir < 7 and 2 <= ic < 7:
106
                    mb.display.set_pixel(6-ic, 6-ir, convpix(tmp, 15, 40))
107
                reg += 2
108
        return indmax
109
110
111
mq = Maqueen()
112
amg = Amg8833()
113
114
def obstacle():
115
    d = mq.distance()
116
    if d < 20:
117
        r = randint(1, 3)
118
        if r == 1:
119
            mq.recule()
120
            mb.sleep(2000)
121
            mq.moteurGauche(0)
122
            mb.sleep(1000)
123
        elif r == 2:
124
            mq.moteurDroit(50)
125
            mq.moteurGauche(0)
126
            mb.sleep(1000)
127
        elif r == 3:
128
            mq.moteurDroit(-50)
129
            mq.moteurGauche(50)
130
            mb.sleep(1000)
131
132
def hotsearch():
133
    pause = 200
134
    hs = amg.hotspot_x()
135
    # mb.display.show(hs)
136
    m2 = mq.moteurDroit
137
    m1 = mq.moteurGauche
138
    if hs < 1:
139
        m2(100)
140
        m1(0)
141
        mb.sleep(pause)
142
    elif hs < 2:
143
        m2(50)
144
        m1(0)
145
        mb.sleep(pause)
146
    elif hs < 3:
147
        m2(25)
148
        m1(0)
149
        mb.sleep(pause)
150
    elif hs > 6:
151
        m2(0)
152
        m1(100)
153
        mb.sleep(pause)
154
    elif hs > 5:
155
        m2(0)
156
        m1(50)
157
        mb.sleep(pause)
158
    elif hs > 4:
159
        m2(0)
160
        m1(25)
161
        mb.sleep(pause)
162
163
mq.setVitesse(25)
164
165
while True:
166
    hotsearch()
167
    obstacle()
168
    mq.avance()
169
170