in this case there is mostly one simple solution, and there are some better ones.
the problem you faced with the method:
First of all, the read() function indeed only returns what you send to it, it mostly is useful for if you want to know what position you send it to in some other part of code(for example can be used to plan what way to go first if you need to go to many points, or if you already are going to the desired position).
but since the rc servo has only one signal wire and that wire sends no signals back you don't know what state they are in, rc servo's are mostly for ease of use and not for accuracy and speed, if you need that you can look into a real servo(stepper motor).
so yes that function doesn't return the actual position, it returns the desired position.
Solution1: the easy software based method: to do this you can either read the datasheet to read the speed in duration per degree or measure it, and then use that to calculate the time it would take.
actually here the Servo.read() function really comes in use, so this is a perfect example of why you would actually sometimes need or want it.
- save the duration in miliseconds per degree in a variable.
- next just before you write to the servo use Servo.read() to get the current theoretical position and use that combined with the position you want to go to calculate the difference in dergees (for example 100 and 40 results in a difference of 60), then multiply that with the duration per step. and you will have the answer of how long a change would take.
Example1 warning, untested, might contain spelling mistakes and such
/* tuned delay Sweep
This example code is in the public domain.
No warranty
modified 27 March 2023
by Daan V.Loon
*/
#include <Servo.h>
#define DELAYPERDEGREE 2 //this is the value for the sg90 servo from the datasheet(0,12s(120ms)per 60degrees.
#define SAFETYMARGIN 110 //this is to add 10% extra time to wait, to make sure it finishes in time even under load.
//# define is used kind of like a variable that doesn't need to change, purely in code and not in ram
unsigned long waitDuration=0;//variable that shows how long to wait.
Servo myservo;
unsigned long CalcWait(Servo s,int desiredPosition)//method to calculate and return duration to wait in ms 1000ms is 1 s.
{
int spos=s.read();
if(spos>desiredPosition){return (((spos-desiredPosition)*DELAYPERDEGREE)*SAFETYMARGIN)/100;}
else{ return(((desiredPosition-spos)*DELAYPERDEGREE)*SAFETYMARGIN)/100;}
}//end of method
void ServoAndWait(int pos){
//insert range limiting code here if needed, warning this method doesn't check if you try to use numbers bigger or smaller than your servo or the servo library can actually handle.
waitDuration=CalcWait(myservo,pos);//call method to update how long to wait, needs to be done before telling the servo to move.
myservo.write(pos);//tell servo to rotate
delay(waitDuration);//blocking wait for servo to complete
}
void setup() {
myservo.attach(2);
ServoAndWait(180);//rotate to 180 degrees and wait until it should be there+10% with the safetymargin of 110
}
void loop() {
ServoAndWait(0);
ServoAndWait(180);
//this should reult in the famous sweep example, however then at the max speed of your servo, this way you can test it.
//to optimize speed use a safetymargin far over 100(like 200 or such) then reduce it until you see it just stop or just not stop anymore at the edges.
//also make sure to first test if your servo can actually handle 0 and 180 degrees, for example first test if it moves if you tell it to go from 10 to 0 and from 170 to 180, not all servos reach the full 180 degree range.
}
Example2:(no comments)
/* tuned delay Sweep
This example code is in the public domain.
No Warranty
modified 27 March 2023
by Daan V.Loon
*/
#include <Servo.h>
#define DELAYPERDEGREE 2
#define SAFETYMARGIN 110
unsigned long waitDuration = 0;
Servo myservo;
unsigned long CalcWait(Servo s, int desiredPosition)
{
int spos = s.read();
if (spos > desiredPosition) {
return (((spos - desiredPosition) * DELAYPERDEGREE) * SAFETYMARGIN) / 100;
}
else {
return (((desiredPosition - spos) * DELAYPERDEGREE) * SAFETYMARGIN) / 100;
}
}
void ServoAndWait(int pos) {
waitDuration = CalcWait(myservo, pos);
myservo.write(pos);
delay(waitDuration);
}
void setup() {
myservo.attach(2);
ServoAndWait(180);
}
void loop() {
ServoAndWait(0);
ServoAndWait(180);
}
Example3: Shorter and more stack overflow alien themed for people who just copy paste or find this clearer
/* tuned delay Sweep
This example code is in the public domain.
No Warranty
modified 27 March 2023
by Daan V.Loon
*/
#include <Servo.h>
#define DELAYPERDEGREE 2
#define SAFETYMARGIN 110
Servo myservo;
unsigned long CalcWait(Servo s, int desiredPosition)
{
int spos = s.read();
return (spos>desiredPosition?((spos - desiredPosition) * DELAYPERDEGREE * SAFETYMARGIN) / 100 : ((desiredPosition - spos) * DELAYPERDEGREE * SAFETYMARGIN) / 100);
}
unsigned long waitDuration = 0;
void ServoAndWait(int pos) {
waitDuration = CalcWait(myservo, pos);
myservo.write(pos);
delay(waitDuration);
}
////---code you would see below, headers and functions and variables above---////
void setup() {
myservo.attach(2);
}
void loop() {
ServoAndWait(0);
ServoAndWait(180);
}
These above should be fully functional codes that should work for you, they all work the same. I didn't test it with a servo however, but the logic worked when I tested that. also make sure to change the delay per degree to whatever your datasheet says, in this case the datasheet said 0.12s/60 degrees sg90(very common servo for arduino) so I turned seconds into miliseconds and divided it by 60, so 120/60=2, if you have a much faster servo or need more accurate results for different servos you can use the safety margin to tune, or you can use microseconds in the calculations or such, or just multiply the numbers by 10 and then at the end of the calculations divide it by 10 before returning.
Then here is also the more difficult hardware version
to do this you can either try to use the servo's buildin hardware and modify the servo with a 4th wire, either add it to the rotation sensor/potentiometer, or add it to the chip if it has a place where you can read it directly, with this you are on your own however since it is very speciffic for every servo.
a more easy way to do this is to use a compactor(or something else that gives you one digital output(typical rc servos will always keep the motor on,and just very rapidly change direction once they are on position, if it actually stops you might just want to read it like a normal digital signal(perhaps do something to protect against motor spikes), and read the wires both standalone instead of comparing them to each other) to compare the voltages on the wires to the motor in the servo, since when it moves one side has a higher voltage than the other side, when it stops, they either are equal or rapidly change to keep position. so you just check for whenever it changes. or if you know the direction you can be even more accurate and fast. this ofcource requires you opening up the servo.
or you can indeed add something like a rotary encoder, no need for a complex one, since you can just count the stripes and if you suddenly don't get changes anymore the servo is also stopped in case you missed some, or you can use something like a small microphone or vibration meter since the servo when it moves, this however may become inaccurate under higher load.
just try to avoid reading it with analog signals, since for what you want to use it for analog signals can be really slow.
also
you might want to look into non blocking delays, it won't make the servo's faster, but it allows you to do other things while the servo is moving so you won't have to wait until it is finished.
servo.read()
as a convenience function. I do not need to remember the last target position, I can ask the object, across other functions and methods. No hobby servo I know feeds back the actual position, because they are usually used in radio-controlled models, which have no feedback path.