Основы · 17 мин чтения

Монитор порта Arduino: скорость в бодах, отправка данных, плоттер

Как пользоваться монитором порта Arduino: Serial.begin и скорость в бодах, борьба с мусорными символами, конец строки, parseInt, плоттер по последовательному соединению, примеры для ESP32 и Pico.

Монитор порта Arduino (Serial Monitor) — это окно в Arduino IDE, через которое плата и компьютер обмениваются текстом по USB-кабелю. Здесь видно, что выводит каждая строка Serial.println() в скетче, и здесь же набираются команды, которые нужно отправить обратно в плату. Это самый быстрый способ узнать, что прямо сейчас измеряет датчик, до какого места дошла программа и действительно ли нажимается кнопка, поэтому монитор порта — инструмент отладки номер один в мире Arduino. Чтобы им пользоваться, достаточно строки Serial.begin(9600) в коде, правильного порта в списке выбора платы и той же скорости в мониторе, что и в коде.

В этом руководстве сначала откроем монитор порта, а затем разберём, что на самом деле означает скорость в бодах, как выводить числа в десятичном, шестнадцатеричном и двоичном виде с помощью print и println, как управлять количеством знаков после запятой и как собирать аккуратные строки через snprintf. На примерах скетчей, которые действительно компилируются, разберём все причины мусорных символов, влияние настройки конца строки на код, отправку чисел и текстовых команд с компьютера в плату (Serial.read, parseInt, readStringUntil), построение живых графиков в плоттере по последовательному соединению (Serial Plotter) и приёмы отладки, которые сильно упростят жизнь. В последних разделах показано, чем отличается вывод в порт на ESP32 и Raspberry Pi Pico (и в Arduino, и в MicroPython) и как решить частые проблемы вроде пустого монитора.

Плата Arduino Uno R3 и её USB-разъём
Arduino Uno: микросхема рядом с USB-разъёмом преобразует последовательный порт процессора в USB; светодиод TX коротко мигает при передаче данных в компьютер, а светодиод RX — при приёме данных платой.

Что такое монитор порта Arduino и как он работает?

ATmega328P, «мозг» Arduino Uno, общается с внешним миром через блок последовательной связи под названием UART: он передаёт данные бит за битом по одной линии (TX) и принимает по другой (RX). В современных компьютерах такого последовательного порта уже нет, поэтому вторая микросхема на плате (ATmega16U2 на оригинальной Uno, на клонах обычно CH340) преобразует сигнал в USB. Компьютер видит плату как виртуальный последовательный порт: в Windows это что-то вроде COM3, в Linux — /dev/ttyACM0 или /dev/ttyUSB0, а в macOS — имя, начинающееся с /dev/cu.usbmodem или /dev/cu.usbserial. Монитор порта открывает этот порт, показывает входящие байты как текст и так же отправляет в плату то, что ты набираешь. По умолчанию Arduino использует формат 8N1: на каждый байт один стартовый бит, 8 бит данных, без бита чётности и один стоповый бит. Значит, один символ занимает на линии 10 бит; это число пригодится в расчётах скорости.

ХарактеристикаArduino UnoESP32 DevKitRaspberry Pi Pico
Подключение по USBМикросхема-мост ATmega16U2 или CH340Микросхема-мост CP2102 или CH340Собственный USB у RP2040 (USB CDC)
Пины последовательного портаD0 (RX), D1 (TX)GPIO 3 (RX), GPIO 1 (TX)Пинов нет, напрямую по USB
Дополнительные аппаратные портыНет (программный порт через SoftwareSerial)Serial1 и Serial2 (Serial2 часто используют на GPIO 16 RX, GPIO 17 TX)Serial1 (GP0 TX, GP1 RX), Serial2 (GP8 TX, GP9 RX)
Типичная скорость9600 бод115200 бодНе важна, работает на скорости USB
Сброс при открытии монитораДа (линия DTR)Зависит от платы и программыНет
Serial.printf()Нет, используй snprintfЕстьЕсть
Модуль преобразователя USB-TTL на CH340
Преобразователь USB-TTL на CH340: та же микросхема, что стоит на большинстве клонов Arduino, только в виде отдельного модуля; он превращает USB в пины TX, RX и GND и позволяет пользоваться монитором порта с платами без USB, например Pro Mini.

Как открыть монитор порта и как им пользоваться

  1. Добавь строку Serial.begin(9600); в раздел setup() скетча и загрузи скетч в плату. Без этой строки плата ничего не пишет в последовательный порт.
  2. Выбери порт, к которому подключена плата, в списке выбора платы на верхней панели или в меню Инструменты › Порт (Tools › Port). Если порт не выбран, монитор порта показывает предупреждение Not connected. Select a board and a port to connect automatically (не подключено, выбери плату и порт).
  3. Нажми на значок лупы в правом верхнем углу, выбери меню Инструменты › Монитор порта (Tools › Serial Monitor) или нажми Ctrl+Shift+M (Cmd+Shift+M в macOS). В IDE 2 монитор открывается на нижней панели рядом с разделом «Вывод» (Output).
  4. В меню скорости справа на панели выбери ту же скорость в бодах, что и в коде; в этом примере 9600. Если скорость отличается, экран заполняется мусорными символами.
  5. Чтобы отправить данные в плату, набери сообщение в поле ввода вверху панели и нажми Enter. Конец строки выбирается в меню прямо рядом с ним; ниже объясняем, почему эта настройка важна.
  6. Кнопками в правом верхнем углу можно приостановить автопрокрутку (удобно, когда читаешь старые строки), включить отметки времени, которые добавляют время компьютера в начало каждой строки, и очистить вывод.

