measuring servo speed

Hello folks. Thanks for all the help with my past projects. I’ve got a new project I’m working on. For this particular one I want to measure a servo speed. I’ve made an encoder disc, and attached it to the servo. The disc has 60 slots / revolution. When the servo goes from 48 to 110 it’ll see ~11 slots (11 pulses). I’ve got a photo-interrupt which monitors the disc slots - on interrupt 0. The controller is an arduino mega adk.
Below is my sketch. You can see that in the setup I’ll initialize the attachinterrupt. All it’s doing is monitoring int0, when there is rise it’ll call the count function and increase the pulses by one.
In the loop function there are two for loops. One drives the servo from A to B, the other one drives it from B to A. It’ll then assign the pos variable to the servo – thus stepping the servo from A to B by one degree. There is a built in delay. If I change this I can speed up or slow down the servo.
After each for loop I’ll print the #pulses to the serial monitor.
The problem is that if I’m running 10-15 ms time delay it’ll count 11 pulses, if I’m running less than 7 time delay the #pulses will be all over the map, but in general it’ll be close to 6. In both cases I can see the servo arm running the full throw. Once I can count the pulses I’ll measure the elapsed time and calculate the RPM.
Any help/ideas are appreciated to figure out why the pulse count is different @ higher and lower speeds. Thank you!

#include <Servo.h>
Servo myservo;

int closed_angle = 110;
int open_angle = 48;
int pos = 0;
int time = 15;
volatile unsigned int pulses = 0;

void setup()
{
Serial.begin(9600);
attachInterrupt(0, count, RISING);
}

void loop()
{
pulses = 0;
myservo.attach(4); // attaches the servo on pin XXX to the servo object
for(pos = closed_angle; pos >= open_angle; pos–) // goes from *** degrees to *** degrees
{
myservo.write(pos); // tell servo to go to position in variable ‘pos’
delay(time); // waits xx ms for the servo to reach the position
}
Serial.print("# of pulses ");
Serial.println(pulses);

pulses = 0;
for(pos = open_angle; pos <= closed_angle; pos++) // goes from *** degrees to *** degrees
{
myservo.write(pos); // tell servo to go to position in variable ‘pos’
delay(time); // waits xx ms for the servo to reach the position
}
Serial.print("# of pulses ");
Serial.println(pulses);
}

void count()
{
pulses++;
}

From your description of the problem, I wonder about the response speed of your sensor.

Can you provide a manufacturer/part number for the photo interrupter? How wide is the gap and what is the thickness of your disc. Is it an analog device? Is it a digital device with a built in schmidt trigger?

Can you provide a wiring diagram as to how you have it wired, and the resistor values on both sides?

There are questions to answer, but I don’t see anything obviously wrong with your code (except that it was not posted within the code tags generated by the </> icon on the tool bar). You could type “pulses” as a volatile byte instead of an int. Typically values read from within an ISR need to be “protected” so that they don’t change during the reading, but with your low counts, the data is limited to one byte so it’s not the issue causing your problem.

How is the servo powered? If by the Arduino the higher speed might be affecting the power supply.

    myservo.attach(4);  // attaches the servo on pin XXX to the servo object A minor point, but why are you doing this in loop() ?

With a sweep of 62° (110° - 48°) you should have a count of 10-1/3. I suspect that when your delay drops below 7 milliseconds (about 140 degrees per second) you have exceeded the slew rate of your servo and the arm position is falling behind. Since you print out the pulse count before giving the arm time to catch up you are naturally reporting a lower count.

As an experiment try adding delay(500); after the movement loop and before displaying the pulse count. If the pulse counts become consistent then you are certainly displaying the pulse count before the arm stops moving.

If I want to measure the speed of the sweep I would record the time in microseconds that the first pulse arrived and the time in microseconds that the last pulse arrived. Subtraction will tell you how fast the movement occurred.

Cattledog: Thanks for the advice. The sensor is being used for 3D printers as an end stop switch with TCST2103 photo interrupter. These are the optical end stops: http://www.ebay.com/itm/-/291387502807
The gap is 3.0 mm the encoder disc thickness is 1.3 mm. I believe it’s an analog device. The servo is Hitec HS 322HD.

Groundfungus: The mega adk is connected to a ramps v1.4 board. There are extra pins for the servos. You can put a jumper and it’ll get 5V from the board. Instead I added a 5V voltage regulator which only powers the servo. It can draw 800 milliamps, so I put a heat sink on the voltage regulator.

UKheliBob: thanks for the suggestion, I’ll move it to the setup

JohnWasser: thanks for the advice. I’ll implement that routine, and see if it gives better results.

Thank you All!

Getting tons of frustrations on this project. As a mechanical engineer this programming thing seems to be not black and white at all. Just thought about those “business cases” when they sell microcontrollers to 10 yr olds. Even with tutorials. C’mon! they’ll never learn it.
Back to my issue.
I’ve tried to measure the servo speed with an encoder wheel attached to the shaft. Couldn’t make it work. Then I said screw it. What is the simplest way I can do this. I’m not really curious about the servo’s speed at a given time, I’m just curious how fast can it travel let’s say 60 degrees under various loads. I know from the servo’s user manual that it can do 60 degrees in 150 milliseconds at no load.
So I printed a new encoder wheel it has only two slots in it, spaced 60 degrees apart. There is one (1) optical end stop which can pick up the encoder’s position.
I’ll not copy my code here, cause all of them are useless. I tried interrupts, while function, if function etc. nothing really worked.
The general structure of my code looked like:

Variables
Initialize
Interrupt definition comes here
Main Loop
Tell the servo to go to open position
Run a for loop
Check if end stop tripped if it is
Return
Tell the servo to go to closed position
Run a for loop
Check if end stop tripped if it is
Return
End main loop

Here is what I think happening. If I put the servo command in a for loop the servo speed will be defined by the delay in the for loop. I want the servo to go from A to B as fast as it can (under load), therefore I’m not particularly interested about the delay in the for loop. I really want arduino to send the servo from A to B and just twiddle until and interrupt comes by or the end stop is tripped. This might not seem logical but remember I’m a mechanical engineer. Once the servo got to B, then reverse and go to A until the end stop is tripped again.
As always any comments are welcome. Thank you!

I'll not copy my code here, cause all of them are useless.

So, you don't want help making them work. I'm OK with that. Good luck.

I really want arduino to send the servo from A to B and just twiddle until and interrupt comes by or the end stop is tripped.

That is NOT what interrupts do. Think about sitting at home on a summer afternoon, watching a baseball game on TV. The phone rings. That is an interrupt. You mute the TV and answer the phone. Damn telemarketers. You hang up and go wash the car. Hey wait, no that it not what you do. You get another beer and zone out in front of the TV again.

don't take me wrong. it's just I've got 20+ iteration of the code and not sure which one should I show here. that's why I said they're useless.
I just want to gain a general feeling on how to implement this. a skeleton.

and not sure which one should I show here.

Does it matter? Pick one. Write the names on slips of paper. Toss them in a hat. Close your eyes. Pick a slip of paper. Post the named code.

How to implement a solution depends on whether you are polling the pin that is supposed to trigger the end of the timing operation, or whether that change is supposed to trigger an interrupt.

Forget using for loops to move the servo.

Position the servo to a home position using a single servo.write(). When an input changes state, possibly a pushbutton, or after waiting a suitable time for the servo to reach the start position, save the time from millis() or micros() and move the servo to a new position more that 60 degrees from the first one using a single servo.write().

Detect when your encoder passes through 60 degrees by using an interrupt that simply saves the current value of millis() or micros(). Subtract the start time from the end time to get the time taken to move through 60 degrees.

I'm not really curious about the servo's speed at a given time, I'm just curious how fast can it travel let's say 60 degrees under various loads.

I'd just use two pieces of wire, one on the servo arm, and the other in a Y setup to do the timing. Set an arduino pin high connected to the servo arm wire, and the Y wire connected to ground. Have the servo arm wire touching one arm of the Y wire pulling it low to start. Then move the servo arm wire until it touches the other arm of the Y wire pulling the servo arm wire low again. Count the millis during the time while the servo arm wire is high.

Thanks everyone for all the valuable comments. Below is the code. At least in its current state.

#include <Servo.h> 
Servo myservo;   

int closed_angle = 108;
int open_angle = 47;
unsigned long StartTime;
unsigned long ElapsedTime;

void setup()
{
  Serial.begin(9600);
  myservo.attach(4);
  myservo.write(70);
  delay(500);
  StartTime = millis();
}

void loop()
{      
    delay(2000);
    StartTime = millis();
    myservo.write(open_angle);              
        for(int i = 0; i < 500; i++)
        {
          delayMicroseconds(10);
            while(digitalRead(2) == 1)
              {
                return;
              }
        }
        ElapsedTime = (millis() - StartTime);
        Serial.println(ElapsedTime);
      delay(2000); 
      myservo.write(closed_angle);              
        for(int i = 0; i < 500; i++)
        {
          delayMicroseconds(10);
            while(digitalRead(2) == 1)
              {
                return;
              }
        }    
}

Again, there is an encoder wheel attached to the servo. It only has 2 windows 72 deg apart. There is an optical end stop which picks up the encoder’s two positions.
At this state I’m not sure the encoder is stopping the servo, when the servo reaches the end position
The stop watch does not work.
Again, my goal was to have the servo reach it’s end position as fast as it can, and while the servo is still running to it’s end position and the end stop is tripped, then the servo should switch direction and run towards the other end.

Is your switch active high or low?

Does the switch pin need to be pulled high?

Here’s some code which assumes the switch is active low. It also assumes no pull-up is needed.

#include <Servo.h>
Servo myservo;

int closed_angle = 108;
int open_angle = 47;
const byte INACTIVE = HIGH;
const unsigned long MOVE_OFF_SWITCH_INTERVAL = 40;
unsigned long timer[3];

