Ограничения библиотеки
Эта страница описывает границы возможностей библиотеки UNI: что важно знать заранее, чтобы поведение робота не стало сюрпризом. Большинство пунктов — не ошибки, а осознанные компромиссы, и для каждого указано, как с ним работать.
Движение и команды
Одновременно выполняется только одна команда движения
Очереди команд нет: новая команда движения молча отменяет текущую. Код
moveDistAsync(50, 500); rotateAsync(50, 90); выполнит только поворот —
движение по дистанции будет прервано сразу после запуска.
Как работать: между асинхронными командами дожидайтесь завершения через
waitMove() или используйте блокирующие версии (moveDist,
rotate), которые ждут сами.
Минимальная мощность движения — minPower
Мощность ниже minPower (по умолчанию 15) в командах движения автоматически
поднимается до него: на меньшей мощности моторы не страгиваются с места. Ехать медленнее
~15% командами moveDist/rotate и т.п. нельзя.
Как работать: прямые команды моторов motors() и
motorsSync() это ограничение не применяют — для очень медленного движения
используйте их. Порог настраивается в TuningConfig::minPower.
Скорость задается в процентах, а не в мм/с
Параметр power — процент от максимума, а не физическая скорость. Реальная
скорость зависит от заряда батареи: на свежей батарее робот едет быстрее, чем на
подсевшей. Замкнутые контуры компенсируют это по положению (точность остановки
сохраняется), но время выполнения команды плавает.
Как работать: не завязывайте логику на время движения — проверяйте
результат по одометрии (getDistance(), getAngle()).
Таймаут безопасности завершает движение молча
Если робот не достиг цели за ~3× расчетного времени (уперся в стену, колеса заблокированы), команда завершается сама — без кода ошибки. Блокирующий вызов просто вернет управление, как будто движение закончилось.
Как работать: там, где застревание вероятно, используйте
moveDistAsync() + waitMove(timeout): возврат false
означает, что робот не доехал за отведенное время. После этого сравните
getDistance() с ожидаемой дистанцией.
Ограничения дуг
moveArcRadius()требует радиус не меньше половины колеи (~53 мм). Для разворота на месте используйтеrotate().- В
moveArcDist()/moveArcTime()/motorsArc()параметрangleс |angle| > 90 заставляет внутреннее колесо вращаться назад — траектория остается дугой, но поведение менее предсказуемо. moveTime()иmoveArcTime()останавливаются по времени без профиля замедления — остановка резче, чем у команд по дистанции.
Точность и одометрия
Физическая точность равна точности калибровки
Повороты физически точны настолько, насколько точно задана колея
(trackLengthMM), дистанции — насколько точны диаметр колеса
(wheelDiameterMM) и тики на оборот (ticksPerRev).
Правило калибровки: если одометрия показывает ровно цель
(getAngle() = 90 после rotate(50, 90)), а физически робот
повернул больше или меньше — калибруйте геометрию в UniConfig, а не
коэффициенты ПИД.
Одометрия накапливает дрейф
Позиция считается только по энкодерам колес: проскальзывание, неровности пола и удары
одометрия не видит. Гироскопа или внешней коррекции нет. moveTo() и
setPosition() хорошо работают на маршрутах в несколько метров, но ошибка
растет с пробегом и количеством резких маневров.
Как работать: на длинных маршрутах периодически «заземляйте» позицию по
внешнему ориентиру (стена, линия) и вызывайте setPosition() с известными
координатами.
Допуски завершения и отсутствие обратной доводки
Команды движения завершаются при входе в допуск по одометрии:
rotateTolDeg (по умолчанию 1°) для поворотов и moveTolMM
(1 мм) для дистанций. Если робот проскочил цель, он фиксируется тормозом — назад для
коррекции он принципиально не сдает (это убирает дерганье в конце движения).
Как работать: нужна точность выше — уменьшайте допуск и
moveAccel/rotateAccel в TuningConfig (плавнее
торможение — меньше перелет).
moveTo: боковой снос пропорционален допуску поворота
moveTo() сначала доворачивает на курс к цели (с точностью
rotateTolDeg), затем едет по прямой, удерживая курс, — но не
подруливает на точку по ходу движения. Ошибка курса в 1° дает боковой снос
~5 мм на каждые 300 мм пути.
Как работать: для повышения точности уменьшите rotateTolDeg
или разбейте длинный отрезок на два moveTo() — промежуточная точка
пересчитает курс.
Система координат и сбросы
- Ось X направлена вперед от стартовой позы робота, Y — вбок, положительный угол — поворот направо.
resetDistance()иresetAngle()сбрасывают только счетчики пройденного пути и угла — позу X/Y они не трогают.- Чтобы привязать одометрию к полю (или обнулить все сразу), используйте
setPosition(x, y, angle).
Поведение и блокировки
Блокирующие вызовы
Некоторые функции останавливают выполнение программы:
stop(HARD)— блокирует ~50 мс (время торможения);waitButton()— ждет нажатия бесконечно;ultraSonic()— до ~70 мс, если эхо не приходит (3 попытки);- все эффекты светового кольца (
pixelsRainbowи др.) — блокируют на всю длительностьduration.
Как работать: в цикле, который следит за датчиками во время движения,
не вызывайте длинные эффекты; для подсветки во время езды используйте базовые
pixel()/pixelsAll().
Ленивая инициализация
begin() вызывать не обязательно — любая первая команда инициализирует робота
сама. Но у этого есть следствие: скетч, который не вызывает ни одного метода
робота, не оживит экран и одометрию.
Как работать: если робот должен «просто стоять и показывать экран» —
вызовите robot.begin() явно в setup().
API рассчитан на вызовы из setup() и loop()
Библиотека сама использует второе ядро ESP32 для одометрии и регуляторов, а вызовы API синхронизированы в расчете на одно пользовательское ядро. Вызовы методов робота из собственных задач FreeRTOS пользователя не поддерживаются и могут привести к гонкам.
Периферия
Дисплей
- Строки
displayPrint()— максимум 15 символов, длиннее обрезаются. - Экран обновляется ~10 раз в секунду: значения, меняющиеся быстрее, частично теряются — это нормально.
Сервоприводы
- Максимум 12 серво одновременно (16 PWM-каналов ESP32 минус 4 на моторы).
- PWM-канал закрепляется за портом при первом использовании и не переиспользуется для
другого порта (но
servoDetach()каналы не расходует). - Импульсы фиксированы: 0.5–2.5 мс при 50 Гц. Серво с нестандартной калибровкой подстроить нельзя.
- Скорость движения качалки не контролируется — серво идет к углу с максимальной скоростью. Плавность достигается серией мелких шагов угла с задержками.
Ультразвуковой датчик
Рабочий диапазон ultraSonic() — 20–4000 мм. Возврат 0 означает
«нет эха»: цель слишком близко, слишком далеко или не отражает звук.
Важно: всегда проверяйте ноль отдельно, иначе условие
dist < порог ложно сработает как «препятствие вплотную»:
int dist = module.ultraSonic(P6, P7);
if (dist > 0 && dist < 150) {
robot.stop(HARD); // препятствие действительно близко
}
Аналоговый вход недоступен на P5 и P6
При попытке аналогового чтения с P5/P6 библиотека выводит предупреждение в Serial.
Как работать: для датчика линии и любых аналоговых датчиков
используйте порты с АЦП — P1, P2, P3, P4 (ADC2) или
P7 (ADC1). Цифровые датчики (digitalSensor())
работают на любом порту, включая P5/P6.
Примечание про Wi-Fi: P1–P4 используют ADC2, который недоступен, пока активен Wi-Fi. Сама библиотека Wi-Fi не использует, поэтому P1–P4 безопасны; но если вы добавите Wi-Fi в свой скетч, единственный независимый аналоговый вход — P7 (ADC1).