javascriptserial-portp5.jsarduino-unoarduino-ide

Mapping P5.js to Arduino potentiometers and push buttons


I have a school project that I need help with. I am trying to create an etch-a-sketch with p5.js and an Arduino. I have two potentiometers hooked up and one button. The potentiometers should control the lives on the X and Y axis and the button should clear the drawing.

I have installed node.js and the serial port successfully. I also wrote the Arduino IDE so that it is able to read the values of the potentiometers when they are rotated. These values show up both in the Arduino IDE and the P5.js sketch.

However, when I push the button, nothing happens. When I run the p5.js sketch, a small dot appears where that is supposed to represent the beginning of the line. However, after a few seconds it flies off of the page and disappears.

I don't think I am understanding how to map the code to the controller properly. The p5.js Sketch code is below. I added a photo of the Arduino IDE code that I am using. I also added photos of how my Arduino is connected

P5.js Sketch Code:

let prevX, prevY;
//let isCleared = false;
let serialPortName = '/dev/tty.usbserial-1130';
let sensors = [200,200,0]

function setup(){
  createCanvas(400,400);
  serial = new p5.SerialPort();
  serial.list();
  serial.on('open', onOpen);
  serial.on('BtnData', BtnData);
  prevX = width/2;
  prevY = height/2;
  
}

function draw(){
  if(isCleared == true);{
  background(250);
  isCleared = false;
  }
  stroke(0);
  strokeWeight(5);
  line(prevX, prevY, sensors[0], sensors[1]);
  //prevX = sensors[0];
  //prevY = sensors[1];
}

function onOpen(){
  print('serial monitor opened.'); 
}

function onData(){
  let data = serial.readLine();
  let sensors = data.split(",");
  prevX = sensors[0];
  prevY = sensors[1];
  prevX = map(prevX, 0, 1023, 0,511);
  prevY = map(prevY, 0, 1023, 0, 511);
  print(data);
}

function BtnData(){
  let currentString = serial.readLine();
  trim(currentString);
  
  if(!currentString)return;
  console.log(currentString);
  latestData = currentString;
  
  
//}

//}
//function mousePressed(){
 // isCleared = true;
}

Link to the p5.js Sketch https://editor.p5js.org/KelseyV/sketches/asGl0G0MC

Arduino Etch-A-Sketch Code:

const int switchPin = 4;
const int potPinX = A0; // pin the port is connected to
const int potPinY = A1;

//int switchVal = 0;
int potValX = 0; // variable to hold the value from the sensor
int potValY = 0 ;
int count = 0:

void setup(){
    pinMode(LED_BUILTIN, OUTPUT) ;
    pinMode(switchPin, INPUT);
    Serial.begin(9600);
  // pinMode(switchPin, INPUT);

  // establishContact();
}

void loop(){
    int switchVal = digitalRead(switchPin);
    int potValX = analogRead(potPinX) ;
    int potValY = analogRead(potPinY);
    
    Serial.print (count);
    Serial.print(",");
    Serial.print (potValX);
    Serial.print(",");
    Serial.println(potValY);
    //Serial.print(",");

    delay(1000);
 }
//}

Photos of Arduino Connections

top view of Arduino Uno front view of bread board back view of bread board

I would like to be able to control the p5.js sketch with the arduino so that the two potentiometers draw a line on the X and Y axis and the button clears the drawing when pressed.


