Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

servoConfig and servoWrite(microseconds) #54

Closed
dtex opened this issue Jan 19, 2018 · 14 comments
Closed

servoConfig and servoWrite(microseconds) #54

dtex opened this issue Jan 19, 2018 · 14 comments
Assignees

Comments

@dtex
Copy link

dtex commented Jan 19, 2018

I noticed a lot of activity going on in the repo so I thought it might be a good time to get this request in while people are focused on the project. It's really two feature requests but they are closely related so I'm putting them in a single issue.

These requests are necessary to allow Johnny-Five to get more precise control over servos and be able to support other non-standard servos (i.e. 90°, 360° and multi-turn).

  1. In firmata if servoWrite is called with a value greater than 544 it should be assumed to be microseconds instead of degrees.

Here is the code in firmata

  1. firmata.js has a servoConfig method that allows us to to set the min and max dutyCycle in microseconds on a pin-by-pin basis. Servos are all a little different so there is no single range that is right for all. I've noticed that there is quite a variety of ranges across the different io plugins and I assume this is because the value chosen worked for whatever servo the developer's happened to have on hand.

Here is the servoConfig code in firmata.js for reference.

Here is a PR for tessel-io that shows implementations for both.

@fivdi
Copy link
Collaborator

fivdi commented Jan 19, 2018

@dtex this could be added in v3.0.0. I can't figure out how the values for SERVO_MIN and SERVO_MAX were calculated:

const SERVO_MIN = 0.03;
const SERVO_MAX = 0.12;

Can you explain how they were calculated please?

@dtex
Copy link
Author

dtex commented Jan 19, 2018

Sure. There are 1,000,000us in a second and servos operate at 50hz which means there are 20,000us in a single cycle. Servo's tend to respond 600us - 2400us PWM range so:

600 / 20,000 = .03
2400 / 20,000 = 0.12 <-- Edited

The problem is that not all servos are calibrated to respond to exactly that range so we need to be able to adjust it which is where servoConfig comes in. Also if we can set it from Johnny-Five then we can get the same behavior regardless of the io plug-in that's being used. Right now servos will respond differently on different boards. Check this out:

  • In Tessel-io, Galileo-io, linux-io the range is 600us - 2400us
  • In imp-io the range is 600us - 2000us
  • In particle-io, nino-io and rasp-io the range is 1000us - 2000us
  • In firmata the range is 544us - 2400us

@fivdi
Copy link
Collaborator

fivdi commented Jan 20, 2018

@dtex

The range for BeagleBone-IO is 400us - 2600us.

To get slightly more than 180 degrees of movement from a servo that I'm using for testing at the moment the range is 461us to 2282us. The maximum value that this servo will accept is approximately 2320us. Anything above that results in issues like unwanted vibrating etc...

If Johnny-Five uses microseconds for some features in the future I assume that the minimum value it will use is 544us. The maximum value the servo that I'm using for testing will accept is approximately 2320us. This would give a range of 544us - 2320us. However, this range only gives about 160 degrees of rotation. It feels like something isn't quite correct here or am I misunderstanding something?

@dtex
Copy link
Author

dtex commented Jan 20, 2018

I assume that the minimum value it will use is 544us

Correct.

this range only gives about 160 degrees of rotation

I don't think you're misunderstanding anything. Servos vary wildly in quality. Is it a ~$4 micro servo from an inventors kit or bulk ordered for a hackathon? If so don't put much stock in it, it could mislead you. Trust standard servos from Futaba, Hitec and other name brand manufacturers.

What kind of servo is it? I will try and find the specs.

To get slightly more than 180 degrees of movement

Careful here, getting more range than the servo is designed for could be bad thing. Some servos really are designed to have a little more than 180° and others are just low quality.

Those extra degrees could be the servo operating beyond the range of the potentiometer. You can test for this by moving in one degree increments toward 0 or 180. If at some point you move a single degree (i.e. from 12° to 11°) and the servo suddenly sweeps all the way to the endpoint, the servo is operating beyond the range of the pot. You will never be able to set the servo to any point between 1° and 11°.

