[SOLVED] X- Y- Laser Control with Joystick, Absolute vs. Incremental movement

Hello everyone!
Today I created a project that I have been greatly looking forward to, I created a “Cat Toy,” a laser pointer on top of two servos, X- and Y-axis, both controlled via joystick.
I am using an Arduino UNO R3.
As of a couple hours ago, my project is up and working, but I now have another idea to improve it. The problem is I am having trouble coding out exactly what I want.
What I want is for my ‘work envelope’ (The space that my laser can reach) to be incrementally controlled, i.e. more like a FPS game, where you can move left, stop, let the joystick re-center, then move more left without the dot re-centering. In short, I am seeking to get rid of the snap-back movement that occurs when the joystick is released. Incremental vs. Absolute coordinates/movement.

Here is my code that is running with two HI-TEC HS-422 digital servos and the sparkfun “retail kit” joystick. The laser is an old .22lr cheap laser sight connected to a digital pin on the UNO.

#include <Servo.h>    

Servo servoX;       
Servo servoY;
int laser = 2;
int potpinX = A4;
int potpinY = A5;
const int servoPause = 1;

void setup() 
{ 
  servoX.attach(9);  
  servoY.attach(11); 
  pinMode(laser, OUTPUT);
  pinMode(potpinX, INPUT);  
  pinMode(potpinY, INPUT);
  
  digitalWrite(laser, HIGH);
  Serial.begin(9600);
} 

void loop() 
{ 
  
  int XpotVal = analogRead(potpinX);
  int YpotVal = analogRead(potpinY);
  int servoAngleX = map(XpotVal, 0, 1023, 180, 0);
  int servoAngleY = map(YpotVal, 0, 1023, 180, 0);
  
  servoX.write(servoAngleX);
  servoY.write(servoAngleY);
  
  Serial.print(XpotVal);
  Serial.print("  ");
  Serial.println(YpotVal);
  
  delay(servoPause);
  
}

Thanks in advance!

const int XCenterVal = 512, YCenterVal = 512;  //  Values of pot input when centered
const int XpotSpeed = 200, YpotSpeed = 20;  // Scale value for controlling speed of movement (higher=slower)

  int XpotVal = analogRead(potpinX) - XCenterVal;
  int YpotVal = analogRead(potpinY) - YCenterVal;
  int servoAngleX = constrain(servoAngleX + (XpotVal/XpotSpeed), 180, 0);
  int servoAngleY = constrain(servoAngleY + (YpotVal/YpotSpeed), 180, 0);

HAHA well thank you ill compile the code today..

Well I couldn't wait, so I plug and played, I also changed the window to print the servo angles, and they return a constant 180.
I uploaded it, and sure enough both servos shot straight to 180, 180.
Did I plug and play wrong?

GORE:
Did I plug and play wrong?

I can't see what you did from here so I can't tell.

#include <Servo.h>    // Use Servo library, included with IDE

Servo servoX;        // Create Servo object to control the servo 
Servo servoY;
int laser = 2;
int potpinX = A4;
int potpinY = A5;
const int servoPause = 1;

void setup() 
{ 
  servoX.attach(9);  // Servo x is connected to digital pin 9
  servoY.attach(11); 
  pinMode(laser, OUTPUT);
  pinMode(potpinX, INPUT);  
  pinMode(potpinY, INPUT);
  
  Serial.begin(9600);
  
  digitalWrite(laser, HIGH);
} 

void loop() 
{ 
  
  const int XCenterVal = 512, YCenterVal = 512;  //  Values of pot input when centered
  const int XpotSpeed = 200, YpotSpeed = 20;  // Scale value for controlling speed of movement (higher=slower)

  int XpotVal = analogRead(potpinX) - XCenterVal;
  int YpotVal = analogRead(potpinY) - YCenterVal;
  int servoAngleX = constrain(servoAngleX + (XpotVal/XpotSpeed), 180, 0);
  int servoAngleY = constrain(servoAngleY + (YpotVal/YpotSpeed), 180, 0);
  
  servoX.write(servoAngleX);
  servoY.write(servoAngleY);
  
  Serial.print(XpotVal);
  Serial.print("  ");
  Serial.println(YpotVal);
  
  delay(servoPause);
  
}

Well lets begin with the constrain- why constrain? I’m not looking to limit values, i still want to use the full range of motion of the servos as well as the pot. That is why I used the map function in my original code. Also, while running the code in my head I noticed that if the pot read less that 512 the int X/YpotVal would be a negative number, isn’t this undesirable and non-functional?

And why the difference in scale values? I assumed the difference was simply a typo, but did also compile it with these values in the interest of running the code as provided.

What would have proved more helpful is a description along with the code you provided. I assume the code was to be applied as such, and it did compile correctly.

In short, I am seeking to get rid of the snap-back movement that occurs when the joystick is released. Incremental vs. Absolute coordinates/movement.

