LoginSignup
19
16

More than 1 year has passed since last update.

M5StackでCNCを作る

Last updated at Posted at 2022-10-08

M5Stackとそのモジュールを使ってCNCを作ってみました。

使用するデバイス

基本的にスイッチサイエンスさんとAmazonで買ったものを使用しています。

M5Stack

ESP32にディスプレイ、ボタン、スピーカー、バッテリーにSDカードまで合体した欲張りセット。
これがあれば「あれ作りたいな~」ってなったとき大体何とかなります。

GRBLモジュール

M5Stackの下にスタックすることでステッピングモーターが使えるようになります。

今は新しいのが出ているようです。

Amazon | SUS(エスユウエス) SF-20・20 SF9-202 200mm 4本入 (アルミフレーム) | 棒
https://www.amazon.co.jp/gp/product/B072VHBJ5T/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1
image.png

これでフレームを構築します。
7本必要です。

Amazon.co.jp: 3DプリンタガイドレールセットT8リードスクリュー ピッチ1mmリード1mm +リニアシャフト8 * 100mm + KP08 SK8 SC8UU +ナットハウジング+カップリング+ステップモータ (200mm) : 産業・研究開発用品

https://www.amazon.co.jp/gp/product/B07D3P1YST/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&th=1
image.png

ステッピングモーターとガイドレールのセット
モーターもついててこの値段はお買い得

レイアウトの自由度が上がるので30cmにした方が良かったかも?image.png

image.png

これでフレームとレールなどを固定します。

image.png

マイクロスイッチをリミットスイッチとして使用します。

ハードを構築する

CNCのハード周りを構築していきます。

アルミフレームを長方形に組んで土台にします。
image.png

手前に見えてるのは以前作った安いリニアレールで作ったペンプロッタです。
安いって言っても1000円~2000円くらいするからモーターセットがお買い得すぎるんですよね・・・

Y軸を設置するためにやぐらを立てます。
image.png

ガイドレールセットにブラケットがついてなかったので3Dプリンタで作成しました
image.png
image.png

レールをすべて固定したらブロックを固定する板を3Dプリンタで作成して固定します。

image.png

ホームセンターで板を買ってきてねじ穴をドリルであけてブロックに固定しました。
これでXステージは完成です!

Y軸もX軸と同様にレールを固定します。
image.png

マイクロスイッチでリミットスイッチを作成します。
3Dプリンタでジグを作ってマイクロスイッチを固定します。
image.png
image.png
GRBLモジュールのリミットスイッチの端子はGNDが共通なので接続しようとするとこんなふうになりますw(新しく出てるステッピングモーターモジュールは改善されてる)
image.png

Z軸のペン上下は紆余曲折があったのですが、
ホームセンターでこんな部品を見つけました。
image.png
これ多分アルミサッシとかに入ってる奴ですね。

んで3Dプリンタで台車とフレームをプリントすると・・・
image.png
こうなって
image.png
こうじゃ!
image.png

いろいろ改善点はあるのですがこれを今も使っています。
ペンの上下はできるようになったのでペンホルダーを作っていきます。

image.png

さっきの上下する部分にM4の穴を開けておいたのでボルトで固定して簡単に交換できるようにしてあります。

ボルトで固定するとこんな感じになります。

これでハード周りは完成です!

ソフトを構築する

ステッピングモーターを動かすのはGRBLモジュールを使います。

m5-docs
https://docs.m5stack.com/en/module/grbl13.2

MODULE_GRBL13.2/xyz_control.ino at master · m5stack/MODULE_GRBL13.2 · GitHub
https://github.com/m5stack/MODULE_GRBL13.2/blob/master/examples/xyz_control/xyz_control.ino

こちらのサンプルを見るといくつか操作方法がありますがGcodeを使って操作します。

#include <M5Stack.h>
#include "MODULE_GRBL13.2.h"

#define STEPMOTOR_I2C_ADDR 0x70

GRBL _GRBL = GRBL(STEPMOTOR_I2C_ADDR);

void setup() {
  M5.begin();
  _GRBL.Init(200, 200, 40, 50);
  _GRBL.setMode("absolute");
}
void loop() {
  if (M5.BtnB.wasPressed()){
      _GRBL.sendGcode("G1 X5Y5Z5 F200");
      _GRBL.sendGcode("G1 X0Y0Z0 F200");
  }
  M5.update();
}

Init()の引数が違いますがこれはGcodeのXYZ1.0当たりのステップ数を変更しています。
G1 X1Y1Z1としたときに1mm動くようにモーターによって変更してください。

これでGcodeでペンを動かせるようになったのですが、
手打ちでGcodeを書いていくのは大変なのでおもむろにFusion360をインストールします。

Fusion 360 | 3D CAD/CAM/CAE/PCB クラウドベースのソフトウェア | Autodesk
https://www.autodesk.co.jp/products/fusion-360/overview

Fusion360は非商用なら3D CADやCAMが無料で使える意味が分からないソフトです。

無料でCNCのシミュレーションができてGcodeも出力できます(本当に無料でいいの?)

テストという事で2cm角にCの字を削り出す想定にしてみました。

Fusion360のCAMで出力したGcodeをM5StackのSDカードに置いて、
GBRLモジュールのライブラリに流し込んでペンを動かします。


G0 X0 Y0 Z0 F100
G1 Z2.
G1 Z1.3 F333.
G1 X15.279 Y15.261 Z1.228
G1 X15.275 Y15.231 Z1.163
G1 Y15.178 Z1.104
G1 X15.289 Y15.11 Z1.065
G1 X15.303 Y15.074 Z1.058
G1 X15.322 Y15.04 Z1.05
G3 X15.851 Y15.372 Z1.016 I0.265 J0.166
G3 X15.322 Y15.04 Z0.981 I-0.265 J-0.166
G3 X15.851 Y15.372 Z0.947 I0.265 J0.166
G3 X15.322 Y15.04 Z0.913 I-0.265 J-0.166
G3 X15.851 Y15.372 Z0.879 I0.265 J0.166
G3 X15.322 Y15.04 Z0.844 I-0.265 J-0.166
G3 X15.851 Y15.372 Z0.81 I0.265 J0.166


void gcode() {
  File f = SD.open("/gcode.nc");
  String str = "";
  int temp = 0;

  while (f.available()) {
    temp = f.read();
    if (temp != '\n') {
      str += String((char)temp);
    }
    else {
      Serial.printf("%s\n", str.c_str());

      Serial.println();
      _GRBL.Gcode((char *)(str.c_str()));
      str = "";
      _GRBL.WaitIdle();
    }
  }
}

GRBLモジュールはバッファが小さい?みたいでGcodeをドカンと流し込むとスキップされちゃうのでWaitIdle()を入れていますが、
ウエイトが長すぎるので(モーターが動き終わってから0.5秒くらい止まる)調整した方がいいと思います。

ペンでテストしてみたところいい感じに動いたので、いよいよCNCに挑戦します。
!

ペンプロッタをCNCにする

CNCにするという事でリューターでも買おうかなぁと思っていたのですがタイミングよくDIMEという雑誌にUSB駆動のルーターがついていたので購入しました。

image.png

ルーターに合わせてFusion360でホルダーをデザインしてプリントしました(角ばってるのはサポートをプリントしたくなかったから)

image.png

ペンホルダーと換装すればCNCの完成です!

image.png

というわけで発泡スチロールを削ってみました。

撮影中に疲労からか不幸にも黒塗りの高級車に脱調してしまったので緊急停止したのですが、
なかなかいい感じに削れてるのではないでしょうか?
image.png
image.png

