Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
phpoc_man
Published © GPL3+

Drawing via Web Using Micro-Step Motor Controller and PHPoC

Whatever you draw on a webpage will be replicated on the drawing paper.

IntermediateShowcase (no instructions)4,625
Drawing via Web Using Micro-Step Motor Controller and PHPoC

Things used in this project

Hardware components

PHPoC Blue
PHPoC Blue
×1
Makeblock XY plotter
This include all mechanical parts, two step motors (control position of pen), 1 servo motor (control pen up/down) and four touch sensors (detect the top, bottom, left and right border reach)
×1
PHPoC Stepper Motor Controller Ⅱ (S-type or T-type)
PHPoC Stepper Motor Controller Ⅱ (S-type or T-type)
×2
Limit Switch
DIYables Limit Switch
×4
DC Power Jack Connector
DIYables DC Power Jack Connector
×1
12V power adapter
×1

Story

Read more

Schematics

schematic

Code

User Interface (index.php)

PHP
<!DOCTYPE html>
<html>
<head>
<title>PHPoC - XY Plotter</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7">
<style>
body {
	text-align: center;
	background-color: #33C7F2;
}
#cvs_frame {
	margin-right: auto;
	margin-left: auto; 
	position: relative;
}
.canvas {
	position: absolute; 
	left: 0px;
	top: 0px;
	overflow-y: auto;
	overflow-x: hidden;
	-webkit-overflow-scrolling: touch; /* nice webkit native scroll */
}

#layer_1 {
	z-index: 2;
}
#layer_2 {
	z-index: 1;
}
#layer_3 {
	z-index: 0;
	background-color: #FFFFFF;
}

</style>
<script>
var PEN_UP = 0;
var PEN_DOWN = 1;
var CMD_PEN_UP = 0;
var CMD_PEN_DOWN = 1;
var CMD_MOVE = 2;
var RESOLUTION = 20;

var MAX_X = 1710, MAX_Y = 2140;

var ws = null;
var layer_1 = null, layer_2 = null, layer_3 = null;
var cvs_frame = null;
var ctx1 = null, ctx2 = null, ctx3 = null;

var canvas_width = 0, canvas_height = 0;
var x = 0, y = 0;
var touch_x = 0; touch_y = 0;
var cvs_pos_x = 0, cvs_pos_y = 0;
var pre_cvs_pos_x = 0, pre_cvs_pos_y = 0;
var pre_pen_state = PEN_UP;

function init()
{
	cvs_frame = document.getElementById("cvs_frame");
	
	layer_1 = document.getElementById("layer_1");
	layer_2 = document.getElementById("layer_2");
	layer_3 = document.getElementById("layer_3");
	
	var body = document.getElementsByTagName("BODY")[0];
	body.addEventListener("touchstart", disable_zoom);
	body.addEventListener("touchend", disable_zoom);
	body.addEventListener("touchmove", disable_zoom);
	body.addEventListener("mousedown", disable_zoom);
	body.addEventListener("mouseup", disable_zoom);
	
	layer_1.addEventListener("touchstart", mouse_down);
	layer_1.addEventListener("touchend", mouse_up);
	layer_1.addEventListener("touchmove", mouse_move);
	layer_1.addEventListener("mousedown", mouse_down);
	layer_1.addEventListener("mouseup", mouse_up);
	layer_1.addEventListener("mousemove", mouse_move);
	
	ctx1 = layer_1.getContext("2d");
	ctx1.translate(0, canvas_height);
	
	ctx2 = layer_2.getContext("2d");
	ctx2.translate(0, canvas_height);
	
	ctx3 = layer_3.getContext("2d");
	ctx3.translate(0, canvas_height);
	
	canvas_resize();
}
function canvas_clear()
{
	ctx1.clearRect(0, 0, canvas_width, -canvas_height);
	ctx2.clearRect(0, 0, canvas_width, -canvas_height);
	ctx3.clearRect(0, 0, canvas_width, -canvas_height);
}
function event_handler(event, type)
{
	var pre_x = x, pre_y = y;
	// convert coordinate
	if(event.targetTouches)
	{
		var targetTouches = event.targetTouches;
		
		touch_x = targetTouches[0].pageX - cvs_frame.offsetLeft;
		touch_y = targetTouches[0].pageY - cvs_frame.offsetTop - canvas_height;
	}
	else
	{
		touch_x = event.offsetX;
		touch_y = event.offsetY - canvas_height;
	}
	
	var temp_x = Math.round(touch_x / canvas_width * MAX_X);
	var temp_y = Math.round((-touch_y) / canvas_height * MAX_Y); 
	
	if(type == "MOVE")
	{
		var delta_x = temp_x - pre_x;
		var delta_y = temp_y - pre_y;
		var dist = Math.sqrt( Math.pow(delta_x, 2) + Math.pow(delta_y, 2) );
		
		if(dist < RESOLUTION)
			return false;
	}
	
	x = temp_x;
	y = temp_y;
	
	return true;
}
function ws_onmessage(e_msg)
{
	var arr = JSON.parse(e_msg.data);
	var pos_x			= arr[0];
	var pos_y			= arr[1];
	var cur_pen_state	= arr[2];
	
	pre_cvs_pos_x = cvs_pos_x;
	pre_cvs_pos_y = cvs_pos_y;
	
	cvs_pos_x = Math.round(pos_x * canvas_width / MAX_X);
	cvs_pos_y = -Math.round(pos_y * canvas_height / MAX_Y);
	
	ctx1.clearRect(0, 0, canvas_width, -canvas_height);
	ctx1.beginPath();
	ctx1.arc(cvs_pos_x, cvs_pos_y, 7, 0, 2*Math.PI);
	ctx1.fill();
	
	if(pre_pen_state == PEN_UP && cur_pen_state == PEN_DOWN)
		ctx2.beginPath();
	
	if(cur_pen_state == PEN_DOWN)
	{
		ctx2.lineTo(cvs_pos_x, cvs_pos_y);
		ctx2.stroke();
	}
	
	pre_pen_state = cur_pen_state;
}
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", "csv.phpoc");
		document.getElementById("ws_state").innerHTML = "CONNECTING";

		ws.onopen = ws_onopen;
		ws.onclose = ws_onclose;
		ws.onmessage = ws_onmessage;  
	}
	else
		ws.close();
}
function mouse_down()
{
	if(event.targetTouches)
	{
		event.preventDefault();
		if(event.targetTouches.length > 1)
			return;
	}
	
	event_handler(event, "DOWN");
	
	if(ws == null || ws.readyState != 1)
		return;

	ws.send(CMD_PEN_DOWN + " " + x + " " + y + "\r\n"); 
	
	draw_xy_line();
}
function mouse_up()
{
	if(event.targetTouches)
	{
		event.preventDefault();
	}
	
	if(ws == null || ws.readyState != 1)
		return;
			
	ws.send(CMD_PEN_UP + "\r\n"); 
}
function mouse_move()
{
	if(event.targetTouches)
	{
		event.preventDefault();
		if(event.targetTouches.length > 1)
			return;
	}
	
	if(!event_handler(event, "MOVE"))
		return;
	
	if(ws == null || ws.readyState != 1)
		return;
	
	ws.send(CMD_MOVE + " " + x + " " + y + "\r\n"); 
	
	draw_xy_line();
}
function disable_zoom()
{
	//event.preventDefault();
}
function canvas_resize()
{
	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;
	}
	
	cvs_frame.style.width = canvas_width + "px";
	cvs_frame.style.height = canvas_height + "px";
	
	layer_1.width = canvas_width;
	layer_1.height = canvas_height;
	ctx1.translate(0, canvas_height);
	ctx1.lineWidth = 6;
	ctx1.fillStyle = "green";
	
	layer_2.width = canvas_width;
	layer_2.height = canvas_height;
	ctx2.translate(0, canvas_height);
	ctx2.lineWidth = 3;
	ctx2.strokeStyle = "black";
	
	layer_3.width = canvas_width;
	layer_3.height = canvas_height;
	ctx3.translate(0, canvas_height);
	ctx3.strokeStyle = "00FFFF";
}
function draw_xy_line()
{
	ctx3.clearRect(0, 0, canvas_width, -canvas_height);
	
	ctx3.beginPath();
	ctx3.moveTo(0, touch_y);
	ctx3.lineTo(canvas_width, touch_y);
	ctx3.stroke();
	
	ctx3.beginPath();
	ctx3.moveTo(touch_x, 0);
	ctx3.lineTo(touch_x, -canvas_height);
	ctx3.stroke();
}
window.onload = init;
</script>
</head>

<body onresize="canvas_resize()">
<br>
<div id="cvs_frame">
	<canvas id="layer_1" class="canvas"></canvas>
	<canvas id="layer_2" class="canvas"></canvas>
	<canvas id="layer_3" class="canvas"></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="canvas_clear();">Clear</button>
</body>
</html>

Main task (task0.php)

PHP
<?php

include_once "/lib/sd_340.php";
include_once "/lib/sd_spc.php";
include_once "/lib/sn_tcp_ws.php";

define("PWM_PERIOD", 20000); // (20ms)
define("WIDTH_MIN", 600);
define("WIDTH_MAX", 2450);

define("SID_X",			13);
define("SID_Y",			14);

define("MAX_X", 		1710);
define("MAX_Y", 		2140);
define("TOUCH_OFFSET",	100);

define("PEN_UP",		0);
define("PEN_DOWN",		1);

define("CMD_PEN_UP",	0);
define("CMD_PEN_DOWN",	1);
define("CMD_MOVE",		2);

define("SPEED_X_COEF",		20);
define("SPEED_Y_COEF",		20);
define("SPEED_X_OFFSET",	50);
define("SPEED_Y_OFFSET",	50);
define("SPEED_X_MAX",		5000);
define("SPEED_Y_MAX",		5000);

define("ACCEL_X_COEF",		1500);
define("ACCEL_Y_COEF",		1000);
define("ACCEL_X_OFFSET",	0);
define("ACCEL_Y_OFFSET",	0);
define("ACCEL_X_MAX",		20000);
define("ACCEL_Y_MAX",		20000);

define("STATE_X_LOCK",	1);
define("STATE_X_RUN",	2);
define("STATE_Y_LOCK",	4);
define("STATE_Y_RUN",	8);


function step_cmd($sid, $cmd)
{
	$resp = spc_request($sid, 4, $cmd);

	if($resp)
	{
		$resp = explode(",", $resp);

		if((int)$resp[0] != 200)
		{
			echo "step_cmd : '$cmd' request error ", $resp[0], "\r\n";
			return false;
		}

		if(count($resp) > 1)
			return $resp[1];
		else
			return "";
	}
	else
		return false;
}

function spc_check_did($sid, $did)
{
	$resp = spc_request_csv($sid, 0, "get did");

	if($resp === false)
	{
		echo "spc_check_did: sid$sid - device not found\r\n";
		return false;
	}

	if($resp[1] != "40002405")
	{
		echo "spc_check_did: unknown device ", $resp[2], "\r\n";
		return false;
	}

	return true;
}

function pen_up()
{
	$angle = 110;

	$width = WIDTH_MIN + (int)round((WIDTH_MAX - WIDTH_MIN) * $angle / 180.0);

	if(($width >= WIDTH_MIN) && ($width <= WIDTH_MAX))
		ht_pwm_width(2, $width, PWM_PERIOD);
}

function pen_down()
{
	$angle = 180;

	$width = WIDTH_MIN + (int)round((WIDTH_MAX - WIDTH_MIN) * $angle / 180.0);

	if(($width >= WIDTH_MIN) && ($width <= WIDTH_MAX))
		ht_pwm_width(2, $width, PWM_PERIOD);
}

function xy_goto($x, $y)
{
	global $mode;
	
	if($x < TOUCH_OFFSET)
		$x = TOUCH_OFFSET;
	if($x > (MAX_X - TOUCH_OFFSET))
		$x = MAX_X - TOUCH_OFFSET;
	
	if($y < TOUCH_OFFSET)
		$y = TOUCH_OFFSET;
	if($y > (MAX_Y - TOUCH_OFFSET))
		$y = MAX_Y - TOUCH_OFFSET;
	
	$x0 = (int)step_cmd(SID_X, "get pos") / $mode;
	$y0 = (int)step_cmd(SID_Y, "get pos") / $mode;
	
	$delta_x = $x - $x0; 
	$delta_y = $y - $y0;
	
	if($delta_x == 0 && $delta_y == 0)
		return;
	
	$speed_x = SPEED_X_COEF * abs($delta_x) + SPEED_X_OFFSET;
	$speed_y = SPEED_Y_COEF * abs($delta_y) + SPEED_Y_OFFSET;
	
	$x *= $mode; 
	$y *= $mode;
	$speed_x *= $mode;
	$speed_y *= $mode;
	
	if($speed_x > SPEED_X_MAX * $mode)
		$speed_x = SPEED_X_MAX * $mode;
	
	if($speed_y > SPEED_Y_MAX * $mode)
		$speed_y = SPEED_Y_MAX * $mode;
	
	$accel_x = ACCEL_X_COEF * $speed_x + ACCEL_X_OFFSET;
	$accel_y = ACCEL_Y_COEF * $speed_y + ACCEL_Y_OFFSET;
	
	if($accel_x > ACCEL_X_MAX * $mode)
		$accel_x = ACCEL_X_MAX * $mode;
	
	if($accel_y > ACCEL_Y_MAX * $mode)
		$accel_y = ACCEL_Y_MAX * $mode;
	
	if($delta_x == 0)
	{
		step_cmd(SID_Y, "goto $y $speed_y $accel_y");
	}
	else if($delta_y == 0)
	{
		step_cmd(SID_X, "goto $x $speed_x $accel_x");
	}
	else
	{
		step_cmd(SID_X, "goto $x $speed_x $accel_x");
		step_cmd(SID_Y, "goto $y $speed_y $accel_y");
	}
	//echo "state: ", step_cmd(SID_Y, "get state"), "\r\n";
	//echo SID_X, ", goto $x $speed_x $accel_x\r\n";
	//echo SID_Y, ", goto $y $speed_y $accel_y\r\n";
}

function xy_state()
{
	$ret_val = 0;
	
	$x_state = (int)step_cmd(SID_X, "get state");
	$y_state = (int)step_cmd(SID_Y, "get state");
	
	if($x_state == 1) // motor is locked
		$ret_val |= STATE_X_LOCK;
	else if($x_state > 1) // motor is running
		$ret_val |= STATE_X_RUN;
		
	if($y_state == 1) // motor is locked
		$ret_val |= STATE_Y_LOCK;
	else if($y_state > 1) // motor is running
		$ret_val |= STATE_Y_RUN;
		
	return $ret_val;
}

function xy_wait()
{
	while(xy_state() & (STATE_X_RUN | STATE_Y_RUN))
		usleep(1);
}

