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

photo capteur

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.

Prise en charge avec 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.

photo robot

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

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.

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()