Инструменты пользователя

Инструменты сайта


Практикум для «Олимпийца» (Olympic Bot). Часть 3: управляем роботом-тележкой

В третьей части практикума рассмотрим решения олимпиадных заданий регионального этапа ВсОШ за 2024–2025 учебный год. Разберёмся с алгоритмами управления роботом-тележкой.

 Amperka Olympic Bot

1. Тренировки, тренировки и ещё раз тренировки

Невозможно даже и думать о выполнении заданий олимпиады, если ты не в состоянии составить в голове алгоритм действий робота и находить в нём зависимости. Поэтому, прежде чем переходить к следующему разделу, обязательно пройди два уровня прокачки твоего логического мышления.

1. Игры Блокли, выделенные плюсом.

2. Миссии для робота от известного специалиста К. Ю. Полякова.

  • Постарайся достичь такого уровня, чтобы на решение каждого задания уходило не более 15 минут.
  • Если задания вызывают сложности, лучше потренироваться ещё, прежде чем переходить к продолжению практикума.

2. Задание регионального этапа для 9-х классов

Задание

Участнику необходимо собрать робота, составить его электрическую структурную схему Э1, написать и отладить следующую программу.

Рисунок 1. Пример начального расположения кубиков

Задачи для робота

  • Начать движение в зоне старта.
  • Двигаться по линии с перекрёстками и перемещать зелёный куб между пустыми квадратными зонами.
  • Переместить зелёный куб в зону, закрашенную зелёным цветом, которая находится на одной горизонтали с изначальным расположением зелёного куба.
  • Синие кубы не должны покидать квадратные зоны.
  • Зелёный куб должен перемещаться от одной белой квадратной зоны, ограниченной красной линией, до другой и не должен оказаться полностью внутри зоны, окрашенной в розовый цвет.
  • Робот не должен оказаться внутри двух жёлтых зон одновременно, переехав колёсами из одной в другую. Перемещение из одной жёлтой зоны в другую возможно только через белую зону в верхней части полигона (рисунок 1). Две разные жёлтые зоны разделены розовой зоной.
  • Доставив зелёный куб в зелёную зону, робот перемещается в зону финиша и останавливается.

Рисунок 2. Пример правильного выполнения задания

Примечания

  • Размеры робота на старте не должны превышать 300×300×300 мм, в процессе выполнения задания размеры робота могут увеличиться.
  • Перед стартом робота не допускается ввод в контроллер данных о расположении кубиков.
  • Куб считается размещённым в квадратной зоне любого цвета, если любая часть его вертикальной проекции находится над этой зоной.
  • Если зелёный куб оказался внутри зоны, окрашенной в розовый цвет, попытка останавливается.
  • Если робот переехал любым колесом из одной жёлтой зоны в другую через розовую зону, попытка останавливается.
  • Куб считается расположенным в розовой зоне, если он своей вертикальной проекцией полностью находится над розовой зоной.
  • Робот считается расположенным в жёлтой зоне, если любая часть его вертикальной проекции находится над жёлтой зоной.
  • Чёрные линии, проходящие сквозь жёлтую зону, являются частью жёлтой зоны.
  • Чёрная линия, проходящая сквозь розовую зону, считается частью розовой зоны.
  • Расположение кубов определяется жребием.
  • Робот считается находящимся в зоне старта, если он своей вертикальной проекцией полностью находится внутри зоны, включая провода и все элементы конструкции.
  • Робот считается находящимся в зоне финиша, если он любым колесом соприкасается с зоной финиша.
  • Цветные линии, ограничивающие зоны старта и финиша, являются частью этих зон.

Рисунок 3. Пример перемещения робота и зелёного куба

В конце файла с заданием есть критерии оценки. Глядя на них, становится ясно, что задания нужно выполнять в полном соответствии с примечаниями. И код грамотно оформлять, и писать комментарии.

Критерий оценки Макс. балл
1 Робот полностью выехал со старта (все точки вертикальной проекции робота покинули стартовую зону). 3
2 Робот однократно разместил зелёный куб в каждой пустой белой квадратной зоне, ограниченной красной линией. 7×2
3 Робот однократно разместил зелёный куб в одной из зелёных зон рядом с зоной финиша. 3
4 Зелёная зона, в которой робот разместил куб, находится строго напротив зоны начального размещения зелёного куба. 4
5 Робот полностью переместил синий куб из белой зоны изначального размещения. −3×6
6 Робот остановился в зоне финиша после успешного выполнения любой части заданий п. п. 2–4 (робот любым колесом соприкасается с зоной финиша). 4
7 Составлена электрическая структурная схема Э1 робота на базе Arduino (в соответствии с ГОСТ 2.702-2011). 2
8 Код программы оптимизирован (в коде используются циклы, ветвления, регуляторы). Код взаимосвязан с заданием и выполняет осмысленные действия. Программа компилируется без ошибок. 2
9 Читаемость кода (наличие комментариев к основным блокам кода, информативные имена переменных, выделение отступами циклов и так далее). 1
10 Отсутствие грубых ошибок в конструкции робота (незакрепленные или плохо закреплённые части, провод касается колеса и пола, шины соприкасаются с деталями шасси и так далее). 2

Чтобы было проще и веселее составлять алгоритм для робота, носи с собой Lego-человечка или маленький предмет. Используй его, чтобы отслеживать текущее состояние робота и действия на поле.

3. Робот-тележка

Робот-тележка — это автоматически управляемое транспортное средство, которое перемещает грузы по заданному маршруту. Она полностью автономна, то есть для её управления не нужен человек.

Некоторые особенности использования роботов-тележек:

  • Точность при выполнении любых операций, практически полное исключение внутренних логистических ошибок.
  • Снижение травматизма и риска повреждения материальных ценностей и оборудования.
  • Возможность непрерывной работы в режиме 24/7 без какого-либо контроля со стороны операторов.
  • Эффективная, ускоренная и предельно гибкая транспортировка со схемами, настроенными под конкретный объект.
  • Универсальные погрузочные и разгрузочные механизмы.
  • Оснащение специальными сенсорами, позволяющими видеть окружающие препятствия и людей.

Роботы-тележки постепенно стали основой логистики, и без них не обходится ни один большой склад. Именно поэтому задачи с ними часто встречаются на олимпиадах. Быть может, кто-то из участников вдохновится и будет потом участвовать в разработке современных технологий для складов.

Сейчас можно выделить два типа устройств для автоматизированного перемещения грузов в промышленной среде, которые обеспечивают транспортировку без непосредственного участия оператора.

Автоматически управляемое транспортное средство (от англ. Automated Guided Vehicle, AGV) — это промышленное транспортное средство, которое можно предварительно запрограммировать для перевозки грузов на производстве, складе и между ними. Система AGV — это комплекс таких устройств, взаимодействующих друг с другом для выполнения широкого круга задач по перемещению грузов внутри предприятия.

Среди них:

  • создание линий, заменяющих конвейерные системы;
  • перемещение деталей и готовых изделий со склада и на склад;
  • удаление отходов из рабочих зон;
  • доставка инструмента и расходных материалов на рабочие места;
  • внутрискладские перемещения и другие аналогичные задачи.

Автономный мобильный робот (от англ. Autonomous Mobile Robot, AMR) — более сложный технически, но более простой в эксплуатации вариант AGV, в котором для автономного перемещения материалов без физических направляющих или маркеров используются бортовые датчики и процессоры. Он изучает своё окружение, запоминает своё местоположение и динамически планирует свой собственный путь от одной путевой точки до другой.

Существует три основных вида AGV и AMR: транспортные тележки, вилочные погрузчики и буксирующие устройства.

4. Понижающий стабилизатор DC-DC

В цепи питания нашего робота есть пара важных элементов: понижающий преобразователь напряжения на основе микросхемы TPS54540 и литиевые аккумуляторы популярного формата 18650.

Стабилизаторы на микросхеме TPS54540 широко используются в робототехнике и самодельной электронике. Они позволяют подавать стабильное напряжение на моторы, сервоприводы и другие компоненты, чтобы обеспечить их надёжную работу и защиту от скачков напряжения.

Уровень выходного напряжения регулируется в диапазоне 0,8–25 В специальным винтом на подстроечном резисторе VOUT.

  • Источник питания (батарейный отсек) подключается к входным контактам VIN и GND.
  • Начинка робота подключается к выходным контактам VOUT и GND.
  • Используй физический переключатель, чтобы включать-выключать робота.

Понижающий преобразователь питания нужен, чтобы твой робот сохранял скорость движения по мере разрядки аккумуляторов. Ведь физическая скорость влияет и на интервалы времени езды, которые ты выставляешь в своей программе.

5. Аккумуляторы

Маркировка аккумуляторов содержит полезные сведения: форм-фактор, рабочее напряжение и ёмкость.

Например, наш тренировочный робот питается от двух аккумуляторов Robiton формата 18650 ёмкостью 2200 мА·ч с защитой. Но на олимпиаде тебе могут попасться десятки других вариантов. Главное — знать общие принципы.

Тебя больше всего интересует ёмкость аккумуляторов. От неё напрямую зависит время автономной работы робота.

Ёмкость обычно указывают для 20-часового цикла разрядки до остаточного напряжения. Таким образом, ёмкость 2200 мА·ч — это примерно 20 часов работы с током разрядки 1100 мА.

Давай подсчитаем, сколько тока потребляет наш робот? Для этого обратимся к документации и постараемся учесть всё оборудование на его борту.

Устройство Потребляемый ток Количество
Arduino Uno 50 мА 1
Драйвер моторов L298 36 мА 1
Ультразвуковой дальномер HC-SR04 15 мА 1
Аналоговый датчик линии 10 мА 2
Инфракрасный дальномер 30–40 мА 1
Микросервопривод FS90 100 мА 1
Светодиод 20 мА 2
Коллекторный двигатель N20 200 мА 2
Итого ~700 мА

Исходя из этого, двух аккумуляторов на 2200 мА·ч хватит на добрых 6 часов активного использования нашего бота-олимпийца. Вполне достаточно на целый день тренировок!

На соревнованиях же часто выдают по два комплекта питания, чтобы можно было экстренно заменить подсевшие батареи. Но наездить даже час-полтора чистого времени вряд ли получится, ведь на один полигон претендует сразу 5–7 участников.

6. «Залей» тестовую программу

Перед окончательной сборкой робота в плату Arduino стоит прошить скетч для проверки моторов. Пока будешь собирать, как раз быстренько проверишь работоспособность.

На олимпиаде стоит поступать так же — избавишь себя от неожиданностей с железом! Никто не застрахован от небольшого процента брака и дефектов. В твоих же интересах быстренько проверить платы и электронные модули, что они хотя бы «заводятся» и работают.

FIXME

 

7. Что куда подключать

К каким контактам подключать то или иное оборудование, будет видно из приведённого ниже фрагмента кода. Всё это тебе уже хорошо знакомо.

//описываем константы с помощью директивы препроцессора:
#define DR 4 //пин для направления вращения правого мотора (M1)
#define SR 5 //пин для управления скоростью вращения правого мотора (M1)
#define DL 7 //пин для направления вращения левого мотора (M2)
#define SL 6 //пин для управления скоростью вращения левого мотора(M2)
#define IR A2 //пин для подключения инфракрасного дальномера
#define UT 11 //пин для подключения ультразвукового дальномера (T)
#define UE 10 //пин для подключения ультразвукового дальномера (R для эхо)
#define GRP 9 //пин для подключения сервомотора для захвата
#define LL A5 //пин для левого датчика "цвета"
#define LR A4 //пин для правого датчика "цвета"
#define EL1 3 //пин для первого сигнального контакта левого энкодера
#define ER1 2 //пин для первого сигнального контакта правого энкодера
#define LED_R 13 //пин для красного светодиода
#define LED_G 12 //пин для зелёного светодиода

А что не очень знакомо, так это то, что директива #define — из старого доброго языка C (Си). И есть такой очень дискуссионный вопрос: что лучше использовать — #define или более современный синтаксис?

Да, в языке C определять символьные константы можно было только с помощью #define. А в языке С++ для создания именованных констант можно применять constexpr (или const).

