Etiquetas: , , , ,
0
(0)

Un buen ejercicio de programación es programar un brazo robótico para que reproduzca una trayectoria, como dibujar en una hoja de papel. Una vez que tenga este conocimiento, se puede programarlo para hacer lo que quiera.

Material

Objectivo

Para realizar este proyecto, necesitamos:

  • Montar el brazo robótico (Estructura)
  • Enlace los componentes electrónicos al motor y la potencia para permitir el control del brazo (Hardware)
  • Escriba el programa para que el robot siga una trayectoria (Software)

Estructura

Obj: la estructura debería poder llevar un bolígrafo en tres dimensiones de espacio.

Usamos un brazo robótico de escritorio Handy para este proyecto. Las partes están optimizadas para un pequeño servomotor y se imprimieron desde la base hasta la muñeca. Se diseñó e imprimió un bolígrafo adicional.
que puedes descargar aquí El brazo está montado con 4 micro servomotores y la base está unida a una placa de madera.

handy bras robotisé printer

Hardware

Obj: El microcontrolador debe estar conectada al servomotor y a la alimentación para controlar el servomotor con una señal PWM.

  • Arduino UNO

Arduino, son fáciles de usar y hay una gran comunidad para pedir ayuda. Como el programa pide un poco de memoria, elegimos un Arduino UNO.

  • Servocontrolador

El Arduino no puede alimentar tantos servomotores. Se debe agregar un controlador para distribuir la energía desde otra fuente.

Este tutorial fue seguido para hacer el controlador.

electronics for handy printer

Software

Objectivos

El objetivo del programa se puede dividir en tareas más pequeñas:

  • Describe las coordenadas de la trayectoria en el espacio
  • Transforme las coordinadas de la trayectoria en los ángulos deseados de las juntas del robot.
  • Controla la posición del robot
  • Convertir ángulos de robot en valor de comando del servomotor
  • Enviar comando a servomotor

Describe las coordenadas de la trayectoria en el espacio

La trayectoria elegida es un círculo por su simplicidad. Las ecuaciones paramétricas son:

Para las coordenadas z, tomamos la altura del lápiz con respecto a la muñeca: 20 mm.

Escribimos una función que genera la posición deseada con respecto al paso de tiempo:

void circleTraj(double R,double x,double y,double z,int i,double Te, float Xd[6]){
        Xd[0]=x+R*cos(2*PI*Te*i);
        Xd[1]=y+R*sin(2*PI*Te*i);
        Xd[2]=z;
        Xd[3]=0;
        Xd[4]=0;
        Xd[5]=0;
}

Transforme las coordinadas de la trayectoria en los ángulos deseados de las articulaciones del robot

El brazo robótico se puede describir con el sistema Denavit-Hartenberg como una cadena robótica con 4 enlaces articulados. Este método es interesante para controlar proyectos articulados con más de 3 articulaciones.

El método define un marco cartesiano para cada unión siguiendo la misma lógica que da la definición de parámetros que pueden usarse para describir matemáticamente el movimiento de los enlaces.

Definición de los parámetros con respecto a la geometría (parámetros DH)

jSigmajThetajDj (mm)Aj (mm)alphaj
10Th145090
20Th20750
30Th30750
40Th440-90

Para calcular los ángulos deseados a partir de la posición deseada, usamos el inverso de la matriz jacobiana definida con los parámetros denavit-hartenberg. Para calcular matrices usamos la biblioteca MatrixMath.h.

