Робот «Scootaloo». Инструкция по сборке и программированию.

+457
в блоге Электроброни

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

Цель статьи показать, как можно из доступных элементов собрать простого робота и запрограммировать его на выполнение простых действий. Сначала я расскажу по отдельности о каждой части робота, а потом покажу, как собрать все это вместе и опишу основные принципы написания системы управления робота. Таким образом это не совсем инструкция, а скорее гайд-руководство в стиле www.instructables.com, хотя как инструкцию эту статью тоже можно использовать.

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

Из чего состоит «Scootaloo»?

Самый частый вопрос, который мне задают, когда я показываю людям своего робота: «из чего же ты его сделал?»
И правда, из чего?
Все, кроме последних трех деталей, я купил в китайском интернет магазине DealExtreme (доставка бесплатна по всему миру). Из инструментов мне понадобился только паяльник, мультиметр, комплект маленьких отверток и компьютер. Полный список с ценами я привожу в конце статьи.

Колеса


Эта штука называется Zumo, представляет из себя гусеничную платформу-шасси, к которой крепятся 4 колеса с гусеницами, внутрь вставляется два мотора и 4 батарейки типа АА. Она создавалась специально с учетом международных требований соревнований мини-сумо. Поставляется в разобранном виде, легко собирается и разбирается. Моторчики маленькие и слабенькие, выиграть в соревнованиях с такой платформой врятли получится, но если стоит цель разобраться, как работают роботы, то эта платформа подходит в самый раз.

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

Кстати шасси – это самая дорогая часть робота. Zumo+моторы стоят около 2000 рублей без учета доставки.
К тому же это единственная деталь, которую нельзя найти в китайских интернет магазинах, но зато она есть в российских, или же её можно напрямую купить на официальном сайте http://www.pololu.com.

Arduino

Мозгом робота является платформа Arduino UNO.
Arduino — это простая для освоения платформа с открытым кодом на основе встроенного микроконтроллера и среды разработки с программным интерфейсом. К ней могут присоединяться различные аналоговые и цифровые датчики, которые регистрируют состояние окружающей среды и передают данные в микроконтроллер. Программа обрабатывает входящие данные и выдает новые данные в виде аналоговых или цифровых значений, которые можно использовать для управления внешними устройствами. Короче, это что-то вроде материнской платы с микроконтроллером вместо процессора и с 20-ю портами ввода/вывода, к которым можно подключать различные устройства.

Официально Arduino UNO cтоит около 1000р (в России 1500р.). Но так как платформа ардуино имеет открытую архитектуру, то кто угодно может начать производство Arduino совместимых плат и продавать их не делая никаких лицензионных отчислений. Китайцы уже давно наладили производство аналогов Arduino UNO по цене 500-600р за штуку. Зачастую, никакой разницы в них вообще нет. Всё, вплоть до надписей соответствует официальному ардуину, и работает так же, переплачивать за красивую коробку и мануал смысла нет.

Ещё для платформы Arduino выпускаются дополнительные платы расширения, их еще часто называют шилдами (Shield/Board), которые можно пристыковать к Ардуину сверху.

В зависимости от назначения и производителя платы, цена может быть от $5 до $30 (200 – 1000р). В роботе я использовал два шилда. Это драйвер движков (около 500р) и прототипная плата (около 200р). Дальше я подробней про них расскажу.

Датчики

На Scootaloo установлены два типа датчиков — инфракрасные и ультразвуковые.
Инфракрасные детекторы черного и белого цвета
Плата инфракрасных датчиков нужна для детектирования линии. Расстояние между крайними датчиками 5 см. В ней используются сборки из инфракрасного светодиода и фототранзистора. Светодиод освещает поверхность, а фототранзистор улавливает отраженный сигнал. Чем больше света попадает в приемник, тем больше открывается транзистор. При достижении заданного порога компаратор включает светодиод, а на соответствующий сигнальный вывод будет подано напряжение. Цена ей около 300р.

Демонстрация работы:
Ультразвуковые сонары
Принцип работы: сенсор излучает короткий ультразвуковой импульс, который отражается от объекта и принимается сенсором. Расстояние рассчитывается исходя из времени до получения эха и скорости звука в воздухе.

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

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

Существует несколько разновидностей таких сонаров: HC-SR04, HC-SRF05, Parallax «PING)))». Я использовал сенсоры от Paralax, но не рекомендую их. Дорого, хотя они ничем, по сути, не отличаются от дешевых SR04. Единственный их плюс в том, что для работы им нужен всего один пин вместо двух. А так они все работают по одному и тому же принципу.


HC-SR04 стоят всего 3 бакса за штуку.

Тестируем Arduino

Те, кто знаком с Ардуино, могут пропустить этот раздел.
Сначала надо скачать среду разработки Arduino IDE. Берем установочный exe файл тут arduino.cc/en/Main/Software (текущая версия 1.0.5 r2). Для подключения ардуина к компьютеру понадобится кабель USB2.0 тип A–B, он обычно идет в комплекте. А еще таким кабелем часто подключают принтеры. После подключения должен загореться зеленый светодиод питания, помеченный на плате ON.
Установка драйверов для Arduino Uno на Windows

  • После подключения платы к компьютеру Windows начнет процесс установки драйвера. Через некоторое время, несмотря на все её попытки, процесс закончится безрезультатно. Драйвер надо поставить вручную.
  • Нажмите на кнопку ПУСК и откройте Панель управления.
  • В панели управления перейдите на вкладку Система и безопасность (System and Security). Затем выберите Система. Когда откроется окно Система, выберите Диспетчер устройств (Device Manager).
  • Обратите внимание на порты (COM и LPT). Вы увидите открытый порт под названием «Arduino UNO (COMxx)» Вместо XX у вас будет номер порта, запомните его, он нам еще пригодится.
  • Щелкните на названии «Arduino UNO (COMxx)» правой кнопкой мышки и выберите опцию «Обновить драйвер» (Update Driver Software).
  • Кликните «Выполнить поиск драйверов на этом компьютере» (Browse my computer for Driver software).
  • Для завершения найдите и выберите файл драйвера для Uno – «arduino.inf» или «ArduinoUNO.inf» расположенный в папке Drivers программного обеспечения для Arduino (для Windows 7 это C:\Program Files (x86)\Arduino\drivers\ ).
  • В процессе установки Windows может потребовать подтверждение издателя драйвера.
  • Если установка завершилась без ошибок, то можно запустить среду разработки Arduino IDE.
  • После запуска программы появится зеленое окно с белым листом посередине.

Проверим правильно ли настроена среда разработки на вашу плату. Для этого надо в меню «Сервис» установить номер последовательного порта и тип платы «Arduino UNO». После этого можно открыть и загрузить скетч (так называется программа в среде ардуино):
  • Выбераем Файл > Примеры > 1.Basics > Blink.
  • Откроется новое окно с текстом программы.
  • Для компиляции и загрузки скетча выберите Файл > Загрузить или на панели инструментов нажмите зеленую кнопку со стрелкой указывающей вправо.
  • Через несколько секунд на плате Arduino, раз в секунду, начнет мигать маленький светодиод, обозначенный буквой L и расположенный возле порта с номером 13.
  • Если этого не произошло, проверьте, виден ли «Arduino UNO (COMxx)» в диспетчере устройств, правильно ли установлен тип платы и номер порта в настройках программы.
Использование портов ввода/вывода в Arduino UNO
Платформа Arduino Uno имеет 14 цифровых входов/выходов (6 из которых могут использоваться как выходы ШИМ), а также 6 аналоговых входов (которые можно использовать как дополнительные цифровые входы/выходы).

Каждый из 20 портов ввода/вывода может быть использован как вход или выход, при помощи функций pinMode(), digitalWrite(), и digitalRead(). Что это значит?

Например, любой цифровой порт можно сконфигурировать как ВЫХОДНОЙ при помощи функции:
pinMode(pin, OUTPUT);
(где pin – номер порта), а потом изменить его значение (включить его):
digitalWrite(pin, HIGH);
После этого на соответствующий вывод порта (пин) будет подано напряжение 5 вольт. Это можно использовать, например, чтобы зажечь светодиод или для управления каким-то внешним устройством.
Сбросить напряжение на выходе можно передав функции значение LOW.
digitalWrite(pin, LOW);

HIGH и LOW это высокий и низкий уровни сигнала (5 вольт и 0 вольт) – что то вроде True/False в мире ардуино. На самом деле, в программе это обычные константы и они равны 1 и 0 соответственно.

По умолчанию все цифровые порты сконфигурированы как порты ВВОДА, т.е. чтобы использовать пин как входной не требуется явно это указывать.
Но иногда полезно явно обозначить порт как входной при помощи функции:
pinMode(pin, INPUT);

На такой порт можно подать напряжение 5 вольт и считать его состояние при помощи функции:
val = digitalRead(pin);
Если напряжение подано, то переменной val будет присвоено значение HIGH.
Соответственно если убрать напряжение, то значение изменится на LOW. Это можно использовать для чтения состояния кнопки (нажата/не нажата) или датчика (сработал/не сработал).

Будьте осторожны при подключении проводов, есть опасность сжечь контроллер или датчики, случайно подключив провод не туда. Перед включением питания всегда проверяйте всё ли подключено правильно. (Статья на английском: «10 способов как спалить Arduino»).

Тестируем инфракрасные цифровые датчики

Любым датчикам, которые подключаются к ардуино требуется:
  • Питание. Обычно обозначается на плате устройства как VCC или Vin. Питаться устройство может как от самого ардуина (используя порты 5V и 3.3V), так и от внешнего источника питания. Для простых устройств, в качестве питания можно использовать порты ввода/вывода ардуина;
  • Земля (от ground), «минус», «масса», «ноль», «корпус», «общий» и т.п. Обычно обозначается как GND. Если проводить аналогию с батарейкой то электропитание это «+», а земля это «-». Плюсов может быть несколько с разными напряжениями, а минус всегда один (системы с несколькими фазами или с отрицательными напряжениями мы тут рассматривать не будем);
  • Один или несколько сигнальных входов или выходов, которые подключаются к портам ввода-вывода ардуина.

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

Тринадцатый порт это особенный порт, к нему уже подключена лампочка светодиод распаянная на плате ардуина (на схеме она обозначена буквой L). В следующем примере я буду считывать показания датчика, и отображать его состояние при помощи лампочки.
int led = 13;

void setup(){
   // конфигурируем порт светодиода как выходной
   pinMode(led, OUTPUT);
}

void loop(){
   int val = digitalRead(4);
   
   // Если есть напряжение на пине 4
   // то включить светодиод, иначе выключить
   if(val == HIGH){
     digitalWrite(led, HIGH);
   }
   else
   {
     digitalWrite(led, LOW);
   }
   // задержка 50 миллисекунд
   delay(50);
}
Если прошить эту программу в контроллер и поднести к левому датчику белый лист бумаги то лампочка на плате ардуина будет вспыхивать синхронно с левым светодиодом на плате датчиков, следовательно датчик работает и контроллер может считывать с него данные.

Примечание: Задержка 50 миллисекунд вставлена специально как самый просто способ уменьшения влияния дребезга сигнала от датчика и повышения стабильности работы. Рекомендуемое значение 10-100 миллисекунд.
Поясню немного про функции setup и loop.
Большинство алгоритмов, которым нас учат в школе и в институте, укладываются в следующую схему:

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

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


В этом алгоритме нет «конца», вместо этого один из блоков повторяется раз за разом в бесконечном цикле до выключения питания. В ардуине для этого используются функции и void setup() и void loop()

Тестируем ультразвуковые сонары

Для активации сенсора необходимо подать на сигнальный пин (Trig) импульс длительностью 10 мкс, после чего ультразвуковой модуль отправит восемь ультразвуковых импульсов с частотой 40кГц и будет ожидать отраженное эхо. Чем больше расстояние до объекта, тем больше времени требуется звуку, чтобы его преодолеть. На выходе (пин Echo) сонар вернет электрический импульс. Длительность этого импульса будет соответствовать времени, которое потребовалось звуку, чтобы достичь препятствия и вернуться обратно в виде эха. Замерив это время, можно вычислить расстояние до объекта.

Если никаких препятствий не обнаружено, то на выходе будет сигнал длительностью 38 мс. Следующий импульс может быть излучён, только после исчезновения эха от предыдущего. Это время называется периодом цикла (cycle period). Рекомендованный период между импульсами должен быть не менее 50 мс.


// для удобства создадим переменные с номерами портов
int led = 13;
int Echo_pin = 2;
int Trig_pin = 3;

void setup(){
  pinMode(led, OUTPUT);
  // настроим пин Trig как выходной
  pinMode(Trig_pin, OUTPUT);
}

void loop(){
  // инициализируем переменные
  long duration, distance;
  // убеждаемся, что напряжения на пине Trig нет
  digitalWrite(Trig_pin, LOW);
  // для верности ждем 2 микросекунды
  delayMicroseconds(2);
  // подаем сигнал на пин Trig
  digitalWrite(Trig_pin, HIGH);
  // ждем 10 микросекунд
  delayMicroseconds(10);
  // выключаем пин Trig
  digitalWrite(Trig_pin, LOW);
  // для наглядности включаем светодиод
  digitalWrite(led, HIGH);
  // измеряем время сигнала на пине Echo
  duration = pulseIn(Echo_pin, HIGH, 38000);
  // выключаем светодиод
  digitalWrite(led, LOW);
  // задержка 50 миллисекунд
  delay(50);
}

Функция pulseIn считывает импульс (высокий или низкий уровень сигнала) c цифрового порта и возвращает продолжительность импульса в микросекундах.

Первый параметр этой функции — это номер порта, на котором будет ожидаться сигнал.
Второй параметр — тип ожидаемого сигнала (HIGH или LOW, высокий или низкий уровень сигнала).
Последний параметр этой функции опциональный. Это время ожидания сигнала (таймаут) в микросекундах; по умолчанию оно равно одной секунде. В данном случае я указал 38000 микросекунд, потому что в спецификациях сонара сказано, что продолжительность импульса будет равна 38 миллисекундам, в случае если сенсор не обнаружил препятствие (звук рассеялся или отразился).

После загрузки программы светодиод на плате должен начать мерцать 10-20 раз в секунду. По частоте его мерцания можно определить работает сонар или нет. Он загорается каждый раз, когда сонар отправляет ультразвуковую волну, и гаснет когда сонар получает эхо. Если поднести руку к датчикам сонара, то светодиод должен начать мерцать чаще, а если отдалить или направить в дальний угол комнаты, то реже. А еще во время работы сонары издают еле-еле слышимые щелчки.

Тут плохо видно, но принцип, думаю, ясен.


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

USB порт, с помощью которого ардуин подсоединяется к компьютеру, на самом деле является переходником USB – COM. Через это соединение можно передавать любые данные с ардуина на компьютер и обратно. Для того, чтобы задействовать этот порт, его надо сначала инициализировать при помощи функции Serial.begin(9600) (где 9600 это скорость порта), после чего передавать данные можно при помощи функций Serial.print и Serial.println.

Код
int led = 13;
int Echo_pin = 2;
int Trig_pin = 3;

void setup(){
  pinMode(led, OUTPUT);
  // настроим пин Trig как выходной
  pinMode(Trig_pin, OUTPUT);
  // инициализируем последовательной порт
  Serial.begin(9600);
}

void loop(){
  // инициализируем переменные
  long duration, distance;
  // убеждаемся, что напряжения на пине Trig нет
  digitalWrite(Trig_pin, LOW);
  // для верности ждем 2 микросекунды
  delayMicroseconds(2);
  // подаем сигнал на пин Trig
  digitalWrite(Trig_pin, HIGH);
  // ждем 10 микросекунд
  delayMicroseconds(10);
  // выключаем пин Trig
  digitalWrite(Trig_pin, LOW);
  // для наглядности включаем светодиод
  digitalWrite(led, HIGH);
  // измеряем время сигнала на пине Echo
  duration = pulseIn(Echo_pin, HIGH, 38000);
  // выключаем светодиод
  digitalWrite(led, LOW);
  // Скорость звука в воздухе 340 метров в секунду.
  // 1 секунда это 1000 миллисекунд или 1000000 микросекунд.
  // Расстояние в 1 метр звук проходит за 1000000/340 ~ 2941µs.
  // А расстояние в 1 сантиметр звук проходит приблизительно за 29µs
  // считаем расстояние до объекта:
  distance = duration / 29 / 2;
  // передаем получившиеся значение через последовательный порт
  Serial.print(distance);
  Serial.print(" cm");
  Serial.println();
  // задержка 50 миллисекунд
  delay(50); 
}


В программе Arduino IDE выбираем пункт «Сервис» – «Монитор порта» (Serial Monitor), откроется окно, в котором сверху вниз побегут циферки. Это и есть расстояние до объекта.

Управление моторами

Под управлением моторами я понимаю изменение направления и скорости их вращения. Но как можно менять скорость вращения мотора если у ардуина нет аналоговых выходов? За-то у него есть ШИМ!

Объяснение за авторством DI HALT:
"Широтно Импульсная Модуляция (в буржуйской нотации этот режим зовется PWM — Pulse Width Modulation) это способ задания аналогового сигнала цифровым методом, то есть из цифрового выхода, дающего только нули и единицы получить какие то плавно меняющиеся величины. <…>

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

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

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

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

Тестируем ШИМ
В этом тесте я буду плавно регулировать яркость светодиод при помощи ШИМ. Однако тот светодиод, который есть на плате ардуино, для этого не подходит. Дело в том, что он подключен к 13ому порту, который не поддерживает ШИМ. Только некоторые цифровые порты можно использовать для ШИМ. На плате эти порты обычно помечены тильдой «~». В UNO их всего 6 – это 3,5,6,9,10 и 11.

Для подключения светодиода потребуется:
  • Собственно сам светодиод;
  • Резистор от 220 Ω до 2 KΩ;
  • Беспаечная макетная плата;
  • Два провода.

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




Первый цикл плавно зажигает светодиод, а второй плавно его гасит.
void setup() {
  pinMode(13, OUTPUT);
  pinMode(3, OUTPUT);
}

void loop() {
 // цикл перебирает значения i от 0 до 255
 for(int i=0; i<=255; i++){
  // устанавливаем значение ШИМ на пине 3
  analogWrite(3, i);
  // задержка
  delay(5);
 }

 // цикл перебирает значения i от 255 до 0
 for(int i=255; i>=0; i--){
  // устанавливаем значение ШИМ на пине 3
  analogWrite(3, i);
  // задержка
  delay(5);
 }
}


Для задания значения ШИМ используется функция analogWrite(). В первом её параметре задается номер порта, а во втором само значение ШИМ, которое может меняться от 0 до 255.



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

Дело в том, что порты микроконтроллера задумывались исключительно, как сигнальные и рассчитаны только на подключение светодиодов и датчиков, которые почти ничего не потребляют. Согласно спецификациям Arduino UNO, нагрузка на порты ввода-вывода не должна превышать 40 mA.

Для подключения большей нагрузки необходимо организовать две цепи питания – управляющую и силовую.

Обычно под этим понимается, что низковольтная часть (в нашем случае это Arduino) управляет силовой, высоковольтной частью при помощи реле или транзистора-усилителя или специальной микросхемы драйвера.
Как организовывается питание Arduino UNO?
Для надежной работы микроконтроллера и датчиков требуется стабилизированное напряжение. Это значит, что оно не должно постоянно гулять вверх вниз. Например, напряжение в сети может плавать, батарея садится, теряя вольтаж, а еще напряжение может упасть просто при повышении нагрузки. У стабилизированного источника питания напряжение гарантированно находится в заданных пределах. В данном случае 5V±1%.

Для стабилизации напряжения на плате ардуино распаяна специальная микросхема. Даже две.

Назначение этих микросхем такое же, как и у блока питания компьютера – преобразование напряжения и сглаживание его скачков. На входе она принимает постоянный ток напряжением 7-12V (лимиты 6-20V), а на выходе удерживает напряжение на определенном уровне (в данном случае 5V и 3.3V) независимо от его колебаний на входе. Кстати эта микросхема чаще всего выходит из строя, если превысить допустимую нагрузку по питанию. Мой самый первый ардуин так и сгорел :)

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

1. Питание по USB (стабилизированный источник питания).
Во всех приведенных выше примерах ардуин запитывался прямо от USB порта. Это самый просто способ, достаточно просто подключить ардуин к компьютеру. Согласно спецификациям, напряжение на выходе USB стабилизировано.
Плюсы: USB есть в любом компьютере, а компьютеры сейчас почти у всех.
Минусы: Ардуин привязан проводом к компьютеру. Ток и вольтаж ограничены спецификациями USB – 500 mA, 5V.

2. Питание через пин 5V (стабилизированный источник питания).
Если у вас есть стабилизированный 5 вольтовый источник питания, то вы можете использовать этот вывод для питания ардуина. Тот же пин может использоваться как выход для питания внешних устройств. Именно к нему подключались все датчики в примерах выше.
Плюсы: Стандартный разъем шириной 2.54 мм. Для подключения можно использовать провода с вилкой PLS (jumper wire).
Минусы: Нужен стабилизированный источник питания 5В. Не имеет защиты от переполюсовки. Т.е. если случайно перепутать полярность то Arduino, скорее всего, сгорит.

Никогда не подключайте одновременно два стабилизированных источника питания, на пин 5V и USB, вы можете спалить USB порт!

3. Питание через порт Barrel Jack (нестабилизированный источник питания).
На большинстве плат ардуино имеется круглый 2.1 миллиметровый коннектор. Порт имеет защиту от переполюсовки (плюс в центре). Рекомендованное напряжение для этого порта 7-12 вольт. Если напряжение питания будет ниже 7 вольт, то выходное напряжение на пине 5V упадет ниже 5 вольт и плата может стать нестабильной.
Плюсы: Для стационарных проектов, которые будут располагаться недалеко от розетки, можно использовать широко распространенные AC-DC блоки питания 9V-1A или 12V-500mA. Для мобильных проектов можно использовать батарейки или аккумуляторы. Подойдет крона или, как минимум, шесть пальчиковых батареек. Если в проекте используются пальчиковые NiMH аккумуляторы, то надо помнить, что напряжение у них меньше чем у батареек (всего 1.2V), следовательно, для хорошего питания их должно быть не меньше 7.
Минусы: Коннектор питания довольно громоздкий и некрасиво торчит из порта.

4. Питание через Vin Pin (нестабилизированный источник питания).
Некоторые платы расширения (шилды) используют этот пин для питания своей электроники, но через него можно запитать и сам ардуин! Единственное отличие Vin от Barrel Jack в том, что Vin не имеет защиты от переполюсовки (подключен после защитного диода).
Плюсы: Стандартный разъем шириной 2.54 мм. Для подключения можно использовать провода с вилкой PLS (jumper wire).
Минусы: Если случайно перепутать полярность то Arduino, скорее всего, сгорит.

Подключать одновременно два нестабилизированных источника питания (Vin и Barrel jack) тоже плохая идея!

