Back to All Blog Posts
Tutorial EltroNerd Engineering

Arduino 6-DOF Robotic Arm with ESP32 and PCA9685 via Bluetooth

This guide demonstrates how to control a 6-degree-of-freedom (6-DOF) robotic arm using an ESP32 microcontroller and PCA9685 PWM driver. The arm's movements are controlled wirelessly via Bluetooth. The system utilizes the Adafruit PCA9685 library to control multiple servos through I2C communication, enabling smooth and efficient operation of the robotic arm's joints. The project provides step-by-step instructions for wiring and programming the ESP32, along with code to control each arm joint via Bluetooth commands.

Description:

In this tutorial, we walk through the process of setting up a 6-DOF robotic arm using an ESP32 microcontroller and a PCA9685 PWM driver. This setup allows you to control each joint of the robotic arm (base, shoulder, elbow, wrist pitch, wrist roll, and gripper) remotely via Bluetooth.

The key components used in this setup include:

  • ESP32: The main controller that communicates with the servos through the PCA9685 module.
  • PCA9685: A 16-channel PWM driver that can control up to 16 servos via I2C communication.
  • MG996R servos: Used to control the movement of the arm's joints.
  • HC-05 Bluetooth module: Provides wireless communication, allowing the user to send commands from a smartphone or other Bluetooth-enabled device.

The tutorial explains the wiring, libraries required, and step-by-step code to control the robotic arm. The arm's movement is made possible by sending commands such as 'w', 's', 'a', 'd' for shoulder and base adjustments, and more for other joints. The code uses I2C communication to control the servos through the PCA9685, and Bluetooth commands adjust the positions of each servo.

By following this guide, you can build a robotic arm that can be easily controlled using a Bluetooth terminal app on your smartphone, offering a hands-on experience in robotics and wireless control systems.

Explanation of the Code Logic:

  1. Libraries:

    • Wire.h is used for I2C communication, and Adafruit_PCA9685.h is used to control the PCA9685 driver.
    • SoftwareSerial.h is used for Bluetooth communication, allowing the ESP32 to communicate with an external Bluetooth module like HC-05/HC-06.
  2. Pin Definitions:

    • The SDA and SCL pins for I2C are set to 21 and 22, respectively (these are default pins for ESP32).
    • The Bluetooth is connected using SoftwareSerial to pins 10 (RX) and 11 (TX).
  3. Servo Control:

    • The servos are connected to the PCA9685 driver. The setPWM() function of the PCA9685 library is used to set the PWM signals for each servo. Each servo is assigned a specific channel (e.g., BASE_SERVO_CHANNEL, SHOULDER_SERVO_CHANNEL, etc.).
  4. Bluetooth Communication:

    • The Bluetooth module listens for incoming data. The commands (e.g., 'a', 'd', 'w', 's', etc.) are sent by the user from a Bluetooth terminal.
    • Based on the received command, the respective joint of the arm is moved by adjusting the servo position by 5 degrees at a time.
  5. Servo Control Logic:

    • Each joint of the robotic arm is controlled by a servo. The angles for the servos are constrained between 0° and 180°.
    • The servos are mapped to the PWM values that correspond to their angle using map(). The values range from 0 to 4095 (for the 12-bit resolution of the PCA9685).
  6. Smooth Movement:

    • After each movement, a small delay(10) is introduced to allow the servos to move smoothly before the next command is processed.

Core Components Used

  • Adafruit_PCA9685.h: used to control the PCA9685 driver.

  • SoftwareSerial.h: Enables serial communication on digital pins (used for Bluetooth).

  • 6 MG996R servos for different joints of the robotic arm:

    • Base rotation
    • Shoulder
    • Elbow
    • Wrist pitch
    • Wrist roll
    • Gripper
  • Bluetooth module (HC-05/HC-06) connected to pins 10 (RX) and 11 (TX).


Code Flow Overview

1. Initialization (setup() function)

bluetooth.begin(9600);
Serial.begin(9600);
  • Initializes communication with both Bluetooth and the Serial Monitor.
// Initialize I2C for PCA9685  
  Wire.begin(SDA_PIN, SCL_PIN);  
  pca9685.begin();  
  pca9685.setPWMFreq(60); // Set frequency to 60 Hz (common for servos)

moveAll();

  • All servos are set to the default mid-point (90°) to ensure neutral positioning.

2. Main Loop (loop() function)

if (bluetooth.available()) {
  char command = bluetooth.read();
  Serial.print("Received: ");
  Serial.println(command);
  handleCommand(command);
  moveAll();
}
  • The program continuously waits for input from the Bluetooth module.

  • When a character is received:

    • It's printed to the Serial Monitor.
    • It's passed to the handleCommand() function to interpret which joint to move.
    • After the update, moveAll() is called to apply the new positions to all servos.

3. Command Handling (handleCommand() function)

switch (cmd) {
  case 'a': basePos = constrain(basePos - 5, 0, 180); break;
  case 'd': basePos = constrain(basePos + 5, 0, 180); break;
  ...
}
  • This function maps each received command to a specific joint movement.
  • The angle is increased or decreased in steps of 5 degrees.
  • constrain() ensures that angles never go below 0 or above 180, which are the servo’s safe limits.

Command Mappings:

Command Joint Action
a/d Base Rotate left/right
w/s Shoulder Up/down
q/e Elbow Up/down
r/f Wrist pitch Up/down
t/g Wrist roll Left/right
y/h Gripper Open/close

 


4. Servo Movement (moveAll() function)

pca9685.setPWM(GRIPPER_SERVO_CHANNEL, map(gripperPos, 0, 180, 0, 4095));  
...
delay(10);
  • Each servo is updated with the current angle value.
  • A slight delay (10 ms) ensures smooth transitions and avoids servo jitter.

Final Setup:

  1. Hardware Connections:

    • The PCA9685 should be connected to the ESP32 via I2C.
    • The MG996R servos should be connected to the PCA9685.
    • The HC-05 Bluetooth module should be connected to the ESP32 for wireless communication.
  2. Testing:

    • After uploading the code to the ESP32, connect the Bluetooth module to a Bluetooth terminal app (e.g., on a smartphone).
    • Use the app to send commands like 'a', 'd', 'w', etc., and watch the robotic arm's movements correspond to each command.

Final Code

c
#include <Wire.h>
#include <Adafruit_PCA9685.h>
#include <SoftwareSerial.h>

// I2C pins
#define SDA_PIN 21
#define SCL_PIN 22

// Bluetooth setup
SoftwareSerial bluetooth(10, 11);  // RX, TX for Bluetooth module

// PCA9685 setup
Adafruit_PCA9685 pca9685;

// Servo positions
int basePos = 90;
int shoulderPos = 90;
int elbowPos = 90;
int wristPitchPos = 90;
int wristRollPos = 90;
int gripperPos = 90;

// Servo channels on PCA9685 (0-15)
#define BASE_SERVO_CHANNEL 0
#define SHOULDER_SERVO_CHANNEL 1
#define ELBOW_SERVO_CHANNEL 2
#define WRIST_PITCH_SERVO_CHANNEL 3
#define WRIST_ROLL_SERVO_CHANNEL 4
#define GRIPPER_SERVO_CHANNEL 5

void setup() {
  // Start Serial Communication
  Serial.begin(115200);
  bluetooth.begin(9600);

  // Initialize I2C for PCA9685
  Wire.begin(SDA_PIN, SCL_PIN);
  pca9685.begin();
  pca9685.setPWMFreq(60); // Set frequency to 60 Hz (common for servos)

  // Set all servos to initial position (neutral)
  moveAll();
}

void loop() {
  if (bluetooth.available()) {
    char command = bluetooth.read(); // Read command from Bluetooth
    Serial.print("Received: ");
    Serial.println(command);

    // Handle the command
    handleCommand(command);

    // Update servo positions
    moveAll();
  }
}

void handleCommand(char cmd) {
  // Handle each command by adjusting servo positions
  switch (cmd) {
    case 'a': basePos = constrain(basePos - 5, 0, 180); break;
    case 'd': basePos = constrain(basePos + 5, 0, 180); break;
    case 'w': shoulderPos = constrain(shoulderPos + 5, 0, 180); break;
    case 's': shoulderPos = constrain(shoulderPos - 5, 0, 180); break;
    case 'q': elbowPos = constrain(elbowPos + 5, 0, 180); break;
    case 'e': elbowPos = constrain(elbowPos - 5, 0, 180); break;
    case 'r': wristPitchPos = constrain(wristPitchPos + 5, 0, 180); break;
    case 'f': wristPitchPos = constrain(wristPitchPos - 5, 0, 180); break;
    case 't': wristRollPos = constrain(wristRollPos - 5, 0, 180); break;
    case 'g': wristRollPos = constrain(wristRollPos + 5, 0, 180); break;
    case 'y': gripperPos = constrain(gripperPos + 5, 0, 180); break;
    case 'h': gripperPos = constrain(gripperPos - 5, 0, 180); break;
  }
}

void moveAll() {
  // Set all servo positions by sending PWM values to the corresponding channels
  pca9685.setPWM(BASE_SERVO_CHANNEL, map(basePos, 0, 180, 0, 4095));
  pca9685.setPWM(SHOULDER_SERVO_CHANNEL, map(shoulderPos, 0, 180, 0, 4095));
  pca9685.setPWM(ELBOW_SERVO_CHANNEL, map(elbowPos, 0, 180, 0, 4095));
  pca9685.setPWM(WRIST_PITCH_SERVO_CHANNEL, map(wristPitchPos, 0, 180, 0, 4095));
  pca9685.setPWM(WRIST_ROLL_SERVO_CHANNEL, map(wristRollPos, 0, 180, 0, 4095));
  pca9685.setPWM(GRIPPER_SERVO_CHANNEL, map(gripperPos, 0, 180, 0, 4095));
  delay(10);  // Delay to allow for smoother movement
}

Try this project in your browser

Compile code and simulate hardware output with EltroNerd Cloud IDE.

Launch Cloud IDE