Содержание

Практикум для «Олимпийца» (Olympic Bot). Часть 2: моторы с энкодерами и ПИД-регулятор

Во второй части практикума разберёмся с примерами олимпиадных заданий регионального этапа ВсОШ за 2023–2024 учебный год. А наш тренировочный робот «Олимпиец» на Arduino научится ровно ездить без линии за счёт моторов с энкодерами. Поехали!

 Amperka Olympic Bot

1. Читаем задание

Начнём с разбора задания для девятиклассников, которое было на практической части регионального этапа ВсОШ по робототехнике 2023/2024 года.

Задание

Участнику необходимо собрать робота, составить его электрическую структурную схему Э1, написать и отладить следующую программу: робот в автономном режиме перемещает объекты, установленные в правой части полигона, согласно шаблону в левой части (рисунки 1 и 2).

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

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

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

Примечания

Уже представил в голове, как робот будет выполнять все действия?

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

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

Исходя из того, что должен уметь робот, и будем его собирать:

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

2. Немного о платах

Ознакомься с популярными платами по первой части практикума.

Контроллер Iskra Uno (Arduino Uno)

 Управляющая плата Iskra Uno

Плата расширения Motor Shield

 Плата расширения Motor Shield

Обязательно прочитай документацию и вникни в простые примеры работы:

3. Мотор с энкодером

Главная новинка во второй части практикума — моторы с энкодерами JA12-N20B.

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

Мотор способен вращаться в двух направлениях: по часовой и против часовой стрелки. Чтобы сменить направление вращения, нужно поменять полярность напряжения на контактах двигателя.

Скорость вращения вала редуктора можно регулировать изменением входного напряжения на моторе. Для этого используются контакты 5 и 6, работающие в режиме широтно-импульсной модуляции, или сокращённо ШИМ (англ. PWM, pulse-width modulation). ШИМ-сигнал очень часто применяют для управления мощностью, скоростью, яркостью и т. д.

Основная идея вот в чём: цифровой выход контроллера в режиме ШИМ выдаёт на управляемое устройство серию быстрых импульсов включения и выключения. При этом время включения (рабочий цикл) импульса и определяет подаваемую мощность от 0 до 100%.

Пример ШИМ на 10% мощности

Пример ШИМ на 90% мощности

Энкодер изготовлен из цилиндрического магнита и двух однополярных цифровых датчиков магнитного поля (датчиков Холла). Размещён энкодер на валу мотора. Напряжение питания энкодера — 3,3–5 В.

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

Плата энкодера имеет шесть выводов:

Расцветки проводов могут отличаться, однако порядок у всех моторов JA12-N20B одинаковый. Визуально его удобно определять слева направо со стороны энкодера — как на иллюстрации ниже.

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

Когда мотор не вращается, на выходе энкодера С1 будет нуль.

Когда мотор начнёт вращение, то на С1 будет чередование 0 и 1.

Теоретически, мы можем это отслеживать, но на практике полно нюансов. Нельзя так просто подключить энкодер в аналоговый вход (A0A5) или в цифровой и радоваться. А что, собственно, тут не так?

Итак, энкодер крепится на вал электромотора с обратной стороны. Он считает «обороты» не вала на выходе редуктора, а непосредственно вала мотора. При этом данный простой энкодер генерирует 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 предназначены для обмена данными через последовательный порт — если их задействовать для чего-то ещё, будут проблемы с отладкой.

4. Сборка робота (начало)

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

  • Не применяй «грубую силу» при креплении и затяжке элементов.
  • Пользуйся цветными проводами так, чтобы тебе было легко отличить их по смыслу: красный — питание, чёрный — земля и т. д.
  • Не бери провода одинакового цвета для разных контактов одного и того же модуля, чтобы не путаться.
  • Немного кабель-менеджмента не помешает: прячь лишние провода, прокладывай их с умом.
  • Не торопись крепить захват: сперва протестируй и узнай, где находятся положения 0 и 90°.
  • Колёсному роботу нужно ставить балансиры так, чтобы он стоял на трёх точках (не на четырёх сразу).

5. Общий план работы

