Como hacer un Piccolo

En la siguiente documentación plantearemos como hacer un Piccolo, para ello empezaremos con lo básico

¿Qué es un Piccolo?

Piccolo es un CNC de bolsillo que nos sirve para dibujar en 2D, formas o figuras que le pidamos, trabaja en base a movimientos de 3 servo motores que nos permiten realizar el dibujo y un engranaje sencillo de piñones para dar dirección a nuestra herramienta de dibujo, esta puede ser un lápiz, marcador, bolígrafo, etc.

Materiales para elaborar un Piccolo

  • 3 Micro Servo SG90
  • 8 Tornillos de cabeza plana M3 de 10 mm
  • 3 Tornillos de cabeza plana M3 de 16 mm
  • 2 Tornillos M3 de 10 mm
  • 4 Tornillos M3 de 22 mm
  • 17 Tuercas de bajo perfil M3
  • 1 Llave hexagonal de 2.5 mm
  • 1 Llave hexagonal de 2 mm
  • 1 Cable USB microB
  • 1 Banda elástica
  • 2 Ataduras de cables (opcional)
  • 4 Pies de goma adhesiva de 6.4 mm (opcional)
  • 1 Tarjeta Nucleo STM32F411
  • Piezas de MDF: entre 2,75 y 3,10 mm
  • Partes acrílicas: entre 2,90 y 3,20 mm

Para elaborar las piezas de corte de MDF y las partes acrílicas se necesita realizar un corte láser, a continuación el esquema de corte

/media/uploads/manuelitoys/piccolo.jpg

A tener en cuenta: Los engranajes: iguales son más delgados que las otras partes acrílicas, pero mayores de 2 mm.

Para realizar la de forma correcta la fabricación del Piccolo, por favor visitar la guía oficial de fabricación

https://github.com/DiatomStudio/Piccolo/wiki/Piccolo-Fabrication-Guide

/media/uploads/manuelitoys/img-20170909-wa0000.jpg

¿ Comó ensamblar el Piccolo?

Para el montaje del Piccolo dirigirse a:

https://github.com/DiatomStudio/Piccolo/wiki/Piccolo-Assembly-Instructions

Y Seguir el paso a paso, debería quedar así:

/media/uploads/manuelitoys/img-20170909-wa0001.jpg

Ahora programaremos nuestra Tarjeta NucleoSTM32F411

Para la compilación de la misma usaremos el compilador de Mbed y ahí crearemos nuestro código

Crearemos un nuevo archivo y empezaremos a programar

Primero importaremos las librerías que vamos a usar y habilitaremos los puertos que usaremos para conectar nuestros Servos

/***** Librerías *****/

#include "mbed.h"

/**************PUERTOS I/O DE SISTEMA EMBEBIDO NUCLEO-F411RE*******************/

#define DEBUG 1
Serial command(USBTX, USBRX);
DigitalOut led(LED1);
PwmOut myServoX(PB_3); //Servo X
PwmOut myServoY(PB_4); //Servo Y
PwmOut myServoZ(PC_7); //Servo Z
InterruptIn button(USER_BUTTON); // interrupcion boton stop
volatile bool button1_pressed = false;
volatile bool button1_enabled = true;

Ahora definiremos las variables

 
#define BUFF_SIZE 6         // Tamaño del comando enviado desde CoolTerm
#define COMM_N 0            //
#define INITPARAMETER  1    //  La primer variable que leera en nuestro array
#define MAXPOS 50           // Posicion maxima de la matriz de dibujo  x,y en mm
#define POSDRAW 30          // Posicion del servomotor Z  para dibujar
#define PI 3.1415926        // Definir el valor de PI
uint8_t RSTEP = 5;              // Ini. variable de Resolucion para el dibujo 
uint8_t DET=1;
uint8_t  posx_old=0;     // Posición anterior del eje X
uint8_t  posy_old=0;     // Posición anterior del eje Y
uint8_t SSTIME = 100;  // Tiempo que se demora en ejecutar un dibujo

