Issues with PCA9685 and Servos (Arduino Uno)

Hi all,

I am having quite a bit of trouble attempting to use a PCA9685 to control a servo. While my project calls for 28 servos, I am struggling to get even a single servo to sweep.

Hardware: I am using an Arduino Uno as the microcontroller along with Miuzei SG90 servo motors. The PCA9685 is an Adafruit clone from Hiletgo .

Wiring:

Here is a link to some pictures of my wiring: Imgur: The magic of the Internet

In case those images are not clear, I have VCC <-> 5v, SCL<->A5, SDA<->A4, GND<->GND. Additionally, I have a 5v5a additional power supply screwed into the terminal block (to power the servos).

Code:

I am using the Adafruit PWM ServoDriverlibrary. Documentation Link

I have tried both the "servo" sample example code as well as custom written code, neither of which seem to do the trick.


    /***************************************************
  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

 
  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  150 // This is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  600 // This is the 'maximum' pulse length count (out of 4096)
#define USMIN  600 // This is the rounded 'minimum' microsecond length based on the minimum pulse of 150
#define USMAX  2400 // This is the rounded 'maximum' microsecond length based on the maximum pulse of 600
#define SERVO_FREQ 60 // Analog servos run at ~50 Hz updates

// our servo # counter
uint8_t servonum = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("8 channel Servo test!");

  pwm.begin();
  /*
   * In theory the internal oscillator (clock) is 25MHz but it really isn't
   * that precise. You can 'calibrate' this by tweaking this number until
   * you get the PWM update frequency you're expecting!
   * The int.osc. for the PCA9685 chip is a range between about 23-27MHz and
   * is used for calculating things like writeMicroseconds()
   * Analog servos run at ~50 Hz updates, It is importaint to use an
   * oscilloscope in setting the int.osc frequency for the I2C PCA9685 chip.
   * 1) Attach the oscilloscope to one of the PWM signal pins and ground on
   *    the I2C PCA9685 chip you are setting the value for.
   * 2) Adjust setOscillatorFrequency() until the PWM update frequency is the
   *    expected value (50Hz for most ESCs)
   * Setting the value here is specific to each individual I2C PCA9685 chip and
   * affects the calculations for the PWM update frequency.
   * Failure to correctly set the int.osc value will cause unexpected PWM results
   */
  pwm.setOscillatorFrequency(27000000);
  pwm.setPWMFreq(SERVO_FREQ);  // Analog servos run at ~50 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. It's not precise!
void setServoPulse(uint8_t n, double pulse) {
  double pulselength;
 
  pulselength = 1000000;   // 1,000,000 us per second
  pulselength /= SERVO_FREQ;   // Analog servos run at ~60 Hz updates
  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 input seconds to us
  pulse /= pulselength;
  Serial.println(pulse);
  pwm.setPWM(n, 0, pulse);
}

void loop() {
  // Drive each servo one at a time using setPWM()
  Serial.println(servonum);
  for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
    pwm.setPWM(servonum, 0, pulselen);
  }

  delay(500);
  for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) {
    pwm.setPWM(servonum, 0, pulselen);
  }

  delay(500);

  // Drive each servo one at a time using writeMicroseconds(), it's not precise due to calculation rounding!
  // The writeMicroseconds() function is used to mimic the Arduino Servo library writeMicroseconds() behavior.
  for (uint16_t microsec = USMIN; microsec < USMAX; microsec++) {
    pwm.writeMicroseconds(servonum, microsec);
  }

  delay(500);
  for (uint16_t microsec = USMAX; microsec > USMIN; microsec--) {
    pwm.writeMicroseconds(servonum, microsec);
  }

  delay(500);

  servonum++;
  if (servonum > 7) servonum = 0; // Testing the first 8 servo channels
}

Custom Code

#include <Wire.h>
    #include <Adafruit_PWMServoDriver.h>
    
    Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
    
    #define FREQUENCY 60
    #define SERVOMIN 122
    #define SERVOMAX 590
    
    void setup() {
      Serial.begin(9600);
      Serial.println("Moving the damn servo!");
      pwm.begin();
      pwm.setPWMFreq(60);
    }
    
    void loop() {
    
    int angle = 180;
    int pulse = map(angle,0,180,SERVOMIN,SERVOMAX);
    
      pwm.setPWM(0, 0, pulse);
      delay(1000);
    }

Things I've tried:

  • Confirming voltage of both board and servos using multimeter
  • Verified servos work independently (swapped 12 in total)
  • Changed all wires 3x to ensure no faulty connections
  • Swapped Arduino Uno for a spare to ensure it was not the microcontroller
  • Confirming I2C device was found at 0x40 using I2C Scanner
  • Tried 6 total PCA9685 boards (on third set now)
  • Calibrated the SERVOMIN and SERVOMAX to my servo specifications
  • Using a breadboard, wired up a singular LED to try GPIO functionality, which was successful

If anyone has any ideas, please let me know - I am out of troubleshooting options on my end.

Hi @bluegoo

Welcome to the forum and thank you for posting a detailed question first try!

I haven't used the PCA9685 before, but I do have some thoughts.

Maybe "setPWMFreq"? You define it as 60hz, although the library's example side-note says 50hz.
Possibly the "setPWM" function variables?

Did you try just doing "pwm.setPWM(0,0,180);"?

Are you saying that the servo never moves with either code? If that's not it, what exactly does it do?

Note that your custom code will only move the servo if it isn't already at position 180.

Steve

1 Like

Thank you for the reply.

I played around with both sets of code setting the frequency to both 50hz and 60hz - no luck. For what it's worth, the datasheet for the servos lists the frequency as 50hz.

For the pwm.setPWM function, you need to specify a pulse width modulation value between 0 and 4095 which is why I was using the map() function to re-map to a standard 0-180 degree arc. To your question though, I tried both the 0 to 4095 PWM values and 0 to 180 degree values (using the map function) with no luck.

Thank you for your reply.

Correct - neither code block moves the servo arm at all. I did try hooking the servo up directly to an Arduino pin and used the myservo.write() function to verify that the servo itself is working.

For the custom code I actually had the same thought, but I have played around with random values between 0-180 degrees with no luck.

I'm thinking that the issue may be with how I am calculating the pulse width for my particular servos, although I am not sure exactly where my math may be incorrect.

I ran another test just to verify if the pulse width modulation from the breakout board was working, but instead of a Servo I used an LED. Adafruit outlines this on their website, but you can use the setPWM() function as a GPIO by fully turning on/off the PWM which I was able to do.

Servo Details
Here is the datasheet for my servos: Link
PWM Period: 20ms @50HZ
Duty Cycle 1-2ms (1ms = -90 degrees, 1.5ms = 0 degrees, and 2ms = 90 degrees)
Pulse Width Range (~500-~2500µs)

If its operating at 50hz, that means that there are 20000µs. Given that this is a 12bit PWM controller, there are 4096 increments each of which is 4.88µs in length.
ServoMin should be = 500µs/4.88µs = ~102
ServoMax should be =2500µs/4.8µs =~512

But you have the frequency set to 60Hz which changes the min/max values.

Steve

Did this issue ever get resolved? I'm having the same trouble!
Servos work fine on their own using Arduino but as soon as the PCA9685 is connected in they don't work, even when connected up correctly and using example codes off the internet still no luck.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.