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éthode : Prise 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.
import microbit
import ustruct
class Amg8833():
def __init__(self, addr=0x69):
"""Initiaisation capteur
addr : adresse i2c. 0x69 par defaut"""
self.addr = addr
self.register = 0x80
self.size = 8
self.offset = 0
self.rate = 1
# PCTL NORMAL
microbit.i2c.write(self.addr, b'0x00')
microbit.i2c.write(self.addr, b'0x00')
# INITIAL RESET
microbit.i2c.write(self.addr, b'0x01')
microbit.i2c.write(self.addr, b'0x3F')
# FPS 10
microbit.i2c.write(self.addr, b'0x02')
microbit.i2c.write(self.addr, b'0x00')
def getval(self, val):
absval = (val & 0x7FF)
if val & 0x800:
return - float(0x800 - absval) * 0.25
else:
return float(absval) * 0.25
def pixels(self):
pixs = [[0]*self.size for _ in range(self.size)]
reg = self.register
for ir in range(0, self.size):
for ic in range(0, self.size):
microbit.i2c.write(self.addr, bytes([reg]))
val = ustruct.unpack('<h',
microbit.i2c.read(self.addr, reg, 2))[0]
tmp = (self.getval(val) - self.offset) * self.rate
pixs[ir][ic] = tmp
reg += 2
return pixs
amg = Amg8833()
while True:
print(amg.pixels())
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.
Méthode : Programme 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.
import microbit as mb
import ustruct
class Amg8833():
def __init__(self, addr=0x69):
"""Initiaisation capteur
addr : adresse i2c. 0x69 par defaut"""
self.addr = addr
self.register = 0x80
self.size = 8
self.offset = 0
self.rate = 1
# PCTL NORMAL
mb.i2c.write(self.addr, b'0x00')
mb.i2c.write(self.addr, b'0x00')
# INITIAL RESET
mb.i2c.write(self.addr, b'0x01')
mb.i2c.write(self.addr, b'0x3F')
# FPS 10
mb.i2c.write(self.addr, b'0x02')
mb.i2c.write(self.addr, b'0x00')
def getval(self, val):
absval = (val & 0x7FF)
if val & 0x800:
return - float(0x800 - absval) * 0.25
else:
return float(absval) * 0.25
def convpix(self, val, m1, m2):
r = int((val-m1)/(m2-m1)*9)
r = min(9, r)
r = max(0, r)
return r
def pixels(self):
pixs = [[0]*self.size for _ in range(self.size)]
reg = self.register
for ir in range(0, self.size):
for ic in range(0, self.size):
mb.i2c.write(self.addr, bytes([reg]))
val = ustruct.unpack('<h', mb.i2c.read(self.addr, reg, 2))[0]
tmp = (self.getval(val) - self.offset) * self.rate
pixs[ir][ic] = tmp
reg += 2
return pixs
def hotspot_x(self):
max, indmax = 0, 0
reg = self.register
for ic in range(self.size):
for ir in range(self.size):
mb.i2c.write(self.addr, bytes([reg]))
val = ustruct.unpack('<h', mb.i2c.read(self.addr, reg, 2))[0]
tmp = (self.getval(val) - self.offset) * self.rate
if tmp > max:
max, indmax = tmp, ic
if 2 <= ir < 7 and 2 <= ic < 7:
mb.display.set_pixel(6-ic, 6-ir, self.convpix(tmp, 15, 40))
reg += 2
return indmax
amg = Amg8833()
def hotsearch():
hs = amg.hotspot_x()
# mb.display.show(hs)
if hs < 3:
mb.pin8.write_digital(1)
mb.pin12.write_digital(0)
elif hs > 4:
mb.pin8.write_digital(0)
mb.pin12.write_digital(1)
else:
mb.pin8.write_digital(0)
mb.pin12.write_digital(0)
while True:
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.
Attention : Mé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éthode : Le 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.
import microbit as mb
import ustruct
import machine
from random import randint
class Maqueen():
def __init__(self, addr=0x10):
"""Initiaisation robot
addr : adresse i2c. 0x10 par defaut"""
self.addr = addr
self._vitesse = 0 # vitesse entre 0 et 100
def getVitesse(self):
return self._vitesse
def setVitesse(self, v):
self._vitesse = v
def moteurDroit(self, v=None):
if v is None:
v = self._vitesse
sens = 0 if v >= 0 else 1 # sens moteur
vit = abs(v)*255//100 # vitesse moteur 0..255
mb.i2c.write(self.addr, bytearray([2, sens, vit]))
def moteurGauche(self, v=None):
if v is None:
v = self._vitesse
sens = 0 if v >= 0 else 1 # sens moteur
vit = abs(v)*255//100 # vitesse moteur 0..255
mb.i2c.write(self.addr, bytearray([0, sens, vit]))
def avance(self, v=None):
if v is not None:
self._vitesse = v
self.moteurDroit()
self.moteurGauche()
def recule(self):
self.moteurDroit(-self._vitesse)
self.moteurGauche(-self._vitesse)
def stop(self):
mb.i2c.write(self.addr, bytearray([0, 0, 0]))
mb.sleep(1)
mb.i2c.write(self.addr, bytearray([2, 0, 0]))
def distance(self):
"""Calcule la distance à l'obstacle en cm
pin1 : Trig
pin2 : Echo"""
mb.pin1.write_digital(1)
mb.sleep(10)
mb.pin1.write_digital(0)
mb.pin2.read_digital()
t2 = machine.time_pulse_us(mb.pin2, 1)
d = 340 * t2 / 20000
return d
class Amg8833():
def __init__(self, addr=0x69):
"""Initiaisation capteur
addr : adresse i2c. 0x69 par defaut"""
self.addr = addr
self.register = 0x80
self.size = 8
self.offset = 0
self.rate = 1
# PCTL NORMAL
mb.i2c.write(self.addr, b'0x00')
mb.i2c.write(self.addr, b'0x00')
# INITIAL RESET
mb.i2c.write(self.addr, b'0x01')
mb.i2c.write(self.addr, b'0x3F')
# FPS 10
mb.i2c.write(self.addr, b'0x02')
mb.i2c.write(self.addr, b'0x00')
def getval(self, val):
absval = (val & 0x7FF)
if val & 0x800:
return - float(0x800 - absval) * 0.25
else:
return float(absval) * 0.25
def hotspot_x(self):
def convpix(val, m1, m2):
r = int((val-m1)/(m2-m1)*9)
r = min(9, r)
r = max(0, r)
return r
mx, indmax = 0, 0
reg = self.register
for ic in range(self.size):
for ir in range(self.size):
mb.i2c.write(self.addr, bytes([reg]))
val = ustruct.unpack('<h', mb.i2c.read(self.addr, reg, 2))[0]
tmp = (self.getval(val) - self.offset) * self.rate
if tmp > mx:
mx, indmax = tmp, ic
if 2 <= ir < 7 and 2 <= ic < 7:
mb.display.set_pixel(6-ic, 6-ir, convpix(tmp, 15, 40))
reg += 2
return indmax
mq = Maqueen()
amg = Amg8833()
def obstacle():
d = mq.distance()
if d < 20:
r = randint(1, 3)
if r == 1:
mq.recule()
mb.sleep(2000)
mq.moteurGauche(0)
mb.sleep(1000)
elif r == 2:
mq.moteurDroit(50)
mq.moteurGauche(0)
mb.sleep(1000)
elif r == 3:
mq.moteurDroit(-50)
mq.moteurGauche(50)
mb.sleep(1000)
def hotsearch():
pause = 200
hs = amg.hotspot_x()
# mb.display.show(hs)
m2 = mq.moteurDroit
m1 = mq.moteurGauche
if hs < 1:
m2(100)
m1(0)
mb.sleep(pause)
elif hs < 2:
m2(50)
m1(0)
mb.sleep(pause)
elif hs < 3:
m2(25)
m1(0)
mb.sleep(pause)
elif hs > 6:
m2(0)
m1(100)
mb.sleep(pause)
elif hs > 5:
m2(0)
m1(50)
mb.sleep(pause)
elif hs > 4:
m2(0)
m1(25)
mb.sleep(pause)
mq.setVitesse(25)
while True:
hotsearch()
obstacle()
mq.avance()