Ahora tenemos que definir que comando es que, cuando le enviemos la información por comando serial (consola)

#define LED_NC          0  
#define DOT_NC          1   
#define LINE_NC         2    
#define RECTANGULO_NC   3  
#define CIRCLE_NC       4   
#define HOME_NC         5  
#define RESOLUCION_NC   6  
#define TIEMPOPASOS_NC  7  
#define STOP_NC         8  
#define PAUSA_NC        9  
#define REANUDAR_NC     10 

Ahora crearemos las funciones para mover los servos X, Y, Z

 
int coord2pulse(float coord) // Convertir el movimiento de los motores (0 - 255)
    {
     if(0 <= coord <= MAXPOS)
        return int(750+coord*1900/50);// u6
   return 750; 
    }
    
void vertex2d(float x, float y) // Funcion para enviarle la posicion a (x,y)
    { 
    wait_ms(SSTIME);
    int pulseY = coord2pulse(y);
    int pulseX = coord2pulse(x);
    
    myServoY.pulsewidth_us(pulseY);
    myServoX.pulsewidth_us(pulseX);
    
    }
    
void draw() // Función para enviarle la posicion  de dibujo a Z, lo mueve hacia abajo. 
    {
    wait_ms(SSTIME*10);
    int pulseZ=coord2pulse(POSDRAW);    
    myServoZ.pulsewidth_us(pulseZ);
    wait_ms(SSTIME);
    }
void nodraw() // Función para enviarle la posicion de no dibujar a  Z, lo mueve hacia arriba. 
    {     
    wait_ms(SSTIME*10); 
    int pulseZ=coord2pulse(0);    
    myServoZ.pulsewidth_us(pulseZ);
    
    }
    
void initdraw(float x, float y) //Función para determinar desde donde inicia a dibujar el Piccolo como si existiera un plano cartesiano en el área del dibujo
    {
    vertex2d(x,y);
    wait_ms(SSTIME);
    draw();
}

Ahora creamos las funciones necesarias para leer lo que le enviamos a nuestro Piccolo en la consola

uint8_t buffer_command[BUFF_SIZE]={0,0,0,0,0,0}; // Matriz del Comando enviado, debe ser la misma cantidad de la variable definida anteriormente
 
void print_num(uint8_t val)   //
    {
    if (val <10)
        command.putc(val+0x30);
    else 
        command.putc(val-9+0x40);    
    }
    
void print_bin2hex (uint8_t val) // Imprimir el comando enviado en Hexadecimal
    {
    command.printf(" 0x");
    print_num(val>>4);
    print_num(val&0x0f);    
    }
 
void Read_command() // Leer el comando que se digito en CoolTerm
    {
    for (uint8_t i=0; i<BUFF_SIZE;i++)
        buffer_command[i]=command.getc();
    }
 
void echo_command() //
    {
    for (uint8_t i=0; i<BUFF_SIZE;i++)
        print_bin2hex(buffer_command[i]);     
    }
 
uint8_t check_command() // Verifica el ultimo valor del comando enviado '>' 
    {
    if (buffer_command[BUFF_SIZE-1]== '>'){    
        #if DEBUG
        command.printf("\nComando: ");
        print_bin2hex(buffer_command[COMM_N]);
        command.printf(" -> ");
        #endif
    return 1;
    }
        #if DEBUG
        command.printf("\n !!!!!!!!ERROR EN EL COMANDO!!!!!!!!!!!!!! -> ");
        echo_command();
        #endif
    return 0;         
    }

Ahora agregamos las funciones para poder dibujar en el Piccolo

void Led(int tm) //Función para definir el tiempo de led en milisegundos 
    { 
    #if DEBUG
    command.printf("\nTiempo led: %i seg\n", tm);
    #endif
    led=1;  
    wait(tm);
    led=0;                    
    }
    
void punto(uint8_t x, uint8_t y) //Función para dibujar un punto 
    {
    #if DEBUG
    command.printf("\nCoordenadas  x=%i, y=%i\n",x,y);
    #endif
    initdraw(x,y);
    nodraw();
    }         
    
void linea(float xi, float yi,  float  xf, float yf)
    { 
    #if DEBUG
    command.printf("\nCoordenadas xi=%f, yi=%f, xf=%f, yf=%f, resolucion: %i \n", xi,yi,xf,yf,RSTEP);
    #endif
    float xp,yp;
    float m=(yf-yi)/(xf-xi);
    float b=yf-(m*xf);
    #if DEBUG
    command.printf("\n b =%f, m=%f \n", b,m);
    #endif
    float nstep =(m/RSTEP);
    //nstep=RSTEP;
    #if DEBUG
    command.printf("\nstep = %f \n", nstep);
    #endif
     if ((abs(xf-xi))>abs(yf-yi)){
        if (xf>xi){
           initdraw(xp,yp);
           for (xp=xi; xp<=xf; xp+=RSTEP){
            yp =m*xp+b;
            vertex2d(xp,yp);
            #if DEBUG
            command.printf(" CASO 1: ( dx>dy & xf>xi ) Coordenadas x=%f,y=%f \n", xp,yp);
            #endif    
             } }
        else{
            float temp = xi;
            xi = xf;
            xf = temp;
            initdraw(xp,yp);
            for (xp=xi; xp<=xf; xp+=RSTEP){
            yp =m*xp+b;
            vertex2d(xp,yp);
            #if DEBUG
            command.printf(" CASO 2: ( dx>dy & xf<xi ) Coordenadas x=%f,y=%f \n", xp,yp);
            #endif    
            }}}
    else {
          if (yf>yi){
           initdraw(xp,yp);
           for (yp=yi; yp<=yf; yp+=RSTEP){
            xp=(yp-b)/m;
            vertex2d(xp,yp);
            #if DEBUG
            command.printf(" CASO 3: ( dy>dx & xf>xi ) Coordenadas x=%f,y=%f \n", xp,yp);
            #endif    
            }}
        else{
            float tempo = yi;
            yi = yf;
            yf = tempo;
            initdraw(xp,yp);
            for (yp=yi; yp<=yf; yp+=RSTEP){
            xp=(yp-b)/m;
            vertex2d(xp,yp);
            #if DEBUG
            command.printf(" CASO 4: ( dy>dx & xf<xi ) Coordenadas x=%f,y=%f \n", xp,yp);
            #endif      
             }                                  
         }   
        }
    nodraw(); 
    }
    
void Rectangulo(uint8_t x, uint8_t y, uint8_t a, uint8_t h)
    {
    #if DEBUG
    command.printf("\nCoordenadas x=%i, y=%i, ancho=%i, alto=%i, resolucion=%i\n", x,y,a,h,RSTEP);  
    #endif    
    uint8_t A=x+a;
    uint8_t B=y+h;     
    initdraw(x,y);
 
     for(uint8_t xi=x; xi<=(x+a); xi+=RSTEP){
         vertex2d(xi,y);
        #if DEBUG
        command.printf("Coordenadas x=%i,y=%i for 1\n", xi,y);
        #endif    
        }  
    for (uint8_t yi=y; yi<=(y+h); yi+=RSTEP){
        vertex2d(x+a,yi);
        #if DEBUG
        command.printf("Coordenadas x=%i,y=%i for 2\n", x+a,yi);
        #endif    
        }
    for(uint8_t xf=A; xf>x; xf= xf - RSTEP){
         vertex2d(xf,B);
        #if DEBUG
        command.printf("Coordenadas x=%i,y=%i for 3\n", xf,B);
        #endif    
        }  
    for (uint8_t yf=(y+h); yf>y; yf-=RSTEP){
        vertex2d(x,yf);
        #if DEBUG
        command.printf("Coordenadas x=%i,y=%i for 4\n", x,yf);
        #endif    
        } 
        vertex2d(x,y);
        #if DEBUG
        command.printf("Coordenadas x=%i,y=%i for 4\n", x,y);
        #endif 
    nodraw(); 
    }
 
void circle(uint8_t cx, uint8_t cy, uint8_t radio)    
    { 
    int y;
    int x;    
    vertex2d(cx,cy);
    #if DEBUG
      command.printf("\nCoordenadas xc =%i, yc =%i, Radio=%i \n",cx,cy,radio);
     #endif    
     
     for(double i=0; i<=PI/2 ;i+=((PI/2)/RSTEP))   
      {
      x=radio*cos(i);
      y=radio*sin(i);
      initdraw(x+cx,y+cy);
      #if DEBUG
      command.printf("Coordenadas x =%li, y =%li, R=%i, Resolcion:%i \n",x+cx,y+cy,radio,((PI/2)/RSTEP));
      #endif
      }  
       #if DEBUG
      command.printf("\n");
      #endif
  for(double i=PI/2; i<=PI ;i+=((PI/2)/RSTEP))   
      {
      x=radio*cos(i);
      y=radio*sin(i);
      vertex2d(x+cx,y+cy);
      #if DEBUG
      command.printf("Coordenadas x =%li, y =%li, R=%i, Resolcion:%i \n",x+cx,y+cy,radio, ((PI/2)/RSTEP));
      #endif
      }    
      #if DEBUG
      command.printf("\n");
      #endif
  for(double i=PI; i<=((3*PI)/2) ;i+=((PI/2)/RSTEP))   
      {
      x=radio*cos(i);
      y=radio*sin(i);
      initdraw(x+cx,y+cy);
      #if DEBUG
      command.printf("Coordenadas x =%li, y =%li, R=%i, Resolcion:%i \n",x+cx,y+cy,radio,((PI/2)/RSTEP));
      #endif
      }  
      #if DEBUG
      command.printf("\n");
      #endif
  for(double i=((3*PI)/2); i<=(2*PI) ;i+=((PI/2)/RSTEP))   
      {
      x=radio*cos(i);
      y=radio*sin(i);
      initdraw(x+cx,y+cy);
      #if DEBUG
      command.printf("Coordenadas x =%li, y =%li, R=%i, Resolcion:%i \n",x+cx,y+cy,radio,((PI/2)/RSTEP));
      #endif
     }
 nodraw();
 }
 
void home()
    {
    nodraw();
    vertex2d(0,0);
     #if DEBUG
    command.printf("\nCoordenada HOME x=0, y =0");
    #endif
    }
        
void resolucion(int res) // Funcion para definir la resolucion del dibujo 
    {
    RSTEP = res;
    #if DEBUG
    command.printf("\nResolucion definida en=%i \n", RSTEP);
    #endif
    }    
 
void TiempoPasos(int SST) //Funcion para definir el tiempo de led
    {
    SSTIME=SST;     
    #if DEBUG
    command.printf("\nTiempo en pasos definida en:%i\n",SSTIME);
    #endif              
    }
void sstime(uint8_t x, uint8_t y) //
{
    double dx=abs(x-posx_old);
    double dy=abs(y-posy_old);
    double dist= sqrt(dx*dx+dy*dy);
    wait_ms((int)(SSTIME*dist));
    posx_old =x;
    posy_old=y;
   
 }
 
void Stop() //Funcion para detener el programa  
    {
    exit(0);    
    }    
 
void Pausa(int p) //Funcion para definir el tiempo de led
    {   
    #if DEBUG
    command.printf("\n...Pausado...\n");
    #endif               
     }
    
void Reanudar(int det) //Funcion para definir el tiempo de led
    { 
    #if DEBUG
    command.printf("\nReanudando imagen...%i/n", DET);
    #endif    
         
    }
    

Una vez creadas las funciones, crearemos un switch case que es el que nos permitira decidir que comando realizar dependiento de lo que se envie por la consola

void command_exe()
{   
    switch (buffer_command[COMM_N]){ //Lee que comando se envio
 
        case (LED_NC): 
                        #if DEBUG
                        command.printf("    LED ON/OFF\n");
                        #endif
                        Led(buffer_command[INITPARAMETER]);
                    break;
                    
        case (DOT_NC):
                        #if DEBUG
                        command.printf("    PUNTO\n");
                        #endif
                        punto(buffer_command[INITPARAMETER], buffer_command[INITPARAMETER+1]);
                    break;
 
        case  (LINE_NC):
                        #if DEBUG
                        command.printf("    LINEA\n");
                        #endif
                        linea(buffer_command[INITPARAMETER],buffer_command[INITPARAMETER+1],buffer_command[INITPARAMETER+2],buffer_command[INITPARAMETER+3]);   
                    break;
 
        case (RECTANGULO_NC): 
                        #if DEBUG
                        command.printf("    RECTANGULO\n");
                        #endif
                        Rectangulo(buffer_command[INITPARAMETER],buffer_command[INITPARAMETER+1],buffer_command[INITPARAMETER+2],buffer_command[INITPARAMETER+3]);
                    break;
                    
         case (CIRCLE_NC):
                        #if DEBUG 
                        command.printf("    CIRCULO\n");
                        #endif
                        circle(buffer_command[INITPARAMETER],buffer_command[INITPARAMETER+1],buffer_command[INITPARAMETER+2]);
                    break;
 
        case (HOME_NC):
                        #if DEBUG
                        command.printf("    HOME\n");
                        #endif
                        home();
                    break;
                    
        case (RESOLUCION_NC):
                        #if DEBUG
                        command.printf("    RESOLUCION\n");
                        #endif
                        resolucion(buffer_command[INITPARAMETER]);
                    break;
        
       case (TIEMPOPASOS_NC):
                        #if DEBUG
                        command.printf("    TIEMPO EN PASOS\n");
                        #endif
                        TiempoPasos(buffer_command[INITPARAMETER]);
                    break;
                   
        case (STOP_NC):
                        #if DEBUG
                        command.printf("    STOP\n");
                        #endif
                      //  Stop(buffer_command[INITPARAMETER]);
                        
                    break;
                                        
        case (PAUSA_NC):
                        #if DEBUG
                        command.printf("    PAUSA\n");
                        #endif
                        Pausa(buffer_command[INITPARAMETER]);
                    break;  
                    
        case (REANUDAR_NC):
                        #if DEBUG
                        command.printf("    REANUDAR\n");
                        #endif
                        Reanudar(buffer_command[INITPARAMETER]);
                    break;                             
                                                   
    default:
        #if DEBUG
        command.printf("Comando  no encontrado\n");
        #endif        
    }
}

Por último procederemos a escribir el código que validara todas las funciones que hemos creado anteriormente

int main() 
{
    myServoX.period_ms(20);
    myServoY.period_ms(20);
    myServoZ.period_ms(20);
    
    #if DEBUG
    command.printf("inicio con debug\n");
    #else
    command.printf("inicio sin debug\n");
    #endif
    uint8_t val;
    button.fall(&Stop);
    while(1){
        val=command.getc();
        
        if (val== '<'){
            Read_command();         
            if (check_command()){
                command_exe();
                #if DEBUG
                echo_command();
                #endif              
                if(button1_pressed ==false){                  
    }
                                }
                        }   
           }
}

Ahora para poder enviarle los comandos por consola a nuestro Piccolo, se recomienda descargar la herramienta CoolTerm, los comandos que se envien por consola deberán ser en Hexadecimal, ya que asi lo decidimos en nuestro programa, un ejemplo de un comando sería:

3C 00 01 00 00 3E

En este caso 3C equivale a <, 00 la función del Led, 01, el tiempo que demorara el led en estar encendido, osea 1 segundo, y los otros comandos pueden ser cualquiera, ya que la función Led solo requiere de una variable (tm), pero hay que ponerlos sino nuestro programa tendrá un error y no hará nada, por último el 3E equivale a > para indicar que allí finaliza el comando.

Traducción de nuestro comando:

< 0 1 0 0 >


All wikipages