This is an example of communicating between processing and an Arduino:-
/**
* Written for Processing V4.x
* On mouse click
* if the mouse is over a blue rectangle
* write the rectangle, number, and an angle number
* as a message to the serial port of an Arduino
* For this demo code I show the control of 16 servos
*
* This is demo code showing how to send multi parameter integer strings
* from Processing to an Arduino. It is encouraged that you write your own code
* to suit your own needs.
*
* By Mike Cook April 2023 - code is in the public domain
*/
import controlP5.*;
ControlP5 cp5; // for the knob
ControlP5 cp6; // for the bangs
Knob angle; // the name of the Knob control to use
import processing.serial.*;
Serial port; // Create object from Serial class
String connectTo = "/dev/cu.usbmodem14201"; // the name of the serial port to use
Serial[] myPorts = new Serial[2]; // setup a list of ports Processing can see
int myColorBackground = color(0,0,0);
int knobValue = 90; // initial value
int maxAngle = 180; // change as required
int minAngle = 0; // change as required
void setup() {
size(300, 180);
smooth();
noStroke();
cp5 = new ControlP5(this);
angle = cp5.addKnob("angle")
.setColorLabel(0)
.setRange(minAngle,maxAngle)
.setValue(knobValue)
.setPosition(180,35)
.setRadius(50) // size of the knob
//.snapToTickMarks(true) // uncomment for snap
.setNumberOfTickMarks(abs(maxAngle - minAngle) / 10) // just put a number in here if you are happy with that.
.setTickMarkLength(4)
.setColorForeground(color(255))
.setColorBackground(color(0, 160, 100))
.setColorActive(color(255,255,0))
.setDragDirection(Knob.VERTICAL) // direction for click + drag to change reading
;
int bangID = 0;
cp6 = new ControlP5(this);
for(int j=0; j<4; j++){
for (int i=0; i<4; i++) {
bangID = (j * 4) + i;
cp6.addBang("s"+bangID)
.setPosition(10+i*40, (40 * j) + 10)
.setSize(25, 25) // size of the bang controls
.setId(bangID)
;
}
}
// try and open the port named in the "connectTo" variable
int portNumber = 99;
String [] ports;
printArray(Serial.list()); // comment out to skip showing list of all serial devices
ports = Serial.list();
for(int j = 0; j< ports.length; j++) { // go through all ports looking for ours
if(connectTo.equals(Serial.list()[j])) portNumber = j;
}
if(portNumber == 99) portNumber = 0; // if we haven't found our port then connect to the first one
String portName = Serial.list()[portNumber];
println("Connected to "+portName);
port = new Serial(this, portName, 57600); // must be the same as the receiving processor
port.bufferUntil(13); // call serialEvent every Carriage Return 'CR'
}
void draw() {
background(128);
}
void angle(int theValue){
//println("an angle event "+theValue);
knobValue = theValue;
}
public void controlEvent(ControlEvent theEvent) {
//print("the event is "+theEvent);
if(theEvent.getController().getName().equals("angle") ){
return; // exit as we don't want to respond to knob events
}
// as there are only two controllers generating events so this must be a bang event
// look to see which one generated the event
for (int i=0; i< 16; i++) {
if (theEvent.getController().getName().equals("s"+i)) {
sendMessage(theEvent.getController().getId());
}
}
}
void sendMessage(int number){
port.write(str(number)); // Servo number (bang event)
// repeat these next two lines for sending more data in the string.
port.write(0x2C); // ASCII for comma ","
port.write(str(knobValue)); // servo position
// now end it with this
port.write(0); // end of string marker
port.write(13); // end of message marker
}
// look for stuff from the Arduino so we can print it
void serialEvent(Serial port) { // this gets called when something is received on the serial port
String received = port.readString() ;
print(received + " -->from Arduino ");
}
The Arduino code for this example:-
/*
* Sketch to get multi parameter messages from Processing
* Do not open any serial or plotting monitors in the Arduino IDE
* Any Serial.print messages will be sent to the Processing console
*
* The number of variables sent, should match the numbers expected here.
*
* the code here is for servos attached to the Arduino board.
* A Uno can only handle 12 servos so only S0 to S11 will work from processing
* do not attempt to power any servos direct from the 5V pin of the Arduino
* you need an external power supply capable of about 1A per servo.
*
* by Mike Cook April 2023
* In the public domain
*/
#include <Servo.h>
Servo myservo[12]; // create servo object to control a servo
int initVal = 90; // initial angle to set the servos
byte servoPin[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; // define what pins the servos are on
const int numberOfPrams = 2 ; // number of int values in one message
int messagePrams [numberOfPrams]; // place to hold values in the message
const int bufferSize = 20;
char incoming[bufferSize]; // buffer to hold message
void setup() {
for(int i =0; i<12; i++){
myservo[i].attach(servoPin[i]); // attaches the servo on pin 2 to the servo object
myservo[i].write(initVal); // positions the servos to the same point
// consider inserting a delay here if your power supply has trouble supplying enough curret
}
Serial.begin(57600); // to match the processing sketch
delay(100); // let the serial port get up and running
}
void loop() {
if(Serial.available() != 0) { // got something to read
Serial.readBytesUntil(13, incoming, bufferSize);
// echo message to the processing console when uncommented
Serial.print("received message from Processing ");
Serial.println(incoming);
parseData();
showParsedData(); // comment out to hide what was extracted from the message
// now do something with the received parameter values.
driveServos();
}
}
void parseData() {
// split the data into its parts
char * strtokIndx; // this is used by strtok() as an index
strtokIndx = strtok(incoming,","); // get the first part - the string
strcpy(incoming, strtokIndx); // copy data from processing
messagePrams[0] = atoi(strtokIndx);
for(int i = 1; i < numberOfPrams ; i++){ // read the rest of the numbers in the message
strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
messagePrams[i] = atoi(strtokIndx); // convert this part to an integer
}
/*
// When sending float values as well they must be after the int values
strtokIndx = strtok(NULL, ",");
floatMessagePrams[0] = atof(strtokIndx); // convert this part to a float
*/
}
void showParsedData() { // echo the results back to Processing
for(int i = 0; i < numberOfPrams ; i++){
Serial.print("Parameter ");
Serial.print(i);
Serial.print(" value is ");
Serial.println( messagePrams[i] );
}
}
void driveServos(){
// the code here is for servos attached to the Arduino board.
// do not attempt to power any servos direct from the 5V pin of the Arduino
// you need an external power supply capable of about 1A per servo.
// you can fit less than 16 servos if you like
if( messagePrams[0] < 12){ // if less than the number of servos fitted
myservo[messagePrams[0]].write(messagePrams[1]);
}
}
This requires some explanation:-
Using the Processing Language is a great way of providing a custom graphics interface to your Arduino project, while not requiring the complex code, limited display size and large memory, of providing this on the Arduino itself. The Processing language version used here is Version 4.x. Earlier versions might not work.
While there is an example of simple single byte transfer shown in the examples folder of Processing, this is of limited use, and tends to lead a beginner down the wrong path. Used as a basis of real world control requires more than this because of two problems.
- The first in splitting numbers into individual byte.
- Ensuring these individual bytes are synchronised in the receiving Arduino.
The way round this is to send a whole multivariable message as a comer separated list in one string message. This does require the receiving Arduino has then to parse this list to recover the variables. This tutorial shows you how to do this.
Needless to say the list of variables sent by Processing must match the list variables the Arduino is expecting.
Two way Communication
One of the problems with the conventional use of the serial monitor to see what is going on in your Arduino during development, is you can't use the Serial.print commands to debug you code on the Arduino. This is because Processing is busy using the port to send messages. But by using this code there is no need to use the normal serial monitor. This is because any print statement from the Arduino is displayed on Processing's console window.
Most Processing examples involving serial communications start with saying something like "I know on my system that the port I am after is always on the the first port." This is lazy and will not work in most real installations where there can be many devices connected to the computer, and where those devices change over time. Especially when you use different Arduinos or alternate USB connection leads, like an RS232 USB cable.
The solution to this problem is to know the name of the serial port you wish to connect with, rather than its position in a list. and to search for this port. If it is not found then the first port is chosen, which when using a Mac is the wrong option. If you don't know the names of the port you are looking for you can copy it from the list of ports shown when this code starts up.
**About Processing **
Processing is a language based on Java, both use a C like syntax, so superficially it looks like the C you use to program the Arduino. However, the structure of the code is different. It has the setup function just like the Arduino, but the loop function is replaced by a 'draw' function. This is executed at the beginning of a frame of the display. You can set this to how many frames a second you want and then if the drawing of the screen takes too long it drops to the frames per second it can get.
There are a number of on screen controls that can be updated automatically without any need to anything in the "draw" function. This is basically how this demo code works. There are two types of control here. One is a "knob", a click + drag action in the vertical direction changes the display on the screen. The other type of control here uses the "bang" control, that generates an event when clicked on. There is a 4 by 4 matrix of these "bang" buttons used here.
Other sorts of controllers with an example of their use can be found in the examples window under the menu path "Contributed Libraries --> ControlP5 --> controllers. These include buttons, radio buttons, check boxes, colorPicker, dropdown lists, and sliders.
These controls need initialising in the setup function with all their options specified. They also require a function with the same name as the controller where the values of the controller can be extracted when ever one changes.
About Processing code in this demonstration
This follows the normal layout of a C program, with the 'import' command replacing the #include used in the Arduino along with any variables required for defining the things in the 'import' command.
This is followed by the global variables definitions, and then onto the 'setup' function.
This defines the size of the window on the screen, the command to use anti-aliased when drawing, and the command not to draw an outline to graphics.
Then we move onto the defining of the 'Knob' controller with all its options. Then we do the same thing for the bang controllers. This is done with a pair of nested 'for' loops. Each controller has an ID number associated with it as well as a name. We use this ID number to determine what servo number is sent to the Arduino.
Next we try and connected to our specified serial port name. What ever the outcome the chosen connection is displayed in Processing's console window. It is important to have the same baud rate specified here as you set in the Arduino's 'Serial.began() call.
The 'draw' function just draws the background, but it is important to call it as all the control functions are triggered from this.
The 'bang' controller needs to use the 'controlEvent' function, but 'Knob' events will also be passed to this function, and they are not needed here. So at the start of the 'controlEvent' function, we check for the 'Knob' event and simply return. Then the 'bang' controller names are checked, one at a time, in a 'for' loop and when we find the one that caused the event we pass the ID number of that 'bang' to a function that sends out message to the Arduino.
Finally there is a function that is called automatically when a "Carriage Return" byte is received from the Arduino. That means the message from the Arduino is only seen when a 'Serial.printnl(' is sent.
Customising
The initial value the 'Knob' is set to is 90˚. This matches the initial position set by the Arduino code. This can be changed by altering this line:-
int knobValue = 90; // initial value
The Arduino documentation recommends to use only values from 0 to 180˚. Many Servos will do more than this. So you can change this line to suit your needs.
int maxAngle = 180; // change as required
Also some servos require negative angles, again if this is the case or you want to restrict the angle so as not to go to 0˚, you can change this line
int minAngle = 0; // change as required
You can customise other things as well like the colour of the controls and how it behaves by changing the code in the 'setup' function.
The Arduino Code
The code in this example for the Arduino is for servos attached to the Arduino board using the Servo.h library. The number of servos you can control with this library is dependent on what sort of Arduino you have. The Arduino Uno is "only" capable of generating signals to control up to 12 servos, this changes to 48 on the Arduino Mega. While On the Arduino Due you can control up to 60 servos.
Do not attempt to power any servos direct from the 5V pin of the Arduino. You need an external power supply, with its ground (or negative output) connected to the Arduino ground. This should be capable of supplying about 1A per servo to handle the surge currents from the motors. However, this can be reduced because the Processing code only moves one servo at a time. You can fit less than 16 servos if you like.
In the code example the Servos are connected to pins:-
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
These correspond to S0 -> S11 in the graphic 'bang' controls in the Processing code.
The code uses string and not String variables (capital S) these are two different things and in an Arduino environment the use of 'String' can cause memory problems. So this code uses the:-
Serial.readBytesUntil(13, incoming, bufferSize);
method to read in strings with the CR (13) control being used to indicate the end of a message. Many beginners use the:-
Serial.readString(); method but this is a big mistake as it first of all creates a 'String' variable and second that it takes at least a second to read in because it waits until the serial port 'times out', which by default is a second and results in very slow communication.
It then sends the received message back to Processing for you to check during the debugging phase. Normally you would comment these two lines out once the messaging is working. It then uses the 'parseData' function to split each comer separated data out of the message and convert it into an integer and store it in the 'messagePrams' array.
Then, again for development, the extracted parameters are sent back to processing with the 'showParsedData' function. Again you might want to comment out this call once development is completed.
I have not shown how to handle floating point numbers, because basically most physical applications in the Arduino do not need them. When the do then they only require a limited number of decimal points. This is best done my multiplying the parameter you send from Processing, by say 100, and then dividing it by 100 when you extract it from the 'messagePrams' array and into the float value you want to use.
Finally, in the 'loop' function, a call is made to 'driveServos()' function. This simply checks to see if you are trying to drive a servo greater than the number you set (in this case 12) and the writes the angle to the servo.