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

2+ servos can't handle .to method frequently #1340

Closed
under24 opened this issue May 13, 2017 · 13 comments
Closed

2+ servos can't handle .to method frequently #1340

under24 opened this issue May 13, 2017 · 13 comments

Comments

@under24
Copy link

under24 commented May 13, 2017

I have a nodejs server and 3 servos.

I init the servos like this:

var servo1 = new five.Servo({
  address: 0x40,
  controller: "PCA9685",
  pin: 8,
});
...

I push values via socketio from front-end to the back-end where I set the values like this:

.on('change-angle', function(data) {
    servo1.to(data.value);
    servo2.to(data.value);
    servo3.to(data.value);
})

My problem is that the servos get laggy and unresponsive (in other words they begin to pile up the queue).
The issue can be observed only with 2+ servos. If I use it with 1 servo there's no delay and it's perfectly responsive no matter how frequently I push new data from the front-end.

P.S. I also tried like this and it doesn't help:

servo1.stop();
servo1.to(data.value);
servo2.stop();
servo2.to(data.value);
servo3.stop();
servo3.to(data.value);
@under24 under24 changed the title Multiple servos can't handle .to frequently 2+ servos can't handle .to method frequently May 13, 2017
@dtex
Copy link
Collaborator

dtex commented May 13, 2017

Hi @under24

Can you tell us more about your project? What environment is your host server on? Can you share the complete code? A video of the behavior could also be really helpful.

I'm sure you should be able to control that many servos. I have a project that uses 18 servos and each updates about 60 times per second (with a whole ton of other stuff going on). I will admit that I haven't done it with a PCA9685 so maybe this is an issue with I2C communications.

@under24
Copy link
Author

under24 commented May 13, 2017

Hello @dtex.

Here's a video: https://youtu.be/TJq6XeNXwZY
Listen to the servos making noise. With 1 servo the update rate is perfect and as soon as I add one more servo it starts lagging horribly.

@dtex
Copy link
Collaborator

dtex commented May 14, 2017

I've got the code running, but need to run to the lab an pick up a couple of servos so I can test. Assuming I get the same behavior the first thing I'm going to try is bypassing the PCA9685 and see if the behavior still occurs (just hooking the servos up to pins on the Arduino).

@under24
Copy link
Author

under24 commented May 16, 2017

@dtex do you have any news on it?

@dtex
Copy link
Collaborator

dtex commented May 16, 2017

Sorry, no I haven't been able to get to the servos but I will be at the lab tomorrow.

@under24
Copy link
Author

under24 commented May 16, 2017

@dtex actually, I've already found a solution for it.
I2C seems to be unable to transfer data faster than 10 ms which creates this bottleneck.
Anything < 10ms will lag horribly.
The solution is very simple, you just throttle it down like this:

    let prevDate = 0;
    
    const setValue = (value) => {
      a.to(value);
      b.to(value);
      c.to(value);
    }
    
    client.on('angle-changed', function(data) {
      if (+new Date() - prevDate >= 15) { // i use 15 ms just to let it breathe in between 
        setValue(data.value);
        prevDate = +new Date();
      }
    });

@under24 under24 closed this as completed May 16, 2017
@dtex
Copy link
Collaborator

dtex commented May 16, 2017

Oh wow, that's really interesting. I had a project where I was seeing the same kind of servo jankiness on a Tessel 2 with a PCA9685. I had assumed that the T2 just couldn't handle the IK calculations and that was my limiting factor, but I was updating 18 servos and each one was updated with a separate I2C message...

This changes my assumption about what the problem was.

I propose that we rename and reopen this issue.

@under24 under24 reopened this May 18, 2017
@under24
Copy link
Author

under24 commented May 18, 2017

@dtex What do i rename it to? The problem seems to be in the low computing power of the controllers or in the transfer speed of the I2C protocol. I am not really sure in any of this

Here's my solution for this kind of bottleneck with servos:

servo.js.zip
I've added a new property for the class Servo:
throttle: [Boolean] [default: false]

So you just add it to every servo instance and push the data as frequently as you want and it will automatically be throttled down to 20ms per data batch allowing your expander boards to work perfectly responsive.

servo init example:

  let servo1 = new five.Servo({
    address: 0x40,
    controller: "PCA9685",
    pin: 8,
    invert: true,
    throttle: true // <-- this one
  });

  let servo2 = new five.Servo({
    address: 0x40,
    controller: "PCA9685",
    range: [0, 180],
    pin: 15,
    throttle: true // <-- this one
  });

@under24
Copy link
Author

under24 commented Jun 24, 2017

Hello @dtex.

Could you please review/include my solution to the framework? I think this is pretty neat if you work with sockets and multiple servos. I have the file attached in the prev post.

Or do I make a pull request and do it myself?

@dtex
Copy link
Collaborator

dtex commented Jun 24, 2017

I'm not sure this is something that should be fixed in Johnny-Five. It might belong in firmtata.js or firmata itself. To know where requires truly knowing where the bottleneck actually lies.

Are you certain it is a limitation of I2C, the PCA9685, or maybe it's something higher up the chain?

My assumption would be that socket-io is much more of a bottleneck than I2C. Do you have the same problem when the commands are not sent over socket-io?

Can we solve the problem in a better way. For example, should we update the Servos (note the plural) class and add the ability to pipe down multiple values in a single I2C data push instead of a separate push for each servo spaced out over time?

@under24
Copy link
Author

under24 commented Jun 27, 2017

@dtex
It's not socket for sure. Because I only changed the back-end and it started working the way it should.
Plus I tested the performance and it's blazing fast.

How do we implement it with multiple values? How do we know when to expect multiple values? Do we send a batch and then collect a new batch in a span of some ms? As I understood it's the same idea that I had with my throttler. Can you sketch some pseudo code?
Also, can you direct me where I can play with multiple values in one I2C data push?

@dtex
Copy link
Collaborator

dtex commented Jun 27, 2017

To see if writing multiple values in one push even works I would build a test case that requires only firmata.js and sends an I2C message that writes 64 consecutive register values (16 servos * 4 per servo) at once vs just the 4 we need for a single servo.

This is the part of Johnny-Five that handles communication with the PCA-9685 and you can see here where we are only writing to four register values. You can crib code from here and modify it to support writing for N servos at once (this.io is an instance of firmata.js in your case).

If all that works, we would need to modify Johnny-Five Servos so that it has an awareness of situations where writing multiple servo values at once makes sense. I could even see adding a command to the firmata protocol to support this for non-I2C connected servos. We could modify Servos.to so it would handle this new message style and not just delegate to Servo.to.

This is not a trivial investigation so I totally understand if you don't want to take it on. I'm interested in getting to the bottom of it for my own projects, but I can't imagine that I will get around to it anytime soon.

Practical Alternatives:
A) Are you only controlling two servos? Ditch the PCA-9685 and just wire directly to the Arduino. Like I said earlier, I've done this with 18 servos at a time (on a mega) with great results.
B) Monkey-patch J5 with your fix
C) Create a wrapper for Servo that throttles write speeds

@dtex
Copy link
Collaborator

dtex commented Jan 11, 2018

Hi @under24 ,

One of the things that came out of this discussion was the realization that it would be helpful to send multiple servo writes in a single i2c message. It's one of those features that will require a fair amount of work and we just haven't gotten to it yet. Rather than leave this languishing as an open issue we have created a Requested Features page and added the request for bulk servo writes there.

@dtex dtex closed this as completed Jan 11, 2018
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