現在はどうしても脱調が無くせないので開発は止まっています(´・ω・`)

モチベーションが復活したらパラメーターをいじったりして改善していこうと思っています。

ソースコード

ソースコードを残しておきます。
ただしここでは使ってない機能も残っているので見づらいですが・・・

ソースコード

#include <ArduinoJson.h>
#include <M5Stack.h>
#include <M5TreeView.h>
#include <string.h>

#include "GrblControl.h"
#include "WiFi.h"
#include "esp_wps.h"

#define STEPMOTOR_I2C_ADDR 0x70
GRBL _GRBL = GRBL(STEPMOTOR_I2C_ADDR);

int degree = 0;

int PEN_UP = 90;
int PEN_DOWN = 30;

float x = 0.0;
float y = 0.0;

M5TreeView tv;

StaticJsonDocument<2048> jsonObject;

void penUp() {
  String command = "G1 Z0 F800";
  Serial.println(command.c_str());
  _GRBL.Gcode((char *)(command.c_str()));
  // _GRBL.WaitIdle();
}
void penDown() {
  String command = "G1 Z-5 F800";
  Serial.println(command.c_str());
  _GRBL.Gcode((char *)(command.c_str()));
  // _GRBL.WaitIdle();
}

void resetOrigin(char *axis) {

  _GRBL.SetMode("distance"); // 相対移動モード

  String xyz = String(axis);
  String string = "";
  string = "G1 " + xyz + "1 F500";

  // USE Gcode
  _GRBL.Gcode(const_cast<char *>(string.c_str())); // +1移動

  if (axis == "Z") {
    string = "G1 Z-1 F200";
  }
  else {
    string = "G1 " + xyz + "-1 F500";
  }

  while (!_GRBL.InLock()) {
    _GRBL.Gcode(
        const_cast<char *>(string.c_str())); // -0.2ずつロックされるまで移動
    delay(100);
  }

  Serial.println("Locked");
  Serial.println("Back to Origin");

  if (axis == "Z") {
    string = "G1 Z5 F500";
    while (_GRBL.InLock()) {
      _GRBL.UnLock(); // リミットスイッチのロックを解除する
      _GRBL.SetMode("distance"); // absoluteに戻るので再設定
      _GRBL.Gcode(const_cast<char *>(string.c_str()));
      delay(500);
    }
  }
  else {
    string = "G1 " + xyz + "1 F500";
    while (_GRBL.InLock()) {
      _GRBL.UnLock(); // リミットスイッチのロックを解除する
      _GRBL.SetMode("distance"); // absoluteに戻るので再設定
      _GRBL.Gcode(const_cast<char *>(string.c_str()));
      delay(500);
      // スイッチが押しっぱなしだと即座にロックに戻るので移動してから少し待つ
    }
  }

  _GRBL.SetMode("absolute");
  Serial.println("Moved to Origin");
}

// XY:1>1 Z:11>1 ZMax:27mm
void moveTo(float toX, float toY) {
  _GRBL.WaitIdle();

  // toX /= 0.5;
  // toY /= 0.5;
  String command = "G1 X";
  command += String(toX);
  command += " Y";
  command += String(toY);
  command += " F800";
  Serial.println(command.c_str());
  _GRBL.Gcode((char *)(command.c_str()));
}

void tap() {
  penDown();
  penUp();
}

void gcode() {
  File f = SD.open("/gcode.nc");
  String str = "";
  int temp = 0;
  float x = 0;
  float y = 0;
  float prex = 0;
  float prey = 0;

  while (f.available()) {
    temp = f.read();
    if (temp != '\n') {
      str += String((char)temp);
    }
    else {
      Serial.printf("%s\n", str.c_str());

      if (str.indexOf("X") != -1) {

        String temp = str.substring(str.indexOf("X") + 1);
        if (temp.indexOf(" ") != -1) {
          temp = temp.substring(0, temp.indexOf(" "));
        }

        x = temp.toFloat();
        // Serial.printf("X=:%f ", x);
      }
      if (str.indexOf("Y") != -1) {

        String temp = str.substring(str.indexOf("Y") + 1);
        if (temp.indexOf(" ") != -1) {
          temp = temp.substring(0, temp.indexOf(" "));
        }

        y = temp.toFloat();
        // Serial.printf("Y=:%f", y);
      }
      Serial.println();
      _GRBL.Gcode((char *)(str.c_str()));
      float length = sqrt(pow(prex - x, 2) + pow(prey - y, 2));
      if (5 < length) {
        Serial.printf("length=%f4.1\n", length);
      }
      prex = x;
      prey = y;
      if (2 < length) {
        _GRBL.WaitIdle();
      }
      else {
        delay(400);
      }
      // Serial.println();
      str = "";
    }
  }
}

void decodeCommand(String str) {
  Serial.printf("command:%s\n", str.c_str());

  int index = 0;

  while (true) {
    String com = str.substring(index, str.indexOf(",", index));
    Serial.printf("%s\n", com);

    index = str.indexOf(",", index) + 1;

    if (com.startsWith("RS")) {
      resetOrigin("X");
      resetOrigin("Y");
      resetOrigin("Z");
    }
    else if (com.startsWith("BO")) {
      _GRBL.Gcode("G0 X0 Y0 F500");
    }
    else if (com.startsWith("GC")) {
      gcode();
    }
    else if (com.startsWith("TE")) {
      _GRBL.Gcode("G0 X0 Y0 F500");
      _GRBL.WaitIdle();
      penDown();
      _GRBL.Gcode("G1 X20 F500");
      _GRBL.WaitIdle();
      _GRBL.Gcode("G1 Y20 F500");
      _GRBL.WaitIdle();
      _GRBL.Gcode("G1 X0 F500");
      _GRBL.WaitIdle();
      _GRBL.Gcode("G1 Y0 F500");
      _GRBL.WaitIdle();
      _GRBL.Gcode("G1 X20 Y20 F500");
      _GRBL.WaitIdle();
      penUp();
      _GRBL.WaitIdle();
      _GRBL.Gcode("G0 X0 F500");
      _GRBL.WaitIdle();
      penDown();
      _GRBL.Gcode("G1 X20 Y0 F500");
      penUp();
      _GRBL.WaitIdle();
      _GRBL.Gcode("G0 X0 Y0 F500");
      _GRBL.WaitIdle();
      _GRBL.Gcode("G0 Z0 F500");
      _GRBL.WaitIdle();
    }

    if (index == 0) {
      break;
    }
  }
}

void func(MenuItem *mi) {
  Serial.print(mi->parentItem()->tag);
  Serial.print(":");
  Serial.println(mi->tag);

  JsonArray jsonArry = jsonObject["data"].as<JsonArray>();
  Serial.printf("sd:jsonArry.size=%d\n", jsonArry.size());

  String str = String(mi->tag);

  str = "wave" + str;
  Serial.printf("name:%s\n",
                (const char *)jsonArry[mi->parentItem()->tag]["name"]);
  Serial.printf(
      "%s:%s\n", str,
      (const char *)(jsonArry[mi->parentItem()->tag]["command"][str]));
  decodeCommand(jsonArry[mi->parentItem()->tag]["command"][str]);
}

void setup() {
  M5.begin(true, true, true, true);

  _GRBL.Init(200, 200, 40, 50);
  _GRBL.SetMode("absolute");
  dacWrite(25, 0); // ノイズ対策

  tv.itemHeight = 25;
  tv.itemWidth = 100;
  tv.setTextFont(2);

  File f = SD.open("/fgo.json");

  DeserializationError error = deserializeJson(jsonObject, f);

  if (error) {
    Serial.print(F("deserializeJson() failed: "));
    Serial.println(error.f_str());
    return;
  }
  JsonArray jsonArry = jsonObject["data"].as<JsonArray>();
  Serial.printf("sd:jsonArry.size=%d\n", jsonArry.size());

  for (int i = 0; i < jsonArry.size(); i++) {
    Serial.printf("sd:jsonArry[0].name=%s\n",
                  (const char *)(jsonArry[i]["name"]));
  }
  for (int i = 0; i < jsonArry.size(); i++) {
    tv.addItems(std::vector<MenuItem *>{
        new MenuItem((const char *)(jsonArry[i]["name"]), i,
                     std::vector<MenuItem *>{new MenuItem("Wave1", 1, func),
                                             new MenuItem("Wave2", 2, func),
                                             new MenuItem("Wave3", 3, func

                                                          )})});
  }
  tv.begin();
  // penUp();
  delay(1000);
  // penUp();
}

void loop() {
  M5.update();
  tv.update();

  delay(1);
}



19
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
16