The Industrial robots have powerful motors and specific electronic circuitry to control the movements towards the programmed coordinates. Would it be possible to make a small robotic arm with basic electronic modules and components?
The main problem is the weight of the structure of the robotic arm and the actuators to be used to move it, ideally the structure should be made 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?
I have to look for a material that is not very heavy and it must be cheap, in marquetry balsa wood is used, it hardly weighs and it is resistant, with this in mind, it is possible that the servomotors can move the set and in addition, they already have integrated electronics for its control.
I’m going to develop a robotic arm that will pick up pieces with a gripper and deposit them in a different place. This project can be the basis to later add new modules and implement new functionalities.
The materials needed for this project are:
1 x AZ-Delivery Atmega328P microcontroller
1 x PCA9685 module PWM controller 16 outputs
3 x Servo Motor MG995
2 x Servo Motor MG90S
1 x Servo Motor SG90
Female-male cables
5 Vdc power supply
3 mm thick plywood sheets
Wood glue
Wood dowels
The required software is:
Arduino IDE
robot_arm scketch and adjustment sketchs
Wire library
Adafruit_PWMServoDriver library
The first step is to design the structure of our robotic arm in 3 mm thick balsa wood, so that the different parts of our robot can withstand the weight of the next part, we must increase the thickness of these gluing several equal pieces (the weight does not go up much as you can see) and to help the movements of the Servo Motors, we will have to balance a little the weights of the arms taking into account the weight of the arm at the other end.
To assemble our robotic arm it is very important to know the data of the pulse width of the servomotors where it positions the minimum and maximum angle to work in the adjustment sketches and that the robotic arm is positioned in the desired position. According to the data sheet, the MG995 servomotor rotates 120 degrees, while the MG90S and SG90 servomotors rotate 180 degrees, to know the pulse width data needed, we use the sketches Servo_check_120_degrees_slow_motion.ino and Servo_check_180_degrees.ino and an angle protractor as you can see in the drawing.
Servo_check_120_degrees_slow_motion.ino
#include <Wire.h> #include <Adafruit_PWMServoDriver.h> Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(); #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) void setup() { Serial.begin(9600); Serial.println("Reference check of 120 degrees with respect to the pulse length count"); pwm.begin(); pwm.setPWMFreq(50); // Analog servos run at ~50 Hz updates } void loop() { 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); }
Servo_check_180_degrees.ino
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
#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)
void setup() {
Serial.begin(9600);
Serial.println("Reference check of 180 degrees with respect to the pulse length count");
pwm.begin();
pwm.setPWMFreq(50); // Analog servos run at ~50 Hz updates
}
void loop() {
pwm.setPWM(0, 0, 100 ); // Movement to 0 degrees
Serial.println("100 pulse length count --> 0 grados");
delay(5000);
pwm.setPWM(0, 0, 500 ); // Move to 180 degrees
Serial.println("500 pulse length count --> 180 grados");
delay(5000);
}
The two sketches are very similar, so we are going to explain the most important lines of one of them. The following lines belong to the sketch of the MG995 servomotors, the first two lines that we analyze is the minimum and maximum value of the pulse width for any servomotor, which corresponds to the positions of 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 will have to change the data highlighted in red to position the servomotor between 30 degrees and 150 degrees to give in this case the 120 degrees with which the MG995 works; the movement is done slowly with 10 "steps" every 50 milliseconds from 30 degrees to 150 degrees. We will have to do these procedures with all the servomotors that we are going to use and write down the corresponding values of each one, since they are values between which it works 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);
Assembly
Once we have the minimum and maximum pulse values for the minimum and maximum working angle of each servomotor, it is time to assemble everything. To perform the assembly properly it can be done as follows:
- We start the assembly by the gripper, with this closed and the servomotor 0 at 90 degrees.
- The next servomotor is number 1, with the position at 0 degrees and the gripper in the position that can be seen in the photograph, since the rotation towards 180 degrees is clockwise.
- The third servomotor is number 2, this like the previous one in the 0 degree position and the gripper vertically upward, as it will also rotate clockwise to advance towards 180 degrees.
- Now it is the turn of the servomotor number 3, the position of the servomotor in this case we leave it in the 90 degrees and the arm we assemble it in line with the next one, as it gives us 60 degrees of movement in each direction.
- For servomotor number 4 we leave the position at 90 degrees and the arm we assemble it horizontally (parallel to the ground), which as with the previous servomotor, we have a large radius of action with the 60 degrees in each direction.
- The last servomotor, number 5, we leave it as the previous one at 90 degrees and we assemble the turning platform so that the rest of the arm is in the middle of the edge of the side of the base.
The electrical connections of the servomotors to the PCA9685 module are made by connecting them according to the numbering of the servomotor to the number of the similar PWM output port of the module, i.e. servomotor number 0 to output port 0.
The 5 Vdc power supply is connected to the green connector, taking special care in the polarity, since this power supply is the one supplied to the servomotors.
The connections from the Atmega328P Microcontroller to the PCA9685 module are the I2C communication through its port and the 5 Vdc for the power supply of the module electronics.
We already have everything assembled and connected, now remains the most laborious process, enter the values for each servomotor to perform the necessary movement to the desired position at each station.
Circuit and description of operation
As you can see in the schematic, the circuit is very simple, we have the Microcontroller, the PCA9685 module and the 6 servomotors.
The operation of the circuit is very simple, the six servomotors are controlled from the Atmega328P Microcontroller through the PCA9685 module that fulfills two important functions, the first is the power supply of the servomotors with 5 Vdc from an external power supply with enough power to move the 6 servomotors at the same time and the second is to act on the servomotor by the PWM pin of the corresponding port with the information that has been communicated by the Microcontroller through the I2C port. As you can be seen in the scheme, we only need 2 pins for the communication between the Microcontroller and the PCA9685 module to act on the 6 servomotors, leaving the other pins for future uses.
The really laborious part of this project are the adjustments of the values of each position of the arm and the "speeds" of the movements depending on what these are, having to be slower when it has to pick up an object.
For the adjustment and movement of the robotic arm in each station, we have made a sketch for each one of them to be able to make the adjustment individually and when we have all the adjustments made conveniently, we will gather all of them in a single sketch using methods, so the code will be much cleaner and easier to follow. We are going to analyze the code of one station and you will see that it is very simple; for the rest of the stations, the adjustment method is similar.
The code we are going to analyze are the settings for the robotic arm to pick up the first piece, the sketch is pick_up_first_object.ino.
The first two lines of the code are the libraries we need for the sketch to run correctly; Wire is for the 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 be able to control the positions of the servomotors, SERVOMIN and SERVOMAX are the values of the rising edge and falling edge for the 0 degrees and 180 degrees position respectively of the servomotors and the speed variable we use it for the waiting time to move the next servomotor.
Adafruit_PWMServoDriver pca9685 = Adafruit_PWMServoDriver(); #define SERVOMIN 100 #define SERVOMAX 500 int velocidad = 450;
In the setup() method we initialize the serial monitor, send a message and in the next two lines we initialize the PCA9685 module by means of its previously implemented object and indicate the frequency at which the servomotors work, which is 50Hz.
void setup() { Serial.begin(9600); Serial.println("Ajusting to Pick up first object"); pca9685.begin(); pca9685.setPWMFreq(50); }
Now we implement the loop() method and this is where we start the settings for each of the servomotors that must act to perform the movement. We begin with the first servomotor that we are going to move, the servomotor 5 is the one that rotates the robotic arm, with the function pca9685.setPWM(5, 0, 350) we indicate to the module PCA9685 that it must move motor 5 to the position resulting from the subtraction of the value of the rising edge (0) to the falling edge (350) and with the function delay(speed) we wait 450 milliseconds to execute the next function of the next servomotor.
void loop() { // Servomotor 5 pca9685.setPWM(5, 0, 350); delay(velocidad);
The following two functions perform the movements of servomotors 4 and 2, the execution is similar to that explained in the previous function, first move servomotor 4, wait 450 milliseconds and after that time move servomotor 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 perform the movements of servomotors 3 and 2. Let's explain why we have implemented the functions inside loops. The movement of the previous servomotors is performed at the normal speed which is very abrupt, to give a feeling of slow speed, we have implemented the loop in which the servomotor moves one "step" every 10 milliseconds from the start position (150) of the variable pos to its end position (180) of the servomotor 3, the real movement is intermittent movements, but as the waiting time between each movement is only 10 milliseconds, the movement gives a sense of continuity at low speed, as the movement is smooth it helps us to have a good accuracy.
// 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, those that move the servomotors 1 and 0 (gripper) are similar to those previously explained. Servomotor 1 moves at normal speed and servomotor 0, which is the gripper, closes it at a slow speed because it is inside 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 last waiting time in some of the individual sketches is 60 seconds, since it has movements within loops and these would be executed continuously, so we can check the correct coordinates in which positions and we can adjust the values of the variable pos of each servomotor so that the robotic arm is positioned in the coordinates we want.
delay(60000); }
To make the adjustment in each station we must run the corresponding sketch and vary the values of the variable pos of each servomotor until we have the robotic arm in the desired position.
The sketch that we have prepared robot_arm.ino performs a path that starts from a safety position, moves two pieces and returns to the safety position. We have tried to simplify to the maximum the programming of this sketch, for that what we have done is to attach the individual sketches of each station and in the loop() method to program these names as methods, with what the code is cleaner and easier to follow, since we know at every moment the position in which we have our robotic arm.
Just a note to finish with this explanation, when the servomotors are connected to the PCA9685 module and this has the voltage of 5 Vdc power supply for the servomotors, these are powered and retains the position in which they are, taking advantage of this, we have reduced the number of functions in some stations, because if in the previous station we have a servomotor in a position and the next is the same, we do not need to change, so we eliminate the latter, since it retains the position it had previously. In this way we save memory.
robot_arm.ino
/******************************************************************************************************** * * * This sketch controls the servo motors of the robotic arm. * * Last revision 07-11-2021 * * Spain_mtg * * * * * ********************************************************************************************************/ #include <Wire.h> // Library for I2C communications #include <Adafruit_PWMServoDriver.h> // PCA9685 Library Adafruit_PWMServoDriver pca9685 = Adafruit_PWMServoDriver(); // Implementation of an object of module PCA9685 #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) int velocidad = 450; // ******* Velocidad de movimiento void setup() { Serial.begin(9600); pca9685.begin(); pca9685.setPWMFreq(50); // Analog servos run at ~50 Hz updates } void loop() { home(); delay(1000); ready(); delay(1000); get_ready(); delay(1000); pick_up_first_object(); delay(1000); prepare_download_first_object(); delay(1000); download_first_object(); delay(1000); exit_download_first_object(); delay(1000); pick_up_second_object(); delay(1000); prepare_download_first_object(); delay(1000); download_second_object(); delay(1000); ready(); delay(1000); home(); delay(1000); delay(60000); // 60 sg wait to prevent scketch from being repeated } void home() { // Servo Motors position for Robot Arm home position // Servo Motor 5 pca9685.setPWM(5, 0, 265 ); delay(velocidad); // Servo Motor 1 pca9685.setPWM(1, 0, 499); delay(velocidad); // Servo Motor 2 pca9685.setPWM(2, 0, 499 ); delay(velocidad); // Servo Motor 4 pca9685.setPWM(4, 0, 188); delay(velocidad); // Servo Motor 3 pca9685.setPWM(3, 0, 150 ); delay(velocidad); // Servo Motor 0 pca9685.setPWM(0, 0, 166); delay(velocidad); } void ready() { // Position of the Servo Motors for the first movement of the Robot Arm // Servo Motor 4 pca9685.setPWM(4, 0, 295); delay(velocidad); // Servo Motor 3 pca9685.setPWM(3, 0, 220); delay(velocidad); // Servo Motor 2 pca9685.setPWM(2, 0, 300); delay(velocidad); // Servo Motor 1 pca9685.setPWM(1, 0, 300); delay(velocidad); } void get_ready() { // Position of the Servo Motors to prepare to pick up the first object // Servo Motor 5 pca9685.setPWM(5, 0, 350); delay(velocidad); // Servo Motor 3 pca9685.setPWM(3, 0, 150 ); delay(velocidad); // Servo Motor 2 pca9685.setPWM(2, 0, 415); delay(velocidad); // Servo Motor 4 pca9685.setPWM(4, 0, 200); delay(velocidad); // Servo Motor 0 pca9685.setPWM(0, 0, 200); delay(velocidad); } void pick_up_first_object() { // Position of the Servo Motors to pick up the first object // Servo Motor 4 pca9685.setPWM(4, 0, 210); delay(velocidad); // Servo Motor 2 pca9685.setPWM(2, 0, 405); delay(velocidad); // Servo Motor 3 // Loop for to move the Servo Motor 3 slowly for (int pos=150; pos<180; pos +=1) { pca9685.setPWM(3, 0, pos); delay(10); } // Servo Motor 2 // Loop for to move the Servo Motor 2 slowly for (int pos=405; pos>350; pos -=1) { pca9685.setPWM(2, 0, pos); delay(10); } // Servo Motor 0 // Loop to close the clamp slowly for (int pos=200; pos>166; pos -=1) { pca9685.setPWM(0, 0, pos); delay(10); } } void prepare_download_first_object() { // Position of the Servo Motors to prepare for download the first object // Servo Motor 4 pca9685.setPWM(4, 0, 295); delay(velocidad); // Servo Motor 3 pca9685.setPWM(3, 0, 220); delay(velocidad); // Servo Motor 5 pca9685.setPWM(5, 0, 265 ); delay(velocidad); // Servo Motor 2 pca9685.setPWM(2, 0, 300); delay(velocidad); } void download_first_object() { // Position of the Servo Motors to download the first objet // Servo Motor 5 pca9685.setPWM(5, 0, 210); delay(velocidad); // Servo Motor 3 pca9685.setPWM(3, 0, 175); delay(velocidad); // Servo Motor 2 pca9685.setPWM(2, 0, 460); delay(velocidad); // Servo Motor 4 pca9685.setPWM(4, 0, 195); delay(velocidad); // Servo Motor 0 // Loop to open the clamp slowly for (int pos=166; pos<200; pos +=1) { pca9685.setPWM(0, 0, pos); delay(10); } } void exit_download_first_object() { // Servo Motor 3 // Loop to start slowly backing out the Robot Arm for (int pos=175; pos>150; pos -=1) { pca9685.setPWM(3, 0, pos); delay(10); } // Servo Motor 4 pca9685.setPWM(4, 0, 295); delay(velocidad); // Servo Motor 5 pca9685.setPWM(5, 0, 265 ); delay(velocidad); // Servo Motor 2 pca9685.setPWM(2, 0, 300); delay(velocidad); // Servo Motor 1 pca9685.setPWM(1, 0, 300); delay(velocidad); } void pick_up_second_object() { // Servo Motor 5 pca9685.setPWM(5, 0, 300 ); delay(velocidad); // Servo Motor 0 // Open the clamp slowly pca9685.setPWM(0, 0, 200); delay(velocidad); // Servo Motor 4 pca9685.setPWM(4, 0, 245); delay(velocidad); // Servo Motor 3 pca9685.setPWM(3, 0, 220 ); delay(velocidad); // Servo Motor 2 pca9685.setPWM(2, 0, 170 ); delay(velocidad); // Servo Motor 1 pca9685.setPWM(1, 0, 470); delay(velocidad); // Servo Motor 4 // loop to slowly approach to the second object for (int pos=230; pos>210; pos -=1) { pca9685.setPWM(4, 0, pos); delay(10); } // Servo Motor 0 // Loop to close the clamp slowly for (int pos=200; pos>166; pos -=1) { pca9685.setPWM(0, 0, pos); delay(10); } } void download_second_object() { // Servo Motor 5 pca9685.setPWM(5, 0, 360); delay(velocidad); // Servo Motor 2 pca9685.setPWM(2, 0, 300); delay(velocidad); // Servo Motor 3 pca9685.setPWM(3, 0, 210); delay(velocidad); // Servo Motor 0 // Open the clamp pca9685.setPWM(0, 0, 200); delay(velocidad); }