Industrielle Fertigung
Industrielles Internet der Dinge | Industrielle Materialien | Gerätewartung und Reparatur | Industrielle Programmierung |
home  MfgRobots >> Industrielle Fertigung >  >> Manufacturing Technology >> Herstellungsprozess

Arduino Web-gesteuerter XY-Plotter mit Schrittmotor-Controller

Komponenten und Materialien

Arduino UNO × 1
PHPoC WiFi Shield 2 für Arduino × 1
PHPoC Schrittmotor-Controller × 2
Makeblock XY-Plotter × 1

Über dieses Projekt

Demonstration

Wenn Sie neu bei Arduino sind, können Sie mit folgenden Ressourcen starten:

Funktionsweise

Wenn ein Finger den Zeichenbereich auf der Webseite berührt, werden die XY-Koordinaten des Berührungspunkts an den Arduino gesendet. Nach der Skalierung der Koordinaten bewegt der Arduino zwei Schrittmotoren, um den Stift an diese Position zu bringen. Während der Bewegung sendet der Arduino kontinuierlich die Stift-Trajektorie an die Web-App, die diese auf dem Canvas darstellt.

Quellcode

Der Quellcode besteht aus zwei Teilen: Arduino-Code und Web-Benutzeroberflächen-Code.

Arduino-Code

Dieser Arduino-Code läuft in einer Endlosschleife und:

Web-Benutzeroberfläche

Der Code befindet sich im Code-Bereich.

remote_draw.php ist eine Datei, die die Web-Benutzeroberfläche enthält. Sie muss auf dem PHPoC [WiFi] Shield gespeichert werden. Zum Hochladen der Datei auf das PHPoC [WiFi] Shield führen Sie folgende Schritte aus:

Vorgehensweise

Um elektronische Komponenten zu kaufen, können Sie diese bei utsource.net bestellen.

Die besten Arduino-Starterkits für Einsteiger

Siehe die besten Arduino-Kits für Anfänger

Funktionsreferenzen

Code

Arduino-Code

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

#define MAX_X 55550 // Einheit: Schritte
#define MAX_Y 68780 // Einheit: Schritte
#define TOUCH_OFFSET 5000 // Einheit: Schritte
#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 // in Millisekunden

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; // Richtung des XY-Plotters bei Vorwärtsbewegung Motor X, abhängig von der Installation
int forwardDirY = +1; // Richtung des XY-Plotters bei Aufwärtsbewegung Motor Y, abhängig von der Installation
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);

  // Stift zur Position (0, 0) bewegen
  stepX.stepGotoSW(0, -forwardDirX);
  stepY.stepGotoSW(0, -forwardDirY);
  xyWait();
  stepX.setPosition(0);
  stepY.setPosition(0);

  // Für den ersten Lauf: Block auskommentieren und Werte in Zeile 6, 7 dieser Datei und Zeile 33 von index.php gemäß IDE-Konsole anpassen
  /*
  // Maximale Schritte prüfen
  stepX.stepGotoSW(1, forwardDirX);
  stepY.stepGotoSW(1, forwardDirY);
  xyWait();
  // Diese Werte in Zeile 45 von index.php ändern
  Serial.print(F("MAX_X:"));
  Serial.println(stepX.getPosition() * forwardDirX);
  Serial.print(F("MAX_Y:"));
  Serial.println(stepY.getPosition() * forwardDirY);
  */

  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 * abs(deltaX);
  long speedY = SPEED_Y_COEF * abs(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 false;

  lastUpdateMillis = curMillis;
  preX = curX;
  preY = curY;
  sendPositionToWeb(); // Aktuelle Position an Web senden
}

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-Adresse: ");
  Serial.println(Phpoc.localIP());
  Expansion.begin(460800);
  servo.attach(8);

  /** HINWEIS: Für den ersten Lauf:
   * - Letzten Block in xyInit() auskommentieren
   * - Arduino-Code ausführen
   * - Werte in Zeile 6, 7 dieser Datei und Zeile 33 von index.php gemäß IDE-Konsole anpassen.
   **/
  xyInit();
  lastUpdateMillis = millis();
}

