Search Code

Control 32 Servos over Wi-Fi Using ESP32 and PCA9685 via Desktop or Mobile Phone V5

Control 32 Servos over Wi-Fi Using ESP32 and PCA9685 via Desktop or Mobile Phone V5

In this tutorial, we will learn how to control 32 servo motors using the PCA9685 PWM controller module connected to the ESP32 over Wi-Fi. This setup allows you to control each servo individually or all at once through a web interface accessible from a desktop or mobile device. By the end of this guide, you will have a fully functional system capable of managing multiple servos with ease.

PCA9685 module-0

The PCA9685 module provides a simple way to control multiple servos using PWM signals, while the ESP32 handles the Wi-Fi communication and web server functionality. You'll be able to adjust the angle of each servo motor through a user-friendly interface that displays buttons for each servo. For a visual reference, be sure to check out the video (in video at 00:00).

Hardware Explained

This project primarily uses the ESP32 microcontroller and the PCA9685 PWM controller. The ESP32 is a powerful microcontroller with built-in Wi-Fi capabilities, making it ideal for IoT projects. The PCA9685 is a 16-channel PWM controller that can be cascaded to control up to 64 servos. It communicates with the ESP32 via the I2C protocol, allowing multiple controllers to be connected and addressed individually.

Each PCA9685 module has a default I2C address of 0x40. When using multiple modules, you can change their addresses by soldering specific jumpers. For instance, the first module can be at 0x40, the second at 0x41, and so on. This cascading capability enables the control of many servos without needing additional pins on the ESP32.

Connecting two PCA9685 board

As shown in the image bove, for PCA9685 board 2 (on the left), make sure to solder that path to set I2C address so it is different from board 1 (on the righ). 

Datasheet Details

Manufacturer Adafruit
Part number PCA9685
Logic/IO voltage 2.3 V to 5.5 V
Supply voltage 2.3 V to 5.5 V
Output current (per channel) 25 mA
Peak current (per channel) 100 mA
PWM frequency guidance 40 Hz to 1000 Hz
Input logic thresholds 0.3 Vcc (low) / 0.7 Vcc (high)
Voltage drop / RDS(on) / saturation 0.5 V max
Thermal limits 125 °C max
Package TSSOP-28
Notes / variants Up to 64 servos with cascading

 

  • Ensure proper power supply (5V, 2A recommended).
  • Use pull-up resistors on SDA and SCL lines if needed.
  • Check I2C addresses carefully when using multiple PCA9685 modules.
  • Consider heat-sinking for high-power applications.
  • Test servos individually to ensure correct operation.

Common I2C pin mappings: SDA = GPIO 21, SCL = GPIO 22.

Wiring Instructions

ESP32 wiring for PCA99685 for 32 sevo motors

To wire the PCA9685 and ESP32, start by connecting the power and ground. Connect the positive terminal (V+) of the PCA9685 to the 5V output of your power supply. Connect the ground (GND) of the PCA9685 to the ground of the ESP32. Make sure both devices share a common ground.

Next, for the I2C communication, connect the SDA pin of the PCA9685 to GPIO 21 on the ESP32 and the SCL pin to GPIO 22. If you are using multiple PCA9685 modules, ensure that they are appropriately addressed by soldering the A0 jumper to change the address of the second module to 0x41, and so forth. After that, you can connect the servo motors to the PWM output pins of the PCA9685 as required.

Code Examples & Walkthrough

The code begins by including necessary libraries and initializing two PCA9685 boards. The identifiers such as maximumServo and servoAngle define the number of servos and the current angle, respectively.

Adafruit_PWMServoDriver board1 = Adafruit_PWMServoDriver(0x40);
Adafruit_PWMServoDriver board2 = Adafruit_PWMServoDriver(0x41);
int maximumServo = 32; // how many servos are connected

This excerpt shows the initialization of the PCA9685 objects with their respective addresses. The variable maximumServo sets the total number of servos that can be controlled.

In the setup() function, the boards are initialized, and the Wi-Fi connection is established. The initial position of all servos is set using a loop.

void setup() {
  board1.begin();
  board2.begin();  
  board1.setPWMFreq(60);  // Analog servos run at ~60 Hz updates
  board2.setPWMFreq(60);
  //initial position of all servos
  for(int i=0; i < maximumServo; i++) {
    if(i < 16) {
      board1.setPWM(i, 0, angleToPulse(allServoPosition[i]));
    } else {
      board2.setPWM(i-15, 0, angleToPulse(allServoPosition[i]));
    }
  }
}

This code configures the PCA9685 boards and sets the PWM frequency. It initializes all servos to their starting positions, which are defined in the allServoPosition array.

Finally, the main loop handles incoming client requests to control the servos based on user input.

void loop() {
  server.handleClient();
  if (buttonPushed && (servoNumber >= 0 && servoNumber < maximumServo)) {
    if (servoNumber < 16) {
      board1.setPWM(servoNumber, 0, angleToPulse(allServoPosition[servoNumber]));
    } else {
      board2.setPWM(servoNumber-15, 0, angleToPulse(allServoPosition[servoNumber]));
    }
  }
  buttonPushed = 0;
}

