1

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.

4
  • 1
    A picture may be worth a thousand words, but a schematic conveys magnitudes more information than several photos of a breadboard & Arduino. A compressed and unreadable screenshot is practically worth nothing. You neglect to provide any description of the implementation, i.e. the circuit, what data is collected, how data is converted/formatted and sent from the Arduino to a mystery system. There's minimal indication of subsystem testing/debugging. You could easily have not one but multiple issues that need resolving. Commented Apr 10, 2024 at 5:42
  • "the button should clear the drawing" - If there was a review of this design "feature*", I would veto this idea and sent it back for a re-do. Commented Apr 10, 2024 at 5:49
  • Have you considered a 'clear' button on your p5 canvas? Commented Apr 10, 2024 at 16:13
  • Thank you for your suggestion! So today I made some headway. I tried to add a clear button on the p5 canvas. //function buttonPressed(){ // if (sensors[2] == 1){ // isCleared = true; Commented Apr 10, 2024 at 18:14

1 Answer 1

1

That is a nicely written question:

  • clear statement of what the goal is, what was attempted / what the expected behaviour is and what is actually happening
  • images of circuit, etc.

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:

  • the Arduino sketch/firmware
  • the p5.js code to read/parse the serial data to draw

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:

  • debounce the button pin and only send a button command (e.g. B,0\n or B,1\n when the button state changes)
  • stream pot data faster (e.g. S1023,512\n, etc.)
  • the idea is you'd use 1st character to tell a B for button command apart from S for slider command. This adds complexity to your code and depending on your constraints (e.g. deadlines/etc.) this may not be the time to explore this avenue (yet).

Moving on to the p5.js code:

  • a cleanup is a great start
  • isCleared is commented out then later used. This will create unexpected behaviour
  • if(isCleared == true);{
  • the ; might be accidental: it should be removed
  • because let isCleared is commented, isCleared will be undefined (which may evaluate to false in when using ==) so the code inside this condition may never be reached. It appears you want to use isCleared to debounce the button in p5 to only clear once.
  • serial.on('BtnData', BtnData); the BtnData doesn't exist with p5.serialport. Perhaps you meant serial.on('data', onData); ? (you only have one event that returns new serial data so you may need to parse the line once and then split the data in 3 parts instead of 2 (e.g. button,potentiometer1, potentiometer2)
  • serialPortName is declared but never used. perhaps you meant to open the port first: serial.openPort(serialPortName); (otherwise you'd get no serial data). It's a good idea to wrap that in a try/catch and handle common errors. (e.g. someone forgets to attach the arduino via USB therefore handle port not found, someone else forgets to closer Serial Monitor, therefore handle port busy, etc.)
  • let sensors = [200,200,0] suggest data in potentiometer1, potentiometer2, button order when your Arduino is writes button first then the two potentiometers
  • if you're only clearing the data on button press you can draw the line only when there's new data (to avoid redrawing the same line again and again). (not a huge problem, but may produce minor visual artefacts over time (due to overlapping lines))
  • the int() function in p5.js conveniently can also take in array of strings (with numerical values), convert each one to int and return the array. This can be useful when parsing.
  • trimming the input string and early exiting if there is no data are good practices: continue doing that.

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:

  • double check p5.serialserver uses the same baud rate as your Arduino sketch (9600 in this case).
  • if you need to send data faster that will also increase the odds of errors with serial communication, especially when parsing longer strings. Instead of sending 1023 when it gets remapped to 512 for example you can remap from Arduino and maximum value ('512') will only use 3 characters max instead of 4. If you don't need the full 10-bit precision (0-1024) and can get away with 8-bit precision (0-255) you can probably get away with sending just 3 bytes (button, x, y) (using readBytes()), or 4 bytes if for example you limit x,y to 0-254 value and use 255 as your terminator character (using readBytesUntil()). Currently you can have a message as long as 1,1023,1023\n (12 characters/bytes long). This is again may be more complex, however if you have the time, getting experience working with bytes is well worth the time and effort to improve your skills.
  • The caveat with the above is: don't optimise early/unless you really need to. If the reading 12 characters once a second is good enough for the sketch (you might be creatively want a sketch that has an old-school feel mimicking a retro computer somwhat), then don't sacrifice simplicity / code readablity (and therefore maintainability).
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.