Floats to servos

The servo library takes an int as parameter for the angle. I just checked the precision of my servo, a Turnigy TGY-90S (sorry, eBay was the first site to include at least some stuff about the servo). I attached a small laser light to the servo and had it rotate in five degree steps, shining at a wall so that the 5 degrees became somewhat 10 cm. I ran different kinds of loops back and forth and had the red spot still for a second. Seems the accuracy of the servo is pretty good. Of course, with more load it might get worse. Then I had one pattern where it jumped back and forth four times +-10 degrees to one spot, then ascending just one degree and jumped again back and forth to that spot, then again advanced one degree etc. Here:

void loop() {
  for (pos = 60; pos <= 100; pos++) { //
    // in steps of 1 degree
    myservo.write(pos-10);              
    delay(1000);                       
    myservo.write(pos);              // 
    delay(1000);                       
    myservo.write(pos+10);             
    delay(1000);           
    myservo.write(pos);              // 
    delay(1000);                       
    myservo.write(pos-10);              
    delay(1000);                       
    myservo.write(pos);              // 
    delay(1000);                       
    myservo.write(pos+10);              
    delay(1000);           
    myservo.write(pos);              // 
    delay(1000);                       
  }
}

This particular test convinced me that the servo is pretty accurate. The four times the red spot hit what was meant to be the same spot, the deviation was less than one degree. That made me wonder why the servo library deals with ints. One should be able to set the servo to any float degree. Correct me if I'm wrong, but the servo reads an analog signal, doesn't it? A pulse length of 1 to 2 ms, right? And 1.5 ms means middle position or 90 degrees.

The precision of the pulse is some microseconds, right? Having everything rounded to degrees at some point just doesn't make sense. If the smallest time unit inside the processor would be say 5 µs, we would have 200 such units in one ms. And instead of having just 180 discrete degrees, we could have a continuum from 0 to 180 degrees. The continuum would make sense if we for some reason would like to divide the 180 degrees in say 170 equal steps. Having each step rounded to the nearest degree would make the steps of different size. It's a tiny error but unnecessary, if the servo itself doesn't deal with ints, only analog lengths of pulses.

Correct me if I'm wrong, but the servo reads an analog signal, doesn't it?

No, it reads a digital signal with a variable pulse width.
If you want more precise control, use the Servo library's writeMicroseconds method.

How are you powering your servo?
The behavior you describe sounds a lot like the twitching you might get with improper servo power.

edit: +1 on using servo.writeMicroseconds for better accuracy.

Johan_Ha:
The precision of the pulse is some microseconds, right? Having everything rounded to degrees at some point just doesn't make sense.

Only if you're looking for precision, which most people using hobby servos are not. When I move the rudder on my plane left I really don't mind if it travels 30 degrees or 30.1 degrees.

But for those requiring more apparent precision the Servo library does provide the writeMicroseconds() function (which IIRC is what it uses internally after doing the conversion from degrees).

Steve

You should have a look at the reference page for the Servo library. There's already a method for setting the length of the pulse in us. You could write code to turn floating point angles into us, but that would chew up a lot of memory and processor time. That's probably why they left that out of the library.

Anything to do with floats should make your stomach turn a little. They should be avoided at just about any cost. There are so many issues, they're slow, take a lot of memory, take a lot of code space, they're inherently inaccurate. The only place where float is good for you is when you have to deal with numbers over many many orders of magnitude. You can always get more precision out of fixed point math.

Thank you so much for the answers. I guess I should have read the source code for the stepper library. I'm just not that familiar with the IDE yet. I need to develope a habbit to open at least the .h file to any library I use. Now I just open an example file and tweak it.
Anyway, yes, the signal is digital with a variable length. It's digital, because it's either high or low. But the numeral information it contains is analog, namely the length of the pulse. No receiving device can read the number correctly to the least significant bit, if we use microseconds. My guess is that the servo internally skips the step of measuring the pulse length digitally with some digital counter and a chrystal clock chip and whatnot. My guess is that it just charges a capacitor when the input pulse is high and updates a peak hold circuit when it goes low, and further rotates the motor until the attached potentiometer has same voltage as the peak hold. If the servo really included digital circuits, why wouldn't the connection be serial?
I might write my own function for converting float degrees to milli or microseconds. Or if the float will slow down too much (which it won't in my project) i could use millidegrees as ints.
In my test I powered the servo through Arduino. I also powered the laser from Arduino's 3.3 V pin. I might have exceeded some limits, but it worked. I might repeat the test with separate power source both for the servo and the laser, especially if I want to measure the servo precision using microseconds.

edit

If I got it right, there's a 20 ms frame (= 50 Hz), where the signal and its processing happens. When signal goes up, it goes down after 1 to 2 ms. Then there's some 18 ms time for the servo chip to do something. As I wrote, my guess is that during the up time, a capacitor has been charged. Now it holds a voltage and the motor tries now to turn the potentiometer to the same voltage. One probably can't avoid that if the motor is only slightly off, the torque has to be very small, so that the motor won't run over the desired point due to moment of inertia. This causes the deadband, which at the end causes the inaccuracy of a servo.

You don't always have to go to the .h file. There is pretty good documentation for that lib right here on this site.

"No receiving device can read the number correctly to the least significant bit, if we use microseconds. "

Why you say that? It works just fine. When you call write with degrees it just does some math and calls writemicroseconds anyway. So adding more math to that function to deal with fractional degrees doesn't gain anything. You're still using writemicroseconds in the end.

I say that because the information in the signal is analog. My Arduino might create a pulse of length 1435 µs, but I have no oscillograph that actually could verify that the pusle is 1435 µs and not 1434 µs. And when the analog servo receives that pulse, there's no digital data, no digits that really contain the bits 10110011011, just a pulse of some length. The servo "reads" the length, and, as I mentioned twice, probably charges a capacitor. At this point the bits just aren't there anymore. It's just an analog voltage in a capacitor and then it gets compared with an analog voltage in a potentiometer. Of course it can work just fine, if it's quality job. But when I moved my servo from 120 degrees to 60 degrees, then from 0 degrees to 60 degrees, the former move stopped at a little higher position than the latter. Which makes me more convinced that it's about a capacitor. 120 degrees means a longer pulse and a higher voltage in the capacitor. Before a new measurement can be performed, the capacitor must first discharge, then start to charge. But the higher voltage to be discharge, the more of it might be left. Makes sense to me.
And even if the servo would be digital, it still has to read the length. And it could still miss the least significant bit or two.

Yes, float degrees is not better than microseconds. But float degrees would be better than int degrees. I could rotate back and forth with the angles 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9 and 11. Having my laser pointer setup lighting on a wall, I'd get 10 dots evenly distributed. Of course there would be some inaccuracy, but if I would mark each spot, I'd get clusters that would be evenly distributed, 1.1 degrees from each other. But since the write function uses ints and not floats, the angles in the sequence above will truncate to 1, 2, 3, 4, 5, 6, 7, 8, 9, 11.

After all this discussion, I'm going to write the servo function which takes millidegrees as argument. Just because it's easy to deal with say 65500 millidegrees as an int, if one wants 65.5 degrees. Instead of thinking and coding in microseconds, when one wants angles.

Instead of thinking and coding in microseconds, when one wants angles

Servo "angles" are simply an abstraction, unless you have a calibrated servo, and/or set your own max and min pulse lengths, but different power supply or environmental conditions may vary positions from run to run.

Remember, RC servos are man-in-the-loop devices - they're not designed for absolute repeatability in all conditions.
If your RC plane is a little nose-down, you shift the stick a little or nudge the trim. Next flight, things may be very slightly different, and an external observer (aka the pilot) will notice and compensate.

You're right. But all of this makes my project more and more interesting. I thought of creating a plotter with four rulers, as shown on a blog visible on the starting page on this site. With my strong metal gear servos! It will be for "artistic" drawings, not technical prints (heaven forbid!). I've noticed the lack of absolute repeatability, but I reach a certain accuracy repeating immediately a sequence. I get it that tomorrow the sequence might be different, due to cooled down equipment, room temperature and whatnot. The printer's capability to reproduce a piece of art would depend on its mood. :smiley:

Ok, now it's confirmed. Servos deserve floats! They do a much more precise job, if they are given fractions of degrees. Of course they still are mainly meant for man-in-the-loop things. But this is what I managed to draw with my ruler plotter:

img

Using Servo.write(angle) I got very unsure lines. I drew same rectangle two times. You can see that the lines follow each other. Each bump was repeated by the second line. This made me convinced that it was all about the angles truncated to ints.
In the same image you see two rectangles drawn with the float angles converted to microseconds. Much smoother lines! To convert the angle to microseconds I multiplied by 10.313 and added 543.3. Those are the values the servo lib seems to use for converting. If I'd want more geometric precision, I could run some test and calibrate everything.

You may think that the quality is still crap. But it perfectly suits my needs. My ruler joints are still a bit lose. I will get a little better quality after tightening all joints. If the line looks a bit like hand drawn, it's only bonus.

Ok, now it's confirmed. Servos deserve floats!

You're just being silly.
Use writeMicroseconds.

My only point is that since the lib accepts degrees, there's no excuse for using int arguments. If the argument is that ints are faster, the counter argument would be "Use writeMicroseconds".

If I need speed, I have lots of things, where I could gain speed in my code. Right now I convert Cartesian coordinates into two servo angles, which I convert further from radians to degrees (just because I used Servo.write(). But now I convert the degrees further to microseconds. Floats all the way. And the line is smooth! Sure next thing is to convert radians straight to microseconds.

AWOL:
You're just being silly.
Use writeMicroseconds.

As I read it, he DID use writeMicroseconds for the smooth drawing.
But then he (erroneously) attributes that to the success of using floats.

@Johan_Ha, when you mapped your float to int degrees, you were left with a resolution of 0-180 which then got mapped into 1000-2000 microseconds by the servo library. That is your rough example.

Then you mapped your float into int microseconds and wrote that int directly to writeMicroseconds. That was your smooth example.

In both examples, floats were not used to talk to the servos. Only in the math before the servos were involved. Roundoff error is reduced with a larger divisor.

Cool work btw. You should post a video of it in action!

Johan_Ha:
the counter argument would be "Use writeMicroseconds".

I'm pretty sure that has been our counterargument the whole way through.

I'm obviously very poor at phrasing. I understand that the servo receives a pulse of a particular length, which is controlled by a value given in microseconds. The servo doesn't receive values that equal the value I see as degrees. There might even be one abstraction level between microseconds and pulse length, where clock cycles are counted, not microseconds, I don't know. And I don't care. When I wrote that servos deserve floats, of course I never meant that the chip inside the servo actually would receive a 64 bit data chunk of some double format.

"But then he (erroneously) attributes that to the success of using floats."

Well, I did use floats for success. Only I did the conversion from floats to microseconds myself. Where the lib first truncates float angles to int angles, then converts them to microseconds. Semantics. Somewhere I read that if Servo.write() receives an invalid number (not in 0 - 180), it treats it as microseconds. If that is true, I might use a macro so that I could write myservo.write(FLOAT(angle)) where angle is a float variable and FLOAT just turns it into my formula 10.313*angle+543.3. I'd call it FLOAT just to underline my point that write() should accept floats :D. (I'm more amused than annoyed now about the whole issue.)

I'll definitely return with video about the smooth drawing in action. Until then, here's the rough line action:
https://drive.google.com/file/d/0B6Cm-HVDuZnNQ3IwZE1mOUdCX00/view?usp=sharing

Notice in the video how the plotter draws the rectangle the second time and the third time almost exactly along the same rough path.

"just to underline my point that write() should accept floats"

But then every time someone included the servo library and used write their code would be bloated by the float math. While it may please you, most of the rest of us are smarter than that and would be really disappointed if they bloated the servo library.

Floating point numbers are best avoided in the memory limited environment of a micro-controller. There are at least three big reasons

  1. They cause the code size to be larger

  2. They cause the code to run slower.

  3. They are inherently inaccurate.

Anytime you catch yourself using float your stomach should get a little queasy and you should spend some time trying to see if they can be avoided. If you want to work in hundredths of a degree then do that and use an integer number of hundredths instead of a floating point number in whole degrees. That makes things smaller, faster, and more accurate.

The only time where floats are really needed is when you have to deal with numbers that differ by several orders of magnitude.

You're right. But in this project I'm very deep in floats. Or doubles. For each coordinate to translate to two servo commands, I do for each servo one square root and two arc cos calls. Plus some multiplication and division and other simpler stuff. From that point of view saving memory and time by using int angles seemed just like an obstacle for me.

Of course the lib shouldn't be changed! The more I think of it, the more stupid that would be. It doesn't even need a complement of float accepting functions, when anyone can write such a function themselves.

Cool video. Thanks.
Another suggestion to help with accuracy.
Mechanically change things to use more/most of the servo travel.
Right now, it looks like you are only using a very small portion of the travel. This means that for the full travel of your pen, you are only using a small number of the available steps that the servo supports.

Lets say that an inexpensive servo can see 512 discreet steps in the 1000-2000 microsecond PPM signal.
A digital might see 1024 steps, with high quality recognizing 2048 and the extreme servos a full 4096 steps.
For discussion, lets say your servo recognizes 512 steps. That is 512 that correlated into the full swing of the servo arm.
It looks like the total swing used to move the pen the full range on the page is only a small fraction of that full swing. For discussion, lets assume 25% of the total servo travel.
Instead of 512 steps usable, you are down to just 128 steps.

Do something mechanically so that the servos make larger movements to move the pen the same distance.
Put shorter sections attached to the servos. Yes, you will have to reqork your formulas to work with the new lengths, but I think you will end up with finer resolution at the pen with the same program, same transmitted signal resolution.

Make sense?