20120827_202843-1

Project Tachikoma, prototype II

Alweer enige tijd geleden was de start van het Tachikoma project, waarbij het doel is een zo veel mogelijk op een Tachikoma lijkende robot te maken. In het eerste artikel werd de opstart vooral genoemd. In dit artikel een overzicht van de uitgebreide vervolg stap met meer design en implementatie.

Na wat puzzelen om de Tachikoma met het prototype been 1 ook te kunnen laten rond rijden, kwam de realisatie dat de robot zichzelf hiervoor in een hele onhandige onstabiele positie moest zetten om zijn wielen fatsoenlijk te kunnen laten ronddraaien. Een extra draaipunt was dus nodig, dit gaf ook extra flexibiliteit bij het in positie draaien van de wielen. Hiervoor is gebruik gemaakt van de Lynxmotion Low Profile Axis set.

Om een mogelijkheid over te houden om in de toekomst niet de wielen als voeten te gebruiken maar specifieke uitstekende delen aan het uiteinde van de poten met een sensor is hier ook nog naar gekeken en toegevoegd in het ontwerp. Zie de 3D tekeningen. Er wordt dan gebruik gemaakt van de Hexapod Foot Sensor. Door middel van de pols toevoeging kan het been zo ver draaien dat er gestuurd kan worden met de wielen of nog verder zodat er op de poten gelopen kan worden. De ingebouwde sensor kan aangeven dat er daadwerkelijk grond gevoeld word, waardoor lopen op niet vlakke ondergronden ook tot de mogelijkheid gaat behoren.

Inmiddels tevreden met de eerste opzet voor een volledig prototype met vier poten zijn alle nodige onderdelen besteld bij RobotShop. Gevestigd in Frankrijk en met een assortiment waar alle Amerikaanse producten (zoals Lynxmotion) ook onderdeel van zijn kunnen ze snel de gewenste onderdelen leveren. Het prototypen is verder makkelijker gemaakt door de aanschaf van een lab voeding.

Het aansturen van meerdere servo’s werd lastig met enkel een Arduino. Voor deze specifieke toepassing zijn er een aantal microcontrollers beschikbaar, na een kort keuze traject wat voornamelijk bestond uit kijken wat andere mensen samen met Arduino gebruiken is er gekozen voor de SSC-32 servo controller. De SSC-32 ondersteund de “group move” functionaliteit waardoor meerdere servo’s tegelijkertijd synchroon bewogen kunnen worden, een belangrijke eis voor het goed bewegen van meerdere servo’s op meerdere poten.

Na enig geëxperimenteer met de Arduino en SSC-32 was het tijd voor het bedenken van een mooi loop patroon in het Engels ook wel gait genoemd. In het geval van een vier potige robot ook wel quadropod genaamd wordt er vaak gekeken naar de natuur. Vaak wordt er voor dit soort insect achtige robots eerder gekozen voor zes poten, de zogenaamde hexapod robots vanwege de inherent stabielere verdeling van de poten. Tijdens een flinke zoek actie en wat nadenk werk kwam ik op een website waar de sluip beweging van een kat tot in detail beschreven werd. Inclusief een tijd diagram van de positie van elk been in x en y richting, dit was zeer bruikbare informatie. De kant tekening werd wel gemaakt dat ook de sluip beweging in de juiste volgorde gedaan moet worden, anders komt het zwaartepunt van de robot buiten de denkbeeldige driehoek te liggen tussen de drie poten die op de grond staan, verliest de robot zijn evenwicht en valt de robot dus om.

Nu er een ontwerp klaar was voor het loop patroon moest deze nog wel in een programma gegoten worden voor de Arduino. Tijdens de zoek actie voor een mooi loop patroon was ik een website tegen gekomen waar iemand ook met een vier benige robot aan de slag was geweest. Een klein stukje code voor het kunnen uitrekenen van verschillende waardes in een trapezium bracht na wat ombouwen wat er nodig was voor de robot. Uit de trapezium formule kwam een hoogte, die respectievelijk voor de voor achter en omhoog omlaag beweging omgerekend en geschaald wordt naar een puls lengte voor de servo’s. Door in de juiste volgorde en timing de juiste hoeken op te sturen naar de SSC-32 worden alle servo’s in positie gebracht in kleine op een volgende stapjes. Voor duidelijke tekst en uitleg van Arduino C code was de Arduino reference pagina een uitkomst.