void loop() {
  // Auf neuen Client warten:
  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-Benutzeroberfläche (PHP)

<!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-right: auto; margin-left: auto; position: relative; background-color: #FFFFFF; }
canvas { position: absolute; left: 0px; top: 0px; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; }
#layer1 { z-index: 2; }
#layer2 { z-index: 1; }
#layer3 { z-index: 0; }
</style>
<script>
var PEN_STATE_UP = 0;
var PEN_STATE_DOWN = 1;
var CMD_PEN_UP = 0;
var CMD_PEN_DOWN = 1;
var CMD_MOVE = 2;
var RESOLUTION = 1000;
var MIN_UPDATE_INTERVAL = 100; // in Millisekunden
var MAX_X = 55550, MAX_Y = 68780;
var CANVAS_WIDTH = 0, CANVAS_HEIGHT = 0;
var ws = null;
var canvas = null;
var layer1 = null, layer2 = null, layer3 = null;
var ctx1 = null, ctx2 = null, ctx3 = null;
var touchState = 0;
var touchStepX = 0, touchStepY = 0; // Einheit: Schritte
var touchPixelX = 0, touchPixelY = 0; // Einheit: Pixel
var plotterX = 0, plotterY = 0;
var plotterPenState = PEN_STATE_UP;
var lastUpdateMillis;
var buffer = "";

function init() {
  canvas = document.getElementById("canvas");
  layer1 = document.getElementById("layer1"); // Für aktuelle Position vom XY-Plotter
  layer2 = document.getElementById("layer2"); // Für Trajektorie des XY-Plotters
  layer3 = document.getElementById("layer3"); // Für aktuelle Touch-Position des Benutzers
  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();
  var d = new Date();
  lastUpdateMillis = d.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];

  // Schritt-Einheit in Pixel-Einheit umrechnen
  plotterX = Math.round(plotterX * CANVAS_WIDTH / MAX_X);
  plotterY = -Math.round(plotterY * CANVAS_HEIGHT / MAX_Y);

  // Aktuelle Position des Plotters zeichnen
  ctx1.clearRect(0, 0, CANVAS_WIDTH, -CANVAS_HEIGHT);
  ctx1.beginPath();
  ctx1.arc(plotterX, plotterY, 7, 0, 2*Math.PI);
  ctx1.fill();

  // Trajektorie des Plotters nur bei gesenktem Stift zeichnen
  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 = null;
  ws.onclose = null;
  ws.onmessage = null;
  ws = null;
}

function wc_onclick() {
  if(ws == null) {
    ws = new WebSocket("ws://<?echo _SERVER("HTTP_HOST")?>/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) {
  // Koordinaten umrechnen
  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;
  }

  // Von Pixel in Schritte umrechnen
  var newTouchStepX = Math.round(touchPixelX / CANVAS_WIDTH * MAX_X);
  var newTouchStepY = Math.round((-touchPixelY) / CANVAS_HEIGHT * MAX_Y);

  if(type == "MOVE") {
    // Aktualisierungsbedingung prüfen, um zu vermeiden, dass zu viele Daten an den Plotter gesendet werden
    var isUpdate = false;
    var d = new Date();
    var curMillis = d.getTime();
    if((curMillis - lastUpdateMillis) > MIN_UPDATE_INTERVAL) isUpdate = true;
    var deltaX = newTouchStepX - touchStepX;
    var deltaY = newTouchStepY - touchStepY;
    var dist = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
    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); // Nur Trajektorie löschen
}

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 = CANVAS_WIDTH;
  layer1.height = CANVAS_HEIGHT;
  layer2.width = CANVAS_WIDTH;
  layer2.height = CANVAS_HEIGHT;
  layer3.width = CANVAS_WIDTH;
  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>

Herstellungsprozess

  1. DTMF-Decoder mit Arduino bauen – ganz ohne zusätzlichen IC
  2. Web-gesteuertes DMX-Lichtsystem – Arduino Master Controller
  3. Arduino-Uno-Roboter zur Personenerkennung – Sensorintegration Schritt für Schritt
  4. DIY Arduino USB Gaming Controller – Bauen Sie Ihren eigenen High-Performance Gamepad
  5. SERENA: Eigenes Arduino Mega 2560 Alarmsystem mit TFT-LCD-Touchscreen
  6. Android-gesteuertes Roboterfahrzeug mit Raspberry Pi Motor Shield
  7. Autonomer Linienfolger-Roboter mit Arduino UNO
  8. Arduino Rover per Firmata & Xbox One Gamepad steuern
  9. Arduino UNO & WiFi Shield: Echtzeit-Temperaturanzeige im Web via Serial
  10. Arduino per Web-Pattern-Entsperrung sichern – Einfache Einrichtung & Fernsteuerung