Monday, December 1, 2008

Processing ported to an Arduino + TouchShield ported from Processing

This is a little project I just finished working on over the past few weeks, called project "P-A-P-A", which stands for: "Processing-Ported to-Arduino-Running Processing on an-Arduino". It's something of a recursive project, since the Arduino was originally based on a subset of the Processing Graphics language, and can now display a subset of Processing functions through sketches that interact with through a TouchShield. It's not the most heavily optimized code I've ever written, but that's only because I was focused more on just getting it to work, figuring I could go back later and optimize it.



Since I've only ported a subset of the language, I'm calling this the informal "Subprocessing" port of Processing. I'll set up a small website soon to document all of the updates and changes, but for now, my mini-port implements the following functions:

fill()
quad()
ellipse()
rect()
dist()
stroke()
triangle()
line()
point()
mouseX and mouseY
width and height

...as well as some temporary functions that are waiting for full porting to Processing, including:

setbrightness()
gettouch()
clearscreen()
setbcolor()
setfcolor()

This means that you can run almost all of the basic shapes examples in the book, Processing by Casey Reas and Ben Fry (with a little bit of work to make sure the shapes are drawn within the correct screen bounds, since the TouchShield isn't good at handling overflow). As an aside, I've read most of the book now, and I highly recommend it if you're new to Processing, or just curious!

This is a video showing my port of Processing to the TouchShield, so it can be used on an Arduino to run processing graphics programs. I think the coolest part is having different sketches running on different Arduino's, so all I had to do was plug in the TouchShield from one to the next, and the programs automatically updated:



And here's a closeup video of the TouchShield + Arduino + Lithium Backpack running the same sketch as John Resig made over at his website. It was definitely inspirational reading through some of his code to see all the hoops he jumped through to get it working!



Finally, I uploaded some other pictures I took to Flickr.








Anyway, enjoy the recursion! As always, here's the Open Source software and API for the Arduino:

//Set up the serial communications channel between the Arduino and the TouchShield - thanks LadyAda!
#include
#define RXPIN 3
#define TXPIN 2
AFSoftSerial mySerial = AFSoftSerial(RXPIN, TXPIN);


//This is the beginnings of the "Sub-Processing" for TouchShield library
//Version 0.1
//by inthebitz @ antipastohw.blogspot.com & liquidware.com
//updated on nov 30, 2008
#define SCREENX 128
#define SCREENY 128
#define WAIT 10

void sendString( char *buf) {
int i, size; size = strlen(buf);
for (i = 0;i < i =" 0;i" mousex =" SCREENX" mousey =" SCREENY" width =" SCREENX;" height =" SCREENY;" len =" strlen(command);">> 8); command[4]=0;
command[5]=0;command[6]=0;command[7]=0;command[8]=0;
command[9]='~';command[10]=0;
sendStringL(command,11);
delay(200+time*10); //wait for the touchshield to fade
}
void fadein( int time) {
command[0]='|'; command[1]=6;
command[2]=(unsigned char)time; command[3]=(unsigned char)(time >> 8); command[4]=0;
command[5]=0;command[6]=0;command[7]=0;command[8]=0;
command[9]='~';command[10]=0;
sendStringL(command,11);
delay(200+time*10); //wait for the touchshield to fade
}
void point( int x, int y) {
command[0]='|'; command[1]=7;
command[2]=(unsigned char)x; command[3]=(unsigned char)(x >> 8); command[4]=(unsigned char)y;
command[5]=(unsigned char)(y >> 8);command[6]=0;command[7]=0;command[8]=0;
command[9]='~';command[10]=0;
sendStringL(command,11);
delay(WAIT);
}
void drawcircle( int x, int y, int r) {
command[0]='|'; command[1]=8;
command[2]=(unsigned char)x; command[3]=(unsigned char)(x >> 8); command[4]=(unsigned char)y;
command[5]=(unsigned char)(y >> 8);command[6]=(unsigned char)r;command[7]=(unsigned char)(r >> 8);command[8]=0;
command[9]='~';command[10]=0;
sendStringL(command,11);
delay(WAIT);
}
void drawrect( int x1, int y1, int x2, int y2) {
command[0]='|'; command[1]=9;
command[2]=(unsigned char)x1; command[3]=(unsigned char)(x1 >> 8); command[4]=(unsigned char)y1;
command[5]=(unsigned char)(y1 >> 8);command[6]=(unsigned char)x2;command[7]=(unsigned char)(x2 >> 8);
command[8]=(unsigned char)y2;command[9]=(unsigned char)(y2 >> 8);
command[10]='~';command[11]=0;
sendStringL(command,12);
delay(WAIT);
}
void line( int x1, int y1, int x2, int y2) {
command[0]='|'; command[1]=10;
command[2]=(unsigned char)x1; command[3]=(unsigned char)(x1 >> 8); command[4]=(unsigned char)y1;
command[5]=(unsigned char)(y1 >> 8);command[6]=(unsigned char)x2;command[7]=(unsigned char)(x2 >> 8);
command[8]=(unsigned char)y2;command[9]=(unsigned char)(y2 >> 8);
command[10]='~';command[11]=0;
sendStringL(command,12);
delay(WAIT);
}
void drawchar( int x, int y, char text) {
command[0]='|'; command[1]=11;
command[2]=(unsigned char)x; command[3]=(unsigned char)(x >> 8); command[4]=(unsigned char)y;
command[5]=(unsigned char)(y >> 8);command[6]=text;command[7]=0;command[8]=0;
command[9]='~';command[10]=0;
sendStringL(command,11);
delay(WAIT);
}
void drawstring( int x, int y, char *text) {
int len;
command[0]='|'; command[1]=12;
command[2]=(unsigned char)x; command[3]=(unsigned char)(x >> 8); command[4]=(unsigned char)y;
command[5]=(unsigned char)(y >> 8);command[6]=0;command[7]=0;command[8]=0;
strcpy ( &command[9], text);
len = strlen(text);
sendStringL(command,9+len);//10 is where the zero is
delay(WAIT+len*15);
}
void triangle( int x1, int y1, int x2, int y2, int x3, int y3) {
command[0]='|'; command[1]=15;
command[2]=(unsigned char)x1; command[3]=(unsigned char)(x1 >> 8); command[4]=(unsigned char)y1;
command[5]=(unsigned char)(y1 >> 8);command[6]=(unsigned char)x2;command[7]=(unsigned char)(x2 >> 8);
command[8]=(unsigned char)y2;command[9]=(unsigned char)(y2 >> 8);
command[10]='~';command[11]=0;
sendStringL(command,12);
delay(WAIT+100);
command[0]='|'; command[1]=16;
command[2]=(unsigned char)x3; command[3]=(unsigned char)(x3 >> 8); command[4]=(unsigned char)y3;
command[5]=(unsigned char)(y3 >> 8);command[6]=0;command[7]=0;
command[8]=0;command[9]=0;
command[10]='~';command[11]=0;
sendStringL(command,12);
delay(WAIT+300);
}
void clearscreen( void) {
setbcolor( 0, 0, 0);fillback();
}

void background( int b) {
setbcolor( b, b, b);fillback();
}

void stroke( int s) {
setfcolor( s, s, s);
}

void stroke( int r, int g, int b) {
setfcolor( r, g, b);
}

void gettouch( void) {
command[0]='|'; command[1]=13;
command[2]=0; command[3]=0; command[4]=0;
command[5]=0;command[6]=0;command[7]=0;command[8]=0;
command[9]='~';command[10]=0;
sendStringL(command,11);
delay(WAIT);

delay(10);

mySerial.read();
mouseX = mySerial.read();
mouseY = mySerial.read();

if (mouseX < mousex =" 1;"> SCREENX) mouseX = SCREENX;
if (mouseY < mousey =" 1;"> SCREENY) mouseY = SCREENY;
}

float dist(float x1, float y1, float x2, float y2) {
return sqrt(sq(x2-x1)+sq(y2-y1));
}

void rect (int x, int y, int width, int height) {
drawrect(x, y, x+width, y+height);
}

void quad( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
triangle(x1, y1, x2, y2, x3, y3);
delay(1000);
triangle(x2, y2, x3, y3, x4, y4);
delay(1000);
triangle(x1, y1, x3, y3, x4, y4);
delay(1000);
}

void fill( int f) {
setfcolor(f,f,f);
setbcolor(f,f,f);
}

void ellipse( int x, int y, int radx, int rady) {
if (radx == rady) {
drawcircle( x, y, radx);
} else {
command[0]='|'; command[1]=14;
command[2]=(unsigned char)x; command[3]=(unsigned char)(x >> 8); command[4]=(unsigned char)y;
command[5]=(unsigned char)(y >> 8);command[6]=(unsigned char)radx;command[7]=(unsigned char)(radx >> 8);
command[8]=(unsigned char)rady;command[9]=(unsigned char)(rady >> 8);
command[10]='~';command[11]=0;
sendStringL(command,12);
delay(WAIT+100);
}
}

void setup() {
mySerial.begin(9600);
Serial.begin(9600);
//delay(2000);
}




//http://ejohn.org/apps/processing.js/examples/basic/distance2d.html
float max_distance;
int oldx, oldy;

void loopo () {
int a = 0;

setbcolor( 128, 255, 57);

delay(a);

fillback();

delay(a);

setbcolor( 0, 0, 0);

delay(a);

fillback();

delay(a);

//setbcolor( 255, 255, 255);
drawchar(50,50,'X');
drawstring(50,70,"asdf hello");


delay(a);

setbcolor( 0, 0, 255);
fillback();

delay(a);

}


void loop () {

gettouch();
oldx = mouseX;
oldy = mouseY;

max_distance = dist(0, 0, width, height);

setbrightness( 10);
setbcolor( 0, 0, 0);fillback();
draw();

while (1) {

gettouch();
if ((mouseX != oldx) || (mouseY != oldy)) {
oldx = mouseX;
oldy = mouseY;

clearscreen();

setbcolor( 255, 255, 255);
setfcolor( 255, 255, 255);

draw();

}

}
}




void draw( void) {
float size;

//background(51);

for(int i = 0; i <= width; i += 20) { for(int j = 0; j <= width; j += 20) { float size = dist(mouseX, mouseY, i, j); size = size/max_distance * 33; ellipse(i, j, size, size); } } }






And here's the source code for the TouchShield:


#include

COLOR fcolor = { 0, 0, 0 };
COLOR bcolor = { 0, 0, 0 };
COLOR green = { 0, 255, 0 };
COLOR blue = {0,0,255};
COLOR yellow = {255,255,0};
COLOR grey = {0x77,0x77,0x77};
COLOR red = {255,0,0};
COLOR black = {0,0,0};
COLOR white = {255,255,255};

char buf[32];
char tmp[32];
int brightness=5;
POINT my_point;
int trix1, triy1, trix2, triy2, trix3, triy3;

void setup() {
Serial.begin(9600);
lcd_setBrightness(brightness);
lcd_clearScreen(white);
}

void GetString(char *buf, int bufsize)
{
int i;
for (i=0; i9) break;}; // is it the terminator byte?
}
buf[i] = 0; // 0 string terminator just in case
}

