
DDA for cat
We have a cat in our family named Kisa. Young, as well as cowardly and curious at the same time. The only thing that completely discourages her cowardice is a red laser spot from the Boshevsky rangefinder. She is ready to hunt him recklessly. But. The rangefinder has a time limit, you won’t have enough batteries for it, and there’s a lot of time to lose for long games with a cat.
I found all sorts of automatic cat teasers on the Internet, expensive and the functionality is limited. Again, there is no guarantee that the ray will not fall onto the curtains, and they will not be torn to pieces.
Or are we not electronic engineers, arduino engineers ?! And to collect it yourself?
First I took the iron: Arduino Nano , a couple of simple servos (you can buy one with the Arduino in the Master Kit) and a red semiconductor laser from a littered pointer with a speck of aperture. LUTom at the moment made a handkerchief so that the servo was where to stick, well, and the key on the transistor for the laser, so that the Arduin pin is not overloaded.
Schematic diagram of the device:

Board:


In Arduino Nano, a regular USB-mini is stuck from any 5 V power supply. Well, or into a computer to fill in the sketch.
I wanted to make the design, of course, as simple as possible in manufacturing. 3D printer helped. Here is truly a help for home craftsmen! I really liked to print small details instead of sawing them with a file. In an hour, the MC5 DROVA printed four parts for a two-axis rotary device. The printing process itself is so mesmerizing that this hour flew by unnoticed!


We assemble the rotary device together with the circuit board on a piece of painted plywood. This is the economy and we will program.


Here is the fun part. It is necessary to interest the cat with the non-standard movement of the speck, so that it does not lose interest, and protect the wallpaper and curtains from the claws.
Experimentally, after a certain number of experiments, the following principles of the hunting object’s movement were chosen:
- the laser motion does not occur constantly, but with random stops with the speck turned off, while the animal looks around nervously in search of it;
- the trajectory of movement varies from session to session, again randomly;
- the range of motion also changes within a given trajectory;
- after moving to the end of the trajectory, the point freezes so that the beast can trample it in an attempt to grab it;
- the stain should not fall on the walls and curtains, only on the floor!
Having indulged in the simplest movements of the line and square type from point to point by alternating movements of the servos, I also wanted to realize more complex trajectories. After a brief googling, I settled on the good old DDA-line algorithm, rasterizing a line segment between two points. That is, we set the trajectory function, set the abscissa, calculate the ordinate, and move the laser in small successive steps along two coordinates to a new point. He made the paths that came to mind: a fan, a sinusoid, a sector, a square, etc. You can draw more complex functions, if someone is hunting.
Under the spoiler, the full text of the sketch currently working:
Practice.
The first laser failed after a week - the conclusions broke off, although they were made from a stranded wire. The second formed the findings in a spiral. It helped. You can still glue to drip from the glue gun to the place where the leads come out of the body.
Not all power adapters are suitable. Arduino does not start from some, or the laser twitches. Apparently, large ripple at the output. The capacitor was too lazy to solder, just picked up a good adapter, because a lot of them were lying around.
Tested on several cats. Young worn for a long time. Older - they run around, and then lie down and watch how the stain is dodging, or they are staring at the machine itself, as it buzzes and moves. Paw machine did not try. But, just in case, he came up with a casing from a paper box.

