Manufactura industrial
Internet industrial de las cosas | Materiales industriales | Mantenimiento y reparación de equipos | Programación industrial |
home  MfgRobots >> Manufactura industrial >  >> Manufacturing Technology >> Proceso de manufactura

Plotter XY Controlado por Web con Arduino y Motores Paso a Paso

Componentes y Suministros

Componente Cantidad
Arduino UNO 1
PHPoC WiFi Shield 2 para Arduino 1
Controlador de Motor Paso a Paso PHPoC 2
Plotter XY Makeblock 1

Descripción del Proyecto

Este proyecto implementa un plotter XY controlado mediante una interfaz web. Al tocar el área de dibujo en la página web, las coordenadas XY se envían al Arduino, que mueve dos motores paso a paso para posicionar el bolígrafo. Durante el movimiento, el Arduino envía continuamente la trayectoria al aplicativo web, que la dibuja en un canvas HTML5.

Funcionamiento

El sistema consta de dos partes principales: el código Arduino y la interfaz de usuario web.

Código Arduino

El sketch de Arduino se ejecuta en un bucle infinito para:

Interfaz de Usuario Web

El archivo remote_draw.php contiene la interfaz de usuario web y debe almacenarse en el PHPoC WiFi Shield. Para subirlo:

  1. Copie el código y guárdelo como remote_draw.php.
  2. Instale PHPoC Debugger.
  3. Conecte el PHPoC al PHPoC WiFi Shield mediante cable micro-USB (el Arduino debe estar alimentado).
  4. Suba el archivo remote_draw.php al shield siguiendo las instrucciones.

Pasos para Poner en Marcha

  1. Configure la información de red para el shield PHPoC.
  2. Instale la librería PHPoC y la librería PHPoC Expansion.
  3. Compile y suba el código al Arduino.
  4. Suba la interfaz web al PHPoC WiFi Shield.
  5. Abra el Monitor Serie y copie la dirección IP del shield.
  6. Acceda a la interfaz web: https://direccion_ip_del_shield/remote_draw.php.
  7. ¡Dibuje desde la web!

Referencias de Funciones

Código

Código Arduino

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

#define MAX_X 55550 // unidad: pasos
#define MAX_Y 68780 // unidad: pasos
#define TOUCH_OFFSET 5000 // unidad: pasos
#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 // en milisegundos

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; // dirección del plotter XY cuando el motor X avanza
int forwardDirY = +1; // dirección del plotter XY cuando el motor Y sube
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);

  // Mover bolígrafo a (0, 0)
  stepX.stepGotoSW(0, -forwardDirX);
  stepY.stepGotoSW(0, -forwardDirY);
  xyWait();
  stepX.setPosition(0);
  stepY.setPosition(0);

  // Descomente este bloque para la primera ejecución y ajuste los valores
  // según la consola del IDE
  /*
  stepX.stepGotoSW(1, forwardDirX);
  stepY.stepGotoSW(1, forwardDirY);
  xyWait();
  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 * 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("Dirección del servidor WebSocket: ");
  Serial.println(Phpoc.localIP());
  Expansion.begin(460800);
  servo.attach(8);

  // NOTA: Para la primera ejecución:
  // - Descomente el último bloque en xyInit()
  // - Ejecute el código Arduino
  // - Cambie los valores en las líneas 6, 7 de este archivo y línea 33 de index.php
  //   según los valores mostrados en la consola del IDE.
  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();
}

Interfaz de Usuario Web (PHP)

<!DOCTYPE html>
<html>
<head>
  <title>Arduino - PHPoC Shield - Plotter XY</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; overflow-y: auto; overflow-x: hidden; }
    #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, canvas = null;
    var 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();
      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];
      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 = "Desconectar";
    }

    function ws_onclose() {
      document.getElementById("ws_state").innerHTML = "CLOSED";
      document.getElementById("wc_conn").innerHTML = "Conectar";
      ws.onopen = ws.onclose = 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) {
      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 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); }
    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();">Conectar</button>
  <button type="button" onclick="canvasClear();">Limpiar</button>
</body>
</html>

Proceso de manufactura

  1. Construye un Decodificador DTMF Solo con Arduino – Sin CI Externos
  2. Sistema de Iluminación DMX Controlado por Web – Controlador Maestro Arduino
  3. Robot de Detección Humana con Arduino Uno: Integración de Sensores Paso a Paso
  4. Mando USB para juegos DIY con Arduino – Construye tu propio gamepad de alto rendimiento
  5. SERENA: Sistema de Alarma Personalizado Arduino Mega 2560 con Pantalla TFT LCD Táctil
  6. Vehículo Controlado por Android con Raspberry Pi, Arduino y Motor Shield: Guía Completa
  7. Robot Seguidor de Línea Autónomo con Arduino UNO: Guía Paso a Paso
  8. Controla un Rover Arduino con Firmata y Mando Xbox One - Guía Completa
  9. Arduino UNO + Shield WiFi: Visualización de Temperatura en Tiempo Real en Web vía Serial
  10. Protege tu Arduino con desbloqueo por patrón web: configuración fácil y control remoto
  11. Kit de Entrenamiento Arduino Completo con Mega 2560, Sensores, Motores y PCB Personalizada