void computeAngle(){
    float J[6][nbLinks];
    float Jt[nbLinks][6];
    float JJt[nbLinks][nbLinks];
    float invJ[nbLinks][6];
    double detJ;
        //    compute jacobian
        jacobian(sig,a,al,d,th,q,nbLinks,(float*)J);

        // avoid singularity
        Matrix.Transpose((float*)J,6,nbLinks,(float*)Jt);
        Matrix.Multiply((float*)Jt, (float*)J, nbLinks, 6, nbLinks,(float*)JJt);

        
        detJ=detrm(JJt,nbLinks);
        // invert jacobian
        if(detJ>=1){
          Matrix.Invert((float*)JJt,nbLinks);
          Matrix.Multiply((float*)JJt,(float*)Jt,nbLinks,nbLinks,6,(float*)invJ);
        }
        
        for(int i=0;i<nbLinks;i++){
                qu[i]=0;
            for(int j=0;j<6;j++){
                qu[i]+=invJ[i][j]*Xu[j];
            }
        }
}

Convierta el ángulo de articulación en el comando del servomotor

Para convertir ángulos en comandos PWM, se debe conocer la posición del servomotor con respecto a los valores nominales de unión y PWM. Esta conversión también depende de los marcos cartesianos utilizados para describir el enlace mecánico con el método DH.

Servo/JointMin impulseMax impulseMin robotMax robot
15002500-PI/2PI/2
25002500PI0
36002600-PI/2PI/2
45002500-PI/2PI/2
int jointToImp(double x,int i) {
        int imp=(x - joint_min[i]) * (imp_max[i]-imp_min[i]) / (joint_max[i]-joint_min[i]) + imp_min[i];
        imp=max(imp,imp_min[i]);
        imp=min(imp,imp_max[i]);
        return imp;  
}

Enviar comando a servomotor

Para mover los servomotores, usamos la biblioteca Servo.h disponible en el IDE de Arduino. Escribimos una función que envía el comando PWM a cada servomotor de acuerdo con el ángulo solicitado.

void servoToImp(double q[nbLinks],int velocity){
  for (int i=0; i<nbLinks; i++) {
    servo[i].writeMicroseconds(jointToImp(q[i],i));
    delay(velocity);  
    }
}

Controla la posición del robot

Para controlar los servomotores, la posición deseada y real se debe comparar en tiempo real

La posición del robot se calcula con el ángulo de las articulaciones utilizando las matrices de transformación definidas con el método denavit-hartenberg.

        forwardKinematics(sig,a,al,d,th,q,nbLinks,X);

Controle se aplica para calcular la siguiente posición deseada

        circleTraj(20,165,0,20,k,Te,Xd);
        circleTraj(20,165,0,20,k+1,Te,Xk);
        for(int i=0;i<6;i++) Xu[i]=(Xk[i]-Xd[i]+gain*Te*(Xd[i]-X[i]));

Los ángulos del servomotor deseados se calculan para llegar a la siguiente posición deseada

        for(int i=0;i<nbLinks;i++) qN1[i]=q[i];
        computeAngle();
        for(int i=0;i<nbLinks;i++) q[i]=qN1[i]+qu[i];

El comando PWM se aplica a todos los servos.

        servoToImp(q,10);


Código completo

la biblioteca cambió de MatrixMath.h

/*****************************************************************\
* Project : Handy
* Author : X. Wiedmer 
* Version : V01
* Date : 08/06/2017
* Revision : 
* Summary :
\*****************************************************************/
// Library
#include <SoftwareSerial.h>
#include <Servo.h>
#include <MatrixMathExt.h>
// Pin assignement
#define BASE 2 
#define SHOULDER 3 
#define ARM 4 
#define FOREARM 5
#define WRIST 6
//paramètres Denavit-Hartenberg
   const double baseLength = 45;//mm
   const double shoulderLength = 10;//mm
   const double armLength = 75;//mm
   const double forearmLength = 75;//mm
   const double wristLength = 40;//mm
   const int nbLinks=4;
   double sig[nbLinks]={0,0,0,0};
   double a[nbLinks]={0,armLength,forearmLength,wristLength};
   double al[nbLinks]={PI/2,0,0,0};
   double d[nbLinks]={baseLength,0,0,0};
   double th[nbLinks]={0,3*PI/4,-PI/2,-3*PI/4+PI/2};
   
   double restPos[nbLinks]={0,3*PI/4,-PI/2,-3*PI/4+PI/2};
   
   
//paramètres servomoteur
   const int servoPin[nbLinks]={BASE,SHOULDER,ARM,FOREARM};
   Servo servo[nbLinks];
   const double joint_min[nbLinks]={-PI/2,PI,-2*PI/3,-PI/2};
   const double joint_max[nbLinks]={PI/2,0,PI/2,PI/2};
   const int imp_min[nbLinks]={500,500,600,500};
   const int imp_max[nbLinks]={2500,2500,2600,2500};
//paramètre du contrôle
   double Te=0.01; // pas de temps du contrôleur
   double gain=30; // gain du contrôle
   int N=(int)2/Te+1; // nombre d'étape pour parcourir 2 fois le cercle
// Variables
   float X[6],Xd[6],Xk[6],Xu[6];
   double qN1[nbLinks],qu[nbLinks];
   double q[nbLinks]={0,3*PI/4,-PI/2,-3*PI/4+PI/2};
   int firstShot = 0; // Pour que le programme ne s'exécute qu'une fois
/******************************************************************\
* PRIVATE FUNCTION: setup
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Initiate inputs/outputs
*
\******************************************************************/
void setup(){
 Serial.begin(9600); // Initialise le port série
 for (int i=0; i<nbLinks; i++) { // Initiatlise et positionne les servomoteurs au démarrage
   servo[i].writeMicroseconds(jointToImp(0,i));
   servo[i].attach(servoPin[i]);
   delay(500);
 }
}
/******************************************************************\
* PRIVATE FUNCTION: loop
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Main Function of the code 
\******************************************************************/
void loop(){
 if (firstShot == 0){
   //follow trajectory
   for(int k=0;k<N;k++){
       //Calcule la position du robot
       forwardKinematics(sig,a,al,d,th,q,nbLinks,X);
       //Calcule la prochaine position
       circleTraj(20,165,0,20,k,Te,Xd);
       circleTraj(20,165,0,20,k+1,Te,Xk);
       for(int i=0;i<6;i++) Xu[i]=(Xk[i]-Xd[i]+gain*Te*(Xd[i]-X[i]));
       
       //Calcule les angles moteurs pour atteindre la prochaine position
       for(int i=0;i<nbLinks;i++) qN1[i]=q[i];
       computeAngle();
       for(int i=0;i<nbLinks;i++) q[i]=qN1[i]+qu[i];
       
       //Envoie la commande au servomoteur
       servoToImp(q,10);
       // Affiche les angles moteur à chaque itération
       Serial.print("-----------------Iteration ");
       Serial.println(k);
       Serial.print("angle base = ");
       Serial.println(q[0]);
       Serial.print("angle bras = ");
       Serial.println(q[1]);
       Serial.print("angle avant-bras = ");
       Serial.println(q[2]);
       Serial.print("angle poignet = ");
       Serial.println(q[3]);
   }
   firstShot=1;
 }
 if(firstShot==1){
   Serial.println("robot au repos");
   restR();
   Serial.println("robot stop");
   stopR();
   firstShot=2;
 }
 
 }
