I'm working with a Me Arm and using the PCA9685 servo controller board. I'm having difficulty understanding how to calibrate the servo motors for the Adafruit Library code.
Important preliminary Information:
The servo motors have been correctly physically calibrated during construction of the arm. The arm works perfectly when using the standard servo library and writing angles to the servo motors. So that much has been done. But now that I'm using the PCA9685 board and the Adafruit Library code the arm is no longer calibrated correctly. So it appears that I need to do some software tweaking.
Here's the code I'm currently running:
//#include #include // Include Wire Library for I2C Communications #include // Include Adafruit PWM Library for using the PAC9685 board #define MIN_PULSE_WIDTH 650 // Variables for the PCA9685 board #define MAX_PULSE_WIDTH 2350 // Variables for the PCA9685 board #define FREQUENCY 50 // Variables for the PCA9685 board Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40); // The PCA9685 board I2C address begins at 0x40 String str_angles = ""; // Used for Serial input from monitor int base_angle = 90; // Intialize base angle to 90 degrees int u_arm_angle = 180; // Intialize upper arm angle to 90 int l_arm_angle = 90; // Intialize lower arm angle to 90 int base = 0; // used to select which servo to move int u_arm = 1; // used to select which servo to move int l_arm = 2; // used to select which servo to move void setup() { Serial.begin(9600); // Use Serial monitor pwm.begin(); // start Adafruit object pwm.setPWMFreq(FREQUENCY); // set frequency } void loop() { Serial.println("Enter Angles as 3-digit inputs eg. 000,000,000 Base, U_arm, L_arm"); while (Serial.available() == 0){} // wait for input // Take in all three angles a single string. str_angles = Serial.readString(); // Add input rrror checking routine here ~~~ To be done later ~~~ // convert angels to integers base_angle = str_angles.substring(0,4).toInt(); u_arm_angle = str_angles.substring(4,8).toInt(); l_arm_angle = str_angles.substring(8).toInt(); // Write the angles to the monitor screen and the to the servos Serial.print("Setting Base Angle = "); Serial.println(base_angle,DEC); move_Arm(base_angle,base); Serial.print("Setting Upper Arm Angle = "); Serial.println(u_arm_angle,DEC); move_Arm(u_arm_angle,u_arm); Serial.print("Setting Lower Arm Angle = "); Serial.println(l_arm_angle,DEC); move_Arm(l_arm_angle,l_arm); } void move_Arm(int angle, int motorOut) { int pulse_width, pulse_wide; // Trying to map the angle correctly here pulse_wide = map(angle,0, 180, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH); pulse_width = int(float(pulse_wide) / 1000000 * FREQUENCY * 4096); // Display values of pulse_wide and pulse width for debugging Serial.print("Pulse Wide = "); Serial.println(pulse_wide,DEC); Serial.print("Pulse Width ="); Serial.println(pulse_width,DEC); // send the control value out to the correct servo motor. pwm.setPWM(motorOut, 0, pulse_width); }
I modified this from Bill's code example in his video on the PCA9685 board. I imagine my problem is in the move_Arm() method. In the mapping of the angle to pulse_wide
pulse_wide = map(angle,0, 180, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
I'm just not understanding what numbers I need to get for the final pulse_width?
Here's what I get when I run the program for angles 0 through 180.
Entered Angles as 3-digit inputs eg. 000,000,000 Base, U_arm, L_arm
Setting Base Angle = 0
Pulse Wide = 650
Pulse Width = 133
Setting Upper Arm Angle = 0
Pulse Wide = 650
Pulse Width = 133
Setting Lower Arm Angle = 0
Pulse Wide = 650
Pulse Width = 133Entered Angles as 3-digit inputs eg. 090,090,090 Base, U_arm, L_arm
Setting Base Angle = 90
Pulse Wide = 1500
Pulse Width = 307
Setting Upper Arm Angle = 90
Pulse Wide = 1500
Pulse Width = 307
Setting Lower Arm Angle = 90
Pulse Wide = 1500
Pulse Width = 307Entered Angles as 3-digit inputs eg. 180,180,180 Base, U_arm, L_arm
Setting Base Angle = 180
Pulse Wide = 2350
Pulse Width = 481
Setting Upper Arm Angle = 180
Pulse Wide = 2350
Pulse Width = 481
Setting Lower Arm Angle = 180
Pulse Wide = 2350
Pulse Width = 481
I'm getting pulse widths from 133 to 481. But I'm not understanding what numbers for a final pulse width I should be shooting for? Or how to properly map my 0 to 180 angles to obtain a proper value for pulse_wide.
Anyone know how to properly map to a servo when using the Adafruit library and a PCA9685 board.
Thanks.
DroneBot Workshop Robotics Engineer
James
Update:
I'm making some progress on my own. I found the Adafruit PDF file for the PCA9685 and it has some explanations for how to calibrate servos by trial and error method of just trying min and max values for the pulse width. So I'll write up a small calibration program where I can just enter in pulse width values directly on the serial monitor until I find values that put the servos in the correct positions. Then I'll have the numbers I'm looking for. ?
In the meantime I'm going to bed at 3:00 AM. This is too late for a 70 year old robotics nerd to be staying up trying to calibrate servo motors. .
DroneBot Workshop Robotics Engineer
James
Yes, I think calibration that is the correct approach. Servos in the price range we use tend to need tweaking. Then you can add fudge factors to your code to compensate for irregularities in your hardware.
Not unlike a Harbor Freight tool... If you know what you are getting and willing the work around the quirks you can get some bargains.
I just posted this in my main Me Arm thread. But since I also started this thread asking about how to calibrate these servos I figured I better post what I've done here too. I think this will solve my problem.
Progress Report:
I wrote up a small program to find all my limiting pulse width numbers. The program is extremely crude, but I'll post it anyway just in case someone might find it useful in some way. It allows the control of 4 servos individually based on changing the pulse width in steps of 10 increments or decrements at a time. That was enough for me to find the limits of my Me Arm.
Unfortunately because of the mechanical design of the Me Arm things are not as simple as they could be. This has to do with the way the upper and lower arms of the Me Arm interact with each other. How they behave can depend on what position the other half of the arm is already in. So when programming the Me Arm we need to take into consideration what the optimal positions are for the upper and lower parts of the arm. The claw and the base rotation are independent and so they will be easier to deal with.
First the Results: (note that these results may be unique to my Me Arm)
For the Base servo I got the following:
The numbers are pulse widths to be used in the Adafruit command.
Base:
Left maximum travel = 140
Right maximum travel = 450
Centering the arm = 290Upper Arm:
Fully Retracted = 230
Fully extended = 470
Midpoint = 350 * can depend on the position of the lower armLower Arm:
Maxium up = 460
Maxium down = 190
Midpoint = 290 * can depend on the position of the upper arm.Claw:
Fully open = 140
Fully closed = 220
So as you can see each servo will need to have its own mapping in the Arduino program.
Hopefully this will all work out and we can move forward with trying to program the arm to do some interesting tasks.
Here's the code I used to discover the pulse width limits of the Me Arm. It's a crude program that could use so prettying up. I just wanted to get these numbers and move forward.
/* Me_Arm_Servo_Calibration_V1 * by Robo Pi */ #include // Include Wire Library for I2C Communications #include // Include Adafruit PWM Library for using the PAC9685 board #define FREQUENCY 50 // Variables for the PCA9685 board // The PCA9685 board I2C address is 0x40 Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40); String str_input = ""; // Used for Serial input from monitor int int_Char; // Used for Serial input int base_angle; // Base angle int u_arm_angle; // Upper arm angle int l_arm_angle; // Lower arm angle int claw_angle; // Claw angle // Servo PCA9685 board pins int servo_pin; int base = 0; int u_arm = 1; int l_arm = 2; int claw = 3; void setup() { Serial.begin(9600); // Use Serial monitor pwm.begin(); // start Adafruit object pwm.setPWMFreq(FREQUENCY); // set frequency } void loop() { Serial.println("Enter 0, 1, 2, or 3 for Base, U_arm, L_arm or Claw"); while (Serial.available() == 0){} // wait for input str_input = Serial.readString(); // Add input rrror checking routine here ~~~ To be done later ~~~ servo_pin = str_input.toInt(); move_Arm(servo_pin); } void move_Arm(int motorOut) { int pulse_width = 300; while(true) { Serial.println("Enter 1 or 2 to inc or dec angle, or 3 to end"); while (Serial.available() == 0){} // wait for input str_input = Serial.readString(); int_Char = str_input.substring(0,1).toInt(); //Serial.println(int_Char,DEC); switch (int_Char) { case 1: pulse_width = pulse_width - 10; Serial.print("Pulse Width = "); Serial.println(pulse_width,DEC); pwm.setPWM(motorOut, 0, pulse_width); break; case 2: pulse_width = pulse_width + 10; Serial.print("Pulse Width = "); Serial.println(pulse_width,DEC); pwm.setPWM(motorOut, 0, pulse_width); break; case 3: return; } } }
DroneBot Workshop Robotics Engineer
James