Project showcase # Robotic Arm And 3D Modeling

A 3D printed robotic arm, controlled through an Arduino, also being 3D rendered in Python OpenGL, using the Ci20 board.

• 9,214 views
• 2 comments
• 24 respects

## Necessary tools and machines 3D Printer (generic)

## About this project

### Overview

The main idea of this project was to build a robotic arm that can be controlled through user input, while also using the Ci20 to create a 3D rendering of the arm on screen.

### 3D Render

In this area of my project, I used the python opengl library to create a 3D render of the robotic arm I built. This was a very challenging project. Prior to this I had no experience in 3D graphics processing. It took me quite some time to understand how to build this type of program. With the help of python opengl tutorials by Sentdex on Youtube, I was able to gain a better understanding of this subject, giving me a starting base of knowledge to expand upon with this project.

There were a few things in the overlying subject of 3D graphics that I was initially confused on. The main two being:

• Camera Positioning
• Object Translation and Rotation

When it comes to camera positioning there is a very important idea to grasp. This idea is that, the camera does not move; it is the world that moves around it. The camera is located at (0,0,0) facing negative z. In order to 'move' the camera, the entire world must be translated and rotated with reference to it. In the case of the camera, the objects on screen are all being moved with respect to it so it appears as though the camera is doing the moving. This also creates the situation where coordinates in the translate and rotate functions must be inverted in order to have their desired effects.

The other area I had problems with was translation and rotation of objects. I had a hard time understanding how to move objects, because the functions for moving objects are the same as 'moving' the camera, only in this case they are actually moving. An aspect that is important to note in 3D graphics is, the order translations and rotations take place in, because their order affects their outcome. Changing the order of a translation and rotation can have drastically different results.

The bulk of the program was inspired by the Sentdex python opengl tutorials, but after the first few I set out on my own to learn my way, through trial and error, and simply doing it to see what works.

This program creates three 3D objects: The axes, the humerus, and the radius. The first is simply a cross section of planes, 50 by 50 units, colored red, blue, and green. I added this to add a reference point for the motion of the arm as it is translated and rotated. The humerus is the upper bone of the arm, and remains static. The radius is one of the two bones in the fore arm. I would have added the ulna bone, but was strapped for time on this project. The radius can be controlled through the G and H keys, rotating and translating it up and down.

This was an area that took a good chunk of my time to figure out; how to sync up the joint of the elbow in the 3D model. The main problem is that the radius's local origin is not based in the upper part of the object, but in the bottom part. This means that it rotates from the bottom not the top. When the radius is rotated it must also be translated back to the elbow joint in order for it to meet up. When trying to figure out this part, I really over thought it. I was using inverse trig functions to try and determine the distance vertically and horizontally it had to be translated. But it turned out I didn't need that, all I needed were two functions.

So my code went from this:

``````glRotatef(a,1,0,0)
a = rad(a)
glTranslatef(10-10*math.atan(a),0,0)
glTranslatef(0,20+20*math.atan(a),0)
``````

To this:

``````glRotatef(a,1,0,0)
glTranslatef(20,0,0)
``````

Which was surprising, that the simple code is exactly what I needed for it to work. I learned most of the things for this project through trial and error, and now I know what not to do.

### Program Notes

These are some notes that I took as I programmed or when I had a problem that I didn't understand. They were mainly just for my reference, but some of them have coherent information on them, if they are legible.

### Robotic Arm

I designed all of the parts for this arm myself in Solidworks. They were then printed on my school's 3D printer. This was actually my first experience of printing something that I designed from a 3D printer, all other times I merely left it digital.

I learned quite a few things from this aspect of my project. I learned key lessons in design and engineering. Mainly through my mistakes and tries, I learned good habits for designing something.

The design of this robotic arm is inspired by human anatomy. It is comprized of three main bones and a number of joints to give a realistic range of motion. There are two sets of pulleys that are driven through the two microservos. The main idea for replicating the motion of a human forearm, relies on the pulleys. They act as tendons to pull up the radius and ulna bones. The two pulleys can act in tandom to pull the arm up or down. But the major point in this design is that when the servos go in opposite directions, it creates a twisting motion, similar to how the wrist works.

There are somethings that I could have designed better, mainly the connections for the pulleys and servo mounts. The current belts are actually very large specialty o-rings. They may be a good size and fit, but they do tend to slip. I could have also used flat belts and toothed pulleys to have a more reliable transfer of motion. I had also run into some problems along the way as I designed this arm. For a little while I was left without servos because the ones I had initially installed on the arm had broken. They broke due to an error of my own, while attempting to adjust their trim pots. After they broke I took the opportunity to buy much stronger servos, as well as back ups in case the new ones failed.

I learn much from this project, but can always learn much more. This project helped to serve as a foundation for me to continue on with other projects, as well as update and maintain this one.

## Code

##### Python OpenGL Robotic Arm ModelPython
A program written using the python opengl library. It features a controllable arm model and movable camera.
Key Controls:
- L and R arrows: translate x
- U and D arrows: translate y
- J and L keys: translate z

- A and D keys: x rotation
- W and S keys: y rotation
- I and K keys: z rotation

- G and H keys: Arm rotation
```import serial
import time
import sys
import math
import pygame
from pygame.locals import *

from OpenGL.GL import *
from OpenGL.GLU import *

#Initialize Serial Port (On uart0)
port = serial.Serial("/dev/ttyS0",baudrate=9600,timeout=1)

#Map function variables
in_min=0
in_max=1024
out_min=0
out_max=90

def axes():
axesVerticies=(
(0,0,0),
(50,0,0),
(0,50,0),
(0,0,50),
(50,50,0),
(0,50,50),
(50,0,50)
)
axesEdges= (
(0,1),
(0,2),
(0,3),
(1,4),
(1,6),
(2,4),
(2,5),
(3,5),
(3,6)
)
axesSurfaces= (
(0,1,6,3),
(0,1,4,2),
(0,2,5,3)
)
axesColors = (
(1,.5,.5),#red
(.5,1,.5),#green
(.5,.5,1),#blue
(1,.5,.5),
(.5,1,.5),
(.5,.5,1),
(1,.5,.5),
(.5,1,.5),
(.5,.5,1),
(1,.5,.5),
(.5,1,.5),
(.5,.5,1)
)

glBegin(GL_QUADS)

i=0
for surface in axesSurfaces:
i+=1
glColor3fv(axesColors[i])
#Color the different surfaces red, blue, and green
for vertex in surface:
glVertex3fv(axesVerticies[vertex])

glEnd()

#Build Axes
glBegin(GL_LINES)
for edge in axesEdges:
for vertex in edge:
glVertex3fv(axesVerticies[vertex])
glEnd()

def radius(a):
radiusVerticies= (
(0,0,0),
(0,3,0),
(20,0,0),
(20,5,0),
(0,0,.5),
(0,3,.5),
(20,0,.5),
(20,5,.5)
)
radiusEdges= (
(0,1),
(0,2),
(0,4),
(1,3),
(1,5),
(2,3),
(2,6),
(3,7),
(4,5),
(4,6),
(5,7),
(6,7)
)
radiusSurfaces= (
(0,1,2,3),
(4,5,6,7),
(0,1,4,5),
(2,3,6,7),
(0,2,4,6),
(1,3,5,7)
)

glRotatef(90,0,0,1)#Position the Radius Vertically
glRotatef(90,1,0,0)
glTranslatef(25,25,25)#Move to center

glRotate(-a,0,0,-1)#Rotate Radius by a, a is changed on keypress
glTranslatef(-20,0,0)#Keep the Radius the same distance away after rotation

glBegin(GL_QUADS)

for surface in radiusSurfaces:
glColor3fv((1,1,1))#Color the Radius White

glEnd()

#Build the Radius
glBegin(GL_LINES)
for edge in radiusEdges:
for vertex in edge:
glVertex3fv(radiusVerticies[vertex])
glEnd()

def humerus():
humerusVerticies= (
(0,0,0),
(0,5,0),
(25,-1,0),
(25,6,0),
(0,0,.5),
(0,5,.5),
(25,-1,.5),
(25,6,.5)
)
humerusEdges= (
(0,1),
(0,2),
(0,4),
(1,3),
(1,5),
(2,3),
(2,6),
(3,7),
(4,5),
(4,6),
(5,7),
(6,7)
)
humerusSurfaces= (
(0,1,2,3),
(4,5,6,7),
(0,1,4,5),
(2,3,6,7),
(0,2,4,6),
(1,3,5,7)
)

#Position the Humerus Vertically
glRotatef(90,0,0,1)
glRotatef(90,1,0,0)
glTranslatef(25,25,25)

glBegin(GL_QUADS)

for surface in humerusSurfaces:
glColor3fv((1,1,1))#Color the Humerus White

glEnd()

#Build the Humerus
glBegin(GL_LINES)

for edge in humerusEdges:
for vertex in edge:
glVertex3fv(humerusVerticies[vertex])
glEnd()

def map(rcv,in_min,in_max,out_min,out_max):
#This function is from the arduino. It maps the values
#of one input, onto a different range, creating the output
return(rcv-in_min*(out_max-out_min)/(in_max-in_min)+out_min)

def serialInput():
#This function reads the serial port, uart0
#It then processes the data into a usable variable
#by the radius function

#This Function is broken and does not work currently
rcv = port.readline()
int(rcv)
a = map(rcv,in_min,in_max,out_min,out_max)
return(a)

def text(x,y,z):
#Print out the position of the camera
#Rotation isn't implemented, so it won't be accurate
print("Position:",x,y,z)
sys.stdout.write("\033[F")

def main():

#Build Pygame Window
pygame.init()
display = (800,800)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)

gluPerspective(45, (display/display), 0.1, 300.0)

#Set Camera Position
glRotatef(-45,-1,0,0)#Rotate Camera down
glRotatef(45,0,-1,0)#Rotate Camera turn
glTranslatef(-100,-150,-100)#Translate the camera to the corner

#I had problems figuring out how to properly arrange these functions
#There order will effect their outcomes; Changing one value can
#have drastically different results.
#Rotating the camera first is important, because if you
#translate it first, the rotation after it is then multiplied
#with the translation, causing it to do things you wouldn't
#expect.

#Variables

#Radius Angle
a=0

#Coordinates
x=0
y=0
z=0

#Keystates
KLEFT=False
KRIGHT=False
KUP=False
KDOWN=False
KJ=False
KL=False
KA=False
KD=False
KW=False
KS=False
KI=False
KK=False
KG=False
KH=False

#Infinite while loop
while True:
#Event Handling loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
#Kill Window

#KEYEVENT KEYDOWN HANDLING
if event.type == pygame.KEYDOWN:
#TRANSLATE
if event.key == pygame.K_LEFT:
KLEFT=True
if event.key == pygame.K_RIGHT:
KRIGHT=True
if event.key == pygame.K_UP:
KUP=True
if event.key == pygame.K_DOWN:
KDOWN=True
if event.key == pygame.K_j:
KJ=True
if event.key == pygame.K_l:
KL=True

#ROTATE
if event.key == pygame.K_a:
KA=True
if event.key == pygame.K_d:
KD=True
if event.key == pygame.K_w:
KW=True
if event.key == pygame.K_s:
KS=True
if event.key == pygame.K_i:
KI=True
if event.key == pygame.K_k:
KK=True
#Arm Control
#Optional key usage to move arm model

if event.key == pygame.K_g:
KG=True
if event.key == pygame.K_h:
KH=True

#KEYEVENT KEYUP HANDLING
if event.type == pygame.KEYUP:
#TRANSLATE
if event.key == pygame.K_LEFT:
KLEFT=False
if event.key == pygame.K_RIGHT:
KRIGHT=False
if event.key == pygame.K_UP:
KUP=False
if event.key == pygame.K_DOWN:
KDOWN=False
if event.key == pygame.K_j:
KJ=False
if event.key == pygame.K_l:
KL=False

#ROTATE
if event.key == pygame.K_a:
KA=False
if event.key == pygame.K_d:
KD=False
if event.key == pygame.K_w:
KW=False
if event.key == pygame.K_s:
KS=False
if event.key == pygame.K_i:
KI=False
if event.key == pygame.K_k:
KK=False

#Arm Angle
#Optional key usage to move arm model

if event.key == pygame.K_g:
KG=False
if event.key == pygame.K_h:
KH=False

#KEY PRESS ACTIONS

#TRANSLATE
if KLEFT==True:
glTranslatef(5,0,0)
x+=5
if KRIGHT==True:
glTranslatef(-5,0,0)
x-=5
if KUP==True:
glTranslatef(0,-5,0)
y+=5
if KDOWN==True:
glTranslatef(0,5,0)
y-=5
if KJ==True:
glTranslatef(0,0,-5)
z+=5
if KL==True:
glTranslatef(0,0,5)
z-=5

#ROTATE
if KA==True:
glRotate(5,0,1,0)
if KD==True:
glRotate(5,0,-1,0)
if KW==True:
glRotate(5,-1,0,0)
if KS==True:
glRotate(5,1,0,0)
if KI==True:
glRotate(5,0,0,-1)
if KK==True:
glRotate(5,0,0,1)

#Arm Angle
#Optional key usage to move arm model
if KG==True:
a+=5

if KH==True:
a-=5

#Another area I had trouble with was understanding how the camera
#translation, and rotation, all worked together. An assumption
#I had from when I started learning about 3D programming,
#was that when translate or rotate was called, the camera
#was doing the moving. This caused me major confusion, due
#to me then not understanding how to translate and rotate
#objects.

#This is an important distinction:
#	-The world is not static and the camera moves
#	-But the camera is static and the world moves
#Once I realized the second statement is true, then everything
#became much clearer, and I could continue working on my project
#with much less confusion.

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

#Get joystick input
#serialInput()

#Draw shapes
axes()

glPushMatrix()
radius(a)
glPopMatrix()

glPushMatrix()
humerus()
glPopMatrix()

text(x,y,z)

pygame.display.flip()
pygame.time.wait(10)

main()
```
##### Python Serial TestPython
This was a test program for testing out the serial port of the Ci20.
```import serial
port = serial.Serial("/dev/bus/usb/003",baudrate=9600,timeout=3.0)
#"/dev/bus/usb/003"
while True:
rcv = port.readline()
print(rcv)
```
##### Arduino Joystick to Servo ControlArduino
This program takes the input of the joysticks and maps the servos position to it.
```#include <Servo.h>

Servo servo1;
Servo servo2;

void setup() {

Serial.begin(9600);
while(!Serial){
//Wait until Serial port opens before sending data
}
pinMode(A0,INPUT);
pinMode(A1,INPUT);
pinMode(10,OUTPUT);
pinMode(9,OUTPUT);
servo1.attach(10);
servo2.attach(9);
}

void loop() {

servoFunction();

}

void servoFunction(){
//Read Joystick Position
int x = analogRead(A0);
int y = analogRead(A1);

//Send Joystick Position to the serial port
Serial.println(x);
Serial.println(' ');
Serial.println(y);

//Map motor position to joystick position
int s1position = map(x, 0, 1023, 500, 2300);
int s2position = map(y, 1023, 0, 600, 2400);
//Range of servo1: 500-2300
//Range of servo2: 600-2400
//Each Servo has a specific range for its end stops
//This was determined through trial and error

//Set motor position to joystick position
servo1.writeMicroseconds(s1position);
servo2.writeMicroseconds(s2position);

}
```

## Custom parts and enclosures

elbow_bracket.SLDPRT
elbow_hinge.SLDPRT
elbow_pulley.SLDPRT
Humerus.SLDPRT
shoulder_bracket.SLDPRT
shoulder_hinge.SLDPRT
ulna.SLDPRT
upper_pulley.SLDPRT
wrist_bracket.SLDPRT
wrist_connector.SLDPRT
wrist_hinge_axis_1.SLDPRT
wrist_hinge_axis_2.SLDPRT

## Schematics

Depicted in this schematic is the circuit to control the servos and connect them to the arduino. (Not Depicted: The Arduino's Tx line is also connected to the Ci20's UART0 RX Pin, located on GPIO pin 10 of the main header. The Ci20 also supplies 3.3v to the arduino's VIN pin, and the ground pins of the arduino and Ci20 are also connected.) ## Comments

#### Simple Programmable Robotic Arm

Project showcase by Ryan Chan

• 99,614 views
• 94 comments
• 256 respects

#### Robotic Arm Controlled by Human Arm

Project showcase by zezarandrade

• 7,910 views
• 0 comments
• 12 respects

#### Local and Remote Programmable Robotic Arm

Project tutorial by MJRoBot

• 23,637 views
• 7 comments
• 63 respects

#### LittleArm: Arduino Robot Arm

Project showcase by Slant Concepts

• 18,137 views
• 1 comment
• 76 respects

#### Record and Play Arduino 3D Printed Robotic Arm

Project tutorial by Mirko Pavleski

• 2,624 views
• 1 comment
• 14 respects

#### Android Controlled 6DoF Robotic Arm

Project in progress by Team Mans

• 14,118 views
• 0 comments
• 24 respects