Sorry if this has already been asked. I was unable to find it. I've been trying to get a Hitec hs-485 servo to go to certain angles and stop. This would seem to be easy with the arduino servo library, but I've found that it does not function at all as I expected.
First, I tested both the microseconds and angle commands, to make sure the differences in the pulse length of this particular servo didn't cause angle errors for the angle command. This proved to work fine. However, I find that regardless of what method I use, the device will not move to exactly the right angle. Usually if moving < 10 degrees, it will not move at all. And it seems to move to something like 105 degrees when I tell it to move to 90.
The other main problem I get is a constant jittering around the angle I send it to, when giving it any angle that is not 0 or 180. Is this due to the error margin of the ATMega's PWM?
It's worth noting that I tried the sample sweep program, and it worked fine. Additionally, I tried to move to said angles modifying the sweep program (so going 1 degree each time), but this did not change the jittering or angle error. I'm sending the angle I want it to move to via USB using serial commands.
Am I doing something you guys clearly can tell is wrong?
I can post one of the programs I used, if necessary. Mainly, I was just wondering if this was a common problem.
I have noticed that setting an angle with the servo library (let's say 20degrees) won't necessarily result in a movement of 20 degrees on the servo, as different servos are capable of moving different amounts. What you need to do is to determine what input gives you what output on the servo, you could then use the map function to map one value to the other.
it would be possible to knock up a routine using the 16bit timer to control the position of the servo very accurately, to well within the accuracy of the servo itself.
What is it that you are tying to do with your program?
Am I doing something you guys clearly can tell is wrong?
Yeah - you are expecting to get positional accuracy from a cheap hobby-grade servo connected to a cheap microcontroller using cheap software, when you don't even have any form of positional feedback from the cheap hobby-grade servo to the cheap microcontroller in the first place.
That's the blunt answer.
If you want more accuracy, there are several directions you could take:
Keep everything the same, but bring the output pin for the potentiometer in the servo outside of the case and hook it up to an analog input pin on the Arduino; this has been explained how to do elsewhere, so I won't elaborate. Once you have done this, then you would have to set up software to read the pin, and automatically adjust the servo position to match the angle you want. This will supply the feedback you need.
Do the same a #1, but remove the control board from the control of the motor, and control it directly with the Arduino (either using the control board's PCB for the h-bridge, or your own h-bridge). You would have to write your own software (window comparator, or PID) to control the h-bridge and move the motor the desired direction/speed.
You could also perform #2 yourself by using your own motor and gearbox for the "servo", and your own potentiometer (I am doing this myself for a "large torque" servo motor).
For more accuracy, you could use an optical encoder instead of a potentiometer; this encoder would have a bit value that could be read by the Arduino that would change for each degree of rotation (an 8 bit encoder would give you 256 "degrees" around the circle).
You can buy digitial servos that are more accurate; there are also special digital servos that use a serial bus that are more accurate (someone recently posted about one he developed based on the ATMega) - such servos aren't cheap.
You can buy optical encoders (and/or motors with them) for positional tracking; these aren't cheap either.
All of this boils down to that you need a way to have the ability to read the absolute position of the shaft, and close the feedback loop with the Arduino. With a standard hobby servo, the feedback loop is closed at the servo, and the Arduino knows nothing else about it - it just sends the command (the PPM signal - positional pulse modulation) to the servo, and the servo does the rest. Different servos do different things (even the same servo from the same manufacturer and lot will do different things - sometimes depending on the time of day and such!). Without closing the feedback loop back to the Arduino itself (or giving the servo more intelligence beyond the simple analog circuitry it is using), you are going to be hard pressed to get the accuracy you desire.
We haven't even talked about repetitive accuracy (forget it with hobby servos)...
I've got to concur with cr0sh - a hobby servo may appear to be closed-loop (and it is - to point), but in it's intended application, the actual loop is finally closed by the operator.
"Plane flying a little too downwards? Give it a touch more elevator" (next time, maybe trim it a bit with the turn-buckle or the trim pot)
"Banking a little to the left? Maybe a nudge to the right on the ailerons."
Despite cr0sh's doom-saying, you can get reasonable results provided you send the servo an accurate signal and the servo isn't completely rubbish, and Hi-Tec servo's aren't the worst in the world. True, you won't have a very high degree of accuracy or repeatability, but depending on the application it could be more than enough. Everything is relative to the application.
If you start supplying standard hobby servos with higher than normal voltages, they can start to jitter. Otherwise it is probably code that is probably causing the problem. I've got servos connected to two different servo controllers and they are not jittering. That being said, hobby servos are made inexpensive and I'm suprised they work as well as they do considering the simple pot inside them. I've experimented with hobby servos and the accuracy will only be ~.5 deg at best. Hobby servos have built in control dead bands, ususlly ~4us to minimize overshoot. You may want to go with a dedicated servo controller if you can't solve the arduino servo control issues.
Thanks for the replies, everyone. Here is one of my (overly long) coding tests I was trying to get working. I was wondering if by incrementing slightly every time till I reached the desire angle would fix the problem, but it didn't.
#include <Servo.h>
Servo myservo;
int pin = 9;
char textinput[4];
char buffer[4];
int angle, signal, n, pos, temp;
void setup()
{
myservo.attach(pin);
myservo.write(0);
Serial.begin(9600); //begins serial comm at 9600 baud
Serial.println("Please choose the angle you want the servo to move to: ");
}
void loop()
{
if(Serial.available() == 3)
{
angle = GetAngle();
signal = 600 + (angle * 10);
if(angle >= myservo.read())
{
temp = myservo.read()*10;
for(pos = (temp) + 600; pos < signal; pos += 10) // goes from 0 degrees to 180 degrees
{ // in steps of 1 degree
myservo.writeMicroseconds(pos); // tell servo to go to position in variable 'pos'
delay(10); // waits 15ms for the servo to reach the position
}
}
else
{
temp = myservo.read()*10;
for(pos = (temp) + 600; pos>= signal; pos -= 10)
{
myservo.writeMicroseconds(pos);
delay(10);
}
}
}
}
int GetAngle()
{
for(n = 0; n < 3; n++)
{
textinput[n] = Serial.read();
}
Serial.flush();
int output = atoi(&textinput[0]);
Serial.flush();
Serial.write("\nYou input: ");
Serial.write(&textinput[0]);
Serial.write("\n");
itoa(output,&buffer[0],10);
Serial.write("\n");
Serial.write(buffer);
return (output);
}
As for the actual interfacing, I tried both by supplying voltage from a 6V battery and using a voltage supply in a lab at school. The device was grounded to both the arduino and the voltage source. Control signal came from pin 9 in this case. Pretty basic setup, not sure a schematic is necessary.
What values do you get when you do 000, 090 and 180?
Does 090 jitter?
GB
[edit]Sorry, forgot to add that "delay(10);" is shorter than the servo pulse which is 20 milliSeconds, and so may account for some jitter. Hence trying to make the code as simple as practical.[/edit]
mem - I accept that you likely know much more about servo's than me, so would you help me to understand, and please explain:
I don't expect a delay of less than 20 ms to be the cause of jitter.
?
My 'logic' goes like this.
Those Hitec hs-485
have an "Operating Speed (6.0V): 0.18sec/60 degrees"
But this is the 'top speed' and not their acceleration or deceleration rate.
So assuming one control pulse is 0.020sec, they can, at best do 60/(0.180/0.020) = 60/9 degrees = 6.6, say 7 degress in that time.
AFAIK, when servos are close to the 'correct' position (i.e. the position the last control pulse sent them to) they move much more slowly.
In the sketch, they are being asked to change position at every control pulse. I'm wondering if this in itself may cause an issue.
Further, the value is sometimes +/-1, and sometimes +/-2 (as the delay is 10mSec, this seems feasible).
Won't they 'hunt', trying to get the right position, on every control pulse? Might that lead to a 50Hz-ish jitter?
As I said, I am not much of a servo user, so would you please take the time to explain this?
GB, the Servo library will only update a servo every 20ms irrespective of how frequently the write function is called. The library waits for until the previous 20ms 'frame' has finished before updating the servo with the most recent value passed to the servo.write function.
the Servo library will only update a servo every 20ms irrespective of how frequently the write function is called. The library waits for until the previous 20ms 'frame' has finished before updating the servo with the most recent value passed to the servo.write function.
I understand that, but I can understand why you might think I thought that. Sorry I wasn't clear.
I know it must work that way, or it wouldn't look like a reasonable 20mSec servo pulse.
I've read the source, and understand that the code is implemented correctly.
The points I am trying to make are:
the servo pulse-width changes at every pulse while the servo is moving very slowly, trying to get to the correct angle. Might this cause jitter?
the servo-pulse width doesn't vary at a constant rate at each pulse. Instead it jitters at each servo pulse, because it's value is changed by a 1, 2, or 3 degrees; it changes more frequently than the servo pulse.
Hey everyone, sorry for the late reply. Thanks for all the input! However, it turns out the code was not to blame for the problems I ran into. I was just given a particularly shoddy servo. I tried it on another one of the same model, and it worked perfectly. It had been getting really hot, which I attributed to it simply being a servo (someone told me servos got hot). Turns out I was wrong.
If you are only using one or two servos you can use Timer1 to produce a very accurate pulse.
I devised this code if you would give it a go.
I used the main loop() and GetAngle() posted by mem, but the rest of it is my own. Tested it and it works for me without any jitter wotsoever.
/**
* Precision servo positioning.
*
* Uses timer1 in phase and frequency correct PWM mode
* Doesn't use interrupts, so should be immune to interrupt issues.
*
* Chris Parish 2010
*
*/
//The standard timings for servos are 1.5ms for center, 1ms and 2ms for the full extents of travel
//however, most modern servos can operate an extended range, this can be as wide as 0.6ms to 2.4ms
//Consult the documentation for your servo for further details
#define SERVO_LOWER_LIMIT 1000
#define SERVO_UPPER_LIMIT 2000
#define SERVO_CENTER 1500
//Set your servo travel here. In standard mode most servos travel approx 90 degrees (45 either side of center)
#define SERVO_ANGLE_LOWER 0
#define SERVO_ANGLE_UPPER 90
const int servo = 9;
int angle;
void setup()
{
//Set up the timer------------------------
//TCCR1A register
TCCR1A = 0; //clear the register completly
TCCR1A |= _BV(COM1A1) | _BV(COM1B1); //set the required bits
//TCCR1B register
TCCR1B = 0; //clear the register completley
TCCR1B |= _BV(WGM13) | _BV(CS11); //set the required bits
//TCCR1C register
TCCR1C = 0; //clear the register completley
//Set the top limit - Complete frame length
ICR1 = 20000; //In this mode (assuming that you are running at 16mhz) The values are set in microseconds.
//Set the output compare registers which dictate the servo position
OCR1A = SERVO_CENTER; //1.5ms pulse, center the servo
OCR1B = SERVO_CENTER; //1.5ms pulse, center the servo
//Set pins 9 and 10 to output the waveform to the servos
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
Serial.begin(9600);
}
void setServoAngle(int servo, int angle)
{
//constrain the angle measurement
angle = constrain(angle, SERVO_ANGLE_LOWER, SERVO_ANGLE_UPPER);
//convert the angle measurement into a pulse length
angle = map(angle, SERVO_ANGLE_LOWER, SERVO_ANGLE_UPPER, SERVO_LOWER_LIMIT, SERVO_UPPER_LIMIT);
//write the angle to the appropreate servo output (1 or 2 -aka- 9 or 10)
if (servo == 1 || servo == 9)
{
OCR1A = angle;
}
else if (servo == 2 || servo == 10)
{
OCR1B = angle;
}
}
int getServoAngle(int servo)
{
int pulse;
int angle;
if (servo == 1 || servo == 9)
{
pulse = OCR1A;
}
else if (servo == 2 || servo == 10)
{
pulse = OCR1B;
}
return map(pulse, SERVO_LOWER_LIMIT, SERVO_UPPER_LIMIT, SERVO_ANGLE_LOWER, SERVO_ANGLE_UPPER);
}
void loop()
{
if(Serial.available() >= 3)
{
angle = GetAngle();
setServoAngle(servo, angle);
Serial.print("angle = ");
Serial.print(angle);
Serial.print(" (" );
Serial.print(getServoAngle(servo));
Serial.println(")");
delay(1000);
}
}
// get three digits from serial
// ToDo - check for non-digits
int GetAngle()
{
int pos = 0;
for(int n = 0; n < 3; n++)
{
char ch = Serial.read();
pos = (pos * 10) + ch - '0' ;
}
Serial.print("\nYou input: ");
Serial.println(pos);
return (pos);
}
The servo that nsulmol is using is a hs-485 by Hi-Tec. A bit of searching reveals that this servo is capable of 180 degrees of movement over the extended timing range of 600 to 2400 microseconds. So if you use the code above and replace the servo upper and lower limits with 2400 and 600 respectivly and the upper and lower angle limits with 180 and 0 you should get full movement of the servo.
With any servo a simple bit of trial and error tuning can be used to get the travel you require. Agreed it is not as accurate as any true closed loop system, but by using timer1 you can get it as acurate as the servo itself can be.
Chris, it might be helpful to comment about the state that Timer 1 is being put into.
I am probably not alone, and I find it horrible to reconstitute that information from the datasheet as it is a bit spread out.
This is similar to the old servo library (0016 and earlier): servo library
Where they use:
TCCR1A = _BV(WGM11); /* Fast PWM, ICR1 is top */
TCCR1B = _BV(WGM13) | _BV(WGM12) /* Fast PWM, ICR1 is top */
| _BV(CS11) /* div 8 clock prescaler */
;
OCR1A = 3000;
OCR1B = 3000;
ICR1 = clockCyclesPerMicrosecond()*(20000L/8); // 20000 uS is a bit fast for the refresh, 20ms, but
// it keeps us from overflowing ICR1 at 20MHz clocks
// That "/8" at the end is the prescaler.
As you can see, the values can be calculated from clockCylesPerMicrosecond so that it works on all Arduino's
Like you, I wrote my own before I saw a servo library, then found it in the Example->Libraries. Now it's different because of the interrupts (which can interfere with some code )
I wish that both were retained, maybe the Timer1 code renamed Timer1Servo?
GB
[edit]Ooops - tiny suggestion.
It would make the code easier to reuse if all of the servo setup code were moved from the setup() function, and put it into its own function, maybe servoSetup(), which is then called from setup().
Maybe even go one step further, and create a second file in the IDE, and keep all the servo code completely separate from the test program.
Just a thought.[/edit]