// Decrit la trajectoire dans l'espace en focntion du temps
void circleTraj(double R,double x,double y,double z,int i,double Te, float Xd[6]){
       Xd[0]=x+R*cos(2*PI*Te*i);
       Xd[1]=y+R*sin(2*PI*Te*i);
       Xd[2]=z;
       Xd[3]=0;
       Xd[4]=0;
       Xd[5]=0;
}
// Calcule la matrice de transformation d'un lien robotique
void computeLinkTransform(double a,double al,double d, double th, float T[][4]){
   float trans[4][4] = {{cos(th),  -sin(th)*cos(al),  sin(th)*sin(al), a*cos(th)},
         {sin(th),   cos(th)*cos(al), -cos(th)*sin(al), a*sin(th)},
         {     0,            sin(al),         cos(al),        d      },
         {     0,                 0,               0,            1      } };
   Matrix.Copy((float*)trans,4,4,(float*)T);
}
// Calcule la position de la chaine robotique
void forwardKinematics(double* sig,double* a,double* al,double* d,double* th,double* q, int nbLinks, float X[6]){
   float A[4][4]={{1, 0, 0, 0},
          {0, 1, 0, 0},
          {0, 0, 1, 0},
          {0, 0, 0, 1}};
   float R[4][4];
   float Ti[4][4];
   // Compute transformation matrix
   for (int i=0; i<nbLinks; i++){
       if (sig[i]==0){
           th[i]=q[i];
       }else{
           d[i]=q[i];
       }
       //A=A*Ti
       computeLinkTransform(a[i],al[i],d[i],th[i],Ti);
       Matrix.Multiply((float*)A, (float*)Ti,4, 4,4, (float*)R);
       Matrix.Copy((float*)R,4,4,(float*)A);
   }
   X[0]=A[0][3];
   X[1]=A[1][3];
   X[2]=A[2][3];
   X[3]=0;
   X[4]=0;
   X[5]=0;
}
// Calcule un produit vectoriel
void CrossProduct1D(float* a, float* b, float* r){
   /*Ex: double v1[3]={1,0,0};
    double v2[3]={0,0,1};
    double w[3];
    CrossProduct1D(v1,v2,w);*/
 r[0] = a[1]*b[2]-a[2]*b[1];
 r[1] = a[2]*b[0]-a[0]*b[2];
 r[2] = a[0]*b[1]-a[1]*b[0];
}
// Calcule la matrice jacobienne d'une chaine robotique
void jacobian(double* sig,double* a,double* al,double* d,double* th,double* q, int nbLinks, float* J){
   float Ti[4][4];
   float O[nbLinks+1][3];
   float z[nbLinks+1][3];
   float A[4][4]={{1, 0, 0, 0},
      {0, 1, 0, 0},
      {0, 0, 1, 0},
      {0, 0, 0, 1}};
   float R[4][4];
   float v[4];
   O[0][0]=0;
   O[0][1]=0;
   O[0][2]=0;
   
   z[0][0]=0;
   z[0][1]=0;
   z[0][2]=1;
   
   float v0[4]={0,0,1,1};
   
   for (int i=0; i<nbLinks; i++){
   
       // Compute transformation matrix
       if (sig[i]==0){
           th[i]=q[i];
       }else{
           d[i]=q[i];
       }
       computeLinkTransform(a[i],al[i],d[i],th[i],Ti);
       // A=A*T[j];
       Matrix.Multiply((float*)A, (float*)Ti,4, 4,4, (float*)R);
       Matrix.Copy((float*)R,4,4,(float*)A);
       //Compute joint center and orientation vector
       O[i+1][0]=A[0][3];
       O[i+1][1]=A[1][3];
       O[i+1][2]=A[2][3];
   
       Matrix.Multiply((float*)A, (float*)v0,4, 4,1, (float*)v);
       //z{i}=[v(1); v(2); v(3)]-O{i};
       z[i+1][0]=v[0]-O[i+1][0];
       z[i+1][1]=v[1]-O[i+1][1];
       z[i+1][2]=v[2]-O[i+1][2];
   
   }
   
   //Jacobian
   float tempv[3];
   float cprod[3];
   
   for(int i=0;i<nbLinks;i++){
       if (sig[i]==0){
           // Ji=[z(i-1)xON-O(i-1); z(i-1)]
           Matrix.Subtract((float*)O[nbLinks], (float*)O[i],1,3,(float*)tempv);
           CrossProduct1D(z[i],tempv,cprod);
           J[0*nbLinks+i]=cprod[0];
           J[1*nbLinks+i]=cprod[1];
           J[2*nbLinks+i]=cprod[2];
           J[3*nbLinks+i]=z[i][0];
           J[4*nbLinks+i]=z[i][1];
           J[5*nbLinks+i]=z[i][2];
       }else{
           J[0*nbLinks+i]=z[i][0];
           J[1*nbLinks+i]=z[i][1];
           J[2*nbLinks+i]=z[i][2];
           J[3*nbLinks+i]=0;
           J[4*nbLinks+i]=0;
           J[5*nbLinks+i]=0;
       }
   }
}
// Calcul le déterminant d'une matrice
float detrm(float a[nbLinks][nbLinks],float k)
{
 float s=1,det=0,b[nbLinks][nbLinks];
 int i,j,m,n,c;
 if(k==1)
 {
   return(a[0][0]);
 }
 else
 {
   det=0;
   for(c=0;c<k;c++)
   {
     m=0;
     n=0;
     for(i=0;i<k;i++)
     {
       for(j=0;j<k;j++)
       {
         b[i][j]=0;
         if(i!=0&&j!=c)
         {
           b[m][n]=a[i][j];
           if(n<(k-2))
             n++;
           else
           {
             n=0;
             m++;
           }
         }
       }
     }
     det=det+s*(a[0][c]*detrm(b,k-1));
     s=-1*s;
     }
 }
 return(det);
}
// Calcule les angles moteur en fonction de la position désirée
void computeAngle(){
   float J[6][nbLinks];
   float Jt[nbLinks][6];
   float JJt[nbLinks][nbLinks];
   float invJ[nbLinks][6];
   double detJ;
       //    compute jacobian
       jacobian(sig,a,al,d,th,q,nbLinks,(float*)J);
       // avoid singularity
       Matrix.Transpose((float*)J,6,nbLinks,(float*)Jt);
       Matrix.Multiply((float*)Jt, (float*)J, nbLinks, 6, nbLinks,(float*)JJt);
       
       detJ=detrm(JJt,nbLinks);
       // invert jacobian
       if(detJ>=1){
         Matrix.Invert((float*)JJt,nbLinks);
         Matrix.Multiply((float*)JJt,(float*)Jt,nbLinks,nbLinks,6,(float*)invJ);
       }
       
       for(int i=0;i<nbLinks;i++){
               qu[i]=0;
           for(int j=0;j<6;j++){
               qu[i]+=invJ[i][j]*Xu[j];
           }
       }
}
// Convertie l'angle moteur en commande PWM
int jointToImp(double x,int i) {
       int imp=(x - joint_min[i]) * (imp_max[i]-imp_min[i]) / (joint_max[i]-joint_min[i]) + imp_min[i];
       imp=max(imp,imp_min[i]);
       imp=min(imp,imp_max[i]);
       return imp;  
}
// Intitialise servomoteur
void servoAttach(){
 for (int i=0; i<nbLinks; i++) {
   servo[i].attach(servoPin[i]);
 }   
}
// Envoie la commande PWM à chaque servomoteur
void servoToImp(double q[nbLinks],int velocity){
 for (int i=0; i<nbLinks; i++) {
   servo[i].writeMicroseconds(jointToImp(q[i],i));
   delay(velocity);  
   }
}
// Met le robot en position de repos
void restR(){
 servoToImp(restPos,500);
}
// Déconnecte les servomoteurs
void stopR(){
 for(int j=0;j<nbLinks;j++){
   servo[j].detach();
 }
}

Resultado

El robot sigue la trayectoria y puede dibujar un círculo en el papel

Si desea obtener más información sobre este proyecto, no dude en dejar un comentario o envíenos un mensaje.

Fuentes

Puede encontrar este robot aqui.

¿De cuánta utilidad te ha parecido este contenido?

¡Haz clic en una estrella para puntuar!

Promedio de puntuación 0 / 5. Recuento de votos: 0

Hasta ahora, ¡no hay votos!. Sé el primero en puntuar este contenido.

Ya que has encontrado útil este contenido...

¡Sígueme en los medios sociales!

¡Siento que este contenido no te haya sido útil!

¡Déjame mejorar este contenido!

Dime, ¿cómo puedo mejorar este contenido?