Arduino и Python по последовательному порту: pyserial, CSV и графики в реальном времени
Связь Arduino и Python по последовательному порту с pyserial: установка, поиск порта, запись данных датчика в CSV, отправка команд и графики в реальном времени с matplotlib.
Связь Arduino и Python по последовательному порту означает, что программа на Python, запущенная на компьютере, обменивается данными с Arduino через USB-кабель. Arduino измеряет показания датчика и пишет результат в последовательный порт; Python читает эти строки с помощью библиотеки pyserial, сохраняет их в CSV-файл, строит график или принимает решение и отправляет команду обратно в Arduino. Так точное управление железом на Arduino соединяется с мощью Python в обработке данных, графиках, ИИ и автоматизации. Дополнительный модуль тоже не нужен: хватает того же USB-кабеля, через который загружаются скетчи.
В этом руководстве пошагово разберём, как установить pyserial, автоматически найти порт Arduino, записывать данные датчика с метками времени в CSV-файл, отправлять команды для светодиода и ШИМ из Python в Arduino и рисовать график в реальном времени с matplotlib. Затем поговорим о выборе формата данных и конца строки, о двух самых запутанных проблемах (ошибка занятого порта и перезагрузка платы при открытии порта) и о том, чем отличается та же задача на Raspberry Pi Pico с MicroPython. Примеры написаны для Arduino Uno; изменения для ESP32 и Pico описаны отдельно.

Как Arduino общается с Python?
USB-UART-преобразователь на Arduino Uno (на оригинальных платах это ATmega16U2, на клонах обычно CH340) виден компьютеру как виртуальный последовательный порт: в Windows это COM3, COM4 и так далее; в macOS — /dev/cu.usbmodem… или /dev/cu.usbserial-…; в Linux — /dev/ttyACM0 или /dev/ttyUSB0. Каждый символ, который Arduino пишет через Serial.print(), уходит через этот порт в компьютер в виде байтов; на стороне Python pyserial открывает порт как файл, читает из него и пишет в него. Важное различие: Arduino Uno не выполняет код на Python. На плате работает код Arduino на C++, на компьютере работает Python, и общаются они только по последовательной линии. Чтобы обе стороны говорили на одном языке, эти настройки должны совпадать:
| Настройка | Сторона Arduino | Сторона Python | Внимание |
|---|---|---|---|
| Порт | Создаётся автоматически при подключении по USB | serial.Serial("COM3", ...) | Имя порта зависит от операционной системы |
| Скорость (бод) | Serial.begin(115200) | serial.Serial(port, 115200) | Если скорости разные, приходят мусорные символы |
| Формат кадра | 8 бит данных, без чётности, 1 стоповый бит (8N1) | У pyserial по умолчанию тоже 8N1 | Менять обычно не нужно |
| Конец строки | Serial.println() добавляет \r\n | readline() читает, пока не встретит \n | Не забудь добавить \n при отправке из Python |
| Таймаут | Serial.setTimeout(), по умолчанию 1000 мс | timeout=1 (секунды) | При timeout=None readline() ждёт бесконечно |
Установка pyserial (Windows, macOS, Linux)
- Убедись, что установлен Python 3. В Windows на первом экране установщика с официального сайта Python отметь галочку Add python.exe to PATH; в macOS и Linux Python 3 обычно уже есть в системе.
- Открой терминал (в Windows — командную строку или PowerShell) и введи python -m pip install pyserial. Если Windows не узнаёт команду python, попробуй py -m pip install pyserial; в macOS и Linux команда может выглядеть как python3 -m pip install pyserial.
- Проверь установку: команда python -c "import serial; print(serial.__version__)" должна вывести номер версии, например 3.5.
- Для раздела с графиком в реальном времени нужна ещё matplotlib: python -m pip install matplotlib.
- В Linux добавь своего пользователя в группу dialout, чтобы можно было открыть порт: sudo usermod -a -G dialout $USER, затем выйди из системы и войди снова. Если в Raspberry Pi OS или свежих версиях Ubuntu pip выдаёт ошибку externally-managed-environment, используй sudo apt install python3-serial или создай виртуальное окружение командой python3 -m venv и запускай pip внутри него.
На каком порту Arduino? Ищем порт с помощью Python
Проще всего посмотреть в меню Инструменты › Порт (Tools › Port) в Arduino IDE, но Python и сам умеет выводить список портов. Введи в терминале python -m serial.tools.list_ports -v, и для каждого порта появятся имя, описание и аппаратный идентификатор (VID:PID). Если непонятно, какая строка относится к Arduino, отключи плату, запусти команду ещё раз и запомни порт, который исчез. В macOS одна и та же плата видна и как /dev/tty…, и как /dev/cu…; в Python используй тот, что начинается с cu.