Second, if we really are getting 200 degrees of range out of the servo, but our input values for mapping to duty cycle are 0 - 180, then the further the user gets away from 0° the less accurate the servo is going to be (the user asks for 180 but they get 200. That's bad). This really matters for IK solving on joint systems. This is another thing I'm trying to solve by allowing us to pass in nanoseconds. J5, can be told that this servo really does have 200 degrees (or 90, or 360, or 2160, or whatever) and handle the mapping so the io plug-in doesn't have to.

I have a tool that can help with finding the pwmRange on a servo. It uses an Arduino and firmata.

https://github.com/dtex/servo-tuner

@fivdi
Copy link
Collaborator

fivdi commented Jan 20, 2018

Yeah, it's a cheap servo. It's an Arduino TINKERKIT Micro Servo Module / SM-S2309S advertised as having a rotation angle of 180 degrees in the spec sheet here or here. It's also advertised as having a rotation angle of +/-60 degrees which isn't quite the same here.

I don't have an Arduino handy at the moment so I tried the test you suggested above with the following program:

var five = require('johnny-five');
var BeagleBone = require('beaglebone-io');

var board = new five.Board({
  io: new BeagleBone()
});

board.on('ready', function() {
  var servo = new five.Servo({
    pin: 'P9_14',
    pwmRange: [470, 2270] // Needed for a rotation angle of 180 degrees
  });

  function upper(degrees) {
    servo.to(degrees);

      setTimeout(function () {
        if (degrees < 180) {
          upper(degrees + 1);
        } else {
          lower(30);
        }
      }, 1000);
  }

  function lower(degrees) {
    servo.to(degrees);

      setTimeout(function () {
        if (degrees > 0) {
          lower(degrees - 1);
        } else {
          upper(150);
        }
      }, 1000);
  }

  lower(30);
});

The test works well and the servo moves 1 degree per second from 30 degrees to 0 degrees and then from 150 degrees to 180 degrees without suddenly sweeping to the endpoint. The test program uses a pwmRange of [470, 2270] which results in a rotation angle of 180 degrees (I measured the angle).

At the end of the day I guess the question is are you sure that 544us is the smallest value that will be needed to move a servo to 0 degrees? This would imply that there is something wrong with my test setup here (which may very well be the case) because it needs a 470us pulse to move to 0 degrees.

Edit: fixed a few typos.

@dtex
Copy link
Author

dtex commented Jan 20, 2018

I'm positive about the 544us value. It is pulled directly from the Arduino servo library. We are shooting for functional parity across all io-plugins and firmata.js is our "prototype" if you will (firmata depends on the Arduino library).

I expect the 3rd spec sheet is right and that your servo is actually a 120° servo. Your tests really seem to prove it out. I also think it's interesting to note that you could never get 180° out of this servo on an Arduino using their default ranges.

When considering the range of a servo, you should measure +/- from the center of its range, not the range between achievable endpoints. Even though the range varies, most servos should be at the center of their range when set to 1500us (and CR servos should be stopped at that value). This center can vary a little (+/- 100us) but by and large it's a standard we can use as a starting point for measuring the actual range of the device.

If you set your servo range to 730us - 2270us do you get about 120°? Those values seem more plausible to me.

@fivdi
Copy link
Collaborator

fivdi commented Jan 20, 2018

@dtex I hope I'm not annoying you here as it's certainly not my goal. I just don't get it yet.

I'm positive about the 544us value.

Can you explain the math behind 544us please or link to something that explains it? I don't understand where it comes from. I don't understand why you are positive that 544us is the minimum value needed when the Arduino servo library isn't positive about it. The Arduino servo library allows the minimum to be overridden by calling servo.attach(pin, min, max).

I expect the 3rd spec sheet is right and that your servo is actually a 120° servo. Your tests really seem to prove it out.

Do you mean the test proves that it's a 120° servo although the servo flawlessly moves 180° in the test?

I also think it's interesting to note that you could never get 180° out of this servo on an Arduino using their default ranges.

If the defaults are used, yes. But the defaults can be overridden with servo.attach(pin, min, max) and then it would work. In Johnny-Five 544us is a hard-coded value that can't be overridden. With the Arduino Servo library the min value can be set to a value less than 544 if needed, with Johnny-Five it can't.

If you set your servo range to 730us - 2270us do you get about 120°?

Jep, it's about 120°.

@dtex
Copy link
Author

dtex commented Jan 21, 2018

Can you explain the math behind 544us

I'm afraid I can't. My guess is the original developer got a cheap servo out of some inventors kit or something and that was the minimum value that worked for them ¯_(ツ)_/¯ We are just trying to match their default values so that all the IO plugins + devices behave the same as firmata.js + an Arduino. There is actually an open issue in the Arduino Servo repo about this mysterious value.

The Arduino servo library allows the minimum to be overridden by calling servo.attach(pin, min, max)

Thast's true, but it still uses the MIN_PULSE_WIDTH constant to determine wether a value being passed in to servoWrite is in degrees or microseconds.

In Johnny-Five 544us is a hard-coded value that can't be overridden.

That link is to the tessel-io repo but it's just as hard coded in the Arduino servo lib. There is no way to change the MIN_PULSE_WIDTH constant.

Overriding the range is provided by servoConfig which has been in firmata.js for a couple of years and J5 uses servoConfig when a pwmRange property is passed in a servo opts object. This line in Johnny-Five will throw an error with a number of the io-plugins.

It is my intent to get all the IO-plugins using the same default pwmRange that will be defined in Johnny-Five but that requires everyone supporting servoConfig. I had a PR in but I closed it because I discovered that many of the plug-ins don't have servoConfig yet.

Do you mean the test proves that it's a 120° servo

Yes but I inferred a lot. The lower servo range is so far beyond typical values and the midpoint is so far from 1500us that to me the 3rd spec sheet you link to makes an awful lot of sense.

@fivdi
Copy link
Collaborator

fivdi commented Jan 21, 2018

Thanks for the detailed responses. Things are a lot clearer now and I agree with what you say above.

There is one side effect of servoWrite handling values >= 544 as microseconds that's a little strange.

Calling servoConfig(pin, 500, 2500) is a valid call that sets the min and max values to 500μs and 2500μs respectively. However, if servoWrite(pin, 500) is called 500 which is less than 544 will be interpreted as degrees. The 500 will be scaled and constrained to 180°. In this case the call servoWrite(pin, 500) looks like it would set the position of a standard servo to 0° bit it sets it to 180°.

It probably makes sense for servoConfig(pin, min, max) to reject min values less than 544 as they will be interpreted as degrees when servoWrite is called.

The default range for BeagleBone-IO is 400μs - 2600μs today. It probably also makes sense to change this to 600μs - 2400μs.

@fivdi
Copy link
Collaborator

fivdi commented Jan 21, 2018

If #58 is merged servoWrite should be able to handle degrees or microseconds. linux-io was also modified to support this feature. See servoConfig and servoWrite in linux-io.

@dtex
Copy link
Author

dtex commented Jan 21, 2018

Does that mean servoConfig will also work in beaglebone-io? I'm still learning how it's all organized, so I'm not clear.

@fivdi
Copy link
Collaborator

fivdi commented Jan 21, 2018

Does that mean servoConfig will also work in beaglebone-io?

Yes. BeagleBone inherits from LinuxIO using old syntax and is equivalent to:

class BeagleBone extends LunuxIO {
  ...
}

The complete implementation of servoConfig is in LinuxIO so classes that extend LinuxIO don't need to implement anything for servoConfig.

servoWrite is partially implemented in LinuxIO. It does what it can and then delegates to _servoWriteSync to do the rest. _servoWriteSync for BeagleBone in PR #58 is here.

@fivdi
Copy link
Collaborator

fivdi commented Jan 21, 2018

Feel free to ask as many questions as you want 😄

@fivdi fivdi closed this as completed Jan 22, 2018
@fivdi
Copy link
Collaborator

fivdi commented Jan 22, 2018

@dtex beaglebone-io@3.0.0 has been published on npm.

servoWrite now handles degrees or microseconds. The required changes were also made to servoConfig.

rileyjshaw added a commit to rileyjshaw/Servo that referenced this issue Apr 5, 2019
R/C servos have a standard pulse width range of 1000 to 2000µs<sup name="a1">[1](#f1)</sup>, with the zero point between the two at 1500µs. Currently, Arduino's Servo library sets:

- [`#define MIN_PULSE_WIDTH 544`](https://github.com/arduino-libraries/Servo/blob/4970d615a13f4c0d08026ee361cc8a01974924a2/src/Servo.h#L80)
- [`#define MAX_PULSE_WIDTH 2400`](https://github.com/arduino-libraries/Servo/blob/4970d615a13f4c0d08026ee361cc8a01974924a2/src/Servo.h#L81)
- [`#define DEFAULT_PULSE_WIDTH 1500`](https://github.com/arduino-libraries/Servo/blob/4970d615a13f4c0d08026ee361cc8a01974924a2/src/Servo.h#L82)

This causes a lot of confusion<sup name="a2">[2](#f2)</sup>, especially since [the docs say `write(90)` should correspond to the mid-point] (https://www.arduino.cc/en/Reference/ServoWrite); in actuality, it results in a call to `writeMicroseconds(1472)`<sup name="a3">[3](#f3)</sup>.

This change adjusts the defaults to align with R/C standards. Specifically,

- `write(0)` now corresponds to the standard min pulse width of 1000µs.
- `write(90)` now corresponds to the standard zero point pulse width, and aligns with the library's `DEFAULT_PULSE_WIDTH` variable.
- `write(180)` now corresponds to the standard max pulse width of 2000µs.

Tested on an Arduino Uno with a [Tower Pro Micro Servo SG90](http://www.ee.ic.ac.uk/pcheung/teaching/DE1_EE/stores/sg90_datasheet.pdf), and a [Parallax Feedback 360° High-Speed Servo](https://parallax.com/sites/default/files/downloads/900-00360-Feedback-360-HS-Servo-v1.2.pdf).

---

<a name="f1" href="#a1">1</a>: For example, http://www.ee.ic.ac.uk/pcheung/teaching/DE1_EE/stores/sg90_datasheet.pdf

<a name="f2" href="#a2">2</a>: For instance:

 - julianduque/beaglebone-io#54
 - arduino-libraries#3
 - https://toolguyd.com/oscilloscope-arduino-servo-pwm-signal-mistakes/
 - https://makezine.com/2014/04/23/arduinos-servo-library-angles-microseconds-and-optional-command-parameters/

I also see a _lot_ of posts on https://forum.arduino.cc about this.

<a name="f3" href="#a3">3</a>: There is actually no way to set a standard servo to the zero-point using `write(angle)`; the closest you can get is `write(92)`, for a pulse of 1504µs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants