📜 ⬆️ ⬇️

Creating a CNC machine from parts available with minimum plumbing work

We continue to review the activities of our Hackspace Club .

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

Parts for the machine tried to choose from the commercially available ones, many of which do not even require additional plumbing treatment.
')


The controller, we chose the Arduino Mega 2560 and that not to think a lot, the driver for the stepping motors used RAMPS 1.4 (like in the RepRap 3D printer).



The program of the controller was written using the algorithm of the state machine method. The last time I heard about him about 20 years ago at the institute, I don’t remember what subject I studied. It was a very good idea. The code turned out to be small and easily expandable without loss of readability (if in the future you need not only the XYZ axis, or use another G-code). The controller's program receives the G-code from the USB port and, in fact, gives the command to the XYZ axes' engines to move in a given direction. Who does not know, the G-code is a sequence of structures of the type 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, in the G-code there are many different constructions (for example, G90 - the absolute coordinate system is used, G91 is relative) and many modifications of the code itself. On the Internet, a lot has been described about him.

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

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

In this code of the state machine method, the variable takes the value of waiting for the first byte from the USB port, in the second case construction the data availability is checked and the variable _s takes the value get_cmd. Ie read the 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; 

Then we read everything that is in the port, the _s variable 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 <Stepper.h> #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<len)&&(cmd[c_i]<=32)) c_i++; i=0; while((c_i<len)&&(cmd[c_i]>=65)){ tag[n_t][i]=cmd[c_i]; i++; c_i++; while((c_i<len)&&(cmd[c_i]<=32)) c_i++; } if(i==0){ Serial.println("er2 cmd - 'no tag'"); _s=begin_cmd; break; } tag[n_t][i]=0; _s=get_val; break; case get_val: while((c_i<len)&&(cmd[c_i]<=32)) c_i++; i=0; while((c_i<len)&& ( (cmd[c_i]=='.')||(cmd[c_i]=='-')||((cmd[c_i]>=48)&&(cmd[c_i]<=57)) ) ){ val[n_t][i]=cmd[c_i]; i++; c_i++; while((c_i<len)&&(cmd[c_i]<=32)) c_i++; } if(i==0){ Serial.println("er3 cmd - 'no val'"); _s=begin_cmd; break; } val[n_t][i]=0; n_t++; _s=get_tag; if(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<n_t;i++){ Serial.print(i); Serial.print("="); Serial.print(tag[i]); Serial.print("("); Serial.print(val[i]); Serial.println(")"); } for (int k=0; k<=j; k++) { g_cmd[k].reset(); } j=0; i=0; while(i<n_t){ if(tag[i][0]=='N'){ g_cmd[j].n=(int)strtod(val[i],&trash); i++; while((i<n_t)&&(tag[i][0]!='N')){ //(g_cmd[j].g==1)&& if(tag[i][0]=='G'){ g_cmd[j].g=(int)strtod(val[i],&trash); g_cmd_prev = g_cmd[j].g; } else { g_cmd[j].g = g_cmd_prev; } if(tag[i][0]=='X') g_cmd[j].x=(double)strtod(val[i],&trash); if(tag[i][0]=='Y') g_cmd[j].y=(double)strtod(val[i],&trash); if(tag[i][0]=='Z') g_cmd[j].z=(double)strtod(val[i],&trash); i++; }//while((i<n_t)&&(tag[i]!="G")) j++; }//if(tag[i]=="G") else i++; }//while(i<n_t) amount_G=j; Serial.print("compilation cmd,output ("); Serial.print(amount_G); Serial.println("): "); for(j=0;j<amount_G;j++){ Serial.print(j); Serial.print("="); Serial.print("N");Serial.print(g_cmd[j].n);Serial.print(" "); Serial.print("G");Serial.print(g_cmd[j].g);Serial.print(" "); Serial.print("X");Serial.print(g_cmd[j].x);Serial.print(" "); Serial.print("Y");Serial.print(g_cmd[j].y);Serial.print(" "); Serial.print("Z");Serial.print(g_cmd[j].z);Serial.println(" "); } n_run_cmd=0; _s=run_cmd; break; case run_cmd: Serial.print("run cmd ["); Serial.print("N");Serial.print(g_cmd[n_run_cmd].n);Serial.print(" "); Serial.print("G");Serial.print(g_cmd[n_run_cmd].g);Serial.print(" "); Serial.print("X");Serial.print(g_cmd[n_run_cmd].x);Serial.print(" "); Serial.print("Y");Serial.print(g_cmd[n_run_cmd].y);Serial.print(" "); Serial.print("Z");Serial.print(g_cmd[n_run_cmd].z);Serial.print(" "); Serial.println("]"); int f_cmd_coord=0; if(g_cmd[n_run_cmd].g==90){ f_abs_coord=1; f_cmd_coord=1; Serial.println("change to ABS coord"); } if(g_cmd[n_run_cmd].g==91){ f_abs_coord=0; f_cmd_coord=1; Serial.println("change to REL coord"); } 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(")"); if(f_cmd_coord){if(++n_run_cmd>=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<unit_len;unit_step++){ if((abs(x-px)*unit_scale)>=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 finishes with the case run_cmd construction: where the control signal to the engine itself is given. Engine management could use the library #include <Stepper.h> but we wrote our function (char impstep - for a bipolar engine, void UNIimpstep - unipolar), so that we could not wait until one engine runs, 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 another engine driver, programmatically set the half-step or engine pitch. In the current version with RAMPS, 1/16 steps are obtained. If anyone is interested, you can write a separate article for hours about controlling stepper motors.

Now a little about iron.



The engines used 17HS8401, the most powerful of the NEMA17 that could be found on ebay. They also bought bearings and optical limit switches.


Everything else is domestic, native. The original idea with the guides, made them from long chrome handles for furniture, they are just 12 mm in diameter for bearings, they are up to a meter in length, and strength is quite enough. At the ends of the handles, drill holes and tap into threads. This allowed just a bolt to securely connect the guides with the bearing structure. For the Z axis, in general, the handle was attached to the whole 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 8 mm. The nut with the bearing and the bearing structure of the Y axis was connected using a connecting bracket. Staples bought in a specialized store for shop windows. You’ve probably seen such chrome constructions in the stores that have ties or shirts hanging on them, so these brackets are used to connect chrome tubes. The engine was connected to the shaft with a coupling, which was made from a piece of steel bar with a diameter of 14mm, drilled a hole in the center and a pair of holes on the side, for clamping with screws. You can not bother and buy ready on ebay on request cnc coupling their heap falls out. Bearing constructive we cut down the guillotine for 1000 p. The assembly of all this took not much time and got such a machine at the exit, there are no limit switches in the photo yet, the controller and the cutter motor are not installed.



Accuracy turned out just amazing, firstly a stepper motor strides 1/16 of a step, secondly a shaft with a fine thread. When a pen was inserted into the machine instead of a milling cutter, he drew a complex figure, then looked around the figure several more times, and in the figure you could see it was as if he drew once, looked at a magnifying glass trying to find another line. The rigidity of the machine is also good. Reeling only in bearings within the allowable limits of their own tolerance and fit. A little still reeling along the Y axis, well, here I think the constructive Z axis needs to be improved.

The photo turned out not quality, in the background the glass reflects. I do not know what kind of designer I am, but I’m just a photographer. Here's a little better.



Now about the control program. I do not remember why, but we decided to make our program, which transmits the ready G-code from the computer to the controller. Maybe they just did not find a suitable one.

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

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); -  . 


Another standard component, the msflexgrid table, was used, in which the G-code currently being executed is entered in real time. Those. This program simply opens the ready G-code and in small portions pushes it into the controller.

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

We have plans to make a more powerful machine on NEMA 23 engines, but for this you need to think of what to do with more powerful guides. In the controller firmware add the ability to change the speed of rotation of the spindle. It is especially interesting for us to install a camera and make something like a vision system, so that the machine itself determines the dimensions of the workpiece, calculates the initial coordinate of the workpiece along all axes in the program minimum. In the program, the maximum that the machine could control all stages of its work with the help of the camera, perhaps even made decisions to change the program. Well, for example, he saw that the roughness was more than acceptable, he took and sent the mill on the second circle to grind everything at a higher speed.

Hopefully, we will be able to continue to share our developments with you, respected hack readers.

Source: https://habr.com/ru/post/250385/


All Articles