Creating a CNC machine from available parts with a minimum of locksmithing

    We continue to review the activities of our Hackspace Club .

    We have long dreamed of buying a CNC machine in our club. But we decided to do it ourselves. From scratch, starting from hardware and ending with software (controller firmware and control program). And we did it.

    We tried to choose parts for the machine from those available on the free market, many of which do not even require additional metalwork.



    The controller we chose was the Arduino Mega 2560, and so as not to think much, the stepper motor driver used RAMPS 1.4 (like the RepRap 3D printer).



    The controller program was written using the finite state machine method algorithm. The last time I heard about him about 20 years ago at the institute, I don’t remember what subject they studied. It was a very good idea. The code turned out to be small and easily extensible without loss of readability (if in the future we need not only the XYZ axis, or use another G-code). The controller program receives a G-code from the USB port and, in fact, instructs the XYZ axis motors to move in a given direction. Who does not know, the G-code is a sequence of designs like G1X10Y20Z10, which tells the machine to move along the X axis by 10 mm, Y by 20 mm and Z by 10 mm. In fact, there are many different constructions in the G-code (for example, G90 - the absolute coordinate system is used, G91 - the relative one) and many modifications of the code itself. On the Internet a lot about him is described.

    I will dwell on the description of the sketch (controller firmware).

    First, in the description of the variables, we prescribe to which output of the controller the motors and limit switches will be connected.

    In this code of the finite state machine method, the variable takes on the value to wait for the first byte from the USB port, in the second case construct the data is checked and the _s variable takes the value get_cmd. That is, read data from the port.

     switch(_s){
      case begin_cmd:         Serial.println("&"); //038
                              //to_begin_coord();
                              n_t=0;
                              _s=wait_first_byte;
                              len=0;
                              break;
      case wait_first_byte:   if(Serial.available()){
                                  Serial.print(">");
                                 _s=get_cmd;
                                  c_i=0;
                               }
                              break;
    

    Next, we read everything that is in the port, the variable _s is set to get_tag; those. go to the reception of the literal value of the G - code.

    case get_cmd:           c=Serial.read();
                              if(c!=-1){
                                  if(c=='~'){
                                    _s=get_tag;
                                    c_i=0;
                                    n_t=0;
                                    break;
                                  }
                               Serial.print(c);
                               if((c>=97)&&(c<=122))
                                 c-=32;
                               if( (c==32)||(c==45)||(c==46)||((c>=48)&&(c<=57))||((c>=65)&&(c<=90)) ){
                                 cmd[c_i++]=c;
                                 len++;
                               }
                              }
                              break;
    


    Well and so on.

    full code can be found here.
    #include 
    #define STEPS 48
    //#define SHAG 3.298701
    #define STEP_MOTOR 1
    double koefx = 1.333333;
    double koefy = 1.694915;
    Stepper stepper0(STEPS, 5, 4, 6, 3);
    Stepper stepper1(STEPS, 8, 9, 10, 11);
    Stepper stepper2(STEPS, 13, 12, 7, 2);
    int x,y,x1,m;
    int motor;
    int inPinX = 15;                   // кнопка на входе 22
    int inPinY = 14;                   // кнопка на входе 23
    int inPinZ = 24;                   // кнопка на входе 24
    int x_en = 62;
    int x_dir = 48;
    int x_step = 46;
    int y_en = 56;
    int y_dir = 61;
    int y_step = 60;
    const int begin_cmd=1;
    const int wait_first_byte=2;
    const int wait_cmd=3;
    const int get_cmd=4;
    const int test_cmd=5;
    const int get_word=6;
    const int get_tag=7;
    const int get_val=8;
    const int compilation_cmd=9;
    const int run_cmd=10;
    int abs_coord=1;
    const int _X=1;
    const int _Y=2;
    //const int =10;
    //const int =11;
    //const int =12;
    int _s=begin_cmd;
    const int max_len_cmd=500;
    char cmd[max_len_cmd];
    char tag[100][20];
    char val[100][20];
    int n_t=0;
    int c_i=0;
    char c;
    int i,j;
    int amount_G=0;
    int len=0;
    char*trash;
    int n_run_cmd=0;
    int g_cmd_prev; //ya предыдущее значение G
    class _g_cmd{
    public:
    _g_cmd(){
     reset();
    }
    int n; //ya
    int g;
    double x;
    double y;
    double z;
    void reset(void){
     n=g=x=y=z=99999;
    }
    };
    double _x,_y,_z;
    double cur_abs_x,cur_abs_y,cur_abs_z; //ya stoyalo int
    int f_abs_coord=1;
    _g_cmd g_cmd[100];
    void setup()
    {
      stepper0.setSpeed(150);
      stepper1.setSpeed(150);
      stepper2.setSpeed(1000);
      Serial.begin(9600);
      pinMode(inPinX, INPUT);
      pinMode(inPinY, INPUT);
      pinMode(inPinZ, INPUT);
      pinMode(x_en, OUTPUT);
      pinMode(x_dir, OUTPUT);
      pinMode(x_step, OUTPUT);
      pinMode(y_en, OUTPUT);
      pinMode(y_dir, OUTPUT);
      pinMode(y_step, OUTPUT);
      digitalWrite(x_en, 1);
      digitalWrite(y_en, 1);
       to_begin_coord();
      //UNIimpstep(12,13,2,7,3,1000);
      //UNIimpstep(12,13,2,7,3,-1000);
    }
    void to_begin_coord(void)
    {
     impstep(_X,-10000,1);
     impstep(_Y,-10000,1);
     cur_abs_x=cur_abs_y=cur_abs_z=0;
    }
    void loop()
    {
      switch(_s){
      case begin_cmd:         Serial.println("&"); //038
                              //to_begin_coord();
                              n_t=0;
                              _s=wait_first_byte;
                              len=0;
                              break;
      case wait_first_byte:   if(Serial.available()){
                                  Serial.print(">");
                                 _s=get_cmd;
                                  c_i=0;
                               }
                              break;
      case get_cmd:           c=Serial.read();
                              if(c!=-1){
                                  if(c=='~'){
                                    _s=get_tag;
                                    c_i=0;
                                    n_t=0;
                                    break;
                                  }
                               Serial.print(c);
                               if((c>=97)&&(c<=122))
                                 c-=32;
                               if( (c==32)||(c==45)||(c==46)||((c>=48)&&(c<=57))||((c>=65)&&(c<=90)) ){
                                 cmd[c_i++]=c;
                                 len++;
                               }
                              }
                              break;
      case get_tag:           while((c_i=65)){
                                 tag[n_t][i]=cmd[c_i];
                                 i++;
                                 c_i++;
                                 while((c_i=48)&&(cmd[c_i]<=57)) ) ){
                                 val[n_t][i]=cmd[c_i];
                                 i++;
                                 c_i++;
                                 while((c_i=len)
                                  _s=compilation_cmd;
                              break;
      case compilation_cmd:   Serial.println("");
                              Serial.print("compilation cmd,input (");
                              Serial.print(n_t);
                              Serial.println("): ");
                              for(i=0;i=amount_G) _s=begin_cmd; break;}
                             if(f_abs_coord){
                                Serial.println("zdes kosjak G90 ABS");
                                if (g_cmd[n_run_cmd].x==99999)
                                _x=0;
                                else
                               _x=g_cmd[n_run_cmd].x-cur_abs_x;
                                if (g_cmd[n_run_cmd].y==99999)
                                _y=0;
                                else
                               _y=g_cmd[n_run_cmd].y-cur_abs_y;
                               if (g_cmd[n_run_cmd].z==99999)
                                _z=0;
                                else
                               _z=g_cmd[n_run_cmd].z-cur_abs_z;
                             }else{
                               Serial.println("normalno G91 REL");
                               _x=g_cmd[n_run_cmd].x;
                               _y=g_cmd[n_run_cmd].y;
                               _z=g_cmd[n_run_cmd].z;                         
                             }
                             if((_x==0)&&(_y==0)&&(_z==0)){
                               Serial.println("exit: _x=0,_y=0,_z=0");
                               if(++n_run_cmd>=amount_G) _s=begin_cmd; break;
                             }
                            // _x=_x*koefx;
                            // _y=_y*koefy;
                             //_z=_z*koef;
                             double max_l=abs(_x); //ya
                             if(abs(_y)>max_l)
                                 max_l=abs(_y);
                             if(abs(_z)>max_l)
                                 max_l=abs(_z);
                             double unit_scale=90; // steps in 1.0
                             double unit_len=max_l*unit_scale,unit_step;
                             double px=0,py=0,pz=0,x=0,y=0,z=0,kx,ky,kz;
                             int all_x_steps=0,all_y_steps=0,all_z_steps=0;
                             kx=_x/unit_len;
                             ky=_y/unit_len;
                             kz=_z/unit_len;
                        //     Serial.print("unit_len - "); Serial.print(unit_len); Serial.print(" _x- "); Serial.print(_x); Serial.print(" max_l- "); Serial.println(max_l);
                        //     Serial.print("kx=");Serial.print(kx);Serial.print(" ky=");Serial.print(ky);Serial.print(" kz=");Serial.println(kz); 
                             if((kx==0)&&(ky==0)&&(kz==0)){if(++n_run_cmd>=amount_G) _s=begin_cmd; break;}
                             for(unit_step=0;unit_step=1){
                                impstep(_X,STEP_MOTOR*kx/abs(kx),1);  //stepper0.step(STEP_MOTOR*kx/abs(kx));
                                //Serial.print("x_step ");Serial.println(kx/abs(kx));
                                all_x_steps++;
                                px=x;
                              }
                              if((abs(y-py)*unit_scale)>=1){
                                impstep(_Y,STEP_MOTOR*ky/abs(ky),1); //stepper1.step(STEP_MOTOR*ky/abs(ky));
                                //Serial.print("y_step ");Serial.println(ky/abs(ky));
                                all_y_steps++;
                                py=y;
                              }
                              if((abs(z-pz)*unit_scale)>=1){
                                UNIimpstep(12,13,2,7,3,10*kz/abs(kz)); //stepper2.step(STEP_MOTOR*kz/abs(kz)); 
                                //Serial.print("z_step ");Serial.println(kz/abs(kz));
                                all_z_steps++;
                                pz=z;
                              }
                              x+=kx; y+=ky; z+=kz;
                           //   Serial.print(unit_step);Serial.print("  :  ");
                            //  Serial.print(x);Serial.print("  |  ");Serial.print(y);Serial.print("  |  ");Serial.println(z);                          
                             }
                              Serial.println("-----------------------------------------");
                              Serial.print("all_steps(");Serial.print(all_x_steps);Serial.print(",");Serial.print(all_y_steps);Serial.print(",");Serial.print(all_z_steps);Serial.print(")");
                              cur_abs_x+=_x; cur_abs_y+=_y; cur_abs_z+=_z;                         
                              Serial.print("cur_abs(");Serial.print(cur_abs_x);Serial.print(",");Serial.print(cur_abs_y);Serial.print(",");Serial.print(cur_abs_z);Serial.println(")");
                              Serial.println("-----------------------------------------");   
                             if(++n_run_cmd>=amount_G) _s=begin_cmd;
     }//switch(_s)
    }
    char end_button(int coord)
    {
      int but=0;
      if(coord==_X)
          but=digitalRead(inPinX);
      if(coord==_Y)
          but=digitalRead(inPinY);
      if(but){
       if(coord==_X)
           Serial.println("[ X out of range ]"); 
       if(coord==_Y)
           Serial.println("[ Y out of range ]"); 
      }
      return but;
    }
    char impstep(int coord,int kol,int f_test_coord)
    {
      int IN_en,IN_dir,IN_step,pause;
      pause=2; //35
      switch(coord){
       case _X:  IN_en=x_en; IN_dir=x_dir; IN_step=x_step;
         digitalWrite(IN_en, 0);
         break;
       case _Y:  IN_en=y_en; IN_dir=y_dir; IN_step=y_step;
         digitalWrite(IN_en, 0);
         break;
        }
      if(!f_test_coord)
         Serial.println("[ break step ]");
      //delay(100);
      if (kol<0)
        for (int i=0; i<=abs(kol); i++){
        if(f_test_coord&&end_button(coord)){
          impstep(coord,200,0);
          return 0;
        }
        digitalWrite(IN_dir, LOW);
        digitalWrite(IN_step, HIGH);
        delay(pause);
        digitalWrite(IN_step, LOW);
        delay(pause);
      }else
        for (int i=0; i <= kol; i++){
          if(f_test_coord&&end_button(coord)){
            impstep(coord,200,0);
            return 0;
          }
        digitalWrite(IN_dir, HIGH);
        digitalWrite(IN_step, HIGH);
        delay(pause);
        digitalWrite(IN_step, LOW);
        delay(pause);
        }
        digitalWrite(IN_en, 1);
        return 1;
      }
    void UNIimpstep(int IN1,int IN2,int IN3,int IN4,int pause,int kol)
    {
          //delay(100);
      if (kol<0)
        for (int i=0; i<=abs(kol); i++){
             digitalWrite(IN1, 0);
        digitalWrite(IN2, 1);
        digitalWrite(IN3, 0);
        digitalWrite(IN4, 0);
        delay(pause);
        digitalWrite(IN1, 1);
        digitalWrite(IN2, 0);
        digitalWrite(IN3, 0);
        digitalWrite(IN4, 0);
        delay(pause);
        digitalWrite(IN1, 0);
        digitalWrite(IN2, 0);
        digitalWrite(IN3, 0);
        digitalWrite(IN4, 1);
        delay(pause);
        digitalWrite(IN1, 0);
        digitalWrite(IN2, 0);
        digitalWrite(IN3, 1);
        digitalWrite(IN4, 0);
         delay(pause);
       }
       else
        for (int i=0; i <= kol; i++){
        digitalWrite(IN1, 1);
        digitalWrite(IN2, 0);
        digitalWrite(IN3, 0);
        digitalWrite(IN4, 0);
        delay(pause);
        digitalWrite(IN1, 0);
        digitalWrite(IN2, 1);
        digitalWrite(IN3, 0);
        digitalWrite(IN4, 0);
        delay(pause);
        digitalWrite(IN1, 0);
        digitalWrite(IN2, 0);
        digitalWrite(IN3, 1);
        digitalWrite(IN4, 0);
        delay(pause);
        digitalWrite(IN1, 0);
        digitalWrite(IN2, 0);
        digitalWrite(IN3, 0);
        digitalWrite(IN4, 1);
        delay(pause);
     }
    }
    



    The state machine ends with the case run_cmd construct: where, in fact, the control signal is supplied to the engine. Engine control could use the #include librarybut we wrote our own function (char impstep for a bipolar motor, void UNIimpstep for a unipolar motor) so that you would not have to wait until one engine worked out to send a signal to another. Well, for the future, a separate procedure will allow more flexible use of the capabilities of a stepper motor. For example, if we use a different engine driver, programmatically set the half-step or step of the engine. In the current version with RAMPS, 1/16 steps are obtained. If anyone is interested, you can write a separate article for hours about stepper motor control.

    Now a little about iron.



    The engines used 17HS8401, the most powerful of the NEMA17s they could find on ebay. There they bought bearings and optical limiters.


    Everything else is domestic, native. An original idea with guides, they were made of long chrome furniture handles, they are just 12 mm in diameter for bearings, they are sold up to a meter long, and there is enough strength. Holes were drilled at the ends of the handles and a thread was cut with a tap. This made it possible to simply reliably connect the guides with the supporting structure using a bolt. For the Z axis, in general, the handle was attached to the entire structural plate. The shaft is sold in any hardware store as a stud with a thread of any diameter. We used on 8 mm. Accordingly, the nuts are 8 mm. The nut with the bearing and the Y-axis supporting structure was connected using a connecting bracket. Staples bought in a specialized store for shop windows. They probably saw such chrome constructions in stores, on which ties or shirts hang, here they use such brackets for connecting chrome tubes. The engine was connected to the shaft by a coupling, which was made of a piece of steel bar with a diameter of 14 mm, drilling a hole in the center and a couple of holes on the side, for clamping with screws. You can not bother and buy ready-made on ebay at the request of cnc coupling a lot of them drop out. The bearing construct was cut down to us on a guillotine for 1000 r. Assembling all this did not take much time and we got such a machine at the exit, the trailer has not yet been installed in the photo, the controller, and the cutter motor is not installed. You can not bother and buy ready-made on ebay at the request of cnc coupling a lot of them drop out. The bearing construct was cut down to us on a guillotine for 1000 r. Assembling all this did not take much time and we got such a machine at the exit, the trailer has not yet been installed in the photo, the controller, and the cutter motor is not installed. You can not bother and buy ready-made on ebay at the request of cnc coupling a lot of them drop out. The bearing construct was cut down to us on a guillotine for 1000 r. Assembling all this did not take much time and we got such a machine at the exit, the trailer has not yet been installed in the photo, the controller, and the cutter motor is not installed.



    The accuracy was just amazing, firstly, the stepper motor steps 1/16 of the step, and secondly, the shaft with a fine thread. When a pen was inserted into the machine instead of a cutter, he drew a complex figure, then circled this figure several more times, and the figure shows as if he was drawing once, under a magnifying glass they tried to find another line. The rigidity of the machine is also good. It staggers only in dog-hair within the permissible limits of their own tolerance and landing. Still a little staggering along the Y axis, well, here I think the construct of the Z axis needs to be improved.

    The photo was not high-quality, in the background the glass reflects. I don’t know what kind of designer I am, but I’m just no photographer. Here is a little better.



    Now about the control program. I don’t remember why, but we decided to create our own program, which transmits the finished G-code from the computer to the controller. Maybe they just didn’t find a suitable one.

    The program is written in Microsoft Visual C ++, the libraries used were:

    Module: SERIALPORT.H
    Purpose: Declaration for an MFC wrapper class for serial ports
    Copyright 1999 by PJ Naughter. All rights reserved.
    The program is still raw, well, in a nutshell we use
    port.Open(8, 9600, CSerialPort::NoParity, 8, CSerialPort::OneStopBit, CSerialPort::XonXoffFlowControl);
    для открытия порта
    port.Write(text, l); - запись в порт
    port.Read(sRxBuf, LEN_BUF); - чтение порта.
    


    The standard component msflexgrid table was also used, in which the currently executing G-code is entered in real time. Those. this program simply opens the finished G-code and pushes it into the controller in small portions.

    The source code of the control program can be found here github.com/konstantin1970/cnc.git
    For understanding, I’ll add that the standard windows hyperterminal or putty does the same thing, pushes the data into the controller.
    The G-code itself can be done in any CAD / CAM system, for example, I liked ARTCAM.

    We plan to make a more powerful machine on NEMA 23 engines, but for this we need to figure out what to make more powerful guides from. In the controller firmware add the ability to change the spindle speed. It is especially interesting for us to install a camera and do something similar to the technical vision system, so that the machine itself determines the dimensions of the workpiece, calculates the initial coordinate of the workpiece on all axes in the program at least. There is a maximum in the program so that with the help of the camera the machine controls all the stages of its work, perhaps even made decisions to change the program. Well, for example, he saw that the roughness was more than acceptable, he took it and sent the cutter in the second circle to grind everything with a higher speed.

    I hope we can continue to share our developments with you, dear habretchiki.

    Also popular now: