// pins for motor 1
const byte M1_A = 8;
const byte M1_B = 7;
const byte M1_C = 6;
const byte M1_D = 5;
// pins for motor 2
const byte M2_A = 4;
const byte M2_B = 3;
const byte M2_C = 2;
const byte M2_D = A1; // (note: analog pin used as digital)
// pins for input. (note: analog pins used as digital)
const byte I_LEFT = A2;
const byte I_UP = A3;
const byte I_DOWN = A4;
const byte I_RIGHT = A5;
// pin sequence for stepping motor1 and motor2
const byte M1PINS[] = {M1_A,M1_B,M1_C,M1_D};
const byte M2PINS[] = {M2_A,M2_B,M2_C,M2_D};
// each step needs to be a pulse a few ms wide, or motor doesn't step.
byte stepDwell = 10;
// pause a few ms between repeating input triggers
byte iPause = 20;
// track the current pin index in the step sequence
byte m1Pin = 0;
byte m2Pin = 0;
// holds the command code, sent over serial
char commandValue = '\0';
// holds the data, sent over serial
int dataValue = 0;
//
void setup() {
Serial.begin(9600);
// setup pins for output
pinMode(M1_A, OUTPUT);
pinMode(M1_B, OUTPUT);
pinMode(M1_C, OUTPUT);
pinMode(M1_D, OUTPUT);
pinMode(M2_A, OUTPUT);
pinMode(M2_B, OUTPUT);
pinMode(M2_C, OUTPUT);
pinMode(M2_D, OUTPUT);
// setup pins for input
pinMode(I_LEFT, INPUT);
pinMode(I_UP, INPUT);
pinMode(I_DOWN, INPUT);
pinMode(I_RIGHT, INPUT);
}
//
void loop() {
// priority is for commands coming over serial
if (Serial.available()) {
char incomingChar = Serial.read();
if ('\n' == incomingChar) { // eol recvd, so process command and value collected so far
processCommand(commandValue, dataValue);
Serial.print("(");Serial.print(commandValue);Serial.print(dataValue);
Serial.println(") ok >");// reply back to calling device
commandValue = '\0'; // reset
dataValue = 0; // reset
} else if (isDigit(incomingChar)) {
dataValue = (dataValue * 10) + (incomingChar - '0'); // accumulate the data value
} else { // non-digit, so consider as a command code
commandValue = incomingChar; // set the current command
}
} else { // if no serial commands to be processed, check for direct inputs and button presses
if (digitalRead(I_LEFT) == HIGH)
step(I_LEFT);
if (digitalRead(I_UP) == HIGH)
step(I_UP);
if (digitalRead(I_DOWN) == HIGH)
step(I_DOWN);
if (digitalRead(I_RIGHT) == HIGH)
step(I_RIGHT);
}
}
/* -------------- support routines -------------- */
// returns true if 'digit' represents an ascii digit between 0 and 9
boolean isDigit (char digit) {
return (digit >= '0' && digit <= '9');
}
//
void processCommand(char commandValue, int dataValue) {
if ('i' == commandValue) {// set iPause
iPause = dataValue;
return;
} else if ('s' == commandValue) {// set stepDwell
stepDwell = dataValue;
return;
}
// if "move" command, process it
byte direction = 255;
if ('L' == commandValue)
direction = I_LEFT;
else if ('U' == commandValue)
direction = I_UP;
else if ('D' == commandValue)
direction = I_DOWN;
else if ('R' == commandValue)
direction = I_RIGHT;
if (255 != direction) {
for (int i = 0; i < dataValue; i++)
step(direction);
return;
}
}
//
void step(byte direction) {
switch (direction) {
case I_LEFT:
m1Pin = cShift(-1,m1Pin);
pulse(M1PINS[m1Pin]);
break;
case I_UP:
m2Pin = cShift(1,m2Pin);
pulse(M2PINS[m2Pin]);
break;
case I_DOWN:
m2Pin = cShift(-1,m2Pin);
pulse(M2PINS[m2Pin]);
break;
case I_RIGHT:
m1Pin = cShift(1,m1Pin);
pulse(M1PINS[m1Pin]);
break;
default:
// invalid value, ignore
break;
}
}
//
void pulse(byte pinToPulse) {
digitalWrite(pinToPulse,HIGH);
delay(stepDwell);
digitalWrite(pinToPulse,LOW);
delay(iPause);
}
/*
performs a circular shift of the index indexToShift, by shiftDirection.
operation is done on a 4-bit word. returns the index shifted to.
cShift(1,0) -> 1 (1000 -> 0100), cShift(1,1) -> 2 (0100 -> 0010)
cShift(1,2) -> 3 (0010 -> 0001), cShift(1,3) -> 0 (0001 -> 1000)
cShift(-1,0) -> 3 (1000 -> 0001), cShift(-1,1) -> 0 (0100 -> 1000)
cShift(-1,2) -> 1 (0010 -> 0100), cShift(-1,3) -> 2 (0001 -> 0010)
*/
byte cShift(char shiftDirection, byte indexToShift) {
char shiftedIndex = shiftDirection + indexToShift;
if (shiftedIndex > 3)
shiftedIndex = 0;
else if (shiftedIndex < 0)
shiftedIndex = 3;
return shiftedIndex;
}
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<title>Step a Sketch Controller</title>
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<script type="text/javascript">
/* ----------------- base utils -----------------*/
function $() { return document.getElementById(arguments[0]); }
function $F() { return document.getElementById(arguments[0]).value; }
function getKeyCode(ev) { if (window.event) return window.event.keyCode; return ev.keyCode; }
function syncAjax(url,data) {
var ajax = new XMLHttpRequest();
try {
ajax.open("POST", url, false);
ajax.setRequestHeader("Content-type", "text/plain");
ajax.send(data);
} catch (e) {console.log('exception:' + e);}
return ajax.responseText;
}
/* ----------------- base utils -----------------*/
// basic drawing related ..................................................
var CANVAS;
var CTX;
function init() {
CANVAS = document.getElementById('plot');
CTX = CANVAS.getContext('2d');
clearCanvas();
}
function clearCanvas() {
CTX.clearRect(0, 0, CANVAS.width, CANVAS.height);
CTX.strokeStyle = "#000000";
CTX.strokeRect(0, 0, 800, 500);
}
// manual line drawing related ..................................................
var P1 = null;
var P2 = null;
function getCoordinates(e) {
var x = e.clientX;
var y = e.clientY;
$("xycoordinates").innerHTML = "(" + x + "," + y + ")";
}
function clearCoordinates() {
$("xycoordinates").innerHTML = "";
}
function canvasClicked(e) {
var markerSize = 6;
var x = e.clientX;
var y = e.clientY;
baseDrawLine(x,y-markerSize,x,y+markerSize);
baseDrawLine(x-markerSize,y,x+markerSize,y);
if (null == P1) {
P1 = {};
P1.x = x;
P1.y = y;
} else {
P2 = {};
P2.x = x;
P2.y = y;
}
}
function manualLine() {
drawLine (P1.x, P1.y, P2.x, P2.y);
}
// machine drawing related ..................................................
/*
q determinant m dx dy
-------------------------------
a x1 > x2, y1 > y2, + - -
b x1 < x2, y1 > y2, - + -
c x1 < x2, y1 < y2, + + +
d x1 > x2, y1 < y2, - - +
*/
/*
drawing primitive function from the underlying system.
*/
function baseDrawLine(fromX, fromY, toX, toY) {
//console.log('from:' + fromX + ',' + fromY + ' to:' + toX + ',' + toY);
CTX.lineWidth = 1;
CTX.strokeStyle = "#000000";
CTX.beginPath();
CTX.moveTo(fromX, fromY);
CTX.lineTo(toX, toY);
CTX.stroke();
}
// suports simulating the stepper motor drive
function stepDrawLine(fromX, fromY, toX, toY) {
var relX = toX - fromX;
var relY = toY - fromY;
if (relX < 0)
stepRender('L', Math.abs(relX), fromX, fromY);
else
stepRender('R', Math.abs(relX), fromX, fromY);
if (relY < 0)
stepRender('U', Math.abs(relY), fromX, fromY);
else
stepRender('D', Math.abs(relY), fromX, fromY);
}
// simulates the stepper motor drive on the device
function stepRender(direction, steps, startingX, startingY) {
switch (direction) {
case 'L':
baseDrawLine(startingX, startingY, startingX-steps, startingY);
break;
case 'R':
baseDrawLine(startingX, startingY, startingX+steps, startingY);
break;
case 'U':
baseDrawLine(startingX, startingY, startingX, startingY-steps);
break;
case 'D':
baseDrawLine(startingX, startingY, startingX, startingY+steps);
break;
default:
break;
}
if (! $('simulate_switch').checked)
toDevice(direction, steps);
}
/*
calculates the interpolated points for straight line
*/
function drawLine(fromX, fromY, toX, toY) {
console.log('from:' + fromX + ',' + fromY + ' to:' + toX + ',' + toY);
if (fromX == toX || fromY == toY) {
console.log('X\'s or Y\'s equal');
//baseDrawLine(fromX, fromY, toX, toY);
stepDrawLine(fromX, fromY, toX, toY);
return;
}
var P_next = {};
var P_current = {};
P_current.x = fromX;
P_current.y = fromY;
var slope_m = Math.abs((toY - fromY) / (toX - fromX));
var slope_type = (fromX > toX) ? ((fromY > toY) ? 'A' : 'D') : ((fromY > toY) ? 'B' : 'C');
var is_slope_upward = slope_m > 1;
console.log('slope_m = ' + slope_m + ' is_slope_upward = ' + is_slope_upward + ' slope_type = ' + slope_type);
var step_range = (is_slope_upward) ? Math.abs(fromY - toY) : Math.abs(fromX - toX);
console.log('step_range ' + step_range);
for (var step = 1; step < step_range; step++) {
var relX = (is_slope_upward) ? Math.round(step / slope_m) : step;
var relY = (is_slope_upward) ? step : Math.round(step * slope_m);
switch (slope_type) {
case 'A':
relX = -relX;
relY = -relY;
break;
case 'B':
relY = -relY;
break;
case 'D':
relX = -relX;
break;
default:
break;
}
P_next.x = fromX + relX;
P_next.y = fromY + relY;
//console.log('rel ' + relX + ' ' + relY + ' Pc:' + P_current.x + ',' + P_current.y + ' Pn:' + P_next.x + ',' + P_next.y);
//baseDrawLine(P_current.x, P_current.y, P_next.x, P_next.y);
stepDrawLine(P_current.x, P_current.y, P_next.x, P_next.y);
P_current.x = P_next.x;
P_current.y = P_next.y;
}
}
function processInput() {
var lines = $F('textarea1').split('\n');
var prevX = -1;
var prevY = -1;
for (var i = 0; i < lines.length; i++) {
var currX = -1;
var currY = -1;
var line = lines[i];
if (line.indexOf('G00') == 0 || line.indexOf('G01') == 0) {
var parts = line.split(' ');
if (parts.length > 2) {
if (parts[1].indexOf('X') == 0)
currX = parseInt(parts[1].substring(1)); // cast to wholenumber
if (parts[2].indexOf('Y') == 0)
currY = parseInt(parts[2].substring(1)); // cast to wholenumber
if (currX > -1 && currY > -1) {
console.log('x,y:' + currX + ',' + currY);
console.log(prevX + ',' + prevY);
if (prevX != -1 && prevY != -1) {
//baseDrawLine(prevX, prevY, currX, currY);
drawLine(prevX, prevY, currX, currY);
}
prevX = currX;
prevY = currY;
}
}
}
}
}
// device communication related .................................................
function deviceCommand(ref) {
var command = ref.id.substring(0,1);
var value = $F(command + '_value');
$('comm_log').innerHTML = command + value + '<br />';
var response = toDevice(command, value);
$('comm_log').innerHTML += response;
}
function toDevice(command, value) {
return response = syncAjax('/', command + value);
}
function test() {
}
</script>
<style media="screen" type="text/css">
body,table, button {font-family:verdana,helvetica,arial,sans-serif;margin:0px;font-size:10px;}
button {cursor:pointer;}
input {border: solid 1px gray;}
input.d_input {width:24px;}
</style>
</head>
<body onload="init()">
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td valign="top" width="800">
<canvas id="plot" width="800" height="500" style="background-color:#DCDCDC;" onmousemove="getCoordinates(event)" onmouseout="clearCoordinates()" onclick="canvasClicked(event)">
<p>browser doesn't support canvas.</p>
</canvas>
<table width="100%">
<tr>
<td>
<button onclick="clearCanvas();P1=P2=null;">clear canvas</button>
<button onclick="manualLine()" title="draw line between 2 points picked on canvas">line</button>
</td>
<td align="right"><span id="xycoordinates"></span></td>
</tr>
</table>
</td>
<td valign="top">
G-code to be processed:<br />
<textarea id="textarea1" rows="30" cols="40">
</textarea>
<br />
<button onclick="processInput()">process</button> <input type="checkbox" id="simulate_switch" checked="checked" /> simulate
</td>
</tr>
</table>
<hr />
<table>
<tr>
<td valign="top">
<table style="padding:5px;">
<tr><td colspan="2">serial port
<button id="o_command" onClick="deviceCommand(this)">open</button><input type="hidden" id="o_value" value="" />
<button id="c_command" onClick="deviceCommand(this)">close</button><input type="hidden" id="c_value" value="" />
<hr /></td></tr>
<tr><td><input type="text" class="d_input" id="i_value" maxlength="3" /></td><td><button id="i_command" onclick="deviceCommand(this)">set pause</button></td></tr>
<tr><td><input type="text" class="d_input" id="s_value" maxlength="3" /></td><td><button id="s_command" onclick="deviceCommand(this)">set dwell</button></td></tr>
<tr><td colspan="2"><hr /><div id="comm_log" /></td></tr>
</table>
</td>
<td>
<table style="border:1px solid gray;padding:5px;">
<tr align="middle"><td colspan="2"></td><td><input type="text" class="d_input" id="U_value" maxlength="3" /></td><td colspan="2"></td></tr>
<tr align="middle"><td colspan="2"></td><td><button id="U_command" onclick="deviceCommand(this)">U</button></td><td colspan="2"></td></tr>
<tr align="middle"><td><input type="text" class="d_input" id="L_value" maxlength="3" /></td>
<td><button id="L_command" onclick="deviceCommand(this)">L</button></td>
<td></td><td><button id="R_command" onclick="deviceCommand(this)">R</button></td>
<td><input type="text" class="d_input" id="R_value" maxlength="3" /></td></tr>
<tr align="middle"><td colspan="2"></td><td><button id="D_command" onclick="deviceCommand(this)">D</button></td><td colspan="2"></td></tr>
<tr align="middle"><td colspan="2"></td><td><input type="text" class="d_input" id="D_value" maxlength="3" /></td><td colspan="2"></td></tr>
</table>
</td>
</tr>
</table>
</body>
</html>
#!/usr/bin/python
import BaseHTTPServer
import SimpleHTTPServer
import serial
HTTP_PORT = 56789
SERIAL_PORT = '/dev/ttyUSB0'
SERIAL_REF = None
def execute_command(commandData):
global SERIAL_REF
if 'o' == commandData:
SERIAL_REF = serial.Serial(SERIAL_PORT, baudrate=9600, timeout=None)
return 'ok - opened'
elif 'c' == commandData:
SERIAL_REF.close()
return 'ok - closed'
else:
SERIAL_REF.write(commandData + '\n')
response = SERIAL_REF.readline()
return response
class SimpleHTTPHandlerImpl(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_GET(self):
try:
#serve files, and directory listings by following self.path from current working directory
SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
except:
result = '<html><body>error...</body></html>'
self.wfile.write(result)
def do_POST(self):
"""Handle a post request"""
length = int(self.headers.getheader('content-length'))
data_string = self.rfile.read(length)
result = execute_command(data_string)
self.wfile.write(result)
def main():
server = BaseHTTPServer.HTTPServer(("localhost", HTTP_PORT), SimpleHTTPHandlerImpl)
try:
print "serving at port", HTTP_PORT
server.serve_forever()
except KeyboardInterrupt:
print 'shutting down...'
server.socket.close()
if __name__ == '__main__':
main()