Ключевое слово const сообщает компилятору, что переменная (или указатель) не может быть изменена. Однако это всё равно переменная, и от того, как она используется в коде, зависит потребление оперативной памяти.

Компилятор avr-gcc в Arduino IDE достаточно умён, чтобы понимать, что переменная с модификатором const не может быть изменена в рамках активной программы, и он постарается экономить за счёт этого оперативную память.

constexpr и #define эквивалентны, если использовать их только для простых целочисленных констант, таких как назначение выводов. А это именно наш случай!

//описываем константы
constexpr int DR = 4; // для направления вращения правого мотора (M1)
constexpr int SR = 5; // для управления скоростью вращения правого мотора
constexpr int DL = 7; // для направления вращения левого мотора (M2)
constexpr int SL = 6; // для управления скоростью вращения левого мотора
constexpr int IR = A2; // для подключения инфракрасного дальномера
constexpr int UT = 11; // для подключения ультразвукового дальномера (T)
constexpr int UE = 10; // для подключения УЗ-дальномера (R для эхо)
constexpr int GRP = 9; // для подключения сервомотора для захвата
constexpr int LL = A5; // для левого датчика "цвета"
constexpr int LR = A4; // для правого датчика "цвета"
constexpr int EL1 = 3; // для сигнального контакта левого энкодера
constexpr int ER1 = 2; // для сигнального контакта правого энкодера
constexpr int LED_R = 13; // для красного светодиода
constexpr int LED_G = 12; // для зелёного светодиода

Тут может возникнуть удивление: как же так, разве А4 — целое число? Да! Контакты A0A5 имеют номера с 14 по 19, то есть целые числа. А в коде их незаметно «подменят» именно так, как ты укажешь через define.

  • При использовании constexpr уже морально нельзя использовать имена из заглавных букв. Надо применять «Верблюжий» стиль CamelCase — тем более, что код будут оценивать.
  • Подробнее о разных стилях нотации читай во второй части практикума.

8. Собираем робота

Процесс сборки робота описан в отдельной инструкции.

9. ИК-дальномер

В третьей части мы постараемся не повторять по кругу изученный материал, а углубить и расширить твои знания и возможности.

Возьмём уже знакомый инфракрасный дальномер GP2Y0A21YK0F. Эта модель предназначена для измерения расстояний до препятствий в диапазоне от 10 до 80 см.

Принцип работы датчика таков: излучатель испускает инфракрасные импульсы, которые отражаются от объектов в поле зрения сенсора и возвращаются на приёмник.

Отражённые импульсы собираются линзой и образуют собой некоторое «пятно» на детекторе положения (матрице).

При изменении положения объекта изменяется угол отражённого луча и положение пятна.

В результате датчик выдаёт аналоговый сигнал, который зависит от положения объекта перед датчиком.

А вот дальше существует масса формул и готовых библиотек для пересчёта показаний датчика в сантиметры. Сейчас мы разберёмся, как это правильно сделать.

Если скачать техническую спецификацию, можно увидеть график, на котором практически линейная функция: y = kx + b (красная линия).

Только она в таком виде: V = k ⋅ 1 / (L + 0,42) + b

Тебе нужно просто подставить две точки, которые видны на графике, получив систему из двух линейных уравнений с двумя неизвестными.

Отношение выходного напряжения датчика обратно пропорционально расстоянию и практически линейно на всём диапазоне измерений.

При этом нам не очень нужна высокая точность. Например, нас устроит зелёная линия, проходящая через начало координат. Следовательно, в уравнении b можно считать равным нулю. Значит, можно взять всего одну жёлтую точку и найти этот коэффициент k.

Имеем уравнение: V = k / L [1]

Тогда: L = k / V [2]

Подставляем в уравнение [1] данные выбранной точки: V = 2,3 и L = 10. Получаем k = 23.

Отлично! Теперь наше уравнение [2] выглядит так: L = 23 / V

Его легко запомнить. Только учти, что область допустимых значений L лежит в пределах [10, 80].

А у нас на аналоговом входе контроллера появляются числа от 0 до 1023. Нужно перевести их в вольты.

10. АЦП

Чтобы разобраться с преобразованием результатов измерений, обратимся к технической документации.

Расчёт для ATmega328P (плата Arduino Uno и прямые аналоги)

Когда мы подключаем к микроконтроллеру внешние датчики, их сигнал часто является аналоговым по своей природе. Но микроконтроллер умеет обрабатывать только цифровые сигналы.

Следовательно, сигнал нужно пропустить через аналогово-цифровой преобразователь (АЦП, ADC), который преобразует аналоговый сигнал в цифровую форму, понятную для микроконтроллера.

Платы Arduino Uno имеют 6 встроенных каналов АЦП, которые можно использовать для считывания аналогового сигнала в диапазоне 0–5 В. Эти входы обозначены на плате как A0A5 (иногда их ещё называют «аналоговые пины»).

Для считывания значения с одного из аналоговых входов используют функцию analogRead с аргументом в виде номера пина. Например, analogRead(A2) считает аналоговое значение на 2 пине.

Обычно платы Arduino имеют 10-битный АЦП. Это означает, что он будет выдавать цифровое значение в диапазоне 0…1023. Если мы учтём ноль, получится 1024 (2¹⁰) ступеней. Общее количество этих дискретных значений называют разрешением АЦП.

Пример оцифровки сигнала 0–5 В на АЦП разрядностью 2 бита (4 ступени) и 3 бита (8 ступеней)

Чем выше разрешение АЦП — тем более тонко «нарезан» диапазон аналоговых значений от нуля до величины опорного напряжения Vref. А значит, и точность преобразования сигналов выше.

В нашем случае 10-битное разрешение позволяет отличать сигналы с минимальным шагом в 4,8 мА (делим 5 В на максимальное значение 1023). Вполне достаточно для любительского применения!

Чтобы узнать напряжение с датчика на входе A2, используй следующую формулу:

V = analogRead(A2) * 5 / 1023

Посмотри ещё раз на график. Когда мы заменили его зелёной линией, это было сделано неспроста. Мы применили научный метод аппроксимации — заменили сложную зависимость более простой, но достаточно близкой по поведению.

Кроме того, по графику видно, что реальное расстояние будет чуть больше, чем вычисленное по нашей формуле значение. Разница небольшая, но об этом нужно помнить.

К счастью, нам в задании не нужна особая точность. Роботу достаточно знать, присутствует ли объект в этом диапазоне расстояний.

В формуле из документации указано напряжение Vin, которое питает датчик. Поэтому стоит измерить напряжение именно при питании от аккумуляторов, а не от USB.

Для этого воспользуйся парой проводков и мультиметром в режиме измерения постоянного напряжения DC.

11. Дальномеры: получаем сантиметры

Итак, мы научились переводить значения АЦП в вольты. Подставим их в удобную формулу для аппроксимации измерений дальномера, и получим на выходе сантиметры. За дело!

FIXME

 
  • Обрати внимание, что мы использовали новый метод — ожидание запуска монитора последовательного порта.
  • Оператор ! — это отрицание, то есть логическая операция «НЕ».

Ультразвуковой дальномер HC-SR04 совмещает в себе приёмник и передатчик ультразвукового сигнала. Принцип действия HC-SR04 основан на хорошо известном явлении эхолокации. Излучатель формирует акустический сигнал, который отражается от преграды и регистрируется приёмником датчика. Зная скорость распространения звука в воздухе и время запаздывания между излучённым и принятым сигналом, легко рассчитать расстояние до акустической преграды.

При выборе направления ультразвукового дальномера надо учитывать, что угол наблюдения — 30°, а эффективный угол — 15°.

Чтобы получить данные с ультразвукового дальномера, необходимо:

  • Подать на выход Trig импульс длительностью 10 мкс. Излучатель отправит 8 импульсов частотой 40 кГц.
  • Запустить функцию pulseIn, которая считывает длину сигнала на заданном порту (HIGH или LOW) и возвращает его время ожидания в микросекундах.
  • Импульсы отразятся от препятствия и попадут на приёмник, что обеспечит наличие входного сигнала на контакте Echo.
  • Полученный сигнал нужно перевести в расстояние в сантиметрах по формуле: L = t / 58, где t — ширина импульса в микросекундах.

Кстати, наше крепление позволяет настроить, куда «смотрит» датчик.

12. Проблемы всюду!

На заключительном этапе олимпиады в 2025 году проходной балл был 52 из 100. Робот смог выехать со старта — уже хорошо, остальные баллы — на проекте и теории. Как так?

Дело в том, что везде возможны проблемы, включая железо. Их нужно научиться прогнозировать до, а не во время работы.

Особенность ИК-дальномера в том, что по документации он должен работать до 80 см. Но если объект маленький, как кубик, то дальномер будет ошибаться и при 30 см.

В предыдущих практикумах объекты были близко, и этот нюанс нам не мешал. Поэтому датчик нужно выносить подальше в бок. Например, как на фотографиях.

Ещё одна проблема — пижонство! Вот хочется, чтобы ИК-дальномер выдавал сантиметры, а не непонятные числа. И это своего рода ловушка. Ты можешь потерять драгоценное время на реализацию формул, но при любых ошибках всё равно полезешь смотреть сырые данные от 0 до 1023.

Другими словами, делать «красивое» воплощение надо на шоу и выставках, а не на олимпиадах, где времени и так в обрез.

Не заморачивайся с формулами для обёртки данных и не усложняй себе задачу!

Следующая проблема — светодиоды. Они есть в требованиях к оборудованию, но их почти никто не подключает. А их обязательно стоит подключать по 2–3 штуки! Очень понадобится для отладки программы, ведь при движении робота ты не заглянешь в Serial.print

Если быстро не понять, какой груз ждёт тебя и робота, то минут 15 улетит на подгонку захвата.

Посмотри на два захвата. Как думаешь, какой из них лучше? Когда робот будет переставлять зелёные кубики, точность всегда будет неидеальной. Поэтому захват робота должен покрывать почти весь белый квадрат, а не красиво «облегать» кубик.

Теперь о датчиках линии. Их лучше всего ставить в 2–2,5 см от поверхности, а не 1,6 см, как у леговских! А в технической документации вообще написано 1,4 см. Лишние перестановки обойдутся тебе как минимум в 5 минут времени.

13. Какие нужны процедуры и функции

Прежде чем писать процедуры, стоит составить список: какие точно будут, а какие писать не надо. Посмотри-ка на схему.

Да, основа всего — движение до перекрёстка, причём робот сперва должен съехать с линии перекрёстка. Понадобится езда вперёд/назад, так как роботу нужно будет развернуться после перемещения кубика. Если не отъехать назад, кубик собьётся. Вперёд тоже (посмотри на фото) иногда подвинуть надо.

Повороты точно нужны. Но какие именно? Вроде бы робот всегда поворачивает вправо. По схеме точно видно, что только направо. Но на схеме есть проблемные точки — нижние перекрёстки. В них будет 4 случая: понадобится и поворот налево, и разворот на 180°.

Рассмотри четыре схемы ниже, и всё поймёшь. Нужно ли после захвата кубика пятиться роботом назад? Он будет 6 раз подъезжать к ячейкам и может ошибиться при возврате к перекрёстку. Поэтому пока не будем пятиться.

Ну, вот теперь-то мы можем накидать список процедур и функций.

// быстро протестировать все сенсоры
void sensorsTest(int s) {} //вывод данных с сенсоров
 
//событие при срабатывании прерывания (правый энкодер)
void eventR() {} // правый счётчик увеличиваем
 
//событие при срабатывании прерывания (левый энкодер)
void eventL() {} // левый счётчик увеличиваем
 
// ставим захват в положение 90 градусов
void grip90() {}
 
// плавное поднятие захвата
void up() {}
 
// плавное опускание захвата
void down() {}
 
// движение вперёд по энкодерам (ПД-регулятор)
void fwd(int speed, int limit) {}
 
// движение назад по энкодерам (ПД-регулятор)
void back(int speed, int limit) {}
 
// помигать красным светодиодом (для визуализации)
void blinkRed(int dt, int n) {}
 
// помигать зелёным светодиодом (для визуализации)
void blinkGreen(int dt, int n) {}
 
// по линии до перекрёстка (серый цвет - gr)
//1 шаг = по линии по энкодерам, 2 шаг = по линии до перекрёстка
void toCross(int sL, int sR, int limit) {}
 
// измерить расстояние до объекта в см
int usCm() {} //ультразвуковой дальномер
 
// поворот направо (скорость, предел по энкодерам)
void turnRightFwd(int speed, int limit) {}
 
// поворот налево (скорость, предел по энкодерам)
void turnLeftFwd(int speed, int limit) {}
 
// разворот танковый (скорость, предел по энкодерам)
void turn180(int speed, int limit) {}
 
// взять куб (едет от перекрёстка и возвращается)
void getCube(int x) {}
 
// положить куб (едет от перекрёстка и возвращается)
void putCube(int x) {}
 
// проверка, есть ли кубик справа и свободно ли место слева
int checkDist(int cm, int ik, int n) {}
 
// основной алгоритм (процедура)
void run() {}
 
// все настройки и запуск основного алгоритма работы робота
void setup() {}
 
// всегда пустой цикл
void loop() {}

А ещё нужны константы (define чуть короче и проще) и глобальные переменные.

//описываем константы с помощью директивы препроцессора:
#define DR 4 //пин для направления вращения правого мотора (M1)
#define SR 5 //пин для управления скоростью вращения правого мотора (M1)
#define DL 7 //пин для направления вращения левого мотора (M2)
#define SL 6 //пин для управления скоростью вращения левого мотора(M2)
#define IR A0 //пин для подключения инфракрасного дальномера
#define UT 11 //пин для подключения ультразвукового дальномера (T)
#define UE 10 //пин для подключения ультразвукового дальномера (R для эхо)
#define GRP 9 //пин для подключения сервопривода захвата
#define LL A3 //пин для левого датчика линии
#define LR A1 //пин для правого датчика линии
#define EL1 3 //пин для первого сигнального контакта левого энкодера
#define ER1 2 //пин для первого сигнального контакта правого энкодера
#define LED_R 13 //пин для красного светодиода
#define LED_G 12 //пин для зелёного светодиода
 
// описываем глобальные переменные
volatile long countL = 0, countR = 0; // счётчики для энкодеров
int delL = 10, delR = 10; // для настройки диапазона значений сенсоров линии
int w = 62, b = 20, gr = 42; // на белом, на чёрном и среднее
int dtStop = 500; // учёт инерции при каждой остановке после действия
int turn = 520; // счётчик для поворота одним колесом на 90 градусов
int irFindData = 200; // данные ИК, если больше - есть объект
int usFindCm = 30; // данные УЗ, если больше - место для кубика свободно
int crossEncoders = 150; //при старте движения по линии надо съехать с линии

  • Чтобы лучше вникнуть, какие действия робота реализовать отдельными процедурами и функциями, посмотри видео с примером выполнения задания.
  • У нас получилось более 20 функций. За какое время ты сможешь их написать, не подглядывая в учебник? Самое время потренироваться, чтобы узнать свой результат.
  • Профессиональные разработчики скажут, что define уже не принято использовать. Ничего страшного, это не ошибка. Может, нам так удобнее!
  • Если на соревновании дадут доступ к компьютеру пораньше, исследуй примеры из Arduino IDE. Оттуда можно скопировать стандартные команды, строки, имена.

14. Все процедуры и функции

Итак, ты разобрался с блоком «дефайнов» и подставил правильные номера контактов.

Лови ркомендации по глобальным переменным:

  • Имя должно отражать суть объекта.
  • Значения подбираются экспериментальным путём. Ты как раз тренируешься, чтобы побыстрее подбирать эти значения.
  • Комментарии описывают, что делает код.
  • Счётчики энкодеров должны иметь тип volatile long, чтобы их можно было изменять в любой момент через прерывания.

Пройдёмся по всем процедурам и функциям.

// быстро протестировать все сенсоры
void sensorsTest(int s) { //вывод данных с сенсоров
  while (!Serial) delay(5);
  unsigned int rst = millis();
  while (millis() - rst < s * 1000) {
    //int dist1 = irCm();
    int dist1 = analogRead(IR);
    int dist2 = usCm();
    int ll = (1024 - analogRead(LL)) / 10;
    int lr = (1024 - analogRead(LR)) / 10;
    Serial.print("L=");
    Serial.print(ll);
    Serial.print("\t");
    Serial.print("R=");
    Serial.print(lr);
    Serial.print("\t");
    Serial.print("IR_data = ");
    Serial.print(dist1);
    Serial.print("\t");
    Serial.print("US_cm = ");
    Serial.println(dist2);
    delay(50);
  }
}
  • Используй этот подход, чтобы чтобы показания были от 0 до 100, как у леговских датчиков:
int ll = (1024 - analogRead(LL)) / delL

Так будет легче воспринимать показания, например: 10–20 на чёрном и 60–80 на белом.

  • Помни, что можно показания датчиков линии можно выровнять подстроечным резистором на плате.

Обрати внимание, что тут и ожидание нажатия на иконку «Монитор последовательного порта», и демонстрация как бы сброса таймера, хотя его сбросить и нельзя. Можно только запомнить текущее значение. И уже от него считать время дальше.

Нам нужны два обработчика прерываний: eventL() и eventR().

//событие при срабатывании прерываний (правый и левый энкодеры)
void eventR() { // правый счётчик увеличиваем
  if (digitalRead(ER1)) countR++;
}
void eventL() { // левый счётчик увеличиваем
  if (digitalRead(EL1)) countL++;
}

Идея для компактной записи кода: если нужно задать логическое условие == true, можно пропустить оператор == в сравнении.

if (digitalRead(ER1)) == countR++;
//То же самое, только короче
if (digitalRead(ER1)) countR++;

Счётчики всегда будут увеличиваться фоном при движении робота, куда бы не вращались колёса. Поэтому важно не забывать обнулять счётчики, когда хочешь сделать что-то с использованием энкодеров.

  • Белый (M1) — для мотора: питание (+).
  • Синий (GND) — для энкодера: земля.
  • Зелёный (C1) — для энкодера: сигнальный провод обратной связи 1-го датчика Холла.
  • Жёлтый (C2) — для энкодера: сигнальный провод обратной связи 2-го датчика Холла.
  • Чёрный (VCC) — для энкодера: питание 5 В.
  • Красный (M2) — для мотора: питание (−).

