Мой первый вечер с Leonardo я помню как допрос с пристрастием: залил скетч с приветствием в setup(), открыл монитор — пусто. Передёрнул кабель — на мгновение мелькнуло, пропало. Перезалил — снова пусто, но иногда текст всё-таки успевал показаться. На Uno такой номер не проходил: там Serial работал всегда. Через час я понял: скетч стартует и печатает приветствие раньше, чем программа на компьютере успевает открыть порт, — и текст уходит в никуда. Leonardo не врёт, она живёт по другим правилам USB CDC, и в этом уроке мы их разберём вместе с ESP32, у которой serial-обвязка шире одного вызова Serial.begin.
План такой. Сначала Leonardo: почему у неё Serial — это USB, а UART на пинах 0 и 1 зовётся Serial1, почему скорость из begin игнорируется и зачем нужен while (!Serial). Потом ESP32: три аппаратных UART, Serial2 на пинах 16 и 17 и boot-лог, который сыплется на 115200 при каждом старте. В коде соберём маршрут: команды с ноутбука уезжают на второй порт, ответы датчика возвращаются в консоль, — а в симуляторе прогоним ту же логику с честной оговоркой: Serial2 движок не умеет, зато WiFi-часть там настоящая.
Leonardo: Serial — это USB, а UART зовут Serial1
На Uno между скетчем и компьютером стоит переводчик — USB-мост вроде CH340: скетч пишет в аппаратный UART, мост перегоняет байты в USB. На Leonardo моста нет: процессор ATmega32U4 умеет USB сам, и Serial — виртуальный COM-порт стандарта CDC, поднятый прямо в чипе. Следствия расползаются далеко. Скорость из Serial.begin() на USB-части игнорируется: пишите хоть 9600, хоть 1 — в монитор поедет одно и то же, потому что темпом управляет сам протокол USB. Пины 0 и 1 при этом не пропали: за ними закреплён настоящий UART, и доступ к нему называется Serial1 — через него Leonardo разговаривает с внешними модулями по-старому, крест-накрест, как мы соединяли платы в уроке про связь двух Arduino.
Вторая странность — автосброс. IDE перед заливкой дёргает виртуальный порт на 1200 бод, и Leonardo перезапускается; то же случается при открытии монитора. Поэтому переменные обнуляются, а ранние println из setup() пропадают: порт ещё не открыт. Классическое лечение — подождать хоста: while (!Serial) держит скетч, пока порт не откроют. У приёма есть граница, на которой я обжигался: если Leonardo питается от сети без компьютера, while (!Serial) держит скетч вечно. Живой вариант — ждать с таймаутом: строка while (!Serial && millis() < 5000) { } даёт пять секунд на открытие порта, потом скетч работает без консоли. С Serial1 такой логики не нужно: аппаратный UART живёт без хозяина, как и на Uno.
| Плата | Serial это | Внешний UART | Скорость |
|---|---|---|---|
| Uno | аппаратный UART через USB-мост CH340 | пины 0 и 1 — тот же Serial | настоящая: что в begin, то и в мониторе |
| Leonardo | виртуальный COM (USB CDC) в самом 32U4 | пины 0 и 1 — Serial1 | на USB игнорируется, на Serial1 настоящая |
| ESP32 | UART0 через мост CP2102, он же загрузчик | Serial2 на пинах 16 и 17 | настоящая; boot-лог всегда на 115200 |
ESP32: три UART и Serial2 на пинах 16 и 17
У ESP32 три аппаратных UART, и роли раздаются так. UART0 ведёт на USB-мост и обслуживает загрузку: поэтому при старте платы в монитор на 115200 бод сыплется boot-лог — если у вас мусор в первых строках после перезагрузки, это не сбой, а отчёт загрузчика. UART1 по умолчанию занят флеш-чипом: его не трогаем. А вот UART2 на типовых DevKit-платах выведен на пины 16 (RX2) и 17 (TX2) — подарок: второй полноценный порт без всякого SoftwareSerial, аппаратный, со своим буфером на 64 байта — тем же, что мы разбирали в уроке про буфер UART. Я повесил на него GPS-модуль на 9600 и забыл про классическую боль Uno, где второй порт приходилось рисовать программно из урока про SoftwareSerial.
Код статуса WiFi стоит запомнить хотя бы в двух значениях. Тройка — WL_CONNECTED: подключено, можно открывать соединения. Шестёрка — WL_DISCONNECTED: ещё нет, статус после WiFi.begin без сети так и останется шестёркой. На реальной плате соединение занимает секунды, и наивный скетч, который лезет в сеть сразу после begin, промахивается мимо; живой паттерн — проверять status в loop и ждать тройку с таймаутом по millis(), как мы ждали монитор у Leonardo. В симуляторе всё то же самое, только быстрее: begin занимает секунду с лишним.
Открывается порт одной строкой: Serial2.begin(9600, SERIAL_8N1, 16, 17) — скорость, формат, RX, TX. И это не жёсткая привязка: матрица GPIO у ESP32 назначает UART почти на любые пины, Serial2.begin(9600, SERIAL_8N1, 25, 26) тоже законно. Мой режим по умолчанию: Serial на 115200 — мост CP2102 тянет легко, а Serial2 — на той скорости, что просит модуль, чаще всего 9600. Следите за уровнями: ESP32 ждёт на RX 3,3 вольта, и пятивольтовый TX от Uno для него много — делитель из резисторов 1 кОм и 2 кОм спасает.
WiFi на ESP32 — соседняя песня, но для урока хватит трёх вызовов. WiFi.begin(имя, пароль) стартует подключение, WiFi.status() отдаёт код состояния, а WiFi.localIP() — адрес в сети, который плата получила. В симуляторе все три живут честно: после подключения статус печатается тройкой, адрес — цифрами через точку, и на реальной плате тот же код работает слово в слово; с него мы и начинали знакомство с разделом ESP32 в уроке про первый WiFi.
// ESP32: консоль на Serial (USB-мост), датчик на Serial2 (пины 16 и 17).// Уровни: RX2 ждёт 3.3 В — от пятивольтовой платы ставьте делитель.String zapros = ""; void setup() { Serial.begin(115200); // мост CP2102 тянет легко Serial2.begin(9600, SERIAL_8N1, 16, 17); // RX2=16, TX2=17 Serial.println("ESP32 gotov: pishite zapros datchiku");} void loop() { if (Serial.available() > 0) { // команды с ноутбука String cmd = Serial.readStringUntil(10); cmd = cmd.trim(); if (cmd.length() > 0) { Serial.print("USB> "); Serial.println(cmd); Serial2.println(cmd); // уехало в датчик по второму порту } } if (Serial2.available() > 0) { // ответы датчика — в консоль String otvet = Serial2.readStringUntil(10); Serial.print("2> "); Serial.println(otvet); }}В модели ниже роли разнесены префиксами: USB> — то, что вы послали с ноутбука, 2> — ответы датчика, который на реальной плате сидел бы на пинах 16 и 17. Логика маршрута та же, что в скетче выше: незнакомые консоли строки уходят датчику, датчик отвечает, консоль печатает. Команда wifi запускает WiFi.begin — в журнале пролетает системная строка о подключении, stat печатает код статуса, ip — адрес. Команды ping, temp и who адресуются датчику: ping отвечает понгом с меткой времени, temp — парой temp= с шумом в пару градусов, who — именем. Строку, которую консоль не знает, модель передаёт датчику без раздумий — так маршрутизируют и на настоящей ESP32: консольным командам своя ветка, транзиту своя.
На что смотреть: скорость здесь 115200 — как у настоящего моста CP2102. Если бы движок умел Serial2, между моделью и платой была бы ровно одна строка разницы: Serial2.begin(9600, SERIAL_8N1, 16, 17) вместо второго чтения из Serial. Всё остальное — разбор строки, ответы, статусы — переносится на плату без правок. Монитор порта держите на 115200: иначе boot-лог ESP32 превратится в кашу, и вы перепутаете отчёт загрузчика с битой посылкой.
- Leonardo: Serial — виртуальный USB CDC в самом 32U4, скорость из begin игнорируется; внешний UART — Serial1 на пинах 0 и 1
- Открытие монитора перезапускает Leonardo, ранние println из setup() пропадают — ждите хост через while (!Serial) с таймаутом по millis()
- ESP32: три UART; UART0 — мост и загрузчик, UART1 занят флешем, Serial2 выведен на 16 (RX2) и 17 (TX2)
- Serial2.begin(9600, SERIAL_8N1, 16, 17): скорость, формат, RX, TX — пины переназначаются матрицей GPIO
- ESP32 — плата 3,3 вольта: TX от пятивольтовой Uno гасится делителем 1 кОм и 2 кОм; boot-лог всегда идёт на 115200
- 1. На Leonardo скорость в Serial.begin(9600) для монитора порта...
- 2. Как на Leonardo обратиться к настоящему UART на пинах 0 и 1?
- 3. На каких пинах типового ESP32 DevKit выведен второй аппаратный порт?
- 4. В первых строках монитора ESP32 после старта мелькает мусор. Что это?
- 5. TX пина Uno (5 вольт) соединили напрямую с RX2 ESP32. Последствия?
Частые вопросы по теме урока
- На Leonardo скорость в Serial.begin(9600) для монитора порта...
- USB CDC — виртуальный порт: темпом управляет протокол USB, поэтому число в begin на USB-части Leonardo ни на что не влияет. На Serial1 скорость настоящая. игнорируется: темпом USB управляет сам протокол CDC
- Как на Leonardo обратиться к настоящему UART на пинах 0 и 1?
- На платах с USB CDC имя Serial занято виртуальным портом, а аппаратный UART на пинах 0 и 1 доступен как Serial1. Serial1
- На каких пинах типового ESP32 DevKit выведен второй аппаратный порт?
- UART2 выведен на GPIO 16 и 17; матрица GPIO при желании переназначает его на другие пины через Serial2.begin со всеми четырьмя аргументами. 16 (RX2) и 17 (TX2)
- В первых строках монитора ESP32 после старта мелькает мусор. Что это?
- UART0 обслуживает загрузку, и отчёт загрузчика всегда идёт на 115200. Держите монитор на этой скорости — и мусор в начале превращается в читаемый лог. Boot-лог загрузчика на 115200 — штатный отчёт
- TX пина Uno (5 вольт) соединили напрямую с RX2 ESP32. Последствия?
- ESP32 — трёхвольтовая плата: 5 вольт на RX вне спецификации. Делитель из резисторов 1 кОм и 2 кОм опускает уровень до безопасных 3,3 В. ESP32 ждёт 3,3 вольта: вход под 5 В под риском, нужен делитель