А что будет если подключить одновременно USB и батарею? Такое часто встречается, если, например, вы собрали схему для тестирования электродвигателей и вам надо подключиться к ардуину, чтобы залить программу.
В этом случае Arduino автоматически выберет, откуда ему питаться. При этом предпочтение он будет отдавать питанию через Vin/Barrel jack. Если напряжение на одном из этих портов больше 7.5 вольт, то ардуин будет питаться от них. Поэтому лучше всегда, сначала включать питание от батареи, а потом уже подключаться к USB, а выключать в противоположном порядке, сначала USB потом батарею.
Драйвер движков.
Для разграничения питания силовой и управляющей части используется специальная микросхема драйвер. В «Scootaloo» я использовал драйвер движков на основе микросхемы L298P. Микросхема L298P представляет собой сдвоенный мостовой драйвер двигателей и предназначена для управления DC или шаговыми двигателями. Данная микросхема находит очень широкое применение в любительской робототехнике. Она способна независимо управлять скоростью и направлением вращения двух двигателей при максимальной нагрузке до 2А на каждый двигатель.

Шилды на основе микросхемы L298P выпускают несколько производителей, все они приблизительно одинаковы (могут отличаться номерами используемых портов и способами подключения внешней нагрузки). Плата занимает 4 цифровых порта, два из которых задают скорость вращения двигателей при помощи ШИМ, а два других задают направление вращения. Для разделения цепей питания, плата запитывается напрямую от батареи через Vin либо от внешнего источника питания через клеммы на плате. Фактически драйвер это усилитель, который усиливает ШИМ от ардуино за счет внешнего источника питания.
Немного о моторах.
В своём роботе я использовал два мотора производства фирмы pololu. В шасси Zumo под них есть крепления.
Напряжение: 6-12V.
Нагрузка: до 1600 mA
Мотор поставляется со встроенным редуктором. Для участия в соревнованиях сумо я ставил моторы с редуктором 1:298. Во всех примерах ниже я использовал именно эти моторы, хотя для езды по линии можно взять моторы с другим редуктором с меньшим передаточным числом, чтобы скорость была выше.
Схема тестирования


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


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

int PWMA = 5; // управляющий ШИМ двигателя A
int PWMB = 6; // управляющий ШИМ двигателя B
int INA = 4; // направление вращения двигателя А
int INB = 7; // направление вращения двигателя В

void setup()
{
  pinMode(PWMA, OUTPUT); // конфигурируем все управляющие пины как выходные
  pinMode(PWMB, OUTPUT);
  pinMode(INA, OUTPUT);
  pinMode(INB, OUTPUT);
}

void loop()
{
  digitalWrite(INA, HIGH); // направление вращения двигателя А - вперед
  analogWrite(PWMA, 80); // включаем двигатель А на скорости 80 из 255
  delay(2000); // ждем 2 секунды
  
  digitalWrite(INA, LOW); // направление вращения двигателя А - назад
  analogWrite(PWMA, 80); // включаем двигатель А на скорости 80 из 255
  delay(2000); // ждем 2 секунды

  digitalWrite(INA, HIGH); // направление вращения двигателя А - вперед
  analogWrite(PWMA, 255); // включаем двигатель А на максимальной скорости
  delay(2000); // ждем 2 секунды

  digitalWrite(INA, LOW); // направление вращения двигателя А - назад
  analogWrite(PWMA, 255); // включаем двигатель А на максимальной скорости
  delay(2000); // ждем 2 секунды
}



— It's Alive!!!

Пора собрать робота

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

Нагретым над конфоркой шилом я проделал отверстия сверху и снизу батарейного отсека. Снизу он крепится к Zumo винтиками, а сверху к нему крепится Arduino при помощи двух пластиковых стоечек и винтиков.


Перед тем как подключаться к USB, надо обязательно вынимать провод из порта Vin, а то робот будет пытаться ехать (по этому проводу на моторы будет идти питание от USB).



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

Еще я поменял в программе названия переменных с номерами портов. У робота должны быть не просто моторы A и B, а ЛЕВЫЙ и ПРАВЫЙ моторы, которые крутят левую и правую гусеницу соответственно. Также я создал две константы с именами FORWARD и BACKWARD для обозначения направления вращения двигателя вперед или назад.

Задействуем датчики:
Код
int PWM_L = 5; // управляющий ШИМ левого двигателя
int PWM_R = 6; // управляющий ШИМ правого двигателя
int DIR_L = 4; // направление вращения левого двигателя
int DIR_R = 7; // направление вращения правого двигателя

// инфракрасные датчики IR
int IR_L = 2; // левый датчик
int IR_C = 3; // центральный датчик
int IR_R = 8; // правый датчик

const int FORWARD = 1;
const int BACKWARD = 0;

void setup()
{
  pinMode(PWM_L, OUTPUT); // конфигурируем все управляющие пины как выходные
  pinMode(PWM_R, OUTPUT);
  pinMode(DIR_L, OUTPUT);
  pinMode(DIR_R, OUTPUT);
}

void loop()
{
  // получаем показания датчиков
  // переменные sensor_L, sensor_C, и sensor_R содержат показания датчиков,
  // которые могут быть HIGH или LOW (true/false или 1/0).
  int sensor_L = digitalRead(IR_L);
  int sensor_C = digitalRead(IR_C);
  int sensor_R = digitalRead(IR_R);

  digitalWrite(DIR_L, FORWARD); // направление вращения левого двигателя - вперед
  digitalWrite(DIR_R, FORWARD); // направление вращения правого двигателя - вперед

  // если один из датчиков оказался на черной линии, то останавливаемся
  // иначе едем прямо
  if(sensor_L == LOW || sensor_C == LOW || sensor_R == LOW){
    analogWrite(PWM_L, 0); // выключаем левый двигатель
    analogWrite(PWM_R, 0); // выключаем правый двигатель
  }
  else
  {
    analogWrite(PWM_L, 255); // включаем левый двигатель на максимальной скорости
    analogWrite(PWM_R, 255); // включаем правый двигатель на максимальной скорости
  }
  // ждем 50 миллисекунд
  delay(50);
}

Если датчики робота находятся над чем-то светлым то робот едет прямо, если хотя бы один из датчиков оказывается над чем-то темным, то он останавливается.
Тут я налепил кусок черной изоленты на лист бумаги:


— А поворачивать как?

Поворачивать можно двумя способами.
Во первых, можно разворачиваться стоя на месте. Для этого запускаем двигатели в разные стороны:
    digitalWrite(DIR_L, FORWARD);
    analogWrite(PWM_L, 255);
    digitalWrite(DIR_R, BACKWARD);
    analogWrite(PWM_R, 255);

Или можно плавно поворачивать во время движения вперед или назад, для этого надо немного притормозить один из двигателей. Например, чтобы робот повернул вправо надо замедлить правый двигатель:
    digitalWrite(DIR_L, FORWARD);
    analogWrite(PWM_L, 255);
    digitalWrite(DIR_R, FORWARD);
    analogWrite(PWM_R, 80);


— Может можно уже и по линии поездить?

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

Расстояние между крайними датчиками на плате – 5см, если поставить робота ровно на линию, то все три лампочки, которые сигнализируют состояние датчиков, должны быть выключены. Если же немного сдвинуть робота с линии, то один из датчиков (левый или правый) сработает.

А зачем тогда центральный датчик на плате? На самом деле, часто случается так, что радиуса поворота робота не хватает, чтобы ехать ровно вдоль извивающихся участков линии, и робот начинает все больше и больше съезжать с неё. Когда вне линии оказывается уже не один, а два датчика (боковой и центральный) то самое время принимать меры. Для этого я буду использовать разворот на месте. Обычно достаточно даже кратковременно включить один из движков на реверс, чтобы сильно сократить радиус поворота и вернуть робота обратно на линию.



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

