Публикации сообщества

Михаил Семионенков • 19 февраля 2020

"Следование по линии": учимся вместе с роботом

Материал посвящён теме, популярной на роботехнических фестивалях: "Следование по линии".

Эта публикация - вклад в разработку главы 3 анонсированного курса основ программирования и робототехники, но может рассматриваться как самостоятельный текст для читателей, имеющих опыт программирования в среде makecode.

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

Признаюсь, до поры я относился к теме следования по линии без симпатии. Но, осознав природу своего чувства, я изменил мнение. Почему мне не нравилась эта тема? Дело в том, что результат здесь не достигается как чисто логическое решение задачи, как это происходит в математике или программировании. Если программа (я программист) написана правильно, то она, вообще говоря, будет одинаково выдавать результаты на разных компьютерах, процессорах и операционных системах. Робот, следующий линии - совсем другое дело: небольшое изменение трассы, конструкции робота и даже смена батареек может изменить поведение робота и нарушить замысел. Почему так происходит? Добро пожаловать в физический мир: здесь качество резины на колесах, микрокочка на пути робота, слабый ток батарейки и прочие мелочи, от которых мы абстрагируемся в математических задачах, ставят проблемы, которые с наскока не так просто даже осознать. Программирование - инженерная дисциплина, но она рафинирована почти до математического уровня строгости рукотворной средой с формальными правилами, тогда как следование по линии - это настоящая инженерия в физическом мире. Это даёт задаче большой учебный потенциал и, одновременно, бросает вызов педагогу.

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

Инженерная история 1

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

Мораль: для решения сложной инженерной задачи нужна хорошая подготовка

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

Подготовка: сбор начальной информации

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

Немного подумав, можно прийти к выводу, что нужно 

  1. понимать возможности робота 
  2. понимать особенности трассы.

Возможности робота - это об источниках информации для робота (информация с датчиков следования линии). В нашей ситуации речь идёт о роботе Maqueen, оснащённом парой датчиков для следования линии. Датчики цифровые, т.е. возможен возврат двух значений: 0 и 1 ("цифровые" - такова терминология, происхождение и смысл которой мне не ясны, поскольку 0 и 1 ассоциируются, прежде всего, с булевой алгеброй, а не с цифрой, но ... такова терминология, укоренившаяся в микроконтроллерном мире). 

Поскольку документации в коробке с роботом нет,

Задача 1: определить кодировку датчика

Это - несложная задача для самостоятельной работы, не будем останавливаться на деталях.

На роботе Maqueen белый цвет кодируется единичкой. Мы получили первые константы, пора начинать сбор информации в проект "Следование по линии":

Кодировка цветов датчика следования по линии

Задача 2: определить скорость робота

Задача допускает разные решения (например, можно запустить робота на 10 секунд).

 У меня получилась скорость 19.5 см/c на солевых батарейках Flarx. Стоп, батарейки-то уже были какое-то время в работе. Меняем батарейки на новые: 23.5 см/c. Добавлю, что можно попробовать и с другими типами батареек, и результат наверняка будет другой. 

Делаем вывод: нельзя жёстко опираться на скорость: 

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

Дополнительное задание: определить угловую скорость робота, когда одно колесо остановлено, а другое двигается.


Задача 3: определить расстояние между датчиками следования линии

У робота Maqueen расстояние между датчиками фиксировано и равно 15мм. 

Задача 4. Определить параметры поля для следования по линии

Фирма dfrobot предлагает 2 поля (они напечатаны с разных сторон бумажного листа).

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

Поле для следования линии
Поле для следования линии

Размеры?

  • длина по продольной оси (включая ширину линии) - 86 см
  • ширина (включая ширину линии) 46 см   
  • радиус закругления (включая ширину линии) - 23 см
  • ширина линии - 15 мм

Определение стратегии следования по линии

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

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

Отправная точка разработки - определение состояния робота, которое нас устраивает, то есть, не требует коррекции. Конечно, нам хочется, чтобы робот двигался вперёд.

А показания датчиков? Чтобы определиться с этим вопросом, нужно сначала сравнить ширину линии трассы и расстояние между датчиками:

  • если расстояние между датчиками больше ширины линии, то можно попытаться держать датчики слева и справа от линии, то есть, ориентироваться на показания "белый И белый"
  • если расстояние между датчиками меньше ширины линии, то можно попытаться держать датчики над линией, то есть, ориентироваться на показания "чёрный И чёрный"
  • если расстояния равны?

В последнем случае (а это как раз наш случай), очевидно, придётся обеспечивать несимметричное положение робота относительно линии трассы: один датчик должен находиться вне линии ("белый"), а второй - на линии ("черный).

Вариант показания датчиков "белый И чёрный" допускает два расположения робота относительно линии:

  • расположение "внутри", когда "белый" датчик находится внутри трассы
  • расположение "снаружи", когда "белый" датчик находится снаружи трассы

Что выбрать: прохождение трассы снаружи или внутри? Самоё простое соображение - внутри путь короче. Остановимся для начала на этом решении (но будем помнить, что выбору мы посвятили совсем немного времени, значит, надо оставить за собой право пересмотреть подход позже. Вообще, развилки проекта следует помнить и документировать, чтобы позже иметь возможность проверить альтернативные подходы).

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

Настал момент сформулировать желаемое состояние робота во время прохождения трассы:

  • робот движется вперед
  • левый датчик показывает "белый", правый - "чёрный"

 

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

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

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

Состояния робота и структура программы

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

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

Шаблон программы
Шаблон программы

 

Вопрос: почему используется бесконечный цикл, а не блок "постоянно"? Дело в том, что блок "постоянно" будет исполняться всегда, не оставляя возможности завершить программу в случае обнаружения неразрешимых проблем (а с такой ситуацией в инженерной задаче нужно считаться). Блок же "пока" работает, пока условие в нем истинно. Поэтому мы задаём переменную код завершения, которую можно изменить позже - внутри вызываемых функций.

Впереди - 

Разработка функций

Функция "БелыйЧёрный" - делать ничего не надо

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

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

Функция "ЧёрныйБелый" - запретное состояние

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

В начальном варианте мы просто выставим флажок окончания работы, то есть, зададим переменной код завершения значение 1. (Единица выбрана не случайно: комбинация чёрный-белый, в числовом виде  01 в двоичной системе и есть 1.)  

Получаем следующее:

Функция ЧёрныйБелый

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

Теперь приступаем к содержательной части задачи: коррекции курса робота.

 Функция "БелыйБелый" - выход из положения внутри трассы

Что делать, если мы оказались внутри трассы? Для начала надо вернуться к линии.

Как? Например, выполняя поворот направо, то есть, остановив правое колесо, продолжить движение до тех пор, пока линия не будет "поймана" датчиками (не забудем занести наше решение в дневник). 

Что дальше? Не будем спешить, для начало посмотрим результат возвращения на линию:

Функция БелыйБелый, начало

 

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

Как долго нужно корректировать курс. Примерно столько же, сколько робот "ловил" линию.

А как отмерить "примерно столько же"? Заведём счётчик числа обращений к датчикам, чтобы использовать его как меру времени:

Функция БелыйБелый

​​​​​​Функция БелыйБелый

Зачем мы опрашиваем датчики во втором цикле? Просто чтобы шаг цикла потреблял примерно такое же время, как шаг первого цикла.

Функции ЧёрныйЧерный и ЧёрныйБелый

После испытаний получившейся версии, пора приступить к коррекции в состоянии ЧёрныйЧёрный. В этой ситуации робот настроен выскочить наружу (вправо). Попробуем подбором найти время коррекции. После нескольких опытов получилось, что вариант с 200 мс (переменная время после поворота) работает более или менее.

Поскольку робот выскакивает наружу, стоит пересмотреть запрет состояния ЧёрныйБелый. Как нетрудно видеть, ситуация в состоянии ЧёрныйЧёрный схожа с ситуацией ЧёрныйБелый: надо немного подрулить влево. Для начала просто используем функцию ЧёрныйЧёрный в состоянии ЧёрныйБелый.

Функция ЧёрныйЧёрный
ФунуцияЧёрныйЧёрный

Получившийся вариант заработал: робот достаточно стабильно ходит по трассе как по часовой стрелке (снаружи линии), так и против (внутри линии), и это приятный бонус.

Заключение

Программу можно посмотреть по адресу https://makecode.microbit.org/_5piHMFL4gDo0

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

 

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

Словом, наступает время перехода на традиционные текстовые языки.

Инженерная история 2

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

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

Приложение 1. Текст программы на JavaScript

function БелыйЧёрный () {
    
}
function БелыйБелый () {
    maqueen.motorStop(maqueen.Motors.M2)
    счётчик = 0
    правый_датчик_ББ = правый_датчик
    левый_датчик_ББ = левый_датчик
    while (левый_датчик_ББ == белый && правый_датчик_ББ == белый) {
        счётчик += 1
        левый_датчик_ББ = maqueen.readPatrol(maqueen.Patrol.PatrolLeft)
        правый_датчик_ББ = maqueen.readPatrol(maqueen.Patrol.PatrolRight)
    }
    maqueen.motorStop(maqueen.Motors.M1)
    maqueen.motorRun(maqueen.Motors.M2, maqueen.Dir.CW, 255)
    while (счётчик > 0) {
        счётчик += -1
        левый_датчик_ББ = maqueen.readPatrol(maqueen.Patrol.PatrolLeft)
        правый_датчик_ББ = maqueen.readPatrol(maqueen.Patrol.PatrolRight)
    }
    maqueen.motorRun(maqueen.Motors.M1, maqueen.Dir.CW, 255)
}
function ЧёрныйЧерный () {
    maqueen.motorStop(maqueen.Motors.M1)
    basic.pause(время_после_поворота)
    maqueen.motorRun(maqueen.Motors.M1, maqueen.Dir.CW, 255)
}
function ЧерныйБелый () {
    ЧёрныйЧерный()
}
let левый_датчик_ББ = 0
let правый_датчик_ББ = 0
let счётчик = 0
let правый_датчик = 0
let левый_датчик = 0
let время_после_поворота = 0
let белый = 0
белый = 1
let чёрный = 0
let код_завершения = 0
время_после_поворота = 200
maqueen.motorRun(maqueen.Motors.All, maqueen.Dir.CW, 255)
while (код_завершения == 0) {
    левый_датчик = maqueen.readPatrol(maqueen.Patrol.PatrolLeft)
    правый_датчик = maqueen.readPatrol(maqueen.Patrol.PatrolRight)
    if (левый_датчик == белый && правый_датчик == белый) {
        БелыйБелый()
    }
    if (левый_датчик == чёрный && правый_датчик == чёрный) {
        ЧёрныйЧерный()
    }
    if (левый_датчик == чёрный && правый_датчик == белый) {
        ЧерныйБелый()
    }
    if (левый_датчик == белый && правый_датчик == чёрный) {
        БелыйЧёрный()
    }
}
maqueen.motorStop(maqueen.Motors.All)
basic.showNumber(код_завершения)
 

Кол-во комментариев: (10)

Влад Сухарев
Михаил Николаевич, спасибо за интереснейший материал. По поводу инженерной истории братьев Райт - в пространстве больше степеней свободы (к углам рысканья добавляются углы тангажа и вращения). Как отмечено в водном курсе робототехники на ресурсе http://robocraft.ru/blog/759.html, знание о матрицах поворота, направляющих косинусах и оперативных координатах может здорово помочь в понимании прямого и инверсного преобразования для описания кинематики манипулятора. В контексте движения на плоскости остаётся один существенный параметр - угол отклонения от тренда вдоль линии. Наша идея вычислять этот угол приближённо по данным измерения времени пребывания робота в промежуточных состояниях. Промежуточными считаем возможным называть состояния "БелыйЧерный" и "ЧерныйБелый". Для правильной ориентации имеет смысл ввести навигационные индикаторы "Линия слева/справа от продольной оси симметрии робота" и "Центр поля слева/справа от ПОС робота". Это позволит выделить макросостояния "прямолинейный участок" и "разворот по дуге". Для каждого из этих состояний можно подобрать оптимальные характеристики движения. Однако выделение "комфортного" состояния, не требующего корректирующих воздействий, лично мне кажется не лучшей идеей. Статичность и консерватизм глубоко чужды соревновательному характеру. Представляется уместнее устроить автоколебательную систему с затуханием. Другими словами, выстроить траекторию движения робота от одного края (внешнего) к другому (внутреннему) и обратно с уменьшением величины корректирующих воздействий. Таким образом можно уменьшить количество поворотов и приблизиться к теоретически предсказуемой быстроте прохождения трассы.
  • Войдите или зарегистрируйтесь, чтобы оставлять комментарии
  • Михаил Семионенков
    Владислав Игоревич, спасибо большое за интересный и содержательный отзыв. Я осознаю и не скрываю, что моё описание - это не соревновательный уровень, цель моей публикации - помочь преодолеть начальный барьер. По строительной аналогии - я не замахиваюсь на небоскрёб, просто предлагаю выйти из пещеры и построить шалаш, осознать возможность строительства собствеными руками :-) Ещё раз спасибо, и до скорой встречи на Фестивале. Выходите на связь по личным каналам.
  • Войдите или зарегистрируйтесь, чтобы оставлять комментарии
  • Михаил Семионенков
    Алексей (извините, не увидел отчества), я считаю, что IF...ELSE хороши только для развилки на 2 варианта, для сложных случаев IF ELSE усложняет понимание логики. Для большего числа вариантов я бы предпочёл конструкцию типа CASE, а за неимением - совокупность IF, чьи условия не пересекаются и покрывают пространство событий. Другими словами, набор IF более наглядно отражает пространство событий, чем ступенчатые if else else, где очередное else зависит от контекста - от предыдущих if.
  • Войдите или зарегистрируйтесь, чтобы оставлять комментарии
  • Алексей Клячин
    Михаил Николаевич, тут больше дело в обработке IF, если у вас что-то выполняется из условий, то программа переходит к следующим командам, в вашем случае необходимо пройти по всем IF'ам, даже если нужное условие уже найдено и выполнено. Замена CASE'ов (или SWITCH'ей) - это связка IF - ELSE IF - ELSE, с точки программирования это более правильный вариант, нежели куча IF'ов друг под другом. Что касается программы: насколько я понял, в данном случае простой релейный регулятор для движения по черной линии, в этом случае вся программа будет выглядеть следующим образом: https://makecode.microbit.org/_Wu8aAj0Vo3Jg условия выхода из цикла - по истечении 5 секунд.
  • Войдите или зарегистрируйтесь, чтобы оставлять комментарии
  • Михаил Семионенков
    Алексей, я согласен, что Ваш "switch" смотрится нормально, мне нужно взглянуть ещё раз свежим взглядом, спасибо. Что касается конкретной реализации, я бы в общем случае рекомендовал число if по числу обрабатываемых событий, резервируя последний else для внутреннего контроля программы. И "упаковка" номера случая выглядит остроумно, но для данного конкретного случая избыточно, поскольку от читателя требуется некоторый напряг для проверки корректности преобразования. По существу программу постараюсь посмотреть позже.
  • Войдите или зарегистрируйтесь, чтобы оставлять комментарии
  • Михаил Семионенков
    Алексей Клячин, наглядность улучшилась :-) Но было бы ещё лучше, если бы в "если" отражались все 4 варианта, а не 3. По существу программы, выражу своё скромное мнение, что 5 секунд она на трассе не продержится, быстро вылетит. Заключение на основание воображения и опыта, отражённого в статье :-)
  • Войдите или зарегистрируйтесь, чтобы оставлять комментарии
  • Алексей Клячин
    Михаил Семионенков 5 секунд - как пример, можно было сделать условие выхода - наличие препятствия спереди и т.д. Насчет 4 вариантов в "IF" - смотря какие цели вы преследуете: либо обучить прикладному программированию и объяснить, что возможны _всего_ 4 варианта, где 3 из них видны в IF, ELSE IF и если там варианты не подходят, значит остается четвертый - в ELSE. Классика программирования, имхо либо пытаетесь показать "красивый" код, вводя в заблуждение юных программистов ;o)
  • Войдите или зарегистрируйтесь, чтобы оставлять комментарии
  • Михаил Семионенков
    Алексей Клячин, речь не о 5 секундах и не об условии выхода. Просто Ваша программа - это не готовый алгоритм, а начальная гипотеза, нужно её опробовать, попытаться понять, почему не работает, поправить, опять испытать т д. А по поводу "имхо" - после 40-летнего зарабатывания денег программированием - "их есть у меня" :-)
  • Войдите или зарегистрируйтесь, чтобы оставлять комментарии