mattjlewis / diozero

Java Device I/O library that is portable across Single Board Computers and microcontrollers. Tested with Raspberry Pi, Odroid C2, BeagleBone Black, Next Thing CHIP, Asus Tinker Board and Arduinos / Pico. Supports GPIO, I2C, SPI as well as Serial communication. Also known to work with Udoo Quad.
https://www.diozero.com
MIT License
261 stars 59 forks source link

PCA9685 has troubles with certain servo angles #178

Closed EAGrahamJr closed 1 year ago

EAGrahamJr commented 1 year ago

Basically, when using a servo that's not using the default trim, there are certain angles the servo will not rotate to.

This is pretty weird, so I'm going to also attach the code to reproduce. The output is pretty explanatory: the code rotates the servo through all 180 degrees. If a certain angle cannot be "set" after 5 tries, the "failed" angle is output.

System: Raspberry Pi 3B+, I2C speed=400000, using the SparkFun 'hat (same chip)

# Using SG90 trim
» java -jar servomatic.jar
Starting sweep
Failed to set angle to 35
Failed to set angle to 53
Failed to set angle to 78
Failed to set angle to 96
Failed to set angle to 134
Failed to set angle to 152
Failed to set angle to 177
Sweep complete - rolling back

This reproduces for any value of the trim where the delta_us is > 900 (the angles "not hit" differ based on how the delta changes).

EAGrahamJr commented 1 year ago
import com.diozero.api.ServoDevice;
import com.diozero.api.ServoTrim;
import com.diozero.devices.PCA9685;

/**
 * This is weird...
 */
public class ServoAdvance {
    public static void main(String[] args) throws Exception {
        try (PCA9685 servoController = new PCA9685()) {
            ServoDevice servo = new ServoDevice.Builder(0)
                    // does not work
                    //  .setTrim(ServoTrim.TOWERPRO_SG90)
                    //  .setTrim(newServoTrim(1500,901))
                    //  .setTrim(newServoTrim(1450,901))

                    // works
//                    .setTrim(ServoTrim.DEFAULT)
                    .setTrim(new ServoTrim(1450, 900))
                    .setFrequency(servoController.getBoardPwmFrequency())
                    .setDeviceFactory(servoController)
                    .build();

            servo.setAngle(0f);
            Thread.sleep(1000);
            System.out.println("Starting sweep");
            for (int i = 0; i <= 180; i++) {
                boolean done = false;
                float current = 0f;
                for (int j = 0; j < 5; j++) {
                    servo.setAngle(i);
                    Thread.sleep(5);
                    current = servo.getAngle();
                    done = servo.getAngle() == i;
                    if (done) break;
                }
                if (!done) {
                    System.out.printf("Failed to set angle %d - current is %.02f%n", i, current);
                }
            }
            System.out.println("Sweep complete - rolling back");
            for (int i = 180; i >= 0; i--) {
                servo.setAngle(i);
                Thread.sleep(5);
            }
            System.out.println("Rollback complete");
        }
    }
}
mattjlewis commented 1 year ago

Must be rounding errors - it would be interesting to see what servo.getAngle() returned, I assume it is one out.

EAGrahamJr commented 1 year ago

Yep, one off:

Failed to set angle 35 - current is 34.00
Failed to set angle 53 - current is 52.00
Failed to set angle 78 - current is 77.00
Failed to set angle 96 - current is 95.00
Failed to set angle 134 - current is 133.00
Failed to set angle 152 - current is 151.00
Failed to set angle 177 - current is 176.00

Note that I do not get this when using straight GPIO or a different MCU, so it's likely isolated to this board?

EAGrahamJr commented 1 year ago

I compared the set values from the Adafruit CircuitPython code and it does look like there is a rounding issue.

34 --> 33.54952640873334
35 --> 34.93658693209183
36 --> 35.86129394766415
EAGrahamJr commented 1 year ago

The aforementioned PR didn't quite get it fixed. There's still a few misses.