Тебе стоит записать всю последовательность действий и чётко ей следовать:

  1. Быстрое чтение задания олимпиады и выбор, сколько оборудования и как устанавливать — дальномеры, датчики и т. д.
  2. Первичная сборка робота (уже сделано).
  3. Создание схемы Э1.
  4. Запуск, проверка и настройка программного обеспечения.
  5. Тестирование всего оборудования (платы Arduino, моторов, датчиков, сервопривода).
  6. Окончательная сборка робота.
  7. Написание шаблона программы.
  8. Написание и отладка алгоритма и программы.

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

А сейчас несколько слов о сложностях и целях. Морально готовься, что у тебя не будет под рукой готовых ответов, как в каком-нибудь ГДЗ.

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

Попробуй прямо сейчас найти в интернете информацию о структурной и принципиальной схеме робота.

Как видишь, чтобы начертить такую штуку, недостаточно просто вызубрить какой-то шаблон. Всё это создаётся и правится на лету.

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

6. Схемы Э1 и Э3

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

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

Тебе дадут вот такой бланк на бумаге формата A3 или A4.

Девятиклассники должны начертить схему Э1, а вот учащиеся 10–11 классов — схему Э3. Поэтому есть смысл обзавестись радиотехнической линейкой, чтобы чуть-чуть быстрее всё начертить.

Итак, что же это за схемы такие? Мы уже разбирали этот вопрос в первой части практикума, поэтому вернись туда и освежи свои знания.

А теперь попроси (или распечатай сам) бланки и начерти две схемы своего робота — Э1 и Э3. Обязательно засеки время секундомером. По-хорошему, тебе надо научиться чертить схему за 5 минут.

7. Настройка Arduino IDE

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

8. Тестируем моторы

Напомним, что в этот момент у тебя ещё не до конца собран робот, а подключения не проверены. Сейчас этим займёмся!

FIXME


  • Не запутайся в сторонах:
    • Крутим левое колесо → поворот направо.
    • Крутим правое колесо → поворот налево.
  • Если вращение пошло не в ту сторону, поменяй HIGH на LOW (или наоборот).
  • Обрати внимание, что нужно проверить скорость именно на 75. В требованиях к олимпиаде указано, что мотор должен вращаться на 30% от максимального значения (255).
  • Если на 75 мотор не поедет, попробуй значение 77. Если снова не поедет — значит, мотор неисправен, и это на совести организаторов.
  • Моторы будут вращаться с разной скоростью. Подбери значения, при которых робот будет двигаться почти прямо и достаточно медленно.

Если моторы успешно заработали, переходим далее.

9. Тестируем сервопривод

Сейчас нужно проверить повороты на 0 и 90°, чтобы прикрутить захват.

Напомним, что у нас типовой аналоговый сервопривод с удержанием угла. Его диапазон вращения — от 0 до 180°.

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

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

Импульс 544 мкс соответствует положению 0°, 1540 мкс — поворот в 90°, а 2400 мкс — в 180°. Нужно понимать, что поворот возможен только в пределах этой полуокружности.

FIXME


  • Продолжительность импульса может различаться в зависимости от производителя, единого стандарта нет. Поэтому может потребоваться или почитать документацию, или подобрать продолжительность импульса экспериментально. Чаще всего встречается строчка наподобие: «Pulse Width: 500–2400 μs».
  • Иногда сервопривод невозможно повернуть на 180°, а только на 140–150°. Это нормально. Главное, чтобы он мог поднять (90°) и опустить захват (0°).

А теперь сделаем плавное опускание и подъём захвата.

FIXME



Конечно, надо написать отдельную команду, чтобы серво поворачивал в положение 90°, а после всё многократно протестировать (пока тренируешься и готовишься).

FIXME



Можно использовать стандартную библиотеку Servo, которая входит в состав Arduino IDE. Но надо помнить, что при её использовании пропадает возможность использования ШИМ на контактах 9 и 10.

Как управлять сервоприводом через библиотеку Servo, можно посмотреть в примерах Arduino IDE: ExamplesServoSweep.

Всегда есть смысл посмотреть официальную документацию Arduino или её русскоязычные версии.

10. Тестируем дальномеры

Ультразвук — звуковая волна, частота которой превышает диапазон слышимости человека. Люди могут слышать колебания частотой примерно до 20 000 Гц, которые звучат как высокий свист. Однако ультразвук частотой свыше 20 кГц попросту неслышен для человека.

