Guides · 18 min de lecture

Communication série Arduino Python : pyserial, CSV et graphique

Communication série Arduino Python avec pyserial : installation, trouver le port, enregistrer les mesures en CSV, envoyer des commandes, graphique en direct.

La communication série Arduino Python, c'est un programme Python qui tourne sur ton ordinateur et échange des données avec un Arduino par le câble USB. L'Arduino mesure un capteur et écrit le résultat sur le port série ; Python lit ces lignes avec la bibliothèque pyserial, les enregistre dans un fichier CSV, les affiche sur un graphique ou prend une décision et renvoie une commande à l'Arduino. Tu combines ainsi le contrôle matériel précis de l'Arduino avec la puissance de Python pour le traitement des données, les graphiques, l'intelligence artificielle et l'automatisation. Aucun module supplémentaire n'est nécessaire : le câble USB qui te sert à téléverser tes programmes suffit.

Dans ce guide, tu vas apprendre pas à pas à installer pyserial, à trouver automatiquement le port de l'Arduino, à enregistrer les données d'un capteur avec un horodatage dans un fichier CSV, à envoyer des commandes LED et PWM depuis Python vers l'Arduino et à tracer un graphique en direct avec matplotlib. Nous verrons ensuite comment choisir le format des données et la fin de ligne, les deux problèmes les plus déroutants (l'erreur de port occupé et le redémarrage de la carte à l'ouverture du port) et ce qui change quand on fait la même chose en MicroPython sur un Raspberry Pi Pico. Les exemples sont écrits pour l'Arduino Uno ; les modifications nécessaires pour l'ESP32 et le Pico sont indiquées à part.

Carte Arduino Uno R3 et prise USB
Arduino Uno : la prise USB sert à la fois à téléverser le code et à communiquer en série avec Python ; les LED TX et RX clignotent pendant le passage des données.

Comment l'Arduino communique-t-il avec Python ?

La puce USB-série de l'Arduino Uno (un ATmega16U2 sur les cartes officielles, le plus souvent un CH340 sur les clones) apparaît sur l'ordinateur comme un port série virtuel : COM3, COM4, etc. sous Windows ; /dev/cu.usbmodem… ou /dev/cu.usbserial-… sous macOS ; /dev/ttyACM0 ou /dev/ttyUSB0 sous Linux. Chaque caractère écrit côté Arduino avec Serial.print() passe par ce port vers l'ordinateur sous forme d'octets ; côté Python, pyserial ouvre le port comme un fichier, y lit et y écrit. Une distinction importante : l'Arduino Uno n'exécute pas de code Python. La carte exécute du code Arduino écrit en C++, l'ordinateur exécute Python, et les deux ne se parlent que par la liaison série. Pour que les deux côtés parlent la même langue, ces réglages doivent correspondre :

RéglageCôté ArduinoCôté PythonAttention
PortCréé automatiquement par l'USBserial.Serial("COM3", ...)Le nom du port dépend du système d'exploitation
Débit (bauds)Serial.begin(115200)serial.Serial(port, 115200)S'ils diffèrent, des caractères illisibles arrivent
Trame8 bits de données, sans parité, 1 bit de stop (8N1)La valeur par défaut de pyserial est aussi 8N1Il est rarement nécessaire de la changer
Fin de ligneSerial.println() ajoute \r\n à la finreadline() lit jusqu'à rencontrer \nN'oublie pas d'ajouter \n en envoyant depuis Python
Délai d'attenteSerial.setTimeout(), 1000 ms par défauttimeout=1 (secondes)Avec timeout=None, readline() attend indéfiniment

Installer pyserial (Windows, macOS, Linux)

  1. Python 3 doit être installé. Sous Windows, coche la case Add python.exe to PATH sur le premier écran de l'installateur téléchargé sur le site officiel de Python ; sous macOS et Linux, Python 3 est généralement déjà présent.
  2. Ouvre un terminal (Invite de commandes ou PowerShell sous Windows) et tape python -m pip install pyserial. Si Windows ne reconnaît pas la commande python, essaie py -m pip install pyserial ; sous macOS et Linux, la commande peut être python3 -m pip install pyserial.
  3. Vérifie l'installation : python -c "import serial; print(serial.__version__)" doit afficher un numéro de version comme 3.5.
  4. La partie sur le graphique en direct nécessite aussi matplotlib : python -m pip install matplotlib.
  5. Sous Linux, ajoute ton utilisateur au groupe dialout pour pouvoir ouvrir le port : sudo usermod -a -G dialout $USER, puis déconnecte-toi et reconnecte-toi. Si pip affiche l'erreur externally-managed-environment sous Raspberry Pi OS ou une version récente d'Ubuntu, utilise sudo apt install python3-serial ou crée un environnement virtuel avec python3 -m venv et lance pip à l'intérieur.
Il existe sur PyPI un autre paquet nommé serial qui n'a rien à voir avec les ports série. Si tu tapes pip install serial, ton code passe la ligne import serial mais échoue ensuite avec AttributeError: module 'serial' has no attribute 'Serial'. Le bon paquet est pyserial ; supprime le mauvais avec pip uninstall serial puis réinstalle pyserial. La même erreur apparaît si tu nommes ton propre fichier serial.py, car Python importe alors ton fichier au lieu de la bibliothèque.

Sur quel port est l'Arduino ? Trouver le port avec Python

