Осенью я повесил на кухне датчик температуры и протянул сигнал в комнату восьмиметровым проводом — старой телефонной лапшой из кладовки. На столе всё разговаривало безупречно, а на кухонной линии примерно раз в сорок секунд в логе всплывала строка вида t=2#7 hum=41: один-два байта из двадцати четырёх приезжали битыми. Три вечера я пересобирал датчик, менял библиотеку, перешивал плату — а виноваты были провод, скорость и моя привычка держать 115200 везде, где влезет. Этот урок про то, как я потом нашёл виновника за двадцать минут, и про трёх подозреваемых, которых надо допрашивать первыми: земля, скорость, провода. Список не случайный: в девяти глючных линиях из десяти, которые я разбирал после, виновник жил в этой тройке, а не в скетче.
Договоримся о карте. Сначала разберём, как физически ломается байт: без общего GND приёмнику не от чего мерить напряжение, а каждая наводка от реле или блока питания вырывает бит из посылки. Потом — методика диагностики: пять шагов от перемычки до причины, которые я теперь делаю по порядку, и почти всегда виновник находится на втором. Соберём loopback-тестер на SoftwareSerial из урока про второй порт, а в конце сломаем линию руками в симуляторе: модель позволяет выкручивать землю, длину и скорость и смотреть, что каждая беда делает с посылкой.
Земля, скорость, провод: как именно портится байт
Механика простая, и я жалею, что не врезал её в голову раньше. Линия UART в покое держит высокое напряжение: передатчик молчит — на проводе 5 вольт (у ESP32 — 3,3). Стартовый бит роняет линию в ноль, дальше идут восемь битов данных, стоповый бит возвращает высокое. Приёмник замеряет провод в середине каждого бита. Если общего GND нет, приёмнику не с чем сравнивать: он меряет напряжение относительно своего локального нуля, а тот плывёт вместе с потреблением платы. Итог — не отказ связи, а случайные нули и единицы: приёмник честно собирает байты из воздуха. Крест-накрест и третий провод из урока про связь двух плат — не рекомендация, а физическое условие работы канала.
Скорость бьёт по каналу иначе. Байт в UART — это десять битов: старт, восемь данных, стоп. На 9600 бод бит стоит 104 микросекунды, байт — около миллисекунды. На 115200 бит уже 8,7 мкс, и наводке от щёлкнувшего реле достаточно растянуться на пару микросекунд, чтобы приёмник прочитал чужой ноль там, где ждала единица. Моё правило с границей: всё, что короче полуметра на макетке, живёт на 115200 без вопросов; всё, что длиннее полуметра, — 9600; всё, что дальше трёх метров, — 9600 плюс скрутка сигнальных проводов между собой, чтобы наводка доставалась обеим жилам одинаково. От десяти метров и дальше я UART уже не тяну: там начинается территория RS-485 с модулями MAX485, но это отдельная история.
Отдельный вид мусора — рассинхрон скоростей, и его стоит уметь отличать от наводок. Если стороны договорились о разных темпах, приёмник заглядывает на линию в чужие моменты и собирает бессмысленные байты подряд — в мониторе это сплошная каша с первого же байта. Наводки выглядят иначе: девяносто процентов строк чистые, и лишь изредка в середине слова прорастает знак вопроса. Я разделяю их так: каша сплошняком — скорости; редкие битые байты — линия или земля. И помните про сторону компьютера: выпадающий список скоростей в мониторе проверяется за две секунды, поэтому начинать стоит именно с него.
| Симптом | Диагноз | Лечение |
|---|---|---|
| Мусор раз в N секунд, скорость 115200 | наводки на длинном проводе: бит слишком короткий | снизить до 9600, скрутить провода, укоротить линию |
| Лог грязнеет при каждом щелчке реле | силовая цепь лежит рядом с сигнальной | развести провода, реле питать отдельно, GND не гнать через силовой |
| Приёмник молчит совсем, байтов нет | нет общего GND или TX встречен с TX | третий провод на землю, проверка крест-накрест |
| Мусор даже на столе, когда трогаешь макетку | RX висит в воздухе: второй конец отцеплён | вернуть конец на место или подтяжка резистором 10 кОм на GND |
Диагностика: пять шагов от перемычки до причины
Порядок, который я выстрадал. Шаг первый — сверить скорости: значение в Serial.begin с одной стороны и настройка монитора с другой; кракозябры из первого урока в девяти случаях из десяти растут отсюда. Шаг второй — loopback: передатчик соединяется с приёмником перемычкой, тестер шлёт слово и сверяет то, что вернулось. Эхо чистое — обе половины вашей платы здоровы, виновата линия или вторая сторона. Шаг третий — мультиметр между GND двух плат: должно быть почти ноль вольт. Я однажды нашёл между землями 1,8 вольта из-за зарядки в другом конце квартиры — вот и весь мусор.
Шаг четвёртый — укоротить линию до тридцати сантиметров. Самый быстрый разделитель: на коротком проводе мусор пропал — беда в линии, длина и наводки; не пропал — беда в настройках или железе. Шаг пятый — снизить скорость вдвое и повторить посылки: битые строки исчезли — вы держали скорость, которую линия не тянет. Пять шагов занимают меньше времени, чем один вечер перепрошивок наугад. Когда мусор всё-таки доезжает, его ловят контрольной суммой — мы это собирали в уроке про фрейм и CRC; здесь же цель другая: убрать причину, а не обнаруживать следствие.
Отдельная ремарка про диагностику пачками. Буфер приёма на Uno — 64 байта, и когда скетч занят delay или длинным циклом, входящие строки копятся в нём; переполнение выбрасывает новые байты мимо. Симптом напоминает глючную линию: обрезанные слова, полстроки, пропуски. Отличение простое: мусор от линии случаен по содержанию, а обрезка от буфера систематична — у неё страдает хвост длинной строки. Если тестируете длинными посылками на загруженном скетче, сначала проверьте, успевает ли loop вычерпывать порт: про очередь на 64 байта и вычерпывание мы подробно говорили в уроке про буфер UART.
#include <SoftwareSerial.h>// Перемычка: пин 4 (TX) соединить с пином 5 (RX).// Раз в секунду уходит слово, тестер сверяет эхо с оригиналом.SoftwareSerial link(5, 4); // RX, TX int itogo = 0;int bitih = 0; void setup() { Serial.begin(9600); link.begin(9600); Serial.println("Loopback test: zhdu otveti");} void loop() { String slovo = "PING " + String(itogo); link.print(slovo); link.print(10); // перевод строки — терминатор delay(50); // 6 байтов на 9600 — около 6 мс, ждём с запасом String elho = ""; if (link.available() > 0) { elho = link.readStringUntil(10); } itogo = itogo + 1; if (elho == slovo) { Serial.print("OK t="); Serial.println(itogo); } else { bitih = bitih + 1; Serial.print("BITOE t="); Serial.print(itogo); Serial.print(" elho="); Serial.println(elho); Serial.print("bitih vsego="); Serial.println(bitih); } delay(1000);}В симуляторе линия тоже модельная: движок не гоняет байты по медному проводу, зато честно воспроизводит зависимость порчи от условий. Команда test SLOVO отправляет слово через модельную линию: в журнал уходят две строки — TX> то, что отправлено, RX> то, что получил приёмник. Порча в модели вероятностная: у каждого символа есть шанс превратиться в вопросительный знак, и шанс растёт с длиной провода и скоростью — ровно как у настоящего бита, которому наводке нужно поймать только середину.
Покрутите три беды руками: gnd 0 убирает общий провод — и портиться начинает почти всё; dlina 10 растягивает линию до кухонной; skorost 115200 укорачивает бит до 8,7 микросекунды. Светодиод на пине 12 — лампочка проверки: горит, когда эхо совпало, гаснет на битой посылке. Команда diag печатает текущие условия и вердикт — ту логику, которой вы только что пользовались глазами. Числа идут парами имя=значение, так что ряд shans= удобно наблюдать в плоттере: включите dlina 10 и увидите, как шанс порчи подпрыгивает.
Модель вероятностная, и в этом её честность: настоящая наводка не обещает испортить ровно каждую вторую посылку. Две команды test подряд при одних условиях могут дать разные результаты — так и на реальной линии: мусор приходит волнами, вслед за включённым паяльником или компрессором холодильника. Поэтому смотрите не на единичный прогон, а на частоту: отправьте test раз пять и посчитайте битые. Три из пяти при dlina 10 — линия ваша вина; ноль из пяти при чистых условиях — ищите вторую сторону.
- Байт в UART — десять битов: на 9600 это около миллисекунды, на 115200 — 87 микросекунд; чем выше скорость, тем меньше наводке нужно поймать
- Без общего GND канала нет: приёмник меряет напряжение относительно плывущего нуля и собирает байты из воздуха
- Порядок допроса: скорости с двух сторон, loopback-перемычка, мультиметр между землями, укорочение линии до 30 см, снижение скорости вдвое
- Моё правило: до полуметра — 115200 можно, дальше — 9600, дальше трёх метров — скрутка; от десяти метров — RS-485
- Висящий RX — антенна: держите конец линии на месте или притягивайте резистором 10 кОм на GND
- 1. Приёмник получает кашу из символов, а платы соединены одним сигнальным проводом TX–RX. Первое подозрение?
- 2. Сколько времени занимает один байт на линии 9600 бод?
- 3. Loopback-тест показал чистое эхо, но с удалённой платой связь грязная. Где искать?
- 4. Мусор в логе появляется при каждом щелчке реле. Что делать первым делом?
- 5. RX-пин висит в воздухе: второй конец линии отцеплён. Что будет?
Частые вопросы по теме урока
- Приёмник получает кашу из символов, а платы соединены одним сигнальным проводом TX–RX. Первое подозрение?
- Один сигнальный провод без третьего — классика: напряжение меряется относительно плывущего нуля, и приёмник собирает байты из случайных нулей и единиц. Нет общего GND: приёмнику не от чего мерить напряжение
- Сколько времени занимает один байт на линии 9600 бод?
- Кадр — старт, восемь данных, стоп: десять битов. На 9600 бод бит стоит 104 мкс, байт — примерно 1,04 мс; 87 мкс — это бит на 115200. Около миллисекунды: десять битов по 104 микросекунды
- Loopback-тест показал чистое эхо, но с удалённой платой связь грязная. Где искать?
- Чистое эхо оправдывает обе половины вашей платы: передатчик и приёмник здоровы. Остаются линия — длина и наводки — и вторая сторона с её землёй. В линии и второй стороне: длина, наводки, общий GND
- Мусор в логе появляется при каждом щелчке реле. Что делать первым делом?
- Реле — источник наводок: разнесите цепи, а коротким битам на 115200 оставьте только короткие чистые линии. На 9600 тот же щелчок чаще всего проходит незамеченным. Развести сигнальный и силовой провода и понизить скорость
- RX-пин висит в воздухе: второй конец линии отцеплён. Что будет?
- Висящий вход собирает наводки от рук, сетевых проводов и паяльника. Конец линии либо подключают обратно, либо притягивают резистором 10 кОм к GND. RX ловит наводки как антенна: в мониторе случайные символы