function xy_init()
{
	global $mode;
	
	$width = WIDTH_MIN + (int)round((WIDTH_MAX - WIDTH_MIN) * 110 / 180.0);
	ht_pwm_setup(2, $width, PWM_PERIOD);
	pen_up();
	
	spc_reset();
	spc_sync_baud(460800);

	if(!spc_check_did(SID_X, "40002405"))
		return;
	if(!spc_check_did(SID_Y, "40002405"))
		return;

	step_cmd(SID_X, "set vref stop 4");
	step_cmd(SID_X, "set vref drive 15");
	step_cmd(SID_X, "set mode $mode");
	step_cmd(SID_X, "set rsnc 120 250");
	
	step_cmd(SID_Y, "set vref stop 4");
	step_cmd(SID_Y, "set vref drive 15");
	step_cmd(SID_Y, "set mode $mode");
	step_cmd(SID_Y, "set rsnc 120 250");
	
	// move pen to (0, 0)
	$v = 800 * $mode;
	$a = 10000 * $mode;
	step_cmd(SID_X, "goto -sw1 $v $a");
	step_cmd(SID_Y, "goto -sw1 $v $a");
	xy_wait();
	step_cmd(SID_X, "reset");
	step_cmd(SID_Y, "reset");
	/*
	$time_stemp = st_free_get_count(0);
	step_cmd(SID_X, "goto 100 $v $a");
	step_cmd(SID_Y, "goto 100 $v $a");
	xy_wait();
	$time_stemp = st_free_get_count(0) - $time_stemp;
	
	echo $time_stemp;
	
	step_cmd(SID_X, "reset");
	step_cmd(SID_Y, "reset");
	*/
	xy_goto(TOUCH_OFFSET, TOUCH_OFFSET);
	xy_wait();
	
	// uncomment this block for the first run and change the value in line 45 of index.php
	/*
	// check max steps
	step_cmd(SID_X, "goto +sw0 1600 20000");
	step_cmd(SID_Y, "goto +sw0 1600 20000");
	
	xy_wait();
	// change this value in line 45 of index.php: var MAX_X * $mode = 3421, MAX_Y * $mode = 4296;
	echo "Max X:", step_cmd(SID_X, "get pos"), "\r\n";
	echo "Max Y:", step_cmd(SID_Y, "get pos"), "\r\n";
	*/
}

function send_position($pen_state)
{
	global $mode;
	
	$x = (int)step_cmd(SID_X, "get pos") / $mode;
	$y = (int)step_cmd(SID_Y, "get pos") / $mode;
	$wbuf = "[$x, $y, $pen_state]";
	ws_write(0, $wbuf);
}

$mode = 32;

ws_setup(0, "xy_plotter", "csv.phpoc");
xy_init();

$pre_x = 0;
$pre_y = 0;
$cur_x = 0;
$cur_y = 0;
$rbuf = "";
$pen_state = CMD_PEN_UP;
$x_is_unlock = false;
$y_is_unlock = false;

while(1)
{
	if(ws_state(0) == TCP_CONNECTED)
	{
		$rlen = ws_read_line(0, $rbuf);
		
		if($rlen)
		{
			$data = explode(" ", $rbuf);
			$cmd = (int)$data[0];
			
			switch($cmd)
			{
				case CMD_PEN_DOWN:
					$x = (int)$data[1];
					$y = (int)$data[2];
					xy_goto($x, $y);
					xy_wait();
					
					pen_down();
					$pen_state = PEN_DOWN;
					
					send_position($pen_state);
					
					break;
				case CMD_PEN_UP:
					pen_up();
					$pen_state = PEN_UP;
					
					send_position($pen_state);
					
					break;
				case CMD_MOVE:
					$x = (int)$data[1];
					$y = (int)$data[2];
					xy_goto($x, $y);
					//xy_wait();
					
					break;
			}
		}
	}
	
	$cur_x = (int)step_cmd(SID_X, "get pos") / $mode;
	$cur_y = (int)step_cmd(SID_Y, "get pos") / $mode;
	
	if(abs($pre_x - $cur_x) > 10 || abs($pre_y - $cur_y) > 10)
	{
		$pre_x = $cur_x;
		$pre_y = $cur_y;
		send_position($pen_state);
	}
	
	$xy_state = xy_state();
	
	if(($xy_state & STATE_X_LOCK) && ($x_is_unlock == false))
	{
		step_cmd(SID_X, "unlock");
		$x_is_unlock = true;
	}
	else if(($xy_state & STATE_X_RUN))
	{
		step_cmd(SID_X, "eio set 0 mode lock");
		step_cmd(SID_X, "eio set 1 mode lock");
		$x_is_unlock = false;
	}
	
	if(($xy_state & STATE_Y_LOCK) && ($y_is_unlock == false))
	{
		step_cmd(SID_Y, "unlock");
		$y_is_unlock = true;
	}
	else if(($xy_state & STATE_Y_RUN))
	{
		step_cmd(SID_Y, "eio set 0 mode lock");
		step_cmd(SID_Y, "eio set 1 mode lock");
		$y_is_unlock = false;
	}
}

?>

Credits

phpoc_man

phpoc_man

62 projects • 408 followers

Comments