Сегодня мы начинаем публикацию серии статей о проекте »Домовой» — домашнем роботе на основе робототехнической платформы Strela — в которой автор, Пашкевич Николай, рассказывает о своем опыте работы с платформой, о том, какие задачи ставит перед собой, и как решает их в попытке создать собственного робота.
Мы предлагаем вашему вниманию первые три статьи, из которых вы узнаете о целях проекта, знакомитесь с его основными компонентами и даже сможете ознакомиться с первой версией программы робота «Домового».
Вступление
Данный проект больше носит обучающую и развлекательную направленность нежели будет иметь серьезное практическое применение. Но многие аспекты затронутые в нем позволят коснутся тем которые будут совсем не лишними при разработке других идей и проектов.
Когда я спрашиваю себя, а что бы я хотел получить в конце этого проекта, то мозг переполняется идеями и желаниями что можно сделать, проблема в том что чем круче идея тем сложнее ее реализовать. Таким образом я не ставлю перед собой цель сесть и создать все и сразу, принцип работы будет итеративный и от простого к сложному, задача — решение, задача — решение. Не стоит браться за все сразу, лучше идти по шагам, так проще решать проблемы, проще отлавливать «баги», да и вообще итеративный принцип часто описывается в различных книгах по программированию как — не большие но частые итерации лучше чем редкие и большие, и я в этом убедился ни разу на личном опыте. Так поступлю и здесь.
И все же лично мое видение некоего конечного результата с разумными вполне осуществимыми задумками.
«Домовой» это мобильная платформа позволяющая управлять ей через TCP/IP соединение, несущая на себе управляемую видеокамеру и набор датчиков и сенсоров, под управлением разработочного для этого проекта приложения. В итоге у меня получится мобильное устройство с помощью которого я смогу всегда проехать по квартире находясь в любом месте с интернетом и проверить все ли в порядке, так же если этого удастся добиться возможна функция «патруля» которая позволит платформе самостоятельно делать обход квартиры и определять наличие угроз в виде воды на полу, возгораний, утечки газа или наличия живых организмов когда их дома быть не должно с оповещением меня каким-либо способом.
В целом путь к этому будет не быстрый, так как потребуется много знаний и времени что бы все это реализовать, а уж вопрос функции «патруль» это вообще из разряда «мастер-класс» и не уверен получится ли это реализовать, так как очень сложно научить электронику определять свое точное положение в пространстве с минимальными погрешностями (минимальная погрешность GPS пара метров, сойдет для леса но для квартиры явно великоват разброс) без создания в квартире нечто наподобие GPS, в общем эта тема лишь как интересная и сложная задача, получится ее решить или нет понятия не имею но пока и без нее будет чем заняться.
Что касательно исходного кода, то я всегда буду отдавать предпочтение понятности кода нежели эффективности, до тех пор пока она реально не потребуется. Часто встречал в книгах известных авторов по разработке ПО заметки о том что не стоит производить преждевременную оптимизацию кода, до тех пор пока она реально не потребуется. Но это не значит что надо писать «быдло-код», я категорически против и всегда стараюсь придерживается «хорошего тона» в написании кода. Я не являюсь проффи, я только учусь, поэтому не судите слишком строго.
На этом думаю и все, пора приступить к не спешной работе над проектом.
Начальные компоненты
Не стал я особо привередничать и искать что то крутое, и обзавелся купленной на eBay не дорогой китайской платформой с 4-мя электрическими двигателями и Arduino — совместимой платой «Strela» от ребят из Амперки. Так как пока я явно не хочу заморачиваться с TCP/IP решением ибо там будет на чем съесть стаю собак и еще пару кошечек, для начала решил взять уже имеющиеся у меня модули XBee. XBee в целом хватит для почти всех начальных задач, да и опять таки «стрела» имеет слот под эти модули а эти модули у меня уже есть, зачем покупать что-то еще.
Почему я выбрал «стрелу», просто «бутерброд» из Arduino UNO + Motor Shield + XBee Shield меня не очень радовал, а «стрела» имеет на борту все что нужно для начала на одной плате, плюс она Arduino — совместимая а значит пока меня будут устраивать готовые библиотеки для Arduino я смогу их использовать без изменений.
Да и текстовый LCD мне не очень хотелось выносить с решением вопроса о том как его подключить к Arduino UNO, не потратив при этом пачку портов и не нажив конфликта с тем самым «бутербродом» из «шилдов».
В целом собрав все это в кучу я получил вот такую платформу:
Вот с этим теперь мне и предстоит работать, постепенно модернизируя и улучшая.
Скажу сразу. со временем изменится почти все, так как «стрела» явно не предназначена для работы качестве «мозга» системыы, а XBee потоковое видео просто не потянет, всеми этими функциями в будущем займутся другие «жители» платформы, и не только потому что существующие не предназначены или не потянут, а хотя бы потому что разделение обязанностей отличная вещь. А пока такой вариант позволяет почти моментально получить результат, а когда что то уже работает это существенно повышает мораль и интерес к проекту.
Не много забегая мыслями вперед, пока что на роль будущего главного «мозга» на платформе рассматривается Raspberry Pi (малина) а «стрела» будет осуществлять только управление двигателями и возможно сбор данных с датчиков и сенсоров с передачей их на «малину», ну как минимум на первом этапе а там видно будет.
Ну да ладно, мечты в сторону, пора приступать к первым шагам и получению первых результатов.
Программа для платформы. v1.0
И так, физически все есть, теперь пришла пора все это оживить. Франкенштейну удалось и у меня получится.
Для начала определюсь с тем что я желаю на начальном этапе:
- Движение вперед
- Движение назад
- Поворот на месте влево
- Поворот на месте в право
- Остановка
- Таймер безопасности
- Проверка связи с платформой
- Вывод текущей команды на LCD
Если в с пунктами движения и остановки все понятно то вот что такое «таймер безопасности» который я хочу ввести я сейчас расскажу.
Любое соединение имеет привычку пропадать в самый не подходящий момент, либо ПК на котором работает управляющая программа может внезапно выключится или программа аварийно завершится и т.д. и т.п.
А что если платформа в этот момент активно движется вперед и ждет команды на остановку, а связи с ней нет, вот так и будет она ехать или вертеться на месте до тех пор пока будет «порох в ее пороховницах».
Отсюда вывод — надо предусмотреть вариант внезапной потери связи. Этим вариантом и станет мой «таймер безопасности», идея которого будет до безобразия простая, если с момента поступления последней команды пройдет больше N времени, платформа должна будет прекратить любые движения и просто остановится.
Проверка связи потребуется что бы можно было проверить «в сознании» ли платформа и способна ли она реагировать на команды. Суть данного метода на начальном этапе будет в очередной раз простейшая, отправка команды проверки связи (пинг) на который в ответ платформа должна отослать подтверждение, для начала такой простой схемы будет более чем достаточно.
На начальном этапе я не стану сильно мудрить с форматами команд и проверки их правильности по средствам контрольных сумм и т.д. и т.п., так как на этом этапе это не актуально да и мне точно не известно какой стек протоколов использует XBee и гарантирует ли он доставку данных без искажения, пока буду считать что гарантирует, далее жизнь покажет.
XBee модули я настроил согласно этому не большому руководству.
Формат команд для начала будет просто символы, до боли знакомые любому геймеру это:
- w — вперед
- s — назад
- a — влево
- d — вправо
- space — стоп
- p — пинг (системная — пинг)
- o — понг (системная- подтверждение)
В целом, можно определить любые значения, но для простоты я использовал эти, в будущем будет разработан совершенно другой формат команд когда потребуется более продвинутые функции управления движением, но пока будет использован этот набор.
И так, вот такой получится базовый код для платформы:
#include <Wire.h> #include <Strela.h> #include <LiquidCrystal_I2C.h> const char FORWARD_CMD = 'w'; const char BACK_CMD = 's'; const char LEFT_CMD = 'a'; const char RIGHT_CMD = 'd'; const char STOP_CMD = ' '; const char PING_CMD = 'p'; const char PING_ANSWER = 'o'; const int RIGHT_ENGINE_SPEED = 165; const int LEFT_ENGINE_SPEED = 185; const int ZERO_SPEED = 0; const int SAFETY_INTERVAL = 3000; enum state {STOP, FORWARD, BACK, RIGHT, LEFT}; state currentState = STOP; unsigned long lastCmdTime = 0; LiquidCrystal_I2C lcd(LC_ADDR, LCEN, LCRW, LCRS, LC4, LC5, LC6, LC7); void setup( void ) { motorConnection(1, 0); lcd.begin(8, 2); Serial1.begin(9600); printToLcd("Strela:", "Ready..."); } void loop( void ) { while (Serial1.available() < 1) { boolean isMoving = currentState != STOP; boolean isSafetyIntervalExceeded = (lastCmdTime + SAFETY_INTERVAL) < millis(); if (isMoving && isSafetyIntervalExceeded) { stop(); } } lastCmdTime = millis(); switch (Serial1.read()) { case FORWARD_CMD: moveForward(); break; case BACK_CMD: moveBack(); break; case LEFT_CMD: turnLeft(); break; case RIGHT_CMD: turnRight(); break; case STOP_CMD: stop(); break; case PING_CMD: pingAnswer(); break; default: printToLcd("Unknown", "command"); } } inline void moveForward( void ) { if (currentState != FORWARD) { drive(RIGHT_ENGINE_SPEED, LEFT_ENGINE_SPEED); currentState = FORWARD; printToLcd("Move:", "Forward"); } } inline void moveBack( void ) { if (currentState != BACK) { drive((RIGHT_ENGINE_SPEED * -1), (LEFT_ENGINE_SPEED * -1)); currentState = BACK; printToLcd("Move:", "Back"); } } inline void turnLeft( void ) { if (currentState != LEFT) { drive(RIGHT_ENGINE_SPEED, ZERO_SPEED); currentState = LEFT; printToLcd("Turn:", "Left"); } } inline void turnRight( void ) { if (currentState != RIGHT) { drive(ZERO_SPEED, LEFT_ENGINE_SPEED); currentState = RIGHT; printToLcd("Turn:", "Right"); } } inline void stop( void ) { if (currentState != STOP) { drive(ZERO_SPEED, ZERO_SPEED); currentState = STOP; printToLcd("Strela:", "Stop"); } } inline void pingAnswer( void ) { Serial1.write(PING_ANSWER); } void printToLcd( const char *inputLine1, const char *inputLine2 ) { lcd.clear(); lcd.home(); lcd.print( inputLine1 ); lcd.setCursor(0, 1); lcd.print( inputLine2 ); }
* Код протестирован в Arduino IDE 1.0.5-r2
Данный код рассчитан на компиляцию в Arduino IDE с использованием библиотек Wire. LiquidCrystal. Strela. Библиотека Wire уже имеется в составе IDE собственно как и библиотека LiquidCrystal, хотя в последней по умолчанию отсутствуют файлы для I2C на котором работает LCD на стреле, поэтому вам придется взять полную версию. Ссылки на библиотеки вы можете найти тут как собственно и примеры кода для работы со стрелой. Я целенаправленно не даю прямые ссылки на библиотеки, так как разработчики стрелы могут внести какие-либо изменения в месторасположения библиотек, или изменить что-то еще, таким образом надеюсь что информация на сайте производителя стрелы будет актуальной.
Работа с Serial в данном коде происходит через объект Serial1 (единица на конце), так как стрела имеет виртуальные порты, и поэтому просто Serial это для связи по USB а Serial1 это через модуль беспроводной связи.
В моем коде константы RIGHT_ENGINE_SPEED и LEFT_ENGINE_SPEED имеют разные значения, дело в том что у меня платформу «вело» в один бок при езде прямо, пришлось внести корректировки в значения скорости моторов. В будущем я планирую организовать систему которая позволит платформе самостоятельно настраивать работу моторов.
Ну что же, если «залить» код на платформу и открыть монитор порта, можно посылать описанные выше команды и платформа будет их послушно выполнять, а если команды не будет дольше чем указано в константе SAFETY_INTERVAL то платформа просто остановится, данная проверка реализована в цикле активного ожидания команды, в будущем можно задействовать таймер микроконтроллера и прерывание от него, но пока не стану усложнять.
Отлично, все работает как задумано.
Но у меня как и у Франкенштейна тоже не обошлось без неожиданностей. Дело в том что XBee подкинул эту самую неожиданность, примерно через каждые 8 команды модуль впадает в «спячку» на 3-4 секунды а потом платформа получала все что я послал за эти 3-4 секунды, и это все делает динамическое управление платформой не возможным. Но если посылать команды примерно раз в секунду, проблем нет, но это же смешно, если активно «рулить» платформой может потребоваться до 5 команд в секунду а то и больше, смотря как рулить короче.
В общем теперь придется решать эту проблему.
Автор: Пашкевич Николай
pashkevich.me