Во второй части практикума разберёмся с примерами олимпиадных заданий регионального этапа ВсОШ за 2023–2024 учебный год. А наш тренировочный робот «Олимпиец» на Arduino научится ровно ездить без линии за счёт моторов с энкодерами. Поехали!
Это полезно
Начнём с разбора задания для девятиклассников, которое было на практической части регионального этапа ВсОШ по робототехнике 2023/2024 года.
Участнику необходимо собрать робота, составить его электрическую структурную схему Э1, написать и отладить следующую программу: робот в автономном режиме перемещает объекты, установленные в правой части полигона, согласно шаблону в левой части (рисунки 1 и 2).
Рисунок 1. Пример начального расположения кубиков
Рисунок 2. Пример правильного выполнения задания
Уже представил в голове, как робот будет выполнять все действия?
Начинаем разбираться. Взгляни на фотографию полигона. В центре установлена стена цилиндрической формы. Перекрёстки только в зонах с номерками и в центре.
Получается, что помимо стандартных действий, знакомых тебе с первой части практикума, робот должен уметь: останавливаться у стены, двигаясь по линии; двигаться вдоль стены, находя чёрные линии.
Исходя из того, что должен уметь робот, и будем его собирать:
Помни, что у тебя буквально 5 минут на чтение задания и концепцию робота для его решения. После этого нужно сразу приступать к сборке.
Ознакомься с популярными платами по первой части практикума.
Обязательно прочитай документацию и вникни в простые примеры работы:
Главная новинка во второй части практикума — моторы с энкодерами JA12-N20B.
Коллекторный (щёточный) мотор собран на двигателе N20. На вал двигателя установлен съёмный металлический соосный (оси на одной линии) редуктор, состоящий из нескольких ступеней зубчатых передач. Редуктор задаёт такое передаточное соотношение между валом мотора и валом редуктора, при котором снижается скорость вращения и повышается крутящий момент на валу редуктора.
Мотор способен вращаться в двух направлениях: по часовой и против часовой стрелки. Чтобы сменить направление вращения, нужно поменять полярность напряжения на контактах двигателя.
Скорость вращения вала редуктора можно регулировать изменением входного напряжения на моторе. Для этого используются контакты 5 и 6, работающие в режиме широтно-импульсной модуляции, или сокращённо ШИМ (англ. PWM, pulse-width modulation). ШИМ-сигнал очень часто применяют для управления мощностью, скоростью, яркостью и т. д.
Основная идея вот в чём: цифровой выход контроллера в режиме ШИМ выдаёт на управляемое устройство серию быстрых импульсов включения и выключения. При этом время включения (рабочий цикл) импульса и определяет подаваемую мощность от 0 до 100%.
Пример ШИМ на 10% мощности
Пример ШИМ на 90% мощности
Энкодер изготовлен из цилиндрического магнита и двух однополярных цифровых датчиков магнитного поля (датчиков Холла). Размещён энкодер на валу мотора. Напряжение питания энкодера — 3,3–5 В.
С помощью энкодера можно отслеживать вращение вала, чтобы рассчитывать пройденное роботом расстояние, угловую скорость колеса и т. д.
Плата энкодера имеет шесть выводов:
Расцветки проводов могут отличаться, однако порядок у всех моторов JA12-N20B одинаковый. Визуально его удобно определять слева направо со стороны энкодера — как на иллюстрации ниже.
Когда мотор не вращается, на выходе энкодера С1 будет нуль.
Когда мотор начнёт вращение, то на С1 будет чередование 0 и 1.
Теоретически, мы можем это отслеживать, но на практике полно нюансов. Нельзя так просто подключить энкодер в аналоговый вход (A0–A5) или в цифровой и радоваться. А что, собственно, тут не так?
Итак, энкодер крепится на вал электромотора с обратной стороны. Он считает «обороты» не вала на выходе редуктора, а непосредственно вала мотора. При этом данный простой энкодер генерирует 12 импульсов за 1 оборот оси вала самого мотора. А скорость вращения вала достигает 6000 об/мин. Получилось 1200 импульсов в секунду — это много или мало?
Кто занимался лего-робототехникой, сразу должен насторожиться, вспоминая про 300 Гц при опросе датчиков у LEGO NXT. А тут у нас в 4 раза больше импульсов и процессор в 3 раза медленнее. Не будет работать, однозначно.
Ардуинка не может заниматься параллельными вычислениями. Она же обычно не просто отслеживает один порт, но ещё в этот момент что-то делает, считает, обрабатывает, отправляет что-то в последовательный порт. К тому же, есть паузы командой delay.
И ладно бы, если контроллер пропускал импульсы с энкодера равномерно — он же будет пропускать их случайно. А если каждый раз при одинаковом количестве оборотов мы будем получать разные значения — то теряется весь смысл энкодера…
Что делать? Выход из этой ситуации — использование прерываний.
Прерывание — это сигнал, который сообщает процессору о наступлении какого-либо события, которое требует незамедлительного внимания. Процессор должен отреагировать на этот сигнал, прервав выполнение текущих инструкций и передав управление обработчику прерывания (англ. ISR, Interrupt Service Routine).
Обработчик — это обычная функция, которую мы пишем сами и помещаем туда код для реакции на событие. После обслуживания прерывания функция завершает свою работу, и процессор продолжает выполнять код с того места, на котором остановился.
Плата Arduino Uno имеет всего два прерывания на контактах 2 и 3, поэтому контакты энкодеров С1 будем подключить именнно к этим пинам.
А ещё 2 контакта С2 на каждом энкодере куда девать? Просто не будем их никуда подключать. Они бы помогли нам отслеживать направление вращения моторов, но в этом сигнале сейчас нет необходимости.
Кстати, у контроллера Arduino Leonardo / Iskra Neo целых 5 контактов с прерываниями: 0, 1, 2, 3, 7, но тогда роботу будет нужна другая плата для моторов. И нужно помнить, что пины 0 и 1 предназначены для обмена данными через последовательный порт — если их задействовать для чего-то ещё, будут проблемы с отладкой.
Предлагаем изучить сборку робота по первой части практикума. Здесь действуют всё те же правила и рекомендации, что и раньше.
Тебе стоит записать всю последовательность действий и чётко ей следовать:
Обрати внимание, что схему лучше создать и начертить сразу, как соберёшь робота. Ещё даже до проверки.
А сейчас несколько слов о сложностях и целях. Морально готовься, что у тебя не будет под рукой готовых ответов, как в каком-нибудь ГДЗ.
Понятно, что все хотят стать победителями, или хотя бы призёрами. Строго говоря, для этого нужно не сидеть и думать о призе, а уметь находить ответ на каверзный вопрос: «Что же должно получиться?»
Попробуй прямо сейчас найти в интернете информацию о структурной и принципиальной схеме робота.
Как видишь, чтобы начертить такую штуку, недостаточно просто вызубрить какой-то шаблон. Всё это создаётся и правится на лету.
Поэтому лучше ставить такую цель: получить доступ к тем знаниям, которыми обладают лучшие робототехники.
Структурная схема в робототехнике — это графическое изображение, на котором показано взаимное расположение частей робота. На структурной схеме изображают основные функциональные части (элементы, устройства) и основные взаимосвязи между ними.
Принципиальная схема — графическое изображение для передачи с помощью условных обозначений (пиктограмм) связей между элементами устройства. Она показывает, как соединяются между собой выводы реальных элементов.
Тебе дадут вот такой бланк на бумаге формата A3 или A4.
Девятиклассники должны начертить схему Э1, а вот учащиеся 10–11 классов — схему Э3. Поэтому есть смысл обзавестись радиотехнической линейкой, чтобы чуть-чуть быстрее всё начертить.
Итак, что же это за схемы такие? Мы уже разбирали этот вопрос в первой части практикума, поэтому вернись туда и освежи свои знания.
А теперь попроси (или распечатай сам) бланки и начерти две схемы своего робота — Э1 и Э3. Обязательно засеки время секундомером. По-хорошему, тебе надо научиться чертить схему за 5 минут.
Предлагаем снова обратиться к первой части практикума, где мы разобрали общие рекомендации по работе с Arduino IDE.
Напомним, что в этот момент у тебя ещё не до конца собран робот, а подключения не проверены. Сейчас этим займёмся!
HIGH на LOW (или наоборот).Если моторы успешно заработали, переходим далее.
Сейчас нужно проверить повороты на 0 и 90°, чтобы прикрутить захват.
Напомним, что у нас типовой аналоговый сервопривод с удержанием угла. Его диапазон вращения — от 0 до 180°.
Внутри сервопривода находится мотор, работающий от постоянного тока, управляющая электроника и редуктор. Положение выходного вала зависит от длины импульса.
При поступлении управляющего импульса электроника сервопривода генерирует мотору свой сигнал, длительность которого определяется датчиком обратной связи.
Импульс 544 мкс соответствует положению 0°, 1540 мкс — поворот в 90°, а 2400 мкс — в 180°. Нужно понимать, что поворот возможен только в пределах этой полуокружности.
А теперь сделаем плавное опускание и подъём захвата.
Конечно, надо написать отдельную команду, чтобы серво поворачивал в положение 90°, а после всё многократно протестировать (пока тренируешься и готовишься).
Можно использовать стандартную библиотеку Servo, которая входит в состав Arduino IDE. Но надо помнить, что при её использовании пропадает возможность использования ШИМ на контактах 9 и 10.
Как управлять сервоприводом через библиотеку Servo, можно посмотреть в примерах Arduino IDE: Examples → Servo → Sweep.
Всегда есть смысл посмотреть официальную документацию Arduino или её русскоязычные версии.
Ультразвук — звуковая волна, частота которой превышает диапазон слышимости человека. Люди могут слышать колебания частотой примерно до 20 000 Гц, которые звучат как высокий свист. Однако ультразвук частотой свыше 20 кГц попросту неслышен для человека.
Ультразвуковой дальномер HC-SR04 состоит из двух ультразвуковых преобразователей. Один из них работает как передатчик и посылает вперёд ультразвуковые импульсы частотой 40 кГц. Другой работает как приёмник и улавливает отражённые импульсы, что позволяет вычислить расстояние до объектов.
Такой датчик бесконтактно определяет расстояния от 2 до 400 см с точностью до 3 мм.
Для начала измерения триггерный вывод устанавливается в состояние HIGH на 10 мкс. В ответ датчик передаёт серию из восьми ультразвуковых импульсов частотой 40 кГц. Последовательность специально разработана так, чтобы приёмник мог отличить передаваемые импульсы от окружающего ультразвукового шума. Импульсы начинают распространяться по воздуху.
В это время на выводе ECHO устанавливается высокий уровень напряжения, чтобы инициировать обратный сигнал. Если эти импульсы не отражаются обратно, сигнал эха затухает и становится низким через 38 мс. Таким образом, импульс длительностью 38 мс указывает на отсутствие препятствий в зоне действия датчика.
Если эти импульсы отражаются обратно, то на выводе ECHO устанавливается низкий уровень и генерируется импульс шириной от 150 мкс до 25 мс, в зависимости от времени получения сигнала.
Для вычисления расстояния почти везде «попросят» разделить результат на 58. Строго говоря, это не слишком точная формула, где скорость звука 0,034 м/мкс округляют до 1/29. Такая аппроксимация (приближение) сильно упрощает вычисления ценой незначительного роста погрешности, а формулу запоминать намного проще.
abs.int. Нам такой точности хватит.Инфракрасный дальномер Sharp GP2Y0A21 предназначен для измерения расстояния до препятствий от 10 до 80 см. Рекомендуем почтать техническую документацию к датчику.
Посмотри на задержку перед измерением и на время отклика — это почти 120 мс на одно измерение. Также обрати внимание на зону детекции при максимальном расстоянии 80 см — датчик увидит объекты до 12 см.
Работать с этим датчиком довольно просто: подключаем к нему питание Vcc и землю GND, а вывод Vo — на аналоговый вход Ардуинки.
Аналоговый вход принимает значения от 0 до 1023. При этом можно не преобразовывать данные в сантиметры,
а просто сравнивать показания. Так проще и быстрее. Нам же не надо знать точное расстояние до объекта, а лишь определить наличие, или получить результат сравнения «ближе/дальше».
Примем в программе сокращения:
US — ultrasonic;IR — infrared;UE — ultrasonic (echo);UT — ultrasonic (trigger).Ты можешь придумать свои варианты самых коротких, но понятных названий для переменных. В идеале — 2–3 буквы, ведь на «правильные» длинные названия не будет времени.
При подготовке необходимо сделать простенький измерительный стенд, как на фотографии.
Используй кубики разных цветов. Важно учесть, что результат измерений несколько зависит от цвета объекта: тёмные цвета поглощают больше света. Ещё больше виляет форма объекта и, особенно, текстура материала, из которого сделан предмет: гладкий он или шершавый. Помни про эти нюансы.
Сделай для себя такую табличку. Здесь приведены показания для пластиковых кубиков. Если есть возможность, исследуй показания на деревянных разноцветных кубиках.
| Расстояние до объекта | Показания датчика | Что ставить в условии |
|---|---|---|
| 5–9 см | 600–640 | dist > 500 |
| 10 см | 430–470 | dist > 500 |
| 20 см | 220–250 | dist > 500 |
| 30 см | 140–180 | dist > 500 |
| Нет объекта | < 100 | dist < 100 |
В первой части практикума мы использовали доступные и популярные сенсоры на основе TCRT5000.
В этой части тоже будем пользоваться аналоговыми датчиками линии на оптопаре TCRT5000.
Результат работы сенсора — аналоговый сигнал. Чем светлее поверхность под сенсором, тем меньше выходное значение.
Основной фотоэлемент работает в инфракрасном спектре. На сенсоре установлен светодиод, который загорается, когда под датчиком светлая поверхность. Это удобно для диагностики и настройки.
Установленный на плате подстроечный резистор позволяет регулировать чувствительность сенсора в широких пределах. С его помощью можно откалибровать датчик линии под разные типы покрытия и различные условия внешнего освещения, а также выровнять показания датчиков на нашем роботе.
Пользоваться переменным резистором нужно очень аккуратно: излишними усилиями его можно отломать от платы.
Распиновка датчика линии:
GND микроконтроллера.Vcc.А0–А5).
Если тебе доводилось писать ПД-регулятор для Lego-робота, то там датчики показывали от 0 до 100. А тут от 0 до 1023, де ещё и с инверсией. Мозгу тяжело перестроиться. Поэтому есть смысл перевести показания в леговские [0…100].
Программная инверсия, чтобы на белом датчик показывал больше, а на чёрном меньше (диапазон от 0 до 1023):
dl = (1024 - analogRead(LL)) dr = (1024 - analogRead(LR))
Сжимаем диапазон до [0…100], но реально он будет меньше.
dl = (1024 - analogRead(LL))/10; dr = (1024 - analogRead(LR))/10;
Поскольку линии прямые, то лучше ещё сжать диапазон до [0…60]:
dl = (1024 - analogRead(LL))/16; dr = (1024 - analogRead(LR))/16;
Делитель можно подобрать так, как хочется, только нужно всё тщательно проверить над белым полем. Ещё стоит запомнить делитель для разных производителей.
Тут может встретиться неприятная проблема: если датчики показывают сильно разные значения, нужно получить доступ к подстроечному резистору. Для этого нужно быстренько открутить его со стойкой и расположить сбоку, как на рисунке. А дальше — крутить резистор очень медленно, по чуть-чуть.
Подстроечный резистор
Можно протестировать датчики до полной сборки робота. То есть подключаешь моторы и датчики линии и сразу их тестируешь, а уже потом подключаешь всё остальное. Выбирать оптимальную стратегию придётся тебе. А для этого надо много раз тренировать сборку/разборку робота и проверку сенсоров.
Вот мы и добрались до прерываний. Исследуй несколько примеров кода.
volatile, если её значение может быть изменено за пределами участка программы, где она объявлена. Например, функцией, которая обрабатывает соответствующее прерывание.attachInterrupt(interrupt, function, mode) задаёт функцию обработки внешнего прерывания.Когда будешь тестировать энкодер, просто аккуратно покрути колёса руками и посмотри на показания в мониторе последовательного порта.
Обрати внимание, что мы не отслеживаем направление вращения. В этом нет особого смысла, так как при движении робота что вперёд, что назад показания счётчиков будут увеличиваться.
Параметры attachInterrupt:
interrupt — задаёт номер прерывания или номер контакта.function — функция, вызываемая прерыванием. Она должна быть без параметров и не возвращать значений.mode — режим обработки прерывания.Возможные значения режима:
RISING (рост) — срабатывает при изменении сигнала с LOW на HIGH.FALLING (падение) — срабатывает при изменении сигнала с HIGH на LOW.CHANGE (изменение) — срабатывает при любом изменении сигнала с LOW на HIGH и наоборот.
Используя два счётчика, можно контролировать, какое расстояние проедет робот. Заставим его двигаться прямо, написав ПИД-регулятор, в котором отклонение (ошибка) — это разность значений левого и правого счётчика. А воздействовать регулятор будет на скорость моторов. Красота!
А ещё мы как-то забыли измерить диаметр колёс. Надо заполнить этот пробел, чтобы узнать, сколько робот проедет за 1 оборот колеса (и за половину оборота, за четверть).
Напишем пропорционально-интегрально-дифференциальный регулятор (ПИД-регулятор), чтобы робот мог прямолинейно двигаться без всякой линии.
Алгоритм такой: мы включаем моторы с желаемой скоростью, а регулятор (программа) отслеживает счётчики энкодеров. Если на одном колесе счётчик показывает большее значение, чем на другом, то этот мотор притормаживаем, а другому увеличиваем скорость.
Итак, познакомимся с основными понятиями систем автоматического управления.
Объект управления — это то, чем управляет система. В нашем случае — это сам робот.
Уставка (целевое значение) — это значение, за соблюдением которого «смотрит» система. Для нас целевое значение — нуль. Именно такая разность между левым и правым счётчиком должна быть при прямолинейном движении.
Рассогласование (ошибка, ошибка управления) — это отклонение текущего состояния от целевого. В нашем случае это разность в показаниях левого и правого счётчика.
Датчики — то, чем мы определяем рассогласование. Для нашего робота это энкодеры.
Обратная связь — сигнал с датчиков, на который опирается система управления, принимая решение об управляющем воздействии.
Регулятор — программа, которая поддерживает необходимые значения какой-либо величины или параметра. Именно регулятор «принимает решение», что сделать, чтобы контролируемый параметр вернулся к целевому значению. В нашем случае регулятор — это часть программы робота, благодаря которой он способен корректировать своё движение. Именно регулятор поддерживает нулевое значение разности показаний энкодеров, чтобы робот двигался прямо.
Управляющее воздействие (корректировка) — это действия, которые принимает система, чтобы вернуться к целевому значению. У нас это изменение скорости моторов робота.
Привод — часть системы, которая реализует управляющее воздействие. В нашем случае это моторы.
Буква «П» в названии ПИД-регулятора означает слово «пропорциональный». П-регулятор рассчитывает управляющее воздействие на моторы пропорционально текущему отклонению от целевого значения.
«Д» означает слово «дифференциальный». Д-регулятор работает следующим образом: вычисляется изменение ошибки за некоторый промежуток времени (у нас в коде — за 5 мс). Далее необходимое управление рассчитывается как произведение этого изменения на некий коэффициент. То есть управляющее воздействие становится пропорционально изменению ошибки.
«И» — от слова «интегральный». И-регулятор рассчитывает управляющее воздействие как произведение некоторого коэффициента на сумму всех ошибок за указанное время, то есть управляющее воздействие пропорционально сумме ошибок. В силу того, что сумма ошибок значительно больше, чем просто ошибка, берётся очень маленький коэффициент.
Объединяя эти регуляторы воедино, получим ПИД-регулятор из трёх составляющих:
ПИД-регулятор используют примерно в 90% всех систем автоматического управления. Поскольку он работает только с числами, его можно применять практически к любым процессам, которые можно описать математикой.
Невозможно заниматься отладкой, если на роботе нет динамика или светодиода для простенькой индикации. Поэтому установим роботу дополнительный светодиод.
Напомним, что:
8;GND.
Если макетная плата приклеена к плате расширения, то подписи контактов не видно. Запомни визуальные ориентиры на плате, где расположены важные пины:
8 находится скраю группы колодок.GND — это 2 и 3 контакты после «разрыва».5V находится левее земли.
#define LED 8 //пин для светодиода pinMode(LED, OUTPUT); // светодиод очень полезен для отладки // помигать светодиодом для отладки void blink(int dt, int n) { for (int j = 0; j < n; j++) { digitalWrite(LED, HIGH); delay(dt); digitalWrite(LED, LOW); delay(dt); } }
Вот мы и протестировали все модули, пора браться за программу.
Уже придумал алгоритм? В голове его чётко представляешь? Посмотри-ка видео с демонстрацией, как робот должен выполнять задание.
Выглядит не оптимально, но мы сделаем лучше. Напомним, что ты сейчас готовишься, и чем больше вариантов попробуешь — тем легче будет на самом соревновании.
Давай «проговорим» алгоритм, чтобы понять, какие надо написать процедуры.
Это примерное изложение алгоритма в общих чертах. А теперь укрупним действия до трёх:
Это и будет первая часть алгоритма.
Конечно, есть ещё большущая вторая часть с обработкой 7 установленных кубиков, из которых часть нужно сдвинуть, а один привезти в зону старта. Сделаем это позже.
Переходим к написанию необходимых процедур для управления роботом:
Сперва пишем все define; пытаемся спрогнозировать, какие переменные будут нужны; задаём режим работы контактов Arduino; создаём общий шаблон программы.
Напомним, что процесс компиляции прошивки очень непростой и проходит в несколько этапов.
Один из первых — работа препроцессора. Препроцессор — это специальная программа, которая является частью компилятора языка. Она предназначена для предварительной обработки текста программы. Препроцессор позволяет включать в текст программы файлы и вводить макроопределения. Работа препроцессора осуществляется с помощью специальных директив (указаний), которые отмечаются знаком решетки #. В конце строк такими с директивами точку с запятой можно не ставить.
Трансляция в программировании — преобразование программы, представленной на одном из языков программирования, в программу на другом языке. Цель трансляции — преобразование текста с одного языка на язык, понятный адресату.
При трансляции компьютерной программы адресатом может быть:
Препроцессору можно давать команды, которые он выполнит перед компиляцией кода прошивки: это может быть подключение файлов, замена текста, условные конструкции и так далее. Также у препроцессора есть макросы, которые позволяют добавлять в код некоторые интересные вещи.
#define — это команда препроцессору заменить один набор символов на другой. Например, команда #define MOTOR_SPEED 50 при компиляции заменит все встречающиеся в коде MOTOR_SPEED числом 50.
Если ничего не писать после указания первого набора символов, препроцессор так и заменит их на «ничего», то есть #define MOTOR_SPEED просто удалит из кода все сочетания MOTOR_SPEED.
Директива #define позволяет вводить в текст программы константы. Это очень удобно, но ведь к именам констант есть определённые требования.
Перечислим наиболее популярные виды нотации для оформления кода.
| Нотация | Описание | Пример |
|---|---|---|
| lowerCamelCase | Первый символ в нижнем регистре, последующие слова начинаются с заглавной буквы | camelCaseExample |
| UpperCamelCase | Все слова начинаются с заглавной буквы. | CamelCaseExample |
| Snake Case | Все слова разделяются подчеркиванием и пишутся в нижнем регистре. | snake_case_example |
| Screaming Snake Case | Все слова разделяются подчеркиванием и пишутся в верхнем регистре. | SCREAMING_SNAKE_CASE |
| Kebab Case | Все слова разделяются дефисом и пишутся в нижнем регистре. | kebab-case-example |
| Train Case | Все слова разделяются дефисом и пишутся с заглавной буквы. | Train-Case-Example |
| Dot Notation | Все слова разделяются точкой и обычно пишутся в нижнем регистре. | dot.notation.example |
SCREAMING_SNAKE_CASE.MOTOR_RIGHT_DIRECTION_PIN пиши просто DR.
pulseIn и все задержки для сервопривода, а также команды работы с прерываниями.Так как выезд из стартовой зоны оценивается в несколько баллов, то напишем его первым. Нам нужно, чтобы робот двигался по линии и останавливался у объекта.
if в пределах 0–200. Иначе робот разгонится и слетит с линии.c.Для движения по линии будем использовать ПД-регулятор.
Первоначально договоримся, что показания датчиков одинаковые. Их разность равна нулю. Это целевое значение или уставка.
Если показания разные, эту разность и зафиксируем, как целевое значение.
Ошибкой будем называть отклонение от уставки. Фактически, это разность показаний левого и правого датчика линии.
Тогда мощности моторов будем вычислять по формулам:
er = dl − dr;
pl = sl + kp ⋅ er + kd ⋅ (er + ero);
pr = sr − kp ⋅ er + kd ⋅ (er − ero);
Кстати, датчики нужно располагать на высоте 1,5–2 см над полем. При этом расстояние между датчиками 3–3,5 см.
Пока готовишься и тренируешься, не забудь протестировать робота с разными коэффициентами ПД-регулятора. Тебе нужно интуитивно прочувствовать, как изменение каждого коэффициента влияет на поведение робота и характер его движения.
После того как робот остановился у стены на расстоянии 12–14 см, надо повернуть направо примерно на 90°. Робот может поворачивать вправо, вращая левым колесом.
Также роботу предстоит поворачивать к зонам с шаблонными кубиками, как на схеме.
Обрати внимание, что первым делом нужно сбросить счётчик, чтобы можно было указать, сколько должен «накрутить» моторчик перед остановкой робота.
Двигаться вдоль стены нужно до тех пор, пока датчик линии не увидит чёрный луч (линию, исходящую от стены).
Целевым значением будет расстояние в 12 см. Можно подобрать нужное значение в диапазоне 10–15 см. Нам больше понравился результат при 12 см.
Чтобы исключить случайную остановку, добавляется пятикратная проверка условия, что датчик линии увидел что-то тёмное.
Также робот не должен опрашивать датчик в самом начале движения вдоль стены. Мы сделали движение на определённое время dtLine.
Итоговый алгоритм проезда по кругу будет примерно таким:
Следующая процедура — движение до перекрёстка. Кстати, а сколько раз роботу придётся выполнять это действие?
Если внимательно посмотреть на схему, то легко посчитать. 4 кубика вытолкнуть по шаблону, 1 захватить и привезти в центр, 2 раза побывать в центре. Итого — 7 раз.
Этот код можно упростить и уменьшить, но на олимпиаде лучше писать так, чтобы как можно чаще пользоваться функцией «копировать/вставить».
Итак, у нас 7 мест и 4 кубика в них. У каждого места есть свой номер.
Получается, нам удобно использовать массивы.
Массив (array) — именованный набор однотипных переменных с доступом к отдельным элементам по общему имени массива и их индексу.
Примеры объявлений массива:
// указываем количество ячеек bool cubes [7]; // указываем содержимое ячеек, компилятор сам посчитает их количество int pins[] = {2, 4, 8, 3, 6,}; // указываем и то и то, количество ячеек в [ ] должно совпадать с { } float distance [3] = {0.2, 0.4, -8.5};
i, то у итогового — 6 − i. Поэтому при сканировании левой части поля можно сразу преобразовывать номера массива, чтобы было проще потом обрабатывать.last).Осталась последняя процедура — движение назад, которое нужно для обработки правой части.
Учти, что опрос энкодеров идёт постоянно. Важно вовремя обнулять переменные, где хранятся значения. Если обнулить их в начале движения до перекрёстка, то будет известно, на сколько вернуться назад — ведь счётчики всё сохранили. В этом вся суть метода.
Действия робота по схеме на рисунке:
turnRightFwd(100, turn); delay(dt); // толкаем кубик и возвращаемся ... countL = 0; countR = 0; toCross(50, 50); delay(dt); int limL = countL; int limR = countR; int limit = (limL + limR) / 2; back(60, limit - 20); delay(dt); turnRightBack(100, turn); delay(dt);
Теперь можно дописывать основной алгоритм с учётом созданных процедур.
int (от англ. integer — целое число). Переменные этого типа занимают 2 байта памяти и могут хранить числа от −32768 до 32767 (от −2¹⁵ до 2¹⁵−1).void setup() { Serial.begin(9600); delay(3000); int x; x = -32768; x = x - 1; Serial.print("1. x = "); Serial.println(x); x = 32767; x = x + 1; Serial.print("2. x = "); Serial.println(x); } void loop() { }
Теперь будем улучшать и робота, и алгоритм его работы. Посмотри видео с примером, как ещё можно было выполнить задание.
Чтобы уменьшить количество действий, есть смысл установить инфракрасный дальномер сбоку, а не спереди.
При этом мы уже не сможем использовать движение по линии до объекта. Нам понадобится процедура движения по линии на определённое расстояние (точнее, на показания энкодеров).
Добавим ещё одну оптимизацию в алгоритме: если в последней зоне стоит непарный кубик, можно его захватить и не ездить второй круг.
В итоге программа примет следующий вид.
Здесь ещё есть пространство для улучшения алгоритма.
Попробуй придумать и реализовать свою идею по улучшению!
Участнику необходимо собрать робота, составить его электрическую принципиальную схему Э3, написать и отладить следующую программу: робот в автономном режиме перемещает объекты, установленные в правой части полигона, согласно шаблону в левой части (рисунки 1 и 2).
Рисунок 1. Пример начального расположения кубиков
Рисунок 2. Пример правильного выполнения задания
Первая часть — сканирование — остаётся неизменной. А в правой части полигона нужно переставить два кубика, при этом сканировать правую часть не нужно. Надо придумать алгоритм, как это сделать.
Сразу можно сказать, что при отладке левая часть полигона не используется. Расположение вводим в массив и тестируем всё только на правой половине.
При этом мы точно знаем, что один из трёх кубиков уже стоит правильно. То есть роботу нужно проанализировать местоположение всех трёх кубиков, а перевезти только два из них.
Движение робота будет всегда против часовой стрелки. Конечно, он может развернуться, но тогда программа сильно усложнится, чего стоит всячески избегать.
Чтобы упростить общую программу, напишем процедуры по захвату и отпусканию кубика, когда наш робот стоит напротив нужной зоны.
Каков общий план обработки кубиков?
Обработка одного кубика не будет какой-то сложной или длинной. Например, сделаем так.
Не так уж много кода. Правда, самих кубиков три, и простое «копировать/вставить» уже не прокатит.
Сведём воедино весь алгоритм обработки.
cubes[] (0 и 1, справа-налево) и больше не сканировать зелёную зону.Подсказка: напиши для этого отдельную процедуру движения вдоль стены с проверкой показаний ИК-дальномера. Максимальные показания датчика во время движения позволят понять, есть там объект или нет.
Наконец, рассмотрим вариант задания ВсОШ 2023/2024 года для одиннадцатых классов.
Участнику необходимо собрать робота, составить его электрическую принципиальную схему Э3, написать и отладить следующую программу: робот в автономном режиме перемещает объекты в обеих частях полигона зеркально симметрично, в каждой части в три соседние ячейки (рисунки 1 и 2).
Рисунок 1. Пример начального расположения кубиков
Рисунок 2. Пример правильного выполнения задания
Сразу скажем, что правильных решений будет много. На рисунке 2 использовалась идея, чтобы в синей зоне перенести всего один кубик, а в зелёной — два. Итого будет три переноса.
Кроме того, сканировать нужно только одну зону. Кубики расположены в местах, которые в соседней зоне пустуют. А вот алгоритм переноса кубиков не столь очевиден.
Лишний кубик, который нужно привезти на старт, расположен в зелёной зоне. Однако мы начнём не с зелёной зоны, а с синей. Почему?
Это маленькая хитрость: если в синей зоне переставить два кубика, то в зелёной нужно будет перенести один. А если в синей перенести один, то в зелёной — два.
Если посмотреть на схемы «до/после», можно понять общий алгоритм. Главная идея — зафиксировать место второго кубика синей зоны. Третий переставить на «место второго + 1», а первый — на «место второго − 1».
Внимательно изучи схемы. На них видно, как робот может переносить кубики.
А чтобы ещё лучше представить алгоритм движения, посмотри видео, как робот переставляет кубики в синей зоне.
Напомним, что кубики в зелёной зоне расположены там, где их нет в синей. Это значительно облегчит выполнение второй части. А ещё можно ввести счётчик, сколько кубиков переставлялось в синей зоне.
Вникни в пример кода для сортировки кубиков в синей зоне. Если что-то непонятно — ищи подсказки в комментариях.
Счётчик перемещений кубиков в синей зоне позволит оптимизировать обработку зелёной. Если было два перемещения, то в зелёной должно быть одно. Тогда первый кубик ставится в то место, где должен стоять второй в тройке.
Какой кубик привозить на старт, решается так: если четвёртый кубик по ходу движения не в тройке, то везём его. Если же он «занят», то везём второй по ходу движения робота в зелёной зоне.
Таким несложным алгоритмом мы закроем достаточное количество вариантов расположения кубиков.
В принципе, уже можно всё демонстрировать и радоваться. Однако нужно ещё дописать, как робот будет сортировать кубики в зелёной зоне, когда в синей зоне было всего одно перемещение кубика.
В третьей части практикума мы разберёмся, как запрограммировать робота-тележку для решения задач с перемещением грузов.
Благодарность автору
«Амперка» выражает особую благодарность преподавателю Денису Геннадьевичу Копосову, чьё учебное пособие «Робототехника: подготовка к практическому туру регионального этапа Всероссийской олимпиады школьников» легло в основу данного практикума.
Все материалы использованы с разрешения автора. При перепечатке обязательна ссылка на оригинал.