Solution

  • That is a nicely written question:

    The following are hopefully helpul tips on how to improve your code and general tips to follow beyond this question.

    You're tackling two problem at once:

    It's a good idea to split a bigger problem into smaller problems that can be tackled individually much more easily. (I recommend Kevin Workman's tutorial for more info).

    Let's start with the Arduino code:

    1. it's a good idea to cleanup code: remove commented code or any unused variables. This will make it easier to read/understand/fix. The less mental load the better so you can use your resources for more challenging problems
    2. You're declaring some variables (such as potValX, potValY) twice, but in different scopes (first as global variables then as as local to the loop()). This might be an oversight, but the danger here is variable shadowing where you might want to use the global variables somewhere else other than loop() in your code expecting them to be updated in loop(), however the local variables only will be updated, not the global ones.
    3. count is declared, but never updated so Serial.print (count); will always print 0. This is an oversight/bug as this is not the button state and your sketch won't clear.

    Regarding the button I recommend paying attention to the values output in Serial Monitor first to double check the expected value is printed. For example, some Arduino Uno pins have internal pull up resistors you can use. See the Arduino digital input pullup tutorial for more info. Check if you get a 1 or 0 when the button is pressed and code the rest of your program accordingly.

    Again, on splitting the problem you can start with simpler sketches you can later merge into one. For example:

    1. a sketch that simply reads the button digital pin and outputs the value you need over Serial. You can test pullup (or look at debouncing if you want to only send a button state when the state changes).
    2. a sketch that reads and reliably sends potentiometer data, then two potentiometers
    3. a sketch that merges the button and pot data into one

    The idea is to thoroughly test each program and ensure it's bug-free before moving to the next task. This way you can rely on past code with less worries. Additionally, you may encounter unexpected behaviour when merging code and adding one new thing that breaks will be easier to isolate/debug/fix than adding multiple things in one go. Sometimes going slow will get you the results faster than rushing.

    Here is a tweaked version of your Arduino code with some of the notes above:

    const int switchPin = 4;
    const int potPinX = A0; // pin the port is connected to
    const int potPinY = A1;
    
    int switchVal = 0;
    int potValX = 0; // variable to hold the value from the sensor
    int potValY = 0 ;
    
    void setup(){
        pinMode(LED_BUILTIN, OUTPUT) ;
        pinMode(switchPin, INPUT);
        Serial.begin(9600);
    }
    
    void loop(){
        switchVal = digitalRead(switchPin);
        potValX = analogRead(potPinX) ;
        potValY = analogRead(potPinY);
        
        Serial.print (switchVal);
        Serial.print(",");
        Serial.print (potValX);
        Serial.print(",");
        Serial.println(potValY);
    
        delay(1000);
     }
    

    (Close the p5.serialserver first if you want to use Serial Monitor. You can have only one connection to a serial port at a time and if p5.serialserver uses it you'll get a Port Busy error in Arduino Serial Monitor (and vice-versa)).

    This should output the button state (1 or 0) and the pot values separated by commas one time per second. This may be what you want or you may want to do something like:

    Moving on to the p5.js code:

    Here's an updated version of your p5.js sketch using the above notes:

    let serialPortName = '/dev/tty.usbserial-1130';
    
    // line drawing vars driven by pot. data
    let prevX, prevY;
    // button data to clear the drawing
    let isButtonPressed = false;
    let wasButtonPressed = false;
    
    
    function setup(){
      createCanvas(400,400);
      // setup serial
      serial = new p5.SerialPort();
      serial.list();
      serial.on('open', onOpen);
      serial.on('data', onData);
      // also handle other events
      serial.on('connected', serverConnected); // callback for connecting to the server
      serial.on('open', portOpen); // callback for the port opening
      serial.on('data', onData); // callback for when new data arrives
      serial.on('error', serialError); // callback for errors
      serial.on('close', portClose); // callback for the port closing
    
      serial.open(portName); // open a serial port
      
      prevX = width/2;
      prevY = height/2;
      currX = width/2;
      prevY = height/2;
    
      // if drawing styles don't change run once in setup()
      stroke(0);
      strokeWeight(5);
    }
    
    function draw(){
     // not much here since we're only drawing/clearing when there's new serial data 
    }
    
    // serial events
    function serverConnected() {
      console.log('connected to server.');
      // if this doesn't fire probably p5.serialserver isn't running this could handled
    }
    
    function portOpen() {
      console.log('the serial port opened.')
      // good news: can expect data now
    }
    
    function serialError(err) {
      console.log('Something went wrong with the serial port. ' + err);
      // can provide mode helpful error data (e.g. depending on `err` value add suggestions for port busy vs port not found, etc.)
    }
    
    function portClose() {
      console.log('The serial port closed.');
    }
    
    function onOpen(){
      print('serial monitor opened.'); 
    }
    
    function onData(){
      let currentString = serial.readLine();
      currentString = trim(currentString);
      
      if(!currentString)return;
    
      let data = int(currentString);
      
      updateDrawingFromSerial(data);
    }
    
    function updateDrawingFromSerial(data){
      //basic input validation
      if(data.length < 3) return; // we expect at least 3 values
      // this can be futher improved by checking if each value is a Number
      // ...and if so, if each value is in the expected range (otherwise can use constrain())
    
      // handle button
      clearOnButton(data[0]);
    
      // handle potentiometers
      drawLineOnPots(data[1], data[2]);
    }
    
    function clearOnButton(buttonNewState){
      isButtonPressed = buttonNewState;
      // debounce
      if(isButtonPressed && !wasButtonPressed){
        background(255);
        wasButtonPressed = true;
      }
      if(!isButtonPressed && wasButtonPressed){
        wasButtonPressed = false;
      }
    }
    
    function drawLineOnPots(potX, potY){
      // remap to sketch size
      let currentX = map(potX, 0, 1023, 0, width);
      let currentY = map(potY, 0, 1023, 0, height);
      
      // draw line
      line(prevX, prevY, currentX, currentY);
    
      // update previous values (after drawing line)
      prevX = currentX;
      prevY = currentY;
    }
    

    Note that I've split the string parsing into multiple functions. While this may make the code slightly more verbose, it encapsulates each functionality which makes it easier to debug fix (since only small bits can go wrong instead one bug chunky function) and parts of the code can potentially be re-used in new sketches.

    Closing notes: