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:
- 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
- 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.
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:
- 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).
- a sketch that reads and reliably sends potentiometer data, then two potentiometers
- 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).