В старой Arduino IDE 1.8 монитор порта открывается в отдельном окне; элементы управления делают то же самое, только расположены иначе: флажки автопрокрутки и отметок времени внизу слева, меню конца строки и скорости внизу справа. Следить за последовательным портом можно и без IDE: то же самое умеют PuTTY или CoolTerm в Windows, команда screen /dev/ttyUSB0 115200 в Linux и macOS и arduino-cli monitor -p COM3 -c baudrate=115200 в любой системе. Помни одну вещь: последовательный порт одновременно может открыть только одна программа. Пока порт занят скриптом на Python или PuTTY, загрузить код из IDE не получится; установку IDE и остальной интерфейс мы разобрали в руководстве по установке Arduino IDE.

На Arduino Uno и Nano пины D0 (RX) и D1 (TX) — это та же линия, что идёт к USB. Если к этим пинам подключён Bluetooth-модуль, GPS или другая плата, загрузка может не пройти, в мониторе порта появляются перемешанные данные, а модуль к тому же получает весь вывод Serial.print(). Во время загрузки оставляй эти пины свободными, а модули подключай к другим пинам через SoftwareSerial. Пины последовательного порта своей платы можно посмотреть в утилите распиновки.

Serial.begin() и скорость в бодах: 9600 или 115200?

Serial.begin(скорость) запускает последовательный порт на заданной скорости и почти всегда вызывается один раз внутри setup(). Скорость задаётся в бодах — это число бит, передаваемых по линии за секунду. Так как каждый символ занимает 10 бит, 9600 бод означают максимум 960 символов в секунду; передача одного символа занимает около 1,04 миллисекунды. Строка из 30 символов на этой скорости передаётся 31 мс, так что, как только нужно выводить сотни измерений в секунду, упираешься в этот предел. На 115200 бод та же строка занимает всего 2,6 мс. Значение в скетче и значение в мониторе порта должны совпадать, потому что общего тактового сигнала нет и приёмник считывает биты только по времени.

Скорость, бодСимволов в секундуВремя на символГде используется?
96009601,04 мсПримеры Arduino; по умолчанию у модулей вроде HC-05 и GPS NEO-6M
576005760174 µsСкорость загрузки у Nano со старым загрузчиком, некоторые модули
748807488134 µsЗагрузочные сообщения ESP8266
1152001152087 µsESP32, Pico и быстрая отладка; скорость загрузки нового загрузчика Uno
2500002500040 µsБыстрая запись данных; тактовая частота Uno 16 MHz даёт её без ошибки
100000010000010 µsОчень быстрая передача данных; нужен короткий кабель

Тактовая частота Uno 16 MHz не позволяет получить любую скорость точно: 115200 бод на деле отклоняются примерно на 2% и составляют 117647 бод. В мониторе порта эта погрешность проблем не вызывает, но при связи с другим микроконтроллером по длинному кабелю может приводить к ошибкам; в таких случаях надёжнее 57600 или 250000 бод, которые получаются без ошибки. Для начала используй 9600 для Uno и Nano и 115200 для ESP32 и Pico. Если две платы соединены проводами TX–RX, скорость с обеих сторон должна быть одинаковой, земли (GND) должны быть общими, а линию TX на 5 V перед входом RX на 3,3 V (ESP32, Pico) нужно понизить делителем напряжения (номиналы подскажет утилита делителя напряжения). Подробности — в статье про последовательную связь UART.

Serial.print() и Serial.println(): вывод и форматирование

Serial.print() выводит переданное значение и оставляет курсор на той же строке; Serial.println() после этого ещё добавляет символы конца строки (\r\n), поэтому следующий вывод начинается с новой строки. Чтобы показать несколько значений в одной строке, выводи первые части через print, а последнюю через println. Эти функции принимают почти любой тип: int, long, float, char, текст и String, а необязательный второй параметр задаёт формат. Для целых чисел можно выбрать DEC, HEX, BIN и OCT, а для чисел с плавающей точкой второй параметр — это количество знаков после запятой. По умолчанию float выводится с 2 знаками после запятой, с округлением. В коде ниже результат каждой строки записан в комментарии:

C++ (Arduino IDE)
1// Biçimlendirme örnekleri: çıktılar yorumlarda / Formatting examples: output in the comments
2void setup() {
3  Serial.begin(9600);
4  int sayi = 78;
5  float pi = 3.14159;
6
7  Serial.println(sayi);           // 78
8  Serial.println(sayi, HEX);      // 4E
9  Serial.println(sayi, BIN);      // 1001110
10  Serial.println(sayi, OCT);      // 116
11  Serial.println(pi);             // 3.14 (varsayılan 2 basamak / 2 decimals by default)
12  Serial.println(pi, 4);          // 3.1416
13  Serial.write(65);               // A (ham bayt / raw byte)
14  Serial.println();               // yalnız satır sonu / line break only
15
16  Serial.print("Sicaklik:\t");    // \t = sekme / \t = tab
17  Serial.print(23.46, 1);         // 23.5
18  Serial.println(" C");           // Sicaklik:  23.5 C
19
20  char satir[32];
21  int saat = 9, dakika = 5;
22  snprintf(satir, sizeof(satir), "Saat %02d:%02d", saat, dakika);
23  Serial.println(satir);          // Saat 09:05
24
25  char metin[10];
26  dtostrf(pi, 6, 2, metin);       // Uno'da %f yerine / instead of %f on the Uno
27  Serial.println(metin);          // "  3.14"
28}
29
30void loop() {
31}
КодЧто показывает монитор портаПримечание
Serial.print(78);78По умолчанию десятичный вид (DEC)
Serial.print(78, HEX);4EШестнадцатеричный; для адресов, цветов и значений байтов
Serial.print(78, BIN);1001110Двоичный; ведущие нули не выводятся
Serial.print(3.14159);3.14У float по умолчанию 2 знака после запятой
Serial.print(3.14159, 4);3.14164 знака, с округлением
Serial.write(65);AОтправляет символ ASCII с кодом 65, а не число
Serial.print('A');AТип char выводится как символ
Serial.print(5e9);ovfНа Uno числа float больше примерно 4,29 млрд вывести нельзя

