Pull to refresh

DDA для кошки

Reading time 6 min
Views 23K
Есть у нас в семье кошка по имени Киса. Молодая, а также трусливая и любопытная одновременно. Единственное, что начисто отбивает у нее всю трусость – это красное лазерное пятнышко от бошевского дальномера. Она готова охотиться за ним безоглядно. Но. У дальномера есть ограничение по времени работы, батареек на него не напасешься, да и влом на длительные игры с кошкой время терять.

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



Или мы не инженеры-электронщики-ардуинщики?! А самому собрать?

Сначала взял железо: 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(minh,(minv+1),random(1,4)*10); break;
case 2:
servo_move(minh,(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(minh,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(minh,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(minh,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(minh,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);

}
}



Практика.

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

Не все адаптеры питания подходят. От некоторых Ардуино не заводится или лазер дергается. Видимо, большие пульсации на выходе. Конденсатор лень было впаивать, просто подобрал хороший адаптер, благо их куча валялась.
Испытано на нескольких кошках. Молодые долго носятся. Постарше – побегают, а потом лежат и смотрят, как пятно шарахается, или на саму машинку пялятся, как она жужжит и шевелится. Лапой машинку не пытались. Но, на всякий случай, придумал кожух из коробки для бумажек.



При опытах обои, занавески и кошки не пострадали.

В общем, рекомендую такую штуку всем кошатникам-электронщикам!
Tags:
Hubs:
+33
Comments 23
Comments Comments 23

Articles

Information

Website
masterkit.ru
Registered
Founded
Employees
Unknown
Location
Россия