microrobotics icon

Arduino & ESP32 interfaced with PCA9685

microrobotics | PRO | 11/12/24 08:32:24 AM UTC (Edited) | 0 ⭐ | 14114 👁️ | Never ⏰ | []
Arduino |

11.44 KB

|

Software

|

0 👍

/

0 👎

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
 
/*
 * PCA9685 Servo Controller Code with I2C Scanner
 * Compatible with both Arduino UNO and ESP32.
 *
 * Usage Instructions:
 * - Use the serial monitor to send commands in the following format:
 *   <SPEED:speed_value;ANGLE:channel-angle_pairs;ENABLE:channels;DISABLE:channels>
 *
 * - All settings can be input as a single string.
 * - Parameters:
 *   - SPEED: Set the servo movement speed (delay in ms between steps).
 *     - Example: SPEED:5
 *   - ANGLE: Set the angles of servos on specified channels.
 *     - Provide channel-angle pairs separated by commas.
 *     - Example: ANGLE:0-90,1-45,2-135
 *   - ENABLE: Enable specified channels.
 *     - Provide channels separated by commas.
 *     - Example: ENABLE:0,1,2
 *   - DISABLE: Disable specified channels.
 *     - Provide channels separated by commas.
 *     - Example: DISABLE:3,4,5
 * - Combine multiple parameters using semicolons.
 *   - Example: SPEED:5;ANGLE:0-90,1-45;ENABLE:0,1;DISABLE:2,3
 *
 * Notes:
 * - Channels range from 0 to 15.
 * - Angles range from 0 to 180 degrees.
 * - All parameters are optional; include only what you want to change.
 * - Order of parameters does not matter.
 *
 * Default Behavior:
 * - On startup, only channel 0 is enabled by default; all other channels are disabled.
 * - The servo on channel 0 will automatically sweep back and forth between 0 and 180 degrees.
 */
 
#ifdef ESP32
  #define SERIAL_BAUDRATE 115200
  #define OE_PIN 27
  #define SDA_PIN 21
  #define SCL_PIN 22
#else
  #define SERIAL_BAUDRATE 115200
#endif
 
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
 
// Global variables to store speed and enabled channels
int servoSpeed = 5;           // Default delay between servo movements
bool channelEnabled[16];      // Array to keep track of enabled channels
 
// PCA9685 I2C address
uint8_t pca9685_address = 0x40;  // Default address
 
// Create an instance of the PWM servo driver
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(pca9685_address);
 
// Constants for servo min and max pulse length
#define SERVOMIN 150  // Min pulse length out of 4096
#define SERVOMAX 600  // Max pulse length out of 4096
#define MAX_RETRIES 5
 
void setup() {
  Serial.begin(SERIAL_BAUDRATE);
  delay(1000);  // Allow time for Serial to initialize
  Serial.println("PCA9685 Servo Controller Initialized");
 
  // Initialize I2C
  #ifdef ESP32
    Wire.begin(SDA_PIN, SCL_PIN);
    // Set OE_PIN as output and pull low to enable PWM output
    pinMode(OE_PIN, OUTPUT);
    digitalWrite(OE_PIN, LOW);
  #else
    Wire.begin();
  #endif
 
  // Run I2C Scanner
  scanI2CDevices();
 
  // Initialize all channels as disabled by default
  for (int i = 0; i < 16; i++) {
    channelEnabled[i] = false;
  }
  // Enable channel 0 by default
  channelEnabled[0] = true;
 
  // Initialize the PCA9685
  bool initialized = false;
  for (int i = 0; i < MAX_RETRIES; i++) {
    pwm = Adafruit_PWMServoDriver(pca9685_address);
    pwm.begin();
    pwm.setPWMFreq(60);  // Set the PWM frequency to 60 Hz
    delay(10);  // Short delay to allow settings to take effect
 
    // Read MODE1 register to verify connection
    uint8_t mode1 = readRegister(pca9685_address, 0x00);  // MODE1 register address is 0x00
 
    if (mode1 != 0xFF) {  // 0xFF indicates a failed read
      initialized = true;
      Serial.println("PCA9685 initialized successfully.");
      break;
    } else {
      Serial.print("Attempt ");
      Serial.print(i + 1);
      Serial.println(": PCA9685 initialization failed! Retrying...");
      delay(500);
    }
  }
 
  if (!initialized) {
    Serial.println("Error: PCA9685 initialization failed after multiple attempts! Check I2C address and wiring.");
    while (1);
  }
 
  Serial.println("PWM frequency set to 60 Hz.");
  Serial.println("Enter commands to control the servos in the following format:");
  Serial.println("<SPEED:speed_value;ANGLE:channel-angle_pairs;ENABLE:channels;DISABLE:channels>");
  Serial.println("Example: SPEED:5;ANGLE:0-90,1-45;ENABLE:0,1;DISABLE:2,3");
}
 
void loop() {
  // Automatic movement for enabled servos
  static uint16_t angle = 0;
  static int8_t increment = 1;
 
  // Move enabled servos
  for (uint8_t channel = 0; channel < 16; channel++) {
    if (channelEnabled[channel]) {
      setServoAngle(channel, angle);
    }
  }
 
  angle += increment;
  if (angle >= 180 || angle <= 0) {
    increment = -increment;
  }
 
  delay(servoSpeed);
 
  // Check for serial input
  if (Serial.available() > 0) {
    String input = Serial.readStringUntil('\n');
    input.trim();  // Remove any leading/trailing whitespace
    processCommand(input);
  }
}
 
// Function to scan I2C devices
void scanI2CDevices() {
  Serial.println("Scanning for I2C devices...");
  uint8_t 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.print(address, HEX);
      Serial.println(" !");
      nDevices++;
 
      // If it's the first device found, assume it's the PCA9685
      if (nDevices == 1) {
        pca9685_address = address;
      }
    } else if (error == 4) {
      Serial.print("Unknown error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
  }
 
  if (nDevices == 0) {
    Serial.println("No I2C devices found. Please check your connections.");
  } else {
    Serial.print(nDevices);
    Serial.println(" I2C devices found.");
    Serial.print("Using PCA9685 at address 0x");
    if (pca9685_address < 16)
      Serial.print("0");
    Serial.println(pca9685_address, HEX);
  }
}
 
// Function to read a register from the PCA9685
uint8_t readRegister(uint8_t deviceAddress, uint8_t reg) {
  Wire.beginTransmission(deviceAddress);
  Wire.write(reg);
  if (Wire.endTransmission() != 0) {
    return 0xFF;  // Indicate failure to read
  }
 
  Wire.requestFrom(deviceAddress, (uint8_t)1);
  if (Wire.available()) {
    return Wire.read();
  } else {
    return 0xFF;  // Indicate failure to read
  }
}
 
// Function to process serial commands
void processCommand(String command) {
  command.toUpperCase();  // Convert command to uppercase for case-insensitive comparison
 
  // Split the command into parameters using ';' as the delimiter
  int paramStart = 0;
  int paramEnd = command.indexOf(';', paramStart);
 
  while (paramStart < command.length()) {
    String param;
    if (paramEnd == -1) {
      param = command.substring(paramStart);
      paramEnd = command.length();
    } else {
      param = command.substring(paramStart, paramEnd);
    }
 
    param.trim();  // Remove whitespace
 
    // Process each parameter
    if (param.startsWith("SPEED:")) {
      // Set servo speed
      String valueStr = param.substring(6);
      valueStr.trim();
      int value = valueStr.toInt();
      if (value >= 0) {
        servoSpeed = value;
        Serial.print("Servo speed set to ");
        Serial.print(servoSpeed);
        Serial.println(" ms delay between steps.");
      } else {
        Serial.println("Invalid speed value. Please enter a positive integer.");
      }
    } else if (param.startsWith("ANGLE:")) {
      // Set angles for specified channels
      String anglesStr = param.substring(6);
      anglesStr.trim();
      processAngleCommands(anglesStr);
    } else if (param.startsWith("ENABLE:")) {
      // Enable specified channels
      String channelsStr = param.substring(7);
      channelsStr.trim();
      processEnableDisableCommands(channelsStr, true);
    } else if (param.startsWith("DISABLE:")) {
      // Disable specified channels
      String channelsStr = param.substring(8);
      channelsStr.trim();
      processEnableDisableCommands(channelsStr, false);
    } else {
      Serial.print("Unknown parameter: ");
      Serial.println(param);
    }
 
    paramStart = paramEnd + 1;
    paramEnd = command.indexOf(';', paramStart);
  }
}
 
