Múltiples encoders por USB

Vamos a controlar la posición de varios encoders con el PIC 18F4550 y enviaremos esas posiciones al PC a través del USB. El programa del PC tomará los valores y los hará servir para controlar un brazo robot virtual. Dentro de las distinta formas de comunicación con USB he elegido la clase Bulk Transfer. Hace tiempo publiqué un ejercicio muy sencillo para ver cómo funcionaba, puedes hacer clic aquí para verlo. Esta clase es la más rápida y efectiva y no perturba otros procesos que se estén ejecutando.

A continuación muestro un vídeo para ver el resultado final y así te harás una idea. He usado un brazo robot virtual, de cinco grados de libertad, pero tu puedes darle la aplicación que desees; lo que muestro es sólo un ejemplo de aplicación.

Para descargar todo el proyecto haz clic aquí.

Contiene todos los códigos fuentes, librerías y driver necesario para la comunicación PIC-PC para el modo Bulk-Transfer.

(Si usas antivirus Avast has de añadir una exclusión para poder ejecutar el exe que contiene. Para analizar este o cualquier otro archivo puedes hacer clic aquí)

Está todo lo necesario, es decir, el código fuente y ejecutable de FreeBasic y CCS (.C y .HEX), los drivers de instalación para el PIC y el fichero "usb_desc_scope.h"

Si al compilar te da "error 58" elimina el fichero "createtex.bi" que está junto con el código fuente y demás ficheros. Lo puse para que pueda ser compatible con versiones antiguas del compilador FreeBasic.

Está probado en Windows XP, Colossus, Windows 7, 8 y 10.

Windows 7 da un error si la librería "MPUSBAPI.DLL" es antigua, cosa que no ha de sucederte porque he puesto en el zip de descarga la última versión (versión 6).

En el ZIP de descarga hay una carpeta con el driver necesario para hacer funcionar el modo "Bulk". Windows 8.x y 10 no permite por defecto instalar ningún driver que no esté firmado y ni siquiera te preguntará. Hay que lidiar un poco pero si sigues los pasos que indica esta web resolverás ese problema y podrás instalar el driver de la manera acostumbrada. Cuando lo hagas y reinicies has de ir directamente a la instalación del driver y ya no tendrás problemas.

El PIC tiene una patilla en el que es necesario poner un condensador (mayor de 450 nF) para estabilizar la tensión interna del USB; en los PICs 18F4550 sería entre el pin 18 y masa. De otro modo te daría fallos intermitentes o sencillamente no funcionaría la comunicación USB.

Veamos el programa para el PIC, hecho en CCS C:

#include <18F4550.h>
#fuses XTPLL,NOWDT,NOPROTECT,NOLVP,NODEBUG,USBDIV,PLL1,CPUDIV1,VREGEN  // XTAL=04MHz
                           
#use delay(clock=48000000)
#Byte PortA = 0xF80            // Dirección del puerto A para la familia 18Fxx5x.
#Byte PortB = 0xF81            // Dirección del puerto B para la familia 18Fxx5x.
#Byte PortC = 0xF82            // Dirección del puerto C para la familia 18Fxx5x.
#Byte PortD = 0xF83            // Dirección del puerto D para la familia 18Fxx5x (Sólo 40/44 pines).
#Byte PortE = 0xF84            // Dirección del puerto E para la familia 18Fxx5x.
#bit  A0    = PortA.0          // Creamos una asignación para hacer referencia al bit RA0 del PIC.

#define USB_HID_DEVICE     FALSE              //Disable HID
#define USB_EP1_TX_ENABLE  USB_ENABLE_BULK    //turn on EP1(EndPoint1) for IN bulk/interrupt transfers.
#define USB_EP1_RX_ENABLE  USB_ENABLE_BULK    //turn on EP1(EndPoint1) for OUT bulk/interrupt transfers.
#define USB_EP1_TX_SIZE    10                 //size to allocate for the tx endpoint 10 buffer.
#define USB_EP1_RX_SIZE    1                  //size to allocate for the rx endpoint 1  buffer.

#include <pic18_usb.h>      //Microchip PIC18Fxx5x Hardware layer for CCS's PIC USB driver.
#include "usb_desc_scope.h" //Enumerador PalitroqueZ.
#include <usb.c>            //handles usb setup tokens and get descriptor reports.

#use fast_io(a)
#use fast_io(b)
#use fast_io(c)
#use fast_io(d)
#use fast_io(e)

int8  Aux1=0, Aux2=0, Aux3=0, Aux4=0, Aux5=0;
int8  Enc1=0, Enc2=0, Enc3=0, Enc4=0, Enc5=0;
int8  Q=0;
int8  outa[1];
int16 cont[5];

void main()
{                                          
    set_tris_a(0b11111110);             //PortA
    set_tris_b(0b11111111);             //PortB
    set_tris_c(0b11111111);             //PortC
    set_tris_d(0b11111111);             //PortD
    set_tris_e(0b11111111);             //PortE
     
    setup_adc (adc_clock_div_32); //No ADC, no comparators...
    setup_adc_ports(NO_ANALOGS);
    setup_adc(ADC_OFF);
    setup_comparator(NC_NC_NC_NC);
    setup_vref(FALSE);
    port_b_pullups(FALSE);
     
    usb_init();  //init USB.
    usb_task();  //enable usb device and interruptions.
    usb_wait_for_enumeration();  //wait.
     
    outa[0]=0;
    do 
    {
        cont[Q]=0;      // Además de poner a cero el array Cont, aprovecho para hacer parpadear
        Q++;            // un LED al comienzo. Parpadeará una vez que el PIC y el PC se reconocen.
         
        A0=0;
        delay_ms(100);
        A0=1;
        delay_ms(100);
    }
    while (Q<6);
    Q=0;
     
    while (true)
    {    
      
       Aux1 = Enc1;                                  // Igualamos para luego saber si hay cambios en el encoder.   
       Enc1 = PORTB & 3;                             // Aislamos los dos primeros bits de PortB.
       If ((Aux1 == 2) && (Enc1 == 3))  { cont[0]++;}// Si hay flanco de subida, incrementa el contador.
       If ((Aux1 == 3) && (Enc1 == 2))  { cont[0]--;}// Si hay flanco de bajada, decrementa el contador.
       If (bit_test(PORTD, 7)   == 0)   { cont[0]=0;}// Si se ha pulsado el encoder, poner a cero el contador.
        
       Aux2 = Enc2;                            
        Enc2 = PORTB & 12;                                
       If ((Aux2 == 8)   && (Enc2 == 12))  { cont[1]++;}  
       If ((Aux2 == 12)  && (Enc2 == 8))   { cont[1]--;}  
       If (bit_test(PORTD, 6)   == 0)      { cont[1]=0;} 
        
       Aux3 = Enc3;                           
        Enc3 = PORTB & 48;                                                       
       If ((Aux3 == 32)  && (Enc3 == 48))  { cont[2]++;}
       If ((Aux3 == 48)  && (Enc3 == 32))  { cont[2]--;}
       If (bit_test(PORTD, 5)   == 0)      { cont[2]=0;}
        
       Aux4 = Enc4;                            
        Enc4 = PORTB & 192;                               
       If ((Aux4 == 128) && (Enc4 == 192)) { cont[3]++;}  
       If ((Aux4 == 192) && (Enc4 == 128)) { cont[3]--;}
       If (bit_test(PORTD, 4)     == 0)    { cont[3]=0;}
        
       Aux5 = Enc5;
       Enc5 = PORTD & 3;
       If ((Aux5 == 2) && (Enc5 == 3))  { cont[4]++;}
       If ((Aux5 == 3) && (Enc5 == 2))  { cont[4]--;}
       If (bit_test(PORTD, 3)   == 0)   { cont[4]=0;}
        
       If (usb_enumerated())
       {
           usb_put_packet(1, cont, 10, USB_DTS_TOGGLE);
        } 
    }  
}

El programa CCS C tiene mucha similitud con otro proyecto llamado "Cinco encoders y un solo PIC" que puedes ver haciendo clic aquí. Pero en vez de enviar la información a un LCD, esta vez la enviaremos al PC. El PIC envía los datos de todos los encoder a la vez al ordenador con una resolución de 16 bits. Por ello en la configuración del buffer de envío se declara:

#define USB_EP1_TX_SIZE 10

Ese 10 significa 10 bytes a enviar en cada paquete. Como el array 'Cont' está declarado como word (16 bits) hay que poner el total en bytes. Son 5 encoders de 16 bits (valores que comprenderán del 0 al 65535), por tanto: 5 encoders por 2 bytes es igual a 10 bytes. Multiplicamos por 2, porque cada word son 2 bytes.

He puesto como opción el poder recibir un byte del PC al PIC, sin embargo esta opción no la usamos realmente, pero lo he dejado como posibilidades futuras. En el método Bulk Transfer no hay problemas de bidireccionalidad de datos (enviar y recibir) y no perturba la lectura de los encoders. Por eso recomiendo fehacientemente usar este método de comunicación, porque además es el más rápido y fiable. Ya puse anteriormente en otro apartado de esta web un ejemplo sencillo de bidireccionalidad, en el que a partir de ahí puedes desarrollar el uso del USB sin problemas.

Programa sencillo para el PC (sólo datos)

Para simplificar el programa usé un truco en el que pongo las definiciones de la librería "MPUSBAPI.DLL" como fichero externo para ser invocado y unirse en el momento de la compilación. Se llama:

USB_Bulk_Transfer.bi

De esta forma nos concentraremos en lo esencial, que es recibir los datos del PIC. Este fichero externo no está el código expuesto aquí, es necesario que lo descargues (lo encontrarás en la descarga de todo el proyecto), si no, evidentemente, no podría compilar.

Al igual que en la programación para el PIC, el programa para el PC está configurado para poder hacer envío y recepción de datos, pero sólo haremos recepción de datos.

Echemos un vistazo al programa para el PC.

Primer programa, hecho en FreeBasic:

Screen 12
#Include Once "USB_Bulk_Transfer.bi"
Dim As String   Tecla
Cls
Do     
    
    'MPUSBWrite(myOutPipe, @Put2Pic(0),  1, @SentDataLength, 1000) ' Escritura en el PIC (No usado.)
     MPUSBRead (myInpPipe, @Get2Pic(0), 10, @RecdDataLendth, 1)    ' Lectura del PIC.
     
     Locate 10,1
     Print ( Get2Pic(1)*256)+Get2Pic(0) & Space(10)                ' Reconstrucción de los datos
     Print ( Get2Pic(3)*256)+Get2Pic(2) & Space(10)                ' recibidos.
     Print ( Get2Pic(5)*256)+Get2Pic(4) & Space(10)
     Print ( Get2Pic(7)*256)+Get2Pic(6) & Space(10)
     Print ( Get2Pic(9)*256)+Get2Pic(8) & Space(10)
     
     Tecla=InKey()
     
Loop Until Tecla=Chr(27)
MPUSBClose ( MyInpPipe )   ' Al finalizar el programa, liberamos la librería de la memoria.
MPUSBClose ( MyOutPipe )
End

Este programa nos sirve para tener un primer contacto y hacer las pruebas de funcionamiento y comprensión del ejercicio. Se reciben los datos del PIC y se muestra en pantalla en modo texto (no gráfico). Cuando hagas girar los encoders verás sus valores representados por números en el monitor.

Cabe destacar tres cuestiones:

1.) La primera es esta:

MPUSBRead (myInpPipe, @Get2Pic(0), 10, @RecdDataLendth, 1)

Aquí nos interesa tres detalles:

  1. El array "@Get2Pic(0)" siempre ha de comenzar por el valor cero, ya que es un puntero (por eso tiene la arroba delante de la variable) y apunta a la dirección donde comienza los datos, pero no es necesario que sepas de punteros.
  2. El valor 10 son los bytes que vamos a recibir.
  3. y el 1 final, la latencia en milisegundos para esperar un valor que no llega. Puedes ponerle el valor que quieras, pero para este ejemplo recomiendo dejarlo tal como está.

Todo lo demás que ves en esas funciones son cuestiones técnicas que de momento no nos interesa, pero si deseas profundizar haz clic aquí (el documento pertenece a 'Slalen' del foro www.todopic.com.ar )

