#include #include /* * 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: * * * - 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 #include // 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(""); 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); }