101100 / pca9685

PCA9685 I2C 16-channel PWM/servo driver Node.js module
MIT License
29 stars 7 forks source link

when two servos given simultaneous instructions, madness ensues #12

Closed kaeverens closed 7 years ago

kaeverens commented 7 years ago

my setup: servo on channel 0, controls a turret on a model tank servo on channel 8, controls the elevation of the cannon

When I run the below code with staggered timeout offsets (offset1=2000, offset2=4000), it all works perfectly. But if I run with the /same/ offsets (4000 for both, for example), the servos appear to lose track of what they were doing.

I'm not familiar with TypeScript, so am not certain I'll find anything in your code, but what it /appears/ to be is a variable from one servo's controller has been set with global scope and is being referenced by the other servo's controller by accident.

  pca9685 = new Pca9685Driver({
    i2c: i2cBus.openSync(1),
    address: 0x40,
    frequency: 50,
    debug: true
  }, function() {
    console.log("Initialization done"); 
  });
  function laserTurretBase(deg) {
    var servoNumber=0;
    if (deg<0 || deg>180) {
      return;
    }
    pca9685.setPulseLength(servoNumber, 1500+Math.ceil((deg-90)/180*1499));
    clearTimeout(timers.laserTurretBase);
    timers.laserTurretBase=setTimeout(function() {
      pca9685.setPulseRange(servoNumber, 0, -1);
    }, 3000);
  } 
  function laserTurretElevation(deg) {
    var servoNumber=8;
    if (deg<0 || deg>180) {
      return;
    }
    pca9685.setPulseLength(servoNumber, 1500+Math.ceil((deg-90)/180*1499));
    clearTimeout(timers.laserTurretElevation);
    timers.laserTurretElevation=setTimeout(function() {
      pca9685.setPulseRange(servoNumber, 0, -1);
    }, 3000);
  }
  laserTurretBase(90);
  laserTurretElevation(90);
  var offset1=4000, offset2=4000;
  setTimeout(function() {
    laserTurretElevation(45);
    setTimeout(function() {
      laserTurretElevation(135);
        setTimeout(function() {
          laserTurretElevation(90);
        }, 4000);
    }, 4000);
  }, offset1);
  setTimeout(function() {
    laserTurretBase(45);
    setTimeout(function() {
      laserTurretBase(135);
        setTimeout(function() {
          laserTurretBase(90);
        }, 4000);
    }, 4000);
  }, offset2);
kaeverens commented 7 years ago

I think the issue is that you are using asynchronous write commands in the I2C library. These may be overlapping, somehow?

I will try converting your JS to use synchronous calls instead to see if that works out any better

101100 commented 7 years ago

@kaeverens Thanks for reporting the issue!

Switching to synchronous calls would solve the problem, but would block the rest of the program whenever you performed a servo command. Instead, the library should ensure that only one servo command is sent at a time. I've pushed version 4.0.0 (as a beta) to npm. Can you see if it works in all cases? You will need to force the version since it is marked as beta:

npm install pca9685@4.0.0
kaeverens commented 7 years ago

Thanks - I feel that this /is/ an asynchronous issue, but your fix didn't do the trick.

I've made a small video showing the issue. In it, you see two servos, which first do some small movements individually, and then finally try to do one single thing simultaneously. You can also see your debug statements on the laptop in the video.

https://www.youtube.com/watch?v=4I1nyUG9nlQ&feature=youtu.be

I've attached the script I used to create the movements. bot3.js.zip

Dennislampert commented 7 years ago

Hey, just testing this code on one servo, using a led indicating the power, awesome! But, it stops with max power. I want to know how I can stop one channel so it doesn't use any power? If I set the puls to 0 it is max light any way, how can i fix this? Thanks!

101100 commented 7 years ago

@kaeverens

The new code was supposed to line up the commands separately, but I used the wrong stream operator. Can you try out version 4.0.1?

@Dennislampert

Version 4.0.1 also has two new methods: channelOff and channelOn to turn the power completely off or on 100%. These are called automatically by setDutyCycle if the percent is 0 or lower or 100% or higher.

kaeverens commented 7 years ago

Thanks - I've tried 4.0.1. It still acts just the same as in the video. I used the new channelOff/On functions as well(thanks) instead of setting pulse range (n, 0, 0), so it's clearer in the debug what's going on, but at this point, I feel this is somehow in the module itself.

