Advertisement
microrobotics

Arduino & ESP32 interfaced with PCA9685

Oct 10th, 2024 (edited)
339
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Arduino 11.44 KB | Software | 0 0
  1. #include <Wire.h>
  2. #include <Adafruit_PWMServoDriver.h>
  3.  
  4. /*
  5.  * PCA9685 Servo Controller Code with I2C Scanner
  6.  * Compatible with both Arduino UNO and ESP32.
  7.  *
  8.  * Usage Instructions:
  9.  * - Use the serial monitor to send commands in the following format:
  10.  *   <SPEED:speed_value;ANGLE:channel-angle_pairs;ENABLE:channels;DISABLE:channels>
  11.  *
  12.  * - All settings can be input as a single string.
  13.  * - Parameters:
  14.  *   - SPEED: Set the servo movement speed (delay in ms between steps).
  15.  *     - Example: SPEED:5
  16.  *   - ANGLE: Set the angles of servos on specified channels.
  17.  *     - Provide channel-angle pairs separated by commas.
  18.  *     - Example: ANGLE:0-90,1-45,2-135
  19.  *   - ENABLE: Enable specified channels.
  20.  *     - Provide channels separated by commas.
  21.  *     - Example: ENABLE:0,1,2
  22.  *   - DISABLE: Disable specified channels.
  23.  *     - Provide channels separated by commas.
  24.  *     - Example: DISABLE:3,4,5
  25.  * - Combine multiple parameters using semicolons.
  26.  *   - Example: SPEED:5;ANGLE:0-90,1-45;ENABLE:0,1;DISABLE:2,3
  27.  *
  28.  * Notes:
  29.  * - Channels range from 0 to 15.
  30.  * - Angles range from 0 to 180 degrees.
  31.  * - All parameters are optional; include only what you want to change.
  32.  * - Order of parameters does not matter.
  33.  *
  34.  * Default Behavior:
  35.  * - On startup, only channel 0 is enabled by default; all other channels are disabled.
  36.  * - The servo on channel 0 will automatically sweep back and forth between 0 and 180 degrees.
  37.  */
  38.  
  39. #ifdef ESP32
  40.   #define SERIAL_BAUDRATE 115200
  41.   #define OE_PIN 27
  42.   #define SDA_PIN 21
  43.   #define SCL_PIN 22
  44. #else
  45.   #define SERIAL_BAUDRATE 115200
  46. #endif
  47.  
  48. #include <Wire.h>
  49. #include <Adafruit_PWMServoDriver.h>
  50.  
  51. // Global variables to store speed and enabled channels
  52. int servoSpeed = 5;           // Default delay between servo movements
  53. bool channelEnabled[16];      // Array to keep track of enabled channels
  54.  
  55. // PCA9685 I2C address
  56. uint8_t pca9685_address = 0x40;  // Default address
  57.  
  58. // Create an instance of the PWM servo driver
  59. Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(pca9685_address);
  60.  
  61. // Constants for servo min and max pulse length
  62. #define SERVOMIN 150  // Min pulse length out of 4096
  63. #define SERVOMAX 600  // Max pulse length out of 4096
  64. #define MAX_RETRIES 5
  65.  
  66. void setup() {
  67.   Serial.begin(SERIAL_BAUDRATE);
  68.   delay(1000);  // Allow time for Serial to initialize
  69.   Serial.println("PCA9685 Servo Controller Initialized");
  70.  
  71.   // Initialize I2C
  72.   #ifdef ESP32
  73.     Wire.begin(SDA_PIN, SCL_PIN);
  74.     // Set OE_PIN as output and pull low to enable PWM output
  75.     pinMode(OE_PIN, OUTPUT);
  76.     digitalWrite(OE_PIN, LOW);
  77.   #else
  78.     Wire.begin();
  79.   #endif
  80.  
  81.   // Run I2C Scanner
  82.   scanI2CDevices();
  83.  
  84.   // Initialize all channels as disabled by default
  85.   for (int i = 0; i < 16; i++) {
  86.     channelEnabled[i] = false;
  87.   }
  88.   // Enable channel 0 by default
  89.   channelEnabled[0] = true;
  90.  
  91.   // Initialize the PCA9685
  92.   bool initialized = false;
  93.   for (int i = 0; i < MAX_RETRIES; i++) {
  94.     pwm = Adafruit_PWMServoDriver(pca9685_address);
  95.     pwm.begin();
  96.     pwm.setPWMFreq(60);  // Set the PWM frequency to 60 Hz
  97.     delay(10);  // Short delay to allow settings to take effect
  98.  
  99.     // Read MODE1 register to verify connection
  100.     uint8_t mode1 = readRegister(pca9685_address, 0x00);  // MODE1 register address is 0x00
  101.  
  102.     if (mode1 != 0xFF) {  // 0xFF indicates a failed read
  103.       initialized = true;
  104.       Serial.println("PCA9685 initialized successfully.");
  105.       break;
  106.     } else {
  107.       Serial.print("Attempt ");
  108.       Serial.print(i + 1);
  109.       Serial.println(": PCA9685 initialization failed! Retrying...");
  110.       delay(500);
  111.     }
  112.   }
  113.  
  114.   if (!initialized) {
  115.     Serial.println("Error: PCA9685 initialization failed after multiple attempts! Check I2C address and wiring.");
  116.     while (1);
  117.   }
  118.  
  119.   Serial.println("PWM frequency set to 60 Hz.");
  120.   Serial.println("Enter commands to control the servos in the following format:");
  121.   Serial.println("<SPEED:speed_value;ANGLE:channel-angle_pairs;ENABLE:channels;DISABLE:channels>");
  122.   Serial.println("Example: SPEED:5;ANGLE:0-90,1-45;ENABLE:0,1;DISABLE:2,3");
  123. }
  124.  
  125. void loop() {
  126.   // Automatic movement for enabled servos
  127.   static uint16_t angle = 0;
  128.   static int8_t increment = 1;
  129.  
  130.   // Move enabled servos
  131.   for (uint8_t channel = 0; channel < 16; channel++) {
  132.     if (channelEnabled[channel]) {
  133.       setServoAngle(channel, angle);
  134.     }
  135.   }
  136.  
  137.   angle += increment;
  138.   if (angle >= 180 || angle <= 0) {
  139.     increment = -increment;
  140.   }
  141.  
  142.   delay(servoSpeed);
  143.  
  144.   // Check for serial input
  145.   if (Serial.available() > 0) {
  146.     String input = Serial.readStringUntil('\n');
  147.     input.trim();  // Remove any leading/trailing whitespace
  148.     processCommand(input);
  149.   }
  150. }
  151.  
  152. // Function to scan I2C devices
  153. void scanI2CDevices() {
  154.   Serial.println("Scanning for I2C devices...");
  155.   uint8_t error, address;
  156.   int nDevices = 0;
  157.  
  158.   for (address = 1; address < 127; address++) {
  159.     Wire.beginTransmission(address);
  160.     error = Wire.endTransmission();
  161.  
  162.     if (error == 0) {
  163.       Serial.print("I2C device found at address 0x");
  164.       if (address < 16)
  165.         Serial.print("0");
  166.       Serial.print(address, HEX);
  167.       Serial.println(" !");
  168.       nDevices++;
  169.  
  170.       // If it's the first device found, assume it's the PCA9685
  171.       if (nDevices == 1) {
  172.         pca9685_address = address;
  173.       }
  174.     } else if (error == 4) {
  175.       Serial.print("Unknown error at address 0x");
  176.       if (address < 16)
  177.         Serial.print("0");
  178.       Serial.println(address, HEX);
  179.     }
  180.   }
  181.  
  182.   if (nDevices == 0) {
  183.     Serial.println("No I2C devices found. Please check your connections.");
  184.   } else {
  185.     Serial.print(nDevices);
  186.     Serial.println(" I2C devices found.");
  187.     Serial.print("Using PCA9685 at address 0x");
  188.     if (pca9685_address < 16)
  189.       Serial.print("0");
  190.     Serial.println(pca9685_address, HEX);
  191.   }
  192. }
  193.  
  194. // Function to read a register from the PCA9685
  195. uint8_t readRegister(uint8_t deviceAddress, uint8_t reg) {
  196.   Wire.beginTransmission(deviceAddress);
  197.   Wire.write(reg);
  198.   if (Wire.endTransmission() != 0) {
  199.     return 0xFF;  // Indicate failure to read
  200.   }
  201.  
  202.   Wire.requestFrom(deviceAddress, (uint8_t)1);
  203.   if (Wire.available()) {
  204.     return Wire.read();
  205.   } else {
  206.     return 0xFF;  // Indicate failure to read
  207.   }
  208. }
  209.  
  210. // Function to process serial commands
  211. void processCommand(String command) {
  212.   command.toUpperCase();  // Convert command to uppercase for case-insensitive comparison
  213.  
  214.   // Split the command into parameters using ';' as the delimiter
  215.   int paramStart = 0;
  216.   int paramEnd = command.indexOf(';', paramStart);
  217.  
  218.   while (paramStart < command.length()) {
  219.     String param;
  220.     if (paramEnd == -1) {
  221.       param = command.substring(paramStart);
  222.       paramEnd = command.length();
  223.     } else {
  224.       param = command.substring(paramStart, paramEnd);
  225.     }
  226.  
  227.     param.trim();  // Remove whitespace
  228.  
  229.     // Process each parameter
  230.     if (param.startsWith("SPEED:")) {
  231.       // Set servo speed
  232.       String valueStr = param.substring(6);
  233.       valueStr.trim();
  234.       int value = valueStr.toInt();
  235.       if (value >= 0) {
  236.         servoSpeed = value;
  237.         Serial.print("Servo speed set to ");
  238.         Serial.print(servoSpeed);
  239.         Serial.println(" ms delay between steps.");
  240.       } else {
  241.         Serial.println("Invalid speed value. Please enter a positive integer.");
  242.       }
  243.     } else if (param.startsWith("ANGLE:")) {
  244.       // Set angles for specified channels
  245.       String anglesStr = param.substring(6);
  246.       anglesStr.trim();
  247.       processAngleCommands(anglesStr);
  248.     } else if (param.startsWith("ENABLE:")) {
  249.       // Enable specified channels
  250.       String channelsStr = param.substring(7);
  251.       channelsStr.trim();
  252.       processEnableDisableCommands(channelsStr, true);
  253.     } else if (param.startsWith("DISABLE:")) {
  254.       // Disable specified channels
  255.       String channelsStr = param.substring(8);
  256.       channelsStr.trim();
  257.       processEnableDisableCommands(channelsStr, false);
  258.     } else {
  259.       Serial.print("Unknown parameter: ");
  260.       Serial.println(param);
  261.     }
  262.  
  263.     paramStart = paramEnd + 1;
  264.     paramEnd = command.indexOf(';', paramStart);
  265.   }
  266. }
  267.  
  268. // Function to process angle commands
  269. void processAngleCommands(String anglesStr) {
  270.   // Split the angles string into channel-angle pairs using ',' as the delimiter
  271.   int pairStart = 0;
  272.   int pairEnd = anglesStr.indexOf(',', pairStart);
  273.  
  274.   while (pairStart < anglesStr.length()) {
  275.     String pair;
  276.     if (pairEnd == -1) {
  277.       pair = anglesStr.substring(pairStart);
  278.       pairEnd = anglesStr.length();
  279.     } else {
  280.       pair = anglesStr.substring(pairStart, pairEnd);
  281.     }
  282.  
  283.     pair.trim();  // Remove whitespace
  284.  
  285.     // Split the pair into channel and angle using '-' as the delimiter
  286.     int dashIndex = pair.indexOf('-');
  287.     if (dashIndex != -1) {
  288.       String channelStr = pair.substring(0, dashIndex);
  289.       String angleStr = pair.substring(dashIndex + 1);
  290.       channelStr.trim();
  291.       angleStr.trim();
  292.  
  293.       int channel = channelStr.toInt();
  294.       int angle = angleStr.toInt();
  295.  
  296.       if (channel >= 0 && channel < 16 && angle >= 0 && angle <= 180) {
  297.         setServoAngle(channel, angle);
  298.       } else {
  299.         Serial.print("Invalid channel or angle in pair: ");
  300.         Serial.println(pair);
  301.         Serial.println("Channel: 0-15, Angle: 0-180.");
  302.       }
  303.     } else {
  304.       Serial.print("Invalid channel-angle pair: ");
  305.       Serial.println(pair);
  306.       Serial.println("Use the format channel-angle (e.g., 0-90).");
  307.     }
  308.  
  309.     pairStart = pairEnd + 1;
  310.     pairEnd = anglesStr.indexOf(',', pairStart);
  311.   }
  312. }
  313.  
  314. // Function to process enable/disable commands
  315. void processEnableDisableCommands(String channelsStr, bool enable) {
  316.   // Split the channels string into individual channels using ',' as the delimiter
  317.   int chanStart = 0;
  318.   int chanEnd = channelsStr.indexOf(',', chanStart);
  319.  
  320.   while (chanStart < channelsStr.length()) {
  321.     String chanStr;
  322.     if (chanEnd == -1) {
  323.       chanStr = channelsStr.substring(chanStart);
  324.       chanEnd = channelsStr.length();
  325.     } else {
  326.       chanStr = channelsStr.substring(chanStart, chanEnd);
  327.     }
  328.  
  329.     chanStr.trim();  // Remove whitespace
  330.  
  331.     int channel = chanStr.toInt();
  332.  
  333.     if (channel >= 0 && channel < 16) {
  334.       channelEnabled[channel] = enable;
  335.       if (enable) {
  336.         Serial.print("Channel ");
  337.         Serial.print(channel);
  338.         Serial.println(" enabled.");
  339.       } else {
  340.         pwm.setPWM(channel, 0, 0);  // Turn off PWM signal to the channel
  341.         Serial.print("Channel ");
  342.         Serial.print(channel);
  343.         Serial.println(" disabled.");
  344.       }
  345.     } else {
  346.       Serial.print("Invalid channel: ");
  347.       Serial.println(chanStr);
  348.       Serial.println("Channel must be between 0 and 15.");
  349.     }
  350.  
  351.     chanStart = chanEnd + 1;
  352.     chanEnd = channelsStr.indexOf(',', chanStart);
  353.   }
  354. }
  355.  
  356. // Function to set the angle of a specific servo
  357. void setServoAngle(uint8_t n, uint16_t angle) {
  358.   if (n > 15) {
  359.     Serial.print("Error: Invalid channel ");
  360.     Serial.println(n);
  361.     return; // Only allow channels 0-15
  362.   }
  363.   if (!channelEnabled[n]) {
  364.     Serial.print("Channel ");
  365.     Serial.print(n);
  366.     Serial.println(" is disabled. Enable it first.");
  367.     return;
  368.   }
  369.   if (angle > 180) {
  370.     Serial.println("Warning: Angle exceeds 180 degrees. Setting to 180.");
  371.     angle = 180;
  372.   }
  373.   uint16_t pulselen = map(angle, 0, 180, SERVOMIN, SERVOMAX);
  374.   pwm.setPWM(n, 0, pulselen);
  375. }
  376.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement