About The Project
This project is similar to my another project https://forum.phpoc.com/blogs/khanh-...ller-and-phpoc. Instead of using Stepper Motor Controller (using Full-step or Haft-step method to control step motors), this project used the Stepper Motor Controller Ⅱ (using micro-stepping method to control step motors). The micro-stepping method makes the step motor move smoother and significantly reduce the vibration in comparison with full-step and half-step method (vibration is one of weakness of step motor).
Things Used In This Project
- 1 x Makeblock XY-Plotter Robot Kit V2.0 (No Electronics). 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 x PHPoC blue (Provide web interface and control two step motors via PHPoC Motor Controller)
- 2 x PHPoC Stepper Motor Controller Ⅱ (control to two step motors to move the pen )
How It Works
When a finger touches to the drawing area in webpage, the XY coordinate of touching point is sent to PHPoC. After scaling the coordinate, PHPoC will move two step motors to locate the pen to that coordinate. During moving period, PHPoC continuously send trajectory of the pen to web app, the web app draw the trajectory on the canvas.
Source Code
Main task (task0.php):
- Receiving the command from webpage and do task according to command.
- CMD_MOVE: move pen to a position by controlling two step motor
- CMD_PEN_UP: raise the pen by changing the angle of servo motor
- CMD_PEN_DOWN: lower the pen by changing the angle of servo motor
- Continuously read the current position of pen and send to webpage
<task0.php>
PHP Code:
<?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;
}
}
?>
Web interface (index.php)
- Providing the user interface
- Handling the user event and send command with coordinate to PHPoC
- Receiving the trajectory from PHPoC and draw it on webpage
<index.php>
PHP Code:
<!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>
If yes, then will b code and pinpositions..
If no then why???
The above source code only work for step motor controller. Because the step motor has many built-in functions. You just need to care about how to control the step motor. You just need to send the command to control step motor.
If you implement this project by using TB6600 driver, you may need to write a lot of code.