DDA для кішки

Є у нас в родині кішка на ім'я Киця. Молода, а також боягузлива і цікава одночасно. Єдине, що начисто відбиває у неї всю боягузтво – це червоне лазерне плямочка від бошевского далекоміра. Вона готова полювати за ним безоглядно. Але. У далекоміра є обмеження по часу роботи батарей на нього не напасешся, так і влом на тривалі гри з кішкою час втрачати.

Знаходив на просторах інтернету всякі автоматичні котячі дражнилки – дорого, та й функціонал обмежений. Знову ж, немає гарантії, що промінчик не потрапить на фіранки, і вони не будуть при цьому подраны вщент.



Або ми не інженери-електронники-ардуинщики?! А самому зібрати?

Спочатку взяв залізо: Arduino Nano, пару сервомашинок простеньких (можна у наборі з Arduino у Майстер-Кіт придбати) і червоний напівпровідниковий лазер від завалялася указки з апертурою цяткою. ЛУТом в момент зробив хустці, щоб серви було куди встромити, ну і ключ на транзисторі для лазера, щоб ардуиновский пін не перевантажувати.

Принципова схема пристрою:



Плата:




Arduino Nano встромляється звичайний USB-mini з будь-якого блоку живлення на 5 Ст. Ну, або в комп'ютер для заливки скетчу.

Конструкцію хотілося зробити, звичайно, як можна простіше у виготовленні. Допоміг 3D-принтер. Ось воістину знахідка для домашніх умільців! Дуже сподобалося друкувати невеликі деталі замість того, щоб пиляти їх напилком. За годину MC5 D. R. O. V. A. надрукував чотири детальки для двухкоординатного поворотного пристрою. Сам процес друку настільки заворожує, що час пролетів цей взагалі непомітно!





Зібране поворотний пристрій разом з платою кріпимо на обрізку фарбованої фанери. Це господарство і будемо програмувати.





Ось тут найцікавіше. Треба зацікавити кошатину нестандартним рухом плямочки, щоб вона не втрачала інтерес, і убезпечити шпалери та штори від кігтів.

Експериментальним шляхом після деякої кількості експериментів були обрані наступні принципи руху об'єкта полювання:

— ворушіння лазером відбувається не постійно, а з випадковими зупинками з вимиканням плямочки, при цьому тварина нервово озирається в його пошуках;
— траєкторія руху змінюється від сеансу до сеансу знову ж випадковим чином;
— розмах руху теж змінюється в межах заданої траєкторії;
— після переміщення в кінець траєкторії точка завмирає, щоб звір міг її затоптати в спробі схопити;
— пляма не повинно потрапляти на стіни і фіранки, тільки на підлогу!

Побалувавшись з найпростішими рухами типу лінія і квадрат від точки до точки поперемінними рухами сервомашинок, захотілося реалізувати і більш складні траєкторії. Після недовгого гугления зупинився на старому доброму алгоритмі DDA-лінії, растеризующим відрізок прямої між двома точками. Тобто, задаємо функцію траєкторії, задаємо абсциссу, обчислюємо ординату, і переміщаємо лазер дрібними послідовними кроками по двох координатах на нову точку. Траєкторії зробив, які в голову прийшли: віяло, синусоїда, сектор, квадрат і т. п. Можна і більш складні функції малювати, якщо кому охота.

Під спойлером повний текст працюючого на даний момент скетчу:

Скетч#include <Servo.h> // бібліотека сервомашинок
#include <Metro.h> // бібліотека таймер
#define laser 7 // лазер включений на цей pin
#define led 13 // вбудований в Ардуїнов світлодіод

// експериментально вибираємо безпечну зону переміщення плями
int minv = 85; //10; // крайнє нижнє положення плями
int maxv = 115; //55; // крайнє верхнє
int minh = 90; //45; // крайнє ліве, дивитися спереду
int maxh = 145; //120; // крайнє праве, дивитися спереду

unsigned long DelayBetweenMovements = 1000; // затримка в мс
unsigned long second = 1000; // одна сек. = 1000 мм

