Skip to Content
Dan k
Chen
Modular Robot – Fully Assembled
Explorations Dan Chen / May 11, 2016

Application Demo

Introduction

The Modular Robot simply consist of pan, tilt and interchangeable end-effector modules. The number of modules and configuration creates a wide range of motions and complexity.

By making the communication modular, we can address each individual parts independently from a centralized or decentralized “command center”. The power-up sequence for each modules determines their placement in any configuration, from bottom up. Each module would broadcast it’s capability and available input and out put.

dc1

dc

(Future developement)
The graphical interface helps the user understand each module’s capability, status and end-effector position. It is also possible to have 2 configurations where 2 end-effector would meet for application such as assembling or 5 axis milling.

 

 

Modular Hardware Design

Body
4″ PVC pipe. You can use thicker walls on certain sections for stronger support. You can vary the length of the PVC pipe, depending on your application or cut them into small sections that you can combine for a longer length.

IMG_5560

Interlocking Joins
Download ESP

Untitled-1

Female: Left
Male Join: Right

Screen Shot 2015-05-05 at 12.35.54 AM

I used 6/32 X 3/8 Socket Cap Screw for the male part of the plate. There is no need to tap the whole before inserting the screw. You can vary the length of the screw if you have a thicker acrylic plate. The plate should fit nicely in the pipe.

The interlocking mechanism passes power and it’s easy to lock and unlock.
The power is passed through with DC barrel jack, this allows the join to rotate and click in place.
This allows the users the freedom of inserting new degree of freedom or changing different end-effector.
We can even create end-effector that assembles the robot itself. There are 4 angles that you can lock the module, simply by turn the join plate 90 degrees you have another configuration.

(Future developement)
It would be nice to have the interlocking disks securely fit onto the rim of the pipe. Right now I am using hot glue with friction fit.

Base
Download STL
The base stabilizes the robot, it is also a great place to hide the battery pack. We supply the power for the entire robot through the base. That also means the robot powers up from bottom up as you stack them.

Shoulders
Download EPS
The shoulder design would have more torque because the robot does not have to fight the gravity when lifting objects.

Modular Movement

The modular design allows you to add or subtract degree of freedom in your mechanism design.
You can also change out servo motor for stepper motor for your design.
The Interlocking Joins allows you to change the mounting direction, 90 degree at a time.

Rotation

This is a rotation plate assembly. Download the STl files.

DSC02207 DSC02196

Tilting

Download Stl files.
The tilting mechanism contains 4 printed parts, you can change the length of the arm to create a longer reach and larger angle.

IMG_5203

IMG_5218

Modular End-Effector

The modular End-Effector allows you to quickly switch out tool heads or add new capability to your application.

Linear Gripper drive by 2 Servos
Download STL
This gripper uses  2 small servos to drive the fingers. This is not a perfect method to deliver all the torque but it works fine if you can solve the friction issue. I applied some mold release as lubricant on the gripper track.

DSC02389

Linear Gripper drive by an actuator
Download STL
I used a compact linear actuator by Firgelli, it is fast, strong and light weight however it cost around $70 to $100. Visit their store 

DSC02224

Other possible ModularEnd-Effector
Milling machine with quadcopter motor
Suction gripper
Sensors on the end-effector for scanning

Modular Communication

The ESP8266 WiFi Module is a self contained SOC with integrated TCP/IP protocol stack that can give any microcontroller access to your WiFi network. They are cheap enough ($4.00) for us to embed in each individual module. Each ESP8266 will be connecting to a WiFi router, and they can broadcast the modular type and ID, status and capability.

We control and gather feedbacks for each Module using http get request. This allows us to control the robot all within a web browser without running any other server. However this method creates a huge latency. Communication with TCP seems to resolve the issue.

Using a small Micro controller that talks to ESP8266 allows us to fit the whole package nicely into each module. In my case, I used A-Star 32U4 Micro from https://www.pololu.com.

Untitled-1 DSC02074

DSC02077

Application: Grabbing Sushi

For demoing the application, I demonstrated the positioning of the robot, the pressure sensitive gripper and the capability for scanning objects.

Process Video

The Code (wired connection)

#include  

Servoactuonix;
Servo bodyRotationServo;
Servo armRotationServo;
Servo liftDuleR;
Servo liftDuleL;
Servo armLift;


int led = 13; 

intactuonixPin             = A4;
int bodyRotationServoPin    = A0;
int armRotationServoPin     = 6;
int liftDuleRPin            = 7;
int liftDuleLPin            = 8;
int armLiftPin              = 9;

int distanceSenPinBottom = A1;
int distanceSenPinTop    = A2;
int pressureSenPin       = A3;

int distanceBottom       = 0;
int distanceTop          = 0;
int pressure          = 0;

boolean bottomFound=false;
boolean topFound=false;

boolean sushiPlateFound=false;
boolean sushiFound=false;
boolean grabFroceOkay=false;
boolean grabbibgTheSushi=false;


int CurrentFirgelliPos;
int CurrentbodyRotationPos;
int CurrentArmLiftPos;
int CurrentliftDulePos;






void setup() {
  

  Serial.begin(9600);
  pinMode(led, OUTPUT);
  
  
  delay(20);
  initPosition();

 analogWrite(led, 100);
delay(500);
 analogWrite(led, 0);
delay(500);
 analogWrite(led, 100);
delay(500);
 analogWrite(led, 0);
}




void loop() {
  

      
      sens();
      
      // Serving Station:  waiting state / looking for human hand or plate 
       
       
      
      
      
      // Sushi Station: Move body to Sushi Station and find Sushi
      //Serial.print(bottomFound);

      if ( bottomFound ){
        //bottomActive=true;
        //pre parallelMove move up
        
        liftDuleMove(CurrentliftDulePos, 100, 10); // arm down, move back 
        CurrentliftDulePos=100;
        
        armLiftMove(CurrentArmLiftPos, 30, 10);//lower arm about to check sushi location        
        CurrentArmLiftPos=30;
        
        
        //premove the body
        bodyRotationServoMove(CurrentbodyRotationPos, 60, 20); // go away from general sushi station
        CurrentbodyRotationPos=60;
        
        
        
        
        
        liftDuleMove(CurrentliftDulePos, 90, 10); // arm down, move back 
        CurrentliftDulePos=90;
        //lower to scan
        //armLiftMove(25, 45, 10);// higher the number lower the arm            
        
        
        // Start scanning using body move
        //Find Sushi serving Plate: source of Sushi
        // stop if foud
        
        findSourcePlate();
         
     
        //liftDuleMove(100, 90, 10); // arm down
   
        Serial.println(sushiPlateFound);
        Serial.println("findSourcePlate Done");
        
        
        
        
        if(sushiPlateFound) {
          
            Serial.println("Start Grabbing Sushi");
              armLiftMove(CurrentArmLiftPos, 71, 20);  // higher the number lower the arm    
              CurrentArmLiftPos=71;
              
              sens();
             
             while (!topFound){
               sens();
             }
                 
               // delay(1000);
                 //sushiFound=true;
                 
                               // start grabbing sushi with pressure sensor
                               int loopa=0;
                               int loopb=93; // larger = tighter
                               int delayTime=25;
                               for(int fpos = loopa; fpos <= loopb ;  fpos += 1){
                                                          
                                                        float t1 = (float) (fpos - loopa) / (float) (loopb - loopa);
                                                        float t2 = (float) (loopb - fpos) / (float) (loopb - loopa);
                                                     actuonix.write(180-fpos); 
                                                      //delay(delayTime);
                                                      sens();
                                                      //grab sushi with sensor
                                                      if (grabFroceOkay){ CurrentFirgelliPos=fpos; fpos =loopb; grabbibgTheSushi=true; analogWrite(led, 100);}
                                                      delay( delayTime+pow (max(t1, t2), 6) * 30); 
                      
                                }//grab sushi end
              
              
                         delay(200);
                 
              
                           // have grabbed the sushi, we have the sushi
                           //if (grabbibgTheSushi){

                                     // lift sushi
                                     armLiftMove(CurrentArmLiftPos, 50, 20); // higher the number lower the arm    
                                     CurrentArmLiftPos=50;                         
                                     
                                     //move to soy sauce station 
                                     armRotationServoMove(90, 30, 15 ); // turn right
                                     delay(500);
                                     
                                     bodyRotationServoMove(CurrentbodyRotationPos, min(CurrentbodyRotationPos+30, 180), 35); // go to general sushi station
                                     CurrentbodyRotationPos=CurrentbodyRotationPos+30;
                                     
                                     delay(1000);
                                     //bodyRotationServoMove(loopb, 90, 20);
                                     
                                     //double dipping
                                     
                                     //down
                                     armLiftMove(CurrentArmLiftPos, 70, 5); // higher the number lower the arm
                                     CurrentArmLiftPos=70;
                                     //up
                                     armLiftMove(CurrentArmLiftPos, 65, 5); // higher the number lower the arm
                                     CurrentArmLiftPos=65;   
                                    //down
                                      armLiftMove(CurrentArmLiftPos, 70, 5); // higher the number lower the arm
                                     CurrentArmLiftPos=70;
                                     //up
                                     armLiftMove(CurrentArmLiftPos, 50, 5); // higher the number lower the arm
                                     CurrentArmLiftPos=50;                                       
    
    
                                     //move sushi back to source positon  
                                     armRotationServoMove(30, 90, 20 ); // turn right                                     
                                     //move sushi back to human
                                   
                                      bodyRotationServoMove(CurrentbodyRotationPos, 45, 25); // go to human drop sushi 
                                      CurrentbodyRotationPos=45;
                                      
                                     
                                     //down
                                      armLiftMove(CurrentArmLiftPos, 80, 20); // higher the number lower the arm
                                     CurrentArmLiftPos=80;
                                     
                                     
                                     //drop sushi
                                    actuonixMove(CurrentFirgelliPos, 0, 5);
                                     
                                     delay(500);
                                     //retrack
                                      /*
                                      liftDuleMove(CurrentliftDulePos, 110, 20); // arm down, move back 
                                      CurrentliftDulePos=110;
                                     */
                                     
                                     
                                     
                                                                           
                                      armLiftMove(CurrentArmLiftPos, 60, 20); // higher the number lower the arm
                                     CurrentArmLiftPos=60;
                                      
                           
                           //}
                           
                        
             
             
        }// end of sushi plate found
              
              delay(5000);
              reset();

    }// end of bottom found
        
       // parallelMove (70,100,50);        
        
        //liftDuleMove(45, 90, 30); // arm down
        //armLiftMove(90, 80, 50);//lower arm to check sushi location
        
        
        
        
      
      
      
      
      
      

}


//////////////////////////////////////////////
//////////////////////////////////////////////



/// Functions
void sens(){
    distanceBottom = analogRead(distanceSenPinBottom);    
    distanceTop    = analogRead(distanceSenPinTop);    
    pressure       = analogRead(pressureSenPin); 
 
      if (distanceTop<400)   { topFound=true;} else{topFound=false;}
      
      if (distanceBottom<400){ bottomFound=true;} else{bottomFound=false;}       if (pressure>8 ){ grabFroceOkay=true;}else{grabFroceOkay=false;}
      
      
    /*
    Serial.print(distanceBottom);
    Serial.print("   ");
    Serial.print(distanceTop);
    Serial.print("   ");
    Serial.println(pressure);
    */
    delay(20);
    
}


void grabber(){
        if (500<distanceBottom) {                         // in steps of 1 degree                         actuonix.write(120);                          delay(50);         }         else if (500>distanceTop){
                   actuonix.write(80); 
                    delay(5000);   
        }
}




void bodyRotationServoMove(int loopa, int loopb, int delayTime){
      sens();
              if (loopa > loopb){
                               for(int pos = loopa; pos>=loopb; pos-=1) {  
                                    float t1 = (float) (pos - loopb) / (float) (loopa - loopb);
                                    float t2 = (float) (loopa - pos) / (float) (loopa - loopb);                              
                                      bodyRotationServo.write(180-pos); 
                                      //delay(delayTime);  
                                     delay( delayTime+pow (max(t1, t2), 6) * 30); 
                              }   
              }
              else{
                               for(int pos = loopa; pos <= loopb; pos += 1){                                  float t1 = (float) (pos - loopa) / (float) (loopb - loopa);                                   float t2 = (float) (loopb - pos) / (float) (loopb - loopa);                                 bodyRotationServo.write(180-pos);                                  //delay(delayTime);                                 delay( delayTime+pow (max(t1, t2), 6) * 30);                                }                } } void armRotationServoMove(int loopa, int loopb, int delayTime){               if (loopa > loopb){
                               for(int pos = loopa; pos>=loopb; pos-=1) {  
                                    float t1 = (float) (pos - loopb) / (float) (loopa - loopb);
                                    float t2 = (float) (loopa - pos) / (float) (loopa - loopb);                              
                                      armRotationServo.write(180-pos); 
                                      //delay(delayTime);  
                                     delay( delayTime+pow (max(t1, t2), 6) * 30); 
                              }   
              }
              else{
                               for(int pos = loopa; pos <= loopb; pos += 1){                                  float t1 = (float) (pos - loopa) / (float) (loopb - loopa);                                   float t2 = (float) (loopb - pos) / (float) (loopb - loopa);                                 armRotationServo.write(180-pos);                                  //delay(delayTime);                                 delay( delayTime+pow (max(t1, t2), 6) * 30);                                }                } } void liftDuleMove(int loopa, int loopb, int delayTime){               if (loopa > loopb){
                               for(int pos = loopa; pos>=loopb; pos-=1) {  
                                    float t1 = (float) (pos - loopb) / (float) (loopa - loopb);
                                    float t2 = (float) (loopa - pos) / (float) (loopa - loopb);                              
                                    liftDuleR.write(180-pos); 
                                liftDuleL.write(pos); 
                                      //delay(delayTime);  
                                     delay( delayTime+pow (max(t1, t2), 6) * 30); 
                              }   
              }
              else{
                               for(int pos = loopa; pos <= loopb; pos += 1){                                  float t1 = (float) (pos - loopa) / (float) (loopb - loopa);                                   float t2 = (float) (loopb - pos) / (float) (loopb - loopa);                                 liftDuleR.write(180-pos);                                  liftDuleL.write(pos);                                  //delay(delayTime);                                 delay( delayTime+pow (max(t1, t2), 6) * 30);                                }                }            } void armLiftMove(int loopa, int loopb, int delayTime){               if (loopa > loopb){
                               for(int pos = loopa; pos >= loopb; pos-=1) {  
                                    float t1 = (float) (pos - loopb) / (float) (loopa - loopb);
                                    float t2 = (float) (loopa - pos) / (float) (loopa - loopb);                              
                                    armLift.write(180-pos); 
                                    //delay(delayTime);  
                                    delay( delayTime+pow (max(t1, t2), 6) * 30); 
                              }   
              }
              else{
                               for(int pos = loopa; pos <= loopb; pos += 1){                                   float t1 = (float) (pos - loopa) / (float) (loopb - loopa);                                   float t2 = (float) (loopb - pos) / (float) (loopb - loopa);                                   armLift.write(180-pos);                                    //delay(delayTime);                                   delay( delayTime+pow (max(t1, t2), 6) * 30);                                }               }     } voidactuonixMove(int loopa, int loopb, int delayTime){               if (loopa > loopb){
                               for(int pos = loopa; pos>=loopb; pos-=1) {  
                                    float t1 = (float) (pos - loopb) / (float) (loopa - loopb);
                                    float t2 = (float) (loopa - pos) / (float) (loopa - loopb);                              
                               actuonix.write(180-pos); 
                                      //delay(delayTime);  
                                     delay( delayTime+pow (max(t1, t2), 6) * 30); 
                              }   
              }
              else{
                               for(int pos = loopa; pos <= loopb; pos += 1){
                                 float t1 = (float) (pos - loopa) / (float) (loopb - loopa);
                                  float t2 = (float) (loopb - pos) / (float) (loopb - loopa);
                               actuonix.write(180-pos); 
                                //delay(delayTime);
                                delay( delayTime+pow (max(t1, t2), 6) * 30); 

                              } 
              }
}




// // // // // // //
// one angle drive




void bodyRotationServoGo(int go){
              bodyRotationServo.write(180-go); 
}

void armRotationServoGo(int go){
              armRotationServo.write(180-go); 
}


void liftDuleGo(int go){
              liftDuleR.write(180-go); 
              liftDuleL.write(go); 
             
}
void armLiftGo(int go){
              armLift.write(180-go); 
}
voidactuonixGo(int go){
 actuonix.write(180-go);
}



// tests


void t1(){
  // arm lift + lift dule + arm rotation
  
  int loopa=70;
  int loopb=100;
  
            for(int pos = loopa; pos <= loopb; pos += 1) // goes from 0 degrees to 180 degrees              {                                  // in steps of 1 degree                 armLiftGo(180-pos*1.25);                liftDuleGo(pos);                armRotationServoGo(pos*1.50);                delay(15);             }                           delay(1000);                                      for(int pos = loopb; pos>=loopa; pos-=1)     // goes from 180 degrees to 0 degrees 
            {                                
               armLiftGo(180-pos*1.25);
               liftDuleGo(pos);
               armRotationServoGo(pos*1.50);
               delay(15);   
            } 
}

void parallelMove(int loopa, int loopb, int delayTime){
             if (loopa > loopb){
                               for(int pos = loopa; pos>=loopb; pos-=1) {  
                                    float t1 = (float) (pos - loopb) / (float) (loopa - loopb);
                                    float t2 = (float) (loopa - pos) / (float) (loopa - loopb);                              
                                     armLiftGo(180-pos);
                                     liftDuleGo(180-pos);
                                     delay( delayTime+pow (max(t1, t2), 6) * 30); 
                              }   
              }
              else{
                               for(int pos = loopa; pos <= loopb; pos += 1){
                                 float t1 = (float) (pos - loopa) / (float) (loopb - loopa);
                                  float t2 = (float) (loopb - pos) / (float) (loopb - loopa);
                                armLiftGo(180-pos);
                                liftDuleGo(180-pos);
                                delay( delayTime+pow (max(t1, t2), 6) * 30); 

                              } 
              }  
  
  
}


void grabbingSushiHardcoded(){
 //init
      delay(5000);
     actuonixGo(200);
      delay(1500);

      armLiftMove(60, 80, 20); 
      delay(500);
      bodyRotationServoGo(90);
      delay(1000);
      
      // grab
     actuonixGo(120);
        delay(3000);

      //lift
      armLiftMove(80, 40, 20); 
            delay(1000);

      // move body
      bodyRotationServoMove(90, 130, 50);
      delay(2000);
      // move mini arm
      armLiftMove(40, 80, 20); 
      delay(100);
      //drop      
     actuonixGo(180);
      delay(500);
      //Go back
      armLiftMove(80, 40, 20); 

      bodyRotationServoMove(130, 90, 50);
      
      delay(5000000); 
}


void initPosition(){
  
      //servo tester
      /*
      bodyRotationServoMove(90, 80, 50);
      bodyRotationServoMove(80, 100, 50);
      bodyRotationServoMove(100, 90, 50);
      delay(500);*/
      
      
      
      /*
      armRotationServoMove(90, 70, 50);
      armRotationServoMove(70, 120, 50);
      //armRotationServoMove(120, 10, 50);
      delay(500);
      liftDuleMove(90, 70, 50);  // (outward / inward)
      liftDuleMove(70, 100, 50);  // (outward / inward)
      liftDuleMove(100, 70, 50);  // (outward / inward)
      delay(500);
      armLiftMove(90, 40, 50);// (outward / inward)
      armLiftMove(40, 80, 50);// (outward / inward)
      delay(500);
      
      
            */
            
            
            
            
            
 bodyRotationServo.attach(bodyRotationServoPin);
      bodyRotationServoMove(90, 45, 30);
      CurrentbodyRotationPos=45;bodyRotationServoGo(CurrentbodyRotationPos);delay(600);            
            
            
            
            
            
           actuonix.attach(firgelliPin);
  
  liftDuleR.attach(liftDuleRPin);
  liftDuleL.attach(liftDuleLPin);
  
  
  
       armRotationServo.attach(armRotationServoPin);



  armLift.attach(armLiftPin);
      armLiftMove(90, 40, 20);
      CurrentArmLiftPos=40; armLiftGo(CurrentArmLiftPos);delay(600);  // higher the number the lower the arm
      
      CurrentFirgelliPos=0;actuonixGo(CurrentFirgelliPos);delay(1000); // larger the wilder
      
      CurrentliftDulePos=90; liftDuleGo(CurrentliftDulePos);delay(1000); // larger the wilder
      
      
      
     
      armRotationServoGo(90);delay(600);
      Serial.println("Init DONE");
      
}




boolean findSourcePlate(){
         armLiftMove(CurrentArmLiftPos, 60, 10); // higher the number lower the arm  
         CurrentArmLiftPos=60;
         
         int loopa=60;
         int loopb=150;
         int delayTime=80;
         for(int bodypos = loopa; bodypos <= loopb ; bodypos += 1){
          
                                  float t1 = (float) (bodypos - loopa) / (float) (loopb - loopa);
                                  float t2 = (float) (loopb - bodypos) / (float) (loopb - loopa);
                                bodyRotationServo.write(180-bodypos); 
                                //delay(delayTime);
                                sens();
                                if (bottomFound){
                                    CurrentbodyRotationPos=bodypos; bodypos =loopb; sushiPlateFound=true;
                                    bodyRotationServoMove(CurrentbodyRotationPos, CurrentbodyRotationPos+3, 20);
                                    CurrentbodyRotationPos=CurrentbodyRotationPos+3; // move a little bit over
                                }
                                Serial.println("found the sushi plate");
                                delay( delayTime+pow (max(t1, t2), 2) * 30); 

        }
        return sushiPlateFound;
        
}

void reset(){
      bodyRotationServoMove(CurrentbodyRotationPos, 45, 20);CurrentbodyRotationPos=45;
      armLiftMove(CurrentArmLiftPos, 40, 20);CurrentArmLiftPos=40; delay(600);  // higher the number the lower the arm
      
      CurrentFirgelliPos=0;actuonixGo(CurrentFirgelliPos);delay(1000); // larger the wilder
      
      liftDuleMove(CurrentliftDulePos, 90, 20); // arm down, move back 
      CurrentliftDulePos=90;
      
      bottomFound=false;
      topFound=false;
      sushiPlateFound=false;
      sushiFound=false;
      grabFroceOkay=false;
      grabbibgTheSushi=false;
            
}

DAN K CHEN © 2024