Роботизированная машина. Сборка.

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

Важная особенность конструкции - это применение недорогого 8-битного микроконтроллера фирмы Atmel; его вычислительных мощностей вполне хватает для управления и принятия решений в режиме реального времени.

 

Данная статья устарела 1 января 2018 года. Есть смысл рассматривать её только как инструкцию по сборке. Вопросы программирования гораздо лучше освещены в новой статье.

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

  • Для всей системы должен быть только один источник питания. Желательно, аккумуляторы. Любимый формат - 18650.
  • Простая и надёжная силовая часть. Никаких гипер-сложных "силовых шилдов".
  • Поведение, целиком определяемое прошивкой
  • Отладка в результате натурных испытаний (смена режимов сопровождается звуковым сигналом).

Рассмотрим этапы решения.

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

Состав набора:

  • Две акриловые пластины корпуса
  • Четыре мотора
  • Четыре колеса
  • Футляр для аккумуляторов
  • Метизы

Далее пришлось открывать верхний ящик стола и на ощупь искать дополнительные комплектующие. Нам пригодятся:

  • Микроконтроллерный модуль
  • Плата расширения Sensor Shield v.5
  • Модуль драйвера моторов на базе L298N
  • Сонар HC-SR04 с крепёжной скобой
  • Соединительные провода

В качестве микроконтроллерного модуля был выбран один из клонов платформы Arduino на базе Atmel ATmega 328/P. После долгих размышлений и взглядов в сторону Raspberry Pi или "голого" PIC16 с аскетичным ассемблером решено было остановиться именно на этой платформе, т.к. она обеспечивает простоту сопряжений с другими блоками, имеет массу примеров кода, опубликованных в свободном доступе, унифицирована с колоссальным количеством датчиков - другими словами, развязывает руки разработчику и позволяет сосредоточиться на воплощении задумки.

Более того, неоспоримый дополнительный плюс - это масштабируемость.

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

Фактически, вся сборка начинается с модуля драйвера моторов на базе L298N и крутится вокруг него. На мой взгляд, именно этот модуль следовало бы назвать ядром всей системы. Фактически, это умощнённая версия выпускаемой ранее микросхемы/платы L293D. Совместимость сохранена.

L298N

Важно отметить, что входы ENABLE A/B отвечают за возможность вращения моторов, причём на них можно подавать как логические уровни ("низкий" - запрет, "высокий" - разрешено), так и аналоговые сигналы (например, посредством ШИМ) для регулировки скорости вращения. Разработчики предусмотрели два пятивольтовых вывода рядом с ENABLE A/B - они позволяют установить "намертво" перемычки на плату, тогда всегда будет разрешена полная скорость вращения; таким образом можно сэкономить две выходные линии микроконтроллера. Подавать отдельно +5В на эти вспомогательные выводы не требуется!

Ещё одна перемычка видна вблизи колодки питания, в глубине платы: она должна быть установлена, если питание модуля L298N будет превышать +12В. Можно запитывать модуль L298N и большим напряжением (вплоть до 30 вольт), тогда придётся снять данный джампер, а также придётся дополнительно подводить к модулю +5В. В моём случае планируется применение аккумуляторов на 7-8 вольт, поэтому я смело оставил джампер установленным и радостно воспользовался имеющимися пятью вольтами для питания логики.

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

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

Все тонкости пайки показаны на рисунке выше. Моторы запаиваем крест-накрест между собой, затем припаиваем выходные (красные) провода от драйвера.

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

И, да, не забываем, что редукторы у нас не железные, колёса придётся насаживать нежно! Есть смысл капнуть каплю клея на оси, так как практика показала, что колёса имеют тенденцию отваливаться на поворотах по ковру.

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

Сведём все коммуникации в один понятный рисунок.

 

Собираем второй этаж.

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

Суть алгоритма:

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

Листинг программы для прошивки.

// 4WD RoboCar
// With Sonar
// 2017-July-24

long randomNumber;

// Описываем подключение драйвера к микроконтроллеру
// A - правый борт
// В - левый борт

int pinB1 = 1;
int pinB2 = 2;
int enableB = 3;
int pinA2 = 4;
int pinA1 = 5;
int enableA = 6; // Setup Ultrasonic Sensor pins #define trigPin 8 #define echoPin 9 // Setup passive buzzer pins int tonePin = 12; void setup() { // Направление работы портов: pinMode (enableA, OUTPUT); pinMode (pinA1, OUTPUT); pinMode (pinA2, OUTPUT); pinMode (enableB, OUTPUT); pinMode (pinB1, OUTPUT); pinMode (pinB2, OUTPUT); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); pinMode(13 , OUTPUT); // Warming-up movement midi(); // play song enableMotors(); pisk(); delay(500); forward(1300); delay(500); pisk(); backward(1300); delay(500); pisk(); right(1300); delay(500); pisk(); left(1300); disableMotors(); delay(1000); pisk(); digitalWrite(13, LOW); // Выключаем встроенный диод } // Управление моторами void motorAforward() { digitalWrite (pinA1, HIGH); digitalWrite (pinA2, LOW); } void motorBforward() { digitalWrite (pinB1, LOW); digitalWrite (pinB2, HIGH); } void motorAbackward() { digitalWrite (pinA1, LOW); digitalWrite (pinA2, HIGH); } void motorBbackward() { digitalWrite (pinB1, HIGH); digitalWrite (pinB2, LOW); } void motorAstop() { digitalWrite (pinA1, HIGH); digitalWrite (pinA2, HIGH); } void motorBstop() { digitalWrite (pinB1, HIGH); digitalWrite (pinB2, HIGH); } void motorAcoast() { digitalWrite (pinA1, LOW); digitalWrite (pinA2, LOW); } void motorBcoast() { digitalWrite (pinB1, LOW); digitalWrite (pinB2, LOW); } void motorAon() { digitalWrite (enableA, HIGH); } void motorBon() { digitalWrite (enableB, HIGH); } void motorAoff() { digitalWrite (enableA, LOW); } void motorBoff() { digitalWrite (enableB, LOW); } // Управление движениями void forward (int duration) { motorAforward(); motorBforward(); delay (duration); } void backward (int duration) { motorAbackward(); motorBbackward(); delay (duration); } void right (int duration) { motorAbackward(); motorBforward(); delay (duration); } void left (int duration) { motorAforward(); motorBbackward(); delay (duration); } void coast (int duration) { motorAcoast(); motorBcoast(); delay (duration); } void breakRobot (int duration) { motorAstop(); motorBstop(); delay (duration); } void disableMotors() { motorAoff(); motorBoff(); } void enableMotors() { motorAon(); motorBon(); } // Setup Ultrasonic Sensor distance measuring int distance() { int duration, distance; digitalWrite(trigPin, HIGH); delayMicroseconds(1000); digitalWrite(trigPin, LOW); duration = pulseIn(echoPin, HIGH); distance = (duration/2) / 29.1; return distance; } // Setup the main car function void launch() { int distance_0; distance_0 = distance(); // Debugging: Serial.print(distance_0); Serial.println(" inch. "); // Keep moving forward in a straight line while distance of objects in front > 12 cm away while(distance_0 > 12) { motorAon(); motorBon(); forward(10); // Сколько ехать вперёд? distance_0 = distance(); } breakRobot(0); } void avoid() { // Go back and turn slightly right to move car in new direction if object detected tone(tonePin, 999, 400); delay (1000); randomNumber = random(1,2); backward(randomNumber); tone(tonePin, 900, 400); delay (400); randomNumber = random(1,10); if (randomNumber < 5) { right(random(50,460)); } else { left(random(50,460)); }; } void pisk(){ tone(tonePin, 700, 350); delay(350); tone(tonePin, 900, 350); delay(350); tone(tonePin, 1100, 750); delay(550); noTone(tonePin); } void midi() { tone(tonePin, 174, 249.99975); delay(277.7775); tone(tonePin, 233, 499.9995); delay(555.555); tone(tonePin, 174, 374.999625); delay(416.66625); tone(tonePin, 195, 124.999875); delay(138.88875); tone(tonePin, 220, 499.9995); delay(555.555); tone(tonePin, 146, 249.99975); delay(277.7775); tone(tonePin, 146, 249.99975); delay(277.7775); tone(tonePin, 195, 499.9995); delay(555.555); tone(tonePin, 174, 374.999625); delay(416.66625); tone(tonePin, 155, 124.999875); delay(138.88875); tone(tonePin, 174, 499.9995); delay(555.555); tone(tonePin, 116, 249.99975); delay(277.7775); tone(tonePin, 116, 249.99975); delay(277.7775); tone(tonePin, 130, 499.9995); delay(555.555); tone(tonePin, 130, 374.999625); delay(416.66625); tone(tonePin, 146, 124.999875); delay(138.88875); tone(tonePin, 155, 499.9995); delay(555.555); tone(tonePin, 155, 374.999625); delay(416.66625); tone(tonePin, 174, 124.999875); delay(138.88875); tone(tonePin, 195, 499.9995); delay(555.555); tone(tonePin, 220, 374.999625); delay(416.66625); tone(tonePin, 233, 124.999875); delay(138.88875); tone(tonePin, 261, 749.99925); delay(833.3325); tone(tonePin, 174, 249.99975); delay(277.7775); } void loop() { randomNumber = random(2,5); // increase randomization :) launch(); // function keeps moving car forward while... avoid(); // function makes car go back, turn slightly right to move forward in new direction }