Servo myservo_ver; // переміщення по вертикалі
Servo myservo_hor; // переміщення по горизонталі

void setup()
{
pinMode(laser, OUTPUT);
pinMode(led, OUTPUT);
digitalWrite (led,HIGH);
ServoOn();
myservo_hor.write((maxh + minh)/2); // установливаем лазер
myservo_ver.write((maxv + minv)/2); // у середнє положення
delay(2000); // і пару секунд дивимося

}

//-Main----------------------------------------------
void loop()
{
randomSeed(analogRead(0)); // ініціалізація random випадковим значенням з порту 0
int g = random(1,7); // вибираємо випадково одне траєкторію з семи
randomSeed(analogRead(0));
int tg = random(1,3); // вибираємо випадково тривалість сеансу 30, 60 або 90 сек
DelayBetweenMovements = second * random(1,5)/2; // вибираємо випадково час переміщення між точками траєкторії
switch (g){
case 1: GameRandom(tg*30*second); break;
case 2: GameFan(tg*30*second); break;
case 3: GameFan1(tg*30*second); break;
case 4: GameFan2(tg*30*second); break;
case 5: GameCorners(tg*30*second); break;
case 6: GameSinHor(tg*30*second); break;
case 7: GameSinVer(tg*30*second); break;
}
delay(random(10,60)*second);

}

//-End Main-----------------------------------------

// синусоїда вертикальна з випадковою амплітудою
void GameSinVer(unsigned long GameTime){
Metro game_time = Metro(GameTime);
ServoOn();
servo_move((maxh+minh)/2,minv,10);
while (!game_time.check()){
randomSeed(analogRead(0));
int i = minv;
int j = random(1,5);
for (i; i<maxv+1; i = i+j) {
servo_move((maxh+minh)/2+(maxh/(j+1))*sin(i),i,10);
delay(DelayBetweenMovements);
}
//digitalWrite (laser,random(2));
}
ServoOff();
}

// синусоїда горизонтальна з випадковою амплітудою
void GameSinHor(unsigned long GameTime){
Metro game_time = Metro(GameTime);
ServoOn();
servo_move((maxv+minv)/2,minh,10);
while (!game_time.check()){
randomSeed(analogRead(0));
int i = minh;
int j = random(1,5);
for (i; i<maxh+1; i = i+j) {
servo_move(i,(maxv+minv)/2+(maxv/(j+1))*sin(i),10);
delay(DelayBetweenMovements);
}
//digitalWrite (laser,random(2));
}
ServoOff();
}

// квадрат із випадковим розміром
void GameCorners(unsigned long GameTime){
Metro game_time = Metro(GameTime);
ServoOn();
servo_move((maxh+minh)/2,minv,10);
randomSeed(analogRead(0));
int c = random(1,4);
while (!game_time.check()){
int i = random(1,5);
switch (i){
case 1:
servo_move(в'єтмінь,(minv+1),random(1,4)*10); break;
case 2:
servo_move(в'єтмінь,(maxv+1),random(1,4)*10); break;
case 3:
servo_move(maxh,(maxv+1),random(1,4)*10); break;
case 4:
servo_move(maxh,(minv+1),random(1,4)*10); break;
}
delay(DelayBetweenMovements);
//digitalWrite (laser,random(2));
}
ServoOff();
}

// віялові руху з випадковим розмахом
void GameFan(unsigned long GameTime){
Metro game_time = Metro(GameTime);
ServoOn();
servo_move((maxh+minh)/2,minv,10);
while (!game_time.check()){
randomSeed(analogRead(0));
servo_move(random(в'єтмінь,maxh+1),maxv,random(1,4)*10);
delay(DelayBetweenMovements);
servo_move((maxh+minh)/2,minv,random(1,4)*10);
delay(DelayBetweenMovements);
//digitalWrite (laser,random(2));
}
ServoOff();
}

// віялові руху
void GameFan1(unsigned long GameTime){
Metro game_time = Metro(GameTime);
ServoOn();
servo_move((maxh+minh)/2,maxv,10);
while (!game_time.check()){
randomSeed(analogRead(0));
servo_move(random(в'єтмінь,maxh+1),minv,random(1,4)*10);
delay(DelayBetweenMovements);
servo_move((maxh+minh)/2,maxv,random(1,4)*10);
delay(DelayBetweenMovements);
//digitalWrite (laser,random(2));
}
ServoOff();
}

// віялові руху
void GameFan2(unsigned long GameTime){
Metro game_time = Metro(GameTime);
ServoOn();
servo_move((maxh+minh)/2,(maxv+minv)/2,10);
while (!game_time.check()){
randomSeed(analogRead(0));
servo_move(random(в'єтмінь,maxh+1),random(minv,maxv+1),random(1,4)*10);
delay(DelayBetweenMovements);
servo_move((maxh+minh)/2,(maxv+minv)/2,random(1,4)*10);
delay(DelayBetweenMovements);
//digitalWrite (laser,random(2));
}
ServoOff();
}

// випадкові переміщення у всіх напрямках
void GameRandom(unsigned long GameTime){
Metro game_time = Metro(GameTime);
ServoOn();
while (!game_time.check()){
randomSeed(analogRead(0));
servo_move(random(в'єтмінь,maxh+1),random(minv,maxv+1),random(1,4)*10);
delay(DelayBetweenMovements);
//digitalWrite (laser,random(2));
}
ServoOff();
}

void ServoOn(void){ // задіяти серви і включити лазер
myservo_ver.attach(9); // серво по вертикалі приєднати на цифровий pin 9
myservo_hor.attach(8); // серво по горизонталі приєднати на цифровий pin 8
digitalWrite (laser,1); // включаємо лазер
}

void ServoOff(void){ // відключити серви і лазер — відпочиваємо
myservo_ver.detach();
myservo_hor.detach();
digitalWrite (laser,0);
}

// перемістити з поточної точки х1, y1 в точку x2, y2 із затримками між кроками delay_ms
void servo_move(double x2, double y2, int delay_ms)
{
double x1 = myservo_hor.read(); // читаємо поточне положення серв
double y1 = myservo_ver.read();

int iX1 = round(x1); // округляємо координати
int iY1 = round(y1);
int iX2 = round(x2);
int iY2 = round(y2);

// Довжина і висота лінії
int deltaX = abs(iX1 — iX2);
int deltaY = abs(iY1 — iY2);

// Вважаємо мінімальна кількість ітерацій, необхідне
// для відтворення відрізка. Вибираючи максимум з довжини і висоти
// лінії, забезпечуємо зв'язність лінії
int length = max(deltaX, deltaY);
if (length == 0) return;

// Обчислюємо збільшення на кожному кроці по осях абсцис і ординат
double dX = (x2 — x1) / length;
double dY = (y2 — y1) / length;

// Початкові значення
double x = x1;
double y = y1;

// Основний цикл
length++;
while (length--)
{
x += dX;
y += dY;
myservo_hor.write(x);
myservo_ver.write(y);
delay(delay_ms);

}
}



Практика.

Перший лазер вийшов з ладу через тиждень – відломилися висновки, хоч і зроблені вони з багатожильного дроти. У другого отформовал висновки спіраллю. Допомогло. Можна ще клею капнути з клейового пістолета на місце, звідки висновки виходять з корпусу.
Не всі адаптери живлення підходять. Від деяких Ардуїнов не заводиться або лазер смикається. Мабуть, великі пульсації на виході. Конденсатор лінь було паяти, просто підібрав хороший адаптер, благо їх валялася купа.
Випробувано на декількох кішок. Молоді довго носяться. Старші – побігають, а потім лежать і дивляться, як пляма сахається, або на саму машинку витріщаються, як вона дзижчить і ворушиться. Лапою машинку не намагалися. Але, на всяк випадок, придумав кожух з коробки для папірців.



При дослідах шпалери, фіранки і кішки не постраждали.

Загалом, рекомендую таку штуку всім кошатникам-электронщикам!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.