void loop() {
int cptr, x1, y1, x2, y2, r, r2, timer;
GetString(buf, sizeof(buf));

delay(10);

if (touch_get_cursor(&my_point)) {

}
if (buf[0] == '|') {
cptr = 1;
switch (buf[cptr]) {
case 1:
fcolor.red = buf[cptr+1];
fcolor.green = buf[cptr+2];
fcolor.blue = buf[cptr+3];
break;
case 2:
bcolor.red = buf[cptr+1];
bcolor.green = buf[cptr+2];
bcolor.blue = buf[cptr+3];
break;
case 3:
lcd_clearScreen(bcolor);
break;
case 4:
lcd_setBrightness(buf[cptr+1]);
brightness = buf[cptr+1];
break;
case 5:
timer = (buf[cptr+2] << i =" brightness;">0; i--) {
lcd_setBrightness(i);
delay(timer);
}
CLRBIT(PORTE, PE3);
break;
case 6:
timer = (buf[cptr+2] << i =" 0;" x1 =" (buf[cptr+2]" y1 =" (buf[cptr+4]" x1 =" (buf[cptr+2]" y1 =" (buf[cptr+4]" r =" (buf[cptr+6]" x1 =" (buf[cptr+2]" y1 =" (buf[cptr+4]" x2 =" (buf[cptr+6]" y2 =" (buf[cptr+8]" x1 =" (buf[cptr+2]" y1 =" (buf[cptr+4]" x2 =" (buf[cptr+6]" y2 =" (buf[cptr+8]" x1 =" (buf[cptr+2]" y1 =" (buf[cptr+4]" x1 =" (buf[cptr+2]" y1 =" (buf[cptr+4]" x1 =" (buf[cptr+2]" y1 =" (buf[cptr+4]" r =" (buf[cptr+6]" r2 =" (buf[cptr+8]" trix1 =" (buf[cptr+2]" triy1 =" (buf[cptr+4]" trix2 =" (buf[cptr+6]" triy2 =" (buf[cptr+8]" trix3 =" (buf[cptr+2]" triy3 =" (buf[cptr+4]" mycolor =" {255," red =" fill.red;mycolor.green" blue =" fill.blue;" red =" outline.red;mycolor.green" blue =" outline.blue;" x =" 0," y =" b;" a2 =" (long)a*a," b2 =" (long)b*b;" crit1 =" -(a2/4" crit2 =" -(b2/4" crit3 =" -(b2/4" t =" -a2*y;" dxt =" 2*b2*x," dyt =" -2*a2*y;" d2xt =" 2*b2," d2yt =" 2*a2;">=0 && x<=a) { lcd_pixel(xc+x, yc+y, mycolor); if (x!=0 || y!=0) lcd_pixel(xc-x, yc-y, mycolor); if (x!=0 && y!=0) { lcd_pixel(xc+x, yc-y, mycolor); lcd_pixel(xc-x, yc+y, mycolor); } if (t + b2*x <= crit1 || /* e(x+1,y-1/2) <= 0 */ t + a2*y <= crit3) /* e(x+1/2,y) <= 0 */ incx(); else if (t - a2*y > crit2) /* e(x+1/2,y-1) > 0 */
incy();
else {
incx();
incy();
}
}
}


void dispFillEllipse(int xc, int yc, int a, int b)
{ /* e(x,y) = b^2*x^2 + a^2*y^2 - a^2*b^2 */
int x = 0, y = b;
unsigned int width = 1;
long a2 = (long)a*a, b2 = (long)b*b;
long crit1 = -(a2/4 + a%2 + b2);
long crit2 = -(b2/4 + b%2 + a2);
long crit3 = -(b2/4 + b%2);
long t = -a2*y; /* e(x+1/2,y-1/2) - (a^2+b^2)/4 */
long dxt = 2*b2*x, dyt = -2*a2*y;
long d2xt = 2*b2, d2yt = 2*a2;
while (y>=0 && x<=a) { if (t + b2*x <= crit1 || /* e(x+1,y-1/2) <= 0 */ t + a2*y <= crit3) { /* e(x+1/2,y) <= 0 */ incx(); width += 2; } else if (t - a2*y > crit2) { /* e(x+1/2,y-1) > 0 */
//dispRectangle(xc-x, yc-y, width,1);
lcd_rectangle(xc-x, yc-y, xc-x+width, yc-y+1, mycolor, mycolor);
if (y!=0)
//dispRectangle(xc-x, yc+y, width,1);
lcd_rectangle(xc-x, yc+y, xc-x+width, yc+y+1, mycolor, mycolor);
incy();
}
else {
//dispRectangle(xc-x, yc-y, width,1);
lcd_rectangle(xc-x, yc-y, xc-x+width, yc-y+1, mycolor, mycolor);
if (y!=0)
//dispRectangle(xc-x, yc+y, width,1);
lcd_rectangle(xc-x, yc+y, xc-x+width, yc+y+1, mycolor, mycolor);
incx();
incy();
width += 2;
}
}
if (b == 0)
//dispRectangle(xc-a, yc, 2*a+1,1);
lcd_rectangle(xc-a, yc, xc-a+2*a+1, yc+1, mycolor, mycolor);
}


//There's definitely an easier way to do this, but for now I'll use some help from:
//http://www.codeproject.com/KB/GDI/antialias.aspx#dwuln
//Edited by inthebitz
void DrawWuLine ( short X0, short Y0, short X1, short Y1, COLOR linecolor )
{
unsigned short IntensityShift, ErrorAdj, ErrorAcc;
unsigned short ErrorAccTemp, Weighting, WeightingComplementMask;
short DeltaX, DeltaY, Temp, XDir;
short BaseColor = 0;
short NumLevels = 2;
unsigned short IntensityBits = 2;

/* Make sure the line runs top to bottom */
if (Y0 > Y1) {
Temp = Y0; Y0 = Y1; Y1 = Temp;
Temp = X0; X0 = X1; X1 = Temp;
}
/* Draw the initial pixel, which is always exactly intersected by
the line and so needs no weighting */
lcd_pixel(X0, Y0, linecolor);

if ((DeltaX = X1 - X0) >= 0) {
XDir = 1;
} else {
XDir = -1;
DeltaX = -DeltaX; /* make DeltaX positive */
}
/* Special-case horizontal, vertical, and diagonal lines, which
require no weighting because they go right through the center of
every pixel */
if ((DeltaY = Y1 - Y0) == 0) {
/* Horizontal line */
while (DeltaX-- != 0) {
X0 += XDir;
lcd_pixel(X0, Y0, linecolor);
}
return;
}
if (DeltaX == 0) {
/* Vertical line */
do {
Y0++;
lcd_pixel(X0, Y0, linecolor);
} while (--DeltaY != 0);
return;
}
if (DeltaX == DeltaY) {
/* Diagonal line */
do {
X0 += XDir;
Y0++;
lcd_pixel(X0, Y0, linecolor);
} while (--DeltaY != 0);
return;
}
/* Line is not horizontal, diagonal, or vertical */
ErrorAcc = 0; /* initialize the line error accumulator to 0 */
/* # of bits by which to shift ErrorAcc to get intensity level */
IntensityShift = 16 - IntensityBits;
/* Mask used to flip all bits in an intensity weighting, producing the
result (1 - intensity weighting) */
WeightingComplementMask = NumLevels - 1;
/* Is this an X-major or Y-major line? */
if (DeltaY > DeltaX) {
/* Y-major line; calculate 16-bit fixed-point fractional part of a
pixel that X advances each time Y advances 1 pixel, truncating the
result so that we won't overrun the endpoint along the X axis */
ErrorAdj = ((unsigned long) DeltaX << erroracctemp =" ErrorAcc;" weighting =" ErrorAcc">> IntensityShift;
//////////////////////////////////////////////////////
lcd_pixel(X0, Y0, linecolor);
//lcd_pixel(X0 + XDir, Y0, mycolora);
//lcd_pixel(X0, Y0, BaseColor + Weighting);
//lcd_pixel(X0 + XDir, Y0,
// BaseColor + (Weighting ^ WeightingComplementMask));
}
/* Draw the final pixel, which is
always exactly intersected by the line
and so needs no weighting */
lcd_pixel(X1, Y1, linecolor);
return;
}
/* It's an X-major line; calculate 16-bit fixed-point fractional part of a
pixel that Y advances each time X advances 1 pixel, truncating the
result to avoid overrunning the endpoint along the X axis */
ErrorAdj = ((unsigned long) DeltaY << erroracctemp =" ErrorAcc;" weighting =" ErrorAcc">> IntensityShift;
///////////////////////////////////////////////////
lcd_pixel(X0, Y0, linecolor);
//lcd_pixel(X0, Y0 + 1, linecolor);
//lcd_pixel(X0, Y0, BaseColor + Weighting);
//lcd_pixel(X0, Y0 + 1,
// BaseColor + (Weighting ^ WeightingComplementMask));
}
/* Draw the final pixel, which is always exactly intersected by the line
and so needs no weighting */
lcd_pixel(X1, Y1, linecolor);
}



//http://www.swissdelphicenter.ch/en/showcode.php?id=2400
//http://tog.acm.org/GraphicsGems/gemsiii/triangleCube.c
//I'm sure there's a more elegant and cleaner way, but with help from the links above I was able to cludge
//this together, so that it at least works!
#define MIN3(a,b,c) ((((a)<(b))&&((a)<(c))) ? (a) : (((b)<(c)) ? (b) : (c))) #define MAX3(a,b,c) ((((a)>(b))&&((a)>(c))) ? (a) : (((b)>(c)) ? (b) : (c)))


void triangle ( int x1, int y1, int x2, int y2, int x3, int y3, COLOR tcolor, COLOR ocolor) {
for (int x=MIN3(x1, x2, x3); x 0) {
return 1;
} else if (orin <>

4 comments:

Chris said...

Sick!

I can't wait to try out some examples from the book!

Oolong said...

What a cool project!

By the way your link on the Processing Discourse board seems to have lost its final l somehow.

Unknown said...

I might be being stupid, but this code doesn't even compile for me - its full of errors. This stuff was what convinces me to get a Touch Shield, and it doesn't work.

Matt said...

Uh oh ... good point. I have to update this post since the new touchshield core has been released. Have you checked out the new core over here: http://www.liquidware.com/apps/show/26 It has some new examples built into the Stealth core that shows how to get this working.... let me know what you think, or just drop me an email at inthebitz over at gmail.