この木製の試作機は頑丈ですが簡単に組みなおせるバラックみたいなものです。現時点ではY軸用のリニアステージは底板に載せてあるだけなのですが、今後メカニズムを入れ替えることで新しいアイデアの他のプロット方式にとりかえるようにしてあります。
現在はXY方式ですが、どんな方式にしても位置決め&刻印プログラムの構造はあまり変わらない予定ですので、本体のプログラムを簡単に説明しておきます。Arduinoで作っています。
XY方式での動きは、X軸は絶えず忙しく動かしますが、Y軸は行が変るまで休ませています。
2つの値で位置を決める形の構造で、しかけを簡単に作るならどちらかを忙しく動かし片方は時々動かすことになります。
ここで少し面倒かもしれませんが、ステッパーの動かし方について、勝手な留意点を説明しておきます。理解しにくい時はこの項は読み飛ばしても構いません。
◎A4988モジュールを介したステッパーでの留意点
直流電流の防止
忙しく動くステッパーへは、4Wireバイポーラ型ステッパーの場合、結局いつも交流を送っていることになります。しかし、時々動くステッパーでは休止期間は直流となるため電流を止めないといけません(A4988に直流を減らす機能がありますが、実験するとそれだけではステッパーもICも温度が上がってしまいます)。A4988は電流の上限調整機能も備えていますが、トルク面で限界があります。
そこで、休止期間はSleepさせて電流を止めるようにすると問題が防げます。
Sleepをかける際の留意点
Sleepをかける場合、いっきにかけると惰性(ステッパーだけでなく、長い送りねじの回転モーメントは大きい)で位置がわずかにずれることがあります。そこで止めてから少しだけ(ここでは2mSにしています)時間をずらしてSleepをかけることで慣性によるずれを防止します。
Sleepをやめる際の留意点
Sleepをやめる(眠りから起こす)際には、A4988が自分にResetをかけるようになっています。リセット処理とはステッパーの初期化であり、現位置がサイクル初期のフェーズ前であるとみなされます。位置によっては少しずれることがあります。
そこで、止めるときに必ず初期位相にして止めればずれが出ることはありません。つまり、動かすときにはサイクル単位で動かし、その途中で止めなければよいわけです。ネジ送り機構にもよりますが1サイクルの動きはせいぜい0.1ミリ未満なのでこの用途にはあまり問題ないと思います。
また、Sleepから復帰する際、A4988内部のチャージポンプ充電に1mSを要するのでそれ以上待ってから動かしだします(ここでは2mSにしています)。
なお、A4988の1サイクルはMS(MicroStep)端子の使い方で設定できます。通常はMS端子全てはプルダウンされているので、指定したいMS端子をプラスにします。ここではMS1だけフプラスにして1-2相励磁とします(目的はステッパーの音と振動を小さくするためです)。そうすると1サイクルは8ステップとなりますから、回転は常にその倍数となるようにプログラムするわけです。
以上の勝手な留意点を踏まえて作れば、最も単純に作れます。Simple is the best!ですね。
ここに回路を再度掲載します。
上図のATmega328P(Aruduino-Uno)のの各ピンに書いてある通りにピンのシンボルをつけます。Integerなどの定数にする必要のないところは全て#defineを使って定義します(こうするとメモリーを使わないからです)。
◎レーザー駆動の留意点(必須)
レーザーの電源ライン
Arduinoの電源立ち上げ時には、レーザーの電源供給ライン(通常12Vで比較的大電流、とはいってもここではせいぜい0.5A)は、ステッパーの電源供給とともに必ずオフでなければなりません。Arduino自体の初期化の際に複雑な動きが生じます。するとレーザーが予期しないビームを出してしまうため危険だからです。ステッパーについても同じことがいえ、予期しない動きをして初期の位置が狂います。レーザーとステッパーで12V電源を共有することが多いでしょうが、その場合は共有の12V電源は、Arduinoが立ち上がってから、レーザーとステッパーを使い始める前にオン操作をするというわけです。
つまりArduino側は立ち上がったら必ずその操作を待つためにいったん休み、レーザー電源供給後に再開するようにプログラムを組む必要があります。再開のトリガーとしてタクトスイッチかプッシュスイッチなど(上図でSw)を使います。もし手動用Swをつけたくない場合は、Arduinoの立ち上がり後に一定時間の休止(Delay)を入れておき、レーザーとステッパーへの電源がその休止時間の中でオンとなるように、電源投入遅延回路を仕組んでおきます。
そのようなことをしないと、Arduino初期化の間の挙動は複雑ですから不用意にレーザーがオンとなり事故を招きます。加えて、レーザーの電源ラインは緊急オフにできるように別途電源スイッチが必要です(強いレーザーでは世界共通ルール)。レーザーにまつわる重要な注意書きを記事の後ろのほうにつけておきますので必ず見てください。
レーザーへのPWMのデューティ比
レーザー電源供給を開始する時点で、レーザー制御回路へのPWMのDuty比を必ずゼロにした状態となるようにプログラムしてください。
その後で位置調整のための照射を行うには、最低のデューティ比で行う。ここに示すプログラムでは、位置調整の際には更にそれが点滅するようにして危険を防いでいます。
また、PWMの周波数が低いArduinoの場合は、そのなかでもできるだけ周波数の高いピンを割り当てます。UNOの場合はデジタル5番かデジタル6番ですね。
予めレーザーの仕様に基づいてテストをしておいてください。テストだからといってPWMを省略したりはせずに、きちんとした回路を作り落ち着いて低いデューティ比からテストしてください。安全のために必須です。
そのようなテスト用のマイコンであっても、立ち上げの電源の注意をはじめ、レーザー使用の注意は全く同じで必須です。事故防止のためにも、貴重なレーザーの寿命の為にも、そもそも最大時でもフルのデューティ比は避けるべきことです。
また、適切な照射時間もテストで確かめておけばなお良いです。不明なら短い時間からテストします。個々のレーザーで違いますので、プログラム例の値をそのまま使うことがないようにしてください。
レーザーについては全て自己責任でおこなってください。運用時だけでなく不使用時のレーザーの厳格な管理も同様です。事故がおきても責任は一切もてません。
次がXY形式でのプログラム例ですが、全体でも短く、ご覧のとおり単純です。ArduinoUnoの所要プログラム容量は4割未満、グローバル変数は5割未満です。
/* *************************************************
* Laser cutter type XY (V00 July 28, 2019)
* Version 00a Aug 10, 2019
* (c) Akira Tominaga, All rights reserved.
****************************************************/
#include "SPI.h"
#include "SD.h"
// pin assignment
// *** A4988 stepper driver
#define NsleepX 14 // !sleepX
#define StepX 9 // StepX
#define DirX 8 // DirectionX
#define NsleepY 7 // !sleepX
#define StepY 6 // StepY
#define DirY 4 // DirectionY
// ***for laser
#define Pwm 5 // PWM 980Hz for Laser
// *** micro SD (SPI)
// D13=SCK
// D12=MISO
// D11=MOSI
# define ChipSel 10
File mySD; // symbol for SD file
// *** LED and tact switch for operations
#define Led 2
#define Sw 3
// *** reserved
#define Stoppers 15 // OR for limitters (A0=D15)
#define Btctl 2 // Burn time control (A2)
// constants and values
#define tmStpX 700 // stepperX phase time in μS
#define tmStpY 700 // stepperY phase time in μS
#define enlargeX 2 // enlarge-multiplier for X
#define enlargeY 2 // enlarge-multiplier for Y
#define dutyOn 150 // duty ratio while laser On
#define dutyOff 0 // duty ratio while laser Off
#define dutyPos 1 // duty ratio while positioning
#define burnTime 14 // burning time (milliseconds)
int sizeX; // picture size x
int sizeY; // picture size y
int pX = 0; // current position X
int pY = 0; // current position Y
int newX; // new position X
int newY; // new position Y
byte Byte; // byte work area
int Value; // value work area
int k; // common loop counter
int loopNum = 0; // data number of file
void setup() { // Arduino Setup *******************
analogWrite(Pwm, dutyOff); // laser off
pinMode(NsleepX, OUTPUT);
digitalWrite(NsleepX, LOW); // sleep X
pinMode(NsleepY, OUTPUT);
digitalWrite(NsleepY, LOW); // sleep Y
pinMode(DirX, OUTPUT);
pinMode(DirY, OUTPUT);
pinMode(StepX, OUTPUT);
digitalWrite(StepX, LOW);
pinMode(StepY, OUTPUT);
digitalWrite(StepY, LOW);
pinMode(Sw, INPUT_PULLUP);
pinMode(Led, OUTPUT);
digitalWrite(Led, LOW);
Serial.begin(9600);
delay(1000);
// ** 1) 12V power-on process
Serial.println("Pwr 12V on");
while (digitalRead(Sw) == HIGH) {
bLED(1, 50, 10); // repeat LED on while waiting
}
delay(500); // avoid double detection of Sw
// ** 2) adjust center position
Serial.println("Adj Center");
while (digitalRead(Sw) == HIGH) {
analogWrite(Pwm, dutyPos); // blink weak laser-beam
digitalWrite(Led, HIGH); // LED on while waiting
delay(30);
analogWrite(Pwm, dutyOff); // blink laser-beam
digitalWrite(Led, LOW); // blink LED, too
delay(40);
}
analogWrite(Pwm, dutyOff); // laser off
delay(500); // avoid double detection of Sw
// ** 3) prompt to wear protection glasses
Serial.println("Protect eyes!");
while (digitalRead(Sw) == HIGH) {
bLED(1, 15, 30); // blinkking of LED
}
delay(500); // avoid double detection of Sw
// check SD card exists
if (!SD.begin(ChipSel)) {
Serial.println("Chk SD"); // if error, show SdEr
while (1) {
bLED(20, 20, 30); // blink LED forever
}
}
// read PICT.txt header
mySD = SD.open("PICT.txt");
Rd4(); // read 4bytes to Value
sizeX = Value - 1000; // get sizeX
Rdskip(1); //skip comma
Rd4(); // read 4bytes to Value
sizeY = Value - 1000; // get sizeY
Rdskip(14); // skip PC-center + CR+LF
newX = -sizeX / 2;
digitalWrite(NsleepX, HIGH); // wake-up X
delay(2); // delay for A4988 charge-pump
movX(); // move to new origin pX=0
newY = -sizeY / 2;
movY(); // move to new origin pY=0
}
void loop() { // Arduino Loop ******************
/* ********************************************
1.Read SD file and get new(X,Y) data
* ***************************************** */
// read PICT.text
Rd4(); // read 4bytes to Value
if (Value >= 9999) {
EndProc();
}
newX = Value - 1000;
Rdskip(1); // skip comma
Rd4(); // read 4bytes to Value
newY = Value - 1000; // get newY
Rdskip(14); // skip PC-data 12 + CRLF 2
loopNum++;
Serial.print("#=");
Serial.print(loopNum); // show data number
/* ********************************************
2.Drive steppers and irradiate laser beam
* ***************************************** */
// move Y and X
Serial.print (" ");
Serial.print (pX);
Serial.print (",");
Serial.print (pY);
Serial.print (" to ");
Serial.print (newX);
Serial.print (",");
Serial.println (newY);
movY(); // move Y first
movX(); // move X next
laserBeam(); // beam on for the burning time
pX = newX; // set new X value to current X
pY = newY; // set new Y value to current Y
} // last line of main loop ***
/***********************************
User defined functions
* **********************************/
// *** Read SD 4 bytes into integer Value ***
void Rd4(void) {
Value = 0;
for (k = 0; k < 4; k++) {
Byte = mySD.read();
Byte = Byte & B00001111;
Value = Value * 10 + Byte;
}
}
// *** Read SD and skip given bytes ***
void Rdskip(int bn) {
for (k = 0; k < bn; k++) {
Byte = mySD.read();
}
}
// *** move stepperX (no sleep actions) ***
void movX(void) {
int Delta;
if (newX == pX) {
return;
}
if (newX > pX) {
digitalWrite(DirX, HIGH); // X fwd
Delta = newX - pX;
}
if (newX < pX) {
digitalWrite(DirX, LOW); // X bkwd
Delta = pX - newX; // set absolute value
}
for (int l = 0; l < Delta * enlargeX; l++) {
for (int m = 0; m < 8; m++) { // 1 cycle for MicroStep1
digitalWrite(StepX, HIGH);
delayMicroseconds(tmStpX);
digitalWrite(StepX, LOW);
delayMicroseconds(tmStpX);
}
}
delay(2);
}
// *** move stepperY (with sleep actions) ***
void movY(void) {
int Delta;
if (newY == pY) {
return;
}
if (newY > pY) {
digitalWrite(DirY, HIGH); // Y fwd
Delta = newY - pY;
}
if (newY < pY) {
digitalWrite(DirY, LOW); // Y bkwd
Delta = pY - newY; // and change sign
}
digitalWrite(NsleepY, HIGH); // wake-up Y
delay(2); // for A4988 charge-pump
for (int l = 0; l < Delta * enlargeY; l++) {
for (int m = 0; m < 8; m++) { // 1 cycle MicroStep1
digitalWrite(StepY, HIGH);
delayMicroseconds(tmStpY);
digitalWrite(StepY, LOW);
delayMicroseconds(tmStpY);
}
}
delay(2); // avoid mechanical inertia
digitalWrite(NsleepY, LOW); // sleep Y
}
// *** Laser beam-on for burnTime ***
void laserBeam(void) {
analogWrite(Pwm, dutyOn);
delay(burnTime);
analogWrite(Pwm, dutyOff);
}
// *** End of File process ***
void EndProc(void) {
newX = sizeX / 2;
newY = sizeY / 2;
movY();
movX();
delay(2); // avoid mechanical inertia
digitalWrite(NsleepX, LOW); // sleep X
Serial.println("Power-off 12V-DC");
mySD.close();
while (1) {}
}
// *** blink LED ***
void bLED(int nTimes, int onmS, int offmS)
{
for (int m = 0; m < nTimes; m++) {
digitalWrite(Led, HIGH);
delay(onmS);
digitalWrite(Led, LOW);
delay(offmS);
}
}
// end of program
では今回はこのへんで。一番下にレーザーに関するご注意を掲載させていただきます。
次の記事:
前の記事:
前の前の記事:
以下、重要な注意です。レーザーは失明や傷害、火災等の危険を伴い、周囲にも危険がおよびます。関連法規と取扱い基準(http://kikakurui.com/c6/C6802-2011-01.html)などに従って、正しく管理してください。この記事をみて自作される場合も全て自己責任でお願いします。
©2019 Akira Tominaga, All rights reserved.