Данная статья интересна именно с точки зрения программирования. Есть интересуют вопросы по сборке, следует обратиться к предыдущей статье.
Обращаю внимание, что изменилась разводка соединительных кабелей по сравнению с исходной статьёй (например, освобождены аппаратные RX/TX). Мне не хочется рисовать схему соединений, надеюсь, что и так всё понятно из шапки программы. На всякий случай прикладываю фотографию, на которой хорошо виден Sensor Shield и подходящие к нему провода.
Алгоритм работы в общих чертах остался прежним: машина едет вперёд до тех пор, пока не "увидит" преграду, затем откатывается назад и поворачивается вокруг своей оси, а потом продолжает движение.
Однако, теперь добавлены нововведения, которые делают поведение машины забавным и витиеватым.
1) Добавлен режим "Паника", в который робот переходит, если несколько раз подряд наталкивается на препятствия. Паника заключается в долгом вращении вокруг своей оси и включении сирены, мигает двухцветный диод и машина издаёт тревожные звуки виу-виу. Для описания условий перехода в данный режим пришлось ввести уникальную систему штрафов, которые назначаются за встречу с препятствием.
2) Добавлен выбор лучшего направления движения после отката от препятствия (поворот на дискретный угол и проверка расстояния - продолжается до тех пор, пока не будет выбрано удачное направление). Это нововведение позволило избавиться от бестолкового поведения, когда робот откатывался от препятствия, но поворачивался неудачно, поэтому снова тыкался в него же.
На картинке видно, что робот загнан в угол, пытается выбрать лучшее направление направление движения. Но он перебирает их случайным образом, не запоминая неудачный выбор, поэтому через некоторое время у него не получается выбраться, начинается бесконечное метание в углу. Примерно через 3-4 отскока от препятствия подряд робот переходит в режим паники. Включается вся иллюминация и начинается дикое вращение.
Заключительная стадия режима "Паника" (на GIF не показана) - это методичный последовательный перебор всех возможных вариантов движения, в итоге выбирается направление движения, в котором расстояние до преграды максимально. Это позволяет реально выпутываться из самых сложных конфигураций стен, включая шторы и ножки нескольких табуреток.
Текст программы приведён ниже. Прошил - и готово!
// 4WD RoboCar // Sonar (NO Servo) + HeadLight + Syrens + LED13 (as backlight) + photosensor // 2017-December-31 // v.0.5с (хорошая версия) // Global variables: byte Critical = 13; // Критическое расстояние до препятствия в [см] byte randomNumber; // Случайное число byte Cost = 60; // Штраф (назначается за неспособность ехать вперёд) byte Profit = 180; // "Прибыль", которая плавно растёт при движении вперёд byte velocity = 220; // Скорость моторов [1..255] // Setup the servo motor // #include // Servo myservo; // int servposnum = 0; // int servpos = 0; // Описываем подключение драйвера двигателей // A - правый борт // В - левый борт int enableB = 3; //~ Остался! int pinB2 = 4; // int pinB1 = 5; // int enableA = 6; //~ Остался! int pinA1 = 7; //~ int pinA2 = 8; // #define illumination A0 // подключаем составной светодиод // Подключаем ультразвуковой датчик #define trigPin 9 #define echoPin 10 #define light 11 // На этот вывод подключены фары int Buzzer = 12;// Подключаем зуммер // Фоторезистор подключен к АЦП #define PHOTO_SENSOR A5 #define Sweep 8000 // скорость нарастания и убывания частоты #define Woo_wait_sec 2 // сколько с. длится гудение на макс. частоте 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); pinMode(light , OUTPUT); pinMode(illumination , OUTPUT); enableMotors(); SayBeep(); 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 goForward (int duration) { motorAforward(); motorBforward(); delay (duration); } void goBackward (int duration) { motorAbackward(); motorBbackward(); delay (duration); } void rotateRight (int duration) { motorAbackward(); motorBforward(); delay (duration); } void rotateLeft (int duration) { motorAforward(); motorBbackward(); delay (duration); } void FullStop (int duration) { motorAstop(); motorBstop(); delay (duration); } void disableMotors() { motorAoff(); motorBoff(); } void enableMotors() { motorAon(); motorBon(); } void CheckLight () { int val = analogRead(PHOTO_SENSOR); /* if (val > 999) { digitalWrite(13, HIGH); // слишком светло panic(); // это повод digitalWrite(13, LOW); // паниковать } */ if (val < 500) { // Темновато, включаем фары digitalWrite(light, HIGH); } else { // Светло, выключаем фары digitalWrite(light, LOW); } } // Пользуемся УЗ датчиком расстояния 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(); // Serial.print(distance_0); // Serial.println(" сантиметров. "); // Движемся вперёд, пока расстояние до преграды > критического [cm] while (distance_0 > Critical) { CheckLight (); // проверяем, не пора ли зажигать фары? analogWrite (enableA, velocity); analogWrite (enableB, velocity); goForward(30); // Едем вперёд некоторое время randomNumber = random(1,100); // передёрнули затвор генератора ПСЧ if (Profit < 254 ) { Profit++; }; // Serial.print(Profit); distance_0 = distance(); } FullStop(100); // Останов, т.к. впереди помеха } void Woo(int freq, long duration){ // первый параметр частота, чем ниже он тем выше частота, второй длительность long time = duration/2/freq; for(long t = 0; t < time; t++) { digitalWrite(Buzzer, HIGH); delayMicroseconds(freq); digitalWrite(Buzzer, LOW); delayMicroseconds(freq); } } void Syren() { for(int i = 0; i <= 1; i++){ // делаем виу-виу 2 раза for(int f = 2000; f >= 100; f=f-40){ // нарастание частоты Woo(f, Sweep); } // Woo(400, Woo_wait_sec*100); // сколько длится гудение на максимальной частоте for(int f = 100; f <= 2000; f=f+40){ // убывание частоты Woo(f, Sweep); } } } void panic() { int distance_tmp; int distance_new = 32000; int angle = 600; // угол поворота, измеряем в [мс] FullStop(100); // Сначала останавливаемся Profit = 255; // Забываем про старые штрафы! // Serial.print(Profit); digitalWrite(illumination, HIGH); // включаем мигалку Syren (); Syren (); Syren (); delay(1000); // пауза, для отдыха // Выполняем манёвры на высокой скорости (ведь паника!) analogWrite (enableA, velocity); analogWrite (enableB, velocity); // 0) Начинаем крутиться волчком в какую-то сторону: randomNumber = random(1,100); if (randomNumber < 50) { motorAbackward(); motorBforward(); } else { motorBbackward(); motorAforward(); }; // Истерично продолжаем крутиться, пока гудит сирена: Syren (); delay (1000); // Ждём FullStop(1000); delay (100); // Ждём // 1) Ищем хоть какое-то направление, куда вообще можно ехать distance_tmp = distance(); do { rotateRight(angle); // Крутимся FullStop(1000); // Ждём и смотрим вдаль distance_tmp = distance(); } while(distance_tmp < Critical); // повторяем поворот, если расстояние всё ещё мало SayBeep(); // rotateLeft(angle); // Возвращаемся на один шаг // 2) Пытаемся выбрать лучшее направление! /* rotateRight(angle); // Крутимся и проверяем дистанцию distance_new = distance(); if (distance_new < distance_tmp) // Не угадали, раньше было лучше, {rotateLeft(angle); } // поэтому поворачиваемся обратно */ /* distance_new = 32000; while( distance_new > distance_tmp ) { distance_tmp = distance(); rotateRight(angle); // Крутимся и проверяем дистанцию FullStop(900); // Ждём и смотрим вдаль distance_new = distance(); delay (800); // Ждём и смотрим вдаль } */ do { distance_tmp = distance(); rotateRight(angle); // Крутимся и проверяем дистанцию FullStop(900); // Ждём и смотрим вдаль distance_new = distance(); delay (800); // Ждём и смотрим вдаль } while( distance_new > distance_tmp ); // Не угадали, раньше было лучше, rotateLeft(angle); // поэтому поворачиваемся обратно FullStop(1000); // Восстанавливаем дыхание, успокаиваемся digitalWrite(illumination, LOW); // успокоились, выключаем мигалку delay (410); // Ждём SayBeep(); } void rollBack() { digitalWrite(13, HIGH); // Включаем встроенный диод! // Сначала откатываемся назад на случайное количество шагов randomNumber = random(700,2100); //analogWrite (enableA, 150); //analogWrite (enableB, 189); goBackward(randomNumber); FullStop(200); // Восстанавливаем дыхание, успокаиваемся // Случайным образом выбираем направление поворота: randomNumber = random(1,100); do { // поворачиваемся в выбранную сторону на случайный угол if (randomNumber < 50) { rotateRight(random(450,1300)); } else { rotateLeft(random(350,1400)); }; FullStop(1000); // Выключаем моторы tone(Buzzer, 600, 333); delay (700); // передышка! } while( distance() < Critical ); // повторяем поворот, если расстояние всё ещё мало analogWrite (enableA, velocity); analogWrite (enableB, velocity); FullStop(600); // Восстанавливаем дыхание, успокаиваемся digitalWrite(13, LOW); // Выключаем встроенный диод // SayBeep(); delay (200); // передышка! } void avoid() { CheckLight (); // проверяем, не пора ли зажигать фары? tone(Buzzer, 2100, 110); delay (700); // передышка! // Штрафуем сами себя: if (Profit > Cost) { Profit = Profit - Cost; // Ещё есть возможность оплатить штраф, rollBack(); // тогда откат назад с разворотом // Serial.print(Profit); } else { // Нельзя штрафовать, всё плохо, значит запутались - паникуем panic(); } } void SayBeep(){ tone(Buzzer, 700, 109); delay(200); tone(Buzzer, 1200, 109); delay(200); tone(Buzzer, 2600, 240); delay(350); noTone(Buzzer); } void loop() { randomNumber = random(2,5); // вхолостую выбираем псевдо-случайное число launch(); // запускаем автомобиль вперёд до встречи с преградой avoid(); // откатываемся от препятствия и как-то поворачиваемся CheckLight (); // проверяем, не пора ли зажигать фары? }
Как видно, программа стала значительно длиннее. Надеюсь, она не потеряла изящности.
В режиме отладки можно отправлять данные в монитор последовательного порта, чтобы оценить динамику набора очков и получения штрафов.
Фактически, наш аппарат перемещается вперёд дискретно, шагами. Одно очко выдаётся за один шаг вперёд. Если впереди препятствие, назначается штраф. Встреча с препятствием показана красной стрелкой на рисунке выше. Затем автомобиль находит направление для дальнейшего движения, теперь очки линейно начисляются за непрерывное движение вперёд.
Для различных помещений и задач можно варьировать как константу штрафа (Cost), так и время выполнения одного "шага". Это позволит переходить реже или чаще в режим паники.
Отзывы
Написал Денис
Опубликовано в: Настройка модуля HC-06Написал deman696
Опубликовано в: Настройка модуля HC-06Написал Борис
Опубликовано в: Сравнение современных СУБДНаписал Den
Опубликовано в: Редактирование сейвов Mass Effect 1Написал Артём
Опубликовано в: Запрет обновлений Google Chrome