đŸƒđŸŒ

Kinetic Sculpture

Kinetic Sculpture

Wiring up 1 MicroServo or Servo HS-318(?)

// Working code to move 1 servo

#include "Servo.h"      // include the servo library

Servo servoMotor;       // creates an instance of the servo object to control a servo
int servoPin = 9;       // Control pin for servo motor
// time when the servo was last updated, in ms
long lastMoveTime = 0;  

void setup() {
  Serial.begin(9600);       // initialize serial communications
  servoMotor.attach(servoPin);  // attaches the servo on pin 9 to the servo object

} 
 
void loop()
{
  int analogValue = analogRead(A0); // read the analog input
  Serial.println(analogValue);      // print it
 
  // if your sensor's range is less than 0 to 1023, you'll need to
  // modify the map() function to use the values you discovered:
  int servoAngle = map(analogValue, 0, 1023, 0, 179);

  // move the servo using the angle from the sensor every 20 ms:
  if (millis() - lastMoveTime > 20) {
    servoMotor.write(servoAngle);
    lastMoveTime = millis();
  }
}

Next steps:

  • Book office hours with Rios OR Rozin (✅), to put in lights, what would I need to do?
    • This board can also be used with LEDs.
  • Solder in a capacitor if:
    • Driving more than 4–6 servos at once.
    • Servos move all at the same time.
    • What value?

      • 100”F per servo (minimum)
      • So for 3 servos: ~330”F is fine (470”F is safer)
      • For 16 servos: 1000–2200”F (6.3V or higher)
      Servos
      Suggested Cap Formula
      Result
      16
      n × 100”F (base rule)
      1600 ”F minimum
      16
      High stability scenario
      2200–4700 ”F preferred

⚡ Bonus: Power Budget for 16 SG90s

From your specs:

  • Running current per servo ≈ 400–500 mA
  • Stall current ≈ 1.3–1.6A
  • Let’s assume real-world peak ~0.8A × 16 servos = 12.8A peak draw

Your 5V 10A supply is close, but you may want to:

  • Be cautious with simultaneous full-speed startup
  • OR eventually upgrade to 5V 15A if you hit instability
💡

Get 360Âș servos of up to 6V. Look up attaching motors to fabric. Motor to fabric fasteners

💡

Can I chain 2 boards using an Arduino Nano or do I need to get an Uno?

image

I2C scanner sketch

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
  while (!Serial); // for Nano 33 IoT
  Serial.println("\nI2C Scanner");
}

void loop() {
  byte error, address;
  int nDevices = 0;

  for (address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
      nDevices++;
    }
  }

  if (nDevices == 0)
    Serial.println("No I2C devices found");

  delay(2000);
}

Voltage readings:

PCA9685 Side VCC & GND: has 3.3V Blue Terminal (top right on board): has 5.21V

_____ I2C is not getting recognized. Serial print at setup is not happening. Replaced PCA9685 board, which seemed to be fried && the motor.

Code working for 1 servo:

#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  120 // This is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  480 // 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 50 // 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(10); // Slow it down for visual feedback
  }

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

code working to sweep through 6 servos

// #include <Wire.h>

// void setup() {
//   Wire.begin();
//   Serial.begin(9600);
//   while (!Serial); // for Nano 33 IoT
//   Serial.println("\nI2C Scanner");
// }

// void loop() {
//   byte error, address;
//   int nDevices = 0;

//   for (address = 1; address < 127; address++ ) {
//     Wire.beginTransmission(address);
//     error = Wire.endTransmission();

//     if (error == 0) {
//       Serial.print("I2C device found at address 0x");
//       if (address < 16)
//         Serial.print("0");
//       Serial.println(address, HEX);
//       nDevices++;
//     }
//   }

//   if (nDevices == 0)
//     Serial.println("No I2C devices found");

//   delay(2000);
// }


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

// Microservo: Min 80 and max 540 works.
#define SERVOMIN  80 // This is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  540 // 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 50 // Analog servos run at ~50 Hz updates

// our servo # counter
uint8_t servonum = 0; // Don’t rely on servonum as a global iterator unless you’re sweeping/testing. Prefer direct channel numbers, descriptive names, or functions for reusable, modular code.
// ✅ Create helper functions like setServoAngle(channel, degrees) if you want to work in angles.

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() {
  Serial.println("Moving servos...");

  for (uint8_t i = 0; i < 6; i++) {
    // Sweep each servo one at a time
    for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
      pwm.setPWM(i, 0, pulselen);
      delay(10);
    }

    delay(250); // Optional pause between servos
  }

  // 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(10); // Slow it down for visual feedback
  // }

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

General tips:

  • Use short, thick wires between your 5V power supply and the PCA9685’s blue terminal block.
  • Make sure GND is shared between Arduino and power supply.

Issues:

  • Motor 15 was overheating severely and making a noise 
 Why? Is there something in the way I wrote the code that makes the PWM to keep getting sent there?!
  • ‣

    Solution with Serial Input working and fail safe

  • Next let's work on eliminating sudden movements of any kind from our SG90 MicroServos.
  • Right now the servos are going from initial position to final angle position very fast and then back to initial position in a relatively controlled speed. I want the speed to be controlled all throughout. Changing the value of delay didn't solve the issue of very sudden movements. Maybe it's just that we're consistently moving servos from 0Âș to 180Âș and therefore they have to go back to 0 to start the loop.

    → The fix: Store the last position and ease into new motion

    • Track the last known pulse for each servo
    • Start the sweep from that last position
    • Only move incrementally from where it last left off
    ‣

    Working Code

💡

GPT: Let me know if you'd like to:

  • Set angle like angle 3 90
  • Control speed like speed 5
  • Create simple motion sequences Could make a grid and then if MediaPipe is on that spot, move fabric there. Need to get a map of positions though.
‣

Full code for 2 boards and 31 servos

Code from PCA9685 library without any changes

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

SG90 9G MicroServo datasheet:

[Applications] SG90 9G servo motor, compatible with RC helicopters, micro robots, robot arm and ships etc. Support various remote control toys and Arduino project [Interface type] This SG90 servo is compatible with JR & Futaba interface [Operating speed] 0.12 second/ 60degree ( 4.8V no load), 0.1 second / 60degree ( 6.0V no load), Max rotation angle:180 degrees [Stalling torque] 17.5oz/in (1kg/cm); Dead zone width: 7 microseconds; Amplifier type: analog controller; Working voltage: 4.8V6.0V [Reminder] The starting current of the analog servo motor must be greater than 1A. SG90 Servo is an analog servo and needs to continuously provide PMW signal to work normally

Pulse Signal PWM: 50Hz/O.5-2.5ms No-load Speed: 0.09S/60 degree@4.8V Operating Voltage Range: 4.8V ~6.0V Operating Temperature Range: -25°c ~ 70°c

Mechanical Specification: ●Limit angle: 200°± 1° ●Weight: 9 ± 1 g ●Connector wire gauge: 28 PVC ●Connector wire length: 250 ± 5 mm ●Spline: 21T ●Horn type: Cross,star and bar-type ●Excessive play: ≀0.5°

Control Specification: ●Control system: Change the pulse width ●Amplifier type: Analog controller ●Operating trave: l120°±10° (900→2100 ÎŒsec) ●Left&Right travelling Angle deviation: ≀ 6° ●Centering deviation: ≀ 1° ●Neutral position: 1500 ÎŒsec ●Dead band width: 7 ÎŒsec ●Rotating direction: Counter Clockwise (900→2100ÎŒsec) ●Pulse width range: 900→2100 ÎŒsec ●Maximum travel: About 120° (900→2100 ÎŒsec)

Wire Layout: ●Electrical Specification (Function of the Performance): Operating ●speed (at no load): 0.09±0.01 sec/60°(4.8V ) 0.08±0.01 sec/60°(6V) ●Running current (at no load): 400±30mA (4.8V ) 500±30mA(6V) ●Stall torque (at locked): 2.0±0.20kg·cm (4.8V ) 2.2±0.20kg·cm(6V) ●Stall current (at locked): 1300±40mA (4.8V ) 1600±50mA(6v) ●Idle current (at stopped): 6±1mA (4.8V ) 6±1mA(6v) ●Running life(at no load): >350000 Turns(4.8V ) >320000 Turns(4.8V )

“If you want, I can now show you a simple step-by-step calibration guide to figure out SERVOMIN and SERVOMAX just by uploading sketches and watching your servo move (no special equipment).”

Working code with 5V 15A

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

// Setup for two PCA9685 boards
Adafruit_PWMServoDriver pwm1 = Adafruit_PWMServoDriver(0x40, Wire);
Adafruit_PWMServoDriver pwm2 = Adafruit_PWMServoDriver(0x41, Wire);

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

// Calibrated pulse length counts (ticks)
#define SERVOMIN  180 // This is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  440 // This is the 'maximum' pulse length count (out of 4096)

// Calibrated pulse lengths in microseconds
#define USMIN  900 // This is the rounded 'minimum' microsecond length based on the minimum pulse of updated 180
#define USMAX  2100 // This is the rounded 'maximum' microsecond length based on the maximum pulse of updated 440
#define SERVO_FREQ 50 // Analog servos run at ~50 Hz updates

// our servo # counter
// uint8_t servonum = 0; // NOT BEING USED!

unsigned long lastAliveTime = 0;


void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // Wait for Serial to connect (needed for native USB boards)
  }
  Serial.println("Starting 32 servo sweep...");

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

  // Initialize both boards
  pwm1.begin();
  pwm1.setOscillatorFrequency(27000000);
  pwm1.setPWMFreq(SERVO_FREQ);

  pwm2.begin();
  pwm2.setOscillatorFrequency(27000000);
  pwm2.setPWMFreq(SERVO_FREQ);

  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()
  // Check if 1 second passed
  if (millis() - lastAliveTime >= 1000) {
    Serial.println("I'm alive");
    lastAliveTime = millis();
  }

  // === Sweep servos on Board 1 ===
  for (uint8_t servoNum = 0; servoNum < 16; servoNum++) {
    Serial.print("Board 1 - Sweeping servo ");
    Serial.println(servoNum);

    // Sweep up
    for (uint16_t pulselen = SERVOMIN; pulselen <= SERVOMAX; pulselen++) {
      pwm1.setPWM(servoNum, 0, pulselen);
      delay(5); // adjust delay for speed
    }

    delay(500); // hold at max // MIGHT BE TOO LONG TO BE INTERACTIVE

    // Sweep down
    for (uint16_t pulselen = SERVOMAX; pulselen >= SERVOMIN; pulselen--) {
      pwm1.setPWM(servoNum, 0, pulselen);
      delay(5);
    }

    delay(500); // hold at min
  }

  // === Sweep servos on Board 2 ===
  for (uint8_t servoNum = 0; servoNum < 16; servoNum++) {
    Serial.print("Board 2 - Sweeping servo ");
    Serial.println(servoNum);

    // Sweep up
    for (uint16_t pulselen = SERVOMIN; pulselen <= SERVOMAX; pulselen++) {
      pwm2.setPWM(servoNum, 0, pulselen);
      delay(5); // adjust delay for speed
    }

    delay(500); // hold at max

    // Sweep down
    for (uint16_t pulselen = SERVOMAX; pulselen >= SERVOMIN; pulselen--) {
      pwm2.setPWM(servoNum, 0, pulselen);
      delay(5);
    }

    delay(500); // hold at min
  }
}

Sweeping through all at the same time on 5V 10A

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

// Setup for two PCA9685 boards
Adafruit_PWMServoDriver pwm1 = Adafruit_PWMServoDriver(0x40, Wire);
Adafruit_PWMServoDriver pwm2 = Adafruit_PWMServoDriver(0x41, Wire);

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

// Calibrated pulse length counts (ticks)
#define SERVOMIN  180 // This is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  440 // This is the 'maximum' pulse length count (out of 4096)

// Calibrated pulse lengths in microseconds
#define USMIN  900 // This is the rounded 'minimum' microsecond length based on the minimum pulse of updated 180
#define USMAX  2100 // This is the rounded 'maximum' microsecond length based on the maximum pulse of updated 440
#define SERVO_FREQ 50 // Analog servos run at ~50 Hz updates

// our servo # counter
// uint8_t servonum = 0; // NOT BEING USED!

unsigned long lastAliveTime = 0;

// === Manual Servo Selection ===
// Add servo numbers you want to move in these arrays (0-15)
uint8_t board1_servos[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; // Example → change/add/remove as desired
uint8_t board2_servos[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; // Example → change/add/remove as desired

// Size of each list (automatically calculated)
const uint8_t board1_servo_count = sizeof(board1_servos) / sizeof(board1_servos[0]);
const uint8_t board2_servo_count = sizeof(board2_servos) / sizeof(board2_servos[0]);

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // Wait for Serial to connect (needed for native USB boards)
  }
  Serial.println("Starting 32 servo sweep...");

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

  // Initialize both boards
  pwm1.begin();
  pwm1.setOscillatorFrequency(27000000);
  pwm1.setPWMFreq(SERVO_FREQ);

  pwm2.begin();
  pwm2.setOscillatorFrequency(27000000);
  pwm2.setPWMFreq(SERVO_FREQ);

  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()
  // Check if 1 second passed
  if (millis() - lastAliveTime >= 1000) {
    Serial.println("I'm alive");
    lastAliveTime = millis();
  }

    // Sweep up
  for (uint16_t pulselen = SERVOMIN; pulselen <= SERVOMAX; pulselen++) {
    Serial.print("Sweeping up - Pulse: ");
    Serial.println(pulselen);

    // Board 1 selected servos
    for (uint8_t i = 0; i < board1_servo_count; i++) {
      pwm1.setPWM(board1_servos[i], 0, pulselen);
    }

    // Board 2 selected servos
    for (uint8_t i = 0; i < board2_servo_count; i++) {
      pwm2.setPWM(board2_servos[i], 0, pulselen);
    }

    delay(5);
  }

  delay(500); // hold at max

  // Sweep down
  for (uint16_t pulselen = SERVOMAX; pulselen >= SERVOMIN; pulselen--) {
    Serial.print("Sweeping down - Pulse: ");
    Serial.println(pulselen);

    // Board 1 selected servos
    for (uint8_t i = 0; i < board1_servo_count; i++) {
      pwm1.setPWM(board1_servos[i], 0, pulselen);
    }

    // Board 2 selected servos
    for (uint8_t i = 0; i < board2_servo_count; i++) {
      pwm2.setPWM(board2_servos[i], 0, pulselen);
    }

    delay(5);
  }

  delay(500); // hold at min

  // // === Sweep servos on Board 1 ===
  // for (uint8_t servoNum = 0; servoNum < 16; servoNum++) {
  //   Serial.print("Board 1 - Sweeping servo ");
  //   Serial.println(servoNum);

  //   // Sweep up
  //   for (uint16_t pulselen = SERVOMIN; pulselen <= SERVOMAX; pulselen++) {
  //     pwm1.setPWM(servoNum, 0, pulselen);
  //     delay(5); // adjust delay for speed
  //   }

  //   delay(500); // hold at max // MIGHT BE TOO LONG TO BE INTERACTIVE

  //   // Sweep down
  //   for (uint16_t pulselen = SERVOMAX; pulselen >= SERVOMIN; pulselen--) {
  //     pwm1.setPWM(servoNum, 0, pulselen);
  //     delay(5);
  //   }

  //   delay(500); // hold at min
  // }

  // // === Sweep servos on Board 2 ===
  // for (uint8_t servoNum = 0; servoNum < 16; servoNum++) {
  //   Serial.print("Board 2 - Sweeping servo ");
  //   Serial.println(servoNum);

  //   // Sweep up
  //   for (uint16_t pulselen = SERVOMIN; pulselen <= SERVOMAX; pulselen++) {
  //     pwm2.setPWM(servoNum, 0, pulselen);
  //     delay(5); // adjust delay for speed
  //   }

  //   delay(500); // hold at max

  //   // Sweep down
  //   for (uint16_t pulselen = SERVOMAX; pulselen >= SERVOMIN; pulselen--) {
  //     pwm2.setPWM(servoNum, 0, pulselen);
  //     delay(5);
  //   }

  //   delay(500); // hold at min
  // }
}
‣

Next working session: