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.
(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.
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.
Interlocking Joins
Download ESP
Female: Left
Male Join: Right
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.
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.
Titling
Download Stl files.
The titling mechanism contains 4 printed parts, you can change the length of the arm to create a longer reach and larger angle.
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.
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
Other possible ModularEnd-Effector
Milling machine with quadcopter motor
Suction gripper
Sensors on the end-effector for scanning
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.
For demoing the application, I demonstrated the positioning of the robot, the pressure sensitive gripper and the capability for scanning objects.
#include Servo firgelli; Servo bodyRotationServo; Servo armRotationServo; Servo liftDuleR; Servo liftDuleL; Servo armLift; int led = 13; int firgelliPin = 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); firgelli.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 firgelliMove(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 firgelli.write(120); delay(50); } else if (500>distanceTop){ firgelli.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); } } } void firgelliMove(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); firgelli.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); firgelli.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); } void firgelliGo(int go){ firgelli.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); firgelliGo(200); delay(1500); armLiftMove(60, 80, 20); delay(500); bodyRotationServoGo(90); delay(1000); // grab firgelliGo(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 firgelliGo(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); firgelli.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; firgelliGo(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; firgelliGo(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; }