クラブでCNCマシンを購入することを長い間夢見てきました。 しかし、私たちは自分でやることに決めました。 ハードウェアから始めてソフトウェア(コントローラーファームウェアと制御プログラム)で終わるゼロから。 そしてそれをやった。
私たちは、自由市場で入手可能なものから機械の部品を選択しようとしましたが、その多くは追加の金属加工さえ必要としません。
選んだコントローラーはArduino Mega 2560で、あまり考えないように、ステッピングモータードライバーはRAMPS 1.4(RepRap 3Dプリンターなど)を使用しました。
コントローラープログラムは、有限状態機械法アルゴリズムを使用して作成されました。 約20年前に研究所で彼について聞いたとき、彼らがどの科目を勉強したか覚えていません。 とてもいいアイデアでした。 コードは小さく、読みやすさを損なうことなく簡単に拡張できることが判明しました(将来的にXYZ軸だけでなく、別のGコードを使用する必要がある場合)。 コントローラプログラムは、USBポートからGコードを受信し、実際には、XYZ軸モーターに特定の方向に移動するように指示します。 誰も知らない、GコードはG1X10Y20Z10のような一連の設計であり、X軸に沿って10 mm、Yで20 mm、Zで10 mm移動するように工作機械に指示します。 実際、Gコードにはさまざまな構造があり(たとえば、G90-絶対座標系が使用され、G91-相対座標系)、コード自体の多くの修正があります。 インターネット上で彼について多くのことが説明されています。
スケッチの説明(コントローラーファームウェア)について説明します。
まず、変数の説明で、モーターとリミットスイッチが接続されるコントローラーの出力を規定します。
有限状態マシンメソッドのこのコードでは、変数は値を受け取ってUSBポートからの最初のバイトを待機し、2番目のケースではデータがチェックされ、_s変数が値get_cmdを取ります。 つまり、ポートからデータを読み取ります。
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;
次に、ポートにあるすべてのものを読み取り、変数_sをget_tagに設定します。 つまり G-コードのアルファベット値の受信に移動します。
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;
まあなど。
完全なコードはこちらにあります。
#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); } }
ステートマシンは、run_cmdコンストラクトで終了します。実際には、制御信号がエンジンに供給されます。 #include <Stepper.h>ライブラリを使用してエンジンを制御することもできますが、独自の関数(バイポーラモーターのchar impstep、ユニポーラーモーターのvoid UNIimpstep)を作成したため、あるエンジンが別のエンジンに信号を送信するまで待機できませんでした。 さて、将来的には、別の手順でステッピングモーターの機能をより柔軟に使用できるようになります。 たとえば、別のエンジンドライバーを使用する場合、エンジンのハーフステップまたはステップをプログラムで設定します。 RAMPSを備えた現在のバージョンでは、1/16ステップが取得されます。 興味がある人は、ステッピングモーター制御に関する記事を何時間も書くことができます。
今、鉄について少し。
エンジンは、ebayで見つけられるNEMA17の中で最も強力な17HS8401を使用しました。 そこでベアリングと光学リミッターを購入しました。
それ以外はすべて国内のネイティブです。 ガイド付きのオリジナルのアイデアは、長いクロム製の家具のハンドルで作られていて、ベアリングの直径はわずか12 mmで、長さ1メートルまで販売されており、十分な強度があります。 ハンドルの端に穴を開け、タップで糸を切りました。 これにより、ボルトを使用してガイドを支持構造に簡単に確実に接続することができました。 一般に、Z軸の場合、ハンドルは構造プレート全体に取り付けられ、シャフトは、あらゆる直径のねじ山を備えたスタッドとして、あらゆるハードウェアストアで販売されています。 8mmで使用しました。 したがって、ナットは8 mmです。 ベアリングとY軸支持構造を備えたナットは、接続ブラケットを使用して接続されました。 店の窓用に専門店で購入したステープル。 おそらく、ネクタイやシャツが掛かる店でそのようなクロム構造を見たでしょう。ここでは、そのようなブラケットはクロムパイプを接続するために使用されます。 エンジンはカップリングによってシャフトに接続されました。カップリングは直径14 mmの鉄棒で作られ、中央に穴を開け、側面にいくつかの穴を開けて、ネジで固定します。 あなたはそれらの多くがドロップアウトするcncカップリングのリクエストでebayで既製を気にし、購入することはできません。 ベアリング構造物は、ギロチンで1000 rカットされました。 これらすべての組み立てにはそれほど時間がかからず、出力にそのような機械があり、写真にはエンドスイッチがまだ取り付けられていません。コントローラーとカッターモーターは取り付けられていません。
精度は驚くべきものでした。第1に、ステッピングモーターはステップの1/16をステップし、第2に、細い糸のあるシャフトをステップしました。 カッターの代わりにペンを機械に挿入すると、彼は複雑な図形を描き、この図形をさらに数回丸で囲み、その図形は、別の線を見つけようとする虫眼鏡の下で、一度描いたように表示されます。 機械の剛性も良好です。 それは、彼ら自身の許容と着陸の許容限度内で犬の髪の中でのみよろめきます。 それでも、Y軸に沿って少しずらしていますが、ここでは、Z軸の構造を改善する必要があると思います。
写真は高品質ではなく、背景にガラスが映っています。 私はどのようなデザイナーなのかわかりませんが、写真家ではありません。 ここにもう少し良いです。
次に、制御プログラムについて。 理由は覚えていませんが、完成したGコードをコンピューターからコントローラーに送信する独自のプログラムを作成することにしました。 たぶん彼らは適切なものを見つけられなかったのでしょう。
プログラムはMicrosoft Visual C ++で作成され、ライブラリが使用されました。
モジュール:SERIALPORT.H
目的:シリアルポートのMFCラッパークラスの宣言
Copyright 1999 by PJ Naughter。 無断複写・転載を禁じます。
このプログラムはまだ生のままです。
port.Open(8, 9600, CSerialPort::NoParity, 8, CSerialPort::OneStopBit, CSerialPort::XonXoffFlowControl); port.Write(text, l); - port.Read(sRxBuf, LEN_BUF); - .
標準コンポーネントのmsflexgridテーブルも使用され、現在実行中のGコードがリアルタイムで入力されます。 つまり このプログラムは、完成したGコードを開き、それをコントローラーに少しずつプッシュします。
制御プログラムのソースコードは、 github.com / konstantin1970 / cnc.gitにあります。
理解のために、標準のWindowsハイパーターミナルまたはパテが同じことを行い、データをコントローラーにプッシュすることを追加します。
Gコード自体はどのCAD / CAMシステムでも実行できます。たとえば、ARTCAMが好きでした。
NEMA 23エンジンでより強力なマシンを作成する予定ですが、このために、より強力なガイドを作成するものを把握する必要があります。 コントローラのファームウェアで、スピンドル速度を変更する機能を追加します。 カメラを設置してテクニカルビジョンシステムに似た処理を行うことは特に興味深いので、マシン自体がワークの寸法を決定し、少なくともプログラムのすべての軸に沿ってワークの初期座標を計算します。 プログラムには最大値があるため、カメラの助けを借りて、マシンが作業のすべての段階を制御し、プログラムを変更する決定を下すこともできます。 たとえば、彼は粗さが許容範囲を超えていることを知り、2ラウンド目にカッターを送り、すべてを高速で研削しました。
親愛なるhabretchikiの皆さんと開発を共有し続けられることを願っています。