Вместо того чтобы выводить длинные строки по кусочкам, можно собрать одну строку с помощью snprintf(): %d — целое число, %02d — двузначное целое с ведущим нулём, %s — текст, а %lu — unsigned long, например значение millis(). Ради экономии памяти библиотека C на Uno не поддерживает %f: при попытке вывести float на его месте появится ?. Поэтому сначала преврати float в текст с помощью dtostrf(число, ширина, знаки, буфер). На ESP32 и Pico можно сразу использовать Serial.printf(). У Uno всего 2 КБ ОЗУ; постоянный текст, записанный через макрос F(), как в Serial.println(F("Hello")), хранится во флеш-памяти, а не в ОЗУ, и избавляет большие проекты от проблем нехватки памяти.

Мусорные символы в мониторе порта и как от них избавиться

Если на экране ⸮⸮⸮, � или куча случайных букв и значков («кракозябры»), данные до компьютера доходят, но расшифровываются неправильно. Поскольку у последовательной связи нет общего тактового сигнала, приёмник вычисляет середину каждого бита по своей скорости, начиная с момента, когда видит стартовый бит. Если скорость неверная или тактовая частота платы не та, что должна быть, биты считываются не в те моменты, и каждый байт превращается в другой символ. Обычно причина в одной-единственной настройке; в таблице ниже собраны самые частые случаи по симптомам. Для плат с разными вариантами тактовой частоты, например Pro Mini, загляни также в статью об Arduino Pro Mini.

СимптомПричинаРешение
Мусор от начала до концаСкорость в мониторе порта отличается от значения в Serial.begin()Выбери в меню справа скорость из своего кода
Искажена только одна строка при старте, остальное в порядкеESP8266 на 74880 или ESP32 на 115200 бод выводит собственное загрузочное сообщениеЭто нормально; чтобы его прочитать, переключи монитор на эту скорость
Вывод на Pro Mini всегда искажёнВместо платы на 3,3 V / 8 MHz выбрана плата на 5 V / 16 MHz, поэтому реальная скорость вдвое меньшеВыбери реальную частоту платы в меню Инструменты › Процессор (Tools › Processor)
В обычный текст вклиниваются случайные символыМодуль на пинах D0/D1 тоже передаёт по этой линииПодключи модуль к другим пинам через SoftwareSerial
Странные значки вместо чиселSerial.write(число) отправляет число как сырой байтИспользуй Serial.print(), чтобы вывести число как текст
Данные между двумя платами искажаютсяНет общего GND, длинный кабель или разные скоростиСоедини GND, укороти кабель, снизь скорость
Одна и та же искажённая строка появляется снова и снова, плата перезапускаетсяПитания не хватает, и плата постоянно сбрасываетсяПроверь источник питания и кабель, питай нагрузку отдельно
Быстрее всего проверить, не в скорости ли дело, — по очереди выставить в мониторе порта 9600, 57600 и 115200 и посмотреть, при какой из них текст становится читаемым. На платах ESP8266 (NodeMCU, Wemos D1 Mini), чтобы прочитать искажённую строку, которая появляется после нажатия кнопки RESET, переключи монитор на 74880 бод: появится загрузочная информация вроде rst cause:2, boot mode:(3,6). Эти строки очень ценны, когда нужно понять, почему плата перезапустилась; подробности — в статье об ESP8266 NodeMCU.

Настройка конца строки: No Line Ending, New Line, Carriage Return, Both NL & CR

Когда набираешь текст в поле ввода и нажимаешь Enter, монитор порта отправляет не только набранные символы, но и один-два невидимых символа — в зависимости от выбранного конца строки. Если код их не ожидает, например обрабатывает каждый входящий байт как команду, то '\n' (значение 10) и '\r' (13) тоже читаются как команды. С другой стороны, если код ждёт конец строки, а выбрано No Line Ending, строка никогда не заканчивается: readStringUntil() ждёт, пока не истечёт таймаут. В таблице ниже показаны байты, которые получает плата, если набрать 42 и нажать Enter:

ЗначениеНазвание в IDEДобавляемый символБайты, отправляемые для 42
Ничего не добавляетсяNo Line EndingНет52, 50
Новая строка (LF)New Line\n (LF, 10)52, 50, 10
Возврат каретки (CR)Carriage Return\r (CR, 13)52, 50, 13
Оба (CR + LF)Both NL & CR\r\n (13, 10)52, 50, 13, 10

Практическое правило: для кода, который читает построчно, выбирай New Line и применяй trim() к прочитанному тексту; тогда код работает при любой настройке пользователя. Для односимвольных команд вроде 1, 0 или a самый чистый вариант — No Line Ending. IDE запоминает выбор, так что не забудь проверить его при переходе к другому проекту.

Отправка данных с компьютера в Arduino: Serial.read(), parseInt(), readStringUntil()

Байты, отправленные из монитора порта, встают в очередь в приёмном буфере платы (на Uno 64 байта). Serial.available() сообщает, сколько байтов в нём ждёт, Serial.read() забирает следующий байт и удаляет его из буфера (если данных нет, возвращает -1), а Serial.peek() смотрит на следующий байт, не удаляя его. Помни, что всё с клавиатуры приходит в виде символов ASCII: если набрать 7, плата получит не число 7, а 55 — код символа '7'. Пример простых команд одной клавишей есть в статье о командах Arduino; здесь разбираем многозначные числа и команды из слов. Если буфер переполнится, новые байты теряются, поэтому отсчёт времени через millis() вместо долгого ожидания delay() внутри loop() делает чтение порта ещё и надёжнее.

Serial.parseInt() извлекает из входящих символов первое целое число: пропускает буквы и пробелы в начале, собирает цифры и останавливается на первом символе, который не цифра; знак минуса она тоже понимает. Так текст «128» превращается в число 128. Для дробных чисел то же самое делает Serial.parseFloat(). У этих функций есть одна привычка, о которой нужно знать: если число не приходит, они по умолчанию ждут 1000 мс, а затем возвращают 0. Классическая ошибка выглядит так: при настройке New Line отправляется 42, parseInt читает 42, но '\n' остаётся в буфере. На следующем проходе loop() видит, что available() больше 0, и снова вызывает parseInt, которая ждёт секунду и возвращает 0. В итоге после каждого числа приходит 0, и непонятно, почему светодиод гаснет через секунду после каждого числа. Код ниже решает проблему в корне: отбрасывает следующий символ, если это не цифра, и сокращает ожидание с помощью setTimeout().

C++ (Arduino IDE)
1// Seri Port Ekranı'ndan sayı al: LED parlaklığı 0-255
2// Read a number from the Serial Monitor: LED brightness 0-255
3const int LED_PIN = 9;   // ~9 PWM pini + 220 ohm direnç / ~9 PWM pin + 220 ohm resistor
4
5void setup() {
6  pinMode(LED_PIN, OUTPUT);
7  Serial.begin(9600);
8  Serial.setTimeout(100);   // parseInt en fazla 100 ms bekler / parseInt waits at most 100 ms
9  Serial.println(F("0-255 arasi bir sayi yaz / Type a number from 0 to 255"));
10}
11
12void loop() {
13  if (Serial.available() > 0) {
14    char c = Serial.peek();               // sıradakine bak, silme / look at the next byte, keep it
15    if (isDigit(c) || c == '-') {
16      int deger = Serial.parseInt();      // "128" -> 128
17      deger = constrain(deger, 0, 255);   // aralığın dışına çıkma / stay within range
18      analogWrite(LED_PIN, deger);
19      Serial.print(F("Parlaklik / Brightness: "));
20      Serial.println(deger);
21    } else {
22      Serial.read();                      // '\r', '\n' ve harfleri at / drop '\r', '\n' and letters
23    }
24  }
25}

Для команд из слов проще всего прочитать всю строку как String. Serial.readStringUntil('\n') читает до прихода '\n', отбрасывает символ конца строки и возвращает остальное. Если выбрано Both NL & CR, в конце текста остаётся '\r', команда «on» превращается в «on\r», и сравнение не срабатывает. Поэтому сразу применяй к строке trim(): эта функция убирает пробелы в начале и в конце, а также символы '\r' и '\n'. toLowerCase() делает ON, On и on одинаковыми. Пример ниже понимает команды on, off, blink 250 и status; в мониторе порта выставь 115200 бод и New Line. Так как мигание сделано на millis(), а не на delay(), скетч мгновенно отвечает на новую команду в любой момент.

C++ (Arduino IDE)
1// Metin komutları: on, off, blink 250, status
2// Text commands: on, off, blink 250, status
3const int LED_PIN = LED_BUILTIN;   // Uno'da pin 13 / pin 13 on the Uno
4bool ledAcik = false;              // LED'in şu anki durumu / current LED state
5bool yanipSon = false;             // yanıp sönme modu / blink mode
6unsigned long aralik = 500;        // yanıp sönme aralığı (ms) / blink interval (ms)
7unsigned long sonDegisim = 0;      // LED'in son değiştiği an / last time the LED changed
8
9void ledYaz(bool acik) {
10  ledAcik = acik;
11  digitalWrite(LED_PIN, acik ? HIGH : LOW);
12}
13
14void komutuIsle(String komut) {
15  if (komut == "on") {
16    yanipSon = false;
17    ledYaz(true);
18    Serial.println(F("LED: on"));
19  } else if (komut == "off") {
20    yanipSon = false;
21    ledYaz(false);
22    Serial.println(F("LED: off"));
23  } else if (komut.startsWith("blink")) {
24    long ms = komut.substring(5).toInt();       // "blink 250" -> 250
25    aralik = (ms >= 50) ? ms : 500;             // çok küçükse 500 ms / fall back to 500 ms
26    yanipSon = true;
27    Serial.print(F("Blink (ms): "));
28    Serial.println(aralik);
29  } else if (komut == "status") {
30    Serial.print(F("LED: "));
31    Serial.print(ledAcik ? F("on") : F("off"));
32    Serial.print(F(", blink: "));
33    Serial.println(yanipSon ? F("yes") : F("no"));
34  } else {
35    Serial.print(F("Bilinmeyen komut / Unknown command: "));
36    Serial.println(komut);
37  }
38}
39
40void setup() {
41  pinMode(LED_PIN, OUTPUT);
42  Serial.begin(115200);
43  Serial.setTimeout(100);   // satır sonu gelmezse 100 ms sonra bırak / give up after 100 ms without a line ending
44  Serial.println(F("Komutlar / Commands: on, off, blink 250, status"));
45}
46
47void loop() {
48  if (Serial.available() > 0) {
49    String komut = Serial.readStringUntil('\n');   // Enter'a kadar oku / read up to Enter
50    komut.trim();                                  // '\r' ve boşlukları sil / strip '\r' and spaces
51    komut.toLowerCase();                           // ON ile on aynı olsun / treat ON like on
52    if (komut.length() > 0) {
53      komutuIsle(komut);
54    }
55  }
56
57  if (yanipSon && millis() - sonDegisim >= aralik) {   // delay() olmadan yanıp sön / blink without delay()
58    sonDegisim = millis();
59    ledYaz(!ledAcik);
60  }
61}
ФункцияЧто возвращаетСколько ждёт?Лучше всего подходит для
Serial.read()Один байт (int), -1, если данных нетНе ждётОдносимвольных команд, собственного парсера
Serial.peek()Следующий байт, не удаляя егоНе ждётПросмотра следующего байта перед принятием решения
Serial.parseInt()Целое число long, 0 по таймаутуВремя setTimeout (по умолчанию 1000 мс)Ввода чисел: угол, скорость, яркость
Serial.parseFloat()Число float, 0 по таймаутуВремя setTimeoutДробных настроек, например 2.5
Serial.readStringUntil(c)Текст до символа c (String)Пока не придёт c или не истечёт времяКоманд со словами и параметрами
Serial.readBytesUntil(c, буфер, n)Число прочитанных байтов; записывает текст в массив charПока не придёт c, не будет прочитано n байтов или не истечёт времяЧтения без String, бережного к ОЗУ

Живые графики в плоттере по последовательному соединению

Плоттер по последовательному соединению (Serial Plotter) превращает числа из последовательного порта в живой линейный график; он открывается значком осциллограммы в правом верхнем углу IDE или через меню Инструменты › Плоттер по последовательному соединению. Формат очень простой: раздели значения в строке запятыми, пробелами или табуляцией и заверши строку вызовом println. Каждая строка становится одним шагом по времени на графике, а каждое значение в строке — отдельной цветной линией. Если перед значениями поставить имя в виде метка:значение, линии получат эти названия, и их можно по одной скрывать и показывать флажками над графиком; без меток линии называются value 1, value 2 и так далее. Здесь тоже меню скорости должно совпадать с кодом. Код ниже рисует на одном графике сырое значение с A0 и его сглаженную версию:

C++ (Arduino IDE)
1// Seri Çizici: ham değer ve yumuşatılmış değer / Serial Plotter: raw and smoothed value
2const int SENSOR_PIN = A0;   // potansiyometre ya da LDR bölücü çıkışı / potentiometer or LDR divider output
3float yumusak = 0;           // üstel ortalama / exponential moving average
4
5void setup() {
6  Serial.begin(115200);
7}
8
9void loop() {
10  int ham = analogRead(SENSOR_PIN);           // 0-1023
11  yumusak = yumusak * 0.9 + ham * 0.1;        // yeni ölçümün payı %10 / the new reading weighs 10%
12
13  Serial.print("Min:0,Max:1023,");            // dikey ekseni sabitler / keeps the y-axis fixed
14  Serial.print("Ham:");
15  Serial.print(ham);
16  Serial.print(",Yumusak:");
17  Serial.println(yumusak);                    // satırı bitir: bir ölçüm / end the line: one sample
18  delay(20);                                  // saniyede yaklaşık 50 satır / about 50 lines per second
19}
  • Зафиксируй масштаб: плоттер постоянно перестраивает вертикальную ось под входящие значения, поэтому мелкие колебания выглядят огромными. Если добавить в каждую строку два постоянных значения вроде Min:0,Max:1023,, ось остаётся неподвижной.
  • Без пробелов в метках: пробелы и запятые считаются разделителями, поэтому «Room Temp:24» разбивается на две части; используй имена с подчёркиванием, например Room_Temp.
  • В строке только метки и числа: единицы измерения и описания вроде «Temp 24 C» портят график; описания для монитора порта выводи отдельным кодом.
  • Подбери частоту отправки: delay(20) — это примерно 50 строк в секунду. Если отправлять слишком быстро, график мгновенно уезжает с экрана; если слишком медленно, линия становится похожей на лесенку.
  • Посмотри на фильтр своими глазами: сырое значение и среднее на одном графике сразу показывают, насколько фильтр снижает шум; способы — в статье о фильтрации данных датчиков.
  • Для записи данных нужен другой инструмент: плоттер не сохраняет данные в файл. Для долгих измерений выводи значения строками CSV и импортируй их в Excel или читай на Python.

Приёмы отладки с монитором порта

В Arduino Uno нет отладчика, который проходит код по шагам; эту работу выполняет монитор порта. Но строки вывода, раскиданные по коду как попало, быстро превращаются в нечитаемую кучу текста. Эти привычки помогут находить ошибки гораздо быстрее:

  • Сначала выведи сообщение из setup(): если строка «Started» появляется один раз при каждом запуске платы, значит, плата работает. Если эта строка повторяется снова и снова, плата постоянно сбрасывается, и проблема скорее в питании, чем в коде; см. руководство «Arduino постоянно перезагружается».
  • Выводи значения вместе с именами: вместо голого 512 пиши temp=24.5 hum=40; потом не придётся вспоминать, какое число к чему относится.
  • Отмечай, где находишься: если программа где-то зависает, поставь перед важными шагами короткие метки вроде A, B, C. Последняя метка на экране покажет, где программа застряла.
  • Выводи при изменении, а не на каждом проходе: loop() выполняется тысячи раз в секунду. Вывод на каждом проходе заливает экран и замедляет программу; выводи значение только тогда, когда оно меняется, или раз в секунду с помощью millis().
  • Измеряй время: чтобы узнать, сколько длится операция, сохрани значение micros() до и после неё и выведи разницу. Так можно поймать медленное чтение датчика или слишком долгий цикл.
  • Не выводи ничего внутри прерываний: вывод в порт занимает время; Serial.print внутри функции прерывания без нужды затягивает её и может привести к пропуску других событий. В прерывании просто установи переменную-флаг, а вывод делай в loop(); подробности — в статье о прерываниях Arduino.
  • Спрячь отладочные строки за одним переключателем: с макросами ниже весь отладочный вывод полностью убирается из кода, если задать DEBUG равным 0; в готовом проекте это экономит и флеш-память, и время.
C++ (Arduino IDE)
1// Hata ayıklama makroları: tek satırla aç ya da kapat
2// Debug macros: switch all debug output on or off with one line
3#define DEBUG 1                  // 0 yaparsan debug satırları koddan çıkar / set to 0 to strip debug lines
4
5#if DEBUG
6  #define DBG(x)   Serial.print(x)
7  #define DBGLN(x) Serial.println(x)
8#else
9  #define DBG(x)
10  #define DBGLN(x)
11#endif
12
13const int SENSOR_PIN = A0;
14const int ESIK = 800;            // uyarı eşiği / warning threshold
15bool esikUstunde = false;        // son durum / last state
16unsigned long sonRapor = 0;
17unsigned long turSayisi = 0;     // saniyedeki loop() turu / loop() passes per second
18
19void setup() {
20  Serial.begin(115200);
21  DBGLN(F("Basladi / Started"));
22}
23
24void loop() {
25  turSayisi++;
26  int deger = analogRead(SENSOR_PIN);
27
28  bool simdi = deger > ESIK;
29  if (simdi != esikUstunde) {          // yalnız değişince yaz / print only on change
30    esikUstunde = simdi;
31    DBG(F("[esik / threshold] "));
32    DBG(simdi ? F("ustunde / above") : F("altinda / below"));
33    DBG(F(", deger / value = "));
34    DBGLN(deger);
35  }
36
37  if (millis() - sonRapor >= 1000) {   // saniyede bir özet / one summary per second
38    sonRapor = millis();
39    DBG(F("t = "));
40    DBG(sonRapor);
41    DBG(F(" ms, tur/s / loops/s = "));
42    DBGLN(turSayisi);
43    turSayisi = 0;
44  }
45}

Помни, что у вывода в порт есть цена. На Uno буфер передачи тоже 64 байта; когда он заполнен, Serial.print() ждёт, пока освободится место, и loop() замедляется. Это легко измерить счётчиком циклов в коде выше: если добавить строку из 25 символов, которая выводится на каждом проходе, на 9600 бод программа падает примерно до сорока проходов в секунду, а вывод только при изменении сохраняет тысячи. Чтобы точно знать, что плата вывела прямо перед зависанием или сбросом, вызывай Serial.flush() после критической строки: эта функция ждёт, пока всё содержимое буфера не будет отправлено. В более сложных программах переход на структуру конечного автомата и вывод одной строки только при смене состояния сильно упрощают поиск ошибок.

Вывод в порт на ESP32

На платах ESP32 DevKit Serial — это UART0 на GPIO 1 (TX) и GPIO 3 (RX), подключённый к USB через микросхему CP2102 или CH340 на плате. Обычная скорость — 115200 бод, потому что ПЗУ внутри чипа при загрузке тоже выводит на этой скорости строки вроде rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT); если держать монитор на 115200, эти строки тоже становятся читаемыми. Ядро ESP32 для Arduino предлагает Serial.printf() и поддерживает вывод float через %f. Дополнительные порты Serial1 и Serial2 можно назначить на любые пины; при подключении модуля часто используют Serial2 на GPIO 16 и 17. На платах со встроенным USB, например ESP32-S3 и ESP32-C3, если монитор порта остаётся пустым, переключи Инструменты › USB CDC On Boot на Enabled. Чтобы видеть собственные предупреждения ядра, выбери Error, Warn, Info, Debug или Verbose в меню Инструменты › Core Debug Level, а в своём коде можно выводить записи журнала с именем файла и номером строки макросами log_e, log_w и log_i.

C++ (Arduino IDE)
1// ESP32: Serial (USB), Serial2 (GPIO16/17), printf ve log
2// ESP32: Serial (USB), Serial2 (GPIO16/17), printf and logging
3const int POT_PIN = 34;              // ADC1, yalnız giriş pini / ADC1, input-only pin
4unsigned long sonYazdirma = 0;
5
6void setup() {
7  Serial.begin(115200);                       // açılış mesajlarıyla aynı hız / same rate as the boot messages
8  delay(500);
9  Serial2.begin(9600, SERIAL_8N1, 16, 17);    // RX = GPIO16, TX = GPIO17
10
11  Serial.printf("Model: %s, CPU: %lu MHz\n", ESP.getChipModel(), (unsigned long)ESP.getCpuFreqMHz());
12  Serial.printf("Bos RAM / Free heap: %lu bayt / bytes\n", (unsigned long)ESP.getFreeHeap());
13  log_i("Core Debug Level en az Info ise gorunur / shown when Core Debug Level is Info or higher");
14}
15
16void loop() {
17  while (Serial2.available() > 0) {           // Serial2'den geleni USB'ye aktar / forward Serial2 data to USB
18    Serial.write(Serial2.read());
19  }
20
21  if (millis() - sonYazdirma >= 1000) {       // delay() yok, tampon taşmaz / no delay(), no buffer overflow
22    sonYazdirma = millis();
23    float volt = analogReadMilliVolts(POT_PIN) / 1000.0;          // kalibre gerilim / calibrated voltage
24    Serial.printf("t=%lu ms, GPIO34=%.2f V\n", millis(), volt);   // ESP32'de %f çalışır / %f works on ESP32
25  }
26}

При проблемах с подключением к Wi-Fi, неожиданных сбросах и ошибках Guru Meditation монитор порта — первое место, куда нужно смотреть: здесь появляются тип ошибки, причина сброса и строки backtrace (трассировка стека). Как читать эти сообщения, мы объясняем в руководствах про brownout и Guru Meditation на ESP32 и «ESP32 не подключается к Wi-Fi». Если ESP32 в новинку, начни со статьи «Что такое ESP32?».

Вывод в порт на Raspberry Pi Pico: Arduino и MicroPython

У Raspberry Pi Pico нет микросхемы-моста USB–UART: RP2040 сама работает с USB и видна компьютеру напрямую как последовательный порт (USB CDC). В ядре Arduino-Pico от Earle Philhower Serial — это и есть этот USB-порт, и, поскольку данные идут на скорости USB, значение скорости в Serial.begin() игнорируется. Из этого следуют две вещи: какую скорость выбрать в мониторе, уже не важно, зато плата, в отличие от Uno, не сбрасывается при открытии монитора порта. Первые сообщения, выведенные в setup(), к моменту открытия монитора уже отправлены и потеряны, поэтому в коде стоит ненадолго подождать, пока компьютер откроет порт, как показано ниже. То же относится к платам на ATmega32U4, таким как Leonardo, Micro и Pro Micro. Настоящий UART у Pico на пинах GP0 и GP1 используется под именем Serial1.

C++ (Arduino IDE)
1// Raspberry Pi Pico (Arduino-Pico çekirdeği): USB seri port
2// Raspberry Pi Pico (Arduino-Pico core): USB serial port
3void setup() {
4  Serial.begin(115200);                  // USB'de hız değeri önemsiz / the baud value is ignored over USB
5  unsigned long baslangic = millis();
6  while (!Serial && millis() - baslangic < 3000) {
7    delay(10);                           // port açılana kadar en çok 3 sn bekle / wait up to 3 s for the port
8  }
9  Serial1.begin(9600);                   // UART0: GP0 = TX, GP1 = RX
10  Serial.println("Pico hazir / Pico ready");
11}
12
13void loop() {
14  Serial.print("Sicaklik / Temperature (C): ");
15  Serial.println(analogReadTemp(), 1);   // dahili sensör, yaklaşık / internal sensor, approximate
16  Serial1.println(millis());             // aynı anda UART0'a da yaz / also write to UART0
17  delay(1000);
18}

В MicroPython всё ещё проще: всё, что выводится через print(), уходит в компьютер по USB и появляется в окне Shell в Thonny; скорость в бодах настраивать не нужно. Если открыть в Thonny меню Вид › Плоттер (View › Plotter), числа в строках превращаются в живой график — как в плоттере Arduino IDE. Для приёма данных с компьютера можно использовать input(), но input() останавливает программу до прихода строки. Чтобы слушать команды и при этом продолжать измерения, проверяй через select.poll(), ждут ли данные в stdin, как показано ниже; достаточно набрать on или off в Shell и нажать Enter. Pico и установку MicroPython мы разобрали в статье «Что такое Raspberry Pi Pico?».

MicroPython
1# Pico MicroPython: print() ile yazdır, programı durdurmadan komut al
2# Pico MicroPython: print with print(), read commands without blocking
3import sys
4import select
5import time
6from machine import ADC, Pin
7
8led = Pin(25, Pin.OUT)            # Pico'da dahili LED; Pico W'de Pin("LED") / onboard LED; Pin("LED") on Pico W
9sensor = ADC(4)                   # dahili sıcaklık sensörü / internal temperature sensor
10poll = select.poll()
11poll.register(sys.stdin, select.POLLIN)
12
13while True:
14    volt = sensor.read_u16() * 3.3 / 65535
15    sicaklik = 27 - (volt - 0.706) / 0.001721
16    print("Sicaklik:{:.1f}".format(sicaklik))    # Thonny Plotter da çizer / Thonny's Plotter can graph it
17    if poll.poll(0):                              # bekleyen veri var mı? / is input waiting?
18        komut = sys.stdin.readline().strip()      # satırı al, boşlukları sil / read the line, strip spaces
19        if komut == "on":
20            led.value(1)
21        elif komut == "off":
22            led.value(0)
23        print("Komut / Command:", komut)
24    time.sleep(1)

Монитор порта пуст или порт занят: частые ошибки и решения

СимптомПричинаРешение
Экран совсем пустойНет Serial.begin() или выбран не тот портДобавь Serial.begin() в setup(), выбери правильный порт в списке выбора платы
«Not connected. Select a board and a port to connect automatically» (не подключено, выбери плату и порт)В IDE не выбран порт или плата отключена от компьютераПодключи плату и выбери её в меню Инструменты › Порт
«Serial port busy» или «Access is denied» (порт занят, доступ запрещён)Порт открыт в другом окне IDE, в PuTTY или в скрипте на PythonЗакрой программу, которая держит порт; порт одновременно может открыть только одна программа
Нет первых строк на Leonardo или PicoПлата со встроенным USB не сбрасывается при открытии монитораНенадолго подожди в setup() с помощью while (!Serial)
На ESP32-S3/C3 ничего не появляетсяUSB CDC On Boot выключен, вывод идёт на пины UARTИнструменты › USB CDC On Boot: Enabled
После каждого числа появляется 0parseInt ждёт до таймаута из-за символа конца строкиПропускай нецифровые символы или выбери No Line Ending
Сравнение команд никогда не срабатываетВ конце текста остаётся '\r'Применяй trim() после чтения
Порт так и не появляется в спискеКабель только для зарядки или не установлен драйвер CH340/CP210xИспользуй кабель с передачей данных, установи драйвер
На Uno, Nano и Mega плата сбрасывается каждый раз при открытии монитора порта: линия DTR USB-микросхемы подключена к пину RESET через конденсатор, и эта схема на самом деле нужна для удобной загрузки кода. Чаще всего это даже помогает, ведь сообщения из setup() не теряются. Если в готовом проекте, который общается с программой на компьютере, сброс не нужен, можно поставить конденсатор 10 µF между RESET и GND; только не забудь убрать его перед загрузкой. Если порт вообще не появляется, решения с драйвером и кабелем есть в руководстве «Ошибка загрузки Arduino».

Идеи проектов с последовательной связью

  • Светодиод и реле с управлением с компьютера: расширь парсер команд из этой статьи, чтобы управлять реле или мотором командами on, off и blink.
  • Регистратор данных датчика: выводи температуру и влажность строками CSV через запятую и превращай их на компьютере в таблицу и график; для начала хорошо подходит датчик DHT11.
  • Живой график освещённости: следи за значением фоторезистора (LDR) или потенциометра в плоттере и смотри действие фильтра на том же графике.
  • Настольная панель на Python: читай данные, которые отправляет Arduino, программой на Python и сделай интерфейс с кнопками; подробности — в статье о связи Arduino и Python по последовательному порту.
  • Стенд для проверки модулей: модули GPS, Bluetooth и GSM тоже работают через последовательный порт; перенаправь сырые данные модуля в монитор порта, чтобы проверить, работает ли он (HC-05, GPS NEO-6M).
  • Попробовать без платы: если платы под рукой нет, в Tinkercad тоже есть монитор порта; код из этой статьи можно запустить в симуляции Tinkercad.

Когда монитор порта станет привычным, поиск ошибок в Arduino перестанет быть гаданием и превратится в измерение. Следующим шагом прочитай статью о последовательной связи UART, чтобы связать две платы по TX и RX, и статью о преобразователе USB-TTL, чтобы программировать платы без USB. В приложении BlueGrays есть проекты, где расстояние, измеренное HC-SR04, выводится в монитор порта, освещённость считывается фоторезистором (LDR), температура и влажность измеряются DHT11, а две Arduino связываются по последовательной линии, — пошагово, со схемами подключения и готовым кодом.

Частые вопросы

Как открыть монитор порта Arduino?

После загрузки кода нажми на значок лупы в правом верхнем углу Arduino IDE, выбери меню Инструменты › Монитор порта или нажми Ctrl+Shift+M. В IDE 2 монитор открывается на нижней панели. В списке выбора платы должен быть выбран правильный порт, а значение в меню скорости справа должно совпадать со значением Serial.begin() в коде.

Почему в мониторе порта мусорные символы?

Самая частая причина — скорость в мониторе порта отличается от значения Serial.begin() в коде; как только они совпадут, текст станет читаемым. Если искажена только одна строка при старте, это собственное загрузочное сообщение ESP8266 или ESP32, и это нормально. Такой же симптом дают неверно выбранная тактовая частота на платах вроде Pro Mini или модуль, подключённый к пинам D0/D1.

Какую скорость выбрать для Arduino: 9600 или 115200 бод?

Работают обе; главное, чтобы в коде и в мониторе порта была одна и та же скорость. 9600 бод передают около 960 символов в секунду, и для простых проектов на Uno этого достаточно. Если выводится много данных или используется ESP32 или Pico, выбирай 115200: эта скорость в 12 раз выше, и вывод меньше замедляет программу.

Чем отличаются Serial.print и Serial.println?

Serial.print() выводит значение и оставляет курсор на той же строке; Serial.println() после вывода добавляет символы возврата каретки и перевода строки (\r\n). Чтобы показать несколько значений в одной строке, сначала используй print, а в конце println. Кроме того, строку нужно завершать вызовом println, чтобы плоттер считал каждую строку отдельным отсчётом.

Почему в мониторе порта ничего не отображается?

Сначала проверь, что в setup() есть Serial.begin() и что в списке выбора платы выбран правильный порт. На платах со встроенным USB, таких как Leonardo, Pico и ESP32-S3, первые сообщения из setup() могут уйти и потеряться ещё до открытия монитора; добавь короткое ожидание с помощью while (!Serial). На ESP32-S3 и C3 в меню «Инструменты» также должна быть включена настройка USB CDC On Boot (Enabled).

Почему Arduino перезагружается при открытии монитора порта?

На Uno, Nano и Mega линия DTR USB-микросхемы подключена к пину RESET через конденсатор; при открытии порта на этой линии появляется короткий импульс, и плата перезапускается. Эта схема нужна для удобной загрузки кода и гарантирует, что сообщения из setup() не потеряются. Если это не нужно, можно поставить конденсатор 10 µF между RESET и GND, но перед загрузкой его нужно убрать.

Все проекты
Измерение расстояния HC-SR04 (вывод в порт)
#27Новичок

Измерение расстояния HC-SR04 (вывод в порт)

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

4.6(46)
71
Измерение освещённости с LDR
#11Новичок

Измерение освещённости с LDR

Хочешь увидеть яркость своей комнаты в виде числа? LDR — маленькая деталь, реагирующая на свет, — измеряет освещённость вокруг, а Arduino каждые полсекунды выводит значение на экран компьютера. Закрой

4.6(81)
139
Температура и влажность с DHT11
#55Средний

Температура и влажность с DHT11

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

4.7(47)
73
Измерение освещённости модулем LDR на ESP32
#113Новичок

Измерение освещённости модулем LDR на ESP32

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

4.6(36)
55
Чтение кодов ИК-пульта
#16Новичок
Premium

Чтение кодов ИК-пульта

Каждая кнопка на пульте от телевизора отправляет секретный код светом, которого ты не видишь. ИК-приёмник ловит этот свет, а Arduino выводит на экран компьютера код каждой нажатой кнопки. Узнав эти ко

4.5(39)
55
Чтение значения RFID
#53Новичок
Premium

Чтение значения RFID

В брелоке от подъезда или карте доступа спрятан секретный идентификационный номер, и этот проект его читает. Поднеси карту к считывателю RFID, и Arduino выведет номер карты на экран компьютера. Первый

4.7(47)
61
Односторонняя связь между двумя Arduino
#57Средний
Premium

Односторонняя связь между двумя Arduino

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

4.4(37)
50
Двусторонняя связь между двумя Arduino
#58Средний
Premium

Двусторонняя связь между двумя Arduino

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

4.5(20)
26