| USB-микросхема | Где встречается | VID:PID | Имя в Windows | Имя порта в Linux |
|---|---|---|---|---|
| ATmega16U2 | Оригинальные Uno R3, Mega 2560 | 2341:0043 (Uno) | Arduino Uno (COMx) | /dev/ttyACM0 |
| CH340 | Клоны Uno, Nano и Mega | 1A86:7523 | USB-SERIAL CH340 (COMx) | /dev/ttyUSB0 |
| CP2102 | ESP32 DevKit, NodeMCU | 10C4:EA60 | Silicon Labs CP210x USB to UART Bridge (COMx) | /dev/ttyUSB0 |
| FT232R | Старые Nano, FTDI-кабели | 0403:6001 | USB Serial Port (COMx) | /dev/ttyUSB0 |
| RP2040 (встроенный USB) | Raspberry Pi Pico с MicroPython | 2E8A:0005 | Последовательное устройство USB (COMx) | /dev/ttyACM0 |
Функция ниже выводит список всех портов и, если узнаёт плату по VID, возвращает имя её порта. Сохрани код как port_bul.py, и в других скриптах можно будет написать from port_bul import port_bul и PORT = port_bul() вместо того, чтобы каждый раз менять имя порта вручную.
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")Отправляем данные из Arduino: первый скетч
В примерах используется потенциометр, подключённый к пину A0: средний вывод идёт на A0, один крайний на 5V, другой на GND. Вместо потенциометра можно подключить LDR с резистором 10 kΩ по схеме делителя напряжения (её удобно рассчитать в утилите делителя напряжения). Скетч делает измерение каждые 200 мс и отправляет результат одной строкой в формате время,значение:
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}Если использовать millis() вместо delay(), потом будет проще добавить в тот же цикл чтение команд из Python; подробности в статье millis и delay. После загрузки открой монитор порта на скорости 115200 бод, и побегут строки вроде 2573,512. Это только проверка: перед запуском скрипта на Python обязательно закрой монитор порта, иначе порт будет занят. Если монитор порта пока в новинку, поможет руководство по монитору порта Arduino.
Читаем данные Arduino в Python и сохраняем в CSV
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")Этот скрипт открывает порт с timeout=1: readline() ждёт конца строки не больше 1 секунды, если ничего не пришло, возвращает пустой объект bytes, и цикл никогда не зависает. Входящие данные имеют тип bytes; decode() превращает их в текст, strip() убирает \r\n в конце, а split(",") делит строку на столбцы. time.sleep(2) и reset_input_buffer() после открытия порта выбрасывают обрывки строк, которые приходят, пока Uno перезагружается; почему так происходит, разберём ниже. Открытие файла с newline="" не даёт появляться пустым строкам в Windows, а flush() сохраняет уже записанные измерения на диске, даже если программа внезапно закроется. Чтобы остановить запись, нажми Ctrl+C в терминале.
Отправляем команды из Python в Arduino
Связь работает в обе стороны. Скетч Arduino ниже ждёт команды, приходящие построчно: LED ON и LED OFF включают и выключают встроенный светодиод, PWM 128 задаёт яркость светодиода на пине 9, а READ отправляет обратно значение с A0. На каждую команду приходит короткий ответ, так что Python знает, что команда действительно выполнена. Подходящий резистор для светодиода на пине 9 можно подобрать в калькуляторе резистора для светодиода; как работает ШИМ, объясняет статья Что такое ШИМ?
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}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Самая частая ошибка на стороне Python — забыть \n в конце команды: если readStringUntil('\n') не находит конец строки, Arduino ждёт время, заданное через Serial.setTimeout(), и только потом обрабатывает команду. В этом скетче это время 50 мс; со значением по умолчанию 1000 мс каждая команда опаздывала бы на секунду. write() принимает только байты, поэтому текст нужно преобразовать через encode() или поставить перед ним b (b"LED ON\n"). Сначала скрипт до 3 секунд ждёт строку HAZIR (готово): Uno перезагружается при открытии порта, поэтому команда, отправленная сразу, попадает в загрузчик и теряется. Если после каждой команды читать ответ, то есть работать по схеме «запрос — ответ», обе стороны остаются синхронными, а буферы не переполняются; приёмный буфер Uno — всего 64 байта.
Формат данных и конец строки: что выбрать?
Последовательная линия даёт только поток байтов; где заканчивается одно измерение и начинается следующее, решать тебе. Самый надёжный способ — писать каждое измерение в отдельную строку и заканчивать каждую строку символом \n. Какой формат использовать внутри строки, зависит от размера проекта:
| Формат | Пример строки | Плюсы | Минусы |
|---|---|---|---|
| Одно значение | 512 | Самый простой формат; плоттер Arduino IDE (Serial Plotter) рисует его сразу | При нескольких значениях непонятно, где какое |
| CSV | 2573,512,23.5 | Коротко; Excel и pandas читают его напрямую | Порядок столбцов должен совпадать на обеих сторонах |
| Ключ:значение | temp:23.5,hum:41 | Читается легко; плоттер показывает подписи | Длиннее, разбирать чуть сложнее |
| JSON | {"t":23.5,"h":41} | В Python разбирается одной строкой через json.loads() | Самый длинный формат; нагружает скромное ОЗУ Uno |
| Двоичный | 2 байта: 0x02 0x00 | Самый быстрый и короткий вариант | Человеку не прочитать; нужны модуль struct и стартовый байт |
Концы строк — ещё один частый источник путаницы. Serial.println() добавляет в конец каждой строки два символа: возврат каретки (\r, CR) и перевод строки (\n, LF). readline() в Python ждёт только \n, поэтому в конце строки остаётся \r; strip() убирает оба. В списке концов строки монитора порта Arduino IDE есть варианты No Line Ending («Нет конца строки»), New Line («Новая строка»), Carriage Return («Возврат каретки») и Both NL & CR («NL и CR»); когда скетч Arduino проверяется в мониторе порта до перехода на Python, выбирай New Line, чтобы отправлялся тот же символ, что и из Python. Для дробных чисел указывай количество знаков, как в Serial.print(temperature, 1); на Uno sprintf не поддерживает формат с плавающей точкой (%f), поэтому попытка вывести дробное число через sprintf даёт вопросительный знак вместо числа.
| Скорость (бод) | Байт в секунду (8N1) | Строк по 12 байт в секунду | Когда использовать |
|---|---|---|---|
| 9600 | 960 | 80 | Медленные датчики, длинные кабели, старые примеры скетчей |
| 57600 | 5 760 | 480 | Измерения средней частоты |
| 115200 | 11 520 | 960 | Графики в реальном времени, ESP32 и большинство новых проектов |
По последовательной линии каждый байт передаётся как 10 бит вместе со стартовым и стоповым битами, поэтому 115200 бод — это примерно 11 520 байт в секунду. Для 12-байтной строки из 25730,1023 и конца строки (\r\n) это до 960 строк в секунду. Если скорость на двух сторонах разная, по линии идут мусорные символы, а Python выдаёт UnicodeDecodeError. От 5 до 20 измерений в секунду более чем достаточно и для человеческого глаза, и для большинства датчиков; если отправлять данные намного чаще, чем нужно, график тормозит, а файл раздувается.
Рисуем график в реальном времени с matplotlib
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()График построен на deque, которая хранит последние 200 измерений: каждое новое значение добавляется справа, а самое старое автоматически выпадает слева. FuncAnimation вызывает функцию guncelle (обновить) каждые 50 мс. Цикл while ser.in_waiting внутри функции важен: сейчас Arduino отправляет 5 строк в секунду, но если поднять частоту, чтение одной строки за кадр приведёт к тому, что график начнёт отставать от реального времени; этот цикл читает все накопившиеся строки и держит график актуальным. Не удаляй переменную ani: если на объект анимации не ссылается ни одна переменная, сборщик мусора Python его удалит, и окно застынет. Для ESP32 используй set_ylim(0, 4095), для Pico — set_ylim(0, 65535). Если нужно просто быстро посмотреть, справится и «Плоттер по последовательному соединению» (Serial Plotter) в Arduino IDE; преимущество Python в том, что график можно сохранить, объединить несколько источников и одновременно обрабатывать данные.
Ошибка занятого порта и проблема автосброса
Последовательный порт может одновременно открыть только одна программа. Если во время работы скрипта на Python открыты монитор порта или плоттер Arduino IDE, порт держит другой скрипт на Python или в фоне работает программа, которая сканирует порты (например, программы для 3D-принтеров), то в Windows появится ошибка could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5) (в русской Windows — «Отказано в доступе»), а в macOS и Linux — Resource busy или Device or resource busy. Это работает и в обратную сторону: пока скрипт на Python держит порт открытым, Arduino IDE не может загрузить код. Поэтому в скриптах используй конструкцию with serial.Serial(...) as ser:; порт закроется автоматически, когда блок закончится или произойдёт ошибка. Возьми за привычку останавливать скрипт через Ctrl+C перед загрузкой.
Вторая проблема коварнее: на Arduino Uno, Nano и Mega линия DTR USB-микросхемы подключена к пину RESET процессора через конденсатор 100 nF. Эта схема нужна, чтобы IDE могла автоматически перезагрузить плату при загрузке кода, но каждый раз, когда pyserial открывает порт, DTR тоже меняется, и плата сбрасывается. Сначала плата секунду-другую ждёт в загрузчике, а потом запускает скетч с начала. Команды, отправленные за это время, теряются, а первые строки, которые Arduino печатает в setup(), можно пропустить. Решение — подождать time.sleep(2) после открытия порта или, что надёжнее, дождаться, пока Arduino отправит строку вроде HAZIR; пример с командами выше делает именно так. Открывай порт один раз в начале программы и держи открытым до конца: цикл, который открывает и закрывает порт для каждого измерения, будет сбрасывать плату снова и снова.
Если нужно полностью избежать сброса, есть два способа. Программный: создай закрытый порт через ser = serial.Serial(), задай ser.port и ser.baudrate, напиши ser.dtr = False и только потом вызови ser.open(). В Windows это обычно работает; в Linux и macOS операционная система всё равно может дёрнуть DTR при открытии порта. Аппаратный: поставь конденсатор 10 µF между пинами RESET и GND на Uno, плюсовой ножкой к RESET. Платы со встроенным USB, такие как Leonardo, Micro и Raspberry Pi Pico, не перезагружаются при открытии порта; на них строки, которые код печатает до подключения Python, могут потеряться, поэтому важные сообщения надёжнее отправлять в ответ на пришедшую команду.
Связь ESP32 и Python по последовательному порту
Весь код на Python из этой статьи работает и с ESP32; на стороне Arduino нужно поменять лишь несколько вещей. Для аналогового входа используй один из пинов ADC1 (например, GPIO 34). АЦП у ESP32 12-битный и переводит диапазон 0–3,3 V в 0–4095, поэтому напряжение в Python считай как deger * 3.3 / 4095. При загрузке ESP32 печатает собственные стартовые сообщения на скорости 115200 бод, например rst:0x1 (POWERON_RESET),boot:0x13; скрипты выше пропускают строки, которые нельзя превратить в числа, так что эти сообщения не мешают. В зависимости от схемы автосброса на плате и операционной системы ESP32 тоже может перезагружаться при открытии порта, поэтому ожидание и строка HAZIR пригодятся и здесь. USB-микросхема обычно CP2102 или CH340. Если хочется обойтись без кабеля, ESP32 может отправлять те же данные по Wi-Fi; смотри руководства ESP32 и WebSocket и ESP32 и Google Таблицы. Если это первая ESP32, сначала прочитай статью Что такое ESP32?
Raspberry Pi Pico и MicroPython: в чём разница?

Ещё один способ соединить Python и железо — запускать Python прямо на плате. Raspberry Pi Pico можно программировать на MicroPython: чтение датчиков, управление светодиодами и отсчёт времени выполняются внутри Pico с синтаксисом Python. Однако MicroPython — это урезанная версия настольного Python; matplotlib, pandas и OpenCV на Pico не работают. Поэтому для записи данных, построения графиков или обработки с помощью ИИ Pico тоже подключают к Python на компьютере. Код на pyserial из этой статьи работает и с Pico; нужно лишь адаптировать имя порта, диапазон значений и способ чтения ответов на команды, как описано ниже.
| Характеристика | Arduino + Python на ПК | Pico + MicroPython (автономно) | Pico + Python на ПК |
|---|---|---|---|
| Где работает Python? | Только на компьютере; на плате работает C++ | Внутри Pico | На обеих сторонах: MicroPython на Pico, Python на компьютере |
| Нужен ли компьютер? | Да, данные обрабатываются на компьютере | Нет, работает от USB-питания или батареи | Да |
| Доступные библиотеки | pyserial, matplotlib, pandas, numpy и OpenCV | Ограниченный набор модулей, например machine и time | Все библиотеки компьютера |
| Скорость (бод) | Важна, должна совпадать на обеих сторонах | Последовательный порт не используется | Не важна, данные идут со скоростью USB |
| Сброс при открытии порта | Да, на Uno, Nano и Mega | Нет | Нет |
| Когда выбирать | Arduino уже есть под рукой и нужны готовые библиотеки | Простые автономные устройства | Хочется один язык (Python) на обеих сторонах |
Код на MicroPython ниже — это версия примера для Arduino под Pico: он каждые 200 мс читает потенциометр на GP26, отправляет результат в том же формате время,значение и принимает те же команды для светодиода. Здесь аналог Serial из Arduino — это print() и sys.stdin; благодаря select.poll() цикл измерений не останавливается, пока ждёт команды.
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)Сохрани код на Pico в Thonny под именем main.py, затем закрой Thonny, отключи USB-кабель Pico и подключи снова: Thonny сама использует последовательный порт Pico, поэтому, пока она подключена, твой скрипт на Python не сможет открыть порт, а при подключении она ещё и останавливает запущенную программу; после перезагрузки main.py запускается автоматически. На USB-порту Pico скорость в бодах не важна: какое бы значение ни стояло в Python, данные идут со скоростью USB. read_u16() возвращает значения от 0 до 65535, поэтому напряжение считай как deger * 3.3 / 65535. Поскольку Pico отправляет строки измерений без остановки, readline() в скрипте с командами выше может вернуть измерение вместо ответа; с Pico продолжай читать, пока не придёт строка, начинающаяся с OK. В этом примере нет строки HAZIR и команд PWM и READ; при необходимости добавь их в код для Pico. Осторожно с отправкой двоичных данных с компьютера: байт 0x03 в MicroPython означает Ctrl+C и останавливает программу; это можно отключить через micropython.kbd_intr(-1). Если Pico пока в новинку, хорошее начало — статьи Что такое Raspberry Pi Pico? и Основы MicroPython.
Идеи проектов с Arduino и Python
- Регистратор температуры и влажности: раз в минуту записывай в CSV показания DHT11 и построй график микроклимата в комнате за неделю в Excel или с pandas.
- Управление лампой с компьютера: управляй модулем реле командами ON и OFF из Python и добавь к этому таймер или голосовые команды.
- График расстояния в реальном времени: выводи расстояние, измеренное HC-SR04, на график с matplotlib. Если поворачивать датчик сервоприводом, тем же способом можно нарисовать и экран радара.
- Экран состояния ПК: пусть Python читает загрузку процессора и памяти через библиотеку psutil и отправляет её в Arduino, а Arduino показывает данные на OLED-дисплее.
- Камера, следящая за лицом: OpenCV находит положение лица на изображении, Python переводит его в угол и отправляет, а Arduino поворачивает камеру сервоприводом.
- Своя клавиатура для горячих клавиш: пусть кнопки на Arduino при нажатии отправляют строку, а Python читает её и открывает программу или выполняет сочетание клавиш на компьютере.
Частые ошибки и как их исправить
| Ошибка или симптом | Причина | Решение |
|---|---|---|
| ModuleNotFoundError: No module named 'serial' (модуль не найден) | pyserial не установлен для интерпретатора Python, который запускает скрипт; VS Code или Thonny могут использовать другой Python | Выполни python -m pip install pyserial тем же интерпретатором и выбери нужный интерпретатор в редакторе |
| AttributeError: module 'serial' has no attribute 'Serial' (нет атрибута Serial) | Установлен не тот пакет serial, или твой файл называется serial.py | pip uninstall serial, затем pip install pyserial; переименуй свой файл |
| Порт занят — could not open port 'COM3': PermissionError(13, 'Отказано в доступе.', None, 5) | Порт открыт в другой программе: мониторе порта, плоттере или втором скрипте | Закрой другую программу; используй в скрипте конструкцию with |
| Порт не найден — could not open port 'COM5': FileNotFoundError(2, 'Не удается найти указанный файл.', None, 2) | Неверное имя порта или плата не подключена | Найди правильное имя командой python -m serial.tools.list_ports -v |
| [Errno 13] Permission denied: '/dev/ttyACM0' | У твоего пользователя Linux нет прав на доступ к последовательному порту | sudo usermod -a -G dialout $USER, затем выйди из системы и войди снова |
| Мусорные символы или UnicodeDecodeError | Разная скорость в бодах или помехи при запуске | Используй одинаковую скорость на обеих сторонах; добавь decode(errors="ignore") и reset_input_buffer() |
| readline() всегда возвращает b'' | Arduino не отправляет конец строки (print вместо println), порт неверный или плата ещё загружается | Используй println, проверь порт и скорость, увеличь timeout |
| ValueError: invalid literal for int() with base 10 (строку нельзя превратить в число) | В число пытались превратить неполную, пустую или неожиданную строку | Оберни преобразование в try/except и проверяй количество столбцов |
| Первые команды игнорируются | Uno перезагрузилась при открытии порта, и команда ушла в загрузчик | Дождись строки HAZIR или подожди 2 секунды после открытия порта |
| График отстаёт от реального времени | За кадр читается только одна строка, и данные копятся в буфере | Читай все строки циклом in_waiting и отправляй данные реже |
| Код не загружается, пока работает скрипт | Python держит порт | Останови скрипт через Ctrl+C, затем загружай |
Связать Arduino и Python через последовательный порт — самый быстрый способ превратить простой проект с датчиком в систему, которая записывает данные, рисует графики и управляется с компьютера. Сначала прочитай одно значение и выведи его на экран, потом сохрани в CSV, а команды и графики добавь в последнюю очередь; на каждом шаге проверяй, что скорость в бодах и конец строки совпадают на обеих сторонах. В приложении BlueGrays есть пошаговые проекты со схемами подключения и кодом: измерение освещённости с LDR, температуры и влажности с DHT11, расстояния с HC-SR04, а также последовательная связь между двумя Arduino. С ними можно быстро собрать источник данных, которые потом будет читать Python.
Частые вопросы
Можно ли программировать Arduino на Python?
Внутри Arduino Uno, Nano или Mega Python не работает; эти платы программируются в Arduino IDE на языке Arduino, основанном на C++. Python работает на компьютере и общается с платой по последовательному порту через pyserial; ещё можно загрузить пример StandardFirmata и управлять пинами прямо из Python с помощью библиотеки вроде pyFirmata2. Если нужно, чтобы Python работал на самой плате, выбирай Raspberry Pi Pico или ESP32 с поддержкой MicroPython.
Как установить pyserial?
Выполни в терминале python -m pip install pyserial и напиши в коде import serial. Пакет называется pyserial, а импортируемый модуль — serial; команда pip install serial ставит совершенно другой пакет и приводит к ошибке AttributeError. В Linux ещё нужно добавить своего пользователя в группу dialout.
Как узнать, на каком COM-порту Arduino?
Посмотри в меню «Инструменты › Порт» (Tools › Port) в Arduino IDE или выполни в терминале python -m serial.tools.list_ports -v. В Windows порт также виден в «Диспетчере устройств» в разделе «Порты (COM и LPT)». Если есть сомнения, отключи плату и выведи список портов снова; исчезнувший порт и есть порт Arduino.
Как отправить данные из Python в Arduino?
Открой порт через serial.Serial и отправляй байты, например ser.write(b"LED ON\n"); не забудь преобразовать текст через encode() и добавить конец строки. На стороне Arduino проверяй наличие входящих данных через Serial.available() и читай строку через Serial.readStringUntil('\n'). Uno перезагружается при открытии порта, поэтому перед первой командой подожди около 2 секунд.
Можно ли одновременно пользоваться монитором порта Arduino и Python?
Нет, последовательный порт одновременно может открыть только одна программа; вторая получит ошибку Access is denied («Отказано в доступе») или Resource busy. Зато входящие данные можно в Python одновременно обрабатывать и выводить на экран через print. Если нужны два отдельных канала, подключи к Arduino второй USB-TTL-преобразователь и создай второй последовательный порт через SoftwareSerial.
Почему Arduino перезагружается, когда Python открывает порт?
На Uno, Nano и Mega линия DTR USB-микросхемы подключена к пину RESET через конденсатор, поэтому плата перезагружается при каждом открытии порта; эта схема нужна, чтобы IDE могла загружать код. Подожди 2 секунды после открытия порта или дождись от Arduino строки о готовности. Чтобы полностью избежать сброса, можно поставить конденсатор 10 µF между RESET и GND, но перед загрузкой его нужно убрать.
Что собрать после прочтения
Все проекты
Измерение освещённости с LDR
Хочешь увидеть яркость своей комнаты в виде числа? LDR — маленькая деталь, реагирующая на свет, — измеряет освещённость вокруг, а Arduino каждые полсекунды выводит значение на экран компьютера. Закрой

Измерение расстояния HC-SR04 (вывод в порт)
Датчик HC-SR04 работает совсем как летучая мышь, которая находит дорогу в темноте: он посылает звук, которого ты не слышишь, слушает эхо и измеряет, насколько далеко предмет перед ним. Arduino каждые

Температура и влажность с DHT11
Насколько тепло в твоей комнате и влажный ли воздух или сухой? Датчик DHT11 измеряет температуру и влажность, а Arduino выводит значения на двухстрочный LCD-экран и обновляет их каждые пару секунд. Ес

Измерение освещённости модулем LDR на ESP32
Наблюдение в мониторе порта в реальном времени за сырым значением ADC модуля LDR и относительным процентом освещённости, вычисленным по опорным значениям темноты и света. Этот процент — не измерение в люксах. Учит логике калибровки модуля. Идеально выполнить его перед проектом 10, потому что он нужен для определения порога.

Односторонняя связь между двумя Arduino
Ты заставляешь две Arduino разговаривать по одному проводу: нажми кнопку на одной, и на другой загорится светодиод, нажми снова — погаснет. Плата-передатчик сообщает о нажатии кнопки коротким сообщени

Двусторонняя связь между двумя Arduino
Две Arduino соединены проводами и разговаривают друг с другом: нажми кнопку на одной, и на другой загорится свет, нажми снова — погаснет. На обеих платах одна и та же программа, поэтому каждая и отпра

Управление светодиодом с телефона по Bluetooth
Небольшая репетиция того, как включать и выключать свет в комнате с телефона. Bluetooth-модуль передаёт в Arduino буквы, отправленные с телефона; одна буква включает красный светодиод, другая — зелёны

Регистратор температуры на ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
Собери на ESP32 регистратор температуры с отметками времени! Каждую минуту читай температуру и влажность с DHT22, бери реальное время из DS1307 RTC и записывай всё на карту microSD в формате CSV. OLED показывает текущую температуру (крупным шрифтом), влажность в процентах, время и счётчик записей. Файл /temp_log.csv на SD-карте открывается в Excel, Google Sheets или Python pandas — идеально для теплиц, комнат, лабораторий и хранения продуктов.
Другие статьи
Приложение BlueGrays
Собери эту схему шаг за шагом в телефоне
Схема подключения, код и список деталей на одном экране. 200+ проектов на Arduino, ESP32 и Pico готовы к сборке.
Наведи камеру телефона
Наведи камеру на код — откроется магазин приложений на телефоне.
или открыть магазин