void setup()
{
  Serial.begin(9600);
  myservo.write(closed_angle);
  myservo.attach(4);  
}

void loop()
{
  Serial.println("Start from closed.");
  delay(500);
  timer[0] = micros();
  myservo.write(open_angle);
  delay(MOVE_OFF_SWITCH_INTERVAL); 
  while (digitalRead(2) == INACTIVE){} // assumes active low
  timer[1] = micros();
  myservo.write(open_angle);
  delay(MOVE_OFF_SWITCH_INTERVAL); 
  while (digitalRead(2) == INACTIVE){} // assumes active low
  timer[2] = micros();
  Serial.print("closed to open time = ");
  Serial.print(timer[1] - timer[0]);
  Serial.println(" us");
  Serial.print("open to closed time = ");
  Serial.print(timer[2] - timer[1]);
  Serial.println(" us");
  delay(2000);
  Serial.println("Repeating Test");
  delay(500);
}

I think “digitalRead” is slow. I think you’d get better precision using a pin interrupt.

If you use the above code, you will probably need to adjust the constant “MOVE_OFF_SWITCH_INTERVAL” to give the servo time to move away from the switch.

        for(int i = 0; i < 500; i++)
        {
          delayMicroseconds(10);
            while(digitalRead(2) == 1)
              {
                return;

Why are you stuffing your head in the sand for ANY length of time? Why are you using a for loop at all? EXPLAIN what you are trying to do, with comments.

DuaneDegn: I tried to run your code, but it worked for one cycle only. the servo will get stuck in an open position. When the switch is activated (encoder opening moves into the switch) it's low or 0. This is a 3D printer end stop. It has a LED on it.
I changed const byte inactive = LOW
I also changed the move_off_Switch_interval = 400
didn't work.

I'll try interrupts. again.

thank you!

Paul S. the idea is to rotate the servo left or right - IMPORTANT - as fast as it can go. therefore the closed_angle & open_angle are directional vectors only. I used the 108 & 47 numbers because it'll move the servo back and forth approximately at the right switching points, but in reality I should have used 130 & 20 so I definitely aim the servo to overshot, and if it changes direction at the right time, that can only be attributed to the interrupt & end stop effect.
I used the loop to make the arduino twiddle until a message comes in to stop the servo and change it's direction.
let's say I want to make it really simple:

(1) myservo.write(open_angle);

let's say the servo is @ 108 and since the open angle is 47 it'll have to go 61 units. the HS-322HD manual says @ 5V it'll take 190 ms (60 degree run)
If I have a

delay(200) after line (1)

I can be sure the servo will reach it's 47 position. Right? Well not exactly. It might do it @ no load, but I have time-varying load on the servo. This is why I'm using the encoder as a feedback loop.

Again, my goal was to have the servo reach it's end position as fast as it can, and while the servo is still running to it's end position and the end stop is tripped, then the servo should switch direction and run towards the other end.

If you can't get your current approach to work, I suggest you skip using the encoder to time the servo movement time, . You can command the servo to cycle between two different known positions. Using a limit switch approach (aka two wires touching), you should be able to measure in millis the time the switch is not in its resting state. Keep it simple.

There was an error in the code I posted. The code didn’t tell the servo to close.

There are two of the following statements.

myservo.write(open_angle);

The last one should read:

myservo.write(closed_angle);

Here’s another version of the code which does use delays while the servo is moving (in the main loop).

#include <Servo.h>
Servo myservo;

int closed_angle = 108;
int open_angle = 47;
const byte SWITCH_PIN = 2;
const byte SERVO_LPIN = 4;
byte active;
unsigned long timer[3];

void setup()
{
  Serial.begin(9600);
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  myservo.write(closed_angle);
  myservo.attach(SERVO_LPIN);  
  delay(2000); // give servo time to move
  active = digitalRead(SWITCH_PIN);
}

void loop()
{
  Serial.println("Start from closed.");
  delay(500);
  timer[0] = micros();
  myservo.write(open_angle);
  while (digitalRead(SWITCH_PIN) == active){} // move off switch
  while (digitalRead(SWITCH_PIN) != active){} // move to other switch
  timer[1] = micros();
  myservo.write(closed_angle); 
  while (digitalRead(SWITCH_PIN) == active){} // move off switch
  while (digitalRead(SWITCH_PIN) != active){} // move to other switch
  timer[2] = micros();
  Serial.print("closed to open time = ");
  Serial.print(timer[1] - timer[0]);
  Serial.println(" us");
  Serial.print("open to closed time = ");
  Serial.print(timer[2] - timer[1]);
  Serial.println(" us");
  delay(2000);
  Serial.println("Repeating Test");
  delay(500);
}

This code assumes the switch is active when the servo is in the open and closed positions.

zoomkat:
If you can't get your current approach to work, I suggest you skip using the encoder to time the servo movement time, . You can command the servo to cycle between two different known positions. Using a limit switch approach (aka two wires touching), you should be able to measure in millis the time the switch is not in its resting state. Keep it simple.

I think what he's calling an "encoder" is a limit switch.