Фрагмент реального заезда на видео прекрасно иллюстрирует поведение подопытного.

В результате имеем работающую машинку, которая способна самостоятельно избегать соударения!

Хотелось бы отметить, что очень тщательно выбиралась пара переменных: критическое расстояние Distance и время движения вперёд, т.е. параметр для Forward(). Дело в том, что от пары этих переменных будет очень сильно зависеть поведение робота вблизи препятствий. Я постарался сделать максимально точный аппарат, который будет тормозить в самый последний миг, останавливаясь в миллиметрах от преграды. С другой стороны, ударов тоже допускать нельзя (домашнюю мебель жалко).

Для программирования и прошивки использовалась интегрированная среда разработки UECIDE. Крайне рекомендую данную ИСР, т.к. во-первых, она бесплатна. Во-вторых, она обладает развитым функционалом текстового редактора кода, после которого тяжело перейти на любой другой пакет. Иллюстрирую феноменальную разметку с помощью одного скриншота:

uecide

Не прошло и полгода, а уже захотелось доработать поделку.

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

// 4WD RoboCar
// Sonar (NO Servo) + fara + LED13
// 2017-December-10
// v.0.4 (хорошая версия)

// Global variables:
long randomNumber;
int velocity = 220;

int light = 0; // Сюда подключены фары

// Описываем подключение драйвера к микроконтроллеру
// A - правый борт
// В - левый борт

int pinB1   = 1;
int pinB2   = 2;
int enableB = 3;
int pinA2   = 4;
int pinA1   = 5;
int enableA = 6;

// Подключаем ультразвуковой датчик
#define trigPin 8
#define echoPin 9

// Фоторезистор висит на А0
#define PHOTO_SENSOR A0 

// Подключаем зуммер
int tonePin = 12;

void setup() {
  
  // Определяем направление работы линий
   pinMode (enableA, OUTPUT);
   pinMode (pinA1, OUTPUT);
   pinMode (pinA2, OUTPUT);
   pinMode (enableB, OUTPUT);
   pinMode (pinB1, OUTPUT);
   pinMode (pinB2, OUTPUT); 
   pinMode(trigPin, OUTPUT);
   pinMode(echoPin, INPUT);
   pinMode(13 , OUTPUT);

// Warming-up
midi();
enableMotors();
pisk();
delay(2000);
digitalWrite(13,  LOW); // Выключаем встроенный диод
// Serial.begin(9600);     // Отправляем данные на ПЭВМ
}

// Описываем варианты работы моторов
void motorAforward() {
 digitalWrite (pinA1, HIGH);
 digitalWrite (pinA2, LOW);
}
void motorBforward() {
 digitalWrite (pinB1, LOW);
 digitalWrite (pinB2, HIGH);
}
void motorAbackward() {
 digitalWrite (pinA1, LOW);
 digitalWrite (pinA2, HIGH);
}
void motorBbackward() {
 digitalWrite (pinB1, HIGH);
 digitalWrite (pinB2, LOW);
}
void motorAstop() {
 digitalWrite (pinA1, HIGH);
 digitalWrite (pinA2, HIGH);
}
void motorBstop() {
 digitalWrite (pinB1, HIGH);
 digitalWrite (pinB2, HIGH);
}
void motorAon() {
 digitalWrite (enableA, HIGH);
}
void motorBon() {
 digitalWrite (enableB, HIGH);
}
void motorAoff() {
 digitalWrite (enableA, LOW);
}
void motorBoff() {
 digitalWrite (enableB, LOW);
}

// Описываем варианты движения машины
void forward (int duration) {
 motorAforward();
 motorBforward();
 delay (duration);
}
void backward (int duration) {
 motorAbackward();
 motorBbackward();
 delay (duration);
}
void right (int duration) {
 motorAbackward();
 motorBforward();
 delay (duration);
}
void left (int duration) {
 motorAforward();
 motorBbackward();
 delay (duration);
}
void FullStop (int duration) {
 motorAstop();
 motorBstop();
 delay (duration);
}
void disableMotors() {
 motorAoff();
 motorBoff();
}
void enableMotors() {
 motorAon();
 motorBon();
}

// Пользуемся УЗ датчиком расстояния
int distance() {
  int duration, distance;
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(1000);
  digitalWrite(trigPin, LOW);
  duration = pulseIn(echoPin, HIGH);
  distance = (duration/2) / 29.1; // Переводим в сантиметры
  return distance;
}

// Функция запуска автомобиля
void launch() {
int distance_0;
distance_0 = distance();
// Debugging:
//  Serial.print(distance_0);
//  Serial.println(" сантиметров. ");

// Движемся вперёд, пока расстояние до преграды > 12 cm
while(distance_0 > 12)
  {
     // motorAon();
     // motorBon();
     analogWrite (enableA, velocity);
     analogWrite (enableB, velocity);
     forward(10); // Сколько ехать вперёд?
     distance_0 = distance();
  }
FullStop(0);
}

void avoid()
{
    digitalWrite(13,  HIGH); // Включаем встроенный диод!
    // Сначала откатываемся назад на случайное количество шагов
    tone(tonePin, 999, 400);
    delay (1000);
    randomNumber = random(2,3);
    backward(randomNumber);
    tone(tonePin, 900, 400);
    delay (400);
    // Случайным образом выбираем направление и угол поворота:
    randomNumber = random(1,10);
    if (randomNumber < 5) { right(random(50,460)); } else { left(random(50,460)); };
    digitalWrite(13,  LOW); // Выключаем встроенный диод
}

void pisk(){
    tone(tonePin, 700, 350);
    delay(350);
    tone(tonePin, 900, 350);
    delay(350);
    tone(tonePin, 1100, 750);
    delay(550);
    noTone(tonePin);
}

void midi() {

tone(tonePin, 174, 249.99975);
    delay(277.7775);
    tone(tonePin, 233, 499.9995);
    delay(555.555);
    tone(tonePin, 174, 374.999625);
    delay(416.66625);
    tone(tonePin, 195, 124.999875);
    delay(138.88875);
    tone(tonePin, 220, 499.9995);
    delay(555.555);
    tone(tonePin, 146, 249.99975);
    delay(277.7775);
    tone(tonePin, 146, 249.99975);
    delay(277.7775);
    tone(tonePin, 195, 499.9995);
    delay(555.555);
    tone(tonePin, 174, 374.999625);
    delay(416.66625);
    tone(tonePin, 155, 124.999875);
    delay(138.88875);
    tone(tonePin, 174, 499.9995);
    delay(555.555);
    tone(tonePin, 116, 249.99975);
    delay(277.7775);
    tone(tonePin, 116, 249.99975);
    delay(277.7775);
    tone(tonePin, 130, 499.9995);
    delay(555.555);
    tone(tonePin, 130, 374.999625);
    delay(416.66625);
    tone(tonePin, 146, 124.999875);
    delay(138.88875);
    tone(tonePin, 155, 499.9995);
    delay(555.555);
    tone(tonePin, 155, 374.999625);
    delay(416.66625);
    tone(tonePin, 174, 124.999875);
    delay(138.88875);
    tone(tonePin, 195, 499.9995);
    delay(555.555);
    tone(tonePin, 220, 374.999625);
    delay(416.66625);
    tone(tonePin, 233, 124.999875);
    delay(138.88875);
    tone(tonePin, 261, 749.99925);
    delay(833.3325);
}

void fara () {
  int val = analogRead(PHOTO_SENSOR);
  if (val < 500) {
    // Светло, выключаем фары
    digitalWrite(light, HIGH);
  } else {
    // Темновато, включаем фары
    digitalWrite(light,  LOW);
  }
}

void loop() {
     randomNumber = random(2,5); // вхолостую выбираем псевдо-случайное число
     launch(); // запускаем автомобиль вперёд
     avoid(); // откатываемся от препятствия и как-то поворачиваемся
     fara ();  // проверяем, не пора ли зажигать фары?
}

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

Car Moving

10 комментарии

  • A.R.

    написал A.R.

    Вторник, 25 Июль 2017 16:26

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

  • Владимир Драч

    написал Владимир Драч

    Среда, 26 Июль 2017 08:00

    Спасибо, рад слышать такие замечательные отзывы!
    Твоё мнение особенно ценно для меня.

  • A.R.

    написал A.R.

    Среда, 26 Июль 2017 23:20

    А на этот прибор какой-нибудь ZigBee или BlueTooth нельзя водрузить?
    Тогда останется не так сложно соорудить машинке дистанционное управление с телефона или компа.

  • Владимир Драч

    написал Владимир Драч

    Пятница, 28 Июль 2017 14:59

    Коллега, вы на один шаг опережаете события. Модуль BlueTooth уже лежит
    на столе и ждёт своего часа.... а мы все ждём вторую статью про
    мутацию робо-машинки :-)

  • DAL

    написал DAL

    Воскресенье, 30 Июль 2017 02:40

    Отличная статья, крутой робот.

  • Владимир Драч

    написал Владимир Драч

    Воскресенье, 30 Июль 2017 08:59

    Благодарю, рад слышать!

  • Денис

    написал Денис

    Воскресенье, 30 Июль 2017 23:14

    Ну что я Вам скажу...Вы реализовали то, что я давно хотел, но сделать это на чистом Си и с нуля! :) Задумка классная, но я бы хотел видеть в статье про управление самим роботом, а точнее "управление роботом осуществляется посредством модуля беспроводной связи nrF2401 или блютуз HC-05/06, используя программу на Андроид..."
    Может я чего не понял, но на структурной схеме я видел шилд, датчик расстояния, моторы... Можно было бы побольше комментариев в программу добавить, хотя многие, если копипастят, то бездумно заливают скетч.
    И поэтому надо было бы добавить именно хотя бы пару слов про управление, чтоб не разбираться в программе, пытаясь найти функцию, которая за это отвечает)))
    А много времени ушло на то чтоб собрать и отладить?
    Если и я созрею, буду просить ваши консультации в области робототехники.

  • Владимир Драч

    написал Владимир Драч

    Понедельник, 31 Июль 2017 13:23

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

  • AfroditaSnado

    написал AfroditaSnado

    Вторник, 26 Декабрь 2017 00:39

    Классный робот, но жалко, что только один датчик.

  • Владимир Драч

    написал Владимир Драч

    Пятница, 05 Январь 2018 22:17

    Ну пока что не один, а два датчика! Ультразвуковой датчик расстояния и датчик освещённости.... Но то ли ещё будет :)

Оставить комментарий

Ваше мнение очень важно для нас! Обязательно выскажите Ваши мысли, пожелания и критику! Не стесняйтесь задавать вопросы. Скорее всего, ответ появится уже через 2-3 дня. Спасибо заранее.

Go to top