It has always fascinated me to see the movements of robotic arms when they do all kinds of work. Their movements are precise and continuous, as if I was in a dance class. These industrial robots have powerful motors and special electronic circuits to control movements according to programmed coordinates. Would it be possible to build a small robot arm with simple electronic modules and components?
The main problem is the weight of the structure of the robot arm and the actuators to move it. Ideally, the structure should be of metal and the actuators would have to be stepper motors, but this set is heavy and expensive. The question is: could it be done with servo motors?
We have to look for a material that is not very heavy and inexpensive. Balsa wood is used for inlays (marquetry), which is not heavy and yet resistant. So it is possible for servo motors to move the set, with the electronics for control already integrated there. We will develop a robot arm that picks up parts with a gripper and places them elsewhere.
The first step is to design the structure of our robot arm from 3 mm thick balsa wood. In order for the different parts of our robot to be able to support the weight of the next part, we have to increase the thickness of these parts by gluing several equal pieces (the weight does not increase very much, as you will notice). To support the movements of the servo motors, we need to balance the weights of the arms a little, taking into account the weight of the arm at the other end.
For the operation of our robot arm it is very important to know the pulse width data of the servo motors in order to position the minimum and maximum angle with the adjustment sketches and to bring the robot arm into the desired position. According to the data sheet, the MG995 servo motor rotates 120 degrees, while the MG90S and SG90 servo motors rotate 180 degrees. To determine the required pulse width data, we use the Servo_check_120_degrees_slow_motion.ino and Servo_check_180_degrees.ino sketches and a protractor as shown in the drawing.
The two sketches are very similar, so we will explain the most important lines of one of the two sketches. The following lines belong to the MG995 servo motor sketch. The first two lines we analyse are the minimum and maximum values of the pulse width for each servo motor, corresponding to the positions 0 degrees and 180 degrees.
#define SERVOMIN 100 // This is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX 500 // This is the 'maximum' pulse length count (out of 4096)
In the following lines we create two loops in which we have to change the data marked in red to position the servo motor between 30 degrees and 150 degrees. In this case we get the 120 degrees that the MG995 works with. The movement is slow with 10 "steps" every 50 milliseconds from 30 degrees to 150 degrees. We need to do these procedures with all the servo motors we will be using and write down the corresponding values for each one, as these are the values it works between to position itself.
for (int pos=165; pos<430; pos +=10) { // Loop with movement slow from 30
degrees to 150 degrees
pwm.setPWM(0, 0, pos );
Serial.println("165 pulse length count --> 30 degrees");
delay(50);
}
delay (5000);
for (int pos_m=430; pos_m>165; pos_m -=10) { // Loop with movement slow from 150
degrees to 30 degrees
pwm.setPWM(0, 0, pos_m );
Serial.println("430 pulse length count --> 150 degrees");
delay(50);
}
delay (5000);
MountingOnce we have the minimum and maximum pulse values for the minimum and maximum working angle of each servo motor, it is time to assemble everything. To assemble properly, you can proceed as follows:
1. we start mounting on the gripper with it closed and the servo motor turned 90 degrees.
2. the next servo motor is number 1, with the position at 0 degrees and the gripper in the position shown in the photo, as the rotation is 180 degrees clockwise.
3. the third servo motor is number 2, which, like the previous one, is in the 0 degree position and moves the grab vertically upwards as it also rotates clockwise to move in the direction of 180 degrees.
4. now it is the turn of servo motor number 3, we leave the position of the servo motor at 90 degrees in this case and we mount the arm in alignment with the next one, as this gives us 60 degrees of movement in each direction.
5. for servo motor number 4 we leave the position at 90 degrees and mount the arm horizontally (parallel to the ground), which gives us a large radius of action with 60 degrees in each direction, as with the previous servo motor.
6. we leave the last servo motor, number 5, at 90 degrees like the previous one and mount the rotating platform so that the rest of the arm is in the middle of the side edge of the base.
As you can see in the circuit diagram, the circuit is very simple. We have the microcontroller, the PCA9685 module and the 6 servo motors.
The electrical connections of the servo motors to the PCA9685 assembly are connected according to the numbering of the servo motor with the number of the corresponding PWM output port on the assembly, i.e. servo motor number 0 with output port 0.
The 5V DC power supply is connected to the green screw connector on the driver board, paying attention to the polarity as the servo motors are supplied via this connector.
The connections between the Atmega328P microcontroller and the PCA9685 module are the I2C communication via its port and the 5V for the power supply of the module electronics.
The six servo motors are controlled by the Atmega328P microcontroller via the PCA9685 module, which has two important functions. The first is to supply the servo motors with 5V from an external power supply with enough power to move the 6 servo motors simultaneously. The second is to send the PWM signal to the respective servo motor, which was transmitted via the I2C signal from the microcontroller. As can be seen in the diagram above, we only need 2 pins for communication between the microcontroller and the PCA9685 module, leaving the other pins for other uses.
The really tedious part of this project is setting the values for each position of the arm and the speeds of the movements. The latter must slow down if the arm is to pick up an object.
For the adjustment and movement of the robot arm in each stage, one sketch per stage was created. This allows the settings to be made individually. When we have made all the settings correctly, we will combine them all into one sketch. We will use methods/functions so that the code is cleaner and easier to follow. Analyse the code for one stage and you will see that it is very simple. For the other stages, the adaptation method is similar.
The code we will analyse is the settings for the robot arm to pick up the first part. The sketch is called pick_up_first_object.ino.
The first two lines of code are the libraries we need for the sketch to run correctly. Wire.h is for I2C communication and Adafruit_PWMServoDriver is for using the PCA9685 module.
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
The next 4 lines are in order the implementation of an Adafruit_PWMServoDriver object to control the servo motor positions, SERVOMIN and SERVOMAX are the rising edge and falling edge values for the 0 degree and 180 degree position of the servo motors respectively. The speed variable is used for the waiting time until the next servo motor is moved.
Adafruit_PWMServoDriver pca9685 = Adafruit_PWMServoDriver();
#define SERVOMIN 100
#define SERVOMAX 500
int velocidad = 450;
The next 4 lines are in order the implementation of an Adafruit_PWMServoDriver object to control the servo motor positions, SERVOMIN and SERVOMAX are the rising edge and falling edge values for the 0 degree and 180 degree position of the servo motors respectively. The speed variable is used for the waiting time until the next servo motor moves. In the setup() method we initialise the serial monitor and output a message. In the next two lines we initialise the PCA9685 module using its previously implemented object and specify the frequency at which the servo motors operate, 50 Hz.
In the setup()-method we initialise the serial monitor and output a message. In the next two lines we initialise the PCA9685 module using its previously implemented object and specify the frequency at which the servo motors operate, 50 Hz.
void setup() {
Serial.println("Ajusting to Pick up first object");
pca9685.begin();
pca9685.setPWMFreq(50);
}
Now we implement the loop()-method. Here we start with the settings for each of the servo motors that need to act to execute the movement. We start with the first servo motor we want to move. Servo motor 5 is the one that rotates the robot arm. With the function pca9685.setPWM(5, 0, 350) we tell the PCA9685 module to move motor 5 to the position resulting from subtracting the value of the rising edge (0) from the falling edge (350), and with the function delay(velocidad ) we wait 450 milliseconds to execute the next function of the following servo motor.
void loop() {
// Servomotor 5
pca9685.setPWM(5, 0, 350);
delay(velocidad);
The next two function calls execute the movements of servo motors 4 and 2. The execution is similar to the previous function, first move servo motor 4, wait 450 milliseconds and after this time move servo motor 2 to the position of the difference between the rising and falling edge.
// Servomotor 4
pca9685.setPWM(4, 0, 210);
delay(velocidad);
// Servomotor 2
ca9685.setPWM(2, 0, 405);
delay(velocidad);
The following two functions execute the movements of servo motors 3 and 2. Let's explain why we have implemented loops for this. The movement of the previous servo motors is at the normal speed, which is very abrupt. To give the feeling of a slow speed, we have implemented a loop in which the servo motor moves one "step" every 10 milliseconds from the start position (150) of the variable pos to its end position (180) of the servo motor 3. The actual movement is intermittent, but because the waiting time between each movement is only 10 milliseconds, the movement gives a sense of continuity at low speed. As this makes the movement smooth, it helps us achieve good precision.
// Servomotor 3
for (int pos=150; pos<180; pos +=1) {
pca9685.setPWM(3, 0, pos);
delay(10);
}
// Servomotor 2
for (int pos=405; pos>350; pos -=1) {
pca9685.setPWM(2, 0, pos);
delay(10);
}
The development of the last two functions that move servo motors 1 and 0 (gripper) is similar to that described above. Servo motor 1 moves at normal speed and servo motor 0, which is the gripper, closes it at slow speed because it is in a loop.
// Servomotor 1
pca9685.setPWM(1, 0, 300);
delay(velocidad);
// Servomotor 0
for (int pos=200; pos>166; pos -=1) {
pca9685.setPWM(0, 0, pos);
delay(10);
}
The final waiting time in some of the individual sketches is 60 seconds, as these are movements within loops that are executed continuously. This way we can check which coordinates are correct in which positions and we can adjust the values of the variables pos of each servo motor so that the robot arm is positioned at the desired coordinates.
In order to make the adjustment at each stage, we need to run the corresponding sketch and vary the values of the pos variables of each servo motor until the robot arm reaches the desired position.
The prepared sketch robot_arm.ino executes a path that starts from a safety position, moves two parts and returns to the safety position. I have tried to simplify the programming of this sketch as much as possible. Therefore, I have stored the code for respective stages as separate sketches and programmed them as functions in the loop() method of the robot_arm.ino sketch with the same names. This way we always know in which position our robot arm is.
If the servomotors are connected to the PCA9685 module and this is supplied with voltage, the servos retain the position in which they are located. I exploited that to reduce the number of functions in some stages. If we have a servomotor in a position in the previous stage and the next one is the same, we do not need to change so that we eliminate the latter as it retains the position you had before.
I hope you enjoy this project.
Miguel Torres Gordo
Comments