Ультразвуковой дальномер HC-SR04 состоит из двух ультразвуковых преобразователей. Один из них работает как передатчик и посылает вперёд ультразвуковые импульсы частотой 40 кГц. Другой работает как приёмник и улавливает отражённые импульсы, что позволяет вычислить расстояние до объектов.

Такой датчик бесконтактно определяет расстояния от 2 до 400 см с точностью до 3 мм.

Для начала измерения триггерный вывод устанавливается в состояние HIGH на 10 мкс. В ответ датчик передаёт серию из восьми ультразвуковых импульсов частотой 40 кГц. Последовательность специально разработана так, чтобы приёмник мог отличить передаваемые импульсы от окружающего ультразвукового шума. Импульсы начинают распространяться по воздуху.

В это время на выводе ECHO устанавливается высокий уровень напряжения, чтобы инициировать обратный сигнал. Если эти импульсы не отражаются обратно, сигнал эха затухает и становится низким через 38 мс. Таким образом, импульс длительностью 38 мс указывает на отсутствие препятствий в зоне действия датчика.

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

Для вычисления расстояния почти везде «попросят» разделить результат на 58. Строго говоря, это не слишком точная формула, где скорость звука 0,034 м/мкс округляют до 1/29. Такая аппроксимация (приближение) сильно упрощает вычисления ценой незначительного роста погрешности, а формулу запоминать намного проще.

FIXME


  • Чтобы нивелировать ошибки при измерении, стоит ограничить расстояние до 100 см.
  • Странно, но время от времени могут получаться отрицательные значения. Отсюда в коде появился модуль числа abs.
  • Тут применили функцию, возвращающую целое значение int. Нам такой точности хватит.

Инфракрасный дальномер Sharp GP2Y0A21 предназначен для измерения расстояния до препятствий от 10 до 80 см. Рекомендуем почтать техническую документацию к датчику.

Посмотри на задержку перед измерением и на время отклика — это почти 120 мс на одно измерение. Также обрати внимание на зону детекции при максимальном расстоянии 80 см — датчик увидит объекты до 12 см.

Работать с этим датчиком довольно просто: подключаем к нему питание Vcc и землю GND, а вывод Vo — на аналоговый вход Ардуинки.

Аналоговый вход принимает значения от 0 до 1023. При этом можно не преобразовывать данные в сантиметры, а просто сравнивать показания. Так проще и быстрее. Нам же не надо знать точное расстояние до объекта, а лишь определить наличие, или получить результат сравнения «ближе/дальше».

FIXME



Примем в программе сокращения:

  • 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
  • Помни, что на олимпиаде стол с полигоном может иметь бортики. Датчик может их увидеть, хоть они и заведомо дальше, чем объекты.
  • Отработай подключение и настройку ИК-датчика на время: на олимпиаде пригодятся и круглые числа из памяти, и опыт в подборе параметров для детекции разных объектов.

11. Тестируем датчики линии

В первой части практикума мы использовали доступные и популярные сенсоры на основе TCRT5000.

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

Результат работы сенсора — аналоговый сигнал. Чем светлее поверхность под сенсором, тем меньше выходное значение.

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

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

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

Распиновка датчика линии:

FIXME



Если тебе доводилось писать ПД-регулятор для 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;

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

Тут может встретиться неприятная проблема: если датчики показывают сильно разные значения, нужно получить доступ к подстроечному резистору. Для этого нужно быстренько открутить его со стойкой и расположить сбоку, как на рисунке. А дальше — крутить резистор очень медленно, по чуть-чуть.

Подстроечный резистор

  • Если показания датчика надо увеличить — крутим по часовой стрелке.
  • Если надо уменьшить — крутим против часовой.

Можно протестировать датчики до полной сборки робота. То есть подключаешь моторы и датчики линии и сразу их тестируешь, а уже потом подключаешь всё остальное. Выбирать оптимальную стратегию придётся тебе. А для этого надо много раз тренировать сборку/разборку робота и проверку сенсоров.

12. Тестируем энкодеры

Вот мы и добрались до прерываний. Исследуй несколько примеров кода.

FIXME


  • Чтобы поглубже вникнуть в тему прерываний, почитай дополнительные материалы.
  • Переменная должна быть объявлена volatile, если её значение может быть изменено за пределами участка программы, где она объявлена. Например, функцией, которая обрабатывает соответствующее прерывание.
  • attachInterrupt(interrupt, function, mode) задаёт функцию обработки внешнего прерывания.

Когда будешь тестировать энкодер, просто аккуратно покрути колёса руками и посмотри на показания в мониторе последовательного порта.

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

Параметры attachInterrupt:

Возможные значения режима:

Используя два счётчика, можно контролировать, какое расстояние проедет робот. Заставим его двигаться прямо, написав ПИД-регулятор, в котором отклонение (ошибка) — это разность значений левого и правого счётчика. А воздействовать регулятор будет на скорость моторов. Красота!

А ещё мы как-то забыли измерить диаметр колёс. Надо заполнить этот пробел, чтобы узнать, сколько робот проедет за 1 оборот колеса (и за половину оборота, за четверть).

13. Вперёд на ПИД-регуляторе!

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

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

FIXME


  • Чтобы поглубже вникнуть в тему, рекомендуем почитать учебный материал про ПИД-регуляторы.
  • Постарайся быстро разобраться, какие задать лимиты по счётчикам, чтобы робот проехал 1 оборот колеса или, например, расстояние 10 см (смотря, какие данные тебе удобнее для восприятия).

Итак, познакомимся с основными понятиями систем автоматического управления.

Объект управления — это то, чем управляет система. В нашем случае — это сам робот.

Уставка (целевое значение) — это значение, за соблюдением которого «смотрит» система. Для нас целевое значение — нуль. Именно такая разность между левым и правым счётчиком должна быть при прямолинейном движении.

Рассогласование (ошибка, ошибка управления) — это отклонение текущего состояния от целевого. В нашем случае это разность в показаниях левого и правого счётчика.

Датчики — то, чем мы определяем рассогласование. Для нашего робота это энкодеры.

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

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

Управляющее воздействие (корректировка) — это действия, которые принимает система, чтобы вернуться к целевому значению. У нас это изменение скорости моторов робота.

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

Буква «П» в названии ПИД-регулятора означает слово «пропорциональный». П-регулятор рассчитывает управляющее воздействие на моторы пропорционально текущему отклонению от целевого значения.

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

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

Объединяя эти регуляторы воедино, получим ПИД-регулятор из трёх составляющих:

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

14. Светодиод для отладки

Невозможно заниматься отладкой, если на роботе нет динамика или светодиода для простенькой индикации. Поэтому установим роботу дополнительный светодиод.

Напомним, что:

Если макетная плата приклеена к плате расширения, то подписи контактов не видно. Запомни визуальные ориентиры на плате, где расположены важные пины:

  • Пин 8 находится скраю группы колодок.
  • Земля GND — это 2 и 3 контакты после «разрыва».
  • Пин 5V находится левее земли.
14-1.ino
  1. #define LED 8 //пин для светодиода
  2.  
  3. pinMode(LED, OUTPUT); // светодиод очень полезен для отладки
  4.  
  5. // помигать светодиодом для отладки
  6. void blink(int dt, int n) {
  7. for (int j = 0; j < n; j++) {
  8. digitalWrite(LED, HIGH);
  9. delay(dt);
  10. digitalWrite(LED, LOW);
  11. delay(dt);
  12. }
  13. }

15. Планируем алгоритм и начинаем кодить

Вот мы и протестировали все модули, пора браться за программу.

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

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

Давай «проговорим» алгоритм, чтобы понять, какие надо написать процедуры.

  1. Проехать по линии до стены зелёного круга.
  2. Повернуть на какой-то угол, вращая левое колесо и используя энкодеры.
  3. Двигаться вдоль стены, используя ультразвуковой дальномер, и остановиться у линии.
  4. Повернуть, вращая левым колесом так, чтобы датчик был напротив зоны с кубиком.
  5. Проверить несколько раз, есть ли в зоне шаблонный кубик. Если кубик есть, запомнить его (потребуется числовой массив).
  6. Вращая левое колесо в обратную сторону, сдать назад с поворотом. То есть вернуть робота в положение, как было до поворота к зоне с шаблонным кубиком.
  7. Аналогичным образом просканировать все зоны.
  8. Вернуться в центр, двигаясь по линии до перекрёстка.

Это примерное изложение алгоритма в общих чертах. А теперь укрупним действия до трёх:

Это и будет первая часть алгоритма.

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

16. Обозначение контактов, создание переменных, настройка входов/выходов

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

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

Напомним, что процесс компиляции прошивки очень непростой и проходит в несколько этапов.

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

Трансляция в программировании — преобразование программы, представленной на одном из языков программирования, в программу на другом языке. Цель трансляции — преобразование текста с одного языка на язык, понятный адресату.

При трансляции компьютерной программы адресатом может быть:

  1. Устройство (процессор) — тогда трансляция называется компиляцией.
  2. Программа (интерпретатор) — такая трансляция называется интерпретацией.

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

#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
  • Именованные константы в твоей программе для Arduino должны быть записаны в нотации SCREAMING_SNAKE_CASE.
  • Если ты печатаешь небыстро, экономь время и сокращай названия. Например, вместо MOTOR_RIGHT_DIRECTION_PIN пиши просто DR.
  • Писать развёрнутые комментарии в коде — хорошая практика, но в условиях олимпиады на это нет времени.

FIXME


  • Смело пользуйся средствами автоформатирования в Arduino IDE, чтобы выравнивать отступы, ставить скобки и прочее.
  • Грамотное оформление кода — твой шанс получить дополнительные баллы от жюри.
  • Этот код — лишь шаблон, но кое-какие части стоит выучить. Например, pulseIn и все задержки для сервопривода, а также команды работы с прерываниями.

17. Движение по линии до объекта

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

FIXME


  • Обязательно нужно ограничить скорость условными операторами 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 см.

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

18. Повороты направо и обратно

После того как робот остановился у стены на расстоянии 12–14 см, надо повернуть направо примерно на 90°. Робот может поворачивать вправо, вращая левым колесом.

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

FIXME


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

19. Движение вдоль стены

Двигаться вдоль стены нужно до тех пор, пока датчик линии не увидит чёрный луч (линию, исходящую от стены).

Целевым значением будет расстояние в 12 см. Можно подобрать нужное значение в диапазоне 10–15 см. Нам больше понравился результат при 12 см.

FIXME



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

Также робот не должен опрашивать датчик в самом начале движения вдоль стены. Мы сделали движение на определённое время dtLine.

Итоговый алгоритм проезда по кругу будет примерно таким:

FIXME


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

20. Движение до перекрёстка

Следующая процедура — движение до перекрёстка. Кстати, а сколько раз роботу придётся выполнять это действие?

Если внимательно посмотреть на схему, то легко посчитать. 4 кубика вытолкнуть по шаблону, 1 захватить и привезти в центр, 2 раза побывать в центре. Итого — 7 раз.

FIXME


Этот код можно упростить и уменьшить, но на олимпиаде лучше писать так, чтобы как можно чаще пользоваться функцией «копировать/вставить».

21. Массивы

Итак, у нас 7 мест и 4 кубика в них. У каждого места есть свой номер.

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

Массив (array) — именованный набор однотипных переменных с доступом к отдельным элементам по общему имени массива и их индексу.

Примеры объявлений массива:

FIXME

// указываем количество ячеек
bool cubes [7];
// указываем содержимое ячеек, компилятор сам посчитает их количество
int pins[] = {2, 4, 8, 3, 6,};
// указываем и то и то, количество ячеек в [ ] должно совпадать с { }
float distance [3] = {0.2, 0.4, -8.5};

FIXME


  • Посмотри на рисунок. На самом деле, здесь достаточно взять не два массива, а один — итоговый (правый).
  • Обрати внимание на зависимость индексов: если у первого номер i, то у итогового — 6i. Поэтому при сканировании левой части поля можно сразу преобразовывать номера массива, чтобы было проще потом обрабатывать.
  • Нужно запоминать первый непарный кубик: он будет первый в итоговом, но последний в шаблонном расположении (значение last).

22. Вперёд не надо, надо назад!

Осталась последняя процедура — движение назад, которое нужно для обработки правой части.

FIXME


Учти, что опрос энкодеров идёт постоянно. Важно вовремя обнулять переменные, где хранятся значения. Если обнулить их в начале движения до перекрёстка, то будет известно, на сколько вернуться назад — ведь счётчики всё сохранили. В этом вся суть метода.

Действия робота по схеме на рисунке:

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);

23. Итоговый алгоритм

Теперь можно дописывать основной алгоритм с учётом созданных процедур.

FIXME


  • Обязательно почитай про типы данных в программировании. Например, для хранения чисел очень часто используют тип данных 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() { }

24. Улучшаем реализацию

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

Чтобы уменьшить количество действий, есть смысл установить инфракрасный дальномер сбоку, а не спереди.

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

Добавим ещё одну оптимизацию в алгоритме: если в последней зоне стоит непарный кубик, можно его захватить и не ездить второй круг.

В итоге программа примет следующий вид.

FIXME

 

Здесь ещё есть пространство для улучшения алгоритма.

  • Если 2 последних кубика будут непарными, то на второй круг за ними можно не ездить.
  • Можно ввести счётчик парных кубиков (их 4 штуки), чтобы не делать лишних действий при сканировании.

Попробуй придумать и реализовать свою идею по улучшению!

25. Продолжаем: задание для 10-х классов

Разберём задание ВсОШ 2023/2024 года для десятиклассников.

Задание

Участнику необходимо собрать робота, составить его электрическую принципиальную схему Э3, написать и отладить следующую программу: робот в автономном режиме перемещает объекты, установленные в правой части полигона, согласно шаблону в левой части (рисунки 1 и 2).

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

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

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

Примечания

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

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

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

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

Чтобы упростить общую программу, напишем процедуры по захвату и отпусканию кубика, когда наш робот стоит напротив нужной зоны.

FIXME

 

Каков общий план обработки кубиков?

  1. Подъехать к кубику в зоне.
  2. Проверить, шаблонный ли он.
  3. Если шаблонный, то подъехать к следующему; иначе захватить и перевести в первую свободную шаблонную зону внизу поля.
  4. Подъехать к следующему кубику.

Обработка одного кубика не будет какой-то сложной или длинной. Например, сделаем так.

FIXME

 

Не так уж много кода. Правда, самих кубиков три, и простое «копировать/вставить» уже не прокатит.

Сведём воедино весь алгоритм обработки.

FIXME

 
  • После отладки сканирования зелёной зоны, всю работу можно далее вести только в синей. При тестировании и отладке можно записать расположение кубиков в массив cubes[] (0 и 1, справа-налево) и больше не сканировать зелёную зону.
  • В ходе тестов не забывай проверять работоспособность своей программы при разном заряде аккумуляторных батарей.
  • Подумай, как сделать сканирование без остановок робота, чтобы он за один круг безошибочно определил расстановку кубиков?

Подсказка: напиши для этого отдельную процедуру движения вдоль стены с проверкой показаний ИК-дальномера. Максимальные показания датчика во время движения позволят понять, есть там объект или нет.

26. Продолжаем: задание для 11-х классов

Наконец, рассмотрим вариант задания ВсОШ 2023/2024 года для одиннадцатых классов.

Задание

Участнику необходимо собрать робота, составить его электрическую принципиальную схему Э3, написать и отладить следующую программу: робот в автономном режиме перемещает объекты в обеих частях полигона зеркально симметрично, в каждой части в три соседние ячейки (рисунки 1 и 2).

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

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

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

Примечания

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

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

Лишний кубик, который нужно привезти на старт, расположен в зелёной зоне. Однако мы начнём не с зелёной зоны, а с синей. Почему?

Это маленькая хитрость: если в синей зоне переставить два кубика, то в зелёной нужно будет перенести один. А если в синей перенести один, то в зелёной — два.

Если посмотреть на схемы «до/после», можно понять общий алгоритм. Главная идея — зафиксировать место второго кубика синей зоны. Третий переставить на «место второго + 1», а первый — на «место второго − 1».

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

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

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

Вникни в пример кода для сортировки кубиков в синей зоне. Если что-то непонятно — ищи подсказки в комментариях.

FIXME



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

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

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

FIXME


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

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

Продолжение следует

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

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

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

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

Ресурсы

Файлы

Навигация

← Практикум. Часть 1 | Методические материалы | Практикум. Часть 3 →

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