grbl1.1+Arduino CNCシールドV3.5+bCNCを使用中。
BluetoothモジュールおよびbCNCのPendant機能でスマホからもワイヤレス操作可能。
その他、電子工作・プログラミング、機械学習などもやっています。
MacとUbuntuを使用。

CNCマシン全般について:
国内レーザー加工機と中国製レーザー加工機の比較
中国製レーザーダイオードについて
CNCミリングマシンとCNCルーターマシンいろいろ
その他:
利用例や付加機能など:
CNCルーター関係:



*CNCマシンの制作記録は2016/04/10〜の投稿に書いてあります。


2017年3月24日金曜日

IoTその4:再度ESP8266+Blynk

もともとはCNCマシンのワイヤレス化(bCNCのPendant機能Bluetooth装備など)から始まったのですが、bCNCのカメラ機能をIPカメラでも撮影可能にしようとしたところ、
Pythonの再学習(bCNC自体がPythonで書かれているため)
・Wifiデバイス(ESP8266ESP32)の利用
ということになり、今まで使っていたArduino IDEだけではなく、
ESP-IDF
PlatformIOAtom Editor
もやってみようかと。
要はパソコンやスマホのブラウザからもいろんなものを操作可能にしたいということなので、
・HTML
・Javascript
も同時に学習していかなければいけない。やらなければいけないことが一気に増えてしまったという感じです。

その結果、ある程度はESP8266を使って、
・エアコンのオンオフ(赤外線信号)
・温度と湿度の読み込み
・照明器具のオンオフ(あるいは、CNCマシンのメイン電源のオンオフ)
などをスマホのChrome上からも操作できるようになりました。

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>

#define RelayPin 5
#define IRLedPin 15
#define DHTPin   2
#define LedPin   4

#define duty_high 8
#define duty_low 16
#define DHTTYPE DHT11

DHT_Unified dht(DHTPin, DHTTYPE);

uint32_t delayMS;
String temp;
String humid;

const char* ssid = "*****";
const char* password = "*****";

MDNSResponder mdns;
ESP8266WebServer server(5900);

String webPage = "";

unsigned long airOn[243] = {
 3400, 1400, 600, 250, 550, 250, 550, 1050, 550, 250, 550, 1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550, 
 1050, 550, 250, 550, 300, 500, 250, 550, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 300,
 500, 300, 500, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350,
 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 1200, 450, 350, 450, 350, 450, 350, 450, 1150,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 1200, 450, 350,
 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 400,
 400, 1200, 400, 400, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 400,
 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400,
 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 1150, 450, 1150, 450, 350, 450, 1200, 400, 400, 400,
 400, 400, 1200, 400, 400, 400};
unsigned long airOff[99] = {
 3400, 1450, 550, 250, 550, 250, 500, 1100, 550, 250, 550, 1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550,
 1050, 550, 300, 500, 250, 550, 300, 500, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 350,
 450, 300, 500, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450};

void setup() {
  pinMode(IRLedPin, OUTPUT);
  pinMode(LedPin, OUTPUT);
  pinMode(RelayPin, OUTPUT);
  digitalWrite(LedPin, HIGH);
  digitalWrite(RelayPin, LOW);
  dht.begin();
  sensor_t sensor;
  dht.temperature().getSensor(&sensor);
  dht.humidity().getSensor(&sensor);
  temp_humid();
  webContents();
  
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  delay(1000);
  WiFi.begin(ssid, password);
  Serial.println("");
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }
  WiFi.config(IPAddress(192,168,3,10),IPAddress(192,168,3,1),IPAddress(255,255,255,0));
  
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  
  if (mdns.begin("mirror", WiFi.localIP())) {
    Serial.println("MDNS responder started");
  }

  server.on("/", []() {
    temp_humid();
    webContents();
    server.send(200, "text/html", webPage);
  });
  server.on("/index.html", []() {
    temp_humid();
    webContents();
    server.send(200, "text/html", webPage);
  });
  server.on("/on", []() {
    server.send(200, "text/html", webPage);
    digitalWrite(LedPin, LOW);
    digitalWrite(RelayPin, HIGH);
    delay(1000);
  });
  server.on("/off", []() {
    server.send(200, "text/html", webPage);
    digitalWrite(LedPin, HIGH);
    digitalWrite(RelayPin, LOW);
    delay(1000);
  });
  server.on("/airon", []() {
    temp_humid();
    webContents();
    server.send(200, "text/html", webPage);
    air_on();
    delay(1000);
  });

  server.on("/airoff", []() {
    temp_humid();
    webContents();
    server.send(200, "text/html", webPage);
    air_off();
    delay(1000);
  });

  server.on("/temphumid", []() {
    webContents();
    server.send(200, "text/html", webPage);
    delay(1000);
  });

    ArduinoOTA.onStart([]() {
    Serial.println("Start");
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();  
  server.begin();
  Serial.println("HTTP server started");
  delayMS = sensor.min_delay / 1000;
}

void loop(void) {
  ArduinoOTA.handle();
  server.handleClient();
  yield();
}

void air_off() {
  int dataSize = sizeof(airOff) / sizeof(airOff[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = airOff[cnt];
    unsigned long us = micros();
    while (us + len > micros()) {
      digitalWrite(IRLedPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(IRLedPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void air_on() {
  int dataSize = sizeof(airOn) / sizeof(airOn[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = airOn[cnt];
    unsigned long us = micros();
    while (us + len > micros()) {
      digitalWrite(IRLedPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(IRLedPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void temp_humid() {
  delay(delayMS);
  sensors_event_t event;
  dht.temperature().getEvent(&event);
  if (isnan(event.temperature)) {
    temp = "Error reading!";
  } else {
    temp = String(event.temperature);
  }
  dht.humidity().getEvent(&event);
  if (isnan(event.relative_humidity)) {
    humid = "Error reading!";
  } else {
    humid = String(event.relative_humidity);
  }
}

void webContents() {
  webPage = "";
  webPage += "<html><header><title>WIFI SWITCH</title></header>";
  webPage += "<body style=\"text-align:center;font-size:48px\"><div>ESP8266 WEB SERVER</div><br/>";
  webPage += "<div><div>AC100V SWITCH:</div>";
  webPage += "<div><a href=\"on\"><button style=\"width:80%;font-size:60px\">AC100V:  ON</button></a></div><br/>";
  webPage += "<div><a href=\"off\"><button style=\"width:80%;font-size:60px\">AC100V: OFF</button></a></div><br/>";
  webPage += "<div>AIR-CON SWITCH:</div>";
  webPage += "<div><a href=\"airon\"><button style=\"width:80%;font-size:60px\">AIR-CON: ON</button></a></div><br/>";
  webPage += "<div><a href=\"airoff\"><button style=\"width:80%;font-size:60px\">AIR-CON: OFF</button></a></div>";
  webPage += "<div>Temperature: ";
  webPage += temp;
  webPage += " *C</div>";
  webPage += "<div>Humidity: ";
  webPage += humid;
  webPage += " %</div>";
  webPage += "</div></body></html>";
}


これは前回からの改良で、
・ESP8266
・赤外線LED(エアコン用)
・温度湿度センサー
・リレー(照明器具AC100V用)
を使っています。OTAのコードも含めたので、ワイヤレスでコードの書き換えが可能です。そのぶんコードも長く、メモリ消費量も多いプログラムとなりました。
基本的にはローカルネットワーク内でしか使えないのですが、
Wifi.config()を使えば固定IPアドレスにできるので、ルーターをポートフォワーディングしてみると、外部からもつながりました(スマホ4G通信で確認)。以下はブラウザ上の操作画面。


LANだけでなくWANからも操作できてよかったのですが、以前みつけたスマホアプリのBlynkでやってみたらどうなるかも試してみました。このアプリ見つけたのはいいのですが、しばらく使っていませんでした。どのくらい便利なのか?


以前、ざっと説明を見た限りでは、アプリ上でボタンやスイッチ部品を配置して、余計なコードは書かずにプログラムをアップロード(ワイヤレスでスマホから?)という感じでしたが、
実際手順に沿ってやってみると:
・スマホアプリをインストール+メールアドレスを登録
・New Project作成(使うハードウェアを指定:ESP8266など)
・セキュリティコードがメールへ届く
・Arduino IDEにBlynkライブラリをアップロードする
・Arduino IDEでセキュリティコードとWifiのIPアドレス+パスワードを専用プログラムに追記する
・専用プログラムをパソコンからUSB経由でESP8266へアップロード
・再度スマホアプリに戻り、好きなピンを割り当てながらボタンやスイッチを配置
・LANだけでなくWANからも操作可能
という感じです。


FirmataのようなBlynkファームウェア:
いちいちArduino IDEでプログラムを書かなくても済むというのが予想外に便利でした。最初は、スマホでボタンやスイッチへピンを割り当てて、それにあわせたプログラムをArduino IDEのほうでも書かなければいけないのかと思っていましたが、そうではなく、Blynkの専用プログラムがFirmataのようなファームウェアとして機能しているため、後からでも自由自在にBlynkアプリでESP8266のピンの機能割り当てができるという感じです。デジタル入力にするかデジタル出力にするか、それともPWM出力にするかなど、いちいちESP8266にプログラムをアップロードしなくても、その場で自由に変えられるということです。
ESP8266の場合は、
スケッチ例>Blynk>Boards_WiFi>ESP8266_Standalone
を選択し、
このプログラム内の、
YourAuthToken:メールで送られてきたセキュリティコード
YourNetworkName:自宅Wifiネットワーク名
YourPassword:自宅Wifiのパスワード
を書き込むだけでOKです。すべてのピンへの機能割り当てはBlynkのほうでしてくれるので、他のプログラムを書き込む必要はありません。
最初に送られてくるセキュリティコードはブロジェクトごとに使えるので、ボタンの配置や機能の割り当てを変えてもずっと使えます。

ということで、先ほどのプログラムをBlynkに適用させてみようかと。
しかし、赤外線信号の部分はBlynkではできなさそうなので、Blynkの応用的な機能であるVirtual Pinというものを使ってみました。このVirtual Pinは、実際のESP8266上のピンとは無関係で、Blynk上で使える変数やトリガーのようなものです。今回の赤外線信号の場合であれば、ボタンがオンで1、オフで0という値を割り当てておいて、Virtual Pinが1ならエアコンをオンにする赤外線信号を発する、0ならオフの信号を発するという感じにしておきます。そんな感じで、一度Virtual Pinを介して動作するプログラムにしてみました。

#define BLYNK_PRINT Serial
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>

#define IRPin  4
#define DHTPin  2
#define duty_high 8
#define duty_low 16
#define DHTTYPE DHT11
DHT_Unified dht(DHTPin, DHTTYPE);
int temp;
int humid;

char auth[] = "*****";
char ssid[] = "*****";
char pass[] = "*****";

unsigned long data_on[243] = {
 3400, 1400, 600, 250, 550, 250, 550, 1050, 550, 250, 550, 1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550,
 1050, 550, 250, 550, 300, 500, 250, 550, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 300,
 500, 300, 500, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350,
 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 1200, 450, 350, 450, 350, 450, 350, 450, 1150,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 1200, 450, 350,
 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 400,
 400, 1200, 400, 400, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 400,
 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400,
 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 1150, 450, 1150, 450, 350, 450, 1200, 400, 400, 400,
 400, 400, 1200, 400, 400, 400};
unsigned long data_off[99] = {
 3400, 1450, 550, 250, 550, 250, 500, 1100, 550, 250, 550, 1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550,
 1050, 550, 300, 500, 250, 550, 300, 500, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 350,
 450, 300, 500, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450};

void setup(){
  Serial.begin(9600);
  Blynk.begin(auth, ssid, pass);
  pinMode(IRPin, OUTPUT);
  dht.begin();
  sensor_t sensor;
  dht.temperature().getSensor(&sensor);
  dht.humidity().getSensor(&sensor);
  dht.humidity().getSensor(&sensor);
}

BLYNK_WRITE(V0){
  int value = param.asInt();
  if (value) {
    air_on();
  } else {
    air_off();
  }
}

BLYNK_READ(V1){
  temp_humid();
  Blynk.virtualWrite(V1, String(temp)+" °C");
  Blynk.virtualWrite(V2, String(humid)+" %");
  Blynk.virtualWrite(V3, temp);
}

void air_on() {
  int dataSize = sizeof(data_on) / sizeof(data_on[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = data_on[cnt];
    unsigned long us = micros();
    while (us + len > micros()) {
      digitalWrite(IRPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(IRPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void air_off() {
  int dataSize = sizeof(data_off) / sizeof(data_off[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = data_off[cnt];
    unsigned long us = micros();
    while (us + len > micros()){
      digitalWrite(IRPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(IRPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void temp_humid(){
  sensors_event_t event;  
  dht.temperature().getEvent(&event);
  if (isnan(event.temperature)) {
    temp=0;
  }else {
    temp=int(event.temperature);
  }
  dht.humidity().getEvent(&event);
  if (isnan(event.relative_humidity)) {
    humid=0;
  }else {
    humid=int(event.relative_humidity);
  }
}

void loop(){
  Blynk.run();
}
こんな感じで、BLYNK_WRITE(V0)内に条件分岐をつくって赤外線信号のオンオフ操作をしています。BLYNK_READ(V1)でも、温度センサから読み取った値をBlynkアプリへ渡すようにしています。

スマホアプリのほうでは、こんな感じのボタン配置にしました。
一番上の黄色いスイッチが照明用、その下の白いスイッチがエアコン用、その下に温度と湿度表示、さらに温度のグラフもあります。一応4G通信で外部からアクセスも可能でした。
しかし、温度グラフに関しては、一旦このアプリを消してしまうとそれまでの記録も消えてしまってダメでした。もしかしたら配列を使って過去の記録を覚えさせていけないのかもしれません。このグラフ機能以外にもヒストリーグラフというウィジットがあって、それならいいのかもしれませんが、実はこのアプリ、各ウィジットにはポイント(コスト)があり、例えばボタン一個で200エナジー、グラフが400エナジーであり、合計の上限が決まっています。

ヒストリーグラフは何と900エナジーもします(赤い数値)。この段階で、ボタン2個なので400エナジー、温度と湿度数値表示で400エナジー、そしてグラフで400エナジー、このプロジェクトだけで合計1200エナジー消費しています。もう一つ別のプロジェクトもあって、そっちで500エナジー消費しているので、すべてのプロジェクトを合わせて1700エナジー使っていることになります。残り200エナジーしかありません(画面上部)。
そして、このエナジー量を増やすには課金が必要ということでした。すっかりオープンソースで無料なのかと思っていたら、なるほど、よくある一部有料というアプリでした。

ということで、温度グラフのかわりに消費エナジー500のビデオストリーミングも試してみました。
以前使ったIP WebCamというアプリでタブレットのカメラから配信させてみました。
LAN通信ですが、ちゃんと映ります。これはビデオなのでESP8266とは無関係。このビデオストリーミングの場合、カメラのIPアドレスの欄にhttp://192.168.3.2:8080/videoを入れると大丈夫でした。温度グラフよりも、こっちのほうがよさげ。
ただし、Blynkのサイトにも書いてありますが、Blynkサーバはストリーミングサーバを提供していないためWANからは見れないということです。その場合は他のサービスを使えと書いてあります。

とはいったものの、Blynkで当初予定していた内容はほぼできてしまいました。しかも設定なども簡単です。自前でHTMLやJavascriptまで書いてESP8266をサーバにしてもいいのですが、たしかに面倒。当然自分の思い描いているような内容にすることはできますが、Blynkだとあっというまにできてしまったので、これはこれでいいのかもしれません。この手のサービスはWANからもアクセス可能という部分が便利かもしれません。自宅のルーターをポートフォワーディングさせれば済むことですが、セキュリティなどのことも考えると面倒なので、外部サーバーを利用できるのであれば、それに越したことはありません。

次のステップとしては、ESP8266かESP32にカメラモジュールを接続して小型のIPカメラをつくろうと思っていますが、なかなか思うように進みません。その小型IPカメラをCNCマシンに搭載し、bCNCのカメラ機能をIP化するところまで行くというのがとりあえずの目標です。
ただ、そのついでにその他の照明や電源などもIP化しようとしているので、いろいろやることが増えているという現状です。あわよくば、スマホ上のボタン操作ではなく、音声入力で照明をオンにできないかとも考えていて、Python、HTML5、Javascript、場合によってはPHPなども同時進行という感じです。さらには、それらの開発環境となるAtom EditorやPlatformIOの使い方も学習中です。

2017年3月22日水曜日

IoTその3:ESP-WROOM-32/Arduino IDEとESP-IDF

先日、秋月でESP32(WROOM-32)を購入しました(1480円)。
ESP32はWifiとBluetoothが内蔵されており、しかもArduinoよりもメモリが多くクロック数も高いにもかかわらず、この小型なサイズと値段というのは驚きです。


ただのモジュールだけなら700円で、


aitendoに売っている専用のピッチ変換基板(295円)や、


さらにもっとシンプルな基板(150円)にとりつければ、1000円以下でWifiもBluetoothも使えるということになります。この場合、USBシリアル接続はついていませんが、一度OTA(無線でアップロード)を書き込んでしまえばUSB端子も不要なので、このほうがシンプルでいいかもしれません。

ちなみにAliExpressだとESP32は少し安いくらいです。
AliExpress.com Product - ESP32 ESP-32S Development Board WiFi+Bluetooth Ultra-Low Power Consumption Dual Cores ESP-32 ESP-32S Board New Arrival
1281円(送料無料)。
秋月のと同じものなのか、一応技適マークがついています。


Arduino IDEで試してみる:
まずは、ESP32用のライブラリをこちらのサイトからインストール。ターミナルで入力ですが、書いてある数行のコマンドをまとめてコピペして実行すれば一気に終わります。

USBシリアル変換ドライバのインストール:
さらに、この秋月のボードの場合はCP2012というUSBシリアル変換チップが搭載されているので、このドライバもこちらのサイトからダウンロード&インストールします。

Arduino IDEでアップロード:
ESP32をUSB接続し、とりあえずBlinkをアップロードしてみることにしました。
設定はこんな感じ(そのまま)。
ESP32ボードの角にGNDがあり、そのとなりに23番ピンがあるので、その二つにLEDを差し込んでみました。そのため、以下のBlinkのコードでは23番ピンをデジタル出力に設定してます。
アップロードスピードが921600もあるせいか、あっというま。


とくに問題なし。リセットボタンなど押してからアップロードかと思いましたが、何も押さなくてもアップロードできました(もしかしたら、開発用ボードだからでしょうか?)。
こんなに便利で安いと、もうArduinoボードを買わなくなりそうです。
ちなみに、この秋月のESP32ボードだと、通常のブレッドボードに差し込むと片側に1列しか余白ができません。サンハヤトのニューブレッドボードであれば一列ずつ幅広(通常5列なのが6列になっている)なので大丈夫ですが、意外に高い。


あるいは、

AliExpress.com Product - Free shipping ESP32-T Shield ESP32-Bit Development Board Compatible For ESP-32S Bluetooth WiFi Module ESP32S Wireless Board788円(送料無料)。このボードに搭載するなら、1列分幅が狭いので、通常のブレッドボードでも両脇1列ずつ余裕ができます。これは、aitendoでも売ってますが(680円)、現在品切れ。

ESP-IDF:さらにESP32について調べてみると、どうやらEspressif公式のESP-IDFという開発フレームワークもあるようです。より細かな設定などは、ESP-IDFのほうがいいかもしれないので、そちらも試してみることにしました。

ESP-IDFのインストール:
このサイトにインストール方法がのっています。基本的にターミナルで入力ですが、手順に沿ってやっていけば大丈夫だと思います。

唯一、パスを通すところが面倒かもしれません。

export PATH=$PATH:$HOME/esp/xtensa-esp32-elf/bin
この部分ですが、これを不可視ファイルの.profileに記入。.profileは/Users/username/.profileにあるはずです。不可視ファイルなので、今回はFunterを使って可視化してエディタで上記パスを直接書き込みました(vimやemacsをつかえばいいのかもしれませんが、慣れていないので)。Macなら$HOME(Linuxぽい)は/Users/usernameと同じなので、このままでも大丈夫そうです。
パスを記入する不可視ファイルは.bashrcや.bach_profileなどありますが、今回は.profileに書き込みました。
ただ、書き込んだだけではまだパスが通っていないらしく、

source ~/.profile
をターミナルで入力する必要があるようです。
さらに、パスが通ったか確認するには、

echo $PATH
を入力します。そうすると、先ほど追加したパスが出てきます。他のパスも書き込んである場合、複数でてきます。これでパスに関しては大丈夫かと。

あとは、サンプルとなるmyappをgit clone(ダウンロード)して、書き込むという手順ですが、myappがあるディレクトリへ移動し、
cd ~/esp/myapp
などと入力し、あるいはmyappがなくて、examplesのhello_worldで試すなら、
cd ~/esp/esp-idf/examples/get-started/hello_world
へ移動し、

make menuconfig
この↑コマンドを入力すると、青い画面がでてきて、ESP32ボードのシリアルポートを設定します。
矢印キーで移動しながら、リターンキーで決定しつつ、先ほどのArduino IDEでも選んだシリアルポートを以下の欄に書き込みます。


以下の画面で<Save>を選択してリターンキー。

あとは<Exit>で画面から抜け出て、またターミナルの通常の画面に戻ります。
シリアルポートを見つけるにはMacの場合なら、
ls /dev/tty.*
この↑コマンドで出てくるはずです。これででてこなければ、ドライバがインストールされていないか、それともUSB接続してないかということです。
そして、最後に
make flash
このコマンドで書き込みということになります。


/Users/username/esp/myapp/main/main.cがプログラムです。
これをエディタなどで開いて書き直し、
/Users/username/esp/myappのディレクトリへ移動して
make flashを入力すれば、いいというわけです。

とくに複雑なプログラムじゃないかぎりは、Arduino IDEで十分だと思います。Arduino IDEのほうが他のセンサーなどのライブラリも使えるので。
しかし、ESP-IDFにしかないようなサンプルの場合はこちらを使ったほうがいいかもしれません。例えば、ESP32-camera-demoなどのサンプルは個人的に興味あるので、これはESP-IDFを使おうかなという感じです。

PlatformIO:
その後、開発環境について調べてみると、PlatformIOを使うと便利ということがわかり、早速インストールしました。

いろいろとやることが増えて面倒かと思いましたが、PlatformIOはAtomというエディタの上で動くIDEで、使ってみるとかなり便利そうでした。
Arduino以外にもいろんなボードに対応しており、サンプルなどのインポート、シリアルモニタ、ターミナル、ボードへのアップロードが全てひとつでできます。さらに、プラグインでHTMLの表示もできます。
Arduino IDEも以前に比べるとボードの種類も増え、ボードごとに設定などを変えてアップロードするように複雑になってきたので、それほど面倒さはかわらないかもしれません。この際、PlatformIOに変えて使ったほうがよさそうです。
たとえばArduino Unoのプログラムの際、書いているのはC++ファイルなのですが、#include "Arduino.h"があるためか、内容はArduinoで使っているコマンドと同じで、構造もvoid setup()とvoid loop()という感じです。PlatformIOに変えると、途端に全てがC言語っぽくなるのかと思ったら、そうでもありませんでした。いままでと変わらないので、すぐに使えると思います。

一度Arduino UnoやESP8266あるいはESP32にアップロードしてみるところまでやってみれば、使い方はだいたいわかるので、思ったより手間が増えるというわけでもなかったです(Arduino IDEの簡単さとあまりかわりませんでした)。これからは、たぶんPlatformIOを使っていこうと思います。
ハードウェア/ソフトウェアの種類、プログラミング言語の種類などがいろいろ増えて、どれからやればいいかという感じですが、PlatformIOはそれらをまとめてくれるような感じかもしれません。おかげで手段や方法で悩むより、最終的にやりたいことに集中できそうです。


ちなみに、ESP8266でCNCマシンを動かしている例もあるようです。

ESP32ならピンの数も多いし、さらにメモリやクロック数も上で、WifiだけでなくBluetoothもあるので、もしGrblが移植できるのであればワイヤレスそしてIP化がすぐ可能というわけです。たった切手サイズの1000円以下のマイコンでここまでできるというのは、すごい時代になったと思います。近いうちに全ての電化製品にIPアドレスが割り振られてしまうのでしょうね。

関連:
ESP32:Webサーバから外部ファイルを読み込み

2017年3月16日木曜日

IoTその2:ESP8266(Wifi赤外線リモコン:仮)

以前IoTについて投稿しましたが、その時にAliExpressで注文したESP8266を触ってみることにしました。届くまで数週間かかるのですが、その時間差がある分、他のことに興味が湧いていたりするので、届いてもすぐに開封しないで放置しておくことがよくあります。
Arduino関係はCNC以外にしばらくやっていなかったので、今更ESP8266なのですが、そのおかげでネットで調べるとたくさんサンプルがあるので、すぐにできそうです。

575円(送料無料)。ESP-WROOM-02、直接USB接続できるタイプのものです。この画像でもかすかに見えますが、一応技適マークがついていました。

とりあえず、これでやりたいことは、学習型赤外線リモコンにしてエアコンのオンオフをスマホやMacBookのブラウザからすること。それだけだと勿体無いので、温度センサーやリレーによるAC100V電源のオンオフ(照明器具用)。どちらかというと趣味や興味というよりも、実用的な意味でこういったデバイスが必要なので、これもまた自作しようかと。
できれば、もう一つ作って、CNCマシンの電源(AC100V)もブラウザ経由でオンオフすることで、bCNCのPendant機能やBluetoothによる制御も含め、全てワイヤレス化しようかと思っています。

以前、BroadLinkの以下のWifi赤外線リモコンも使えるかなと思いましたが、


eRemote miniに置き換えられたようで、MACアドレスの識別によって専用アプリからは操作できなくなるようです。しかし、中国版は安いのに、日本版はかなり高い(5000円くらい)。ちなみにこれはリモコンだけで温度センサーはついていないようです。
いずれにしても、このようなWifiリモコンであれば、ESP8266と赤外線LEDがあれば1000円以下ですぐにつくることができる他、温度センサーやその他の機能も追加できます。
RM mini 3やeRemote miniは、Marvel88MC200というかなり高性能なCortex M3プロセッサと88W8801というMarvel Wifiチップが使われており(ここに書いてありました)、ESP8266やArduinoで改造もしにくい感じです。GitにもPythonMQTTで操作する方法など載っていましたが、そこまでやる必要もないかと。
ということで、RM mini3は見送って、ESP8266を使って代替品をつくった方がいいという結論になりました。


赤外線リモコンパーツ:
以前にもArduinoで赤外線を使ったことがあったので、赤外線LED赤外線受信モジュールが残っていました。
昔、秋月で買ったパーツ。安い。右の黒いのが赤外線受信モジュール(数十円)。
これらのパーツとESP8266をつなげばWifiリモコンができるはず。さらに温度センサーやリレーを追加してもESP8266も含め1000円以下。


IRremoteライブラリ:
ネットを調べるとArduinoのIRremoteライブラリを使っている人が多そうだったので使って見ました。FUJITSUのエアコンなのですが、簡単に読み込むことができました。各メーカーごとのデータもこのライブラリ内に揃っているようなので、大体は認識してくれるようです。
あとは、このデータを使って実際にエアコンがオンオフできるか試して見ると、あっさり動きました。
そのまま赤外線受信モジュールで読み込ませたRAWデータを再生させる方法もあったので、これで複数のボタンを覚えさせれば、どんなリモコンであっても簡単に操作できそうです。
しかし、よく調べて見ると、赤外線LEDを取り付けるピンの指定があるようです。要はAVRのタイマーに対応したピンでなければならないようです。基本的にはPWMピンの3番ピンや9番ピンを使えという感じです。
通常のデジタル出力のピン(例えば8番ピンなど)でdelayMicroseconds()でパルス生成してもできるのかもしれませんが、もしかすると安定しないのかもしれません。


38kHz:
赤外線リモコンは、パルスのオンオフで信号を送っているのですが、38kHzでさらに細かいパルス(キャリア)を使わなければいけないようです。前もやったことがあったので、何となく思い出してきました。確かこの38kHzの細かいパルス生成の精度やタイミングが難しかったような。
IRremoteライブラリは、この細かいキャリア周波数を精度よくArduinoで実現している代わりに、ピンに制約があるという感じです。
周期が26.3usで、さらに1/3のデューティ比なので、HIGHの時間はたった8.7usとなるようです。ArduinoのdelayMicroseconds()は3usが限界のようで、おそらく8.7usも処理の仕方や割り込みによってはずれてしまいそうです。Arduino Unoの解像度の限界に近い処理なので、C言語で書いた方が良さそうです。
以前、TVの信号をArduinoでつくった時も、そんな感じでタイミングがずれてしまって、画面が歪んでしまったことがありました。


自前でパルス生成:
ライブラリを使えば簡単でしかも安定した周期を実現できそうですが、動けばいいという程度の精度で十分なので、一度delayMicroseconds()を使って自前でやってみることにしました。どのピンでも使えた方が便利なので。
ネットで調べると、ライブラリを使わずに自前でこの周期をつくっている例もあったので、それらを参考に(こちらのサイト)、Arduino Unoで試してみることに。

digitalWrite(pin,HIGH);
delayMicroseconds(9);
digitalWrite(pin,LOW);
delayMicroseconds(17);

26.3usの1周期は、タイミングのずれを考えなければ数値的にはこんな感じでいいのですが、これだと全くエアコンは反応しませんでした。おそらくArduino Unoの内部処理でずれまくっているのでしょう。もう少し調べて見ると、デューティ比は1/3ではなくても大丈夫そうなので、おそらくこの1周期内にあるHIGHとLOWの経過時間の合計の26.3usを重視した方が良さそう。
Arduino Unoは16MHzなので、1クロックが1,000,000us(1sec)/16,000,000=1/16us(0.0625us)。ただ、必ずしも1クロックで全てを処理してくれるわけではないので、記述の仕方ではかなりロスが出るはず。確かdelayMicroseconds()よりも、C言語の_delay_us()を使った方が速かったような。for文など多用するとかなりロスがあったような記憶があります。しかし参考にしたこの方のサンプルでは単純にdelayMicroseconds()を使っており、

digitalWrite(pin,HIGH);
delayMicroseconds(8);
digitalWrite(pin,LOW);
delayMicroseconds(7);  //UNO:7us, ESP8266:16us

合計で理論上26.3usのところ、8+7=15usですが、このタイミングでやって見るとエアコンが反応しました。ロスが11usくらいあるという感じですが、これで機能するのでこのまま行くことに。C言語やアセンブラ言語で動かした方が確実かもしれませんが、ここはあえて平易なArduino言語で簡単に済ませられるのあれば、それに越したことはないという感じです。業務用ではないので、精度よりも簡単さで。
どうやらESP8266の方がクロック数はArduino Unoよりも上なので、遅いArduino Unoで動くのなら大丈夫なはずです。
追記:
その後ESP8266で試したところ、Arduino UnoではdelayMicroseconds(7)の部分が、デューティ比約1/3のdelayMicroseconds(16)でも動きました。ESP8266のクロック数は80MHzなので、Arduino Unoのようなタイミングのズレは考慮しなくても大丈夫そうです。


送信用プログラム:
ということで、以下が送信用の試験用コードです。まだArduino Unoで試している段階なのでESP8266用のコードではありません。
数値の羅列がエアコンのオンとオフの信号(単位us)です。この信号は、次にある受信用プログラムで読み込んだ値です。この信号データを元に、赤外線LEDがパルスをエアコンに送りオンとオフをします。例えば、data_on[243]の方であれば、配列の最初の3456がHIGHを3456us、そしてLOWに切り替えて1412us、またHIGHで580usという感じで、配列の偶数番目(最初は0番目からスタート)がHIGHで、奇数番目がLOWという繰り返しになります。
Arduinoのシリアルモニタ上で、"a"を送信でオン、"b"を送信でオフ。ちなみにFUJITSUのエアコンです。なぜかオンの信号の方が多い。PWM用のピンではなく、8番ピンでHIGH/LOWを単純に切り替えつつdelayMicroseconds()を使っています。

#define send_pin 8
#define duty_high 7
#define duty_low 7  //UNOの場合は7, ESP8266の場合は16

unsigned int data_on[243] = {
  3456,1412,580,216,580,212,572,1036,564,236,556,1044,552,244,544,252,536,256,532,1072,
  524,1080,512,284,512,280,504,292,504,1108,500,1108,492,296,500,296,500,292,500,292,
  504,292,500,296,500,296,496,296,500,296,500,296,500,296,496,300,504,288,500,1108,
  496,324,476,316,476,320,472,320,476,324,476,316,476,320,476,1132,476,316,476,316,
  476,320,476,324,476,320,464,1136,456,1148,452,1156,448,1160,448,1156,448,1156,448,348,
  448,348,444,348,444,1160,440,352,444,352,444,348,448,352,444,352,444,348,444,360,
  436,356,444,1156,444,1160,444,356,444,348,444,1160,448,348,440,356,444,348,440,352,
  444,352,444,352,444,1164,444,348,444,352,444,1160,444,352,444,352,444,352,444,352,
  440,352,444,352,444,352,444,352,440,348,444,352,440,348,440,356,440,352,444,352,
  444,348,444,356,444,348,444,352,444,352,440,348,444,352,440,356,440,356,440,356,
  440,352,440,376,412,360,436,380,416,356,436,356,440,356,436,364,432,376,416,380,
  416,376,412,380,412,384,412,1196,412,1192,408,380,408,1192,412,384,412,380,412,1196,
  412,388,412};

unsigned int data_off[99] = {
  3492,1400,604,192,596,196,588,1020,580,220,576,1024,572,224,564,232,556,236,552,1052,
  552,1056,552,244,548,244,544,252,540,1064,536,1072,532,264,528,268,524,268,524,268,
  524,268,528,268,528,268,528,268,524,268,524,272,524,272,524,296,500,288,500,1088,
  520,296,496,292,500,300,496,296,500,300,496,292,496,296,500,1112,496,296,496,296,
  496,300,496,296,500,1104,500,296,500,296,496,300,496,300,492,304,492,304,488};
  
void setup() {
  pinMode(send_pin, OUTPUT);
  Serial.begin(57600);
}

void loop() {
  if(Serial.available()>0){
    int val=Serial.read();
    if(val=='a'){
      ir_send(data_on,243);
      Serial.println("on");
    }else if(val=='b'){
      ir_send(data_off,99);
      Serial.println("off");   
    }
  }
}

void ir_send(unsigned int data[], int d){
  for (int i = 0; i < d; i++) {
    unsigned long duration = data[i];
    unsigned long start_time = micros();
    while (start_time + duration > micros()){
      digitalWrite(send_pin, 1-(i&1));
      delayMicroseconds(duty_high);
      digitalWrite(send_pin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

C言語(あるいはArduino言語)の場合、配列の長さを取得するには、Javaなどのarray.lengthのようなコマンドはなく、sizeof(array)を使わなければいけないのを始めて知りました(それと動的に配列の長さを変えられないということも)。しかも、sizeof()がメモリのバイト数なので、intの場合sizeof(array)/sizeof(array[0])というように書かないといけないらしく、配列の長さの変数をir_send()関数に入れると、なぜか処理が滞ってしまうので、データの配列data[]とその配列の長さdをir_send(unsigned int data[], int d)という形で直接渡すことにしています。元々はオンとオフの二つ別々の関数をつくっていたのですが、その後ボタンも追加されて信号用配列データも増えるかもしれないので、ir_send()という関数にしてしまいました。そのせいで、また少し悩んでしまったということです。元々、電子回路の制御用プログラムでは、複雑なコードを書かないので、また勉強になったという感じです。
digitalWrite(send_pin,1-(i&1))のHIGH/LOW切り替えの部分は、&でビットマスクすることで奇数か偶数かを分けつつ反転させていますが、digitalWrite(send_pin,!(i%2))でもいいと思います。ただ、そうすると微妙に処理速度が変わってしまうかもしれないので、参考にしたコードのままにしてあります。


受信用プログラム:
読み取りの方はIRremoteでやってもいいのですが、一応こちらもライブラリは使わず。このプログラムで得たエアコンのオンとオフの信号が、上記送信プログラムに使われています。
送信用のデータでは、配列の一個目(配列における0番目)はHIGHですが、受信モジュールで読み込む際には、HIGHとLOWが反転しています。つまり受信用データ配列の0番目はLOWを検出します。

#define read_pin 8
#define led_pin 13
#define data_size 256
unsigned long ir_data[data_size]={};
boolean state;
unsigned int count;
boolean ready_data;

void setup() {
  Serial.begin(57600);
  pinMode(read_pin,INPUT);
  pinMode(13,OUTPUT);
  state=HIGH;//first:HIGH, then:LOW(pulse starts)
  count=0;
  ready_data=false;
}

void loop() {
  if(Serial.available()>0){
    unsigned long start_time=micros();
    while(state==digitalRead(read_pin)){
       ready_data=true;  
    }
    if(ready_data==true){
      if(micros()-start_time<100000){//count over
          output_data();     
        }
      }else{//time over
        state=!state;
        Serial.println("TIME OVER");
        output_data();
      }
      ready_data=false;
    }
  }else{
    digitalWrite(13,HIGH);
    delay(100);
    digitalWrite(13,LOW);
    delay(100);
  }
}

void output_data(){
  Serial.println("ir_data:");
  for(int i=0;i<data_size;i++){
    if(ir_data[i]==0){
      Serial.println("");
      Serial.print("TOTAL:");
      Serial.println(i);
      break;
    }else{
      Serial.print(ir_data[i]);
      if(i%20==19){
        Serial.println(",");
      }else{
        if(i<data_size-1 && ir_data[i+1]!=0){
          Serial.print(",");
        }   
      }
    }
  }
  Serial.println("END");
  Serial.println("");
  for(int i=0; i<data_size; i++){
    ir_data[i]=0;
  }
  count=0;
  Serial.read();//read 3 bytes
  Serial.read();
  Serial.read();  
}

受信用プログラムで得た送信用データをIRremoteライブラリで得たデータと比較してみましたが、それほど大きなずれもなく使えるので、そのままこのプログラムを使ってデータを読み取りました。

光量不足?:
一応、これら二つのプログラムでエアコンを動かすことができましたが、パルスのせいか赤外線LEDの出力が全開ではないため、2mくらいまで近づけないと反応しない時がありました。それもあってLED用の抵抗は外してしまったのですが、もう一つLEDを増やすか電力アップしないといけないかもしれません。データの読み取り精度の誤差によって反応しにくくなっていることも多少考えられます。ノイズ対策は一切行っていないのでコンデンサーを入れるだけでも少しはましになるかもしれません。


制御用ブラウザ画面:
これら赤外線受信/送信用プログラムを元にESP8266用にブラウザで制御可能なプログラムを書いてみました。まだ赤外線でエアコンをオンオフするだけで、AC100V電源のオンオフはリレーをつけていませんが、以下のような感じ。

これもネットにあったサンプルに必要な部分を追加しただけ。
温度計測はこれから追加する予定です。
以下が、ESP8266のプログラム。14、15行目のssidとpasswordに使っているWifi名とパスワードを入れて、ESP8266にArduino IDEからアップロードし、シリアルモニタを開くとIPアドレスが出てきます。あとはブラウザでそのIPアドレスにアクセスすれば、上の画面が現れるはずです。
ブラウザ上に表示させるHTMLはwebPageという変数に入れてあります。HTML内の「\"」は、「"」のことです。そのまま「"」で送るとArduinoのプログラム上で使われる「"」なのか、それとも送られる文字列としての「"」なのか判別できなくなるために、「\"」を使わなければいけません。

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

#define gpio13Led 13
#define gpio12Relay 12
#define sendPin 14
#define duty_high 8
#define duty_low 7 //UNO:7us, ESP8266:16us


MDNSResponder mdns;

const char* ssid = "*****";
const char* password = "*****";

ESP8266WebServer server(80);

String webPage = "";

unsigned int airOn[243] = {
  3456,1412,580,216,580,212,572,1036,564,236,556,1044,552,244,544,252,536,256,532,1072,
  524,1080,512,284,512,280,504,292,504,1108,500,1108,492,296,500,296,500,292,500,292,
  504,292,500,296,500,296,496,296,500,296,500,296,500,296,496,300,504,288,500,1108,
  496,324,476,316,476,320,472,320,476,324,476,316,476,320,476,1132,476,316,476,316,
  476,320,476,324,476,320,464,1136,456,1148,452,1156,448,1160,448,1156,448,1156,448,348,
  448,348,444,348,444,1160,440,352,444,352,444,348,448,352,444,352,444,348,444,360,
  436,356,444,1156,444,1160,444,356,444,348,444,1160,448,348,440,356,444,348,440,352,
  444,352,444,352,444,1164,444,348,444,352,444,1160,444,352,444,352,444,352,444,352,
  440,352,444,352,444,352,444,352,440,348,444,352,440,348,440,356,440,352,444,352,
  444,348,444,356,444,348,444,352,444,352,440,348,444,352,440,356,440,356,440,356,
  440,352,440,376,412,360,436,380,416,356,436,356,440,356,436,364,432,376,416,380,
  416,376,412,380,412,384,412,1196,412,1192,408,380,408,1192,412,384,412,380,412,1196,
  412,388,412};
  
unsigned int airOff[99] = {
  3492,1400,604,192,596,196,588,1020,580,220,576,1024,572,224,564,232,556,236,552,1052,
  552,1056,552,244,548,244,544,252,540,1064,536,1072,532,264,528,268,524,268,524,268,
  524,268,528,268,528,268,528,268,524,268,524,272,524,272,524,296,500,288,500,1088,
  520,296,496,292,500,300,496,296,500,300,496,292,496,296,500,1112,496,296,496,296,
  496,300,496,296,500,1104,500,296,500,296,496,300,496,300,492,304,492,304,488};

void setup(void) {
  webPage += "<html><header><title>WIFI SWITCH</title></header>";
  webPage += "<body style=\"text-align:center;font-size:20px\"><div><font size=\"2\">WIFI SWITCH</font></div><br/>";
  webPage += "<div style=\"font-size:24px\"><div>AC100V SWITCH:</div>";
  webPage += "<div><a href=\"on\"><button style=\"width:80%;font-size:20px\">POWER  ON</button></a></div><br/>";
  webPage += "<div><a href=\"off\"><button style=\"width:80%;font-size:20px\">POWER OFF</button></a></div><br/>";
  webPage += "<div>AIR-CON SWITCH:</div>";
  webPage += "<div><a href=\"airon\"><button style=\"width:80%;font-size:20px\">AIR-CON ON</button></a></div><br/>";
  webPage += "<div><a href=\"airoff\"><button style=\"width:80%;font-size:20px\">AIR-CON OFF</button></a></div>";
  webPage += "</div></body></html>";
  
  pinMode(sendPin, OUTPUT);
  pinMode(gpio13Led, OUTPUT);
  digitalWrite(gpio13Led, HIGH);
  pinMode(gpio12Relay, OUTPUT);
  digitalWrite(gpio12Relay, HIGH);

  Serial.begin(115200);
  delay(500);
  WiFi.begin(ssid, password);
  Serial.println("");

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (mdns.begin("esp8266", WiFi.localIP())) {
    Serial.println("MDNS responder started");
  }

  server.on("/", []() {
    server.send(200, "text/html", webPage);
  });
  server.on("/on", []() {
    server.send(200, "text/html", webPage);
    digitalWrite(gpio13Led, LOW);
    digitalWrite(gpio12Relay, HIGH);
    delay(1000);
  });
  server.on("/off", []() {
    server.send(200, "text/html", webPage);
    digitalWrite(gpio13Led, HIGH);
    digitalWrite(gpio12Relay, LOW);
    delay(1000);
  });
  server.on("/airon", []() {
    server.send(200, "text/html", webPage);
    air_on();
    delay(1000);
  });

  server.on("/airoff", []() {
    server.send(200, "text/html", webPage);
    air_off();
    delay(1000);
  });

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void) {
  server.handleClient();
}

void air_off() {
  int dataSize = sizeof(airOff) / sizeof(airOff[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = airOff[cnt];
    unsigned long us = micros();
    while (us + len > micros()){
      digitalWrite(sendPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(sendPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void air_on() {
  int dataSize = sizeof(airOn) / sizeof(airOn[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = airOn[cnt];
    unsigned long us = micros();
    while (us + len > micros()) {
      digitalWrite(sendPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(sendPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

こちらのコードでは、エアコンのオンオフは、air_on()とair_off()の二つ関数に分けて書いてあります。試行錯誤して多少前後したので、実は一個前のコードのままです。今後さらに改良して行きます。まだ実験段階という感じです。
それにしても、こういったコードをブログに載せるとエスケープシーケンスでいくつかの記号を置き換えなければいけないので面倒です。
ちなみにGoogle code-prettifyを使ってコードを載せています。

あとは、IPカメラとも接続可能にできるといいのですが、以前やっていたbCNCのIPカメラ化も中断しているので、そのうちやろうと思います。

2017年3月11日土曜日

スピンドルクランプロッド(ER11用)

現在はスピンドルとしてマキタのトリマを使用しています。コレットコーンはトリマ専用のものでシャンク径6mmの小型コレットが内臓されています。
ルータービットにはめ込んである黒いテーパーのついた部品がコレットコーン(内径6mm)です。このコレットコーンには1/4インチ(6.35mm)もあるようですが、あまりバリエーションはなかったはず。普通は、コレット(シャンク)で径を変えるというよりも、ルータービット刃先の方で径を選ぶという感じです。

このルーターの回転軸にERコレットを取り付けることができれば便利になるのではないかと試して見ましたが、どうやら直径は同じ14mm(ネジ山部分)なのに、ネジのピッチが違うようです。残念。
手前が専用コレット・コレットナット、奥がER11コレット・コレットナット。どちらも6mmのエンドミル。
ネジ山のピッチが同じだとしても、そもそもER11のコレットが大きすぎて、このトリマの軸に入らない。つまり、この軸を改造しないことには無理と言う感じ。

そこでたまたま見つけた、以下のような、ER11がついた部品ならいけるかもと、いつものAliExpressで試し買い(そんなに高くないので)。
AliExpress.com Product - Newest ER11 Spindle Motor Chuck Collet Clamp Extension Rod For CNC Milling Carving Machine #05
551円(送料込み)これには軸径3mm、1/8インチ、4mm、5mm、6mm、1/4インチ、8mm、10mmなどあるようです。このショップでは、注文の際に軸径を指定、指定なしだと自動的に5mmになってしまうので要注意。CNCマシンのスピンドルモーターの軸径に合わせて直接取り付ければ、すぐにER11コレットが使えるという結構便利な部品。

こんな感じのものです。今回は6mmの軸用のものを購入しました。左側の部品は直径16mm、長さ35mm(コレットナットなしで)。コレットナットはついていますが、中に入れるコレットはついていないので、必要な径のER11コレットを別途購入する必要があります。 AliExpress.com Product - ER11 1pcs Printemps Collet 1mm 2mm 3mm 4mm 5mm 6mm 7mm 8mm pour CNC Tour Fraisage Machine de Gravure outilとは言っても、AliExpressなら安いです。127円(送料無料)、ER11コレット6mm。
ということで、ルーターに6mmの軸を取り付けて、さらに取り付けると言う感じになります。ちょっと長くなりすぎてブレそうですが、試しにつけてみます。
まずは古くなった6mmのドリルビットをグラインダーで短く切って、取り付け用の軸として使います。

短く切ったドリルビットを装着するとこんな感じになります。この先に、今回の部品を取り付けます。

結構長い。刃の先端まで90mmくらいあります。これは一番詰めた状態。木材ならなんとか削れそうですが、金属だとちょっと微妙な感じ。




まだ加工は試してないので、どうなるかわかりませんが、そのうち結果をアップロードしたいと思います。不安定そうだったら、また元に戻すつもりです。

この部品は、アマゾンにもありましたが、相変わらず高いです(1500円くらい)。


コレットは安いですが、それなりに割高ではあります。
試してみるというほど安くはない感じです。

2017年3月4日土曜日

Universal-G-Code-Sender(Nightly builds)について

普段はbCNCを使っていますが、久しぶりにUniversal-G-Code-Sender(UGS)を使ってみました。UGSはJavaベースなので、MacでもWinでも使えるし、bCNCのようにPythonをインストールしたりする必要もなく、すぐに使えるところがいいと思います。
現在の安定版は1.0.9ですが、今回はNightly buildsを試してみました。
UGSのGitに行くと、このように↑ダウンロード可能な幾つかのバージョンがあります。これの2.0 Nightly buildsというまだ開発中のバージョン。classic GUIとUGS Platformの2種類あります。

こちらはUGS Platformの方です。全く見た目も違います。特にVisualizerがかなりよくなって、このように画面分割表示も可能です。bCNCでは画面分割はできないと思います。

以前のUGSのVisualizer、別画面で座標もないし超シンプル。確かにこれに比べると断然良くなったと思います。

ちなみに画面分割など、表示させたいウィンドウを追加するには、表示領域を選びつつメニューバーから項目を選べば、さらなるウィンドウを追加して行くことができます。

 Grbl1.1のオーバーライド機能にも対応したウィンドウもあります。

そしてジョグ操作もGrbl1.1に合わせて、「$J=」コマンドで送信されるようです。
このUGS Platformは、ほとんど別のソフトというくらい改良されている感じです。Nightly buildsなので、まだ細かなバグなどあるのかもしれませんが、それほど問題なさそうです。というか、オープンソースなので、使ってみてバグがあれば改良するか報告してあげるのがいいと思います。

そして、こちらはNightly buildsのclassic GUIの方の画面。見た目的にはそれほど変わってないのですが、ジョグ操作は「$J=」に対応しています。
そして、今更気づきましたが、画面左上の方にPendantがあります。
これを押すと、なんとUGSでもPendant機能が使えるではありませんか。
スマホからアクセスしてみると、ちゃんと繋がります。これはbCNCのPendant機能同様に加工原点出しの際には便利です。試しにスマホからジョグボタンを押してみると、step sizeが10mmになっているのですが、なぜかゆっくりとFeed1の移動になってしまいます。どこかに設定できるメニューがあるのかと探してみましたが見当たりません。バグでしょうか?
しかし、それよりも現在安定版である1.0.9の方も見てみると、同様にPendant機能がついていました(今まで気づきませんでした)。

総合的に見ると、bCNCの方が、オフセット加工、タブ配置機能、カメラ機能など、実際によく使う機能が豊富についているので便利なことは確かです。ただbCNCの場合は、少しボタンの反応が遅いのが難点かもしれません。
とはいっても、Gコードの方であらかじめオフセットパスやタブをつけておいたりすればいいので、G Code Senderがそれほど多機能である必要もないかもしれません。あとはパソコンとの相性だったり好みの違いになるくらいでしょうか。

2017年3月3日金曜日

経路探索(A*:A-star)アルゴリズムについて

直接CNCには関係ないかもしれませんが、プログラミングではよくテーマになる経路探索について最近調べていました。いくつか探索アルゴリズムがあるようですが、代表的なA*(A-star)アルゴリズムを実際に書いて試してみました。


Gコードを見ると上から順番に加工用の座標が並んでいます。図面上にある複数の座標点をどのような順番で処理していくのかというのが気になっていました。たまに加工中の動作を見ていると、近いところから加工しているように見えますが、突然遠回りしていたりするので、何かしらの判断基準があって、それがどの程度効率よく動いているのかが気になります。
もしGrblのようなソフトをプログラミングするには、こういった加工の順番を処理するプログラムも必要なのかもしれません。例えば、3箇所にドリルの穴を開ける場合、加工開始点から一番近い位置、そして二番目、三番目と移動すれば最短距離になると思いますが、その順番が違えば、無駄な移動が含まれるために加工時間も変わってくるはずです。
もしベクターデータやdxfデータの段階で、その順番がもうすでに決まっていて、Grblはその順番に従って動いているのであれば、Gコード生成プログラムの方にその原因があるのかもしれません。ソフトの中身まで検証していないので、そこまではまだわからないのですが、Gコードのみならず、他の場面においても、探索アルゴリズムは何かの順序を決定する際のプログラムとして汎用性があると思います。

以下が今回プログラムしたアルゴリズムです。このブログ上でも実行可能になっています。
一般的に定義されているA*アルゴリズムになっているかはわかりませんが、自分なりにある程度考えて書いて見ました。

*表示画面内を空クリックしてから(フォーカスを与えてから)、キーを押して下さい。

・緑:スタート(ドラッグで移動可能)
・赤:ゴール(ドラッグで移動可能)
・グレー:壁(クリックで壁の有無を切り替える)
・黒:通路(クリックで壁の有無を切り替える)
・↑(UP)キー:グリッドを増やす(最大160x160グリッド)
・↓(DOWN)キー:グリッドを減らす(最小10x10グリッド)
・Nキー:リセット
・その他のキーで探索開始/一時停止
追記:
画面下部に[START/PAUSE][RESET][MORE GRIDS][LESS GRIDS]ボタンを追加しておきました。

*緑丸は現在の探索位置、薄緑は探索済みエリアです。
*初期状態で緑(スタート)か赤(ゴール)がグレーの壁に囲まれて入れば、すでに手詰まりです。移動可能な方向は上下左右だけです(斜め移動は禁止)。初期状態で手詰まりの場合は、クリックで壁を消すか、ドラッグで緑か赤の位置を変更するか、Nキーでリセットしてください。
*ProcessingのコードをProcessing.jsを使って表示しているので、バグがあるかもしれません。


このアルゴリズムについての説明はこちらのページにあります。

2017年2月16日木曜日

Grbl1.1の$コマンドやGコードについて

Gコードについてはまだまとめていないので、とりあえず覚書程度に書いておこうと思います。
普段Gコードや$コマンドについては、やっているうちに覚えるだろうと思って、それほど深く勉強はしていません。作業の効率化が必要というほどでもないので、だいたい動けば問題ないという程度です。全て覚えようとすると大変なので、いつも使っているGコードや$コマンド(よく使うものや設定の際に必要そうなもの)についてです。

参考としているのは、
・Grbl:CommandsページConfigurationページ
LinuxCNC/クイックリファレンス
・一般的なNCコード(ここがわかりやすい

まずは$コマンド(Grbl用)から:
$$
CNCマシンの各設定を表示させるコマンド。$$と入力。一度設定してしまえば、あまり用はないかもしれませんが、たまに間違って設定したままになっていることがあります。レーザーモード($32=0または$32=1)をオンにし忘れていたなど。とりあえず通信できているか確認する時にもよく使います。

$I
Grblのバージョンを確認するコマンド。$Iと入力。
入力すると、[VER:1.1e.20161208:]などと表示され、バージョン1.1、2016年12月8日ビルドということがわかります。古いバージョンだとバグが直っていなかったりするので、最新の方がいいと思います。問題がありそうなら最新バージョンにしてみるというのも解決策だったりします。特に問題なく動いているのであれば、そのままの方がいいかもしれません。

$X
ロック解除。$Xと入力。最初にCNCマシンと接続開始すると、このコマンド(あるいは専用ボタン)を押さないと、その後の操作ができないようになっています。また作業中に異変があって自動停止したあと再開する時も入力する必要があります。

$H
$Hを入力するとホーミングサイクルが開始します。ただし事前に$22=1で設定をオンにしておく必要があります。ホーミングサイクルによってリミットスイッチを頼りに機械原点を探しに動き始めます。
リミットスイッチの設置や事前の設定がなされていない場合は、$22=1にしたり、$Hに相当するボタンなど押さないよう注意した方がいいと思います。

$G
$Gと入力すると(あるいは毎回表示される)、現在アクティブになっている設定モードが出てきます。
[GC:G0 G54 G17 G21 G90 G94 M0 M5 M9 T0 S0.0 F500.0]
それぞれ何なのか、最低限これくらいを覚えればいいと思います。
G0は位置決め用の早送りモード(G1に変更しない限り移動速度は一定)、G21で単位はmm、G90で絶対座標を使用中など。
例えば、この設定の状態のまま[G0の速度($110で設定)で、単位はmm(インチではなく)、絶対座標(G90)]、X10とだけ入力するとX軸座標10mmの位置へ動くことになります。最後の項目でF500.0が設定されていますが、G0になっているため$110で設定した速度が優先されます。もしF500の速度を反映したい場合はG0をG1へ切り替えます(G1と入力)。

$J=
ジョグ操作コマンド。
$J=G91X10F100入力で、現在地からXを10mm分F100の速度で動かすというジョグコマンド。これはGrbl1.1からの新機能で、従来まで(Grbl0.9)は、$Gにある設定モードに左右された入力方法になっていましたが、それとは無関係に入力できるようになりました。例えば、$G内で早送りモード(G0)かつ絶対座標設定(G90)になっているときに、相対座標(G91)でゆっくり(G1)ジョグ操作したい時には、今までは設定を変えてからジョグ操作し、それが終わればまた元の設定に戻すということでしたが、元のモードに戻さなくても$Gの内容はキープされたままというわけです。
ペンダント(ジョグダイヤルやジョイスティックなど)で操作する場合は、これを使った方がいいらしいです。フィードが設定されていないと動かないのでFも入力必要。

$3
XYZ軸の移動向きの設定。配線したものの、実際動かしてみるとY軸だけ逆の動きになっていたという場合は、ここで向きを反転設定できます。
$3=0 (N/N/N:X軸/Y軸/Z軸:デフォルト値)
$3=1 (Y/N/N:デフォルト値に対してX軸だけ反転)
$3=2 (N/Y/N:デフォルト値に対してY軸だけ反転)
$3=3 (Y/Y/N:デフォルト値に対してX軸とY軸を反転)
$3=4 (N/N/Y:デフォルト値に対してZ軸だけ反転)
$3=5 (Y/N/Y:デフォルト値に対してX軸とZ軸を反転)
$3=6 (N/Y/Y:デフォルト値に対してY軸とZ軸を反転)
$3=7 (Y/Y/Y:デフォルト値に対してすべて反転)
例えば、Y軸だけ反転したいのであれば、$3=2を入力


$10
現在のマシンのステータスを知らせる設定。$10=1になっていないと、使っているG Code Senderに現在の座標値MPosやWPosが表示されなくなります。$10=2にすることでバッファの内容を表示することもできるようですが、通常は$10=1でいいと思います。たまにMPosやWPosが表示されなくなったというときはここをチェックしてみるといいと思います。

$13
$13=0で作業単位mm、$13=1でインチ。

$20
ソフトリミット設定。$20=0でオフ、$20=1でオン。
$130、$131、$132で、そのCNCマシンのXYZ軸の最大移動距離を設定できます。ソフトリミットをオンにしておけば、この設定距離を超えると自動停止してくれます。$Xで再開。

$21
ハードリミット設定。$21=0でオフ、$21=1でオン。
リミットスイッチを各軸につけておけば、$21=1にすることでリミットスイッチが押された時に自動停止してくれます。$Xで再開。

$22
ホーミングサイクル設定。$22=0でオフ、$22=1でオン。
ホーミングサイクルを可能にするには、各軸にリミットスイッチを接続する必要があります。
$Hをコマンド入力(あるいは専用ボタン)すると、ホーミングサイクル開始となり、Z軸が+方向へ、続いてXとY軸も+方向へ移動し、リミットスイッチを感知して機械原点MPos(0,0,0)を見つけ出します。通常は左奥上が機械原点MPos(0,0,0)となり、作業エリアはWPos座標上では全てマイナス座標となります。毎回作業前に、ホーミングサイクルを行えば、CNCマシンは機械原点を覚えるので、その後座標をずらしても常にどこにいるのか把握できます。
しかし、実際の作業はMPosとは別に作業用の原点WPos(0,0,0)を任意の位置に設定し(G92で)、WPosを元に位置を割り出すので、MPosがなくても問題ありません。ただ、複数の部品を複数の座標系を使って作業するときなどは、MPosが絶対座標となってくれるのであった方がいいとはおもいますが、個人的にはホーミングやMPosは毎回使っていません。

$23
ホーミングの際のXYZ軸の動く方向の設定。通常は右奥上ですが、使いやすいように左前上に設定する人もいます。$3の反転設定の表と同じで、デフォルト($23=0)に対してY軸だけ反転したい場合は$23=2を入力。

$24
ホーミングの際にリミットスイッチ手前で位置合わせする移動速度。正確に位置合わせをするためにゆっくりめの速度にしておく。20〜50mm/minなど。$24=25などと入力。

$25
ホーミングでリミットスイッチを探しに行く際の移動速度。最高速よりは遅めで。
$25=600などと入力で600mm/minで移動開始します。

$26
ホーミング中にリミットスイッチを押した際のデバウンス/チャタリング(特に機械式スイッチ)防止用のディレイ時間。20〜250msec程度。$25=50と入力でディレイ時間50msecに設定。

$27
ホーミング終了後、安全のためリミットスイッチから少しだけ離れておく距離の設定。1〜5mm程度。例えば、$27=3.0と入力でリミットスイッチが押されたポイントから3mm戻った位置に止まるように設定。

$32
レーザーモードの設定。$32=0でオフ、$32=1でオン。
Grbl1.1からはレーザモード機能が追加され、主にはダイナミックレーザーモード(M4)というヘッドの移動速度に応じてレーザー出力を微調整する機能があります。
従来までは、$32=0のまま、M3S500(レーザーオン、出力50%)などと入力していましたが、$32=1にするならば、M4S500となります。
レーザーを使わないのであれば、$32=0にしておいた方がいいと思います。

$100、$101、$102
XYZ軸のsteps/mm設定。CNCマシンのスペックに応じて設定する必要があります。
以下の条件であれば:
・ステッピングモーター:1回転200ステップ
・ステッピングモータードライバのマイクロステッピング設定:1/8
・送りネジの1回転で進む量:5mm
1/8のきめ細かさでマイクロステップすることになるので、ステッピングモーターは200x8で1回転するには1600ステップ、1回転で送りネジが5mm進むので、1600/5=320steps/mmとなります。$100=320などと入力。
ここは最初に設定すれば、もう変えることもないでしょう。

$110、$111、$112
XYZ軸の最高速度設定。mm/min。
例えば、$110=1000を入力でX軸を1000mm/minに設定。
これらの値は、G0の速度に適用されます。

$130、$131、$132
XYZ軸の最大移動距離。
そのCNCマシンの各軸の移動可能な距離を入力しておくことで、$20=1でソフトリミットをオンにした時にこの値を越えれば非常停止するようになります。
例えば、$130=600を入力でX軸の移動可能な距離を600mmまでと設定。

おそらく初期設定で必要な$コマンドはこのくらいでしょうか。その他の設定はデフォルトのままでも大丈夫かと思います。


ここからGコード:
G0
位置決め用の移動速度モード(速め:一定速度)。
最初はG0になっていますが、G0またはG1のどちらかにモードを切り替え指定します。
$$で表示される$110、$111、$112(XYZ軸の各最高移動速度)に設定してある一定速度で動く。速度を設定し直したい場合、$110=800などと入力し直す。
ジョグボタンで移動する場合は、この移動速度が適用されます。G0のままF100などフィードを入力設定しても移動速度はG0($110〜$112)の速度になってしまいます。一時的に速度を変えて動かしたい場合は次のG1を入力してモードを変えるか、ジョグ操作であれば$J=を使うといいと思います。
ある地点へ斜め方向に移動する場合、X軸もY軸も固定速度($110〜$112で設定した速度)で動くため、例えばX軸が目標となるX座標へたどり着いたら、Y軸だけがまだ動き続けているという現象になることがあります。最短距離で目標地点に動くのではなく、各軸の固定速度で動くために折れ曲がった軌道を描くことから加工作業には適していないようです。

G1
フィード(F)設定による移動モード。Gコードで設定した速度で移動可能なモード。G0とは違って、その都度設定したフィードで動きます。主に加工用のモードです。モードがG0になっている場合は、G1を入力することで、このモードに切り替わります。
加工条件に合わせたフィード設定が必要となります。F値が設定してない場合(F=0になっている場合)は、移動コマンドを入力してもフィード未設定というエラーが出るので、F値も忘れずに入力。

G2、G3
円弧を描く移動モード。G2が時計回り、G3が反時計回り。
G0とG1が直線なのに対し、G2とG3が円弧で動くという程度で覚えておけばいいと思います。円を描く際には、このほかI、J、K、Rなどの値も設定しなければいけないのですが、わかりにくいのでCADなどで描いてGコード化した方が早いかもしれません。
G2 X0 Y0 I0 J-20 F300
これは以前実験のために使った円を描くGコード(現在地から時計回りに半径20mmの円を描く)ですが、直感的にわかりにくいです。
G2(円弧時計回り)、X0 Y0(終点座標/開始点でもあるけど)、I0 J-20(現在地からの中心座標の差分)、F300(300mm/minのfeed速度設定)。
IはXに、JはYに対応しており、J-20の部分は、現在地からY軸方向へ20mm下がったところに中心点があるという意味になります。
この程度の試運転用Gコードであるならば、覚えるというよりも、コードを記録しておいてコピペして使ったり、ソフトのボタンなどに割り当てておけばいいと思います。

G17
作業がXY平面という設定。これも変えたことがない。G18(XZ平面)、G19(YZ平面)らしい。おそらくXY平面が基本となることがほとんどなので、これもこのままでいいかと。

G21
作業の単位がmm。G20でインチへ変更可能。これも大抵はmmなので、このままでいいかと。動きが変だと思ったら一応チェックする程度。G20かG21で切り替え。

G28、G30
G28(あるいはG30)を入力することで、予め任意に設定しておいた位置(ツール交換位置、ホームポジションなど)へ移動。位置を記憶させるには、ジョグボダンなどで任意の位置まで移動し、その地点でG28.1を入力する。同様にG30.1でもうひとつの位置を登録できる。


G54
これは変えたことがない。G54〜G59までの6種類の異なる加工用の座標系を登録しておくことができるようです。その都度単体のものしか加工しないのであれば、おそらくずっとG54のままで大丈夫かと。大量生産や異なる部品を複数作業台に並べて加工するときは、それに合わせて複数の座標系があったほうが便利なので、そのうち使うかもしれないという程度。

G90
絶対座標系。これは位置調整するときに、よく使うかもしれません。G90X10Y20Z10などと入力。
絶対座標なので、X10という移動コマンドは座標上のX軸10mmの位置へ移動ということ。10mm右へ(X軸上に)動かすというのと混同するので要注意。
よく勘違いしていたのは、G90のままX10を入力して右へ10mm動いた後、再度X10を入力してさらに10mm右へ動かそうとしたのに動かないという現象。この場合は次の相対座標(インクリメンタル/増加分)の設定にしないといけない。
ちなみにG90のままX0Y0Z0と入力すれば、原点に移動ということになります。
G90を入力して、一旦絶対座標に切り替えると、変更しない限りこのモードは持続します。

G91
相対座標系(インクリメンタル/増加分)。G90と比べればわかると思いますが、G91を入力してX10を入力すれば、現在地から10mm右へ動きます。再度X10を入力すれば、さらに右へ10mm動きます。人によっても違うと思いますが、直感的に絶対座標の方がイメージしやすい場合と相対座標の方がイメージしやすい場合があると思います。10mm右へ移動した後、あと5mm右へ移動したいと思うか、それとも15mmの位置へ移動したいと思うかの違いです。
よくあるミスとしては、あと5mm右へ動かそうと思って(相対座標系でのイメージ)X5を入力したら、G91ではなくG90になっており、いきなり原点近く(座標上のX5mmの位置)まで戻ってしまったという現象。移動途中に障害物などなければいいのですが、材料固定用金具にぶつかってしまったり、場合によっては作業エリアをはみ出るまで動こうとしたりするのでかなり危険です。
なので、毎回G91X10と入力すれば、その勘違いも防げると思います。

G92
座標系の設定コマンド。現在地の座標を任意の座標値にセットするコマンド。
例えば現在地でG92X0Y0Z0を入力すると、そこがWPos(0,0,0)に変更されます。また、G92Z20と入力すれば、現在のZ軸の高さが20mmという設定になり、プローブなど使ってZ軸の位置を設定する際に使います。
大抵は、材料に対してのスピンドルの位置決め(加工原点出し)の時に使います。
ソフトによっては、「X=0」というような現在の位置を原点にしてくれるボタンもありますが、同じコマンドを使っているというわけです。

G94
毎分送りというフィードの単位設定。F800mm/minやF20inch/mmという〜/mm。これもこのまま。
このほかG95なら毎回転送り〜/revolutionらしい。G93だとInverse Timeモードと言ってF2.0なら1min/2.0つまりその工程を30秒で終わらせる速度となるみたいです。ここまでは覚えなくても良さそう。

M0
プログラム一時停止。起動させれば、残りの工程を再開。このほかM1も一時停止。

M3
スピンドル出力オン(時計回り)。Grblでスピンドルを制御している場合。ただしM3S1000などと、S値も入力しないといけない。M3S0ならスピンドルオンだけれど出力ゼロ。Grblの場合、S0で出力0%、S1000で100%。
ルーターやミリングでは、加工中にあまりオンオフはしないけれども(Z軸の昇降で切削するかしないかなので)、レーザーなら常に加工中はオンオフしたりS値で出力の強弱をつけたりします。ただし、Grbl1.1からはレーザモードが導入されたため、M3(従来の方法)ではなく、M4を使う方が便利かもしれません。

M4
スピンドル出力オン(反時計回り)。Grblでスピンドルを制御している場合。これもS値が必要。M4S1000などと入力。
Grbl1.1の場合、M4はレーザーモードとして機能が割り当てられています。M3でもレーザー可変出力加工は可能ですが(Grbl0.9の方法)、M4のレーザーモードにするとヘッドの移動速度に応じてレーザー出力を微調整してくれます。その結果、移動開始直後やパスの折り返し地点などの速度が変わる箇所の焦げムラが少なくなります(かなり効果あります)。ただし、M4を使うには事前に$32=1でレーザーモードをオンにしておく必要があります。

S
スピンドルの出力値。GrblならS0で0%、S500で50%、S1000で100%。Arduino Unoの11番ピン(PWM)を使っており8bitの設定になっているようでS0~1000とは言っても256段階しかないようです。cpu_map.h内でPWM周波数を変えることができます。デフォルトは0.98kHz。
ルーターやミリングならスピンドルモーターの可変制御を可能にしてくれるTTLモータードライバをCNCシールドのSpnEn(pwm)へ接続する必要があります。
レーザーならレーザー用TTLドライバを接続し、可変出力制御が可能になります。
オンオフしかできないリレーを使っている場合は、S0(オフ)かS1000(オン:Max値)に設定することになります。

M5
スピンドル停止。Grblでスピンドルを制御している場合。特にレーザーの場合はM5でオフにするかS0で出力ゼロにしないと危険。

M9
クーラント停止。冷却装置をつなげていなければ関係なし。

M30
プログラム終了。Gコードの最後につけることで、作業の終了ということを告げます。生成したGコードファイルをエディタなどで見てみると、たまにM30がない時もあるので、書き足しておくといいと思います。

T0, M6
ツールチェンジ。Tはツールの種類。M6で加工作業中で異なる刃に変えるときのコマンド。これも使わないかと。オートツールチェンジャがあって、自動で刃先を変えてくれるなら必要かもしれませんが、普通は加工中断して手で変えることになると思います。加工途中でのツール交換すらあまりしないし、したことはないです。

F
フィード。送り速度。G1(加工用の移動速度)で必要なフィードを設定します。単位は大抵はmm/minですが、たまにmm/secになっているソフトもあったりします。
個人的には、F600(10mm/sec)くらいがゆっくりめのちょうどいい速度という感じがします。
素材や加工方法に応じて、この速度を設定しなければいけません(詳しくはこちらなどで)。
X10Y20F600などと入力すれば、X軸10mm、Y軸20mmへ600mm/minで移動します。


このうち実際使うのは、$$、$X、M3、M4、S、G90、G91くらいでしょうか。あとはだいたいソフトのボタン操作になるので、それほど覚える必要はないと思います。ただ、ちょっとした動きを確認したり、トラブルがあった時に、コンソール画面のコマンドを多少読めた方がいいというくらいです。
当初はGコードも覚えないとダメなのかなと思っていましたが、ソフトでGコードを自動生成してくれるので、ほとんど覚えなくても作業上問題ないという感じです。
Gコードを入力するとエラーが出る場合は、このページにエラーコード表があるので見てみるといいと思います。設定や入力の仕方が間違っている場合があります。

実際の使用例(接続から加工までの流れ):
・CNCマシンと接続(ソフト上のOpenボタン押す)
・$Xで初期ロック状態解除(Unlockボタンでも可)
・$$で設定内容表示(一応確認)
・ジョグボタンで動作確認(少し動かしてみる)
・必要であれば、ここでホーミングサイクル($H)、機械原点MPos(0,0,0)のセット
・材料を作業エリアに固定する
・Gコードファイルの読み込み(ファイル読み込みボタンなど押す)
・ジョグボタンで材料左手前角に移動(XY軸のみ)
・ジョグボタンでZ軸(刃先)を材料上面ぴったりまで下げる(移動量0.01〜0.1mmボタン使用)
・この位置を加工原点WPos(0,0,0)にセットする(「X=0」ボタンなど押す、もしくはG92X0Y0Z0)
・Z軸を材料上面から少し上げておく(5mm程度の逃げ)
・加工スタート
・加工終了
という感じで、Gコードファイルを読み込ませて加工するときは、Gコード手入力はほとんど使いません。ボタンがソフト側にもついているので、それらのボタンで済んでしまいます。ホーミングサイクル($H)も普段はやりません。MPosではなくWPosで位置決めするという感じです。

このほかG41とG42のオフセット(工具径補正)機能もあるようなのですが、Grblには実装されていないようです。G41(左へオフセット)とG42(右へオフセット)のモードをキャンセルするG40は入っているみたいなので、将来的に実装されるのかもしれません。ただ、Arduino UNO(ATmega328)では、もうメモリに余裕がないので、Arduino MegaやArm CoreのArduino などになってしまうと思います。
ちなみにオフセット加工については、Jscutbcnc、あるいはIncscapeでも可能です。



さらに追記していく予定です。曖昧な部分もあるので、知っている方いらっしゃったらコメントお願いします。

人気の投稿