This loop continuously processes client requests and updates the servo positions based on the button pressed. The variable buttonPushed is reset after each action to ensure proper control.

For the complete code, please refer to the section below the article.

Demonstration / What to Expect

Once everything is set up, you should be able to control each servo individually or all at once from a web interface. You can adjust the angles of the servos by clicking the buttons corresponding to each servo on your device. The system should respond quickly, but keep in mind that power limitations can affect performance (in video at 14:30).

Common pitfalls include ensuring that the I2C addresses are correctly assigned and that sufficient power is provided to the servos. If the servos are not responding, double-check your wiring and connections.

Video Timestamps

  • 00:00 Start
  • 01:19 Introduction
  • 02:32 Setting I2C address
  • 05:07 Wiring explained
  • 07:44 Preparing Arduino IDE for ESP32
  • 09:53 Arduino code explained
  • 25:49 Demonstration on desktop
  • 31:52 Demonstration on mobile phone

Images

PCA9685 module-0
PCA9685 module-0
PCA9685 module-1
PCA9685 module-1
PCA9685 module-2
PCA9685 module-2
PCA9685 module-3
PCA9685 module-3
PCA9685 module
PCA9685 module
ESP32-2
ESP32-2
Connecting two PCA9685 board
Connecting two PCA9685 board
ESP32 wiring for PCA99685 for 32 sevo motors
ESP32 wiring for PCA99685 for 32 sevo motors
881-new- PCA9685 Video V5, Arduino ESP32 Code : Controlling all 32 servo motor over WiFi
Language: C++
/*
* Original PCA9685 Module library source: https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library
 * 
 * This is the Arduino code PCA6985 32 channel servo controller
 * to control 32 Servo Motors over WiFi using ESP32 MCU
 *  get this code and wiring from for this video:  http://robojax.com/RJT365

 * Watch video for this code:  https://youtu.be/bvqfv-FrrLM
 * 
 * Related Videos
V4 video of PCA9685 32 Servo with ESP32: https://youtu.be/JFdXB8Za5Os
V3 video of PCA9685 how to control 32 Servo motors https://youtu.be/6P21wG7N6t4
V2 Video of PCA9685 3 different ways to control Servo motors: https://youtu.be/bal2STaoQ1M
V1 Video introduction to PCA9685 to control 16 Servo  https://youtu.be/y8X9X10Tn1k
 *  
 * Written by Ahmad Shamshiri for Robojax Video channel www.Robojax.com
 * Date: Feb 17, 2020, in Ajax, Ontario, Canada



or make donation using PayPal http://robojax.com/L/?id=64

 *  * This code is "AS IS" without warranty or liability. Free to be used as long as you keep this note intact.* 
 * This code has been download from Robojax.com
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
////////////////////// PCA9685 settings started
#include <Wire.h>

#include <Adafruit_PWMServoDriver.h>

// called this way, it uses the default address 0x40
Adafruit_PWMServoDriver board1 = Adafruit_PWMServoDriver(0x40);
Adafruit_PWMServoDriver board2 = Adafruit_PWMServoDriver(0x41);
int maximumServo = 32;//how many servos are connected

// 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!
// Watch video V1 to understand the two lines below: http://youtu.be/y8X9X10Tn1k
#define SERVOMIN  125 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  575 // this is the 'maximum' pulse length count (out of 4096)

int servoAngle =0;
int servoStep = 10;

int stepDelay = 50;// 50 milliseconds
int servoAngleMin =0;
int servoAngleMax = 180;

// minimum angle of each servo     
int allServoMin[]={
      0,    0,    0,    0,    0,    0,    0,    0,// 1 to 8
      0,    0,    0,    0,    0,    0,    0,    0,//9 to 16
      0,    0,    0,    0,    0,    0,    0,    0,//17 to 24
      0,    0,    0,    0,    0,    0,    0,    0};//25 to 32                   

//maximum value of each servo
int allServoMax[]={
      180,    180,    180,    180,    180,    180,    180,    180,// 1 to 8 
      180,    180,    180,    180,    180,    180,    180,    180,//9 to 16
      180,    180,    180,    180,    180,    180,    180,    180,//17 to 24 
      180,    180,    180,    180,    180,    180,    180,    180};//25 to 32 

// initial position of servos      
int allServoPosition[] ={
      0,    0,    0,    0,    0,    0,    0,    0,// 1 to 8
      0,    0,    0,    0,    0,    0,    0,    0,//9 to 16
      0,    0,    0,    0,    0,    0,    0,    0,//17 to 24
      0,    0,    0,    0,    0,    0,    0,    0};//25 to 32               

int servoNumber = 100;//servo to move
int buttonPushed =0;
int allServo =0;

void handleServo();//this is prototype of function defined at the end of this code
int angleToPulse(int ang); //this is prototype of function defined at the end of this code
////////////////////////PCA9685 ended

#include "PCA9684_32Servo_ESP32.h"
  
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>

const char *ssid = "Robojax";
const char *password = "YouTube2020";

WebServer server(80);

const int led = 13;



/////////////////////////////////////
void handleRoot() {

String HTML_page = pageHeader_p1; 

 if(allServo)
 {
 HTML_page.concat("<div class=\"btn\"><a class=\"angleButton colorAll\"  href=\"/servo?do=stop\">Stop Servo</a></div>");  

 }else{
 HTML_page.concat("<div class=\"btn\"><a class=\"angleButton colorAll\"  href=\"/servo?do=all\">All Servo</a></div>");    
 }

 for (int i=0; i < maximumServo; i++)
 {

  HTML_page.concat("<div class=\"btn\"><a class=\"angleButton colorBtn\"  href=\"/servo?move="); 
  HTML_page.concat(i);  
  HTML_page.concat("\">SRV ");      
  HTML_page.concat(i+1);  
  HTML_page.concat(" </a></div>");  
 }
 
 HTML_page.concat("</body>\n</html>");
 server.send(200, "text/html", HTML_page);
}

void handleNotFound() {
  digitalWrite(led, 1);
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";

  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }

  server.send(404, "text/plain", message);
  digitalWrite(led, 0);
}


void setup() {
  board1.begin();
  board2.begin();  
  board1.setPWMFreq(60);  // Analog servos run at ~60 Hz updates
  board2.setPWMFreq(60);
  //initial position of all servos
  for(int i=0; i < maximumServo; i++) {
    if(i < 16)
    {
      board1.setPWM(i, 0, angleToPulse(allServoPosition[i]) ); 
    }else{
      board2.setPWM(i-15, 0, angleToPulse(allServoPosition[i]) );       
    }
  }//for end
    
    Serial.begin(115200);
    Serial.println("32 channel Servo test!");
	

  //Servo control using ESP32 from Robojax.com

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");
  
    
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());


  if (MDNS.begin("robojaxESP32")) {
    Serial.print("MDNS responder started at http://");
    Serial.println("robojaxESP32");
  }

  server.on("/", handleRoot);
  server.on("/servo", HTTP_GET, handleServo);  

  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("HTTP server started"); 	
}

void loop() {
	server.handleClient();
  if(allServo ){

    for( int angle =servoAngleMin; angle <= servoAngleMax; angle +=servoStep){
      for(int i=0; i<16; i++)
        {      
              
            board2.setPWM(i, 0, angleToPulse(angle) );
            board1.setPWM(i, 0, angleToPulse(angle) );
        }
        delay(stepDelay);
    }
  
// robojax PCA9865 32 channel Servo control
  delay(100);        

  }//if pushed
  if(false){
  Serial.print("Servo #");
  Serial.print (servoNumber);
  Serial.print(" Angle ");
  Serial.println(allServoPosition[servoNumber]);
  }

      
  if( buttonPushed && (servoNumber >=0 && servoNumber < maximumServo) ){
    if(servoNumber < 16)
    {
      board1.setPWM(servoNumber, 0, angleToPulse(allServoPosition[servoNumber]) ); 
    }else{
      board2.setPWM(servoNumber-15, 0, angleToPulse(allServoPosition[servoNumber]) );       
    }
  }

   buttonPushed =0; 
}



/*
 * handleServo()
 * update the buttonPushed varialbe
 * returns nothing
 * Written by Ahmad Shamshiri on Dec 29, 2019
 * www.Robojax.com
 * http://youTube.com/robojaxTV
 */
void handleServo() {
  if(server.arg("do") == "all" )
  {
    allServo =1;

  }else{
    allServo =0;    
  }
   int servoNumberRequested= server.arg("move").toInt();
   
  if(servoNumberRequested >=0 && servoNumberRequested < maximumServo)
  {

    buttonPushed = 1;
    if(allServoPosition[servoNumberRequested] == allServoMin[servoNumberRequested] ) {
     allServoPosition[servoNumberRequested] = allServoMax[servoNumberRequested];
    }else{
     allServoPosition[servoNumberRequested] = allServoMin[servoNumberRequested]; 
    }
    servoNumber =servoNumberRequested;
  }
  
  handleRoot();
}//handleServo() end


/*
 * angleToPulse(int ang)
 * gets angle in degree and returns the pulse width
 * also prints the value on seial monitor
 * written by Ahmad Shamshiri for Robojax, Robojax.com
 */
int angleToPulse(int ang){
   int pulse = map(ang,0, 180, SERVOMIN,SERVOMAX);// map angle of 0 to 180 to Servo min and Servo max 
   //Serial.print("Angle: ");Serial.print(ang);
   //Serial.print(" pulse: ");Serial.println(pulse);
   return pulse;
}

Things you might need

Resources & references

Files📁

No files available.