Servo Pulse Width - Getting it Right

I am new to Arduino and have written a sketch using the Adafruit_PWMServoDriver library to control two servos.

The intent here is to replace a Pololu Maestro servo controller that did not quite fit our needs.

Using the Pololu, the script allowed the target pulse widths to be entered in μs. I've tried to mimic the target pulse widths that were used in the Pololu here in my sketch. The program executes properly on a button press and steps through the desired sequence, but the pulse width values that are output are scaled about 4.6X higher than what I've used in my sketch. I used an oscilloscope and pulse width meters to measure the pulse width.

For example pwm.setPWM(0, 0, 1491); results in a measured pulse width of 6925μs

There's obviously a units or scaling problem with the setup or library, but the solution eludes me.

Any servo-savants out there that see what's not obvious to me ?

/*************************************************** 
  This is an example for our Adafruit 16-channel PWM & Servo driver
  Servo test - this will drive 8 servos, one after the other on the
  first 8 pins of the PCA9685

  Pick one up today in the adafruit shop!
  ------> http://www.adafruit.com/products/815
  
  These drivers use I2C to communicate, 2 pins are required to  
  interface.

  Adafruit invests time and resources providing this open source code, 
  please support Adafruit and open-source hardware by purchasing 
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.  
  BSD license, all text above must be included in any redistribution
 ****************************************************/

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

// called this way, it uses the default address 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
// you can also call it with a different address you want
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x41);
// you can also call it with a different address and I2C interface
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40, &Wire);

// Depending on your servo make, the pulse width min and max may vary, you 
// want these to be as small/large as possible without hitting the hard stop
// for max range. You'll have to tweak them as necessary to match the servos you
// have!
#define SERVOMIN  600  // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  2400 // this is the 'maximum' pulse length count (out of 4096)

// our servo # counter
uint8_t servonum = 0;

const int buttonPin = 2; //push button attached to this pin

int buttonState = LOW; //this variable tracks the state of the button, low if not pressed, high if pressed

long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers

void setup() {
  Serial.begin(9600);
  Serial.println("Servo Sequence");

  pinMode(buttonPin, INPUT);
  pwm.begin();
  
  pwm.setPWMFreq(50);  // Analog servos run at ~60 Hz updates

  delay(10);
}

// you can use this function if you'd like to set the pulse length in seconds
// e.g. setServoPulse(0, 0.001) is a ~1 millisecond pulse width. its not precise!
void setServoPulse(uint8_t n, double pulse) {
  double pulselength;
  
  pulselength = 1000000;   // 1,000,000 us per second
  pulselength /= 50;   // 50 Hz
  Serial.print(pulselength); Serial.println(" us per period"); 
  pulselength /= 4096;  // 12 bits of resolution
  Serial.print(pulselength); Serial.println(" us per bit"); 
  pulse *= 1000000;  // convert to us
  pulse /= pulselength;
  Serial.println(pulse);
  pwm.setPWM(n, 0, pulse);
}

void driveServos() {
  Serial.println("Sequence Started");
  // Drive each servo one at a time
    //Start Frame0 Y+
  Serial.println("Starting with Y+");
  pwm.setPWM(0, 0, 2244);
  pwm.setPWM(1, 0, 1491);
  delay(1000);
  Serial.println("Pause While Measuring");
    //Stop Servos
  pwm.setPWM(0, 0, 0);
  pwm.setPWM(1, 0, 0);
  delay(5000);
    //Frame1 X-
  Serial.println("Moving to X- Position");
  pwm.setPWM(0, 0, 1857);
  pwm.setPWM(1, 0, 1491);
  delay(4500);
  Serial.println("Pause While Measuring");
    //Stop Servos
  pwm.setPWM(0, 0, 0);
  pwm.setPWM(1, 0, 0);
  delay(8000);
  Serial.println("Moving to Y- Position");
    //Frame2 Y-
  pwm.setPWM(0, 0, 1463);
  pwm.setPWM(1, 0, 1491);
  delay(4500);
  Serial.println("Pause While Measuring");
   //Stop Servos
  pwm.setPWM(0, 0, 0);
  pwm.setPWM(1, 0, 0);
  delay(8000);
    //Frame3 X+
  Serial.println("Moving to X+ Position");
  pwm.setPWM(0, 0, 1053);
  pwm.setPWM(1, 0, 1491);
  delay(4500);
  Serial.println("Pause While Measuring");
    //Stop Servos
  pwm.setPWM(0, 0, 0);
  pwm.setPWM(1, 0, 0);
  delay(8000);
    //Frame4 Z+
  Serial.println("Moving to Z+ Position");
  pwm.setPWM(0, 0, 1074);
  pwm.setPWM(1, 0, 1116);
  delay(4500);
  Serial.println("Pause While Measuring");
    //Stop Servos
  pwm.setPWM(0, 0, 0);
  pwm.setPWM(1, 0, 0);
  delay(9500);
    //Frame5 Z-
  Serial.println("Moving to Z- Position");
  pwm.setPWM(0, 0, 1074);
  pwm.setPWM(1, 0, 1898);
  delay(6500);
  Serial.println("Pause While Measuring");
    //Stop Servos
  pwm.setPWM(0, 0, 0);
  pwm.setPWM(1, 0, 0);
  delay(8000);
    //Return to Start
  Serial.println("Returning to Start Position");
  pwm.setPWM(0, 0, 2244);
  pwm.setPWM(1, 0, 1491);
  delay(10000);
  Serial.println("Sequence Complete");
    //Stop Servos
  pwm.setPWM(0, 0, 0);
  pwm.setPWM(1, 0, 0);
  delay(6500);
  Serial.println("Ready to Start");
}

void loop() {
    //sample the state of the button - is it pressed or not?
  buttonState = digitalRead(buttonPin);

  //filter out any noise by setting a time buffer
  if ( (millis() - lastDebounceTime) > debounceDelay) {

    //if the button has been pressed, drive the servos
    if ( ((buttonState == HIGH)) ) {
      driveServos();
      lastDebounceTime = millis(); //set the current time
    }
  }
}

For a typical servo the pulse length for minimum position (0 degrees) is 544us and max (180) is 2400. Is that what your math produces?

You need to convert the microseconds to another value. The calculation is shown in function setServoPulse(), brief explanation below. Or you could always simply use that in place of setPWM.

With FREQ set to 50Hz that's a 20,000 us cycle length. The 12 bit PWMDriver calculates things in 1/4096ths of that length. So 20,000 * 1491/4096 = around 7000.

Steve

slipstick:
You need to convert the microseconds to another value. The calculation is shown in function setServoPulse(), brief explanation below. Or you could always simply use that in place of setPWM.

With FREQ set to 50Hz that's a 20,000 us cycle length. The 12 bit PWMDriver calculates things in 1/4096ths of that length. So 20,000 * 1491/4096 = around 7000.

Steve

Thanks Steve,

I should have mentioned that I am using a winch servo HS-785HB that has 3.5 revolutions of travel. Eventually I'd like to dial in the positions using degrees.

So I could call setServoPulse(0, 1.491) to obtain a pulse width of 1491 rather than pwm.setPWM ?

otherwise;

I need to convert my 1491 to another value?

Peter

No. Didn't you read the comments in the code? It clearly says the parameter to setServoPulse is in SECONDS. if you want 1491 microseconds that would be (0,0.001491) but the result won't necessarily be very accurate.

You don't say what accuracy you're expecting so I suggest you try using setServoPulse() first to see if it gives you sufficient accuracy.

Bear in mind that most hobby servos like that HS785HB won't respond with anywhere near microsecond accuracy either.

Steve

slipstick:
No. Didn't you read the comments in the code? It clearly says the parameter to setServoPulse is in SECONDS. if you want 1491 microseconds that would be (0,0.001491) but the result won't necessarily be very accurate.

You don't say what accuracy you're expecting so I suggest you try using setServoPulse() first to see if it gives you sufficient accuracy.

Bear in mind that most hobby servos like that HS785HB won't respond with anywhere near microsecond accuracy either.

Steve

Yes, I read the comments but I confused ms and us when I refactored.

These servos seem pretty accurate with our 7:1 gear ratio.

Peter