Production Week 11

For this assignment, I worked with Bigo to connect p5.js with an Arduino. We completed three exercises to practice sending data back and forth between the physical and digital worlds.

Part 1: One Sensor to p5.js

In the first exercise, we used a potentiometer connected to the Arduino. This sensor controlled the horizontal position of a circle on the computer screen. The Arduino read the potentiometer value and sent it to p5.js, which then moved the circle left or right based on that input.

Schematic

Arduino Code

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

void loop() {
  // Read analog value and send it as a line of text
  int sensorValue = analogRead(A0);
  Serial.println(sensorValue);
  delay(20); 
}

p5.js Code

let port;
let connectBtn;
let ballX = 0;
let sensorVal = 0;

function setup() {
  createCanvas(600, 400);
  background(50);

  port = createSerial();

  // Open the port automatically if used before
  let usedPorts = usedSerialPorts();
  if (usedPorts.length > 0) {
    port.open(usedPorts[0], 9600);
  }

  connectBtn = createButton('Connect to Arduino');
  connectBtn.position(10, 10);
  connectBtn.mousePressed(connectBtnClick);
}

function draw() {
  // Check if port is open
  if (port.available() > 0) {
    let data = port.readUntil("\n");
    
    if (data.length > 0) {
      // Update value
      sensorVal = Number(data.trim()); 
    }
  }

  background(256);
  
  // Map sensor val to canvas width
  ballX = map(sensorVal, 0, 1023, 25, width - 25);
  
  // Draw ball
  fill(0, 255, 100);
  noStroke();
  ellipse(ballX, height / 2, 50, 50);
}

function connectBtnClick() {
  if (!port.opened()) {
    port.open('Arduino', 9600);
  } else {
    port.close();
  }
}

Part 2: p5.js to LED Brightness

For the second part, we reversed the direction of the data. Instead of sending information from the Arduino to p5.js, we sent it from p5.js back to the Arduino. The ball on the screen acted like a virtual light bulb; dragging it upward made the physical LED brighten, while dragging it downward caused the LED to dim.

Arduino Code

C++

void setup() {
  Serial.begin(9600);
  pinMode(9, OUTPUT); //pmw pin
}

void loop() {
  if (Serial.available() > 0) {
    String input = Serial.readStringUntil('\n');
    int brightness = input.toInt();    // convert str to int
    brightness = constrain(brightness, 0, 255);    // just in case data is weird
    analogWrite(9, brightness);
  }
}

p5.js Code

JavaScript

let port;
let connectBtn;

// Ball variables
let ballX = 300;
let ballY = 200;
let ballSize = 50;
let isDragging = false; 

// Data variables
let brightness = 0;
let lastSent = -1; 

function setup() {
  createCanvas(600, 400);
  
  port = createSerial();
  
  let usedPorts = usedSerialPorts();
  if (usedPorts.length > 0) {
    port.open(usedPorts[0], 9600);
  }

  connectBtn = createButton('Connect to Arduino');
  connectBtn.position(10, 10);
  connectBtn.mousePressed(connectBtnClick);
}

function draw() {
  background(50);

  // ball logic
  if (isDragging) {
    ballX = mouseX;
    ballY = mouseY;
    
    // Keep ball inside canvas
    ballY = constrain(ballY, 0, height);
    ballX = constrain(ballX, 0, width);
  }

  // map brightness to y pos
  brightness = floor(map(ballY, 0, height, 255, 0));

  // send data
  if (port.opened() && brightness !== lastSent) {
    port.write(String(brightness) + "\n");
    lastSent = brightness;
  }
  //draw ball
  noStroke();
  fill(brightness, brightness, 0); 
  ellipse(ballX, ballY, ballSize);
  stroke(255);
  line(ballX, 0, ballX, ballY);

}

// --- MOUSE INTERACTION FUNCTIONS ---
function mousePressed() {
  // check if mouse is inside the ball
  let d = dist(mouseX, mouseY, ballX, ballY);
  if (d < ballSize / 2) {
    isDragging = true;
  }
}

function mouseReleased() {
  // stop dragging when mouse is let go
  isDragging = false;
}