Так прямо и напишем:
void loop()
{
  // переменные sensor_L, sensor_C, и sensor_R содержат показания датчиков,
  // которые могут быть HIGH или LOW (true/false или 1/0).
  int sensor_L = digitalRead(IR_L);
  int sensor_C = digitalRead(IR_C);
  int sensor_R = digitalRead(IR_R);

  // Если все датчики на линии то едем прямо.
  if(sensor_L == LOW && sensor_C == LOW && sensor_R == LOW){
    digitalWrite(DIR_L, FORWARD);
    analogWrite(PWM_L, 255);
    digitalWrite(DIR_R, FORWARD);
    analogWrite(PWM_R, 255);
  }
  // Если правый датчик съехал с линии, поворачиваем влево.
  if(sensor_L == LOW && sensor_C == LOW && sensor_R == HIGH){
    digitalWrite(DIR_L, FORWARD);
    analogWrite(PWM_L, 80);
    digitalWrite(DIR_R, FORWARD);
    analogWrite(PWM_R, 255);
  }
  // Если левый датчик съехал с линии, поворачиваем вправо.
  if(sensor_L == HIGH && sensor_C == LOW && sensor_R == LOW){
    digitalWrite(DIR_L, FORWARD);
    analogWrite(PWM_L, 255);
    digitalWrite(DIR_R, FORWARD);
    analogWrite(PWM_R, 80);
  }
  // Если съехал правый и центральный, то включаем реверс левой гусеницы.
  if(sensor_L == LOW && sensor_C == HIGH && sensor_R == HIGH){
    digitalWrite(DIR_L, BACKWARD);
    analogWrite(PWM_L, 255);
    digitalWrite(DIR_R, FORWARD);
    analogWrite(PWM_R, 255);
  }
  // Если съехал левый и центральный, то включаем реверс правой гусеницы.
  if(sensor_L == HIGH && sensor_C == HIGH && sensor_R == LOW){
    digitalWrite(DIR_L, FORWARD);
    analogWrite(PWM_L, 255);
    digitalWrite(DIR_R, BACKWARD);
    analogWrite(PWM_R, 255);
  }
  // Если все датчики не на линии то едем прямо :)
  if(sensor_L == HIGH && sensor_C == HIGH && sensor_R == HIGH){
    digitalWrite(DIR_L, FORWARD);
    analogWrite(PWM_L, 255);
    digitalWrite(DIR_R, FORWARD);
    analogWrite(PWM_R, 255);
  }
  // ждем 50 миллисекунд
  delay(50);
}


— В глазах рябит.
— Мне уже становится скучно.
— Ну и мешанина! Твайлайт, где твоё чувство прекрасного?
— Что ты предлагаешь, Рэрити?
— Тут в самый раз подойдет конструкция «Переключатель». В Си подобных языках она называется switch.
— Switch в зависимости значения одной переменной выполняет разный код, а у нас тут ТРИ переменных, три датчика!
— Без проблем, показания датчиков это ведь бит, значит, эти переменные могут принимать значения только 0 или 1. Три бита можно сдвигом сложить в одну переменную с типом целое.

void loop()
{
  // получаем показания датчиков
  int sensor_L = digitalRead(IR_L);
  int sensor_C = digitalRead(IR_C);
  int sensor_R = digitalRead(IR_R);

  // Введем новую переменную – state (состояние).
  // В ней будем хранить состояния всех трех датчиков.
  int state = (sensor_L<<2) + (sensor_C<<1) + sensor_R;

  // в зависимости от состояния датчиков будем
  // изменять направление вращения движков и их скорости.
  switch (state)
  {
    case B000: // Если все датчики на линии то едем прямо.
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = FAST;     speed_R = FAST;
              break;
    case B100: // Если левый датчик съехал с линии, поворачиваем вправо.
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = FAST;     speed_R = SLOW;
              break;
    case B110: // Если съехал левый и центральный, то включаем реверс правой гусеницы.
              motor_L = FORWARD;  motor_R = BACKWARD;
              speed_L = FAST;     speed_R = FAST;
              break;
    case B001: // Если правый датчик съехал с линии, поворачиваем влево.
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = SLOW;     speed_R = FAST;
              break;
    case B011: // Если съехал правый и центральный, то включаем реверс левой гусеницы.
              motor_L = BACKWARD; motor_R = FORWARD;
              speed_L = FAST;     speed_R = FAST;
              break;
    default:   // во всех остальных случаях едем прямо :)
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = FAST;     speed_R = FAST;
              break;
  }

  digitalWrite(DIR_L, motor_L); // задаём направление вращения левого двигателя
  digitalWrite(DIR_R, motor_R); // задаём направление вращения правого двигателя

  analogWrite(PWM_L, speed_L);  // задаём скорость левого двигателя
  analogWrite(PWM_R, speed_R);  // задаём скорость правого двигателя

  // ждем 50 миллисекунд
  delay(50);
}


Переменная state может принимать следующие значения:
/-------------------\
| l | c | r | state |
|-----------|-------|
| 0 | 0 | 0 |   0   | - все датчики на линии - едем прямо
| 0 | 0 | 1 |   1   | - один правый датчик не на линии - поворот налево
| 0 | 1 | 1 |   3   | - два правых датчика не на линии - поворот налево
| 1 | 0 | 0 |   4   | - один левый датчик не на линии - поворот направо
| 1 | 1 | 0 |   6   | - два левых датчика не на линии - поворот направо
\-------------------/
0 - на линии, 1 - вне линии

— И правда, красиво стало!
— Кстати, по науке такая конструкция называется «конечным автоматом», или еще иногда можно встретить «машина состояний».


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

Добавим ультразвук!

Только с сонарами есть проблема, неясно как их крепить к роботу.

Причем их надо не просто абы как закрепить, а закрепить ровно по бокам в передней части так, чтобы робот мог видеть слышать предметы прямо перед собой. Еще есть проблема с подключением сонаров. Модулям нужно питание, а единственный выход 5V уже занят планкой инфракрасных датчиков. Что делать?

Для решения этих проблем я воспользовался платой-шилдом под названием Prototype Shield v.5. На первый взгляд она кажется пустой и бесполезной, куча дырок и почти никаких электронных компонентов. Но не стоит судить по первому впечатлению.

Во-первых, по центру платы разведены две контактных дорожки GND и 5V от которых можно запитывать датчики и прочие устройства, а по бокам от них находятся неразведенные дорожки, которые можно использовать для сигнальных контактов датчиков. Всего-то и нужно припаять к этим дорожкам 4 штыревых разъема (PIN Header) и готов слот для подключения датчика!

А монтажные отверстия можно использовать для закрепления сонаров! Я взял обычную скрепку, выпрямил и аккуратно обогнул её вокруг цилиндров-динамиков, концы пропустил в отверстия на сонаре, так чтобы скрепка в распорку закреплялась в них. При этом одним из концов скрепка вставляется в монтажное отверстие на шилде и припаивается. Не очень надежно, но сойдет. :) Надо только проследить, чтобы скрепка ничего нигде не замкнула на плате модуля сонара, проложить изоленту если нужно.

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

Вот что получилось:


Финальная сборка
Вот, сделал даже 3д модель :).


Пусть теперь на пути робота будут иногда попадаться препятствия, а ему надо будет их объезжать. Возьмем самый просто способ. Будем отслеживать расстояние до препятствия, и как только робот подъедет к нему на определенное расстояние, включится алгоритм объезда, в результате выполнения которого, робот съедет с линии и совершит манёвр в виде буквы «П», после чего продолжит движение по линии.

Код
// инфракрасные датчики IR
int IR_L = 2; // левый датчик
int IR_C = 3; // центральный датчик
int IR_R = 8; // правый датчик

// драйвер движков
int DIR_L = 4; // направление вращения левого двигателя
int PWM_L = 5; // управляющий ШИМ левого двигателя
int PWM_R = 6; // управляющий ШИМ правого двигателя
int DIR_R = 7; // направление вращения правого двигателя

// ультразвуковые дальномеры
int TRIG_L = 9;  // управляющий пин левого сонара
int ECHO_L = 10; // выходной пин левого сонара
int TRIG_R = 11; // управляющий пин правого сонара
int ECHO_R = 12; // выходной пин правого сонара

// светодиоды подключены к аналоговым входам
int LED_L = A0;
int LED_R = A1;

const int FORWARD = 1;
const int BACKWARD = 0;

const int FAST = 255;
const int SLOW = 60;
const int STOP = 0;

int obstacle_state = 0;
int i = 0; // счетчик

void setup()
{
  Serial.begin(9600);
  pinMode(PWM_L, OUTPUT); // конфигурируем все управляющие пины как выходные
  pinMode(PWM_R, OUTPUT);
  pinMode(DIR_L, OUTPUT);
  pinMode(DIR_R, OUTPUT);
  pinMode(TRIG_L, OUTPUT);
  pinMode(TRIG_R, OUTPUT);
  pinMode(LED_L, OUTPUT); // конфигурируем пины светодиодов как выходные 
  pinMode(LED_R, OUTPUT);

}

// функция опроса сонара
long check_sonar(int TrigPin, int EchoPin)
{
  // инициализируем переменные
  long duration, distance;

  // убеждаемся, что напряжения на пине Trig нет
  digitalWrite(TrigPin, LOW);
  // для верности ждем 2 микросекунды
  delayMicroseconds(2);
  // подаем сигнал на пин Trig
  digitalWrite(TrigPin, HIGH);
  // ждем 10 микросекунд
  delayMicroseconds(10);
  // выключаем пин Trig
  digitalWrite(TrigPin, LOW);

  // измеряем время сигнала на пине Echo
  duration = pulseIn(EchoPin, HIGH, 38000);
  // Вычисляем расстояние до объекта:
  // Скорость звука в воздухе 340 метров в секунду.
  // 1 секунда это 1000 миллисекунд или 1000000 микросекунд.
  // Расстояние в 1 метр звук проходит за 1000000/340 ~ 2941µs.
  // А расстояние в 1 сантиметр звук проходит приблизительно за 29µs
  distance = duration / 29 / 2;

  return distance;
}

void loop()
{
  long distance_L, distance_R;

  // получаем показания инфракрасных датчиков
  int val_L = digitalRead(IR_L);
  int val_C = digitalRead(IR_C);
  int val_R = digitalRead(IR_R);

  int state = (val_L<<2) + (val_C<<1) + val_R;
  
  int motor_L = FORWARD;
  int motor_R = FORWARD;
  int speed_L = 255;
  int speed_R = 255;
  
  // включаем левый светодиод
  digitalWrite(LED_L, HIGH);
  // опрашиваем левый сонар
  distance_L = check_sonar(TRIG_L, ECHO_L);
  // выключаем левый светодиод
  digitalWrite(LED_L, LOW);   

  // включаем правый светодиод
  digitalWrite(LED_R, HIGH);
  // опрашиваем правый сонар
  distance_R = check_sonar(TRIG_R, ECHO_R);
  // выключаем правый светодиод
  digitalWrite(LED_R, LOW);
  
  // если мы не в режиме объезда препятствия
  if(obstacle_state == 0){
    // и дистанция до объекта меньше 9 см
    if( (distance_L < 9) || (distance_R < 9) )
    {
      // входим в режим объезда препятствий
      obstacle_state = 1;
    }
  }
  
  switch (state)
  {
    case B000: 
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = FAST;     speed_R = FAST;
              break;
    case B100: 
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = FAST;     speed_R = SLOW;
              break;
    case B110: 
              motor_L = FORWARD;  motor_R = BACKWARD;
              speed_L = FAST;     speed_R = FAST;
              break;
    case B001: 
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = SLOW;     speed_R = FAST;
              break;
    case B011: 
              motor_L = BACKWARD; motor_R = FORWARD;
              speed_L = FAST;     speed_R = FAST;
              break;
    default:
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = FAST;     speed_R = FAST;
              break;
  }
  
  // манёвр объезда препятствия
  switch (obstacle_state)
  {
    case 1: // поворот влево
              motor_L = BACKWARD;  motor_R = FORWARD;
              speed_L = FAST;      speed_R = FAST;
              i++; // увеличиваем счетчик
              if(i > 10) {
                i=0; // сбрасываем счетчик
                obstacle_state=2; //переход в состояние 2
              }
              break;
    case 2: // прямо
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = FAST;     speed_R = FAST;
              i++; // увеличиваем счетчик
              if(i > 15) {
                i=0; // сбрасываем счетчик
                obstacle_state=3; //переход в состояние 3
              }
              break;
    case 3: // поворот на право
              motor_L = FORWARD;  motor_R = BACKWARD;
              speed_L = FAST;     speed_R = FAST;
              i++; // увеличиваем счетчик
              if(i > 10) {
                i=0; // сбрасываем счетчик
                obstacle_state=4; //переход в состояние 4
              }
              break;
    case 4: // прямо
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = FAST;     speed_R = FAST;
              i++; // увеличиваем счетчик
              if(i > 25) {
                i=0; // сбрасываем счетчик
                obstacle_state=5; //переход в состояние 5
              }
              break;
    case 5: // поворот на право
              motor_L = FORWARD;  motor_R = BACKWARD;
              speed_L = FAST;     speed_R = FAST;
              i++; // увеличиваем счетчик
              if(i > 10) {
                i=0; // сбрасываем счетчик
                obstacle_state=6; //переход в состояние 6
              }
              break;
    case 6: // прямо
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = FAST;     speed_R = FAST;
              i++; // увеличиваем счетчик
              // пока все датчики не окажутся на линии
              if(state == B000) {
                i=0; // сбрасываем счетчик
                obstacle_state=7; //переход в состояние 7
              }              
              break;
    case 7: // прямо (еще немного)
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = FAST;     speed_R = FAST;
              i++; // увеличиваем счетчик
              if(state > B000) { // едем прямо еще немного
                i=0; // сбрасываем счетчик
                obstacle_state=8; //переход в состояние 8
              }              
              break;
    case 8: // поворот влево
              motor_L = BACKWARD;  motor_R = FORWARD;
              speed_L = FAST;      speed_R = FAST;
              i++; // увеличиваем счетчик
              if(state == B000 || i > 10) {
                i=0; // сбрасываем счетчик
                obstacle_state=0; //конец маневра объезда
              }
              break;
  }

  digitalWrite(DIR_L, motor_L); // задаём направление вращения левого двигателя
  digitalWrite(DIR_R, motor_R); // задаём направление вращения правого двигателя

  analogWrite(PWM_L, speed_L);  // задаём скорость левого двигателя
  analogWrite(PWM_R, speed_R);  // задаём скорость правого двигателя
  // ждем 50 миллисекунд
  delay(50);
}






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

Робот будет поворачиваться вокруг своей оси вслед за объектом. Причем, если он потеряет объект из вида, то будет поворачиваться в ту сторону, где он его видел в последний раз (слежение с памятью).

Код
void loop()
{
  long distance_L, distance_R;

  int motor_L = FORWARD;
  int motor_R = FORWARD;
  int speed_L = FAST;
  int speed_R = FAST;
  
  // опрашиваем левый сонар
  distance_L = check_sonar(TRIG_L, ECHO_L);

  // опрашиваем правый сонар
  distance_R = check_sonar(TRIG_R, ECHO_R);
  
  // obstacle_X присваивается 1 если 
  // в 25 сантиметрах от сонара есть какой-то объект
  int obstacle_L = (distance_L < 25);
  int obstacle_R = (distance_R < 25);
  
  // лампочки отображают видит ли сонар объект
  digitalWrite(LED_L, obstacle_L);
  digitalWrite(LED_R, obstacle_R);

  int state = ( obstacle_L << 1 ) + obstacle_R;
  
  switch (state)
  {
    case B00: 
              // поворот по памяти
              turn = last_turn;
              break;
    case B10: 
              turn = LEFT;
              break;
    case B01: 
              turn = RIGHT;
              break;
    case B11: 
              turn = NOTURN;
              break;
  }

  // сохраняем последнее значение
  last_turn = turn;
  
  switch (turn)
  {
    case LEFT: 
              motor_L = BACKWARD;  motor_R = FORWARD;
              speed_L = FAST;      speed_R = FAST;
              break;
    case RIGHT: 
              motor_L = FORWARD;  motor_R = BACKWARD;
              speed_L = FAST;     speed_R = FAST;    
              break;
    case NOTURN: 
              motor_L = FORWARD;  motor_R = FORWARD;
              speed_L = STOP;      speed_R = STOP;
              break;
  }
  
  digitalWrite(DIR_L, motor_L); // задаём направление вращения левого двигателя
  digitalWrite(DIR_R, motor_R); // задаём направление вращения правого двигателя

  analogWrite(PWM_L, speed_L);  // задаём скорость левого двигателя
  analogWrite(PWM_R, speed_R);  // задаём скорость правого двигателя
  // ждем 50 миллисекунд
  delay(50);
}



Че почем?

Итого: Около 5к рублей. Прилично, хотя если учесть, что я его собирал по частям в течении полугода то не так уж и много. Сначала я купил ардуин и датчики, а как только с ними наигрался купил шасси.

Что еще к нему можно подсоединить?

  • Например, беспроводную связь с компьютером/смартфоном через Wi-Fi, Bluetooth, ZigBee.
  • Голосовое управление. И скомандовать ему… эм… ну вы поняли :)
  • Различные датчики света, цвета, движения, давления, влажности, вибрации, огня, дыма, алкоголя, пыли, магнитного поля и т.д. и т.п.
  • Если захочется подключить видеокамеру то потребуется что-то более мощное чем простой ардуино.
Спектр девайсов и возможных применений огромен и продолжает расширяться.


— This isn't even my final form!

Заключение

Вот такая вот программируемая DIY игрушка получилась. Буду только рад если кто-то захочет построить такого же робота или, может быть, статья подаст кому-то идею для своего собственного проекта. Вопросы можно задавать тут или в скайпе — mutronics.sdf. Помогу чем смогу, хотя в электронике, да и в программировании микроконтроллеров я сам новичок.

P.S. С трудом уместил статью в отведенные 64000 символов (максимально допустимая длина поста на табуне). Пришлось кое-что урезать. В итоге статья получилась длиной ровно 64000 символов!
Как уж тут не вспомнить один старый фантастический рассказ.

75 комментариев

Специально для табуна и ко дню космонавтики!
mutronics
+17
Чувак, моя степень уважения к тебе после этого поста возрасла раз эдак в 2^11 (лол, просто вспомнилась недавняя популярная игрушка).
Svetomech
+4
Черт, все-таки от поней есть толк.
Давно хотел уж разочароваться в фандоме.
Спасибо, порадовал. Прочту.
Shark2dot1
+1
Каждую часть описания спрячь под спойлер!
idem_id
+1
Ошибка: Поле Текст слишком длинное (максимально допустимо 64000 символов)

Да и не вижу смысла. Это чтобы побыстрее прокурить к комментариям?
mutronics
0
К примеру «Как организовывается питание Arduino UNO?» нафиг не нужно. -Вот тебе и лишние символы появятся.
Да и не вижу смысла. Это чтобы побыстрее прокурить к комментариям?

Нет — чтобы выбрать интересующее.
Воды дохрена.
idem_id
0
Тебе не нужно, а другим нужно. Статья все же не на профи рассчитана.
mutronics
+3
Круто!
0x1042E
0
EvilKeeper
+16

Обожаю ету гифку
Krueger
+4

А у меня такой робот ^^
Тут больше фоток vk.com/wall75136633_841
SL-RU
+1
Малинка? Еще не добрался до неё, но собираюсь. Хотя еще думаю на счет beaglebone.
mutronics
0
пост классный:)
а вот схему реально под спойлер бы убрать…
bw_gentlepony
0
Она же ужата до 800х800 пикселей. Например в ЯРОКе ограничение 1280px.
mutronics
0
Шикарно!
Walkcow
0
Молодец. Спорим, не связанные с подобной техникой не осилят?
Статья занимательная, но чому Скуталу?
И да — нужно больше датчиков. И что-нибудь огнестрельное. И прочный корпус. Из титана. И…
Нет, тут нужен двигатель помощнее…
Goklas
+2
Статья занимательная, но чому Скуталу?

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

Всё будет. stay tuned
mutronics
+1
Что же -летать он не может, канон соблюден)
NotQwerty
+6
Это же мегакруто! 0___0
agent_diego
+1
Чувак, да это просто класс! Всё так подробно расписано, что мои кривые руки таки смогут это сделать, время б только ещё отыскать…
dender
+2
При наличии всего необходимого его можно собрать за пару вечеров.
mutronics
+1
Хотеть
am31
+5
Пост космической сложности, по сравнению с тем, что тут обычно.
Было интересно.
ncuxonam
0
комментарий скрыт
ничего то вы не понимаете
drakonn90
+1
А тебе нужен робот-андроид умеющий летать и пулять лазерами?
Nightvision
+1
как минимум я ожидал увидеть что-то вроде плюшевого киборга, то есть внешне это плюшевая скуталу, а внутри — робоначинка, позволяющая ей ходить самостоятельно и не натыкаться на стены. и был разочарован, увидев просто груду радиодеталей на колесиках, больше похожую на танк, чем на пони
SayDeesNeeLyu
0
Однако, я восхищаюсь вашей работой, сударь. Множество из указанного вами мне и не понять, что лишь увеличивает ваши умения и величие.
Octavian
0
Да ну ты че… Это всё мелочи и ламерство.
mutronics
+1
Если даже это для вас мелочи, то то, что для вас более крупные вещи — уже абсолютные подвиги с вашей стороны.
Octavian
+1
Эта кропотливая работа достойна уважения
mutronics , надеюсь этот опыт тебе пригодится в дальнейшем на твоей будущей работе, специалистов по микоконтроллерам не очень много!
drakonn90
0
Работа есть, спасибо. С микроконтроллерами не связана, а вот с программированием да.
mutronics
0
Очень интересно, жалко что в программировании я не силен, а спаять и собрать это мы могем)
Lobotomit
+2
while(1)
{
Уважение++;
}
Отличная работа. Похоже, я осознал, зачем придумали Arduino. Намного интереснее, чем готовая плата, где ничего уже не модифицировать, но это подходит только для подобных самоделок. Алсо, меня впервые не перекосило от её вида.
kt315
0
У ардуины есть своя ниша. Она задумывалась как учебная платформа и свои функции вполне выполняет.
mutronics
0
Это офигенно!
Жаль только непонятно
Reborn
+2
Круть. Просто круть.
Hippie
+1
Проверил ссылку в адресной строке.
Вроде бы я не на хабре о:
А вообще, ето же потрясающе! Выпилить упоминания поней и хоть на хабр в DIY.
Fliardo
+1
Табун не хуже хабра! Кстати ссылка на мою прошлую статью уже проскакивала на хабре.
mutronics
+1
Зачем же выпиливать сразу то? Кучу статей уже с положительными рейтингами видел на Хабре, которые включали в себя поней.
Svetomech
+1
Осталось сделать внешне похоже на каноничную Скут и привезти на РБК :3
LeTchiK
+2
Это нереально круто! Тебе нужно срочно оформлять его в облик Скут и на РБК!
Pony4tonado
0
Сделать из четырёхколёсного на четырёхкопытное? Боюсь не успею.
mutronics
0
Жаль, но было бы интересно увидеть что-нибудь эдакое на каком-нибудь брони-конвенте.
Pony4tonado
0
Стенд «запрограммируй Scootaloo»? Не уверен, что это будет хоть кому-то интересно, да и это не связано с пони.
mutronics
0

Шикарно! Аж самому захотелось подобной фигней заниматься.
Divider
+1
Снимаю шляпу. Вот когда подобные ребята — часть сообщества, моя гордость за него возрастает минимум на 20%
EnergyTone
0
++++++++++++++!

Великолепный материал. Я от практической электроники далек, но даже с моими отрывочными познаниями сумел понять много интересного! Спасибо большое, mutronics!
Dilandu
0
Что, серьёзно?! Настоящщий живой робот???
Krueger
+1
ТЫ ЧЁРТОВ ГЕНИЙ!!! Мой предел — простейший усилок и мультивибраторы правда, ещё с фильтрами были дела
Mihail1028
0
О чем ты? В аналоговой электронике я вообще ничего не понимаю.
mutronics
+1
Мультивибратор няшка ^^
Он был моей первой собранной от начала до конца схемой. Выпаивал целый вечер детальки из старых телевизионных плат и отлаживал :3
SL-RU
0
Ха, не, взял и купил
Mihail1028
0
Ах, вечно эти мажоры и буржуи, со своими дорогими девбордами, с удобными функциями на Си и юсб-интерфейсом…
Куда мне, со своими мегами-тиньками да LPT-программатором на 74HC244… *Ворчливый Зануда mode off*

Вообще, классно, отличная статья! Нужно moarъ роботов!
Надо бы и мне что-нибудь интересное на МК сделать.
Blendwerk
0
Ну все с чего-то начинают. Я вот начал с Arduino. Главное же результат :)
mutronics
0
— Зашел. Прочитал, проскроллил… так, не понял, хабра сменила дизайн?
Wait. ТАБУН?!

Два чаю автору! Спасибо, статья превосходная, подача материала внятная и доходчивая.
Orhideous
0
— Здравствуйте, это канал о пони?
— Да.
— Как собрать преобразователь USB/UART на FT232?
mutronics
+2
Хм… а ковш спереди можно сделать поднимающимся, при контакте с препятствием? Тогда робот мог бы подобно настоящему сумотори лишать противника опоры и выносить с ринга.
А вообще классная штука, когда я вижу такое, то жалею что не смог в физику :(
Skyhide
0
Хм… а ковш спереди можно сделать поднимающимся, при контакте с препятствием?
Это в планах.
жалею что не смог в физику :(
Нет тут физики, по крайней мере не на таком уровне. Это не сложнее чем собрать комп из комплектующих.
mutronics
0
Любая работа с микроэлементами начинается с физики, так же как программирование с информатики) Увы, не могу похвастаться ни тем, ни другим. Разве что копыта только под паять заточены неплохо :D
Skyhide
0
Возможно в банке была фотография Rainbow Dash с автографом...(шутка от Дискорда)
BraveWindie
0
Великолепно сделан робот, но таки зачем 3 датчика цвета? Можно обойтись и одним.Ставишь определённую единицу для показаний с датчика, а после вычитание, домножение и на мощность моторов :-)
Alan_Dreamer
0
А как определить куда поворачивать влево или вправо если датчик один?
mutronics
0
Переведи показания с датчиков в цифры, замерь показания на краях линии, сложить/вычесть, домножить, к двигателям (к одному:«вычесть из показаний с краёв, домножить, на мощность» к другому:«приплюсовать к показаниям, домножить, на мощность»)при повороте не в ту сторону поменять «вычесть и приплюсовать» моторами.Вроде всё)А так восхищён, никогда не понимал принципа «робо-сумо».Победа зависит от случая или как?
Alan_Dreamer
0
Датчик тогда будет на краю линии постоянно болтаться. А так вообще вариант.

Победа зависит от случая или как?

Скорее от мощности движков, но и от удачи тоже.
mutronics
0
Настоящее руководство по использованию Arduino! Определнно полюс.

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

Например, отсуствие энкодеров на колесах не позволит контролировать скорость движения. Это означает, что следует распрощаться с нормальными алгоримами управления движением, основанными на кинематической модели. Еще меня очень удивляет, что в MotorShield Arduino двигатели управляются в нелинейном режиме (ШИМ подается на вывод disable Н-моста). По карайней мере так было в старых версиях. Лучше все-таки использовать линейный, на не полной скважности ШИМ разница будет.

Оптическая линейка только с тремя светодиодами не дает точной информации о растоянии от линии до ее центра. Я бы рекоммендовал подключить ее каналы к АЦП, тогда можно было бы оценивать рассояния суммирую показания датчиков с разными весами. Тогда можно было бы организовать контур обратной связи с П- или ПИ- регулятором для управления ориентацией. Движение стало бы плавнее. Или миновать компараторы нельзя? Тогда можно попробовать какую-либо фильтрацию сигнала…

Кстати, сонары рассчитаны на подключение к таймерам, работающим в режиме input capture (захвата). Но не думаю, что библиотека arduino позволет их так запустить. Основной цикл лучше запускать по таймеру, а не через delay. Например, та же команда измерения длительности импульса с сонаров будет выпоняться разное время, что повлияет на работу регуляторов.
disRecord
0
О! Наконец-то критический комментарий.

Все так. Задач я считаю достаточно для такого простого робота, тут важен компромисс со сложностью.
Энкодеры кстати можно вставить, есть родные, конструкция платформы позволяет.

Для моих задач хватало трех датчиков спереди, и еще двух сзади, но я не писал про них в статье.
И опять же есть и родное решение для этой платформы с шестью датчиками.

Сонары подключены неправильно, знаю ^_^ Их надо вешать на прерывания, библиотека arduino это делать позволяет, но я решил описывать все подробно, в статью это не влезло, упростил. И так сойдет %)
mutronics
0
Если честно, то я просто не посмотрел на дату поста.

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

В данном проекте уже видно, что выводов МК явно уже не хватает на более точные линейки и энкодеры, придется чем-то другим жертвовать или ставить Arduino Mega.

Сам я не сторонник библиотеки Arduino. AVR просты и так, библиотека закрывает местами взгляд на возможности МК, как мне кажется. Но в данном посте этому место явно не было. (Скуталу достоин нескольких постов!)

Будут еще проекты?
disRecord
0
О да! И кстати по теме, которую ниже обсуждают. Stay tuned ^_^
mutronics
0
хм. я то думал хотябы в виде пони будет…

вообще не знал, что есть хорошие брони-электронщики. это круто.

статья полезная, но нудная. зная всякие инструкциис gt и хабры, подумал, что так и должно быть.
ololsha228
0
а еще, ну так на всякий, с этого года я кое-как пытаюсь сделать ходячего робота четырехногого с нервной системой на ардуине. жаль, что никому это не интересно. но это уже сааавсем другая история.
ololsha228
0
А кто сказал что никому не интересно? Может, кто-то уже делает подобное, у меня в планах например. Но до ходячего мне пока далеко, технология простых прочных суставов пока ещё в зачатке
narf
0
Только для нормального устойчивого быстрого хождения там такая математика подтягивается… Хотя есть ходилки, на нейронных осциляторах: www.youtube.com/watch?v=YIN4wSY-lw0, но там тоже непросто, как я понимаю.
disRecord
0
Using efference copy and a forward internal model for adaptive biped walking

Все же здесь, наверное, не нейроосциляторы, но они тоже есть.
disRecord
0
И так все хорошо получилось, компактно и аккуратно. Ноги в роботах, конечно, являются давней мечтой, но по мне особо так и не нужны для большинства приложений. А, если бы природа могла создать колеса, то мы бы на них, возможно, бы и рассекали.
disRecord
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.
Скрыто Показать