Processing
Overview
Processing is an open source programming language and IDE based on Java. It is free, works on PC/Mac/Linux, and is easy to learn. Processing lets the programmer quickly make visual objects and interactive programs. It is also easy to use Processing to communicate with a serial port, so programs can interact with microcontrollers. This wiki page will describe how to get Processing, create a simple program, open a serial port, and use an external library to create a GUI (graphical user interface). At the end you will find a template for a Processing program that is ready to communicate over serial and interface with GUI objects.
Download
Processing can be found here. Download, unzip and install Processing. This will create two important folders. The first is the folder you have unzipped. It contains the actual Processing program. The second folder is something like Documents/Processing, depending on your platform. This folder is where you store your projects and add libraries.
Resources
Internal and External Libraries
Processing comes with a large number of great example projects. Go to File->Examples to see example code specific to making 3D objects, Basics, Libraries and Topics. The Processing website also contains example code and links to other projects that may contain examples.
A Simple Program
Processing is designed to allow people who are unfamiliar with programming to quickly get up and running making visually compelling programs.
A Processing file is called a sketch. The IDE is shown at the right. A sketch is saved as a .pde in its own folder with the same name and opens in a tabbed format.
A simple Processing sketch is shown at the right. The IDE has several icons at the top which allow you to run your code, stop running code, save and open code, and export your project. At the bottom of the IDE is a debugging window that you can write to with the print() and println() functions. Processing is object oriented, which basically means that all functions are data structures, and all of your code will run in functions.
All Processing code has at least two major functions, setup() and draw().
setup() is the first function to run. In it you define the size of the window that Processing opens, how fast it updates, and initialize any variables or other functions. setup() runs only once.
draw() runs after setup(). draw() will be called a certain number of times a second, as defined in frameRate() in setup(). This means that draw() functions as an infinite loop. Typically you use draw() to update your graphics. You can also use draw() to check the status of variables, the mouse position, and other objects.
The sketch above contains the following code:
/* Nick Marchuk 02/23/2010 The basic form of a Processing sketch */ // global variables int x, y; // setup() function // this function is the first part of the code to run // use it to setup propertie of the program and initialize variables void setup() { size(400,400); // size(x,y) of the window in pixels frameRate(30); // call the draw() function at 30 frames per second to update the window background(30,30,220); // set the background color of the window in (red, green, blue), see Tools->Color Selector x = y = 0; println("Program Started!"); // print some text to the debug window } // draw() function // this function acts as your infinite loop, running as often as defined in frameRate in setup() void draw() { x = mouseX; y = mouseY; println("cursor at: " + x + ", " + y); }
Note the sections used:
- An area of comments stating who made the code and when, and the purpose of the code
- Global variables. Remember that variables declared in a function, like in setup() or draw(), are local and their values will not be remembered between multiple calls of the function, so global variables will be needed, but try not to use too many, their use is not good programming style. They will eat up your memory and can get confusing. It is much better to pass variables between functions instead of using globals if at all possible.
- void setup(). This function runs first and only once. We use it to:
- set the size of the window in pixels
- set the frameRate, how many times draw() will be called per second
- set the background color of the window, in rgb
- initialize the global variables x and y
- write some text to the debug window to let us know the program is running
- void draw(). In this case when draw() is called we put the value of the mouse position into the variables x and y and print them to the debug window.
Running this sketch will produce a blue window and a stream of text in the debug window. Hit the escape key or the stop button to end the program.
Another Simple Program
Lets edit the simple program and add some objects. Lets declare a function that will take the position of the mouse and change the color and radius of a circle and inversely change the background color of the window. Lets also move the circle back and forth across the screen.
/* Nick Marchuk 02/23/2010 Another basic sketch, demonstrating how easy it is to program in Processing */ // global variables //int x, y; // lets make them local to draw() instead of global // setup() function void setup() { size(400,400); // size(x,y) of the window in pixels, stored automatically in the parameters width and height frameRate(30); // call the draw() function at 30 frames per second to update the window background(128); // set the background color of the window, one number instead of 3 means the color will go from black->white as 0->255 println("Program Started!"); // print some text to the debug window } // draw() function void draw() { // get the mouse position int x = mouseX; int y = mouseY; println("cursor at: " + x + ", " + y); // call some functions to get the circle size, color and position int circle_rad = get_circle_rad(x,y); int circle_color = get_circle_color(x,y); int background_color = 255 - circle_color; // invert the color for the background background(background_color); // apply the color to the background int circle_x = get_circle_pos(); // draw the circle fill(circle_color); // objects have no color, instead they are drawn with the color currently in fill, so all objects after this fill() will be drawn with this color ellipse(circle_x,height/2,circle_rad,circle_rad); } // send this function the position of the mouse and return what the radius should be int get_circle_rad(int x, int y) { int rad = int(0.25*((2*x-y/2)^2)); // some arbitrary fn, must output an int but ^ works on floats return rad; } // send this function the position of the mouse and return the color the circle should be int get_circle_color(int x, int y) { int c_color = int(map(x+y,0,width+height,0,255)); // some arbitrary fn, new number as float = map(current number,min current number, max current number, min new number, max new number) return c_color; } // return the x position of the circle int get_circle_pos() { float m = millis(); // how many milliseconds have passed since the program started int c_x = int(width*abs(sin(2*PI*1/4000*m))); // make the cirle move back and forth return c_x; }
The result looks like:
Serial Port Communication with Processing
To get Processing to interact with a microcontroller, we will open a serial port and read and write to it. What you read and write will depend on what you plan to do. This section will describe how to see the available comm ports, initialize one, write to it and read what comes in.
To use serial communication you need to include the serial library in your sketch. This library comes with Processing, all you need to do is add it to your sketch by selecting Sketch->Import Library...->Serial I/O. This will add the line
import processing.serial.*;
to the top of your code. You could manually type this in as well.
The serial library contains the data type Serial, which allows you to set the baud, flow control, and writing. A serial Event is created when Processing receives data, allowing you to read.
The following code will initialize a serial comm port, write and read.
// open a serial port // expects a stream of input strings, in the form of "# #\n" // so send it a number followed by a space followed by a number and a newline from your microcontroller // include the serial library import processing.serial.*; // make some Serial objects int numPorts = 1; // how many serial ports you want to use in this sketch int numPortInList = 0; // the port you want to use, not the comm number but which one in the list made in the initSerial() function int numPortUsing = 0; // which port you refer to, if you opened more than one Serial myPorts[] = new Serial[numPorts]; // an array of serial objects // initializations void setup() { size(300,300); frameRate(30); println("Program started"); initSerial(); // call a user written function to initialize the serial port, if there are none then exit the program } // infinite loop void draw() { // your program here // if the mouse is pressed, write the x and y position of the mouse out of the port // if you loop the serial tx to its rx as a test, this code will read the x y position and print it to the debug window if (mousePressed == true) { println("mouse pressed"); myPorts[numPortUsing].write(mouseX + " " + mouseY + "\n"); } } // initialize the serial port void initSerial() { println(Serial.list()); // List all the available serial ports in the debug window // see how many ports there are, if there aren't any then exit if (Serial.list().length > 0) { String portList = Serial.list()[numPortInList]; // grab the name of the port you want to use myPorts[numPortUsing] = new Serial(this, portList, 19200); // initialize the serial port with a baud rate of 19200 // read bytes into a buffer until you get a linefeed (ASCII 10): myPorts[numPortUsing].bufferUntil('\n'); } else { // uh oh, no ports, exit the program println("No serial ports found!"); exit(); } } // serial event. This event is triggered when bytes appear in the serial port buffer // check which port generated the event, just in case there are more than 1 ports talking // this code expects to see a string with a number, a space, a number and a newline // it extracts the two number from the string and prints them to the debug window void serialEvent(Serial thisPort) { // variable to hold the number of the port int portNumber = -1; // iterate over the list of ports opened, and match the one that generated this event for (int p = 0; p < myPorts.length; p++) { if (thisPort == myPorts[p]) { portNumber = p; } } // read the serial buffer as a string until a newline appears String myString = thisPort.readStringUntil('\n'); // if you got any bytes other than the newline if (myString != null) { myString = trim(myString); // ditch the newline // split the string at the spaces, save as integers, can't do floats int dataFromSerial[] = int(split(myString, ' ')); // check to make sure its the right port and there are the correct number of integers in the packet if ((dataFromSerial.length == 2)&&(portNumber==numPortUsing)) { // print what it got in the debug window print(dataFromSerial[0]); print(" "); println(dataFromSerial[1]); } } } // end serialEvent
After loading the code and pressing the mouse a few times, the debug window looks like:
A Graphical User Interface (GUI) with Processing
A GUI contains elements like check boxes, radio buttons, regular buttons, labels and text entry. While it is possible to write your own custom objects to perform the function objects found in a GUI (see the Examples->Topics->GUI->Button example), it would be even better to use a library that contains all of these elements. Processing does not come with a library of GUI objects, but there are several external libraries created by labs and individuals around the world with GUI objects. Check the external libraries available here (scroll down to see them).
We will use the Graphic Interface library controlP5. Download the associated .zip file and unzip in a folder called libraries in the folder that contains your project folder. (Your file structure should look like:
- My Documents
- Processing
- MyProject
- MyProject.pde
- libraries
- controlP5
- MyProject
- Processing
All external libraries should be put in this libraries folder)
After you have added this folder, you should be able to open Processing and add the library to your sketch by going to Sketch->Import Library...->controlP5, or typing
import controlP5.*;
at the top of your sketch.
This library comes with some great example sketches that show off the different objects it provides. Look under my docs/Processing/libraries/controlP5/examples for 40 different example sketches demonstrating the use of individual GUI objects.
The following sketch uses a bunch of common GUI objects to demonstrate the library:
// Nick Marchuk // 3/5/2010 // Show off GUI basics with the controlP5 external library // the best help section is at http://www.sojamo.de/libraries/controlP5/reference/index.html?controlP5/package-summary.html, click on index // you must create a "libraries" folder in the folder where you have your processing files (c://...documents/processing) and unzip the controlP5 folder there to access the library import controlP5.*; // controlP5 GUI library ControlP5 controlP5; // create the handler to allow for controlP5 objects // not required to define each gui object you want, but useful for text objects, makes them easier to access to change their text Textlabel myTextlabelA; // create a text label, an area of text independent of anything else Textarea myTextarea; // a multiline textarea with scrollbar ListBox myListbox; // a list that generates an event when you click on an element in the list ControlFont font; // a unique font to be applied to an object int myColorBackground = color(0,0,0); // background color of window public int numberboxValue = 100; // initial value of numberboxValue, will change on the fly as the numberbox is changed (because it has the same name as the numberbox that will be declared) void setup() { size(600,400); controlP5 = new ControlP5(this); // initialize the GUI controls // print all of the possible fonts that you can use, uncomment to print the big list //println(PFont.list()); // create the font for a specific gui object font = new ControlFont(createFont("Arial",20),14); // ControlFont(the font, font size) font.setSmooth(true); // set the font for all of the other gui objects -> this messes up the text entered in the text field, text typed in is correct but only the last character is shown in the field controlP5.setControlFont(new ControlFont(createFont("Comic Sans MS",20), 12)); // ControlFont(the font, font size) // a slider graphically shows its value in relation to its min and max controlP5.addSlider("slider1",100,167,128,100,250,20,100); // slider(name,min,max,default,x,y,width,height) controlP5.addSlider("slider2",0, 255,128,150,250,100,10); // slider(name,min,max,default,x,y,width,height) // a button press will trigger the function with the same name as the button controlP5.addButton("buttonA",0,100,100,80,19); // buton(name,value,x,y,width,height) controlP5.controller("buttonA").setCaptionLabel("Button A"); // change what the button says on it // set the font and font size of the button controlP5.controller("buttonA").captionLabel().setControlFont(font); controlP5.controller("buttonA").captionLabel().setControlFontSize(15); controlP5.controller("buttonA").captionLabel().toUpperCase(false); // declare the text label this way to make it easier to chage the text in it myTextlabelA = controlP5.addTextlabel("textlabelA","hi there",5,5); // textlabel(name,text,x,y) // a bang is a lot like a button, but simpler controlP5.addBang("bang",60,20,45,45).setTriggerEvent(Bang.RELEASE); //addBang(name,x,y,width,height) controlP5.controller("bang").setLabel("a_bang"); // a numberbox is like a slider but without the graphics controlP5.addNumberbox("numberboxValue",128,100,130,100,14); // addNumberbox(name,default,x,y,width,height) // a textarea is a set of text, like a multiline textlabel, can have a scroll bar // define like this to make changes easier myTextarea = controlP5.addTextarea( // addTextarea(name,text,x,y,width,height) "label1textarea", "the first line of text \n"+ // \n is newline, works but controlP5 doesn't particularly like it, spits out a warning in the debug window "another line of text \n"+ "hint for the GUI objects in controlP5: use ALT + mouseDown to move them around the screen if you don't like their placement.", 375,100,200,60); // a textfield is for text entry // after typing an enter the function textValue is triggered, putting the entered text into the Textarea, as set in the function textValue() below controlP5.addTextfield("textValue",100,170,200,20); // addTextfield(name,x,y,width,height) // a listbox is like a dropdown myListbox = controlP5.addListBox("myList",350,200,120,120); //addListBox(name,x,y,width,height) myListbox.captionLabel().toUpperCase(false); // dont force the caption text to be capitalized. Most object have lots of little fns like this, check the examples / online help myListbox.captionLabel().set("Listbox label"); for(int i=0;i<6;i++) { myListbox.addItem("item "+i,i); // addItem(name,value) } } // inifinite loop, doesn't do much because events are triggered when the GUI objects are used, we don't have to check them automatically void draw() { background(myColorBackground); } // run this when slider1 is triggered void slider1(float theColor) { // theColor is the value of the slider when it is triggered myColorBackground = color(theColor); } // run this when slider2 is triggered void slider2(float theValue) { // theValue is the value of the slider when it is triggered myTextlabelA.setValue("slider2 = "+theValue); } // run this when buttonA is triggered public void buttonA(int theValue) { // theValue is the value of the button when it is triggered (not to interesting in the case of a button) myTextlabelA.setValue("button pushed"); } // run this when bang is triggered void bang() { int theColor = (int)random(255); myColorBackground = color(theColor); } // run this when there is an enter in the textfield public void textValue(String theText) { // theText is the what was entered in the textfield myTextarea.setText(theText); } // print the name of the control being triggered, for debugging purposes or to see if the event was from the Listbox public void controlEvent(ControlEvent theEvent) { // ListBox is of type ControlGroup (a group of GUI objects instead of a single object), you need to check its theEvent with if(theEvent.isGroup())to avoid an error message from controlP5 if (theEvent.isGroup()) { // an event from a group if (theEvent.name()=="myList") { println("got myList"+" value = "+theEvent.group().value()); } } else { //println(theEvent.controller().name()); // this is for debugging purposes, see what GUI object was triggered } }
The code above creates the following GUI:
Two notes about these GUI objects:
- Holding the ALT key and clicking on an object allows you to drag it around the screen. If you print the location of the object to the debug window when it is triggered, you can initially place the object anywhere and note the position you like after moving it and changing your code to match
- You can declare your object at the top of your code like "Textarea myTextarea;" and use "myTextarea = controlP5.addTextarea(..." in setup() or directly use "controlP5.addButton("buttonA",0,100,100,80,19);" in setup() to add the object. The former method is better for objects that you want to change dynamically, like the text in a textArea. The later method is good for objects that you will not need to change after they are initialized, like buttons.
A Template for a GUI using Serial Communication in Processing
This section will describe a template for Processing projects that use a GUI interface and serial communication. It is often a challenge to share code between PCs when the serial port is hard coded for one PC because the name and number of comm ports differs on every PC, particularly when the computer has bluetooth. This template attempts to solve this problem by listing all of the available comm ports and allowing the user to select the port they wish to use when the sketch loads. It contains all of the framework necessary to setup the serial communication and use common GUI objects.
// Nick Marchuk // 2/25/2010 // Template for serial communication and a GUI // Puts all available comm ports in a list, initializes the one that is clicked, text entry for output and text area for input // serial variables import processing.serial.*; // serial library Serial[] myPorts = new Serial[1]; // lets only use one port in this sketch // GUI variables import controlP5.*; // controlP5 library ControlP5 controlP5; // create the handler to allow for controlP5 items Textlabel txtlblWhichcom; // text label displaying which comm port is being used Textarea commTextarea; // text area displaying what has been received on the serial port ListBox commListbox; // list of available comm ports // setup void setup() { size(600,400); frameRate(30); controlP5 = new ControlP5(this); // initialize the GUI controls println(Serial.list()); // print the comm ports to the debug window for debugging purposes // make a listbox and populate it with the available comm ports commListbox = controlP5.addListBox("myList",5,25,120,120); //addListBox(name,x,y,width,height) commListbox.captionLabel().toUpperCase(false); commListbox.captionLabel().set("Listbox label"); for(int i=0;i<Serial.list().length;i++) { commListbox.addItem("port: "+Serial.list()[i],i); // addItem(name,value) } // text label for which comm port selected txtlblWhichcom = controlP5.addTextlabel("txtlblWhichcom","No Port Selected",150,25); // textlabel(name,text,x,y) // when triggered, write the text over the serial line controlP5.addTextfield("EnterTextToSend",5,170,200,20); // addTextfield(name,x,y,width,height) // when new info comes into the serial port, write it to this text area commTextarea = controlP5.addTextarea( // addTextarea(name,text,x,y,width,height) "label1textarea", "No data yet...", 225,170,200,30); commTextarea.setColorBackground(0xffff0000); // a button to send the letter a controlP5.addButton("Send_A",1,5,210,80,19); // buton(name,value,x,y,width,height) } // infinite loop void draw() { background(128); } // print the name of the control being triggered (for debugging) and see if it was a Listbox event public void controlEvent(ControlEvent theEvent) { // ListBox is if type ControlGroup, you need to check the Event with if (theEvent.isGroup())to avoid an error message from controlP5 if (theEvent.isGroup()) { // an event from a group if (theEvent.name()=="myList") { InitSerial(theEvent.group().value()); // initialize the serial port selected //println("got myList"+" value = "+theEvent.group().value()); // for debugging } } else { //println(theEvent.controller().name()); // for debugging } } // run this when there is an enter in the textfield public void EnterTextToSend(String theText) { myPorts[0].write(theText+"\n"); // write the text in the field println("sent "+theText); // print it to the debug screen as well } // run this when buttonA is triggered, send an a public void Send_A(int theValue) { myPorts[0].write("a\n"); // write an a println("sent a"); // print it to the debug screen as well } // initialize the serial port selected in the listBox void InitSerial(float portValue) { println("initializing serial " + int(portValue) + " in serial.list()"); // for debugging String portPos = Serial.list()[int(portValue)]; // grab the name of the serial port txtlblWhichcom.setValue("COM Initialized = " + portPos); myPorts[0] = new Serial(this, portPos, 19200); // initialize the port // read bytes into a buffer until you get a linefeed (ASCII 10): myPorts[0].bufferUntil('\n'); println("done init serial"); } // serial event, check which port generated the event // just in case there are more than 1 ports open void serialEvent(Serial thisPort) { // variable to hold the number of the port: int portNumber = -1; // iterate over the list of ports opened, and match the // one that generated this event: for (int p = 0; p < myPorts.length; p++) { if (thisPort == myPorts[p]) { portNumber = p; } } // read the serial buffer until a newline appears String myString = thisPort.readStringUntil('\n'); myString = trim(myString); // ditch the newline println("got: " + myString); // print to debug window commTextarea.setText(myString); // put it in the text area // uncomment the following if you are getting streaming packets of data that need to be parsed /* // if you got any bytes other than the newline if (myString != null) { //myString = trim(myString); // ditch the newline // split the string at the spaces, save as integers int sensors[] = int(split(myString, ' ')); // convert to x and y or whatever if ((sensors.length == 2)&&(portNumber==0)) { // hardcoded portNumber==0 because only using one port in this sketch //float x = sensors[0]/100.0; // whatever conversion you need to do //float y = sensors[1]/100.0; } } */ } // end serialEvent
This sketch creates the following window:
A New Serial Library with Hardware Flow Control for Processing
The Processing serial library does not have an argument to enable Hardware Flow Control, although standard Java does. If you replace the default serial library with a slightly edited one, you can use Hardware Flow Control, and only one line of code changes!
Open your Processing folder (where the Processing executable is), and go to \modes\java\libraries, and delete the serial folder. Replace it with this serial folder: Media:serial.zip
This new serial library behaves exactly the same as the old library, except with an additional argument when opening a port. For example:
myPort = new Serial(this, portName, 1500000, 'N', 8,1,1);
where
- 1500000 is the baud
- 'N' means no parity
- 8 means 8 bit data
- The first 1 means 1 stop bit
- And the last 1 is the new argument - 1 to enable hardware flow control, and 0 to disable
If you open a port without specifying all of the arguments, for example
myPort = new Serial(this, portName, 9600);
Hardware Flow Control will not be used - **Hardware Flow Control is off by default***
You must specify all of the arguments to enable Hardware Flow Control.