Hobby Servo PWM Solution - Control over a narrow band

I am using an adafruit 12-channel PWM servo Hat for Raspberry Pi to control multiple RobotGeek 18-degree servos and have been fighting with it for two days. It is a 180 degree servo so from all the documentation,  a control signal of 0 should be far left and 4095 should be far right.... right?

I tried setting that and multiple in-between and sometimes the motor would quiver but that was about it. Most times it just sat there.

I checked:
  1. Power and ground were correct off the board (5v)
  2. The signal pin went from 0 to 3.3v linearly inbetween with required state
  3. The white wire was in fact the signal wire.
  4. PWM frequency set to 50 (And 200, 500, 30, 20, ... everything)
And it still didn't work.

The Problem

What I learned is that servos don't actually run of of true PWM. They run off of the length of the HIGH portion of a binary signal at 50 hz. If it is high for (usually) 1.5 msecs, then the servo centers itself. If it is high for less time, it moves left and longer, it moves right. 

AND... if you go out of the range of the allowed voltage, it can break the servo for some and for others, it just doesn't respond.

The tutorial from Adafruit does not discuss this probably because the arduino library has this already calculated but the python version of it does not. So hopefully this is helpful for people.




Solution = 102 to 512 out of 4096

The actual answer is that it goes from 102 to 512 out of 4096 which makes no sense until the timing data is taken into account.

Therefore, the center position is actually at 307 / 4096.

This means that the servo really only has accuracy to 0.88 degrees for control with a 12 bit controller, even if the servo has more accuracy in its positioning than that.

Reason = Legacy Timing

From this forum post the answer is a lot more clear:

First it needs a frequency that results in a 20 msec cycle time by using the formula 1 / Cycle Time = Frequency. So, 1 divided by .020 results in 50 Hz, which is one input needed by our program.

Next let's divide our 20 msec cycle by 4096 units, and we get 4.8 usec per unit. Now we can divide the pulse widths needed by a typical servo by the resolution of our controlling device.

  • .5 ms / 4.8 usec = 102 the number required by our program to position the servo at 0 degrees
  • 1.5 msec / 4.8 usec = 307 the number required by our program to position the servo at 90 degrees
  • 2.5 msec / 4.8 usec = 512 the number required by our program to position the servo at 180 degrees
  • This quick test uses Adafruit's PWM class from their Adafruit_PWM_Servo_Driver module to manually control the position of 2 standard servos. In this case, 104 = full CW and 521 = full CCW. 
The secret is that due to legacy designs, the servos use a pulse width which is fixed from 0.5 msecs  to 2.5 msecs to do 0 to 360 degrees. I believe this is due to the old RC radio standards on analog controllers (RC cars, helicopters, etc).

Therefore even though we have 4096 steps, only a subset of them are useful. If we were to lower the PWM frequency to 10 hz, we would have 5x the steps to use but our servos would only be updating at 10 hz which would be chunky. A while ago someone chose that 50 hz was the correct tradeoffs resulting in this answer.

Low accuracy at the edges

Additionally, when I sent in the correct pulses, I found that it did not actually travel from -90 to 90 degrees. It was more like -87 to 73. It turns out this is normal for hobby servos because they were designed to work over a middle band for things like driving a car, moving the tail of a plane, etc. Therefore the algorithm is designed to optimize accuracy when centered.

The rule of thumb I learned form the manufacturer was that the position should be pretty accurate over the middle 160 degrees, but the last 10 or so degrees have no guarantees.