function connectBtnClick() {
  if (!port.opened()) {
    port.open('Arduino', 9600);
  } else {
    port.close();
  }
}

Part 3: Gravity Wind and Bi-directional Communication

For the final exercise, we brought all the concepts together using the gravity-and-wind example. We modified the code to add two new features.

First, a potentiometer was used to control the wind speed in real-time. Second, we programmed the Arduino so that whenever the ball hit the ground, an LED would turn on.

This part took some troubleshooting. I had to filter out very small bounces because the LED kept flickering while the ball rolled along the floor. Once that was fixed, I also added a visual arrow on the screen to show the current wind direction and intensity.

Arduino Code

void setup() {
  Serial.begin(9600);
  pinMode(9, OUTPUT); // LED on Pin 9
}

void loop() {
  // read & send pot value
  int potValue = analogRead(A0);
  Serial.println(potValue);


  if (Serial.available() > 0) {
    char inChar = Serial.read();
    
    // check for blink command
    if (inChar == 'B') {
      digitalWrite(9, HIGH);
      delay(50); 
      digitalWrite(9, LOW);
    }
  }
  
  delay(15);
}

p5.js Code

let port;
let connectBtn;

let velocity;
let gravity;
let position;
let acceleration;
let wind;
let drag = 0.99;
let mass = 50;
let sensorVal = 512; 

function setup() {
  createCanvas(640, 360);
  noFill();
  
  // Physics Setup
  position = createVector(width/2, 0);
  velocity = createVector(0,0);
  acceleration = createVector(0,0);
  gravity = createVector(0, 0.5*mass);
  wind = createVector(0,0);

  // Serial Setup
  port = createSerial();
  let usedPorts = usedSerialPorts();
  if (usedPorts.length > 0) {
    port.open(usedPorts[0], 9600);
  }
  
  connectBtn = createButton('Connect to Arduino');
  connectBtn.position(10, 10);
  connectBtn.mousePressed(connectBtnClick);
}

function draw() {
  background(255);
  
  // read for wind
  if (port.available() > 0) {
    let data = port.readUntil("\n");
    if (data.length > 0) {
      sensorVal = Number(data.trim());
    }
  }
  
  // map wind
  let windX = map(sensorVal, 0, 1023, -0.8, 0.8);
  wind.set(windX, 0);

  // apply physics
  applyForce(wind);
  applyForce(gravity);
  velocity.add(acceleration);
  velocity.mult(drag);
  position.add(velocity);
  acceleration.mult(0);
  
  // draw
  fill(0);
  ellipse(position.x, position.y, mass, mass);
  drawWindIndicator(windX);

  // detect bounce
  if (position.y > height - mass/2) {
      velocity.y *= -0.9; 
      position.y = height - mass/2;
      
      // send blink command
      if (abs(velocity.y) > 1 && port.opened()) {
        port.write('B');
      }
  }
  
  // collision detection
  if (position.x > width - mass/2) {
    position.x = width - mass/2;
    velocity.x *= -0.9;
  } else if (position.x < mass/2) {
    position.x = mass/2;
    velocity.x *= -0.9;
  }
}

function applyForce(force){
  let f = p5.Vector.div(force, mass);
  acceleration.add(f);
}

function connectBtnClick() {
  if (!port.opened()) {
    port.open('Arduino', 9600);
  } else {
    port.close();
  }
}

// helper to visualize the wind
function drawWindIndicator(w) {
  push();
  translate(width/2, 50);
  fill(150);
  noStroke();
  text("Wind Force", -30, -20);
  stroke(0);
  strokeWeight(3);
  line(0, 0, w * 100, 0); 
  fill(255, 0, 0);
  noStroke();
  if (w > 0.05) triangle(w*100, 0, w*100-10, -5, w*100-10, 5); // Right Arrow
  if (w < -0.05) triangle(w*100, 0, w*100+10, -5, w*100+10, 5); // Left Arrow
  pop();
}

function keyPressed(){
  // reset ball
  if (key==' '){
    mass=random(15,80);
    position.y=-mass;
    position.x = width/2;
    velocity.mult(0);
  }
}

Video Demonstration

Leave a Reply