Onderstaand een video van de huidige implementatie en design stap. De poten bewegen in de juiste richtingen op het juiste moment. De snelheid is te variëren (zowel sneller als langzamer). Het huidige loop patroon zal waarschijnlijk wel, wanneer de robot op de grond staat voor frictie zorgen die resulteert in slippen van de poten of het omhoog gaan van het frame dit doordat er in een boog tegen de grond afgezet wordt.

Een video van het Tachikoma prototype II loop patroon.

De huidige richting voor dit project is voor de robot om los te kunnen lopen met een batterij als voeding. Opties om te kunnen wisselen tussen een lab voeding en batterij gebruik zullen in acht genomen worden.

De code die gebruikt is voor het loop patroon in de video:

//=========================================================
// Header Files
//=========================================================
#include 
//=========================================================
// Variable decleration
//=========================================================
int tick = 1;
int moveamount = 0;
int tick_shifted = 0;
boolean debug_info = false;
// right front leg
// right front leg, up down, pin 12
int leg_rf_ud_pos;
int leg_rf_ud_pin = 12;
// right front leg, forward backward, pin 13
int leg_rf_fb_pos;
int leg_rf_fb_pin = 13;
// right front leg, wrist, pin 14
int leg_rf_wr_pos;
int leg_rf_wr_pin = 14;
// right front leg, continues rotation, pin 15
float leg_rf_cr_velocity;
int leg_rf_cr_pin = 15;
// right rear leg
// right rear leg, up down, pin 0
int leg_rr_ud_pos;
int leg_rr_ud_pin = 0;
// right rear leg, forward backward, pin 1
int leg_rr_fb_pos;
int leg_rr_fb_pin = 1;
// right rear leg, wrist, pin 2
int leg_rr_wr_pos;
int leg_rr_wr_pin = 2;
// right rear leg, continues rotation, pin 3
float leg_rr_cr_velocity;
int leg_rr_cr_pin = 3;
// left front leg
// left front leg, up down, pin 28
int leg_lf_ud_pos;
int leg_lf_ud_pin = 28;
// left front leg, forward backward, pin 29
int leg_lf_fb_pos;
int leg_lf_fb_pin = 29;
// left front leg, wrist, pin 30
int leg_lf_wr_pos;
int leg_lf_wr_pin = 30;
// left front leg, continues rotation, pin 31
float leg_lf_cr_velocity;
int leg_lf_cr_pin = 31;
// left rear leg
// left rear leg, up down, pin 16
int leg_lr_ud_pos;
int leg_lr_ud_pin = 16;
// left rear leg, forward backward, pin 17
int leg_lr_fb_pos;
int leg_lr_fb_pin = 17;
// left rear leg, wrist, pin 18
int leg_lr_wr_pos;
int leg_lr_wr_pin = 18;
// left rear leg, continues rotation, pin 19
float leg_lr_cr_velocity;
int leg_lr_cr_pin = 19;
//=========================================================
// SETUP: the main arduino setup function.
//=========================================================
void setup() {
Serial.begin(9600);
}
//=========================================================
// Loop: the main arduino main Loop function
//=========================================================
void loop() {
while(moveamount < 10){
while(tick <= 80){
if (debug_info == true){Serial.println("---");}
if (debug_info == true){Serial.print("Tick: "); Serial.println(tick);}
// calc right front leg servos
tick_shifted = pattern_shift(tick, 60);
leg_rf_ud_pos = trapazoidal(tick_shifted, 1);
leg_rf_fb_pos = trapazoidal(tick_shifted, 2);
leg_rf_wr_pos = leg_rf_fb_pos - 85;
if (debug_info == true){
Serial.print("leg_rf_ud_pos: "); Serial.print(leg_rf_ud_pos);
Serial.print(" leg_rf_fb_pos: "); Serial.print(leg_rf_fb_pos);
Serial.print(" leg_rf_wr_pos: "); Serial.print(leg_rf_wr_pos);
Serial.print(" leg_rf_cr_velocity: "); Serial.println(leg_rf_cr_velocity);
}         
leg_rf_ud_pos = angle_to_pulse_length(leg_rf_ud_pos);      
leg_rf_fb_pos = angle_to_pulse_length(leg_rf_fb_pos);      
leg_rf_wr_pos = angle_to_pulse_length(leg_rf_wr_pos);      
// calc right rear leg servos
tick_shifted = pattern_shift(tick, 0);
leg_rr_ud_pos = trapazoidal(tick_shifted, 1);
leg_rr_ud_pos = leg_rr_ud_pos * -1;
leg_rr_ud_pos = leg_rr_ud_pos + 25; //compensate for hardware error
leg_rr_fb_pos = trapazoidal(tick_shifted, 2);
leg_rr_fb_pos = leg_rr_fb_pos - 70;
leg_rr_wr_pos = leg_rr_fb_pos + 80;
if (debug_info == true){
Serial.print("leg_rr_ud_pos: "); Serial.print(leg_rr_ud_pos);
Serial.print(" leg_rr_fb_pos: "); Serial.print(leg_rr_fb_pos);
Serial.print(" leg_rr_wr_pos: "); Serial.print(leg_rr_wr_pos);
Serial.print(" leg_rr_cr_velocity: "); Serial.println(leg_rr_cr_velocity);
}      
leg_rr_ud_pos = angle_to_pulse_length(leg_rr_ud_pos);     
leg_rr_fb_pos = angle_to_pulse_length(leg_rr_fb_pos);     
leg_rr_wr_pos = angle_to_pulse_length(leg_rr_wr_pos);      
// calc left front leg servos
tick_shifted = pattern_shift(tick, 20);
leg_lf_ud_pos = trapazoidal(tick_shifted, 1);
leg_lf_ud_pos = leg_lf_ud_pos * -1;
leg_lf_fb_pos = trapazoidal(tick_shifted, 2);
leg_lf_fb_pos = leg_lf_fb_pos * -1;
leg_lf_wr_pos = leg_lf_fb_pos + 75;
if (debug_info == true){
Serial.print("leg_lf_ud_pos: "); Serial.print(leg_lf_ud_pos);
Serial.print(" leg_lf_fb_pos: "); Serial.print(leg_lf_fb_pos);
Serial.print(" leg_lf_wr_pos: "); Serial.print(leg_lf_wr_pos);
Serial.print(" leg_lf_cr_velocity: "); Serial.println(leg_lf_cr_velocity);
}
leg_lf_ud_pos = angle_to_pulse_length(leg_lf_ud_pos);      
leg_lf_fb_pos = angle_to_pulse_length(leg_lf_fb_pos);  
leg_lf_wr_pos = angle_to_pulse_length(leg_lf_wr_pos);      
// calc left rear leg servos
tick_shifted = pattern_shift(tick, 40);
leg_lr_ud_pos = trapazoidal(tick_shifted, 1);
leg_lr_ud_pos = leg_lr_ud_pos - 60;
leg_lr_fb_pos = trapazoidal(tick_shifted, 2);
leg_lr_fb_pos = leg_lr_fb_pos * -1;
leg_lr_fb_pos = leg_lr_fb_pos + 50;
leg_lr_wr_pos = leg_lr_fb_pos - 80;
if (debug_info == true){
Serial.print("leg_lr_ud_pos: "); Serial.print(leg_lr_ud_pos);
Serial.print(" leg_lr_fb_pos: "); Serial.print(leg_lr_fb_pos);
Serial.print(" leg_lr_wr_pos: "); Serial.print(leg_lr_wr_pos);
Serial.print(" leg_lr_cr_velocity: "); Serial.println(leg_lr_cr_velocity);
}
leg_lr_ud_pos = angle_to_pulse_length(leg_lr_ud_pos);      
leg_lr_fb_pos = angle_to_pulse_length(leg_lr_fb_pos);
leg_lr_wr_pos = angle_to_pulse_length(leg_lr_wr_pos);      
if (debug_info == true){Serial.println("---");}
// set all servos...
move(leg_rf_ud_pin, leg_rf_ud_pos, 
leg_rf_fb_pin, leg_rf_fb_pos, 
leg_rf_wr_pin, leg_rf_wr_pos,
leg_rf_cr_pin, leg_rf_cr_velocity,
leg_rr_ud_pin, leg_rr_ud_pos, 
leg_rr_fb_pin, leg_rr_fb_pos, 
leg_rr_wr_pin, leg_rr_wr_pos,
leg_rr_cr_pin, leg_rr_cr_velocity,
leg_lf_ud_pin, leg_lf_ud_pos, 
leg_lf_fb_pin, leg_lf_fb_pos,       
leg_lf_wr_pin, leg_lf_wr_pos,
leg_lf_cr_pin, leg_lf_cr_velocity,
leg_lr_ud_pin, leg_lr_ud_pos, 
leg_lr_fb_pin, leg_lr_fb_pos,
leg_lr_wr_pin, leg_lr_wr_pos,
leg_lr_cr_pin, leg_lr_cr_velocity,
130); // use range 130 - 250    
// tick +1 for next tick
tick ++;
// delay to wait untill servos have moved to 
// new pos before sending next setpoint
delay(5); // use range 130 - 250
}
tick = 1;    
moveamount ++;
}  
}
//=========================================================
// Functions
//=========================================================
// Servo Move Example: "#5 P1600 T1000 "
// Servo Group Move Example: "#5 P1600 #10 P750 T2500 "
void move(int leg_rf_ud_pin, int leg_rf_ud_pos, 
int leg_rf_fb_pin, int leg_rf_fb_pos, 
int leg_rf_wr_pin, int leg_rf_wr_pos,
int leg_rf_cr_pin, int leg_rf_cr_velocity,
int leg_rr_ud_pin, int leg_rr_ud_pos, 
int leg_rr_fb_pin, int leg_rr_fb_pos,
int leg_rr_wr_pin, int leg_rr_wr_pos,
int leg_rr_cr_pin, int leg_rr_cr_velocity,  
int leg_lf_ud_pin, int leg_lf_ud_pos, 
int leg_lf_fb_pin, int leg_lf_fb_pos,       
int leg_lf_wr_pin, int leg_lf_wr_pos,
int leg_lf_cr_pin, int leg_lf_cr_velocity,  
int leg_lr_ud_pin, int leg_lr_ud_pos, 
int leg_lr_fb_pin, int leg_lr_fb_pos,
int leg_lr_wr_pin, int leg_lr_wr_pos,
int leg_lr_cr_pin, int leg_lr_cr_velocity,
int time) {
Serial.print("#");
Serial.print(leg_rf_ud_pin);
Serial.print(" P");
Serial.print(leg_rf_ud_pos);
Serial.print(" #");
Serial.print(leg_rf_fb_pin);
Serial.print(" P");
Serial.print(leg_rf_fb_pos);
Serial.print(" #");
Serial.print(leg_rf_wr_pin);
Serial.print(" P");
Serial.print(leg_rf_wr_pos);
Serial.print(" #");
Serial.print(leg_rf_cr_pin);
Serial.print(" P");
Serial.print(leg_rf_cr_velocity);
Serial.print(" #");
Serial.print(leg_rr_ud_pin);
Serial.print(" P");
Serial.print(leg_rr_ud_pos);
Serial.print(" #");
Serial.print(leg_rr_fb_pin);
Serial.print(" P");
Serial.print(leg_rr_fb_pos);
Serial.print(" #");
Serial.print(leg_rr_wr_pin);
Serial.print(" P");
Serial.print(leg_rr_wr_pos);
Serial.print(" #");
Serial.print(leg_rr_cr_pin);
Serial.print(" P");
Serial.print(leg_rr_cr_velocity);
Serial.print(" #");
Serial.print(leg_lf_ud_pin);
Serial.print(" P");
Serial.print(leg_lf_ud_pos);
Serial.print(" #");
Serial.print(leg_lf_fb_pin);
Serial.print(" P");
Serial.print(leg_lf_fb_pos);
Serial.print(" #");
Serial.print(leg_lf_wr_pin);
Serial.print(" P");
Serial.print(leg_lf_wr_pos);
Serial.print(" #");
Serial.print(leg_lf_cr_pin);
Serial.print(" P");
Serial.print(leg_lf_cr_velocity);
Serial.print(" #");
Serial.print(leg_lr_ud_pin);
Serial.print(" P");
Serial.print(leg_lr_ud_pos);
Serial.print(" #");
Serial.print(leg_lr_fb_pin);
Serial.print(" P");
Serial.print(leg_lr_fb_pos);
Serial.print(" #");
Serial.print(leg_lr_wr_pin);
Serial.print(" P");
Serial.print(leg_lr_wr_pos);
Serial.print(" #");
Serial.print(leg_lr_cr_pin);
Serial.print(" P");
Serial.print(leg_lr_cr_velocity);  
Serial.print(" T");
Serial.println(time);
}
// function to calculate trapazioidal angles 
// for both up down and front back legs          
int trapazoidal(int tick, int option){
int a;
int b;
int c;
int d;
int y;
int amplitude;
int pulselength;
if (option == 1){
// option 1 = up down, a b c d amplitude values  
a = 0;
b = 6;
c = 15;
d = 20;
amplitude = 35;
}
// option 2 = back front, a b c d amplitude values  
if (option == 2){
a = 6;
b = 20;
c = 26;
d = 80;
amplitude = 45;
}  
if (tick >= a && tick < b) {     double AX = b - a;     double BX = amplitude;     double AB = sqrt((AX * AX) + (BX * BX));     double angle = asin(BX / AB);     y = tan(angle) * (tick - a);   }    else if (tick >= b && tick < c) {     y = amplitude;   }    else if (tick >= c && tick < d) {     double CX = amplitude;     double DX = c - d;     double CD = sqrt((CX * CX) + (DX * DX));     double angle = asin( CX / CD);     y = tan(angle) * (d - tick);   }    else {     y = 0;   }      if (option == 1){     y = y + 25; // start from 25 degrees + amplitude of 35 degrees   }      if (option == 2){     y = y + 10; // offset to prevent 0 angle and thus legs hitting each other   }   return y; } /*  Angle (degree) to pulse width in microseconds conversion part A pos value of 500 corresponds to 0.50mS pulse and a pos value of 2500 corresponds to a 2.50mS pulse.  Note normal or extended range usage. 0.50mS = pos value 500 = -90 degrees 1.50mS = pos value 1500 = 0 degree 2.5 mS = pos value 2500 = +90 graden use arduino map function */ int pulse_length_to_angle(int pulse_length){   int angle;   angle = map(pulse_length, 500, 2500, -90, 90);   return angle;   }    int angle_to_pulse_length(int angle){      int pulse_length;      pulse_length = map(angle, -90, 90, 500, 2500);   if (debug_info == true){     Serial.print("Converting ");     Serial.print(angle);     Serial.print(" degree(s) to ");     Serial.print(pulse_length);     Serial.println(" pulse length.");   }   return pulse_length;   } // shift walk pattern per leg function int pattern_shift (int tick, int offset){   // in case of no offset continue as normal   if (offset == 0) {     return tick;   }       else if (offset >= 1 && offset <= 80) {          // add +1 to tick untill 80 is reached or      // nothing is left of offset     while (offset > 0 && tick <= 80){       tick = tick + 1;       offset = offset - 1;     }      // if offset is still not 0, set tick to      // zero to start counting from 0 again     if (offset > 0){
tick = 0;
}
// continu adding +1 to tick untill nothing 
// is left of offset
while (offset > 0){
tick = tick + 1;
offset = offset - 1; 
}
return tick;
}
}

Leave a Reply