Latest debug:

  pca9685 Reseting PCA9685 +0ms
  pca9685 Turning off all channels +52ms
  pca9685 Setting PWM frequency to 50 Hz +47ms
  pca9685 Pre-scale value: 121 +3ms
  pca9685 Setting prescale to: 121 +8ms
  pca9685 Restarting controller +20ms
  pca9685 Setting PWM channel, channel: 1, pulseLength: 1126, onStep: 0 +7ms
  pca9685 Setting PWM channel, channel: 1, onStep: 0, offStep: 230 +3ms
  pca9685 Turning off channel: 1 +3s
  pca9685 Setting PWM channel, channel: 1, pulseLength: 1875, onStep: 0 +502ms
  pca9685 Setting PWM channel, channel: 1, onStep: 0, offStep: 383 +2ms
  pca9685 Turning off channel: 1 +3s
  pca9685 Setting PWM channel, channel: 1, pulseLength: 1126, onStep: 0 +500ms
  pca9685 Setting PWM channel, channel: 1, onStep: 0, offStep: 230 +1ms
  pca9685 Turning off channel: 1 +3s
  pca9685 Setting PWM channel, channel: 8, pulseLength: 1126, onStep: 0 +498ms
  pca9685 Setting PWM channel, channel: 8, onStep: 0, offStep: 230 +1ms
  pca9685 Turning off channel: 8 +3s
  pca9685 Setting PWM channel, channel: 8, pulseLength: 1875, onStep: 0 +497ms
  pca9685 Setting PWM channel, channel: 8, onStep: 0, offStep: 383 +1ms
  pca9685 Turning off channel: 8 +3s
  pca9685 Setting PWM channel, channel: 8, pulseLength: 1126, onStep: 0 +499ms
  pca9685 Setting PWM channel, channel: 8, onStep: 0, offStep: 230 +2ms
  pca9685 Turning off channel: 8 +3s
  pca9685 Setting PWM channel, channel: 1, pulseLength: 1875, onStep: 0 +500ms
  pca9685 Setting PWM channel, channel: 1, onStep: 0, offStep: 383 +1ms
  pca9685 Setting PWM channel, channel: 8, pulseLength: 1875, onStep: 0 +2ms
  pca9685 Setting PWM channel, channel: 8, onStep: 0, offStep: 383 +2ms
  pca9685 Turning off channel: 1 +3s
  pca9685 Turning off channel: 8 +1ms
  pca9685 Setting PWM channel, channel: 1, pulseLength: 1500, onStep: 0 +501ms
  pca9685 Setting PWM channel, channel: 1, onStep: 0, offStep: 306 +1ms
  pca9685 Setting PWM channel, channel: 8, pulseLength: 1500, onStep: 0 +1ms
  pca9685 Setting PWM channel, channel: 8, onStep: 0, offStep: 306 +1ms
  pca9685 Turning off channel: 1 +3s
  pca9685 Turning off channel: 8 +2ms

one thing to add - at the end, channel 1 stays on, holding its position (and buzzing loudly...), even though you can see clearly that I've used the channelOff to turn 1 off.

kaeverens commented 7 years ago

I'll give Adafruit's C library a try after work today to see if it works any better for me. If so, then the only difference I can see is that Adafruit's library sets the bytes as one block, whereas your code sets them in separate i2c commands. https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library/blob/master/Adafruit_PWMServoDriver.cpp

This can be achieved in your own code by using the I2C writeI2cBlock function, but there is little reason to make the change if this issue is only affecting me and it eventually turns out not to be a software issue after all.

101100 commented 7 years ago

@kaeverens

I attempted to reproduce your error at home with two servos and couldn't. Did the error you show in the video happen every time for you? I was running on a Beaglebone Black with node 0.10. What board and node version are you using?

In the meantime, I've tried switching to writeI2cBlock for 4.0.2 so we can see if that works better. Can you try it to see if that works now?

kaeverens commented 7 years ago

it happens every time.

I'm using a CHIP. node v6.6.0

I'll update the pca9685 package now and try again.

kaeverens commented 7 years ago

with 4.0.2, nothing happens with the servos at all. the usual debug messages appear, but nothing physical happens.

I went back to 4.0.1 and tried again and it started up again.

one new thing I noticed that suggests maybe this is hardware - at the end of the sequence of moves, the turret servo (servo 1, say) started turning continuously, which then started twisting the cables of servo 2. I pulled servo 2 from the PCA9685, and servo1 suddenly stopped turning.

so I tried something I don't remember trying (although it seems so obvious...) - try running the entire sequence with just one servo plugged in. it worked perfectly. the servo did what it was supposed to do, even in the part where two servos were supposed to be simultaneously active. I tried with the other servo, and it too performed as it was supposed to.

I think this is no longer a software issue!

Dennislampert commented 7 years ago

I can confirm from testing 4.0.1:

Above 100% (eg duty cycle = 1.1) The servo dies, but led's is running on max light and debug says:

pca9685 Setting PWM channel, channel: 3, dutyCycle: %f, onStep: 1.3 +319ms 0
  pca9685 Turning on channel: 3 +1ms
  pca9685 Setting PWM channel, channel: 3, dutyCycle: %f, onStep: 1.35 +2s 0
  pca9685 Turning on channel: 3 +1ms

0% or less (eg duty cycle = 0 or -5) The servo dies and the led dies and debug says:

pca9685 Setting PWM channel, channel: 1, dutyCycle: %f, onStep: 0 +14s 
pca9685 Turning off channel: 1 +0ms
pca9685 Setting PWM channel, channel: 1, dutyCycle: %f, onStep: -0.05 +25ms 0
pca9685 Turning off channel: 1 +0ms

Just wanted to share my debug result.

This is super good for me and solves my problem to turn one channel down completely!

Thanks alot! :+1:

101100 commented 7 years ago

@kaeverens @Dennislampert Thanks for testing!

Tonight I will revert to the methods used in 4.0.1 and will tidy up everything before a final release. In the meantime, I've unpublished 4.0.2 and re-tagged 4.0.1 as a beta version.

@kaeverens That is unusual that one servo affects the other. Have you tried different servo ports?

@Dennislampert There is something wrong with the debug message, though. That %f should hold the duty cycle value.

kaeverens commented 7 years ago

problem solved. It turns out this was a hardware issue after all.

If I understand it right, it's like this: Having multiple servos running simultaneously causes problems because the current going through the servos cause the ground to rise momentarily. This causes the pulses to the servos to become less distinct, making the servos act erratically.

The solution was to make the grounding more solid. I had been grounding through a breadboard, but that's apparently to be avoided (not sure why yet), so I switched my wires so the PCA9685 was grounding through the Chip.

I think we can close this now. I learned a lot, and I hope I helped improve your library even if it was not broken in the first place!