Le plus simple est de regarder le menu Outils > Port de l'IDE Arduino, mais Python peut aussi lister les ports. Tape python -m serial.tools.list_ports -v dans un terminal : tu verras le nom, la description et l'identifiant matériel (VID:PID) de chaque port. Si tu ne sais pas quelle ligne correspond à l'Arduino, débranche la carte, relance la commande et note le port qui a disparu. Sous macOS, la même carte apparaît à la fois en /dev/tty… et en /dev/cu… ; dans Python, utilise celui qui commence par cu.

Module convertisseur USB-TTL CH340
La puce CH340 : sur la plupart des clones d'Arduino, c'est elle qui fait le pont entre l'USB et le port série ; son identifiant fabricant (VID) est 1A86.
Puce USBOù la trouve-t-on ?VID:PIDNom sous WindowsPort sous Linux
ATmega16U2Uno R3 et Mega 2560 officiels2341:0043 (Uno)Arduino Uno (COMx)/dev/ttyACM0
CH340Clones d'Uno, de Nano et de Mega1A86:7523USB-SERIAL CH340 (COMx)/dev/ttyUSB0
CP2102ESP32 DevKit, NodeMCU10C4:EA60Silicon Labs CP210x USB to UART Bridge (COMx)/dev/ttyUSB0
FT232RAnciens Nano, câbles FTDI0403:6001USB Serial Port (COMx)/dev/ttyUSB0
RP2040 (USB intégré)Raspberry Pi Pico sous MicroPython2E8A:0005Périphérique série USB (COMx)/dev/ttyACM0

La fonction ci-dessous liste tous les ports et, si elle reconnaît une carte grâce à son VID, renvoie le nom de son port. Si tu enregistres le code sous port_bul.py, tu peux écrire from port_bul import port_bul et PORT = port_bul() dans tes autres scripts au lieu de modifier le nom du port à la main à chaque fois.

Python
1import serial.tools.list_ports
2
3# Bilinen USB yongalarının üretici kimlikleri (VID) / Vendor IDs of common USB chips
4BILINEN = {
5    0x2341: "Arduino (orijinal / genuine)",
6    0x2A03: "Arduino (orijinal / genuine)",
7    0x1A86: "CH340 (klon / clone)",
8    0x10C4: "CP210x (ESP32, NodeMCU)",
9    0x0403: "FTDI",
10    0x2E8A: "Raspberry Pi Pico",
11}
12
13
14def port_bul():
15    portlar = sorted(serial.tools.list_ports.comports())
16    for p in portlar:
17        print(p.device, "|", p.description, "|", p.hwid)   # tüm portları listele / list every port
18    for p in portlar:
19        if p.vid in BILINEN:
20            print("Bulundu / found:", p.device, "->", BILINEN[p.vid])
21            return p.device
22    return None
23
24
25if __name__ == "__main__":
26    if port_bul() is None:
27        print("Kart yok: kabloyu ve surucuyu kontrol et / no board: check cable and driver")

Envoyer des données depuis l'Arduino : le premier programme

Les exemples utilisent un potentiomètre branché sur la broche A0 : la broche centrale va sur A0, l'une des broches extérieures sur 5V et l'autre sur GND. À la place du potentiomètre, tu peux aussi brancher une photorésistance (LDR) avec une résistance de 10 kΩ en pont diviseur de tension. Le programme fait une mesure toutes les 200 ms et envoie le résultat sur une seule ligne au format temps,valeur :

C++ (Arduino IDE)
1// A0 değerini "millis,deger" CSV satırı olarak gönderir / Sends the A0 reading as a "millis,value" CSV line
2const int SENSOR_PIN = A0;            // potansiyometrenin orta ucu / potentiometer wiper
3const unsigned long ARALIK = 200;     // gönderim aralığı, ms / send interval, ms
4unsigned long sonGonderim = 0;
5
6void setup() {
7  Serial.begin(115200);               // Python'daki hızla aynı olmalı / must match the baud rate in Python
8}
9
10void loop() {
11  if (millis() - sonGonderim >= ARALIK) {   // delay() olmadan zamanlama / timing without delay()
12    sonGonderim = millis();
13    int deger = analogRead(SENSOR_PIN);     // 0-1023
14    Serial.print(sonGonderim);              // zaman damgası / timestamp
15    Serial.print(',');                      // ayırıcı / separator
16    Serial.println(deger);                  // sonuna \r\n ekler / appends \r\n
17  }
18}

Utiliser millis() au lieu de delay() permet d'ajouter plus facilement, plus tard, la lecture des commandes Python dans la même boucle ; tous les détails sont dans millis et delay. Après le téléversement, ouvre le Moniteur série à 115200 bauds : tu verras défiler des lignes comme 2573,512. Ce n'est qu'une vérification : ferme toujours le Moniteur série avant de lancer le script Python, sinon le port sera occupé. Si tu découvres le Moniteur série, le guide du Moniteur série Arduino te sera utile.

Lire les données de l'Arduino avec Python et les enregistrer en CSV

Python
1import csv
2import time
3from datetime import datetime
4
5import serial
6
7PORT = "COM3"     # macOS: /dev/cu.usbmodem14101, Linux: /dev/ttyACM0
8BAUD = 115200     # Arduino'daki Serial.begin ile aynı / same as Serial.begin on the Arduino
9
10with serial.Serial(PORT, BAUD, timeout=1) as ser, \
11        open("olcumler.csv", "w", newline="", encoding="utf-8") as dosya:
12    time.sleep(2)               # Uno port açılınca resetlenir / the Uno resets when the port opens
13    ser.reset_input_buffer()    # yarım kalmış eski veriyi at / drop stale, partial data
14    yazici = csv.writer(dosya)
15    yazici.writerow(["tarih", "zaman_ms", "deger", "volt"])   # başlık satırı / header row
16    print("Kayit basladi, durdurmak icin Ctrl+C / logging, press Ctrl+C to stop")
17    try:
18        while True:
19            satir = ser.readline().decode("utf-8", errors="ignore").strip()
20            parcalar = satir.split(",")
21            if len(parcalar) != 2:
22                continue                    # boş ya da beklenmeyen satır / empty or unexpected line
23            try:
24                zaman_ms, deger = int(parcalar[0]), int(parcalar[1])
25            except ValueError:
26                continue                    # bozuk satır / corrupted line
27            volt = deger * 5.0 / 1023       # Uno ADC: 0-1023 -> 0-5 V
28            yazici.writerow([datetime.now().isoformat(timespec="seconds"),
29                             zaman_ms, deger, round(volt, 3)])
30            dosya.flush()                   # satırı hemen diske yaz / write the row to disk now
31            print(zaman_ms, deger, f"{volt:.2f} V")
32    except KeyboardInterrupt:
33        print("Durduruldu / stopped")

Ce script ouvre le port avec timeout=1 : readline() attend au plus 1 seconde l'arrivée d'une fin de ligne, renvoie un objet bytes vide si rien n'arrive, et la boucle ne reste jamais bloquée. Les données reçues sont de type bytes ; decode() les transforme en texte, strip() enlève le \r\n final et split(",") découpe la ligne en colonnes. Le time.sleep(2) et le reset_input_buffer() placés après l'ouverture du port servent à jeter les lignes incomplètes qui arrivent pendant le redémarrage de l'Uno ; tu verras pourquoi plus bas. Ouvrir le fichier avec newline="" évite les lignes vides sous Windows, et flush() garde sur le disque les mesures déjà enregistrées même si le programme s'arrête brutalement. Appuie sur Ctrl+C dans le terminal pour arrêter l'enregistrement.

Si Excel ouvre le fichier CSV dans une seule colonne, c'est à cause des paramètres régionaux : avec les paramètres français, Excel attend le point-virgule comme séparateur de liste et la virgule comme séparateur décimal. Utilise csv.writer(dosya, delimiter=";") et écris la tension avec str(round(volt, 3)).replace(".", ","), ou importe le fichier dans Excel avec Données > À partir d'un fichier texte/CSV et choisis toi-même le séparateur. Pour d'autres façons de transférer des données vers Excel, consulte envoyer les données d'Arduino vers Excel.

Envoyer des commandes depuis Python vers l'Arduino

La communication fonctionne dans les deux sens. Le programme Arduino ci-dessous attend des commandes qui arrivent ligne par ligne : LED ON et LED OFF allument et éteignent la LED intégrée, PWM 128 règle la luminosité d'une LED branchée sur la broche 9 et READ renvoie la valeur lue sur A0. Chaque commande reçoit une courte réponse, ce qui permet à Python de savoir qu'elle a bien été exécutée. Tu peux trouver la bonne résistance pour la LED de la broche 9 avec le calculateur de résistance pour LED ; Qu'est-ce que le PWM ? explique le fonctionnement du PWM.

C++ (Arduino IDE)
1// Python'dan gelen satır komutlarını işler / Handles line-based commands from Python
2const int LED_PIN = LED_BUILTIN;      // Uno'da pin 13 / pin 13 on the Uno
3const int PWM_PIN = 9;                // 220 ohm dirençli LED / LED with a 220 ohm resistor
4
5void setup() {
6  pinMode(LED_PIN, OUTPUT);
7  pinMode(PWM_PIN, OUTPUT);
8  Serial.begin(115200);
9  Serial.setTimeout(50);              // readStringUntil en fazla 50 ms bekler / waits at most 50 ms
10  Serial.println("HAZIR");            // Python bu satırı bekler / Python waits for this line
11}
12
13void loop() {
14  if (Serial.available() > 0) {
15    String komut = Serial.readStringUntil('\n');   // satır sonuna kadar oku / read up to the newline
16    komut.trim();                                  // \r ve boşlukları sil / strip \r and spaces
17
18    if (komut == "LED ON") {
19      digitalWrite(LED_PIN, HIGH);
20      Serial.println("OK LED ON");
21    } else if (komut == "LED OFF") {
22      digitalWrite(LED_PIN, LOW);
23      Serial.println("OK LED OFF");
24    } else if (komut.startsWith("PWM ")) {
25      int deger = komut.substring(4).toInt();     // "PWM 128" -> 128
26      deger = constrain(deger, 0, 255);           // 0-255 aralığında tut / keep it within 0-255
27      analogWrite(PWM_PIN, deger);
28      Serial.print("OK PWM ");
29      Serial.println(deger);
30    } else if (komut == "READ") {
31      Serial.println(analogRead(A0));              // A0 ölçümünü gönder / send the A0 reading
32    } else if (komut.length() > 0) {
33      Serial.print("ERR ");                        // bilinmeyen komut / unknown command
34      Serial.println(komut);
35    }
36  }
37}
Python
1import time
2
3import serial
4
5
6def komut_gonder(ser, komut):
7    ser.write((komut + "\n").encode("ascii"))       # satır sonu şart / the newline is required
8    return ser.readline().decode("ascii", errors="ignore").strip()   # cevabı bekle / wait for the reply
9
10
11with serial.Serial("COM3", 115200, timeout=2) as ser:
12    bitis = time.time() + 3                         # en fazla 3 sn bekle / wait at most 3 s
13    while time.time() < bitis:
14        if ser.readline().strip() == b"HAZIR":      # Arduino açıldı / the Arduino is up
15            break
16    print(komut_gonder(ser, "LED ON"))              # OK LED ON
17    time.sleep(1)
18    print(komut_gonder(ser, "PWM 128"))             # OK PWM 128
19    print("A0 =", komut_gonder(ser, "READ"))        # A0 = 512
20    print(komut_gonder(ser, "LED OFF"))             # OK LED OFF

L'erreur la plus fréquente côté Python est d'oublier le \n à la fin de la commande : quand readStringUntil('\n') ne trouve pas la fin de ligne, l'Arduino attend la durée fixée par Serial.setTimeout() et ne traite la commande qu'ensuite. Dans ce programme, cette durée est de 50 ms ; avec la valeur par défaut de 1000 ms, chaque commande arriverait avec une seconde de retard. write() n'accepte que des octets : il faut convertir le texte avec encode() ou en le préfixant d'un b (b"LED ON\n"). Au départ, le script attend jusqu'à 3 secondes la ligne HAZIR (prêt) : comme l'Uno redémarre à l'ouverture du port, une commande envoyée immédiatement part vers le bootloader et se perd. Lire la réponse après chaque commande, c'est-à-dire travailler en requête et réponse, garde les deux côtés synchronisés et évite que les tampons se remplissent ; le tampon de réception de l'Uno ne fait que 64 octets.

Format des données et fin de ligne : que choisir ?

La liaison série ne te donne qu'un flux d'octets ; c'est à toi d'indiquer où une mesure se termine et où commence la suivante. Le moyen le plus fiable est d'écrire chaque mesure sur sa propre ligne et de terminer chaque ligne par \n. Le format à utiliser à l'intérieur de la ligne dépend de la taille de ton projet :

FormatExemple de ligneAvantagesInconvénients
Valeur unique512Le format le plus simple ; le Traceur série d'Arduino le dessine directementAvec plusieurs valeurs, on ne sait plus laquelle est laquelle
CSV2573,512,23.5Court ; Excel et pandas le lisent directementL'ordre des colonnes doit rester identique des deux côtés
Clé:valeurtemp:23.5,hum:41Lisible ; le Traceur série affiche les étiquettesPlus long et un peu plus laborieux à analyser
JSON{"t":23.5,"h":41}Décodé en une ligne avec json.loads() en PythonLe format le plus long ; met à rude épreuve la RAM limitée de l'Uno
Binaire2 octets : 0x02 0x00L'option la plus rapide et la plus compacteIllisible pour un humain ; nécessite le module struct et un octet de début

Les fins de ligne sèment aussi souvent la confusion. Serial.println() ajoute deux caractères à la fin de chaque ligne : le retour chariot (\r, CR) et le saut de ligne (\n, LF). La méthode readline() de Python n'attend que \n, il reste donc un \r à la fin de la ligne ; strip() enlève les deux. La liste des fins de ligne du Moniteur série de l'IDE Arduino propose Pas de fin de ligne, Nouvelle ligne, Retour chariot et Les deux, NL et CR (en anglais No Line Ending, New Line, Carriage Return et Both NL & CR) ; quand tu testes le programme dans le Moniteur série avant Python, choisis Nouvelle ligne pour envoyer le même caractère que depuis Python. Pour les nombres décimaux, précise le nombre de chiffres, comme dans Serial.print(temperature, 1) ; sur l'Uno, sprintf ne prend pas en charge le format à virgule flottante (%f), donc écrire des décimales avec sprintf affiche un point d'interrogation au lieu du nombre.

Débit (bauds)Octets par seconde (8N1)Lignes de 12 octets par secondeQuand l'utiliser ?
960096080Capteurs lents, câbles longs, anciens exemples
576005 760480Mesures à vitesse moyenne
11520011 520960Graphiques en direct, ESP32 et la plupart des nouveaux projets

Sur la liaison série, chaque octet circule sous forme de 10 bits en comptant les bits de start et de stop : 115200 bauds correspondent donc à environ 11 520 octets par seconde. Pour une ligne de 12 octets composée de 25730,1023 et de la fin de ligne (\r\n), cela laisse de la place pour 960 lignes par seconde. Si le débit diffère entre les deux côtés, des caractères illisibles arrivent et Python lève une UnicodeDecodeError. Entre 5 et 20 mesures par seconde suffisent largement pour l'œil humain et pour la plupart des capteurs ; envoyer plus souvent que nécessaire ralentit le graphique et alourdit le fichier.

Tracer un graphique en direct avec matplotlib

Python
1from collections import deque
2
3import matplotlib.pyplot as plt
4import serial
5from matplotlib.animation import FuncAnimation
6
7ser = serial.Serial("COM3", 115200, timeout=0.1)
8N = 200                                          # ekrandaki ölçüm sayısı / readings on screen
9veri = deque([0] * N, maxlen=N)                  # eskiler kendiliğinden düşer / old values drop out
10
11fig, ax = plt.subplots()
12cizgi, = ax.plot(range(N), list(veri))
13ax.set_ylim(0, 1023)                             # Uno ADC aralığı / Uno ADC range
14ax.set_xlabel("Ornek / sample")
15ax.set_ylabel("A0")
16
17
18def guncelle(_):
19    while ser.in_waiting:                        # biriken bütün satırları oku / read every queued line
20        parca = ser.readline().decode("utf-8", errors="ignore").strip().split(",")
21        if len(parca) == 2 and parca[1].isdigit():
22            veri.append(int(parca[1]))
23    cizgi.set_ydata(list(veri))
24    return (cizgi,)
25
26
27ani = FuncAnimation(fig, guncelle, interval=50, blit=True, cache_frame_data=False)   # ani silinmemeli / keep ani
28plt.show()
29ser.close()

Le graphique repose sur une deque qui conserve les 200 dernières mesures : chaque nouvelle valeur entre par la droite et la plus ancienne sort automatiquement par la gauche. FuncAnimation appelle la fonction guncelle (mettre à jour) toutes les 50 ms. La boucle while ser.in_waiting dans la fonction est importante : l'Arduino envoie pour l'instant 5 lignes par seconde, mais si tu augmentes la cadence et ne lis qu'une ligne par image, le graphique prend du retard sur le temps réel ; cette boucle lit toutes les lignes en attente et garde le graphique à jour. Ne supprime pas la variable ani : si plus aucune variable ne pointe vers l'objet d'animation, Python le détruit et la fenêtre se fige. Pour l'ESP32, écris set_ylim(0, 4095), et pour le Pico set_ylim(0, 65535). Pour un simple coup d'œil, le Traceur série de l'IDE Arduino fait l'affaire ; l'avantage de Python est de pouvoir enregistrer le graphique, combiner plusieurs sources et traiter les données en même temps.

Erreur de port occupé et problème du reset automatique

Un port série ne peut être ouvert que par un seul programme à la fois. Si, pendant que ton script Python tourne, le Moniteur série ou le Traceur série de l'IDE Arduino est ouvert, si un autre script Python occupe le port ou si un logiciel qui scrute les ports, comme ceux des imprimantes 3D, fonctionne en arrière-plan, tu obtiens could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5) sous Windows et Resource busy ou Device or resource busy sous macOS et Linux. L'inverse est vrai aussi : tant qu'un script Python garde le port ouvert, l'IDE Arduino ne peut pas téléverser de code. Utilise donc la structure with serial.Serial(...) as ser: dans tes scripts ; le port se ferme tout seul à la fin du bloc ou en cas d'erreur. Prends l'habitude d'arrêter le script avec Ctrl+C avant de téléverser.

Le second problème est plus sournois : sur l'Arduino Uno, le Nano et le Mega, la ligne DTR de la puce USB est reliée à la broche RESET du processeur par un condensateur de 100 nF. Ce circuit existe pour que l'IDE puisse redémarrer automatiquement la carte lors du téléversement, mais chaque fois que pyserial ouvre le port, DTR change aussi et la carte redémarre. Elle attend d'abord une ou deux secondes dans le bootloader, puis exécute ton programme depuis le début. Les commandes envoyées pendant ce temps sont perdues, et tu risques de manquer les premières lignes que l'Arduino affiche dans setup(). La solution consiste à attendre avec time.sleep(2) après l'ouverture du port ou, plus sûr, à attendre que l'Arduino envoie une ligne comme HAZIR ; l'exemple de commandes ci-dessus fait exactement cela. Ouvre le port une seule fois au début du programme et garde-le ouvert jusqu'à la fin : une boucle qui ouvre et ferme le port à chaque mesure réinitialise la carte sans arrêt.

Si tu veux empêcher complètement le reset, il y a deux méthodes. Par logiciel : crée le port fermé avec ser = serial.Serial(), renseigne ser.port et ser.baudrate, écris ser.dtr = False puis appelle ser.open(). Cela fonctionne en général sous Windows ; sous Linux et macOS, le système d'exploitation peut quand même basculer DTR à l'ouverture du port. Par matériel : place un condensateur de 10 µF entre les broches RESET et GND de l'Uno, la patte positive côté RESET. Les cartes à USB intégré comme le Leonardo, le Micro et le Raspberry Pi Pico ne redémarrent pas à l'ouverture du port ; sur ces cartes, les lignes que ton code affiche avant que Python se connecte peuvent être perdues, il vaut donc mieux envoyer les messages importants à la réception d'une commande.

Avec le condensateur de 10 µF en place, l'IDE Arduino ne peut pas téléverser de code, car le téléversement repose sur le même circuit de reset automatique ; retire le condensateur avant de téléverser. Si Python affiche device reports readiness to read but returned no data, la carte a perdu la connexion ou deux programmes utilisent le port en même temps. Si la carte redémarre toute seule en fonctionnement, la cause est généralement une alimentation insuffisante ; alimente les servos et les moteurs avec une source séparée plutôt qu'avec le 5V de l'USB.

Communication série entre l'ESP32 et Python

Tout le code Python de cet article fonctionne aussi avec l'ESP32 ; il suffit de modifier quelques points côté Arduino. Pour l'entrée analogique, utilise une des broches ADC1 (par exemple GPIO 34). L'ADC de l'ESP32 est sur 12 bits et convertit 0–3,3 V en 0–4095 : calcule donc la tension dans Python avec deger * 3.3 / 4095. Au démarrage, l'ESP32 affiche ses propres messages de boot à 115200 bauds, comme rst:0x1 (POWERON_RESET),boot:0x13 ; les scripts ci-dessus ignorent les lignes qui ne peuvent pas être converties en nombres, donc ces messages ne posent pas de problème. Selon le circuit de reset automatique de la carte et le système d'exploitation, l'ESP32 peut lui aussi redémarrer à l'ouverture du port : l'attente et la ligne HAZIR sont donc utiles ici aussi. La puce USB est le plus souvent un CP2102 ou un CH340. Si tu préfères te passer de câble, l'ESP32 peut envoyer les mêmes données en Wi-Fi ; consulte pour cela les guides ESP32 WebSocket et ESP32 Google Sheets. Si c'est ton premier ESP32, lis d'abord Qu'est-ce que l'ESP32 ?.

Raspberry Pi Pico et MicroPython : quelle différence ?

Carte de développement Raspberry Pi Pico
Raspberry Pi Pico : grâce à l'USB intégré du RP2040, il n'a pas besoin de puce USB-série séparée ; avec MicroPython installé, il apparaît directement comme un port série sur l'ordinateur.

Une autre façon de réunir Python et le matériel est d'exécuter Python directement sur la carte. Le Raspberry Pi Pico se programme en MicroPython : lire des capteurs, piloter des LED et gérer le temps se fait dans le Pico avec la syntaxe de Python. Mais MicroPython est une version allégée du Python de l'ordinateur ; matplotlib, pandas ou OpenCV ne fonctionnent pas sur le Pico. Si tu veux enregistrer des données, tracer des graphiques ou les traiter avec de l'intelligence artificielle, tu relies donc aussi le Pico au Python de ton ordinateur. Le code pyserial de cet article fonctionne aussi avec le Pico ; il suffit d'adapter le nom du port, la plage de valeurs et la lecture des réponses aux commandes, comme expliqué plus bas.

CaractéristiqueArduino + Python sur le PCPico + MicroPython (autonome)Pico + Python sur le PC
Où Python s'exécute-t-il ?Seulement sur l'ordinateur ; la carte exécute du C++Dans le PicoDes deux côtés : MicroPython sur le Pico, Python sur l'ordinateur
Faut-il un ordinateur ?Oui, les données sont traitées sur l'ordinateurNon, fonctionne sur l'alimentation USB ou sur batterieOui
Bibliothèques disponiblespyserial, matplotlib, pandas, numpy, OpenCVModules limités comme machine et timeToutes les bibliothèques de l'ordinateur
Débit en baudsImportant, doit être identique des deux côtésPas de port série utiliséSans importance, les données circulent à la vitesse de l'USB
Reset à l'ouverture du portOui sur l'Uno, le Nano et le MegaSans objetNon
Quand la choisir ?Tu as déjà un Arduino et tu as besoin de bibliothèques toutes prêtesAppareils simples et autonomesTu veux un seul langage (Python) des deux côtés

Le code MicroPython ci-dessous est la version Pico de l'exemple Arduino précédent : il lit le potentiomètre sur GP26 toutes les 200 ms, envoie le résultat au même format temps,valeur et accepte les mêmes commandes LED. Ici, l'équivalent du Serial de l'Arduino est print() et sys.stdin ; grâce à select.poll(), la boucle de mesure continue de tourner pendant l'attente des commandes.

MicroPython
1# Pico'ya main.py adıyla kaydet / save it on the Pico as main.py
2import sys
3import select
4import time
5from machine import ADC, Pin
6
7led = Pin("LED", Pin.OUT)          # Pico ve Pico W dahili LED / onboard LED on Pico and Pico W
8pot = ADC(26)                      # GP26 = ADC0, potansiyometre orta ucu / potentiometer wiper
9okuyucu = select.poll()
10okuyucu.register(sys.stdin, select.POLLIN)   # USB'den veri gelince haber ver / notify when USB data arrives
11
12son = time.ticks_ms()
13while True:
14    if okuyucu.poll(0):                       # bekleyen komut var mı? / is a command waiting?
15        komut = sys.stdin.readline().strip()
16        if komut == "LED ON":
17            led.on()
18            print("OK LED ON")
19        elif komut == "LED OFF":
20            led.off()
21            print("OK LED OFF")
22    if time.ticks_diff(time.ticks_ms(), son) >= 200:   # 200 ms'de bir ölçüm / one reading every 200 ms
23        son = time.ticks_ms()
24        print("{},{}".format(son, pot.read_u16()))     # zaman,değer (0-65535) / time,value (0-65535)

Enregistre le code sur le Pico sous le nom main.py avec Thonny, puis ferme Thonny et débranche puis rebranche le câble USB du Pico : Thonny utilise lui-même le port série du Pico, donc ton script Python ne peut pas l'ouvrir tant qu'il est connecté, et il arrête aussi le programme en cours quand il se connecte ; après le redémarrage, main.py se lance tout seul. Sur le port USB du Pico, le débit en bauds n'a pas d'importance ; quelle que soit la valeur indiquée dans Python, les données circulent à la vitesse de l'USB. read_u16() renvoie des valeurs de 0 à 65535 : calcule donc la tension avec deger * 3.3 / 65535. Comme le Pico envoie des lignes de mesure sans arrêt, readline() peut renvoyer une mesure au lieu de la réponse dans le script de commandes ci-dessus ; avec le Pico, continue à lire jusqu'à obtenir une ligne qui commence par OK. Cet exemple n'a ni ligne HAZIR ni commandes PWM et READ ; ajoute-les au code du Pico si tu en as besoin. Attention si tu envoies des données binaires depuis l'ordinateur : l'octet 0x03 correspond à Ctrl+C dans MicroPython et arrête ton programme ; tu peux désactiver ce comportement avec micropython.kbd_intr(-1). Si tu débutes avec le Pico, Qu'est-ce que le Raspberry Pi Pico ? et les bases de MicroPython sont un bon point de départ.

Idées de projets avec Arduino et Python

  • Enregistreur de température et d'humidité : écris les mesures d'un DHT11 dans un CSV une fois par minute et trace le climat de ta pièce sur une semaine dans Excel ou avec pandas.
  • Commande d'une lampe depuis l'ordinateur : pilote un module relais avec des commandes ON et OFF envoyées depuis Python, et associe-le à une minuterie ou à des commandes vocales.
  • Graphique de distance en direct : trace avec matplotlib la distance mesurée par un HC-SR04. Si tu fais tourner le capteur avec un servo, la même méthode te permet de dessiner aussi un écran de radar.
  • Écran d'état du PC : Python lit l'utilisation du processeur et de la mémoire avec la bibliothèque psutil et l'envoie à l'Arduino, qui l'affiche sur un écran OLED.
  • Caméra qui suit les visages : OpenCV trouve la position d'un visage dans l'image, Python la convertit en angle et l'envoie, et l'Arduino oriente la caméra avec un servomoteur.
  • Ton propre clavier de raccourcis : fais envoyer une ligne aux boutons de l'Arduino quand on appuie dessus, et laisse Python la lire pour ouvrir un programme ou lancer un raccourci sur l'ordinateur.

Erreurs fréquentes et solutions

Erreur ou symptômeCauseSolution
ModuleNotFoundError: No module named 'serial'pyserial n'est pas installé pour l'interpréteur Python qui exécute le script ; VS Code ou Thonny utilisent peut-être un autre PythonLance python -m pip install pyserial avec le même interpréteur et choisis le bon interpréteur dans l'éditeur
AttributeError: module 'serial' has no attribute 'Serial'Le mauvais paquet serial est installé ou ton fichier s'appelle serial.pypip uninstall serial puis pip install pyserial ; renomme ton fichier
could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5)Le port est ouvert dans un autre programme : Moniteur série, Traceur série ou un deuxième scriptFerme l'autre programme ; utilise with dans le script
could not open port 'COM5': FileNotFoundError(2, 'The system cannot find the file specified.', None, 2)Nom de port erroné ou carte non branchéeTrouve le bon nom avec python -m serial.tools.list_ports -v
[Errno 13] Permission denied: '/dev/ttyACM0'Ton utilisateur Linux n'a pas le droit d'accéder au port sériesudo usermod -a -G dialout $USER, puis déconnecte-toi et reconnecte-toi
Caractères illisibles ou UnicodeDecodeErrorDébits différents ou parasites au démarrageUtilise le même débit des deux côtés ; ajoute decode(errors="ignore") et reset_input_buffer()
readline() renvoie toujours b''L'Arduino n'envoie pas de fin de ligne (print au lieu de println), mauvais port ou carte encore en démarrageUtilise println, vérifie le port et le débit, augmente le timeout
ValueError: invalid literal for int() with base 10Une ligne incomplète, vide ou inattendue a été convertie en nombrePlace la conversion dans un try/except et vérifie le nombre de colonnes
Les premières commandes sont ignoréesL'Uno a redémarré à l'ouverture du port et la commande est partie vers le bootloaderAttends la ligne HAZIR ou patiente 2 secondes après l'ouverture du port
Le graphique est en retard sur le temps réelUne seule ligne est lue par image et les données s'accumulent dans le tamponLis toutes les lignes avec la boucle in_waiting et envoie moins souvent
Impossible de téléverser pendant que le script tournePython occupe le portArrête le script avec Ctrl+C, puis téléverse
Quand quelque chose ne marche pas, sors d'abord Python de l'équation et teste le programme Arduino dans le Moniteur série au même débit. Si les lignes s'affichent correctement, le problème vient du côté Python (nom du port, débit, fin de ligne ou analyse) ; si elles sont aussi illisibles, corrige d'abord le programme Arduino. Si le code ne se téléverse pas du tout, suis les étapes du guide erreur de téléversement Arduino. Si le côté matériel de la communication série t'intéresse, c'est-à-dire TX, RX et l'UART, jette un œil à la communication série UART.

Faire dialoguer Arduino et Python par le port série est le moyen le plus rapide de transformer un simple projet à capteur en un système qui enregistre des données, trace des graphiques et se pilote depuis l'ordinateur. Commence par lire une seule valeur et l'afficher, enregistre-la ensuite en CSV, puis ajoute en dernier les commandes et les graphiques ; à chaque étape, vérifie que le débit et la fin de ligne sont identiques des deux côtés. Dans l'application BlueGrays, tu trouveras des projets pas à pas avec schémas et code pour mesurer la lumière avec une LDR, la température et l'humidité avec un DHT11, la distance avec un HC-SR04, ainsi que pour la communication série entre deux Arduino : de quoi monter rapidement la source des données que tu liras avec Python.

Questions fréquentes

Peut-on programmer un Arduino en Python ?

Python ne s'exécute pas sur un Arduino Uno, Nano ou Mega ; ces cartes se programment dans l'IDE Arduino avec le langage Arduino, basé sur le C++. Python tourne sur l'ordinateur et dialogue avec la carte par le port série grâce à pyserial ; tu peux aussi téléverser l'exemple StandardFirmata et piloter les broches directement depuis Python avec une bibliothèque comme pyFirmata2. Si tu veux que Python tourne sur la carte elle-même, choisis un Raspberry Pi Pico ou un ESP32 compatibles MicroPython.

Comment installer pyserial ?

Lance python -m pip install pyserial dans un terminal et écris import serial dans ton code. Le paquet s'appelle pyserial, mais le module importé s'appelle serial ; pip install serial installe un paquet complètement différent et provoque une AttributeError. Sous Linux, il faut aussi ajouter ton utilisateur au groupe dialout.

Comment savoir sur quel port COM est mon Arduino ?

Regarde dans Outils > Port de l'IDE Arduino ou lance python -m serial.tools.list_ports -v dans un terminal. Sous Windows, la rubrique Ports (COM et LPT) du Gestionnaire de périphériques affiche aussi le port. En cas de doute, débranche la carte et liste de nouveau les ports ; celui qui disparaît est celui de l'Arduino.

Comment envoyer des données de Python vers l'Arduino ?

Ouvre le port avec serial.Serial et envoie des octets, par exemple ser.write(b"LED ON\n") ; n'oublie pas de convertir le texte avec encode() et d'ajouter une fin de ligne. Côté Arduino, vérifie l'arrivée de données avec Serial.available() et lis la ligne avec Serial.readStringUntil('\n'). Comme l'Uno redémarre à l'ouverture du port, attends environ 2 secondes avant d'envoyer la première commande.

Peut-on utiliser le Moniteur série et Python en même temps ?

Non, un port série ne peut être ouvert que par un seul programme à la fois ; le second reçoit l'erreur Access is denied ou Resource busy. Tu peux en revanche traiter les données dans Python et les afficher à l'écran avec print en même temps. Si tu as besoin de deux canaux séparés, branche un deuxième convertisseur USB-TTL sur l'Arduino et crée un second port série avec SoftwareSerial.

Pourquoi mon Arduino redémarre-t-il quand Python ouvre le port ?

Sur l'Uno, le Nano et le Mega, la ligne DTR de la puce USB est reliée à la broche RESET par un condensateur, donc la carte redémarre à chaque ouverture du port ; l'IDE a besoin de ce circuit pour téléverser. Attends 2 secondes après l'ouverture du port ou attends une ligne de l'Arduino indiquant qu'il est prêt. Pour empêcher totalement le reset, tu peux placer un condensateur de 10 µF entre RESET et GND, mais il faudra le retirer avant chaque téléversement.

Voir tous les projets
Mesurer l'intensité lumineuse avec une LDR
#11Débutant

Mesurer l'intensité lumineuse avec une LDR

Envie de voir en chiffres à quel point ta chambre est lumineuse ? Le LDR, une petite pièce sensible à la lumière, mesure la lumière ambiante, et l'Arduino écrit la valeur sur l'écran de ton ordinateur

4.6(81)
139
HC-SR04 Mesure de la distance (affichage en série)
#27Débutant

HC-SR04 Mesure de la distance (affichage en série)

Le capteur HC-SR04 fonctionne comme une chauve-souris qui trouve son chemin dans le noir : il envoie un son que tu ne peux pas entendre, écoute l'écho et mesure à quelle distance se trouve l'objet dev

4.6(46)
71
Température et humidité avec DHT11
#55Intermédiaire

Température et humidité avec DHT11

Quelle température fait-il dans ta chambre, et l'air est-il humide ou sec ? Le capteur DHT11 mesure la température et l'humidité, et l'Arduino affiche les valeurs sur un écran LCD à deux lignes, mises

4.7(47)
73
Mesure de l'intensité lumineuse avec le module LDR ESP32
#113Débutant

Mesure de l'intensité lumineuse avec le module LDR ESP32

Suivi en temps réel sur le moniteur série de la valeur brute de l'ADC du module LDR et du pourcentage relatif de luminosité calculé à partir de références dans l'obscurité et à la lumière. Ce pourcentage n'est pas une mesure en lux. Permet de comprendre le principe de calibrage du module. Il est préférable de réaliser cet exercice avant le projet 10, car il sert à déterminer la valeur seuil.

4.6(36)
55
Communication unidirectionnelle entre 2 Arduino
#57Intermédiaire
Premium

Communication unidirectionnelle entre 2 Arduino

Tu fais discuter deux Arduino par un seul fil : appuie sur le bouton de l'un et la LED de l'autre s'allume, appuie encore et elle s'éteint. La carte qui envoie signale l'appui par un court message, et

4.4(37)
50
Communication bidirectionnelle entre 2 Arduino
#58Intermédiaire
Premium

Communication bidirectionnelle entre 2 Arduino

Deux Arduino reliés par des fils se parlent : appuie sur le bouton de l'un et la lumière de l'autre s'allume, appuie encore et elle s'éteint. Les deux cartes ont le même programme, donc chacune envoie

4.5(20)
26
Contrôler une LED depuis un téléphone via Bluetooth
#34Débutant
Premium

Contrôler une LED depuis un téléphone via Bluetooth

Une petite répétition pour allumer et éteindre les lumières de ta chambre depuis ton téléphone. Le module Bluetooth transmet à l'Arduino les lettres envoyées par ton téléphone ; une lettre allume la L

4.8(68)
89
Enregistreur de Température ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
#144Avancé
Premium

Enregistreur de Température ESP32 (DHT22 + DS1307 RTC + microSD + OLED)

Construis un enregistreur de température horodaté avec ESP32 ! Lis la température/humidité du DHT22 chaque minute, obtiens l'heure réelle du DS1307 RTC et enregistre sur carte microSD au format CSV. L'OLED affiche en direct la température (grande police), le pourcentage d'humidité, l'horodatage et le compteur. Le fichier /temp_log.csv sur la carte SD s'ouvre dans Excel, Google Sheets ou Python pandas pour analyse — parfait pour surveillance de serre, suivi de température à domicile, c

4.7(7)
12