勝手な電子工作・・

勝手なオリジナル電子工作に関する記事を書きます

オリジナル・レーザープロッター その2 Arduinoでの刻印プログラム

この木製の試作機は頑丈ですが簡単に組みなおせるバラックみたいなものです。現時点ではY軸用のリニアステージは底板に載せてあるだけなのですが、今後メカニズムを入れ替えることで新しいアイデアの他のプロット方式にとりかえるようにしてあります。

f:id:a-tomi:20190811175443j:plain

現在はXY方式ですが、どんな方式にしても位置決め&刻印プログラムの構造はあまり変わらない予定ですので、本体のプログラムを簡単に説明しておきます。Arduinoで作っています。

XY方式での動きは、X軸は絶えず忙しく動かしますが、Y軸は行が変るまで休ませています。

youtu.be

2つの値で位置を決める形の構造で、しかけを簡単に作るならどちらかを忙しく動かし片方は時々動かすことになります。

youtu.be

ここで少し面倒かもしれませんが、ステッパーの動かし方について、勝手な留意点を説明しておきます。理解しにくい時はこの項は読み飛ばしても構いません。

 

◎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!ですね。

ここに回路を再度掲載します。

f:id:a-tomi:20190811182958j:plain

上図の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を省略したりはせずに、きちんとした回路を作り落ち着いて低いデューティ比からテストしてください。安全のために必須です。

そのようなテスト用のマイコンであっても、立ち上げの電源の注意をはじめ、レーザー使用の注意は全く同じで必須です。事故防止のためにも、貴重なレーザーの寿命の為にも、そもそも最大時でもフルのデューティ比は避けるべきことです。

また、適切な照射時間もテストで確かめておけばなお良いです。不明なら短い時間からテストします。個々のレーザーで違いますので、プログラム例の値をそのまま使うことがないようにしてください。

レーザーについては全て自己責任でおこなってください。運用時だけでなく不使用時のレーザーの厳格な管理も同様です。事故がおきても責任は一切もてません。

 

f:id:a-tomi:20190811210602j:plain

安全のため試作でも絶縁箱に入れ、早く固定基板にする

次が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

 

 では今回はこのへんで。一番下にレーザーに関するご注意を掲載させていただきます。

 次の記事:

a-tomi.hatenablog.com

前の記事:

a-tomi.hatenablog.com

 前の前の記事:

a-tomi.hatenablog.com

 

以下、重要な注意です。レーザーは失明や傷害、火災等の危険を伴い、周囲にも危険がおよびます。関連法規と取扱い基準(http://kikakurui.com/c6/C6802-2011-01.html)などに従って、正しく管理してください。この記事をみて自作される場合も全て自己責任でお願いします。

©2019 Akira Tominaga, All rights reserved.