工業製造
産業用モノのインターネット | 工業材料 | 機器のメンテナンスと修理 | 産業プログラミング |
home  MfgRobots >> 工業製造 >  >> Manufacturing Technology >> 製造プロセス

ArduinoとPHPoC WiFiシールドで作るWeb操作XYプロッター(ステッピングモーター制御)

必要な部品と材料

部品 数量
Arduino UNO 1
PHPoC WiFi Shield 2 for Arduino 1
PHPoC Stepper Motor Controller 2
Makeblock XY Plotterキット 1

プロジェクト概要

このプロジェクトでは、WebブラウザからXYプロッターをリアルタイムで操作できるシステムを構築します。指でWebページ上の描画エリアをタッチすると、その座標がArduinoに送信され、2つのステッピングモーターがペンを該当位置に移動させます。移動中はArduinoからWebアプリへ軌跡データが連続送信され、キャンバス上に描画されます。

動作原理

Webページ上で指が描画エリアに触れると、タッチポイントのXY座標がArduinoへ送信されます。座標をスケーリングした後、Arduinoは2つのステッピングモーターを制御してペンをその座標へ移動させます。移動中、Arduinoはペンの軌跡を継続的にWebアプリへ送信し、Webアプリ側でキャンバス上に描画します。

ソースコード構成

ソースコードは「Arduino側コード」と「Webユーザーインターフェース(PHP)」の2部構成です。

Arduinoコード

無限ループ内で以下の処理を実行します:

Webユーザーインターフェース(remote_draw.php)

remote_draw.phpファイルをPHPoC WiFiシールドにアップロードする必要があります。手順:

  1. コードをコピーしてremote_draw.phpとして保存
  2. PHPoC Debuggerをインストール
  3. マイクロUSBケーブルでPHPoCをPHPoC WiFiシールドに接続(Arduinoへの給電必須)
  4. PHPoC Debuggerでremote_draw.phpをシールドへアップロード

セットアップ手順

  1. PHPoCシールドのネットワーク設定
  2. PHPoCライブラリのインストール
  3. PHPoC Expansionライブラリのインストール
  4. Arduinoへコードをコンパイル・アップロード
  5. WebユーザーインターフェースをPHPoC WiFiシールドへアップロード
  6. シリアルモニタを開き、PHPoCシールドのIPアドレスを確認
  7. Webブラウザで https://<シールドのIPアドレス>/remote_draw.php にアクセス
  8. Web上で描画開始

コード

Arduinoコード

#include <Phpoc.h>
#include <PhpocExpansion.h>
#include <Servo.h>

#define MAX_X 55550
#define MAX_Y 68780
#define TOUCH_OFFSET 5000
#define PEN_STATE_UP 0
#define PEN_STATE_DOWN 1
#define CMD_PEN_UP 0
#define CMD_PEN_DOWN 1
#define CMD_MOVE 2
#define STEP_MODE 32
#define SPEED_X_COEF ((long)40 * STEP_MODE)
#define SPEED_Y_COEF ((long)40 * STEP_MODE)
#define SPEED_X_MAX ((long)1500 * STEP_MODE)
#define SPEED_Y_MAX ((long)1500 * STEP_MODE)
#define ACCEL_X_MAX ((long)6000 * STEP_MODE)
#define ACCEL_Y_MAX ((long)6000 * STEP_MODE)
#define STEP_STATE_STOP 0
#define STEP_STATE_LOCK 1
#define RESOLUTION 500
#define MIN_UPDATE_INTERVAL 100

PhpocServer server(80);
ExpansionStepper stepX(14);
ExpansionStepper stepY(13);
Servo servo;

long preX = 0;
long preY = 0;
byte penState = PEN_STATE_UP;
bool isUnlockedX = false;
bool isUnlockedY = false;
int forwardDirX = -1;
int forwardDirY = +1;
unsigned long lastUpdateMillis;

void penUp() { servo.write(110); penState = PEN_STATE_UP; }
void penDown() { servo.write(180); penState = PEN_STATE_DOWN; }

void xyWait() { while(stepX.getState() > 1 || stepY.getState() > 1); }

void xyInit() {
  penUp();
  stepX.setMode(STEP_MODE);
  stepX.setVrefStop(4);
  stepX.setVrefDrive(15);
  stepX.setResonance(120, 250);
  stepX.setSpeed(20000);
  stepX.setAccel(50000);
  stepY.setMode(STEP_MODE);
  stepY.setVrefStop(4);
  stepY.setVrefDrive(15);
  stepY.setResonance(120, 250);
  stepY.setSpeed(20000);
  stepY.setAccel(50000);

  stepX.stepGotoSW(0, -forwardDirX);
  stepY.stepGotoSW(0, -forwardDirY);
  xyWait();
  stepX.setPosition(0);
  stepY.setPosition(0);

  xyGoto(TOUCH_OFFSET, TOUCH_OFFSET);
  xyWait();
  stepX.setEioMode(0, EIO_MODE_LOCK);
  stepX.setEioMode(1, EIO_MODE_LOCK);
  stepY.setEioMode(0, EIO_MODE_LOCK);
  stepY.setEioMode(1, EIO_MODE_LOCK);
}

void xyGoto(long x, long y) {
  if(x < TOUCH_OFFSET) x = TOUCH_OFFSET;
  else if(x > (MAX_X - TOUCH_OFFSET)) x = MAX_X - TOUCH_OFFSET;
  if(y < TOUCH_OFFSET) y = TOUCH_OFFSET;
  else if(y > (MAX_Y - TOUCH_OFFSET)) y = MAX_Y - TOUCH_OFFSET;

  x *= forwardDirX;
  y *= forwardDirY;

  int32_t deltaX = x - stepX.getPosition();
  int32_t deltaY = y - stepY.getPosition();
  deltaX = abs(deltaX);
  deltaY = abs(deltaY);

  long speedX = SPEED_X_COEF * deltaX;
  long speedY = SPEED_Y_COEF * deltaY;

  if(speedX > speedY) {
    if(speedX > SPEED_X_MAX) speedX = SPEED_X_MAX;
    double ratio = deltaY / (double)deltaX;
    speedY = (long)(ratio * speedX);
  } else {
    if(speedY > SPEED_Y_MAX) speedY = SPEED_Y_MAX;
    double ratio = deltaX / (double)deltaY;
    speedX = (long)(ratio * speedY);
  }

  long accelX, accelY;
  if(speedX < speedY) {
    accelY = ACCEL_Y_MAX;
    double ratio = accelY / (double)speedY;
    accelX = (long)(ratio * speedX);
  } else {
    accelX = ACCEL_X_MAX;
    double ratio = accelX / (double)speedX;
    accelY = (long)(ratio * speedY);
  }

  if(deltaX != 0) stepX.command(F("goto %ld %lu %lu"), x, speedX, accelX);
  if(deltaY != 0) stepY.command(F("goto %ld %lu %lu"), y, speedY, accelY);
}

void xyCheckUpdateToWeb() {
  bool isUpdate = false;
  unsigned long curMillis = millis();
  if((curMillis - lastUpdateMillis) > MIN_UPDATE_INTERVAL) isUpdate = true;

  long curX = stepX.getPosition();
  long curY = stepY.getPosition();
  long deltaX = curX - preX;
  long deltaY = curY - preY;
  long dist = sqrt(pow(deltaX, 2) + pow(deltaY, 2));
  if(dist > RESOLUTION) isUpdate = true;

  if(isUpdate == false || dist == 0) return;
  lastUpdateMillis = curMillis;
  preX = curX;
  preY = curY;
  sendPositionToWeb();
}

void sendPositionToWeb() {
  char wbuf[20];
  long x = stepX.getPosition() * forwardDirX;
  long y = stepY.getPosition() * forwardDirY;
  String data = String(F("[")) + x + String(F(",")) + y + String(F(",")) + penState + String(F("]\n"));
  data.toCharArray(wbuf, data.length() + 1);
  server.write(wbuf, data.length());
}

void setup() {
  Serial.begin(9600);
  while(!Serial);
  Phpoc.begin(PF_LOG_SPI | PF_LOG_NET);
  server.beginWebSocket("xy_plotter");
  Serial.print("WebSocket server address : ");
  Serial.println(Phpoc.localIP());
  Expansion.begin(460800);
  servo.attach(8);
  xyInit();
  lastUpdateMillis = millis();
}

void loop() {
  PhpocClient client = server.available();
  if(client) {
    String data = client.readLine();
    if(data) {
      byte separatorPos1 = data.indexOf(':');
      byte separatorPos2 = data.lastIndexOf(':');
      byte cmd = data.substring(0, separatorPos1).toInt();
      long x = data.substring(separatorPos1 + 1, separatorPos2).toInt();
      long y = data.substring(separatorPos2 + 1).toInt();

      switch(cmd) {
        case CMD_PEN_DOWN:
          xyGoto(x, y);
          while(stepX.getState() > 1 || stepY.getState() > 1) xyCheckUpdateToWeb();
          penDown();
          break;
        case CMD_PEN_UP:
          while(stepX.getState() > 1 || stepY.getState() > 1) xyCheckUpdateToWeb();
          penUp();
          break;
        case CMD_MOVE:
          xyGoto(x, y);
          break;
      }
    }
  }
  xyCheckUpdateToWeb();
}

Webユーザーインターフェース(PHP/HTML/JavaScript)

<!DOCTYPE html>
<html>
<head>
<title>Arduino - PHPoC Shield - XY Plotter</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7">
<style>
  body { text-align: center; background-color: #33C7F2; }
  #canvas { margin: 0 auto; position: relative; background-color: #FFFFFF; }
  canvas { position: absolute; left: 0; top: 0; }
  #layer1 { z-index: 2; }
  #layer2 { z-index: 1; }
  #layer3 { z-index: 0; }
</style>
<script>
  var PEN_STATE_UP = 0, PEN_STATE_DOWN = 1;
  var CMD_PEN_UP = 0, CMD_PEN_DOWN = 1, CMD_MOVE = 2;
  var RESOLUTION = 1000, MIN_UPDATE_INTERVAL = 100;
  var MAX_X = 55550, MAX_Y = 68780;
  var CANVAS_WIDTH = 0, CANVAS_HEIGHT = 0;
  var ws = null;
  var canvas = null, layer1 = null, layer2 = null, layer3 = null;
  var ctx1 = null, ctx2 = null, ctx3 = null;
  var touchState = 0, touchStepX = 0, touchStepY = 0;
  var touchPixelX = 0, touchPixelY = 0;
  var plotterX = 0, plotterY = 0, plotterPenState = PEN_STATE_UP;
  var lastUpdateMillis, buffer = "";

  function init() {
    canvas = document.getElementById("canvas");
    layer1 = document.getElementById("layer1");
    layer2 = document.getElementById("layer2");
    layer3 = document.getElementById("layer3");
    ctx1 = layer1.getContext("2d");
    ctx2 = layer2.getContext("2d");
    ctx3 = layer3.getContext("2d");
    layer1.addEventListener("touchstart", mouse_down);
    layer1.addEventListener("touchend", mouse_up);
    layer1.addEventListener("touchmove", mouse_move);
    layer1.addEventListener("mousedown", mouse_down);
    layer1.addEventListener("mouseup", mouse_up);
    layer1.addEventListener("mousemove", mouse_move);
    canvasResize();
    lastUpdateMillis = new Date().getTime();
  }

  function ws_onmessage(e_msg) {
    buffer += e_msg.data;
    var pos = buffer.indexOf('\n');
    if(pos == -1) return;
    var data = buffer.substring(0, pos);
    buffer = buffer.substring(pos + 1);
    var arr = JSON.parse(data);
    plotterX = arr[0]; plotterY = arr[1];
    var newPlotterPenState = arr[2];

    plotterX = Math.round(plotterX * CANVAS_WIDTH / MAX_X);
    plotterY = -Math.round(plotterY * CANVAS_HEIGHT / MAX_Y);

    ctx1.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
    ctx1.beginPath();
    ctx1.arc(plotterX, plotterY, 7, 0, 2*Math.PI);
    ctx1.fill();

    if(plotterPenState == PEN_STATE_UP && newPlotterPenState == PEN_STATE_DOWN)
      ctx2.beginPath();
    if(newPlotterPenState == PEN_STATE_DOWN) {
      ctx2.lineTo(plotterX, plotterY);
      ctx2.stroke();
    }
    plotterPenState = newPlotterPenState;
  }

  function ws_onopen() {
    document.getElementById("ws_state").innerHTML = "OPEN";
    document.getElementById("wc_conn").innerHTML = "Disconnect";
  }
  function ws_onclose() {
    document.getElementById("ws_state").innerHTML = "CLOSED";
    document.getElementById("wc_conn").innerHTML = "Connect";
    ws.onopen = ws.onclose = ws.onmessage = null;
    ws = null;
  }

  function wc_onclick() {
    if(ws == null) {
      ws = new WebSocket("ws:///xy_plotter", "text.phpoc");
      document.getElementById("ws_state").innerHTML = "CONNECTING";
      ws.onopen = ws_onopen;
      ws.onclose = ws_onclose;
      ws.onmessage = ws_onmessage;
    } else ws.close();
  }

  function event_handler(event, type) {
    if(event.targetTouches) {
      if(event.targetTouches.length > 1) return false;
      touchPixelX = event.targetTouches[0].pageX - canvas.offsetLeft;
      touchPixelY = event.targetTouches[0].pageY - canvas.offsetTop - CANVAS_HEIGHT;
    } else {
      touchPixelX = event.offsetX;
      touchPixelY = event.offsetY - CANVAS_HEIGHT;
    }
    var newTouchStepX = Math.round(touchPixelX / CANVAS_WIDTH * MAX_X);
    var newTouchStepY = Math.round((-touchPixelY) / CANVAS_HEIGHT * MAX_Y);

    if(type == "MOVE") {
      var isUpdate = false;
      var curMillis = new Date().getTime();
      if((curMillis - lastUpdateMillis) > MIN_UPDATE_INTERVAL) isUpdate = true;
      var deltaX = newTouchStepX - touchStepX;
      var deltaY = newTouchStepY - touchStepY;
      var dist = Math.sqrt(deltaX*deltaX + deltaY*deltaY);
      if(dist > RESOLUTION) isUpdate = true;
      if(isUpdate == false) return false;
      lastUpdateMillis = curMillis;
    }
    touchStepX = newTouchStepX;
    touchStepY = newTouchStepY;
    drawXYline();
    return true;
  }

  function mouse_down() { event.preventDefault(); event_handler(event, "DOWN"); sendToPlotter(CMD_PEN_DOWN, touchStepX, touchStepY); touchState = 1; }
  function mouse_up() { event.preventDefault(); touchState = 0; sendToPlotter(CMD_PEN_UP, touchStepX, touchStepY); }
  function mouse_move() { event.preventDefault(); if(touchState == 1) { if(event_handler(event, "MOVE")) sendToPlotter(CMD_MOVE, touchStepX, touchStepY); } }

  function sendToPlotter(cmd, x, y) {
    if(ws != null && ws.readyState == 1) ws.send(cmd + ":" + touchStepX + ":" + touchStepY + "\r\n");
  }

  function canvasClear() { ctx2.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); }

  function canvasResize() {
    var width = Math.round(window.innerWidth * 0.95);
    var height = Math.round(window.innerHeight * 0.95) - 100;
    var temp_height = Math.round(width * MAX_Y / MAX_X);
    if(temp_height <= height) { CANVAS_WIDTH = width; CANVAS_HEIGHT = temp_height; }
    else { CANVAS_WIDTH = height * MAX_X / MAX_Y; CANVAS_HEIGHT = height; }
    canvas.style.width = CANVAS_WIDTH + "px";
    canvas.style.height = CANVAS_HEIGHT + "px";
    layer1.width = layer2.width = layer3.width = CANVAS_WIDTH;
    layer1.height = layer2.height = layer3.height = CANVAS_HEIGHT;
    ctx1.translate(0, CANVAS_HEIGHT);
    ctx2.translate(0, CANVAS_HEIGHT);
    ctx3.translate(0, CANVAS_HEIGHT);
    ctx1.fillStyle = "#00979d";
    ctx2.lineWidth = 3; ctx2.strokeStyle = "black";
    ctx3.strokeStyle = "00FFFF";
  }

  function drawXYline() {
    ctx3.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
    ctx3.beginPath();
    ctx3.moveTo(0, touchPixelY); ctx3.lineTo(CANVAS_WIDTH, touchPixelY);
    ctx3.moveTo(touchPixelX, 0); ctx3.lineTo(touchPixelX, -CANVAS_HEIGHT);
    ctx3.stroke();
  }
  window.onload = init;
</script>
</head>
<body onresize="canvasResize()">
  <br>
  <div id="canvas">
    <canvas id="layer1"></canvas>
    <canvas id="layer2"></canvas>
    <canvas id="layer3"></canvas>
  </div>
  <p>WebSocket : <span id="ws_state">null</span></p>
  <button id="wc_conn" type="button" onclick="wc_onclick();">Connect</button>
  <button type="button" onclick="canvasClear();">Clear</button>
</body>
</html>

製造プロセス

  1. ArduinoだけでDTMFデコーダーを構築 – 外付けIC不要
  2. Web制御DMX照明システム:Arduino Megaマスターコントローラー
  3. Arduino Unoベースの人間検知レスキューロボット:センサー統合ステップバイステップ
  4. DIY Arduino USBゲームコントローラー - 高性能ゲームパッドを自作
  5. SERENA:Arduino Mega 2560とTFT LCDタッチスクリーン搭載カスタムアラームシステム
  6. Android加速度センサーで操作するラジコン車両:Raspberry Pi・Arduino・モーターシールド連携
  7. Arduino UNOで作る完全自律ライントラッカーロボット ─ 入門に最適な自作キット
  8. FirmataとXbox OneコントローラーでArduinoローバーをシームレス操作
  9. Arduino UNO + WiFiシールドで温度をWebリアルタイム表示 — シリアル出力だけでグラフ化
  10. ArduinoをWebパターンロックで保護 - 簡単セットアップとリモート制御
  11. Arduino Mega 2560搭載 総合トレーニングキット – センサー、モーター、カスタムPCB完備