// Function to process angle commands
void processAngleCommands(String anglesStr) {
  // Split the angles string into channel-angle pairs using ',' as the delimiter
  int pairStart = 0;
  int pairEnd = anglesStr.indexOf(',', pairStart);
 
  while (pairStart < anglesStr.length()) {
    String pair;
    if (pairEnd == -1) {
      pair = anglesStr.substring(pairStart);
      pairEnd = anglesStr.length();
    } else {
      pair = anglesStr.substring(pairStart, pairEnd);
    }
 
    pair.trim();  // Remove whitespace
 
    // Split the pair into channel and angle using '-' as the delimiter
    int dashIndex = pair.indexOf('-');
    if (dashIndex != -1) {
      String channelStr = pair.substring(0, dashIndex);
      String angleStr = pair.substring(dashIndex + 1);
      channelStr.trim();
      angleStr.trim();
 
      int channel = channelStr.toInt();
      int angle = angleStr.toInt();
 
      if (channel >= 0 && channel < 16 && angle >= 0 && angle <= 180) {
        setServoAngle(channel, angle);
      } else {
        Serial.print("Invalid channel or angle in pair: ");
        Serial.println(pair);
        Serial.println("Channel: 0-15, Angle: 0-180.");
      }
    } else {
      Serial.print("Invalid channel-angle pair: ");
      Serial.println(pair);
      Serial.println("Use the format channel-angle (e.g., 0-90).");
    }
 
    pairStart = pairEnd + 1;
    pairEnd = anglesStr.indexOf(',', pairStart);
  }
}
 
// Function to process enable/disable commands
void processEnableDisableCommands(String channelsStr, bool enable) {
  // Split the channels string into individual channels using ',' as the delimiter
  int chanStart = 0;
  int chanEnd = channelsStr.indexOf(',', chanStart);
 
  while (chanStart < channelsStr.length()) {
    String chanStr;
    if (chanEnd == -1) {
      chanStr = channelsStr.substring(chanStart);
      chanEnd = channelsStr.length();
    } else {
      chanStr = channelsStr.substring(chanStart, chanEnd);
    }
 
    chanStr.trim();  // Remove whitespace
 
    int channel = chanStr.toInt();
 
    if (channel >= 0 && channel < 16) {
      channelEnabled[channel] = enable;
      if (enable) {
        Serial.print("Channel ");
        Serial.print(channel);
        Serial.println(" enabled.");
      } else {
        pwm.setPWM(channel, 0, 0);  // Turn off PWM signal to the channel
        Serial.print("Channel ");
        Serial.print(channel);
        Serial.println(" disabled.");
      }
    } else {
      Serial.print("Invalid channel: ");
      Serial.println(chanStr);
      Serial.println("Channel must be between 0 and 15.");
    }
 
    chanStart = chanEnd + 1;
    chanEnd = channelsStr.indexOf(',', chanStart);
  }
}
 
// Function to set the angle of a specific servo
void setServoAngle(uint8_t n, uint16_t angle) {
  if (n > 15) {
    Serial.print("Error: Invalid channel ");
    Serial.println(n);
    return; // Only allow channels 0-15
  }
  if (!channelEnabled[n]) {
    Serial.print("Channel ");
    Serial.print(n);
    Serial.println(" is disabled. Enable it first.");
    return;
  }
  if (angle > 180) {
    Serial.println("Warning: Angle exceeds 180 degrees. Setting to 180.");
    angle = 180;
  }
  uint16_t pulselen = map(angle, 0, 180, SERVOMIN, SERVOMAX);
  pwm.setPWM(n, 0, pulselen);
}
 

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