Прерывание — это сигнал, который сообщает процессору о наступлении какого-либо события. Процессор реагирует на этот сигнал, прервав выполнение текущих инструкций и передав управление обработчику прерывания (ISR).

Обработчик — это наша функция, которая должна выполняться при прерывании. После обслуживания прерывания функция завершает свою работу, а процессор продолжает выполнять код с того места, на котором остановился.

Обрати внимание, что мы отслеживаем у робота именно вторые датчики Холла (используем жёлтый выход энкодера), а не первые.

В нашем случае захват по умолчанию будет в поднятом состоянии. Это задано перед стартом основного алгоритма.

Поэтому нам понадобятся три процедуры для сервопривода:

  1. Выставить захват в среднее положение 90°.
  2. Плавно поднять захват.
  3. Плавно опустить захват.

FIXME

 

Следующие процедуры — движение робота вперёд и назад с ограничением по показаниям энкодеров, а не продолжительности работы моторов. Процедуры будут отличаться только названием и направлением вращения (HIGH и LOW).

FIXME

 

ПИД-регуляторы — это разновидность систем управления, которые используют обратную связь для управления процессом или системой.

Что отслеживает регулятор?

  1. Величину отклонения от заданного значения. В нашем случае — разность счётчиков на энкодерах.
  2. Сумму маленьких частей отклонений с начала движения робота.
  3. Скорость изменения отклонения. В нашем случае — как изменилось отклонение за 5 мс.

Чтобы поглубже вникнуть в тему, рекомендуем почитать учебный материал про ПИД-регуляторы.

Простейшие программки мигания светодиодом нужны, чтобы тебе было видно, нашёл ли робот объект. К примеру, ты можешь визуализировать выбор в условных операторах, или какая часть программы сейчас выполняется, чтобы её было проще отлаживать.

FIXME

 

Потратив пару минут на написание светодиодной индикации, ты получишь выигрыш при первом же поиске недочёта или ошибки в своём алгоритме.

Следующая процедура — движение по линии до перекрёстка. При этом в начале робот должен проехать по линии какое-то расстояние, которое будет задаваться счётчиками энкодеров (точнее, их средним арифметическим). В предыдущих частях практикума мы ещё не рассматривали такую процедуру.

Стабилизацию движения робота по линии будет выполнять программный ПД-регулятор.

  • «П» — пропорциональный. Регулятор берет в расчёт текущее отклонение от целевого значения и рассчитывает управляющее воздействие на моторы пропорционально текущему отклонению.
  • «Д» — дифференциальный. Вычисляется изменение ошибки за некоторый промежуток времени (у нас — за 5 мс). Далее необходимое управление считается как произведение этого изменения на некий коэффициент, то есть управляющее воздействие пропорционально изменению ошибки.

FIXME

 
  • Очень полезная идея — найти не просто перекрёсток, а увидеть его несколько раз. Это исключит случайные ошибки. Чтобы робот остановился, он должен 3 раза увидеть перекрёсток. Для этого в коде используется переменная с.
  • Чтобы моторы не пищали, нужно добавить ограничения. Например, если регулятор посчитал, что мощность будет 30, то сказать, что 0. Верхнее значение тоже лучше ограничить.
  • В конце процедур с ездой робота нужно обязательно добавлять задержку, чтобы компенсировать инерцию. Ведь робот не может сразу остановиться, как вкопанный. Пускай растормаживается плавнее.

Дальномеры уже разбирали, поэтому просто повторим наши любимые аппроксимированные формулы. ;-)

FIXME

 

А теперь три поворота: направо, налево и разворот на 180° (по часовой стрелке).

FIXME

 

Обрати внимание, что кучу кода здесь можно «копировать-вставить». Учись тиражировать похожие процедуры максимально быстро.

Очень помогут две дополнительные процедуры: взять кубик и положить его. При этом робот при старте находится на перекрёстке.

FIXME

 
  • В параметре x будем указывать номер перекрёстка (от 0 до 3). Это важно, потому что на последнем перекрёстке с индексом 3 не всегда нужны действия.
  • Все числовые значения счётчиков энкодеров придётся находить экспериментально и самостоятельно. Все моторы в этом плане отличаются, какой попадётся тебе — неизвестно.

А теперь — функция проверки наличия объектов справа и слева, когда робот стоит на перекрёстке.

Идея такая:

  1. Делаем нужное количество измерений (n) и считаем, сколько раз робот увидел объект (то есть сравниваем с каким-то числом).
  2. Если он увидел больше, чем половина количества измерений, то там точно есть объект.

Напомним, что для ИК-дальномера объект — это кубик, а для УЗ-дальномера — «дырка» в стене из кубиков.

С++ не Python, и на олимпиаде не будет времени делать вывод нескольких результатов. Поэтому нужно в один числовой результат спрятать (закодировать) сразу два числа. Раз УЗ-дальномер находится слева, а ИК — справа, то и кодировать надо аналогично.

Например, так:

// rez - это специальный код; может иметь 4 значения:
// 0 - слева кубик, справа его нет (по умолчанию)
// 1 - слева кубик, справа есть кубик
// 10 - слева нет кубика, справа нет кубика
// 11 - слева нет кубика, справа есть кубик

В итоге у нас получится такая функция.

FIXME

 

Полученную функцию стоит потестировать, например:

void testCheckDist() {
  while (1) {
    int find = checkDist(30, 200, 10);
     Serial.print("Rez: ");
     Serial.println(find);
     if (find / 10 == 1) blinkRed(200, 2);
     if (find % 10 == 1) blinkGreen(200, 2);
     delay(2000);
  }
}

Итак, нам осталось доделать только три процедуры.

FIXME

 

15. Основной алгоритм

Вся подготовительная часть написана, осталось составить основной алгоритм.

Если вкратце:

  • Двигаться от перекрёстка к перекрёстку, запоминая, где кубик и где свободная ячейка.
  • Переставлять кубик в эти свободные ячейки.
  • Зелёный кубик должен оказаться в зелёной ячейке в том же ряду, в котором первоначально был кубик.

Начинаем составлять программу! Все нюансы — в комментариях к коду.

FIXME

 

Посмотри демонстрационное видео с примером работы робота.

16. Хьюстон, у нас проблема…

Крылатая фраза отсылает нас не только к проблемам во время важной миссии, но и ко второму значению слова «problem» — серьёзная задачка.

Вот и в нашем алгоритме есть одна проблема — кубику в розовую зону никак нельзя!

Внимательно посмотри на фото и попробуй решить задачу самостоятельно.

17. Задание регионального этапа для 10-х классов

Задание

Участнику необходимо собрать робота, составить его электрическую принципиальную схему Э3, написать и отладить программу робота в соответствии с задачами.

Рисунок 1. Пример начального расположения кубиков

Задачи для робота

  • Начать движение в зоне старта.
  • Двигаться по линии с перекрёстками и перемещать зелёные кубы между пустыми квадратными зонами.
  • Разместить 3 зелёных куба в зелёных зонах, расположенных на одной горизонтали с любым из зелёных кубов, по одному в каждой зоне.
  • Синие кубы не должны покидать квадратные зоны.
  • Зелёные кубы должны перемещаться от одной белой квадратной зоны до другой, ограниченной красной линией, и не должны оказаться полностью внутри зоны, окрашенной в розовый цвет.
  • Робот не должен оказаться внутри двух жёлтых зон одновременно, переехав колёсами из одной в другую. Перемещение из одной жёлтой зоны в другую возможно только через белую зону в верхней части полигона. Соседние жёлтые зоны разделены между собой розовой зоной.
  • Доставив зелёные кубы в зелёную зону, робот перемещается в зону финиша и останавливается.

Рисунок 2. Пример правильного выполнения задания

Примечания

  • Размеры робота на старте не должны превышать 300×300×300 мм, в процессе выполнения задания размеры робота могут увеличиться.
  • Перед стартом робота не допускается ввод в контроллер данных о расположении кубов. Расположение кубов определяется жребием.
  • Куб считается размещённым в квадратной зоне любого цвета, если любая часть его вертикальной проекции находится над этой зоной.
  • Если зелёный куб оказался внутри зоны, окрашенной в розовый цвет, попытка останавливается.
  • Если робот переехал любым колесом из одной жёлтой зоны в другую через розовую зону, попытка останавливается.
  • Куб считается расположенным в розовой зоне, если он своей вертикальной проекцией полностью находится над розовой зоной.
  • Робот считается расположенным в жёлтой зоне, если любая часть его вертикальной проекции находится над жёлтой зоной.
  • Чёрные линии, проходящие сквозь жёлтую зону, являются частью жёлтой зоны. Чёрная линия, проходящая сквозь розовую зону, считается частью розовой зоны.
  • Робот считается находящимся в зоне старта, если он своей вертикальной проекцией полностью находится внутри зоны, включая провода и все элементы конструкции.
  • Робот считается находящимся в зоне финиша, если он любым колесом соприкасается с зоной финиша.
  • Цветные линии, ограничивающие зоны старта и финиша, являются частью этих зон.

Рисунок 3. Пример перемещения робота и зелёного куба

Обрати внимание, что в этом задании достаточно одного дальномера. Конечно, лучше использовать инфракрасный, но мы выберем ультразвуковой, так как он уже установлен на роботе с нужной стороны.

Не стоит находить закономерности сразу для трёх вертикальных зон. Лучше проходить их последовательно: 1, 2, 3.

Советы по обработке зон роботом:

  1. Первую зону лучше сперва просканировать сверху вниз, чтобы узнать расположение кубика слева.
  2. Вторую зону можно разделить: обработать 1 линию снизу, затем 2 и 3 (они похожи), а под конец — верхний ряд.
  3. Третью зону лучше тоже обрабатывать снизу вверх по ходу движения робота.

С таким подходом в нашем алгоритме будет чуть меньше действий.

Например, если начать обрабатывать первую зону снизу, то зелёные кубики придётся отвозить чуть назад (во всех случаях, кроме всего одного). А третью зону, наоборот, лучше начать снизу. Переносить кубики справа налево будет проще.

Самая сложная зона — вторая. Поэтому и стоит разделить её на 3 части (отмечены зелёными прямоугольниками), чтобы не запутаться.

Попробуй проговорить последовательность действий: как робот поедет по первой зоне, как проверит объект, что будет делать при обнаружении/отсутствии объекта. И так далее.

Для решения этой задачи нам надо изменить и дописать некоторые процедуры. Кроме того, нам пригодятся три массива: один для хранения данных о кубиках слева (по ходу движения робота) и ещё два массива для кубиков справа (второй и третий вертикальные ряды). Логично, что после обработки первой зоны робот уже узнает, как расположены кубики справа во второй зоне. Аналогично, обработав вторую зону, он будет знать, где кубики в третьей зоне справа (а слева там заведомо пустые ячейки).

Добавим в блок к глобальным переменным описание массивов.

// кодируем наличие объектов в ячейках
// 0 - пусто в ячейке; 1 - синий кубик; 2 - зелёный кубик
int left[4] = { 0, 0, 0, 0 }; // все места/зоны по умолчанию пусты
int right2[4] = { 0, 0, 0, 0 }; // все места/зоны по умолчанию пусты
int right3[4] = { 0, 0, 0, 0 }; // все места/зоны по умолчанию пусты

Немного скорректируем три процедуры.

FIXME

 

И добавим одну новую: сканирование ячейки ультразвуковым дальномером с запоминанием в массив, какой кубик в ней будет. Если ячейка занята, то ставим «1» (синий куб), если свободна, то записываем «2» (будет зелёный).

FIXME

 

Сделай перерыв и поразмышляй, как ты будешь кодить, в какой последовательности. Заряди аккумуляторы, осмотри робота. Пускай информация уляжется в голове.

Выдели день, когда ты сможешь позаниматься 3 часа, и попробуй написать алгоритм самостоятельно. Прочувствуй, сколько времени тебе реально потребуется. Это важный этап в подготовке.

А теперь уточнение: реши не всю задачу. Просто захвати и перевези один ближайший кубик — естественно, соблюдая все условия задачи и правила перестановки. Знаешь, почему?

Посмотри на критерии оценки. Сколько баллов ты получишь, если перевезёшь всего 1 куб?

3+2+2+3+4 = 14. За код будет 5 баллов. Прибавь сюда проект (+20) и теорию (+10), и получится уже 49 баллов. Напомним, что на заключительный этап для 10-х классов в 2025 проходной балл был 46!

В итоге нужно проверить, за какое время ты напишешь программу, чтобы робот просто перевёз кубик. Никуда не смотря и ничего не анализируя, а только двигаясь от перекрёстка до перекрёстка.

Напоминаем, что за схему Э3 дают 2 балла.

  • Изучи пример оформления принципиальной электрической схемы Э3 на картинке выше (кликни, чтобы увеличить).
  • Потренируйся чертить схему для своего робота и засеки, сколько времени у тебя на это уходит.

А вот и долгожданный итоговый алгоритм полного решения задания для 10-х классов. Он, конечно, не до конца оптимизирован.

Как всё сделаешь и проверишь на практике, обязательно займись поиском улучшений. Большое счастье, если внимательная шлифовка алгоритма приносит тебе удовольствие. Если нет — это всё равно очень полезное умение, которым не стоит пренебрегать!

FIXME

 

Кстати, робот на видео с решением не укладывается в 3 минуты. Конечно, нужно уменьшить все задержки.

  • При уменьшении задержки на 100 мс у тебя получится выигрыш сразу в полминуты на всём алгоритме.
  • Стоит вывести скорость в глобальную переменную: пара кликов, и все действия ускорились.

18. Задание регионального этапа для 11-х классов

Задание

Участнику необходимо собрать робота, составить его электрическую принципиальную схему Э3, написать и отладить программу робота в соответствии с задачами.

Рисунок 1. Пример начального расположения кубиков

Задачи для робота

  • Начать движение в зоне старта.
  • Двигаться по линии с перекрёстками и перемещать зелёные кубы между пустыми квадратными зонами.
  • Разместить 2 зелёных куба в зелёных зонах, расположенных на одной горизонтали с любым из зелёных кубов, по одному в каждой зоне.
  • Синие кубы не должны покидать квадратные зоны.
  • Зелёные кубы должны перемещаться от одной белой квадратной зоны до другой, ограниченной красной линией, и не должны оказаться полностью внутри зоны, окрашенной в розовый цвет.
  • Робот не должен оказаться внутри двух жёлтых зон одновременно, переехав колёсами из одной в другую. Перемещение из одной жёлтой зоны в другую возможно только через белую зону в верхней части полигона. Соседние жёлтые зоны разделены между собой розовой зоной.
  • Доставив зелёные кубы в зелёную зону, робот перемещается в зону финиша и останавливается.

Рисунок 2. Пример правильного выполнения задания

Примечания

  • Размеры робота на старте не должны превышать 300×300×300 мм, в процессе выполнения задания размеры робота могут увеличиться.
  • Перед стартом робота не допускается ввод в контроллер данных о расположении кубов. Расположение кубов определяется жребием.
  • Куб считается размещённым в квадратной зоне любого цвета, если любая часть его вертикальной проекции находится над этой зоной.
  • Если зелёный куб оказался внутри зоны, окрашенной в розовый цвет, попытка останавливается.
  • Если робот переехал любым колесом из одной жёлтой зоны в другую через розовую зону, попытка останавливается.
  • Куб считается расположенным в розовой зоне, если он своей вертикальной проекцией полностью находится над розовой зоной.
  • Робот считается расположенным в жёлтой зоне, если любая часть его вертикальной проекции находится над жёлтой зоной.
  • Чёрные линии, проходящие сквозь жёлтую зону, являются частью жёлтой зоны.
  • Чёрная линия, проходящая сквозь розовую зону, считается частью розовой зоны.
  • Робот считается находящимся в зоне старта, если он своей вертикальной проекцией полностью находится внутри зоны, включая провода и все элементы конструкции.
  • Робот считается находящимся в зоне финиша, если он любым колесом соприкасается с зоной финиша.
  • Цветные линии, ограничивающие зоны старта и финиша, являются частью этих зон.

Рисунок 3. Пример перемещения робота и зелёного куба

В принципе, все необходимые процедуры уже написаны. Скорее всего, ты уже примерно представляешь итоговый алгоритм.

Однако давай разобьём задачу на две:

  1. Перевозка одного кубика.
  2. Перевозка двух кубиков.

Если корректно перевезти даже один кубик, можно заработать приличное количество баллов!

Посмотрим на схему и поразмыслим, как можно упрощённо решить первую задачу.

  • Использовать один датчик, который справа.
  • Сканировать две ячейки, где должны находиться зелёные кубики. Один точно попадётся.
  • Если найдётся кубик — захватить, но ехать всё равно до третьего перекрёстка.
  • Развернуться.
  • Сканировать теперь три ячейки справа, где синие кубики. Одна из них точно должна быть пустой.
  • Когда обнаружена пустая ячейка, положить туда захваченный зелёный кубик.
  • Переехать во вторую зону.
  • Захватить этот зелёный кубик (мы уже знаем, где он).
  • Доехать до третьего перекрёстка и развернуться.
  • И так далее.

Используя полученные знания, реализуй обе задачи: перевезти один кубик и перевезти два кубика.

19. Для тренировки

Для тренировок можно использовать вот такое поле.

Задание 1. Проехать роботом трассу и расставить кубики на дальние перекрёстки.

Задание 2. Проехать роботом трассу и расставить кубики на ближние перекрёстки.

Задание 3. Проехать роботом трассу и привезти все непарные кубики в зону старта.

Задание 4. Придумай какое-нибудь задание и найди решение самостоятельно.

Благодарность автору

«Амперка» выражает особую благодарность преподавателю Денису Геннадьевичу Копосову, чьё учебное пособие «Робототехника: подготовка к практическому туру регионального этапа Всероссийской олимпиады школьников» легло в основу данного практикума.

Все материалы использованы с разрешения автора. При перепечатке обязательна ссылка на оригинал.

Ресурсы

Файлы

Навигация

Полезные ссылки