#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
0 B
|👍
/👎