2.) En los arrays "Put2PIC" y "Get2PIC" se almacenarán los valores de entrada y salida. Al ser un arrays, el primer valor se almacenará en 0, el segundo en 1, el tercero en 2, así hasta 64 posiciones posibles contando desde cero (0..63). "Put2PIC(0)" en este caso no lo usamos, pero lo pongo inhabilitado para en un futuro si quisieras hacer envío de información al PIC. En "Get2PIC" se almacenará los bytes recibidos, en este caso son 10 bytes (del 0 al 9).

3.) Como lo que recibimos son paquetes de bytes, hemos de separarlo de dos en dos bytes para reconstruir los datos que nos envía el PIC. Por ello hacemos lo siguiente:

Print ( Get2Pic(1)*256)+Get2Pic(0) & Space(10)

El segundo de cada par de bytes siempre hay que multiplicarlo por 256 y luego sumarle el primero de ese par de bytes, de esta forma volvemos a tener un número de 16 bits (0..65535). El "& Space(10)" no es importante, lo único que hace es añadir espacios, es decir, que cuando movamos el encoder, al representarlo en el monitor no se solapen las cifras al pasar de decenas a unidades, de centenas a decenas, etc. Si tienes dudas, quita esa parte, lo pruebas y verás qué sucede.

Aplicación para brazo robot virtual (con OpenGL)

Una vez que hemos experimentado con la parte sencilla del problema, lo demás es ponerle imaginación para darle una aplicación. Yo he preferido poner como ejemplo de aplicación el manejo de encoders externos a un simulador de brazo robot.

El siguiente programa es el que uso en el vídeo a modo de demostración.

Programa aplicado a un brazo robot virtual realizado en FreeBasic e implementación de OpenGL:

#Include Once "GL/gl.bi"
#Include Once "GL/glu.bi"
#Include Once "GL/glut.bi"
#Include Once "fbgfx.bi"                  
#Include Once "createtex.bi"
Declare Sub DibujaBrazo
'--------------- Adjunta el programa para el USB (Clase Bulk Transfer).
#Include Once "USB_Bulk_Transfer.bi"
 
'--------------- Declaración de variables para el Brazo ------------------
 
Dim Shared As Double   Pi, Rad, Grad, AngGiro, AngBrazo, AngAntBr, AngMunecA, AngMunecB
 
Dim Shared As Integer  LongBrazo, LongAntBr, LongMunec, LongDedos, AlturaH
Dim Shared As Single   LHombro, LBrazo, LAntBr, LMunec, LDedos, DistDedos=.11, Espacio, EscenaY,_
                       EscenaX, Distancia
'--------------------- Programa Principal -------------------------------- 
Color 14,1
Cls
Print "Pulsa:"
Print
Print "Flechas (Up, Down, Left, Right) -----> Mueve el escenario."
Print "Pagina Up/Down                  -----> Aleja/Acerca el escenario."
Print
Print "Pulsa Esc. (escape) para salir"
Sleep 1000
 
'---------Configurar escenario para el OpenGL---------
 
Screen 19, 16, , 2                  ' Este encabezamiento de OpenGL siempre se repite.          
glClearColor 0.0, 0.0, 0.0, 0.0     ' No hay que asustarse al ver este tipo de código
glMatrixMode GL_PROJECTION          ' porque cada vez que implementas OpenGL se copia
glViewport 0, 0, 800, 600           ' y pega esta parte.
gluPerspective  45.0, 800.0/600.0, 0.1, 255.0
gluLookAt 0,0,1,  0,0,0, 0,1,0 
glMatrixMode GL_MODELVIEW
glLoadIdentity
glClearColor 0.0, 0.0, 0.0, 0.5             
glClearDepth 1.0                            
glEnable GL_DEPTH_TEST           
glDepthFunc GL_LEQUAL                      
glHint GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST 
       
'-----Carga de Variables------
 
Pi   = Atn(1) * 4
Rad  = Pi  / 180
Grad = 180 /  Pi
 
'-=-=- Ajustes del Brazo: Dimensiones, distancias, etc. -=-=-
 
AlturaH   = 200     ' Altura del Hombro. (Distancia del suelo hasta el "hombro".)
LongBrazo = 250     ' Longitud Brazo.     Puedes modificar si quieres las dimensiones
LongAntBr = 300     ' Longitud AnteBrazo. de las articulaciones del brazo robot.
LongMunec = 100     ' Longitud Muñeca.
LongDedos =  50
EscenaX=0           ' Perspectiva Inicial del Escenario.
EscenaY=0
 
'------ No tocar los valores de estas variables --------------------
 
LHombro    = AlturaH  /100
LBrazo     = LongBrazo/100   ' Equivalente en dimensiones de OpenGL.
LAntBr     = LongAntBr/100   ' No tocar aquí.
LMunec     = LongMunec/100   
LDedos     = LongDedos/100   
Espacio    = LBrazo+LAntBr   
'-------------------------------------------------------------------
 
'-=-=-=-=-=-   Programa Principal    -=-=-=-=-=-
 
While Not MultiKey(FB.SC_ESCAPE)
      
      MPUSBRead (myInpPipe, @Get2Pic(0), 10, @RecdDataLendth, 1)
      
      AngGiro      =((Get2Pic(1)*256)+Get2Pic(0))
      If AngGiro   > 60000 Then AngGiro=(AngGiro-65536)
      
      AngBrazo     =((Get2Pic(3)*256)+Get2Pic(2))
      If AngBrazo  > 60000 Then AngBrazo=AngBrazo-65536
      
      AngAntBr     =((Get2Pic(5)*256)+Get2Pic(4))
      If AngAntBr  > 60000 Then AngAntBr=AngAntBr-65536
      
      AngMuneca    =((Get2Pic(7)*256)+Get2Pic(6))
      If AngMunecA > 60000 Then AngMunecA=AngMunecA-65536
      
      AngMunecb    =((Get2Pic(9)*256)+Get2Pic(8))
      If AngMunecB > 60000 Then AngMunecB=AngMunecB-65536
      
      If MultiKey(FB.SC_LEFT)     Then EscenaX=EscenaX-.5
      If MultiKey(FB.SC_RIGHT)    Then EscenaX=EscenaX+.5
      If MultiKey(FB.SC_UP)       Then EscenaY=EscenaY+.5
      If MultiKey(FB.SC_DOWN)     Then EscenaY=EscenaY-.5
  
      If MultiKey(FB.SC_PAGEUP)   Then Distancia=Distancia+.1
      If MultiKey(FB.SC_PAGEDOWN) Then Distancia=Distancia-.1
      
      DibujaBrazo
Wend

End
 
Sub DibujaBrazo          ' ------------------ Animación OpenGL --------------------
 
 glClear(GL_COLOR_BUFFER_BIT Or GL_DEPTH_BUFFER_BIT)  ' Es una especie de CLS en OpenGL.
 glLoadIdentity                                    
  
 '---------- Espacio --------------------
  
 glTranslatef       0.0, 0.0, Distancia-10            ' Esta parte nos permite mover todo
 glRotatef      20, 1.0, 0.0, 0.0                     ' el escenario en los ejes X e Y.
 glRotatef EscenaX, 0.0, 1.0, 0.0
 glRotatef EscenaY, 1.0, 0.0, 0.0
 
' ---------- Hombro ---------------------
 
 glBegin(GL_QUADS)                                                                    
   glColor3f   0.2,  0.2,  0.3                        ' Crea la superficie del suelo.
   glVertex3f  Espacio, -LHombro,  Espacio         
   glVertex3f -Espacio, -LHombro,  Espacio         
   glVertex3f -Espacio, -LHombro, -Espacio         
   glVertex3f  Espacio, -LHombro, -Espacio         
 glEnd  
 glRotatef   AngGiro, 0.0, 1.0, 0.0              
  
 glBegin(GL_QUADS)                                    ' Crea el hombro del brazo.
   glColor3f   0.1,  0.2,  0.4                        
   glVertex3f  0.4,  0.0, -0.6           
   glVertex3f -0.4,  0.0, -0.6                        ' Al igual que un cubo tiene 6 facetas (caras)
   glVertex3f -0.4,  0.0,  0.6                        ' aquí "dibujamos" las 6 facetas de la
   glVertex3f  0.4,  0.0,  0.6                        ' articulación del hombro.
                                                      ' Todo esto se repite en adelante con cada
   glColor3f   0.2,  0.2,  0.4                        ' articulación. Sólo cambia el tamaño y color.           
   glVertex3f  0.4, -LHombro,  0.6       
   glVertex3f -0.4, -LHombro,  0.6       
   glVertex3f -0.4, -LHombro, -0.6                       
   glVertex3f  0.4, -LHombro, -0.6                    ' Los valores en OpenGL están comprendidos entre
                                                      ' -1.0 y +1.0 por eso todo está con decimales.
   glColor3f   0.2,  0.1,  0.4                        
   glVertex3f  0.4,  0.0,  0.6                    
   glVertex3f -0.4,  0.0,  0.6                      
   glVertex3f -0.4, -LHombro,  0.6                     
   glVertex3f  0.4, -LHombro,  0.6                      
     
   glColor3f   0.1,  0.2,  0.5                           
   glVertex3f  0.4, -LHombro, -0.6                     
   glVertex3f -0.4, -LHombro, -0.6                         
   glVertex3f -0.4,  0.0, -0.6                         
   glVertex3f  0.4,  0.0, -0.6                          
      
   glColor3f   0.2,  0.1,  0.5                            
   glVertex3f -0.4,  0.0,  0.6                         
   glVertex3f -0.4,  0.0, -0.6                          
   glVertex3f -0.4, -LHombro, -0.6                        
   glVertex3f -0.4, -LHombro,  0.6                         
    
   glColor3f   0.3,  0.2,  0.5                             
   glVertex3f  0.4,  0.0, -0.6                         
   glVertex3f  0.4,  0.0,  0.6                            
   glVertex3f  0.4, -LHombro,  0.6                 
   glVertex3f  0.4, -LHombro, -0.6
 glEnd()
  
 '------------- Brazo ----------------
 glTranslatef          0.0, 0.0, 0.0
 glRotatef   AngBrazo+90, 0.0, 0.0, 1.0               ' Cada vez que veas un glRotatef significa que
                                                      ' la figura gira en un plano del espacio; aquí
 glColor3f       0.0,  1.0,  0.0                      ' representa el movimiento de una articulación.
 glutSolidSphere 0.6, 11,   11                        
                                                      
 glBegin(GL_QUADS)                                    ' Crea las 6 facetas (caras) del brazo.
   glColor3f  1.0,  0.0,  0.0
   glVertex3f LBrazo,  0.3, -0.4                          
   glVertex3f 0.0,  0.3, -0.4                        
   glVertex3f 0.0,  0.3,  0.4                           
   glVertex3f LBrazo,  0.3,  0.4          
      
   glColor3f  1.0,  0.5,  0.0                        
   glVertex3f LBrazo, -0.3,  0.4                    
   glVertex3f 0.0, -0.3,  0.4                   
   glVertex3f 0.0, -0.3, -0.4                   
   glVertex3f LBrazo, -0.3, -0.4                    
      
   glColor3f  1.0,  0.0,  0.5                          
   glVertex3f LBrazo,  0.3,  0.4                        
   glVertex3f 0.0,  0.3,  0.4                       
   glVertex3f 0.0, -0.3,  0.4                       
   glVertex3f LBrazo, -0.3,  0.4                    
      
   glColor3f  1.0,  0.2,  0.0                          
   glVertex3f LBrazo, -0.3, -0.4                        
   glVertex3f 0.0, -0.3, -0.4                      
   glVertex3f 0.0,  0.3, -0.4                        
   glVertex3f LBrazo,  0.3, -0.4                       
      
   glColor3f  1.0,  0.7,  0.2                          
   glVertex3f 0.0,  0.3,  0.4                         
   glVertex3f 0.0,  0.3, -0.4                        
   glVertex3f 0.0, -0.3, -0.4                        
   glVertex3f 0.0, -0.3,  0.4                        
      
   glColor3f  1.0,  0.8,  0.3                           
   glVertex3f LBrazo,  0.3, -0.4                          
   glVertex3f LBrazo,  0.3,  0.4     
   glVertex3f LBrazo, -0.3,  0.4
   glVertex3f LBrazo, -0.3, -0.4 
 glEnd()
                                                      
 '----------- Ant.Brazo -----------          
  
 glTranslatef   LBrazo, 0.0, 0.0
 glRotatef   AngAntBr-90, 0.0, 0.0, 1.0
  
 glColor3f       0.0,  0.0,  1.0
 glutSolidSphere 0.5,   11,   11
  
 glBegin(GL_QUADS)                                    'Crea las 6 facetas del antebrazo.
   glColor3f  0.0,  1.0,  0.0
   glVertex3f LAntBr,  0.3, -0.3
   glVertex3f 0.0,  0.3, -0.3
   glVertex3f 0.0,  0.3,  0.3
   glVertex3f LAntBr,  0.3,  0.3
  
   glColor3f  0.1,  1.0,  0.2
   glVertex3f LAntBr, -0.3,  0.3
   glVertex3f 0.0, -0.3,  0.3
   glVertex3f 0.0, -0.3, -0.3
   glVertex3f LAntBr, -0.3, -0.3
  
   glColor3f  0.2,  1.0,  0.5
   glVertex3f LAntBr,  0.3,  0.3
   glVertex3f 0.0,  0.3,  0.3
   glVertex3f 0.0, -0.3,  0.3
   glVertex3f LAntBr, -0.3,  0.3
  
   glColor3f  0.4,  1.0,  0.2
   glVertex3f LAntBr, -0.3, -0.3
   glVertex3f 0.0, -0.3, -0.3
   glVertex3f 0.0,  0.3, -0.3
   glVertex3f LAntBr,  0.3, -0.3
  
   glColor3f  0.0,  1.0,  0.5
   glVertex3f 0.0,  0.3,  0.3
   glVertex3f 0.0,  0.3, -0.3
   glVertex3f 0.0, -0.3, -0.3
   glVertex3f 0.0, -0.3,  0.3
  
   glColor3f  0.3,  1.0,  0.2
   glVertex3f LAntBr,  0.3, -0.3
   glVertex3f LAntBr,  0.3,  0.3
   glVertex3f LAntBr, -0.3,  0.3
   glVertex3f LAntBr, -0.3, -0.3
 glEnd()
  
 '------------- Muñeca -------------
  
 glTranslatef    LAntBr, 0.0, 0.0
 glRotatef    AngMunecA-90, 0.0, 0.0, 1.0
 glRotatef    AngMunecB, 1.0, 0.0, 0.0
  
 glColor3f       1.0,  0.0,  0.0
 glutSolidSphere 0.4,   13,   13
                                                      
 glBegin(GL_QUADS)                                    ' Crea las 6 facetas de la muñeca.
   glColor3f  0.0,  0.0,  1.0
   glVertex3f LMunec,  0.3, -0.2
   glVertex3f 0.0,  0.3, -0.2
   glVertex3f 0.0,  0.3,  0.2
   glVertex3f LMunec,  0.3,  0.2
  
   glColor3f  0.2,  0.0,  1.0
   glVertex3f LMunec, -0.3,  0.2
   glVertex3f 0.0, -0.3,  0.2
   glVertex3f 0.0, -0.3, -0.2
   glVertex3f LMunec, -0.3, -0.2
  
   glColor3f  0.0,  0.3,  1.0
   glVertex3f LMunec,  0.3,  0.2
   glVertex3f 0.0,  0.3,  0.2
   glVertex3f 0.0, -0.3,  0.2
   glVertex3f LMunec, -0.3,  0.2
  
   glColor3f  0.0,  0.4,  1.0
   glVertex3f LMunec, -0.3, -0.2
   glVertex3f 0.0, -0.3, -0.2
   glVertex3f 0.0,  0.3, -0.2
   glVertex3f LMunec,  0.3, -0.2
  
   glColor3f  LMunec,  0.0,  1.0
   glVertex3f 0.0,  0.3,  0.2
   glVertex3f 0.0,  0.3, -0.2
   glVertex3f 0.0, -0.3, -0.2
   glVertex3f 0.0, -0.3,  0.2
  
   glColor3f  0.2,  0.2,  1.0
   glVertex3f LMunec,  0.3, -0.2
   glVertex3f LMunec,  0.3,  0.2
   glVertex3f LMunec, -0.3,  0.2
   glVertex3f LMunec, -0.3, -0.2
 glEnd()   
  
 '------------ Dedos-------------
 glTranslatef    LMunec, 0.0, (DistDedos/100)
                                                      
 glBegin(GL_QUADS)                                    ' Crea la 6 facetas de la primera pinza.
   glColor3f   0.0,  0.6,  0.5
   glVertex3f  LDedos,  0.1, -0.1
   glVertex3f  0.0,  0.1, -0.1
   glVertex3f  0.0,  0.1,  0.1
   glVertex3f  LDedos,  0.1,  0.1
  
   glColor3f   0.0,  0.8,  0.4
   glVertex3f  LDedos, -0.1,  0.1
   glVertex3f  0.0, -0.1,  0.1
   glVertex3f  0.0, -0.1, -0.1
   glVertex3f  LDedos, -0.1, -0.1
  
   glColor3f   0.0,  0.5,  0.3
   glVertex3f  LDedos,  0.1,  0.1
   glVertex3f  0.0,  0.1,  0.1
   glVertex3f  0.0, -0.1,  0.1
   glVertex3f  LDedos, -0.1,  0.1
  
   glColor3f   0.0,  0.4,  0.4
   glVertex3f  LDedos, -0.1, -0.1
   glVertex3f  0.0, -0.1, -0.1
   glVertex3f  0.0,  0.1, -0.1
   glVertex3f  LDedos,  0.1, -0.1
  
   glColor3f   0.0,  0.3,  0.5
   glVertex3f  0.0,  0.1,  0.1
   glVertex3f  0.0,  0.1, -0.1
   glVertex3f  0.0, -0.1, -0.1
   glVertex3f  0.0, -0.1,  0.1
  
   glColor3f   0.0,  0.2,  0.6
   glVertex3f  LDedos,  0.1, -0.1
   glVertex3f  LDedos,  0.1,  0.1
   glVertex3f  LDedos, -0.1,  0.1
   glVertex3f  LDedos, -0.1, -0.1
 glEnd()
  
 glTranslatef    0.0, 0.0, (DistDedos*(-2))/100   
                                                      
 glBegin(GL_QUADS)                                    ' Crea la 6 facetas de la segunda pinza.
   glColor3f   0.0,  0.6,  0.5
   glVertex3f  LDedos,  0.1, -0.1
   glVertex3f  0.0,  0.1, -0.1
   glVertex3f  0.0,  0.1,  0.1
   glVertex3f  LDedos,  0.1,  0.1
  
   glColor3f   0.0,  0.8,  0.4
   glVertex3f  LDedos, -0.1,  0.1
   glVertex3f  0.0, -0.1,  0.1
   glVertex3f  0.0, -0.1, -0.1
   glVertex3f  LDedos, -0.1, -0.1
  
   glColor3f   0.0,  0.5,  0.3
   glVertex3f  LDedos,  0.1,  0.1
   glVertex3f  0.0,  0.1,  0.1
   glVertex3f  0.0, -0.1,  0.1
   glVertex3f  LDedos, -0.1,  0.1
  
   glColor3f   0.0,  0.4,  0.4
   glVertex3f  LDedos, -0.1, -0.1
   glVertex3f  0.0, -0.1, -0.1
   glVertex3f  0.0,  0.1, -0.1
   glVertex3f  LDedos,  0.1, -0.1
  
   glColor3f   0.0,  0.3,  0.5
   glVertex3f  0.0,  0.1,  0.1
   glVertex3f  0.0,  0.1, -0.1
   glVertex3f  0.0, -0.1, -0.1
   glVertex3f  0.0, -0.1,  0.1
  
   glColor3f   0.0,  0.2,  0.6
   glVertex3f  LDedos,  0.1, -0.1
   glVertex3f  LDedos,  0.1,  0.1
   glVertex3f  LDedos, -0.1,  0.1
   glVertex3f  LDedos, -0.1, -0.1
 glEnd()
  
 Flip                               '<----- Muestra el gráfico en el monitor. -------  
    
End Sub