Два датчика на одном графике слились в паутину, легенда назвала линии «1» и «2», а на пятом запуске они тихо поменялись местами — я полчаса читал график вверх ногами и делал выводы про «деградацию» сенсора, который был совершенно здоров. Урок обошёлся в вечер и одно правило: плоттер — это разборщик строк, и пока вы не договорились с ним о формате, он рисует свою версию реальности.
Базовый формат пар и первый двухрядный график уже были в уроке про Serial Plotter, бюджет кадров от бодов и ловушки autoscale — в уроке про плоттер как осциллограф. Здесь — механика разборщика: как строка делится на серии, откуда берутся подписи, почему легенда иногда едет и как удержать ряды разной природы в одном масштабе. Это те три вещи, из-за которых график читается сам, а не после получаса расшифровки.
Одна строка — один такт
Правило разборщика одно, и из него следует всё остальное: перевод строки завершает кадр. Всё, что скетч напечатал между двумя println, плоттер рисует как один такт времени — если вы пускаете три значения отдельными Serial.print без перевода строки, они слипнутся в одну точку кадра, а не в три ряда. Внутри строки значения разделяются пробелом, табом или запятой; голые числа — например 512, 480, 1 — поедут рядами без имён, привязанными к позиции: первое число — первый ряд, второе — второй. Пары имя:значение дают подписанные линии, имена видны в легенде справа; в документации формат с двоеточием, в живых примерах и старых уроках встречается вариант со знаком равенства — свежие сборки переваривают оба, но проверьте на своей: если подпись не всплыла, смените разделитель. Числа бывают отрицательные и дробные — println(x, 2) печатает два знака после запятой, и плоттер это ест.
Строки без чисел разборщик игнорирует молча — заголовок прогона вроде «start test» безопасен. А вот строка с числом посреди текста рождает мусорный ряд: печать «pot=512» склеенная с чем-то ещё, где разборщик найдёт лишнюю цифру, добавит линию, которой не существует. Отсюда дисциплина кадра: один println на такт, внутри только пары, между кадрами — ничего. Стоп-кадр делается не кнопкой, а условием в скетче: выключили печать — график замер, включили — поехал дальше, и никакая прокрутка не нужна.
Отсюда же — поведение кривой при срыве датчика. Библиотека вернула nan, скетч напечатал строку t=nan — для разборщика это текст без числа, кадр пропущен, и ряд честно рвётся: на графике разрыв, а не яма до нуля. Подстановка нуля вместо пропуска — худший вариант: провал до нуля на кривой комнатной температуры выглядит как заморозок посреди июля. Разрыв — самая честная запись о пропуске, и ради неё не жалейте пустых кадров.
Безымянные ряды — главная ловушка легенды: они привязаны к позиции в кадре, и стоит набору рядов смениться от кадра к кадру — скажем, датчик отключили и вы печатаете два значения вместо трёх, — как линии перетасовываются по позициям, и «первый ряд» внезапно показывает другого датчика. Именованные пары привязаны к подписи: ряд «pot» останется «pot», даже когда соседей нет. Моё правило после вечера с перетасованными линиями: имена короткие, три-шесть символов, и одинаковые от проекта к проекту — рука перестаёт сверяться с легендой глазами, она уже знает, где чья кривая.
Те же пары — рабочая валюта монитора: подстрока в поле фильтра выцепляет свою серию из плотного потока мгновенно, о чём говорилось в разборе панели монитора IDE 2. Один формат на оба окна — монитор для чтения, плоттер для взгляда — экономит отдельный протокол отладки. Печать кадров по таймеру на millis, а не в каждый проход loop, я держу и здесь: равномерные кадры — ровная ось времени, а как устроен такой таймер, показывал в уроке про millis вместо delay.
Цвета — отдельная история: палитру плоттер раздаёт сам, по порядку появления рядов, и ни красный, ни зелёный не приклеены к вашему датчику. Переставили серии в строке — цвета поехали за ними, и старый скриншот с «зелёным температурным» больше не подтверждение, а путаница. Моё лекарство: не расшифровывать палитру глазами, а читать легенду и подписи, а для презентаций печатать ряды в осмысленном порядке и не менять его между прогонами. Спорить с палитрой средствами Arduino бесполезно: у разборщика свой рендер, и он не спрашивает разрешения.
Дисциплина набора рядов, которая экономит вечера: добавляйте по одному. Один ряд — смотрите, дышит ли масштаб; второй — сверяете легенду; третий — проверяете, что флаговая полка не расплющила соседей. Три серии, запущенные разом в первом прогоне, рисуют три картинки сразу, и искать, чья кривая уехала, приходится по всем. По одному — и виновник находится мгновенно, без журнала расследований; к четвёртому ряду вы уже знаете, как ведёт себя каждый.
// Potenciometr A0, knopka D2 k GND. Kadr raz v 40 ms po millis.// Format "imya: znachenie" - tot zhe, kotoryj chitaet Serial Plotter.unsigned long t = 0;int smooth = 0; void setup() { Serial.begin(115200); pinMode(2, INPUT_PULLUP);} void loop() { if (millis() - t >= 40) { t = millis(); int raw = analogRead(A0); smooth = (smooth * 7 + raw * 3) / 10; // sglazhivayushchiy ryad int pot = map(raw, 0, 1023, 0, 100); // vse rjady v odnom masshtabe int sm = map(smooth, 0, 1023, 0, 100); int knop = 0; if (digitalRead(2) == LOW) { knop = 100; } // flag: 0 ili 100 Serial.print("pot: "); Serial.print(pot); Serial.print(" sm: "); Serial.print(sm); Serial.print(" knop: "); Serial.println(knop); }}| Формат строки | Что нарисует плоттер | Когда применять |
|---|---|---|
| одно голое число | один безымянный ряд по позиции | одиночный сигнал, охота за формой |
| числа через пробел | несколько безымянных рядов | быстрый набросок из трёх значений |
| пары имя: значение | подписанные ряды с легендой | постоянный мониторинг двух-четырёх серий |
| текст без чисел | ничего: строка игнорируется | заголовки и пометки прогона безопасны |
Масштаб: общий план для всех рядов
Autoscale честно вместит всё окно, но кривые разной природы он уравнять не может: сырое АЦП гуляет в 0-1023, выход ШИМ живёт в 0-255, температура лежит в -10..40 — и мелкий ряд тонет у нуля, пока большой занимает весь экран. Приём из моей практики — общий план: каждый ряд приводится через map к одному диапазону, чаще всего 0-100. В скетче выше raw и сглаженный дубль оба свёрстаны к сотне, и разница между ними — теперь честная, читаемая, а не «где-то там внизу». Цена приёма: вы теряете сырые единицы, поэтому для АЦП-значений рядом держу ряд-линейку известного уровня, по которому глаз калибруется, — деталь разобрана в уроке про осциллографный режим, здесь не повторяю.
Ряды с отрицательной половиной — температура зимой, ток заряда — приводятся к проценту от своего диапазона с учётом минуса: map(temp, -10, 40, 0, 100) разложит их в общий план без наложений. Флаговые линии — состояние кнопки, порога, реле — печатаю двумя полками 0 и 100: они превращаются в ступеньки-разделители, которые ржут о событиях, не мешая кривым. Если рядов набирается больше четырёх, читаемость умирает быстрее, чем успевает помочь autoscale: группируйте серии по смыслу и печатайте группами — первая группа в первом прогоне, вторая во втором.
Пропуски — тихий нарушитель позиций: пусть серия печатается по условию, скажем второй датчик опрашивается раз в пять кадров, — в кадрах, где её нет, безымянные позиции съезжают, и ряды меняются местами без предупреждения. Лечение совпадает с лечением легенды: все серии в каждом кадре. Для редких датчиков печатайте последнее известное значение, а не пропуск — ряд замрёт ровно там, где его оставили, вместо карусели. Именованные пары смягчают беду — ряд привязан к имени, — но пауза в печати и с ними видна как разрыв линии, что честнее подмены значений.
Последняя грань масштаба — темп: даже идеально свёрстанные ряды не читаются, когда кадры прут двести в секунду. Печать по таймеру 40 мс даёт глазу цепляться за форму, а стоп-кадр через условие — рассмотреть пик спокойно. Живой пример двух рядов с их диапазонами — график температуры и порога реле; там же видно, как флаговая полка и кривая сосуществуют в одном окне. Дисциплина формата стоит одной строки в скетче и возвращает её сторицей: график, который читается сам, экономит вечера.
Честное ограничение напоследок: у плоттера нет экспорта. Скриншот — максимум, что можно унести с собой; CSV из окна не выгрузить, история умирает вместе с вкладкой. Если показания нужны файлом — либо пишите их прямо на карту памяти скетчем, как разобрано в уроке про логирование на SD, либо снимайте поток внешней утилитой и стройте график уже на компьютере. Я пользуюсь простым правилом: плоттер — для взгляда здесь и сейчас, файл — для значений, которые обещают пригодиться. Переоценка плоттера в роль логгера обходится потерянным ночным прогоном.
Про ось времени скажу прямо: у плоттера её нет. Горизонталь — номера кадров, не секунды, и подписывать их нечем; перевод во время делается делением: при кадре раз в 40 мс экран проживает 25 точек в секунду, а минута прогона — полторы тысячи кадров. Отсюда привычка печатать в монитор рядом с кадром метку millis хотя бы раз в секунду: по ней на скриншоте всегда можно восстановить, какой минуте соответствовала та или иная форма кривой.
- 1. Три значения напечатаны тремя Serial.print без перевода строки, затем один println. Что покажет плоттер?
- 2. Как получить подписанные ряды в легенде?
- 3. Датчик отключили, и скетч печатает два значения вместо трёх, без имён. Что случится с линиями?
- 4. Сырое АЦП гуляет 0-1023, а выход ШИМ живёт 0-255 — второй ряд утонул у нуля. Что сделать?
- 5. Строка заголовка «start test» между кадрами — чем она обернётся?
- Перевод строки завершает кадр: всё между двумя println — один такт времени.
- Голые числа — безымянные ряды по позиции; пары имя: значение — подписанные линии в легенде.
- Смена набора безымянных рядов перетасовывает линии; имена короткие и постоянные между запусками.
- Общий масштаб через map (например, всё к 0-100) спасает ряды разной природы; флаги — полками 0 и 100.
- Строки без чисел безопасны, числа, склеенные с текстом, — мусорный ряд; стоп-кадр делается флагом печати.
Частые вопросы по теме урока
- Три значения напечатаны тремя Serial.print без перевода строки, затем один println. Что покажет плоттер?
- Кадр завершается переводом строки: всё, что напечатано между двумя println, рисуется как один такт. Ряды делит не print, а разделители внутри кадра. один ряд-кашу из трёх чисел в одном такте
- Как получить подписанные ряды в легенде?
- Подписи даёт пара имя: значение; голые числа поедут безымянными рядами, привязанными к позиции в кадре. печатать пары имя: значение
- Датчик отключили, и скетч печатает два значения вместо трёх, без имён. Что случится с линиями?
- Безымянные ряды привязаны к позиции в кадре. Смена состава серии перетасовывает линии — лечится постоянными именами на всех рядах. ряды перетасуются по позициям, легенда соврёт
- Сырое АЦП гуляет 0-1023, а выход ШИМ живёт 0-255 — второй ряд утонул у нуля. Что сделать?
- Общий план через map выравнивает ряды разной природы; цена — потеря сырых единиц, для них держат отдельный ряд-линейку известного уровня. привести оба ряда map-ом к общему диапазону, например 0-100
- Строка заголовка «start test» между кадрами — чем она обернётся?
- Строки без чисел плоттер молча пропускает. Опасны строки, где числа склеены с текстом — они рождают лишние безымянные линии. ничем: строки без чисел разборщик игнорирует