Pengontrol Plotter XY Berbasis Web dengan Arduino dan Pengontrol Motor Stepper
Komponen dan Perlengkapan
 |
|
× |
1 |
|
 |
| PHPoC WiFi Shield 2 for Arduino |
|
× |
1 |
|
 |
| PHPoC Stepper Motor Controller |
|
× |
2 |
|
|
|
× |
1 |
|
Tentang Proyek Ini
Demonstrasi
Jika Anda baru menggunakan Arduino, Anda dapat memulai dengan:
- Tutorial Arduino untuk pemula.
- Semua tentang motor untuk Arduino
Cara Kerja
Saat jari menyentuh area gambar pada halaman web, koordinat XY dari titik sentuhan dikirim ke Arduino. Setelah menyesuaikan koordinat, Arduino akan memindahkan dua motor stepper untuk menempatkan pena ke koordinat tersebut. Selama proses pergerakan, Arduino terus mengirim lintasan pena ke aplikasi web, dan aplikasi web menggambar lintasan tersebut pada kanvas.
Kode Sumber
Kode sumber terdiri dari dua bagian: kode Arduino dan kode Antarmuka Pengguna Web.
Kode Arduino
Ini adalah kode Arduino yang berjalan dalam loop tak terbatas untuk:
- Menerima perintah dari halaman web dan menjalankan tugas sesuai perintah.
- CMD_MOVE: memindahkan pena ke posisi dengan mengontrol dua motor stepper
- CMD_PEN_UP: mengangkat pena dengan mengubah sudut motor servo
- CMD_PEN_DOWN: menurunkan pena dengan mengubah sudut motor servo
- Terus membaca posisi pena saat ini dan mengirimkannya ke halaman web
Antarmuka Pengguna Web
Lihat kode di bagian Kode
- Menyediakan antarmuka pengguna
- Menangani peristiwa pengguna dan mengirim perintah dengan koordinat ke Arduino
- Menerima lintasan dari Arduino dan menggambarnya pada halaman web
remote_draw.php adalah file yang berisi Antarmuka Pengguna Web. File ini perlu disimpan pada PHPoC [WiFi] Shield. Untuk mengunggah file ke PHPoC [WiFi] Shield, silakan lakukan langkah berikut:
- Salin kode di bawah ini dan simpan ke file
remote_draw.php.
- Instal PHPoC Debugger
- Hubungkan PHPoC ke PHPoC [WiFi] Shield melalui kabel micro-USB sesuai petunjuk ini. Perhatikan bahwa Arduino harus mendapatkan daya.
- Unggah file
remote_draw.php ke PHPoC [WiFi] Shield sesuai petunjuk ini
Cara Melakukan
- Konfigurasikan informasi jaringan untuk PHPoC shield atau PHPoC WiFi shield
- Instal PHPoC Library
- Instal PHPoC Expansion Library
- Kompilasi dan unggah kode ke Arduino
- Unggah antarmuka pengguna web ke PHPoC [WiFi] shield
- Buka Serial Monitor dan salin alamat IP PHPoC Shield
- Akses Antarmuka Pengguna Web melalui Web Browser: https://ip_address_of_shield/remote_draw.php
- Gambar melalui Web
Untuk membeli komponen elektronik, Anda dapat memesannya dari utsource.net
The Best Arduino Starter Kit for Beginner
Lihat The Best Arduino Kit for Beginners
Referensi Fungsi
- Serial.begin()
- Serial.println()
- delay()
- millis()
- for loop
- while loop
- if else
- loop()
- setup()
- String.toInt()
- String.substring()
- String.indexOf()
- String.remove()
- String.equals()
Kode
- Kode Arduino
- Antarmuka Pengguna Web
Kode ArduinoArduino
#include <Phpoc.h> #include <PhpocExpansion.h> #include <Servo.h> #define MAX_X 55550 // unit is step #define MAX_Y 68780 // unit is step #define TOUCH_OFFSET 5000 // unit is step #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 millisecond 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; /* direction of XY plotter when motor X move forward, depending on installization, It should be tested to determine the values*/ int forwardDirY = +1; /* direction of XY plotter when motor Y move up, depending on installization, It should be tested to determine the values*/ 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); // move pen to (0, 0) stepX.stepGotoSW(0, -forwardDirX); stepY.stepGotoSW(0, -forwardDirY); xyWait(); stepX.setPosition(0); stepY.setPosition(0); // uncomment this block for the first run and change the value in line 6, 7 of this file and line 33 of index.php according to value in IDE console /* // check max steps stepX.stepGotoSW(1, forwardDirX); stepY.stepGotoSW(1, forwardDirY); xyWait(); // change these value in line 45 of index.php 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; long 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(); // send current postion to display on web } 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); /** NOTE: For the first run: * - uncomment the last block in xyInit() * - run Arduino code * - change the value in line 6, 7 of this file and line 33 of index.php according to value in IDE console. **/ xyInit(); lastUpdateMillis = millis(); } void loop() { // wait for a new client: PhpocClient client = server.available(); if(client) { String data = client.readLine(); if(data) { //Serial.println(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); //xyWait(); while(stepX.getState() > 1 || stepY.getState() > 1) xyCheckUpdateToWeb(); penDown(); break; case CMD_PEN_UP: //xyWait(); while(stepX.getState() > 1 || stepY.getState() > 1) xyCheckUpdateToWeb(); penUp(); break; case CMD_MOVE: xyGoto(x, y); break; } } } xyCheckUpdateToWeb(); }
Antarmuka Pengguna WebPHP
<!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; /* nice webkit native scroll */ } #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 millisecond 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; /* unit is step */ var touchPixelX = 0, touchPixelY = 0; /* unit is 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"); /* for drawing current postion, which is received from XY plotter */ layer2 = document.getElementById("layer2"); /* for drawing trajatory of XY plotter, which is received from XY plotter*/ layer3 = document.getElementById("layer3"); /* for drawing the current touch postion, which is inputed by user on screen */ 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]; /* convert step unit to pixel unit */ plotterX = Math.round(plotterX * CANVAS_WIDTH / MAX_X); plotterY = -Math.round(plotterY * CANVAS_HEIGHT / MAX_Y); /* draw current postion of plotter*/ ctx1.clearRect(0, 0, CANVAS_WIDTH, -CANVAS_HEIGHT); ctx1.beginPath(); ctx1.arc(plotterX, plotterY, 7, 0, 2*Math.PI); ctx1.fill(); /* draw trajatory of plotter only when pen down */ 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) { // convert coordinate 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; } /* convert from pixel to step */ var newTouchStepX = Math.round(touchPixelX / CANVAS_WIDTH * MAX_X); var newTouchStepY = Math.round((-touchPixelY) / CANVAS_HEIGHT * MAX_Y); if(type == "MOVE") { /* check update condition to avoid sending too much data to plotter */ 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); /* only clear trajatory */ } 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>