Python est un langage interprété. Il fonctionnera donc sur n'importe quelle plate-forme qui dispose d'un interpréteur. Un interpréteur est un programme relativement complexe qui ne peut pas fonctionner avec de trop faibles ressources. C'est la raison pour laquelle on ne peut pas faire fonctionner Python sur du matériel qui possède trop peu de ressources.
Les interpréteurs Python les moins gourmands sont MicroPython et CircuitPython, qui sont des interpréteurs écrits pour des microcontrôleurs. Toutefois, les microcontrôleurs disposant de trop peu de ressources ne permettront pas de les faire fonctionner.
Actuellement, ni MicroPython, ni CircruitPython ne peuvent fonctionner sur un Arduino Uno. En revanche, MicroPython pourra fonctionner sur un Arduino Due, certains ESP, un micro:bit… CircuitPython est supporté par les cartes Adafruit (il est dérivé de MicroPython).
Ce document traite de l'utilisation de Python avec un arduino Uno, c'est à dire du moyen de faire communiquer un programme Python, qui tourne sur un ordinateur de bureau, avec un programme qui tourne sur un Arduino Uno (donc écrit en ≈ C).
La communication entre l'ordinateur de bureau et l'Arduino sera réalisée par le port série. L'Arduino possède une liaison série (broches RX, TX), et est capable d'émuler d'autres liaisons séries de manière logicielle sur d'autres broches (avec la bibliothèque SoftwareSerial).
Côté Python, on utilise généralement le module pySerial pour utiliser une liaison série.
L'idée générale est la suivante :
Pour utiliser Python et Arduino, il faut donc développer 2 programmes :
Module Python utilisé dans la suite :
""" Utilitaires pour l'utilisation d'une carte Arduino avec Python """ import time import serial.tools.list_ports def get_serial_ports(): """ Renvoie la liste des ports série sur lesquels il semble qu'une Arte Arduino soit branchée """ ports = list(serial.tools.list_ports.comports()) arduino_ports = [] for port_no, description, address in ports: # print(port_no, description, address) if 'Arduino' in description: arduino_ports.append(port_no) return arduino_ports def restart(ser): """ Redémarre le programme de l'Arduino connecté par la liaison série ser """ ser.setDTR(False) time.sleep(0.1) ser.setDTR(True)
Programme pour l'Arduino :
int compteur; void setup () { Serial.begin(9600); pinMode(13, OUTPUT); compteur = 0; } void loop () { Serial.println(compteur); compteur += 1; digitalWrite(13, HIGH); delay (200); digitalWrite(13, LOW); delay(1800); }
Programme pour le PC :
import serial import outils_arduino ports = outils_arduino.get_serial_ports() print(ports) port = ports[0] serial_port = serial.Serial(port=port, baudrate=9600) for i in range(10): data = serial_port.readline() idata = int(data.strip()) print("Read : ", data) print("iRead : ", idata) serial_port.close()
Établit un protocole de communication assez rudimentaire : commande 'L' suivie de 3 octets pour l'intensité de rouge, de vert et de bleu.
Programme pour l'Arduino :
const int redPin = 3; const int greenPin = 5; const int bluePin = 6; int R, G, B; int error = 0; void setup() { // Par défaut, donc facultatif... pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); pinMode(bluePin, OUTPUT); pinMode(13, OUTPUT); R = 0; G = 255; B = 0; Serial.begin(9600); analogWrite(redPin, 255-R); analogWrite(greenPin, 255-G); analogWrite(bluePin, 255-B ); } void loop() { char commande; if (Serial.available()) { commande = Serial.read(); if (commande == 'L') { R = Serial.read(); G = Serial.read(); B = Serial.read(); analogWrite(redPin, 255-R); analogWrite(greenPin, 255-G); analogWrite(bluePin, 255-B ); error = 0; } else { error = 1; } } if (error) digitalWrite(13, HIGH); else digitalWrite(13, LOW); delay(100); }
Programme pour le PC :
import serial import outils_arduino import time def pilote_led(ser, r, g, b): n = ser.write(b'L' + bytes([r, g, b])) ports = outils_arduino.get_serial_ports() print(ports) port = ports[0] serial_port = serial.Serial(port=port, baudrate=9600) time.sleep(3) print("Go") r, g, b = 255, 0, 0 for _ in range(200): pilote_led(serial_port, r, g, b) r, g, b = g, b, r time.sleep(1) serial_port.close()
Plutôt que de développer un programme Arduino spécifique pour chaque application, il existe des programmes génériques qui permettent d'activer les diverses fonctionnalités de l'Arduino.
Certains sont listés sur cette page Arduino and Python
Une des solutions les plus standard semble être l'utilisation du protocole Firmata.
On le trouve sous forme d'une bibliothèque Arduino (Firmata, by Firmata, v 2.5.8).
Une fois la bibliothèque installée, on dispose de plusieurs exemples (menu Fichier/Exemples/Firmata),
dont : StandardFirmata
. On peut par exemple installer ce croquis sur l'Arduino.
Coté PC, il existe plusieurs modules Python qui implémentent le module Firmata. J'ai testé pyFirmata (il y a une liste d'alternatives ici : firmata-client-libraries) :
from pyfirmata import Arduino, util board = Arduino('/dev/ttyACM0') import time r, g, b = 0, 1, 1 pin3 = board.get_pin("d:3:p") pin5 = board.get_pin("d:5:p") pin6 = board.get_pin("d:6:p") for i in range(10): pin3.write(r) pin5.write(g) pin6.write(b) r, g, b = g, b, r time.sleep(1)
Ce type de capteur n'est pas supporté par le programme StandardFirmata. Il existe par contre de nombreuses variantes patchées. Nous testons ici FirmataExpress + Ultrasonic (le premier utilise le second).
Le firmware `FirmataExpress` est téléversé sur un arduino Nano.
Côté PC Python, l'auteur de FirmataExpress MrYsLab (github) propose PyMata, et une version plus récente (python 3.7+) utilisant la programmation asynchrone (pymata-express).
Le test a été réalisé avec Pymata
import time import sys import signal from PyMata.pymata import PyMata def signal_handler(sig, frame): print('You pressed Ctrl+C') if board is not None: board.reset() board.close() sys.exit(0) def loupiote(board, distance): mini = 3 maxi = 20 if distance > maxi: distance = maxi if distance < mini: distance = mini frac = (distance - mini) / (maxi - mini) board.analog_write(3, 255) board.analog_write(5, int(255 * (1- frac))) board.analog_write(6, int(255 * frac)) board = PyMata("/dev/ttyUSB0", verbose=True, baud_rate=115200) signal.signal(signal.SIGINT, signal_handler) board.sonar_config(12, 12) board.set_pin_mode(3, PyMata.PWM, PyMata.DIGITAL) board.set_pin_mode(5, PyMata.PWM, PyMata.DIGITAL) board.set_pin_mode(6, PyMata.PWM, PyMata.DIGITAL) time.sleep(1) while True: data = board.get_sonar_data() distance = data[12][1] print(distance) loupiote(board, distance) time.sleep(.2)