Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Веб-управляемый XY-плоттер на Arduino с контроллером шаговых двигателей

Компоненты и материалы

Arduino UNO 1
PHPoC WiFi Shield 2 для Arduino 1
PHPoC Stepper Motor Controller 2
Makeblock XY Plotter 1

О проекте

Демонстрация

Если вы новичок в Arduino, начните с:

Принцип работы

Когда пользователь касается области рисования на веб-странице, координаты точки отправляются на Arduino. После масштабирования координат Arduino управляет двумя шаговыми двигателями для перемещения пера в заданную точку. Во время движения Arduino непрерывно передает траекторию пера в веб-приложение, которое отображает её на холсте.

Исходный код

Код состоит из двух частей: скетч для Arduino и веб-интерфейс.

Код Arduino

Скетч работает в бесконечном цикле и выполняет:

Веб-интерфейс пользователя

Файл remote_draw.php содержит веб-интерфейс. Его необходимо загрузить на PHPoC WiFi Shield. Для загрузки:

  1. Скопируйте код ниже и сохраните как remote_draw.php.
  2. Установите PHPoC Debugger.
  3. Подключите PHPoC к PHPoC WiFi Shield через micro-USB (Arduino должно быть запитано).
  4. Загрузите remote_draw.php на shield согласно инструкции.

Пошаговая инструкция

  1. Настройте сетевые параметры PHPoC shield или PHPoC WiFi shield.
  2. Установите библиотеку PHPoC.
  3. Установите библиотеку PHPoC Expansion.
  4. Скомпилируйте и загрузите код на Arduino.
  5. Загрузите веб-интерфейс на PHPoC WiFi shield.
  6. Откройте Serial Monitor и скопируйте IP-адрес PHPoC Shield.
  7. Откройте веб-интерфейс в браузере: https://ip_address_of_shield/remote_draw.php.
  8. Рисуйте через веб!

Электронные компоненты можно заказать на utsource.net.

Справочник функций

Код

Скетч 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, preY = 0;
byte penState = PEN_STATE_UP;
bool isUnlockedX = false, 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 || 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 sep1 = data.indexOf(':');
      byte sep2 = data.lastIndexOf(':');
      byte cmd = data.substring(0, sep1).toInt();
      long x = data.substring(sep1 + 1, sep2).toInt();
      long y = data.substring(sep2 + 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();
}

Веб-интерфейс (PHP/JS)

<!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: 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, 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 = "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 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(deltaX*deltaX + deltaY*deltaY);
        if(dist > RESOLUTION) isUpdate = true;
        if(!isUpdate) 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 && 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. Создание DTMF-декодера на Arduino без внешних микросхем
  2. Веб-управляемая DMX-система освещения: Arduino Mega как мастер-контроллер
  3. Робот для поиска людей на базе Arduino Uno: пошаговая интеграция датчиков
  4. DIY USB-геймпад на Arduino: собственный высокопроизводительный контроллер
  5. SERENA: Пользовательская система сигнализации на Arduino Mega 2560 с TFT Touchscreen
  6. Android-управляемая робот-платформа на Raspberry Pi и Motor Shield
  7. Автономный линий-следующий робот на Arduino UNO
  8. Управление Arduino-роботом через Firmata и геймпад Xbox One
  9. Arduino UNO + WiFi Shield: реальный график температуры в вебе через Serial
  10. Защитите свой Arduino с помощью веб-разблокировки по шаблону — простое управление и удалённый контроль