Recently there was another post requesting incremental servo movement via a joystick. Don't think any workable code was posted. Below are previous concerning servos and joysticks. In the past I made an application on a pc where the servos actively followed the joystick position when the joystick trigger was pulled (if trigger pulled). No following when trigger released.

https://www.google.com/search?as_q=servo+joystick&as_epq=&as_oq=&as_eq=&as_nlo=&as_nhi=&lr=&cr=&as_qdr=all&as_sitesearch=http%3A%2F%2Fforum.arduino.cc%2Findex&as_occt=any&safe=images&tbs=&as_filetype=&as_rights=

Great! Thank you for your reply! Yeah its funny because I built my project and it wasn't until I looked into this issue that I found that others had in fact done what I did exactly, even the code! And I wrote the code by hand from scratch!

Ill give those a look, but even if I cant figure this out its a cool project to play with.
Highly recommended.

GORE:
Well lets begin with the constrain- why constrain? I'm not looking to limit values, i still want to use the full range of motion of the servos as well as the pot. That is why I used the map function in my original code.

The constrain() is there to keep your angles from going below 0 or above 180. Did you want to use angles outside the normal range of a servo? I didn't think so.

GORE:
Also, while running the code in my head I noticed that if the pot read less that 512 the int X/YpotVal would be a negative number, isn't this undesirable and non-functional?

You want the angle to change relative to it's current position. If you didn't allow negative motion you would hit the upper limit and stay there forever. Also, since you did not specify the centered values of your pot I had to assume it was 512. You should actually measure and use those values. If they are far off from 512 that would explain why your servos move to one corner.

GORE:
And why the difference in scale values? I assumed the difference was simply a typo, but did also compile it with these values in the interest of running the code as provided.

It was a typo. I don't know how sensitive you want your joystick to be. I gave a hint that you could adjust the sensitivity by changing those values.

GORE:
What would have proved more helpful is a description along with the code you provided. I assume the code was to be applied as such, and it did compile correctly.

If I told you why I was doing everything there would be less room for learning.

Change:

  int servoAngleX = constrain(servoAngleX + (XpotVal/XpotSpeed), 180, 0);
  int servoAngleY = constrain(servoAngleY + (YpotVal/YpotSpeed), 180, 0);

to

  servoAngleX = constrain(servoAngleX + (XpotVal/XpotSpeed), 180, 0);
  servoAngleY = constrain(servoAngleY + (YpotVal/YpotSpeed), 180, 0);

and then add the declarations outside of loop():

 int servoAngleX, servoAngleY;

This makes the declarations "Global" so they maintain their value between runs of loop();

As far as I can tell, what you need to be doing is remembering X and Y between each loop cycle, like johnwasser was suggesting.
Basically, you'll just need to be adding or subtracting your value from some remembered value that then gets written to each servo each time.
#code
//Not actually code, I'm making up functions and such below
int servoX = 90;
int servoY = 90;

loop(){
servoX += readJoystickX(); //This could be a negative to indicate negative axis motion
Servo.write(servoX);
servoY += readJoystickY(); //Same as above
Servo.write(servoY);
}
#/code

johnwasser:
The constrain() is there to keep your angles from going below 0 or above 180. Did you want to use angles outside the normal range of a servo?

No Johnwasser. What I am saying is that I would also like to use the full range of the potentiometers also. Hence, not barring any value from either- what I tried to do was write code that output values from 0 to 180, because If i tell the servo to write to 0, it will write to the furthest right position, 180, left. What I dont want is a "dead-zone" in the pot where I can move it but not the servo.

johnwasser:
You want the angle to change relative to it's current position. If you didn't allow negative motion you would hit the upper limit and stay there forever.

Well, the code that I have been experimenting with involves the general direction and magnitude of the pot, then subtracting or adding a value derived from the magnitude of direction. 90 to 85 to 80, etc. No negative readings need to be written to the servo. This is what I meant.

johnwasser:
You should actually measure and use those values. If they are far off from 512 that would explain why your servos move to one corner.

I did, and I accounted for any difference. It is not the reason. The code ONLY outputs 180. Moving the pots does nothing, even on startup.
And what I meant by an explanation was that any words would have been more useful than nothing, and there could still be room for learning.

Yes, mirith, thats the idea that I ended up trying to act on. I concluded that I needed to know that value at all times too. And yes your example of negative numbers is what I understand should be. What shouldn't is negative numbers being written to the servo. That is what I don't want. Thanks to you both, I will work on the code later.

Ok, so I finally produced a code that works (nearly) as intended.
I say nearly because upon start-up the servos both write to 0 and need to be pushed out of the work envelope (to the other side seemingly) before they will move incrementally. However, they both will operate as intended once they are un-stuck. :roll_eyes:

Yes, I did make numerous attempts to correct this, none successful. Some of these include writing both servos to 90 (center) in setup. Nope. Another was to constrain the possible written values to inside 0 -180, hoping it would not try to move outside of the servo’s range. Again, no dice. I will continue to seek solutions to this issue.

I ended up using small bits from all of our ideas. I did make the servo angles global, and I also used miriths code for producing new angles.

I also used the map function over constrain because I simply could not get constrain to produce the desired result. I mapped the pot values to 13 and -15 to keep the numbers small and thereby keeping the speed of movement down, and they are different to allow for the slight inaccuracy of the resting state of the potentiometer. Yes they are negative. That is of no concern, the issue is when the number being written to the servos is negative- the servos can be moved to anywhere in 0 - 180. Negative values may indeed work, but I prefer to keep them inside the stated range.

I am greatly pleased with the superb increase in accuracy with the incremental code over the absolute positioning code!

Thanks again, mirith and johnwasser. Hopefully this thread will answer others’ questions on this project and topic!

#include <Servo.h>    

Servo servoX;       
Servo servoY;
int laser = 2;
int potpinX = A4;
int potpinY = A5;
const int servoPause = 10;
int servoAngleX, servoAngleY; //Thanks johnwasser

void setup() 
{ 
  servoX.attach(9);  
  servoY.attach(11); 
  pinMode(laser, OUTPUT);
  pinMode(potpinX, INPUT);  
  pinMode(potpinY, INPUT);
  
  digitalWrite(laser, HIGH);
  Serial.begin(9600);
  
  //servoX.write(90); //An attempt to move both to center on start-up, 
  //servoY.write(90); //doesn't seem to work, hence ignored.
} 

void loop() 
{ 
 
  int XpotVal = map(analogRead(potpinX), 0, 1023, 13, -15); //13 and -15 to account for joystick center error, 
  int YpotVal = map(analogRead(potpinY), 0, 1023, 13, -15); //and to slow movement of servos. Center should be near 0
  
  servoAngleX += XpotVal; //Thanks mirith
  servoAngleY += YpotVal; //These can be negative...
  
  servoX.write(servoAngleX); //These can't, or at least shouldn't IMO
  servoY.write(servoAngleY);
  
  Serial.print(analogRead(potpinX)); //To monitor pot values
  Serial.print("  ");
  Serial.println(analogRead(potpinY));
  
  delay(servoPause);
  
}

GORE:
Yes, I did make numerous attempts to correct this, none successful. Some of these include writing both servos to 90 (center) in setup.

servo.write(90); won’t help. You need to set initial values for servoAngleX and servoAngleY. Try:

int servoAngleX=90, servoAngleY=90;

Do your servos complain if you try to push them below 0 or above 180? You should probably put a constrain in there somewhere:

  servoAngleX += XpotVal; //Thanks mirith
  servoAngleY += YpotVal; //These can be negative...
  servoAngleX = constrain(servoAngleX, 0, 180);
 servoAngleY = constrain(servoAngleY, 0, 180);
  servoX.write(servoAngleX); //These can't, or at least shouldn't IMO
  servoY.write(servoAngleY);

or if you don’t trust the constrain() function, do it manually:

  servoAngleX += XpotVal; //Thanks mirith
  servoAngleY += YpotVal; //These can be negative...
  if (servoAngleX < 0)
    servoAngleX = 0;
  if (servoAngleX > 180)
    servoAngleX = 180;
   if (servoAngleY < 0)
    servoAngleY = 0;
  if (servoAngleY > 180)
    servoAngleY = 180;
 servoX.write(servoAngleX); //These can't, or at least shouldn't IMO
  servoY.write(servoAngleY);

Thanks johnwasser, ill give the if sequence a go. Its funny, In the attempts I made I actually used both of those exact li!es of code!
Ill try again exactly as you suggest... I must be missing something because those two alterations make perfect sense! I should have cleaned it up and left the assignment of those variables as opposed to the direct write to 90. Looks sloppy!
Do you see why perhaps the constrain may fail? When I tried that I was very confident that there would be no more attempted movement outside the normal range of the servos.. And the fact that you reccomended it makes me believe that my code must be at fault in one way or another, or at the very least conflicting and causing the constrain to not behave as intended.

Ill try now and see what happens!

So what I ended up doing was using the constrain at the point you suggested, narrowed the range to exclude one degree per limit, and now there is no strain at all or strange jumping behavior- thanks to you problem solved! I must have had an odd line of code last time I attempted to constrain those values..
It did however make the speed of the servo increase by a factor of 5! They were moving at full speed after the code was uploaded. To fix this I took your advice on employing a scale value- 20. I also changed the mapping back to -80/83 to allow for more range of speed and less distance that the servo automatically writes should you peak the value for a split second- now it will stop moving when the stick is released. The servo angles are now equal to XpotVal/scaleValue.
The result is a solid boundary and no strained servos whatsoever!
The only issue left (albeit personal preference) is that the servos still write to the far up/left limit.. neither line of code succeeded in starting the servos at center. Still not sure why.. I can't see any issues preventing this..
Thank you for your suggestions!