During the experiments, the wallpaper, curtains and cats were not affected.
In general, I recommend this kind of thing to all electronic catmates!
I found all sorts of automatic cat teasers on the Internet, expensive and the functionality is limited. Again, there is no guarantee that the ray will not fall onto the curtains, and they will not be torn to pieces.
Or are we not electronic engineers, arduino engineers ?! And to collect it yourself?
First I took the iron: Arduino Nano , a couple of simple servos (you can buy one with the Arduino in the Master Kit) and a red semiconductor laser from a littered pointer with a speck of aperture. LUTom at the moment made a handkerchief so that the servo was where to stick, well, and the key on the transistor for the laser, so that the Arduin pin is not overloaded.
Schematic diagram of the device:
Board:
In Arduino Nano, a regular USB-mini is stuck from any 5 V power supply. Well, or into a computer to fill in the sketch.
I wanted to make the design, of course, as simple as possible in manufacturing. 3D printer helped. Here is truly a help for home craftsmen! I really liked to print small details instead of sawing them with a file. In an hour, the MC5 DROVA printed four parts for a two-axis rotary device. The printing process itself is so mesmerizing that this hour flew by unnoticed!
We assemble the rotary device together with the circuit board on a piece of painted plywood. This is the economy and we will program.
Here is the fun part. It is necessary to interest the cat with the non-standard movement of the speck, so that it does not lose interest, and protect the wallpaper and curtains from the claws.
Experimentally, after a certain number of experiments, the following principles of the hunting object’s movement were chosen:
- the laser motion does not occur constantly, but with random stops with the speck turned off, while the animal looks around nervously in search of it;
- the trajectory of movement varies from session to session, again randomly;
- the range of motion also changes within a given trajectory;
- after moving to the end of the trajectory, the point freezes so that the beast can trample it in an attempt to grab it;
- the stain should not fall on the walls and curtains, only on the floor!
Having indulged in the simplest movements of the line and square type from point to point by alternating movements of the servos, I also wanted to realize more complex trajectories. After a brief googling, I settled on the good old DDA-line algorithm, rasterizing a line segment between two points. That is, we set the trajectory function, set the abscissa, calculate the ordinate, and move the laser in small successive steps along two coordinates to a new point. He made the paths that came to mind: a fan, a sinusoid, a sector, a square, etc. You can draw more complex functions, if someone is hunting.
Under the spoiler, the full text of the sketch currently working:
Sketch
#include // servo library
#include// timer library
#define laser 7 // the laser is switched on to this pin
#define led 13 // the LED built into Arduino
// experimentally select a safe spot movement zone
int minv = 85; //10; // extreme lower position of the spot
int maxv = 115; // 55; // extreme top
int minh = 90; // 45; // leftmost, front view
int maxh = 145; // 120; // extreme right, front view
unsigned long DelayBetweenMovements = 1000; // delay in ms
unsigned long second = 1000; // one second = 1000 ms
Servo myservo_ver; // move vertically
Servo myservo_hor; // horizontal movement
void setup ()
{
pinMode (laser, OUTPUT);
pinMode (led, OUTPUT);
digitalWrite (led, HIGH);
ServoOn ();
myservo_hor.write ((maxh + minh) / 2); // set the laser
myservo_ver.write ((maxv + minv) / 2); // in the middle position
delay (2000); // and watch for a couple of seconds
}
// - Main --------------------------------------- -------
void loop ()
{
randomSeed (analogRead (0)); // initialize random with a random value from port 0
int g = random (1,7); // randomly select one trajectory from seven
randomSeed (analogRead (0));
int tg = random (1,3); // randomly select a session duration of 30, 60 or 90 seconds
DelayBetweenMovements = second * random (1,5) / 2; // randomly select the travel time between the points of the path
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 -----------------------------------------
// sine wave vertical with random amplitude
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
servo_move ((maxh + minh) / 2 + (maxh / (j + 1)) * sin (i), i, 10);
delay (DelayBetweenMovements);
}
// digitalWrite (laser, random (2));
}
ServoOff ();
}
// horizontal sine wave with random amplitude
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
servo_move (i, (maxv + minv) / 2 + (maxv / (j + 1)) * sin (i), 10);
delay (DelayBetweenMovements);
}
// digitalWrite (laser, random (2));
}
ServoOff ();
}
// a square with a random size
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 ();
}
// fan movements with a random swing
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 ();
}
// fan movements
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 ();
}
// fan movements
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 ();
}
// random movements in all directions
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) {// enable
servos and turn on the laser myservo_ver.attach (9); // servo vertically attach to digital pin 9
myservo_hor.attach (8); // connect the servo horizontally to digital pin 8
digitalWrite (laser, 1); // turn on the laser
}
void ServoOff (void) {// turn off the servos and the laser -
relax myservo_ver.detach ();
myservo_hor.detach ();
digitalWrite (laser, 0);
}
// move from the current point x1, y1 to point x2, y2 with delays between steps delay_ms
void servo_move (double x2, double y2, int delay_ms)
{
double x1 = myservo_hor.read (); // read the current position of the serv
double y1 = myservo_ver.read ();
int iX1 = round (x1); // round the coordinates
int iY1 = round (y1);
int iX2 = round (x2);
int iY2 = round (y2);
// Length and height of the line
int deltaX = abs (iX1 - iX2);
int deltaY = abs (iY1 - iY2);
// Count the minimum number of iterations needed
// to draw the line. Choosing the maximum from the length and height
// of the line, we ensure the connectivity of the line
int length = max (deltaX, deltaY);
if (length == 0) return;
// Calculate the increments at each step along the abscissa and ordinate axes
double dX = (x2 - x1) / length;
double dY = (y2 - y1) / length;
// Initial values
double x = x1;
double y = y1;
// The main loop
length ++;
while (length--)
{
x + = dX;
y + = dY;
myservo_hor.write (x);
myservo_ver.write (y);
delay (delay_ms);
}
}
#include
#define laser 7 // the laser is switched on to this pin
#define led 13 // the LED built into Arduino
// experimentally select a safe spot movement zone
int minv = 85; //10; // extreme lower position of the spot
int maxv = 115; // 55; // extreme top
int minh = 90; // 45; // leftmost, front view
int maxh = 145; // 120; // extreme right, front view
unsigned long DelayBetweenMovements = 1000; // delay in ms
unsigned long second = 1000; // one second = 1000 ms
Servo myservo_ver; // move vertically
Servo myservo_hor; // horizontal movement
void setup ()
{
pinMode (laser, OUTPUT);
pinMode (led, OUTPUT);
digitalWrite (led, HIGH);
ServoOn ();
myservo_hor.write ((maxh + minh) / 2); // set the laser
myservo_ver.write ((maxv + minv) / 2); // in the middle position
delay (2000); // and watch for a couple of seconds
}
// - Main --------------------------------------- -------
void loop ()
{
randomSeed (analogRead (0)); // initialize random with a random value from port 0
int g = random (1,7); // randomly select one trajectory from seven
randomSeed (analogRead (0));
int tg = random (1,3); // randomly select a session duration of 30, 60 or 90 seconds
DelayBetweenMovements = second * random (1,5) / 2; // randomly select the travel time between the points of the path
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 -----------------------------------------
// sine wave vertical with random amplitude
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
delay (DelayBetweenMovements);
}
// digitalWrite (laser, random (2));
}
ServoOff ();
}
// horizontal sine wave with random amplitude
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
delay (DelayBetweenMovements);
}
// digitalWrite (laser, random (2));
}
ServoOff ();
}
// a square with a random size
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 ();
}
// fan movements with a random swing
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 ();
}
// fan movements
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 ();
}
// fan movements
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 ();
}
// random movements in all directions
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) {// enable
servos and turn on the laser myservo_ver.attach (9); // servo vertically attach to digital pin 9
myservo_hor.attach (8); // connect the servo horizontally to digital pin 8
digitalWrite (laser, 1); // turn on the laser
}
void ServoOff (void) {// turn off the servos and the laser -
relax myservo_ver.detach ();
myservo_hor.detach ();
digitalWrite (laser, 0);
}
// move from the current point x1, y1 to point x2, y2 with delays between steps delay_ms
void servo_move (double x2, double y2, int delay_ms)
{
double x1 = myservo_hor.read (); // read the current position of the serv
double y1 = myservo_ver.read ();
int iX1 = round (x1); // round the coordinates
int iY1 = round (y1);
int iX2 = round (x2);
int iY2 = round (y2);
// Length and height of the line
int deltaX = abs (iX1 - iX2);
int deltaY = abs (iY1 - iY2);
// Count the minimum number of iterations needed
// to draw the line. Choosing the maximum from the length and height
// of the line, we ensure the connectivity of the line
int length = max (deltaX, deltaY);
if (length == 0) return;
// Calculate the increments at each step along the abscissa and ordinate axes
double dX = (x2 - x1) / length;
double dY = (y2 - y1) / length;
// Initial values
double x = x1;
double y = y1;
// The main loop
length ++;
while (length--)
{
x + = dX;
y + = dY;
myservo_hor.write (x);
myservo_ver.write (y);
delay (delay_ms);
}
}
Practice.
The first laser failed after a week - the conclusions broke off, although they were made from a stranded wire. The second formed the findings in a spiral. It helped. You can still glue to drip from the glue gun to the place where the leads come out of the body.
Not all power adapters are suitable. Arduino does not start from some, or the laser twitches. Apparently, large ripple at the output. The capacitor was too lazy to solder, just picked up a good adapter, because a lot of them were lying around.
Tested on several cats. Young worn for a long time. Older - they run around, and then lie down and watch how the stain is dodging, or they are staring at the machine itself, as it buzzes and moves. Paw machine did not try. But, just in case, he came up with a casing from a paper box.
During the experiments, the wallpaper, curtains and cats were not affected.
In general, I recommend this kind of thing to all electronic catmates!