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

Для тех, кто не в танке или мое знакомство с Ардуино (про Ардуино вообще и про робота в частности). Часть 1.+116
17 янв. 2016 г., 12:58:20 | Александр Шаргаев Химки
Статья http://www.parkflyer.ru/blogs/view_entry/13158/


Теперь пришла пора рассказать о нашей дальнейшей работе. Начну с неприятностей: как-то раз при попытке залить очередной скетч на экране монитора выскочило непонятное сообщение и приехали... Плата перестала работать. Подозрение пало на микроконтроллер ATMEGA348, который является «сердцем» платы. Покупка нового микроконтроллера в данном случае ничего не даст – «пустая» микросхема не будет работать в Ардуино, ей нужна специальная прошивка. Прошивать можно с помощью программатора (которого у нас, естественно, нет) или с помощью другой платы Ардуино с «правильным» микроконтроллером. Второй способ нам показался удобнее - в хозяйстве будет запасная плата, которую вдобавок можно будет использовать для всяких разных других экспериментов и отладки «на столе» новых кусков программы. Поэтому мы заказали новую плату, хотя, конечно, ее ожидание сильно затормозило нашу работу.

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

Но вот, наконец, пришла новая плата и наш подопечный снова ожил. Диагностика посредством замены микросхем местами на двух платах подтвердила наши подозрения - накрылся микроконтроллер. Быстренько купили новый, прошили его и у нас теперь две рабочие платы, что не может не радовать. Также на мотор-шилде на всякий случай перерезали дорожку идущую к ноге, которая входит в гнездо «5В» на плате Ардуино. На этой ноге при включении шилда появляется напряжение 5В, которые «встречаются» с 5В на плате Ардуино, что, думаю, не есть хорошо.

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

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

Что дальше? Сыну очень нравятся разные лампочки и прочие светодиоды, поэтому на следующем этапе роботу предстояло обзавестись «элементами наружного освещения». Спереди установили два ярких белых светодиода, а сзади два обычных красных, а включать их решили с помощью фотодатчика, в качестве которого использовали завалявшийся в закромах фотодиод. Чтобы микроконтроллер по аналоговому входу мог снимать уровень освещенности с датчика, необходимо добавить резистор и тогда получится так называемый делитель напряжения. Напаивать резистор на фотодиод нам показалось непрактичным, но это полбеды. Светодиодов четыре и подключать их к пятивольтовым выходам микроконтроллера крайне неудобно, для подобных вещей однозначно напрашивается питание 12В и транзистор. Как его там ставить? «Вешать» на провода? А ведь ему, как минимум, нужен еще и резистор в цепь базы! В итоге мы пришли к кардинальному решению: сняли плату расширения, которая была установлена сверху «бутерброда» над мотор-шилдом и на макетной плате подходящего размера решили соорудить свой шилд – как мы ее назвали, плату подключений (можно было, конечно, придумать какое-нибудь модное название, типа «интерфейсно-согласующий модуль», но нам так почему-то не понравилось).

Изготовление такой платы выгодно по нескольким причинам: во-первых, на ней мы собрали блок питания на микросхеме КР142ЕН5А. Эта микросхема выдает стабилизированное напряжение 5В для всех подключаемых узлов робота, что разгружает цепи питания самой платы Ардуино и практически сводит к нулю вероятность их выхода из строя. И, что очень важно в подобного рода проектах, данная микросхема не боится перегрузок и замыканий.

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

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

Итак, сделали плату под наши текущие задачи, установили на робота фотодиод, использовав корпус от платы реле китайской автомобильной камеры заднего хода:




В Ардуино есть удобная штука для отладки – монитор порта, который позволяет вывести на экран значение любой переменной и наблюдать, что с ней происходит в процессе работы программы. Было очень интересно наблюдать, как меняется значение на аналоговом входе при изменении освещения фотодиода. В итоге, «наружное освещение» включается атоматически. Наш красавец:


Что дальше? Теперь при заливке скетчей мы опасались отключать мотор-шилд и робота, чтобы он не сбежал во время программирования, приходилось либо держать, либо что-то под него подкладывать. Быстренько установили на нашу плату подключений (еще раз убедились, что с этой платой решение было очень правильным) перемычку, подсоединили ее на один из входов Ардуино, внесли изменения в скетч и теперь красота! Поставил перемычку в одно положение – можешь с удобством программировать робота, отлаживать какие-то места в программе, он спокойно стоит. Переставил перемычку, нажал Reset – поехали!


Чтобы робот был «поумнее», добавили ему процедуры старта и слежения за дистанцией по бокам во время движения. Теперь при включении робот «просыпается», мотая «башкой» и моргая светодиодами (тут мы еще поработаем), а во время движения «осматривается» по сторонам, постоянно контролируя дистанцию. Вот небольшое видео:



Еще были мысли озвучить нашего подопечного, для чего заказали вот эту штуку:
WTV020-SD-16P U-Disk Audio Player SD Card MP3 Voice Module
Товар http://www.parkflyer.ru/ru/product/1685751/

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

Что дальше? Дальше будем устранять серьезный недостаток УЗ датчика дистанции – он совершенно не «видит» мягкие предметы. Также его сигнал отражается и уходит в сторону, когда робот подъезжает к препятствию под углом. Тут вся наша надежда на инфракрасные светодиоды и фотоприемники TSOP, во всяком случае, первые эксперименты показали очень обнадеживающие результаты. Должно получиться хорошо: на большой дистанции работает УЗ датчик, а вблизи ИК. Но об этом в следующий раз.

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

Ну вот, пока что все. Продолжение следует…

Всем удачи!
Скетч варианта, который бегает на видео:

/* Программа управления колесным роботом. Автор Шаргаев Дмитрий, г.Химки */
#include <Servo.h> // подключаем библиотеку для работы с сервоприводами
#define servoPin 3 // номер выхода сервопривода
#define servoMinImp 800 // Длина импульса при котором сервопривод должен принять положение 0°
#define servoSeredImp 1250 // Длина импульса при котором сервопривод должен принять положение 90°
#define servoMaxImp 1700 // Длина импульса при котором сервопривод должен принять положение 180°
/*Цифровые входы/выходы*/
byte outlightPin = 0; // задание номера вывода включения наружного освещения
byte trigPin = 1; // задание номера вывода trig УЗ датчика
byte echoPin = 2; // задание номера вывода echo УЗ датчика
byte motlnPin = 4; // задание номера вывода направления левого мотора
byte motlsPin = 5; // задание номера вывода скорости левого мотора
byte motrsPin = 6; // задание номера вывода скорости правого мотора
byte motrnPin = 7; // задание номера вывода направления правого мотора

/*Аналговые входы*/
byte stopPin = 0; // номер входа перемычки остановки моторов
byte fotoPin = 5; // номер входа фотодатчика

/*Объявление переменных*/

long dist,distN,distL,distR; // дистанции, измеренные УЗ датчиком
int fotodat = 0; // значение с фотодатчика
int mstop; // значение с входа перемычки остановки моторов
int ugol; // угол поворота


/*Задание параметров*/
int lightlevel = 1010; // уровень освещенности, при котором происходит включение наружного света (диапазон 0 - 1023)
int mindist = 3500; // задание минимальной дистанции УЗ датчика, при которой происходит разворот
int mindistD = 1500; // задание минимальной дистанции УЗ датчика при повороте головы
byte v1 = 100; // 1 скорость (макс.возм.значение 255)100/150
byte v2 = 200; // 2 скорость - разворот 200/250
byte z = 0; // счётчик циклов программы
byte kolz = 10; // количество циклов между поворотами головы при движении
Servo myServo;

void setup()
{
myServo.attach(servoPin, servoMinImp, servoMaxImp);
pinMode(outlightPin, OUTPUT); // задание режима работы вывода включения наружного освещения
pinMode(trigPin, OUTPUT); // задание режима работы вывода trig УЗ датчика
pinMode(echoPin, INPUT); // задание режима работы вывода echo УЗ датчика
pinMode(motlnPin, OUTPUT); // задание режима работы вывода направления левого мотора
pinMode(motlsPin, OUTPUT); // задание режима работы вывода скорости левого мотора
pinMode(motrsPin, OUTPUT); // задание режима работы вывода скорости правого мотора
pinMode(motrnPin, OUTPUT); // задание режима работы вывода направления правого мотора
}

//---------------------------------------------- ПОДПРОГРАММЫ -------------------------------------
//-------------------------------------------------------------------------------------------------
byte start() // процедура старта
{
delay (3000);
myServo.writeMicroseconds(servoSeredImp); // голова прямо
stp();
mstop = analogRead(stopPin); // проверка наличия перемычки отключения моторов
if (mstop>500)
{
v1 = 0; v2 = 0; // при наличии перемычки скорость движения равна 0
}
digitalWrite(outlightPin, HIGH); // моргание наружным светом
delay (100);
digitalWrite(outlightPin, LOW);
delay (100);
digitalWrite(outlightPin, HIGH);
delay (100);
digitalWrite(outlightPin, LOW);
delay (100);
digitalWrite(outlightPin, HIGH);
delay (100);
digitalWrite(outlightPin, LOW);
delay (100);
digitalWrite(outlightPin, HIGH);
delay (100);
digitalWrite(outlightPin, LOW);
delay (100); // ----------------------
myServo.writeMicroseconds(servoMinImp); // поворот головы налево
delay(200);
myServo.writeMicroseconds(servoMaxImp); // поворот головы направо
delay(200);
myServo.writeMicroseconds(servoMinImp); // поворот головы налево
delay(200);
myServo.writeMicroseconds(servoMaxImp); // поворот головы направо
delay(200);
myServo.writeMicroseconds(servoSeredImp); // голова прямо
delay(2000);
z=1; // увеличение счетчика циклов программы
return z;
}
//----------------------------------------------------------------------------------------
void go()// движение вперед
{
digitalWrite(motlnPin, HIGH); analogWrite(motlsPin, v1);
digitalWrite(motrnPin, HIGH); analogWrite(motrsPin, v1);
}

//----------------------------------------------------------------------------------------
void stp() // стоп
{
digitalWrite(motlnPin, HIGH); analogWrite(motlsPin, 0);
digitalWrite(motrnPin, HIGH); analogWrite(motrsPin, 0);
}

//----------------------------------------------------------------------------------------
void pov(int ugol) // поворот
{
if (ugol<0)
{
digitalWrite(motlnPin, LOW); analogWrite(motlsPin, v2);
digitalWrite(motrnPin, HIGH); analogWrite(motrsPin, v2);
ugol=ugol*-1;
delay(ugol*5);
}
else
{
digitalWrite(motlnPin, HIGH); analogWrite(motlsPin, v2);
digitalWrite(motrnPin, LOW); analogWrite(motrsPin, v2);
delay(ugol*5);
}
}
//----------------------------------------------------------------------------------------
int izmdist() // измерение дистанции УЗ датчиком
{
digitalWrite(trigPin, LOW); delay(10); // пауза перед измерением расстояния
digitalWrite(trigPin, HIGH); delay(10); digitalWrite(trigPin, LOW); // импульс 10мс на вывод trig УЗ датчика для измерения расстояния
dist = pulseIn(echoPin, HIGH); // считывание расстояния с УЗ датчика
delay(50);
return dist;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------

void loop()
{
if (z==0) // вызов подпрограммы старта
{
start();
}

//----------- Включение наружного света
fotodat = analogRead(fotoPin); // считываем значение с фотодатчика
if (fotodat>lightlevel) // проверяем
{
digitalWrite(outlightPin, HIGH); // свет включается
}
else
{
digitalWrite(outlightPin, LOW); // свет выключается
}

//----------- Измерение дистанции
myServo.writeMicroseconds(servoSeredImp);
izmdist();
if (dist>mindist) // если измеренная дистанция болььше минимальной дистанции едем
{
go();
goto m10;
}

stp();
digitalWrite(outlightPin, HIGH);
delay (100);
digitalWrite(outlightPin, LOW);
delay (100);
digitalWrite(outlightPin, HIGH);
delay (100);
digitalWrite(outlightPin, LOW);
delay (100);
digitalWrite(outlightPin, HIGH);
delay (100);
digitalWrite(outlightPin, LOW);
delay (100);
digitalWrite(outlightPin, HIGH);
delay (100);
digitalWrite(outlightPin, LOW);
delay (100);

distN=dist;
myServo.writeMicroseconds(servoMinImp); // поворот головы налево и измерение дистанции
delay(500);
izmdist();
distL=dist;
myServo.writeMicroseconds(servoMaxImp); // поворот головы направо и измерение дистанции
delay(500);
izmdist();
distR=dist;

if (distN>distL) //1 поиск пути после остановки
if (distN>distR) //2
if (distL>distR) //3
pov(-180);
else
{
pov(180);
}
else
{
pov(90);
}
else
if (distL>distR)
pov(-90);
else
{
pov(90);
}
z = 1;
m10:; //----------------------------

if (z==kolz) //-------------------Повороты головы в движении
{
myServo.writeMicroseconds(servoMinImp); // поворот головы налево
delay (250);
izmdist();
myServo.writeMicroseconds(servoSeredImp); // голова прямо
if (dist<mindistD)
{
pov(45);
}
}
if (z==kolz*2)
{
myServo.writeMicroseconds(servoMaxImp); // поворот головы направо
delay (250);
izmdist();
myServo.writeMicroseconds(servoSeredImp); // голова прямо
if (dist<mindistD)
{
pov(-45);
}
z = 1;
}
z++; //-----------------------------------------------

}