Pi4J / pi4j-v1

DEPRECATED Java I/O library for Raspberry Pi (GPIO, I2C, SPI, UART)
http://www.pi4j.com
Apache License 2.0
1.31k stars 448 forks source link

PCA9685 servo question: setAlwaysOff sometimes makes the servo jump to zero position #540

Closed Sciss closed 3 years ago

Sciss commented 3 years ago

Is this a known issue, and is there a work-around. In something like one out of ten cases, I have some servos that when I call setAlwaysOff in PCA9685GpioProvider.java - so that a robot arm can rest and doesn't have to use force when "parked" - the servo moves rapidly into the "minimal" angle, I guess below the minimum pwm period defined, rather than turning the PWM off. This makes it impossible to use the feature to turn motors off in an unsupervised installation.

The code would be

            device.write(PCA9685A_LED0_ON_L  + 4 * channel, (byte) 0x00);
            device.write(PCA9685A_LED0_ON_H  + 4 * channel, (byte) 0x00);
            device.write(PCA9685A_LED0_OFF_L + 4 * channel, (byte) 0x00);
            device.write(PCA9685A_LED0_OFF_H + 4 * channel, (byte) 0x10); // set bit 4 to high

I don't know if the result can be achieved more safely? I saw this problem with different units of the servos used, so it's definitely not a problem of an individual servo, rather either a problem of the servo type as such, of the PCA9685 board, or the software. Thanks!

Sciss commented 3 years ago

Could it be that the "independent" write commands cause problems? I'm not familiar with i2c, but how does it prevent that the last PWM cycle is "cut off"? or does it always complete one cycle? would using a single void write(byte[] buffer) instead of four individual byte writes help? I see there is an internal locking mechanism, but not sure this has anything to do with ensuring the entire command is atomically received by the chip.

Sciss commented 3 years ago

the other thing I see in setFrequency is that there is some "sleep" mode, e.g.

            oldMode = device.read(PCA9685A_MODE1);
            int newMode = (oldMode & 0x7F) | 0x10; // sleep
            device.write(PCA9685A_MODE1     , (byte) newMode); // go to sleep
            device.write(PCA9685A_PRESCALE  , (byte) prescale);
            device.write(PCA9685A_MODE1     , (byte) oldMode);
            Thread.sleep(1);
            device.write(PCA9685A_MODE1     , (byte) (oldMode | 0x80));

perhaps something like this "sleep" should be used instead of setAlwaysOff?

Sciss commented 3 years ago

I can confirm that simply setting PCA9685A_LED0_OFF_H's bit 4 to high works - the motors are off, and there are no erratic movements. So definitely asynchronously setting the lower bits to zero first is wrong.