3.1. Microcontrôleurs

L’OpenMV Cam fonctionne sur un microcontrôleur (MCU) : une puce unique qui combine un processeur, de la mémoire de travail (RAM), de l’espace de stockage de programme (mémoire flash) et un ensemble de périphériques – des blocs matériels permettant d’interagir avec le monde extérieur.

Les périphériques constituent la partie intéressante. Chacun est un morceau de silicium dédié à une seule tâche : mettre une broche à l’état haut ou bas, mesurer une tension analogique, faire transiter des octets sur un bus série. Le processeur configure et lit chaque périphérique via des registres – des adresses mémoire fixes que le matériel surveille et met à jour.

MicroPython encapsule ces registres dans des classes au sein du module machine. machine.Pin(...) renvoie un objet contrôlant une broche d”entrée/sortie à usage général (GPIO) – un fil que la puce peut maintenir à l’état haut (environ 3,3 V) ou bas (environ 0 V), ou lire comme l’un de ces deux états lorsqu’un élément externe le pilote. machine.ADC(...) expose le convertisseur analogique-numérique, qui mesure la tension sur une broche et la rapporte sous forme de nombre. machine.UART(...) met en œuvre un émetteur-récepteur asynchrone universel (UART) – un périphérique qui envoie et reçoit des octets un bit à la fois sur une paire de fils, TX (transmission) et RX (réception). D’autres classes couvrent le reste des périphériques. Le script lit et écrit des objets Python ; MicroPython traduit chaque accès en les lectures et écritures de registres correspondantes, et celles-ci déplacent des bits sur des fils physiques.

Le contour d'une puce unique contenant des blocs étiquetés -- processeur, RAM, mémoire flash et une rangée de blocs de périphériques (GPIO, ADC, minuteur/PWM, UART/SPI/I2C, CAN) reliés par un bus interne, avec des flèches partant de chaque périphérique pour sortir de la puce vers les étiquettes des broches physiques.

Un MCU regroupe processeur, mémoire et périphériques dans une seule puce. Chaque périphérique est exposé à Python par une classe du module machine.

3.1.1. La boucle principale

Presque tous les programmes de microcontrôleur partagent la même forme : une configuration ponctuelle en haut du script (importation des modules, configuration des broches, ouverture des bus), puis une boucle infinie while True: en bas. À l’intérieur de la boucle, le programme lit les entrées, prend des décisions et met à jour les sorties, encore et encore. La boucle est le programme ; lorsque le script se termine, l’appareil cesse de faire quoi que ce soit.

# setup, runs once
from machine import Pin
led = Pin("P0", Pin.OUT)

# main loop, runs forever
while True:
    led.value(1)
    # ... do work ...
    led.value(0)
    # ... do other work ...

Cette forme – configurer une fois, puis boucler indéfiniment – est le motif de la boucle principale. Tout ce qui suit concerne ce qui se trouve à l’intérieur de celle-ci.

3.1.2. Contrôle en temps réel

Un programme de bureau s’exécute aux côtés de nombreux autres. Le système d’exploitation planifie son travail sur un ou plusieurs threads – des flux d’exécution indépendants entre lesquels il bascule milliseconde après milliseconde. Lorsqu’un thread attend une entrée/sortie (disque, réseau, déplacement de la souris par l’utilisateur), le système d’exploitation confie le processeur à un autre. Le programme est principalement piloté par les événements : le gestionnaire de fenêtres appelle votre code lorsqu’une entrée arrive, la bibliothèque HTTP reprend votre code lorsque des octets arrivent sur le socket. Quelque chose de plus grand vous appelle.

Un programme de microcontrôleur, c’est l’inverse. Par défaut, il n’y a pas de système d’exploitation, pas d’ordonnanceur et pas d’autre thread. La boucle principale qui vient d’être montrée est la seule boucle. Les périphériques déclenchent des interruptions ou exposent des indicateurs d’état ; la boucle les interroge ou traite directement les interruptions. Si la boucle se bloque dans un time.sleep_ms(1000), l’appareil ne fait rien pendant cette seconde ; il n’y a pas d’autre thread pour combler le vide.

Deux conséquences en découlent et s’appliquent partout :

  • Le temps est réel. Lire une broche deux fois dans une boucle serrée prend des microsecondes ; dormir dix millisecondes signifie dix millisecondes durant lesquelles rien d’autre ne se passe. Le motif de temporisation non bloquante est la réponse.

  • Le matériel est réel. Mettre machine.Pin.value à 1 applique environ 3,3 V sur un fil physique ; le mettre à 0 y applique environ 0 V. Les autres parties du circuit voient cette tension immédiatement – y compris les composants que la broche peut endommager si elle est mal pilotée.