Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Робот-решатель лабиринта, использующий искусственный интеллект

Компоненты и расходные материалы

Arduino Nano R3
× 1
Датчик SparkFun RedBot - последователь линии
× 1
ZX03 (на основе TCRT5000) Отражающие инфракрасные датчики (аналоговый выход)
× 2
Android-устройство
× 1
Сервопривод непрерывного вращения RobotGeek
× 2
Держатель батареи 4xAA
× 2

Приложения и онлайн-сервисы

IDE Arduino
MIT App Inventor 2

Об этом проекте

Введение

Это руководство было разработано на основе моего последнего проекта:Робот-следящий за линией - ПИД-регулирование - Настройка Android. Если у вас есть робот со способностью следовать за линией, следующий естественный шаг - дать ему некоторую степень интеллекта. Итак, наш дорогой «Рекс, робот» сейчас попытается найти, как выбраться из «лабиринта» самым коротким и быстрым путем (кстати, он ненавидит Минотавра.

Для начала, в чем разница между Maze и Лабиринт ? Согласно http://www.labyrinthos.net, в англоязычном мире часто считается, что для того, чтобы быть классифицированным как лабиринт, дизайн должен иметь выбор на пути. Ясно, что сюда войдут многие современные сооружения в парках развлечений и туристических достопримечательностях, в том числе наш 2D-лабиринт. Популярный консенсус также указывает на то, что у лабиринтов есть один путь, который неумолимо ведет от входа к цели, хотя зачастую и самые сложные и извилистые маршруты.

Большинство лабиринтов, какими бы сложными они ни казались, по сути, были образованы одной сплошной стеной с множеством стыков и ответвлений. Если стена, окружающая цель лабиринта, соединена с периметром лабиринта у входа, лабиринт всегда можно решить, удерживая одну руку в контакте со стеной, независимо от того, сколько обходных путей может потребоваться. Эти «простые» лабиринты правильно известны как « Просто связанные "или" идеальный лабиринт "или другими словами, которые не содержат циклов .

Возвращаясь к нашему проекту, он будет разделен на две части (или « проходит "):

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

Видео ниже покажет пример того, как Рекс находит выход. В первый раз, когда робот исследует лабиринт, он, конечно, потратит много времени " на размышления «о том, что делать на любом перекрестке. Проверяя многочисленные возможности, он приведет к нескольким неправильным путям и тупикам, что заставит его пробежать более длинные пути и совершать ненужные» развороты ". Во время этого" 1-го прохода " , робот будет накапливать опыт " делать заметки "о различных пересечениях и устранении плохих ответвлений. В его" 2nd Pass ", робот идет прямо и быстро до конца без каких-либо ошибок или сомнений. В этом руководстве мы подробно рассмотрим, как это сделать:

Шаг 1. Спецификация материалов

Список материалов в основном такой же, как и тот, который используется с роботом-следящим за линией, за исключением того, что я добавил 2 дополнительных датчика для большей точности при обнаружении ЛЕВОГО и ПРАВОГО пересечения:

Последний робот все еще очень дешев (около 85 долларов):

Корпус (можно адаптировать под свои нужды или под имеющиеся материалы):

  • 2 деревянных квадрата (80 x 80 мм)
  • 3 зажима для папок
  • 2 деревянных колеса (диаметр:50 мм)
  • 1 заклинатель шаров
  • 9 резинок
  • Полоса кадров команд 3M
  • Пластиковые соединения для крепления датчика
  • Доска для хлеба и электропроводка
  • 2 комплекта из 4 никель-металлогидридных батарей (5 В каждый комплект).
  • 2 X SM-S4303R пластиковый сервопривод непрерывного вращения на 360 градусов
  • Ардуино Нано
  • Модуль Bluetooth HC-06
  • 5 линейных датчиков (4-канальный инфракрасный модуль слежения за линией TCRT5000 + 1 независимый путевой датчик)
  • 2 X ZX03 (на основе TCRT5000) отражающих инфракрасных датчика (аналоговый выход)
  • 1 светодиод
  • 1 кнопка

Примечание :Я использовал пункт 7 выше с аналоговым выходом, потому что у меня не было под рукой датчиков с цифровым выходом, как в пункте 6. В идеале, если возможно, все датчики должны быть одинаковыми. Также я протестировал проект, сохранив только оригинальные 5 датчиков. Это будет работать, но при обнаружении перекрестков потребуется более тонкая настройка.

Шаг 2. Изменения в теле

Удалите исходный набор из 5 датчиков следования по линии и исправьте новый " Far LEFT" и " ВПРАВО «отражающие датчики на каждом конце опорной пластиковой планки. Рекомендуется располагать 7 датчиков как можно более выровненными.

Шаг 3. Установка и тестирование новых датчиков

Новый массив, теперь состоящий из 7 датчиков , смонтирован таким образом, что 5 исходных используются исключительно для ПИД-регулирования (и обнаружения "полной линии", поясняется позже), а 2 новых оставлены для использования исключительно для обнаружения пересечения ЛЕВОГО и ПРАВОГО.

В качестве беглого обзора давайте вспомним, как работают 5 оригинальных «цифровых» датчиков:

Если один датчик отцентрирован по отношению к черной линии, только этот конкретный датчик будет давать ВЫСОКИЙ. С другой стороны, расстояние между датчиками должно быть рассчитано так, чтобы два датчика могли покрыть всю ширину черной линии одновременно, также создавая ВЫСОКИЙ сигнал на обоих датчиках.

Как работают 2 новых «аналоговых» датчика:

Если один из датчиков отцентрован относительно черной линии, на выходе будет аналоговое значение, обычно на выходе АЦП Arduino ниже «100» (помните, что АЦП выдает выходной сигнал от 0 до 1023). Для более светлых поверхностей выходное значение будет выше (например, я тестировал от 500 до 600 на белой бумаге). Это значение необходимо проверить в различных ситуациях с освещением и материалами поверхности, чтобы определить правильную константу ПОРОГ, которая будет использоваться в вашем случае (см. Рисунок здесь).

Глядя на код Arduino, каждый из датчиков будет определен с определенным именем (учтите, что исходному датчику следования по линии, расположенному левее, необходимо присвоить метку " 0 " "):

  const int lineFollowSensor0 =12; // Использование цифрового inputconst int lineFollowSensor1 =18; // Использование аналогового вывода A4 в качестве цифрового inputconst int lineFollowSensor2 =17; // Использование аналогового вывода A3 в качестве цифрового inputconst int lineFollowSensor3 =16; // Использование аналогового вывода A2 в качестве цифрового inputconst int lineFollowSensor4 =19; // Использование аналогового вывода A5 в качестве цифрового inputconst int farRightSensorPin =0; // Аналоговый вывод A0const int farLeftSensorPin =1; // Аналоговый вывод A1  

Помните, что при следовании за линией возможны 5 исходных выходных сигналов матрицы датчиков:

  1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0 0 0 1 1 0 0 0 1 0 0 0 1 1 0 0 0 1 0 0 0 1 1 0 0 0 1 0 0 0 1 1 0 0 0 1 0 0 0 0  

С добавлением двух новых, их возможные результаты:

  • Дальний левый датчик:аналоговый выход больше или меньше ПОРОГА.
  • Дальний ПРАВОЙ датчик:аналоговый выход больше или меньше ПОРОГА.

Для хранения значений каждого датчика создается переменная массива для исходных 5 цифровых датчиков:

  int LFSensor [5] ={0, 0, 0, 0, 0};  

И две целочисленные переменные для 2 новых аналоговых датчиков:

  int farRightSensor =0; int farLeftSensor =0;  

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

  LFSensor [0] =digitalRead (lineFollowSensor0); LFSensor [1] =digitalRead (lineFollowSensor1); LFSensor [2] =digitalRead (lineFollowSensor2); LFSensor [3] =digitalRead (lineFollowSensor3); LFSensor3); LFSensor [4]; =digitalRead (lineFollowSensor4); farRightSensor =analogRead (farRightSensorPin); farLeftSensor =analogRead (farLeftSensorPin);  

Наличие 5 датчиков, как было показано в проекте Follower Line Robot, позволяет генерировать «переменную ошибки», которая поможет контролировать положение робота над линией. Кроме того, переменная под названием "режим" будет использоваться для определения, если робот следует за линией . , над непрерывной линией , перекресток или без строки вообще.

Эта переменная " режим "также будет использоваться с" ВЛЕВО / ВПРАВО "датчики. Для представления, давайте рассмотрим крайний левый и правый датчики, имеющие 3 возможных состояния:

  • H (выше THRESHOLD),
  • L (меньше THRESHOLD) и
  • X (не имеет значения).

Для цифровых выходов будут обычные 0, 1, и мы также представим X:

  • H 0 X X X X L ==> режим =RIGHT_TURN; ошибка =0; (см. пример на изображении выше)
  • L X X X X 0 H ==> режим =LEFT_TURN; ошибка =0;
  • X 0 0 0 0 0 X ==> режим =NO_LINE; ошибка =0;
  • H 0 0 0 0 1 H ==> режим =FOLLOWING_LINE; error =4;
  • H 0 0 0 1 1 H ==> режим =FOLLOWING_LINE; error =3;
  • H 0 0 0 1 0 H ==> режим =FOLLOWING_LINE; ошибка =2;
  • H 0 0 1 1 0 H ==> режим =FOLLOWING_LINE; ошибка =1;
  • H 0 0 1 0 0 H ==> режим =FOLLOWING_LINE; ошибка =0;
  • H 0 1 1 0 0 H ==> режим =FOLLOWING_LINE; ошибка =-1;
  • H 0 1 0 0 0 H ==> режим =FOLLOWING_LINE; error =-2
  • H 1 1 0 0 0 H ==> режим =FOLLOWING_LINE; ошибка =-3;
  • H 1 0 0 0 0 H ==> режим =FOLLOWING_LINE; error =-4;
  • X 1 1 1 1 1 X ==> mode =CONT_LINE; error =0;

Итак, реализуя приведенную выше логику в функции:

  void readLFSsensors ()  

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

  void testSensorLogic (void) {Serial.print (farLeftSensor); Serial.print ("<==ЛЕВЫЙ ПРАВО ==>"); Serial.print (farRightSensor); Serial.print ("режим:"); Serial.print (режим); Serial.print ("ошибка:"); Serial.println (ошибка);}  

Шаг 4. Решение лабиринта - правило левой руки

Как обсуждалось во введении, большинство лабиринтов, какими бы сложными они ни казались, по сути, были сформированы из одной непрерывной стены с множеством стыков и ответвлений. Если стена, окружающая цель лабиринта, соединена с периметром лабиринта у входа, лабиринт всегда можно решить, удерживая одну руку в контакте со стеной, независимо от того, сколько обходных путей может потребоваться. Эти «простые» лабиринты правильно известны как « Просто связанные . "

Поискав в Википедии, мы узнаем, что:

Короче говоря, Правило левой руки можно описать так:

На каждом перекрестке и по всему лабиринту держите левую руку касающейся стены слева.

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

Итак, главное здесь - определить перекрестки , определяя, какой курс выбрать, основываясь на приведенных выше правилах. В частности, в нашем 2D-лабиринте мы можем найти 8 различных типов пересечений (см. Первое изображение выше):

Глядя на картинку, мы можем понять, что возможные действия на перекрестках:

1. На « Кресте ":

  • Перейти влево или
  • Перейти вправо или
  • Двигайтесь прямо

2. В " T ":

  • Перейти влево или
  • Перейти вправо

3. В " Только справа " ":

  • Перейти вправо

4. В поле « Только слева ":

  • Перейти влево

5. В поле « Прямо или налево ":

  • Перейти влево или
  • Двигайтесь прямо

6. В поле « Прямо или вправо ":

  • Перейти вправо или
  • Двигайтесь прямо

7. В " тупике ":

  • Вернуться ("разворот")

8. В " конце лабиринта" ":

  • Остановить

Но, применяя «Правило левой руки», каждое действие будет сведено к одному варианту:

  • У «креста»:идите налево.
  • На букву «Т»:налево.
  • "Только справа":переходите вправо.
  • "Только слева":идите налево.
  • Прямо или налево:идите налево.
  • Прямо или направо:идите прямо.
  • В "тупике":возвращайтесь ("разворачивайтесь")
  • В «конце лабиринта»:стоп.

Мы почти на месте! «Успокойся!»

Когда робот достигает «тупика» или «конца лабиринта», их легко идентифицировать, потому что не существует неоднозначных ситуаций (мы уже реализовали эти действия для робота-следящего за линией, помните?). Проблема в том, когда робот находит, например, «ЛИНИЮ», потому что линия может быть «крестиком» (1) или «Т» (2). Также, когда он достигает «ЛЕВОГО или ПРАВОГО ПОВОРОТА», это может быть простой поворот (варианты 3 или 4) или варианты движения прямо (5 или 6). Чтобы точно определить, на каком перекрестке находится робот, необходимо предпринять дополнительный шаг:робот должен пробежать «лишний дюйм» и посмотреть, что будет дальше (см. Вторую картинку выше в качестве примера).

Итак, с точки зрения потока, возможные действия теперь можно описать как:

1. В "ТЕРМОРЕНЦЕ":

  • Вернуться ("разворот")

2. На ЛИНИИ: Пробежите лишний дюйм .

  • Если есть линия:это "Крест" ==> ПЕРЕЙДИТЕ ВЛЕВО.
  • Если строки нет:это буква "T" ==> ВЛЕВО
  • Если есть другая линия:это «Конец лабиринта» ==> СТОП.

3. При "ПОВОРОТЕ ВПРАВО": пробегите лишний дюйм .

  • если есть линия:это прямая или правая ==> Иди ПРЯМО
  • Если линии нет:это только вправо ==> Перейти ВПРАВО

4. При "левом повороте": пробегите лишний дюйм .

  • если есть линия:это прямая или ЛЕВАЯ ==> Перейти в ЛЕВУЮ
  • Если строки нет:это только ЛЕВЫЙ ==> Перейти в ЛЕВЫЙ

Обратите внимание, что на самом деле в случае «ЛЕВОГО ПОВОРОТА» вы можете пропустить тест, потому что вы все равно выберете ВЛЕВО. Я оставил объяснение более общим только для ясности. На реальном коде я пропущу этот тест. На третьем изображении выше показан очень простой лабиринт на полу моей лаборатории, используемый в тестовых целях.

Шаг 5. Реализация алгоритма «Левая рука на стене» в коде Arduino

Когда у нас есть readLFSsensors () изменена функция, чтобы включить дополнительные 2 датчика, мы можем переписать функцию цикла для запуска алгоритма, как описано на последнем шаге:

  void loop () {readLFSsensors (); переключатель (режим) {case NO_LINE:motorStop (); goAndTurn (СЛЕВА, 180); ломать; case CONT_LINE:runExtraInch (); readLFSsensors (); если (режим ==CONT_LINE) mazeEnd (); иначе goAndTurn (LEFT, 90); // или это "Т" или "Крест"). В обоих случаях переходит в ЛЕВЫЙ разрыв; case RIGHT_TURN:runExtraInch (); readLFSsensors (); если (режим ==NO_LINE) goAndTurn (ВПРАВО, 90); ломать; case LEFT_TURN:goAndTurn (LEFT, 90); ломать; case FOLLOWING_LINE:followingLine (); ломать; }}  

Здесь появляются некоторые новые функции:

  • followingLine () то же самое, что используется с роботом "Следующая строка", где, если он следует только за строкой, он должен calculatePID () ; и управлять двигателями в зависимости от значений PID: motorPIDcontrol ();
  • runExtraInch (): немного подтолкнет робота вперед. Сколько будет работать робот, будет зависеть от времени, которое вы используете в функции задержки, прежде чем вы подадите команду на остановку двигателя.
  void runExtraInch (void) {motorPIDcontrol (); задержка (extraInch); motorStop ();}  
  • goAndTurn (направление, угол): эта особая функция важна, потому что вы не сможете повернуть робота, как только поймете, на каком перекрестке вы находитесь. Помните, что мы спроектировали дифференциального робота, который при поворотах «крутится вокруг своей оси», и поэтому, чтобы двигаться на 90o и непрерывно следовать по линии, центр колес должен быть совмещен с центром пересечения. Как только линия датчиков окажется перед его топором, вы должны запустить робота вперед, чтобы выровнять их. Постоянная времени adjGoAndTurn необходимо отрегулировать в зависимости от расстояния между осью и линией датчика (" d "), скорость и размер колес (см. рисунок выше).
  void goAndTurn (целое направление, целое число градусов) {motorPIDcontrol (); задержка (adjGoAndTurn); motorTurn (направление, градусы);}  

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

Сильфон, тест на этом этапе проекта:

Шаг 6:Сохранение пути

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

  • Влево (L)
  • Назад (B)
  • Влево (L)
  • Влево (L)
  • Влево (L)
  • Назад (B)
  • Прямой (S)
  • Назад (B)
  • Влево (L)
  • Влево (L)
  • Назад (B)
  • Прямой (S)
  • Влево (L)
  • Влево (L)
  • Конец

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

  char path [100] ="";  

Мы также должны создать 2 переменных индекса, которые будут использоваться вместе с массивом:

  беззнаковый char pathLength =0; // длина пути int pathIndex =0; // используется для достижения определенного элемента массива.  

Итак, если мы запустим пример, показанный на картинке, мы закончим на:

  path =[LBLLLBSBLLBSLL] и pathLengh =14  

Шаг 7. Упрощение (оптимизация) пути

Вернемся к нашему примеру. Глядя на первую группу перекрестков, мы поняли, что первая левая ветвь на самом деле является «тупиком», и поэтому, если робот вместо «левый-задний-левый» пройдет прямо на этом первом перекрестке, будет много энергии. и время было бы сэкономлено! Другими словами, последовательность «LBL» фактически будет такой же, как «S». Именно так можно оптимизировать полный путь. Если вы проанализируете все возможности использования «разворота», то набор из 3 перекрестков, на которых появляется этот «разворот» («B») («xBx»), можно сократить до одного.

Выше приведен только один пример, ниже вы можете найти полный список возможностей (попробуйте):

  • LBR =B
  • LBS =R
  • RBL =B
  • SBL =R
  • SBS =B
  • LBL =S

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

  путь =[LBLLLBSBLLBSLL] ==> LBL =Spath =[SLLBSBLLBSLL] ==> LBS =Rpath =[SLRBLLBSLL] ==> RBL =Bpath =[SLBLBSLL] ==> LBL =Spath =[SSBSLL ] ==> SBS =Bpath =[SBLL] ==> SBL =Rpath =[RL]  

Удивительный! Глядя на этот пример, становится ясно, что если на первом перекрестке робот повернет ВПРАВО, а после этого - ВЛЕВО, он достигнет конца лабиринта по кратчайшему пути!

Общий код программы «Первый путь решения лабиринта» будет объединен в функцию mazeSolve () . . Эта функция на самом деле является функцией loop (), которая использовалась ранее, но включает в себя все эти шаги по сохранению и оптимизации пути. Когда первый путь закончится, массив path [] будет иметь оптимизированный путь. Введена новая переменная:

  unsigned int status =0; // решение =0; достичь конца лабиринта =1  

Ниже функции "Первый путь":

  void mazeSolve (void) {в то время как (! статус) {readLFSsensors (); переключатель (режим) {case NO_LINE:motorStop (); goAndTurn (СЛЕВА, 180); recIntersection ('B'); ломать; case CONT_LINE:runExtraInch (); readLFSsensors (); если (режим! =CONT_LINE) {goAndTurn (LEFT, 90); recIntersection ('L');} // или это буква "Т" или "Крест"). В обоих случаях переходит к LEFT else mazeEnd (); ломать; case RIGHT_TURN:runExtraInch (); readLFSsensors (); если (режим ==NO_LINE) {goAndTurn (ВПРАВО, 90); recIntersection ('R');} else recIntersection ('S'); ломать; case LEFT_TURN:goAndTurn (LEFT, 90); recIntersection ('L'); ломать; case FOLLOWING_LINE:followingLine (); ломать; }}}  

Здесь была введена новая функция: recIntersection (direction). Эта функция будет использоваться для сохранения пересечения, а также для вызова другой функции simpleifyPath () , это уменьшит группу из 3 перекрестков, включающих разворот, как мы видели ранее.

  void recIntersection (char direction) {path [pathLength] =direction; // Сохраняем пересечение в переменной пути. pathLength ++; simpleifyPath (); // Упрощаем изученный путь.}  

КРЕДИТ для simpleifyPath ( ) предназначена Патрику МакКейбу для получения кода решения пути (подробности см. на сайте https://patrickmccabemakes.com)! Стратегия упрощения пути заключается в том, что всякий раз, когда мы встречаем последовательность xBx, мы можем упростить ее, вырезав тупик. Например, LBL ==> S . как мы видели на примере.

  void simpleifyPath () {// упрощаем путь только в том случае, если предпоследний поворот был 'B' if (pathLength <3 || path [pathLength-2]! ='B') return; int totalAngle =0; int i; for (i =1; i <=3; i ++) {switch (path [pathLength-i]) {case 'R':totalAngle + =90; ломать; case 'L':totalAngle + =270; ломать; case 'B':totalAngle + =180; ломать; }} // Получаем угол в виде числа от 0 до 360 градусов. totalAngle =totalAngle% 360; // Заменить все эти повороты одним. переключатель (totalAngle) {case 0:path [pathLength - 3] ='S'; ломать; case 90:путь [pathLength - 3] ='R'; ломать; case 180:путь [pathLength - 3] ='B'; ломать; case 270:путь [pathLength - 3] ='L'; ломать; } // Теперь путь на два шага короче. pathLength - =2; }  

Шаг 8:Второй проход:решите лабиринт как можно быстрее!

Основная программа: loop () просто так:

  void loop () {ledBlink (1); readLFSsensors (); mazeSolve (); // Первый проход для решения лабиринта ledBlink (2); while (digitalRead (buttonPin) {} pathIndex =0; status =0; mazeOptimization (); // Второй проход:запускаем лабиринт как можно быстрее ledBlink (3); while (digitalRead (buttonPin) {} mode =STOPPED; status =0; // 1-й проход pathIndex =0; pathLength =0;}  

Итак, когда первый проход заканчивается, мы должны только кормить робота оптимизированным массивом путей. Он начнет работать, и когда будет обнаружено пересечение, он теперь определит, что делать, в зависимости от того, что он хранится в path [] .

  void mazeOptimization (void) {в то время как (! статус) {readLFSsensors (); переключатель (режим) {case FOLLOWING_LINE:followingLine (); ломать; case CONT_LINE:if (pathIndex> =pathLength) mazeEnd (); иначе {mazeTurn (путь [pathIndex]); pathIndex ++;} перерыв; case LEFT_TURN:if (pathIndex> =pathLength) mazeEnd (); иначе {mazeTurn (путь [pathIndex]); pathIndex ++;} перерыв; case RIGHT_TURN:if (pathIndex> =pathLength) mazeEnd (); иначе {mazeTurn (путь [pathIndex]); pathIndex ++;} перерыв; }}}  

Чтобы указать, что делать, новая функция mazeTurn (path []) был создан. Функция mazeTurn (путь []) будет:

  void mazeTurn (char dir) {switch (dir) {case 'L':// Повернуть налево goAndTurn (LEFT, 90); ломать; case 'R':// Повернуть направо goAndTurn (RIGHT, 90); ломать; case 'B':// Повернуть назад goAndTurn (RIGHT, 800); ломать; case 'S':// Прямо runExtraInch (); ломать; }}  

Второй проход готов! На видео ниже показан полный пример работы здесь, первый и второй проход. Ниже кода Arduino, использованного в этом руководстве:

FV6XNJWINJ45XWM.ino F2FXS8MINJ45XX6.h FX5MHFMINJ45XX7.ino FT2S1WXINJ45XXA.ino F9IC3HQINJ45XXB.ino FU2HRXJINJ45XXV.ino

Шаг 9. Использование Android для настройки

Здесь также можно использовать приложение Android, разработанное для проекта «Следующая линия» (при необходимости приложение Android и его код доступны по адресу:Line Follower Robot - PID Control - Android Setup. Код Arduino, представленный на последнем этапе, уже включает связь с устройство Android. Если вы не хотите использовать приложение Android, не проблема, потому что код " transparent ".

Во время проекта я много использовал Android для отправки тестовых данных с робота на устройство с помощью « Message Received "Поле". Несколько переменных должны быть четко определены, чтобы гарантировать, что робот будет поворачивать правильный угол. Наиболее важные из них приведены ниже (те, которые отмечены жирным шрифтом, я менял их несколько раз):

  const int adj =0; float adjTurn =8; int adjGoAndTurn =800; THRESHOLD =150const int power =250; const int iniMotorPower =250; int extraInch =200;  

Шаг 10:Заключение

Это вторая и последняя часть сложного проекта, исследующего возможности робота-последователя линии, в котором Искусственный интеллект (AI) простые концепции были использованы для решения лабиринта.

Я не ИИ эксперт и основываясь на информации, полученной мной из Интернета, я понял, что то, что сделал наш маленький Рекс, робот, решая лабиринт, можно считать применением ИИ. Давайте посмотрим на 2 приведенных ниже источника:

Из Википедии:

Или из этой университетской статьи:«Робот, решающий лабиринт с использованием алгоритма Freeduino и LSRB, Международный журнал современных инженерных исследований (IJMER)»

Обновленные файлы для этого проекта можно найти на GITHUB. Надеюсь, я смогу помочь другим узнать больше об электронике, роботах, Arduino и т. Д. Для получения дополнительных руководств посетите мой блог:MJRoBot.org

Салудо с юга мира!

Спасибо

Марсело

Код

  • Фрагмент кода №1
  • Фрагмент кода 4
  • Фрагмент кода 5
  • Фрагмент кода №6
  • Фрагмент кода № 7
  • Фрагмент кода № 8
  • Фрагмент кода № 12
  • Фрагмент кода 13
  • Фрагмент кода № 14
  • Фрагмент кода 15
  • Фрагмент кода №16
  • Фрагмент кода № 17
Фрагмент кода №1 Обычный текст
 const int lineFollowSensor0 =12; // Использование цифрового inputconst int lineFollowSensor1 =18; // Использование аналогового вывода A4 в качестве цифрового inputconst int lineFollowSensor2 =17; // Использование аналогового вывода A3 в качестве цифрового inputconst int lineFollowSensor3 =16; // Использование аналогового вывода A2 в качестве цифрового inputconst int lineFollowSensor4 =19; // Использование аналогового вывода A5 в качестве цифрового inputconst int farRightSensorPin =0; // Аналоговый вывод A0const int farLeftSensorPin =1; // Аналоговый вывод A1 
Фрагмент кода №4 Обычный текст
 LFSensor [0] =digitalRead (lineFollowSensor0); LFSensor [1] =digitalRead (lineFollowSensor1); LFSensor [2] =digitalRead (lineFollowSensor2); LFSensor [3] =digitalRead (lineFollowSensor3); LFSRead [4] =digitalRead (lineFollowSensor3); LFSRead [ lineFollowSensor4); farRightSensor =analogRead (farRightSensorPin); farLeftSensor =analogRead (farLeftSensorPin); 
Фрагмент кода № 5 Обычный текст
 void testSensorLogic (void) {Serial.print (farLeftSensor); Serial.print ("<==ЛЕВЫЙ ПРАВО ==>"); Serial.print (farRightSensor); Serial.print ("режим:"); Serial.print (режим); Serial.print ("ошибка:"); Serial.println (ошибка);} 
Фрагмент кода №6 Обычный текст
 void loop () {readLFSsensors (); переключатель (режим) {case NO_LINE:motorStop (); goAndTurn (СЛЕВА, 180); ломать; case CONT_LINE:runExtraInch (); readLFSsensors (); если (режим ==CONT_LINE) mazeEnd (); иначе goAndTurn (LEFT, 90); // или это "Т" или "Крест"). В обоих случаях переходит в ЛЕВЫЙ разрыв; case RIGHT_TURN:runExtraInch (); readLFSsensors (); если (режим ==NO_LINE) goAndTurn (ВПРАВО, 90); ломать; case LEFT_TURN:goAndTurn (LEFT, 90); ломать; case FOLLOWING_LINE:followingLine (); ломать; }} 
Фрагмент кода № 7 Обычный текст
 void runExtraInch (void) {motorPIDcontrol (); задержка (extraInch); motorStop ();} 
Фрагмент кода № 8 Обычный текст
 void goAndTurn (целое направление, целое число градусов) {motorPIDcontrol (); задержка (adjGoAndTurn); motorTurn (направление, градусы);} 
Фрагмент кода № 12 Обычный текст
 void mazeSolve (void) {в то время как (! статус) {readLFSsensors (); переключатель (режим) {case NO_LINE:motorStop (); goAndTurn (СЛЕВА, 180); recIntersection ('B'); ломать; case CONT_LINE:runExtraInch (); readLFSsensors (); если (режим! =CONT_LINE) {goAndTurn (LEFT, 90); recIntersection ('L');} // или это буква "Т" или "Крест"). В обоих случаях переходит к LEFT else mazeEnd (); ломать; case RIGHT_TURN:runExtraInch (); readLFSsensors (); если (режим ==NO_LINE) {goAndTurn (ВПРАВО, 90); recIntersection ('R');} else recIntersection ('S'); ломать; case LEFT_TURN:goAndTurn (LEFT, 90); recIntersection ('L'); ломать; case FOLLOWING_LINE:followingLine (); ломать; }}} 
Фрагмент кода № 13 Обычный текст
 void recIntersection (char direction) {path [pathLength] =direction; // Сохраняем пересечение в переменной пути. pathLength ++; simpleifyPath (); // Упрощаем изученный путь.} 
Фрагмент кода № 14 Обычный текст
 void simpleifyPath () {// упрощаем путь только в том случае, если предпоследний поворот был буквой 'B' if (pathLength <3 || path [pathLength-2]! ='B') return; int totalAngle =0; int i; for (i =1; i <=3; i ++) {switch (path [pathLength-i]) {case 'R':totalAngle + =90; ломать; case 'L':totalAngle + =270; ломать; case 'B':totalAngle + =180; ломать; }} // Получаем угол в виде числа от 0 до 360 градусов. totalAngle =totalAngle% 360; // Заменить все эти повороты одним. переключатель (totalAngle) {case 0:path [pathLength - 3] ='S'; ломать; case 90:путь [pathLength - 3] ='R'; ломать; case 180:путь [pathLength - 3] ='B'; ломать; case 270:путь [pathLength - 3] ='L'; ломать; } // Теперь путь на два шага короче. pathLength - =2; } 
Фрагмент кода № 15 Обычный текст
 void loop () {ledBlink (1); readLFSsensors (); mazeSolve (); // Первый проход для решения лабиринта ledBlink (2); while (digitalRead (buttonPin) {} pathIndex =0; status =0; mazeOptimization (); // Второй проход:как можно быстрее запустить лабиринт ledBlink (3); while (digitalRead (buttonPin) {} mode =STOPPED; status =0; // 1-й проход pathIndex =0; pathLength =0;} 
Фрагмент кода №16 Обычный текст
 void mazeOptimization (void) {в то время как (! статус) {readLFSsensors (); переключатель (режим) {case FOLLOWING_LINE:followingLine (); ломать; case CONT_LINE:if (pathIndex> =pathLength) mazeEnd (); иначе {mazeTurn (путь [pathIndex]); pathIndex ++;} перерыв; case LEFT_TURN:if (pathIndex> =pathLength) mazeEnd (); иначе {mazeTurn (путь [pathIndex]); pathIndex ++;} перерыв; case RIGHT_TURN:if (pathIndex> =pathLength) mazeEnd (); иначе {mazeTurn (путь [pathIndex]); pathIndex ++;} перерыв; }}} 
Фрагмент кода № 17 Обычный текст
 void mazeTurn (char dir) {switch (dir) {case 'L':// Повернуть налево goAndTurn (LEFT, 90); ломать; case 'R':// Повернуть направо goAndTurn (RIGHT, 90); ломать; case 'B':// Повернуть назад goAndTurn (RIGHT, 800); ломать; case 'S':// Прямо runExtraInch (); ломать; }} 
Github
https://github.com/Mjrovai/MJRoBot-Maze-Solverhttps://github.com/Mjrovai/MJRoBot-Maze-Solver

Схема

z7IdLkxL1J66qOtphxqC.fzz

Производственный процесс

  1. Робот, использующий Raspberry Pi и Bridge Shield
  2. Робот, управляемый жестами, использующий Raspberry Pi
  3. Робот, управляемый Wi-Fi и использующий Raspberry Pi
  4. ОБНАРУЖЕНИЕ ЧЕЛОВЕКА РОБОТА SONBI С ИСПОЛЬЗОВАНИЕМ KINECT И МАЛИНЫ PI
  5. Bosch добавляет искусственный интеллект в Индустрию 4.0
  6. Искусственный интеллект - вымысел или вымысел?
  7. Искусственный интеллект получает огромный импульс Kubernetes
  8. Искусственный интеллект помогает роботу распознавать объекты на ощупь
  9. Использование искусственного интеллекта для отслеживания обезлесения
  10. Роботы с искусственным интеллектом