Skip to main content

Workshop: Using Arduino with p5.embroider

This is a write up of part of the PEmbroider tech skills workshop explaining how to use inputs from Arduino to alter a PEmbroider sketch. It shows the example of using an LDR to vary the radius of a circle.

There are a few steps to this process:

  1. Circuit
  2. Arduino Code
  3. Processing Code

1. Circuit

First, you need to build your circuit. This is the same as you would build any Arduino circuit and can use anything that will give you a reading such as potentiometer, LDR, button, switch, etc. Below is the circuit for an LDR.

2. Arduino Code

The setup for the arduino code is nice and simple, we just need to read our value then print it to serial. Make sure you are using println() so each value prints on a new line.

void setup() {
  Serial.begin(9600);
}


void loop() {
  int val = analogRead(A0);
  Serial.println(val);
  delay(100); 
}

Once you have uploaded this, make sure that the Serial Monitor is closed. If left open, processing won't be able to read the data. Also take note of the frequency used in Serial.begin() and the COM number when connecting as these both need to be matched in the Processing code.

3. Processingp5 Code

ThereBelow areis a fewtemplate thingsfor thatusing weserial needdata towith set up in this part of the code.p5.embroider.

Import libraries and create objects

// variable to hold an instance of the p5.webserial library:
const serial = new p5.WebSerial();
 
// HTML button object:
let portButton;
let inData;                   // for incoming serial data
let outByte = 0;             // for outgoing data
let vals;
let dataCount = 0;

let _drawMode = "p5";
let stitchWidth;

function setup() {
  createCanvas(400, 400);          // make the canvas

  //Serial setup

  // check to see if serial is available:
  if (!navigator.serial) {
    alert("WebSerial is not supported in this browser. Try Chrome or MS Edge.");
  }
  // if serial is available, add connect/disconnect listeners:
  navigator.serial.addEventListener("connect", portConnect);
  navigator.serial.addEventListener("disconnect", portDisconnect);
  
  serial.getPorts(); // check for any ports that are available:
  serial.on("noport", makePortButton); // if there's no port chosen, choose one:
  serial.on("portavailable", openPort); // open whatever port is available:
  serial.on("requesterror", portError); // handle serial errors:
  serial.on("data", serialEvent);  // handle any incoming serial data:
  serial.on("close", makePortButton);


  //p5.embroider setup

  let drawModeStitchButton = createButton("Draw Mode: Stitch");
  drawModeStitchButton.mousePressed(() => {
    _drawMode = "stitch";
    redraw();
  });

  let drawModeLineButton = createButton("Draw Mode: Realistic");
  drawModeLineButton.mousePressed(() => {
    _drawMode = "realistic";
    redraw();
  });

  let drawModeP5Button = createButton("Draw Mode: p5");
  drawModeP5Button.mousePressed(() => {
    _drawMode = "p5";
    redraw();
  });

  let exportDstButton = createButton("Export DST");
  exportDstButton.mousePressed(() => {
    exportEmbroidery("colorExample.dst");
  });
  exportDstButton.position(0, height + 60);

  let exportGcodeButton = createButton("Export Gcode");
  exportGcodeButton.mousePressed(() => {
    exportGcode("colorExample.gcode");
  });
  exportGcodeButton.position(90, height + 60);
  //noLoop();
}
 
function draw() {
  background(0);
  stitchWidth = 7;
  stroke(255, 255, 255);
  noFill();
  setDrawMode(_drawMode);

  if(typeof(vals[0]) != "undefined" && !isNaN(vals[0])){      
      beginRecord(this);

      strokeWeight(stitchWidth);
      setStitch(0.2, 0.8, 10);
      setStrokeMode("zigzag");

      
      //p5 sketch goes below!!      
      ellipse(pixelToMm(200),pixelToMm(200),abs(vals[0]),abs(vals[1]));
      
      endRecord();
    }
  }


// read any incoming data as a string
// (assumes a newline at the end of it):
function serialEvent() {
  inData = serial.readLine();
  if(inData != null){
    inData = trim(inData);
    vals = int(splitTokens(inData, ",")); 
    
    if(vals.length >= 1){
       value1 = vals[0];
      
    }
  }
}

// if there's no port selected, 
// make a port select button appear:
function makePortButton() {
  // create and position a port chooser button:
  portButton = createButton("choose port");
  portButton.position(10, 10);
  // give the port button a mousepressed handler:
  portButton.mousePressed(choosePort);
}
 
// make the port selector window appear:
function choosePort() {
  if (portButton) portButton.show();
  serial.requestPort();
}
 
// open the selected port, and make the port 
// button invisible:
function openPort() {
  // wait for the serial.open promise to return,
  // then call the initiateSerial function
  serial.open().then(initiateSerial);
 
  // once the port opens, let the user know:
  function initiateSerial() {
    console.log("port open");
  }
  // hide the port button once a port is chosen:
  if (portButton) portButton.hide();
}
 
// pop up an alert if there's a port error:
function portError(err) {
  alert("Serial port error: " + err);
}
 
// try to connect if a new serial port 
// gets added (i.e. plugged in via USB):
function portConnect() {
  console.log("port connected");
  serial.getPorts();
}
 
// if a port is disconnected:
function portDisconnect() {
  serial.close();
  console.log("port disconnected");
}
 
function closePort() {
  serial.close();
}

First, we need to import the correct libraries and create our Serial object, a variable to store the value from the Arduino (which will be a string), and a file number variable (which we will use later).

import processing.serial.*;
import processing.embroider.*;
PEmbroiderGraphics E;

Serial myPort;  // Create object from Serial class
String val = "0"; //Create variable to hold value from Arduino
int fileNumber = 0;

Setup

Within the setup() function, we will find and connect to the correct serial port.

  println(Serial.list());
  String portName = Serial.list()[0]; //change the 0 to match your port
  
  myPort = new Serial(this, portName, 9600); //this freq value must match the arduinos

The first line here will print all the available ports. Using this, you can then select the same one that your Arduino is connected to by changing the number in the square brackets on the second line. Also, make sure that the frequency value matches the Arduino's Serial port.

Draw

Inside the draw() function we will then read the data from the Arduino.

if(myPort.available() > 0){  
 val = myPort.readStringUntil('\n'); 
}

This can then be used as a variable within our sketch. For example, as the radius of a circle.

  float r = map(float(val),0,800,0,300);
  E.circle(width/2,height/2,r);

The value is read as a string so we have to cast it to float in order to use it. The map() function is used to take the values given from the Arduino and map them to the range of values you want to be used in the sketch.

Write out file

This sketch is currently not actually producing an embroidery file so we need to add a trigger that will cause the file to write out. You could use a variety of inputs both from the Arduino or computer to do this. Below, we are using the space bar.

void keyPressed() {
  // Export the embroidery file when we press the space bar. 
  E.optimize(); //important to include - optimizes the embroidery paths
  if (key == ' ') {
    String outputFilePath = sketchPath("PEmbroider_arduino_demo_" + fileNumber + ".pes");
    save("PEmbroider_arduino_demo_" + fileNumber + ".pes");
    E.setPath(outputFilePath); 
    E.endDraw(); // write out the file
    fileNumber += 1;
  }
}

Here we are using the fileNumber variable we created before so that each time we write out a file, it has a new name and won't overwrite previous one.

Full Code

So, for this sketch, the full code would be as below:

import processing.serial.*;
import processing.embroider.*;
PEmbroiderGraphics E;

Serial myPort;  // Create object from Serial class
String val = "0";
int fileNumber = 0;

void setup() {
  size(600, 600); 
  E = new PEmbroiderGraphics(this, width, height);
 
  
  println(Serial.list());
  String portName = Serial.list()[6]; //change the 0 to a 1 or 2 etc. to match your port
 
  
  myPort = new Serial(this, portName, 9600);
  
}

void draw(){
  background(200);
  E.beginDraw(); 
  E.clear();
  E.noFill();
  
  // If data is available, read and store in val
  if ( myPort.available() > 0) {  
    val = myPort.readStringUntil('\n'); 
  }
  
  
  float r = map(float(val),0,800,0,300);
  E.circle(width/2,height/2,r);
  

  //----------  
  E.visualize(true, true, false);   // Display the embroidery path on-screen.

}

void keyPressed() {
  // Export the embroidery file when we press the space bar. 
  E.optimize(); //important to include - optimizes the embroidery paths
  if (key == ' ') {
    String outputFilePath = sketchPath("PEmbroider_arduino_demo_" + fileNumber + ".pes");
    save("PEmbroider_arduino_demo_" + fileNumber + ".pes");
    E.setPath(outputFilePath); 
    E.endDraw(); // write out the file
    fileNumber += 1;
  }
}

Importing Multiple Readings

If you have multiple readings from the Arduino, there are a few ways you could get all the data over to Processing. In this example, we combine all readings into one variable which is sent across then split apart within Processing.

Arduino Code

To send these values as one line, we print each one, except the last, with Serial.print() followed by a comma. The last value is printed with Serial.println() to start a new line for the next reading.

  int val1 = analogRead(A0);
  int val2 = analogRead(A1);

  Serial.print(val1); 
  Serial.print(",");
  Serial.println(val2); 

p5 Code

Once this string is received in Processing, we can split it apart to get each value.

int val1 = int(val.substring(0,4));
int val2 = int(val.substring(4,8));