Final Concept I’m building an interactive clock that uses ping pong balls as display elements. The main feature is a hexagonal matrix made of 128 ping pong balls that will show the current time with different cool background effects which you can control from your phone. Each ball will have an addressable LED inside that can be individually controlled.
Wemos Mini (Arduino)
The Wemos Mini D1 will be the brain of the operation, handling way more than I initially planned.
Main Functions:
Connect to WiFi to get accurate time from pool.ntp.org
Control all 128 WS2812B LEDs
Handle all time calculations and display patterns
Store user settings in its memory
Schematic
P5.js
P5.js will serve as a configuration interface for the clock. It will only send the data entered as it is to the Wemos Mini.
Configuration options:
Font and font color
Background effect
Type (Perlin/Gradient)
Color palette
Brightness
Speed
Angle
Overall brightness of the clock
Night mode (ON/OFF)
Time server and timezone
WiFi settings
For this project I want to try hosting the P5.js sketch on the Wemos Mini, making it accessible through any web browser on the local network.This task sounds to me like it would be a very interesting one to explore.
Challenges Faced
My first major hurdle came from the LED strip specifications. I got the WS2812B LED Strip rated at 30 LEDs per meter, but the actual spacing turned out to be 33.15mm instead of the expected 33.33mm per LED. This small 0.18mm difference created significant alignment issues over the full length of the strip.
My second challenge was the ping pong balls, they weren’t quite what I expected either. Despite ordering balls without logos, each one came with a printed logo on them, which could not be rubbed off with anything, and using sandpaper would cause light diffusion. On top of that, I discovered that each ball wasn’t a single piece as I’d assumed, but actually two halves merged together. This created a visible seam when light shone through the ball.
I found myself at a crossroads: I could either align all the balls so the seams were positioned horizontally, which would make the logos unpossible to cut out because they were placed randomly on the ball, or I could prioritize hiding the logos, which would result in the seams being more visible. After some consideration, I decided to cut out the logos. They were more distracting than the seams, and I figured the uniform light would help mask the seam lines better than it would hide the printed logos.
These unexpected issues with both the LEDs and the ping pong balls have forced me to rethink my entire mounting and display system. It’s been frustrating, but it’s also pushing me to come up with more creative solutions.
Next steps
Set up the Wemos Mini web server
Create the P5.js configuration interface
Design the LED patterns for numbers
Build a proper mounting system that accounts for actual LED spacing
Test WiFi connectivity and time synchronization
Despite these setbacks, I’m still excited about this project. Each challenge is teaching me something new, and I’m looking forward to seeing how the final product turns out.
I have a bit of a better idea now for my final project (though I’m not ruling out the possibility of doing something completely different 😅). I’m not really sure how best to describe it, but simply put, you have the power to manipulate objects with your hands, and you use to protect yourself and fight against a villain, who you must also chase down (a bit of a reference to my midterm).
# Design Description
Arduino:Inputs:
Pressure sensor (inside of a stress ball), for detecting when the player closes their hand.
Capacitive touch sensor strip, as a control for firing.
Some game state info (from p5)
Outputs:
Neopixel (addressable LED strip)
Send values of pressure sensor and touch sensor to p5.
Circuit View:
Note: I used a flex sensor in place of the touch sensor as that wasn’t available right now.
Schematic:
Note: I used a flex sensor in place of the touch sensor as that wasn’t available right now.
p5:Inputs:
MoveNet pose detection, to get angle of the player’s hand in relation with the screen.
Pressure sensor and touch sensor values from Arduino.
For my final project, I want to incorporate and showcase my interests. One of these interests of mines are penguins. Therefore, I have settled on making an interactive game called ‘Pingu Pounce’. Pingu Pounce is essentially an exhilarating game where players help a plucky penguin fly through icy platforms. With simple physical controls, players will use them to make their penguin leap from one slippery platform to the next, aiming to climb as high as possible while avoiding obstacles like sharp platforms.
“make something that uses only one sensor on Arduino and makes the ellipse in p5 move on the horizontal axis, in the middle of the screen, and nothing on Arduino is controlled by p5”
The following code was utilized for this particular exercise:
let sensorValue = 0; // To store the sensor value from Arduino
function setup() {
createCanvas(640, 480);
textSize(18);
}
function draw() {
background(220);
if (!serialActive) {
text("Press Space Bar to select Serial Port", 20, 30);
} else {
text("Connected", 20, 30);
// Display the sensor value
text('Sensor Value: ' + sensorValue, 20, 50);
// Map the sensor value to the horizontal position of the ellipse
let ellipseX = map(sensorValue, 0, 1023, 0, width);
// Draw the ellipse in the middle of the canvas vertically
fill(255, 0, 0);
ellipse(ellipseX, height / 2, 50, 50);
}
}
function keyPressed() {
if (key == " ") {
setUpSerial(); // Start the serial connection
}
}
// This function is called by the web-serial library
function readSerial(data) {
if (data != null) {
let fromArduino = trim(data); // Trim any whitespace
if (fromArduino !== "") {
sensorValue = int(fromArduino); // Convert the sensor value to an integer
}
}
}
The following code was used in the Arduino IDE for this exercise:
// make something that uses only one sensor on Arduino and makes the ellipse in p5 move on the horizontal axis,
// in the middle of the screen, and nothing on arduino is controlled by p5
int sensorPin = A0; // Single sensor connected to A0
void setup() {
Serial.begin(9600);
}
void loop() {
int sensorValue = analogRead(sensorPin); // Read sensor value
Serial.println(sensorValue); // Send sensor value to p5.js
delay(50); // Short delay for stability
}
Exercise 2:
“make something that controls the LED brightness from p5”
The following code was used to make this exercise come to fruition:
let brightness = 0; // Brightness value to send to Arduino
function setup() {
createCanvas(640, 480);
textSize(18);
// Create a slider to control brightness
slider = createSlider(0, 255, 0);
slider.position(20, 50);
}
function draw() {
background(220);
if (!serialActive) {
text("Press Space Bar to select Serial Port", 20, 30);
} else {
text("Connected", 20, 30);
// Display brightness value
text("Brightness: " + brightness, 20, 90);
// Update brightness from the slider
brightness = slider.value();
// Send brightness to Arduino
writeSerial(brightness + "\n");
}
}
function keyPressed() {
if (key == " ") {
setUpSerial(); // Start the serial connection
}
}
function readSerial(data) {
if (data != null) {
let fromArduino = trim(data); // Trim whitespace
brightness = int(fromArduino); // Parse data into an integer
}
}
The following Arduino code was used for this particular exercise:
//make something that controls the LED brightness from p5
int ledPin = 3;
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
}
void loop() {
if (Serial.available()) {
int brightness = Serial.parseInt();
if (Serial.read() == '\n') {
brightness = constrain(brightness, 0, 255);
analogWrite(ledPin, brightness);
Serial.println(brightness); // Send brightness to p5.js
}
}
}
Exercise 3:
The following code is an alteration of professor Aaron Sherwood’s code which was used for this exercise:
let velocity;
let gravity;
let position;
let acceleration;
let wind;
let drag = 0.99;
let mass = 50;
let windSensorValue = 0; // Value from the wind sensor
let connectButton; // Button for connecting to the serial port
function setup() {
createCanvas(640, 360);
noFill();
position = createVector(width / 2, 0); // Initial position of the ball
velocity = createVector(0, 0); // Initial velocity
acceleration = createVector(0, 0); // Initial acceleration
gravity = createVector(0, 0.5 * mass); // Gravity force
wind = createVector(0, 0); // Initial wind force
// Create a button to initiate the serial connection
connectButton = createButton("Connect to Serial");
connectButton.position(10, 10);
connectButton.mousePressed(setUpSerial); // Trigger serial connection on button press
}
function draw() {
background(255);
if (!serialActive) {
text("Click 'Connect to Serial' to start", 20, 50);
return; // Exit the draw loop until the serial connection is established
}
// Map wind sensor value to wind force (affects horizontal movement)
wind.x = map(windSensorValue, 0, 1023, -1.5, 1.5); // Adjust force range as needed
// Apply forces
applyForce(wind); // Apply wind force
applyForce(gravity); // Apply gravity force
// Update velocity and position
velocity.add(acceleration);
velocity.mult(drag); // Apply drag (friction)
position.add(velocity);
acceleration.mult(0); // Reset acceleration
// Ball bounce logic (vertical boundary)
if (position.y > height - mass / 2) {
position.y = height - mass / 2; // Place the ball on the ground
velocity.y *= -0.9; // Reverse and dampen vertical velocity
// Notify Arduino to toggle the LED when the ball touches the ground
writeSerial("1\n"); // Send '1' to Arduino
} else {
// Ensure the LED is off when the ball is not touching the ground
writeSerial("0\n"); // Send '0' to Arduino
}
// Draw the ball
ellipse(position.x, position.y, mass, mass);
}
function applyForce(force) {
// Newton's 2nd law: F = M * A -> A = F / M
let f = p5.Vector.div(force, mass); // Scale force by mass
acceleration.add(f); // Add force to acceleration
}
// Reset the ball to the top of the screen when the space key is pressed
function keyPressed() {
if (key === " ") {
position.set(width / 2, 0); // Reset position to top center
velocity.set(0, 0); // Reset velocity to zero
mass = random(15, 80); // Randomize mass
gravity.set(0, 0.5 * mass); // Adjust gravity based on new mass
}
}
// Serial communication: Read sensor value from Arduino
function readSerial(data) {
if (data != null) {
let trimmedData = trim(data);
if (trimmedData !== "") {
windSensorValue = int(trimmedData); // Read wind sensor value
}
}
}
The following code was used in the Arduino IDE to bring this to life:
//gravity wind example
int ledPin = 2; // Pin connected to the LED
int windPin = A0; // Analog pin for the potentiometer (A0)
void setup() {
Serial.begin(9600); // Start serial communication
pinMode(ledPin, OUTPUT); // Set the LED pin as an output
digitalWrite(ledPin, LOW); // Turn the LED off initially
}
void loop() {
// Read the analog value from the potentiometer
int windValue = analogRead(windPin);
// Send the wind value to p5.js over serial
Serial.println(windValue);
// Check if a signal is received from p5.js for the LED
if (Serial.available()) {
char command = Serial.read(); // Read the signal from p5.js
if (command == '1') {
digitalWrite(ledPin, HIGH); // Turn on the LED when the ball touches the ground
} else if (command == '0') {
digitalWrite(ledPin, LOW); // Turn off the LED
}
}
delay(5); // Small delay for stability
}
The following schematic was used for all 3 of the exercises with slight moderations, provided in class:
Drawing an ellipse and making it move horizontally based on photoresistor
Code:
let x=0;
function setup() {
createCanvas(400,400);
textSize(18);
}
function draw() {
if (serialActive){
background("white");
ellipse(map(x,0,1023,0,400),height/2,50,50);
}else{
background("white");
console.log("not connected");
}
}
function keyPressed() {
if (key == " ") {
// important to have in order to start the serial connection!!
setUpSerial();
}
}
function readSerial(data) {
if (data != null) {
x = int(data);
}else{
x = 0;
}
}
//Arduino code
// void setup() {
// Serial.begin(9600);
// pinMode(A0, INPUT);
// }
// void loop() {
// Serial.println(analogRead(A0));
// }
Exercise 2
Increase the brightness of the LED with mouse click
Code:
let bright=0;
function setup() {
createCanvas(400,400);
textSize(18);
}
function draw() {
if (serialActive){
background("white");
text(bright,10,10);
}else{
background("white");
console.log("not connected");
}
}
function keyPressed() {
if (key == " ") {
// important to have in order to start the serial connection!!
setUpSerial();
}
}
function mousePressed(){
if(serialActive){
bright+=1;
writeSerial(bright);
}
}
function readSerial(data) {
}
//Arduino code
// int brightness;
// void setup() {
// Serial.begin(9600);
// pinMode(9, OUTPUT);
// }
// void loop() {
// brightness = Serial.parseInt();
// analogWrite(9,brightness);
// }
Exercise 3
Turning on LED when the ball bounces and use photoresistor to control wind movement
Circuit
Video:
Code:
let velocity;
let gravity;
let position;
let acceleration;
let wind;
let drag = 0.99;
let mass = 50;
let bounce = 0;
function setup() {
createCanvas(640, 360);
noFill();
position = createVector(width/2, 0);
velocity = createVector(0,0);
acceleration = createVector(0,0);
gravity = createVector(0, 0.5*mass);
wind = createVector(0,0);
}
function draw() {
background(255);
if(!serialActive){
console.log("PRESS a TO CONNECT");
}
else{
applyForce(wind);
applyForce(gravity);
velocity.add(acceleration);
velocity.mult(drag);
position.add(velocity);
acceleration.mult(0);
ellipse(position.x,position.y,mass,mass);
if (position.y > height-mass/2) {
velocity.y *= -0.9; // A little dampening when hitting the bottom
position.y = height-mass/2;
bounce = 1;
}else{
bounce = 0;
}
}
}
function applyForce(force){
// Newton's 2nd law: F = M * A
// or A = F / M
let f = p5.Vector.div(force, mass);
acceleration.add(f);
}
function keyPressed(){
if (keyCode==LEFT_ARROW){
wind.x=-1;
}
if (keyCode==RIGHT_ARROW){
wind.x=1;
}
if (key==' '){
mass=random(15,80);
position.y=-mass;
velocity.mult(0);
}
if (key == "a") {
// important to have in order to start the serial connection!!
setUpSerial();
}
}
function readSerial(data) {
if (data != null) {
console.log(data);
wind.x = map(int(data), 0, 1023, -2, 2);
writeSerial(bounce + '\n');
}
}
//Arduino code
// int bounce;
// void setup() {
// Serial.begin(9600);
// pinMode(9,OUTPUT);
// while (Serial.available() <= 0) {
// Serial.println("0"); // send a starting message
// }
// }
// void loop() {
// while(Serial.available()){
// bounce = Serial.parseInt();
// if (Serial.read() == '\n') {
// digitalWrite(9,bounce);
// }
// }
// int sensor = analogRead(A1);
// Serial.println(sensor);
// // Serial.println(analogRead(A0));
// }
1. Make an ellipse in p5 move on the horizontal axis using only one sensor. Video Link
int leftLedPin = 4;
int rightLedPin = 6;
const int trigPin = 10;
const int echoPin = 9;
void setup() {
// Start serial communication at 9600 baud
Serial.begin(9600);
// Set up the LED and ultrasonic sensor pins
pinMode(LED_BUILTIN, OUTPUT);
pinMode(leftLedPin, OUTPUT);
pinMode(rightLedPin, OUTPUT);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
// Blink LEDs to check wiring
digitalWrite(leftLedPin, HIGH);
digitalWrite(rightLedPin, HIGH);
delay(200);
digitalWrite(leftLedPin, LOW);
digitalWrite(rightLedPin, LOW);
}
void loop() {
// Check if there is data available from p5.js
if (Serial.available()) {
int left = Serial.parseInt(); // Get the left LED value
int right = Serial.parseInt(); // Get the right LED value
// Update LEDs based on values from p5.js
digitalWrite(leftLedPin, left);
digitalWrite(rightLedPin, right);
// Clear any remaining characters in the serial buffer (such as '\n')
Serial.read();
}
// Trigger the ultrasonic sensor
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// determine the time for the echo
long timeTraveled = pulseIn(echoPin, HIGH);
// the distance in centimeters
int distance = timeTraveled * 3.4/2;
int sensor = analogRead(A1);
// Send the distance value to p5.js
Serial.print(sensor);
Serial.print(",");
Serial.println(distance);
delay(100);
}
const int blue_LED = 4;
const int red_LED = 6;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
// Initalization of LED pins
pinMode(blue_LED, OUTPUT);
pinMode(red_LED, OUTPUT);
// Blink LEDs to check wiring
digitalWrite(blue_LED, HIGH);
digitalWrite(red_LED, HIGH);
delay(200);
digitalWrite(blue_LED, LOW);
digitalWrite(red_LED, LOW);
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println(0);
if (Serial.available()) {
int left = Serial.parseInt(); // Get the left LED value
int right = Serial.parseInt(); // Get the right LED value
// Update LEDs based on values from p5.js
digitalWrite(blue_LED, left);
digitalWrite(red_LED, right);
// removes remaining characters in the serial buffer
}
Serial.read();
delay(100);
}
const int LED_pin = 6;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode (LED_pin, OUTPUT);
// digitalWrite(LED_pin, HIGH);
// delay(200);
// digitalWrite(LED_pin, LOW);
}
void loop() {
// put your main code here, to run repeatedly:
if (Serial.available()) {
int LED_value = Serial.parseInt(); // Get the LED value
// Update LEDs based on values from p5.js
digitalWrite(LED_pin, LED_value);
// Clear any remaining characters in the serial buffer (such as '\n')
Serial.read();
}
int sensor = analogRead(A0);
Serial.println(sensor);
}
For my final project, my goal is to build upon my midterm project of bring my game to life. As a reminder, my midterm project was a co-op game which two individuals needed to work together with the WASD and arrow keys to input the right order or combination to proceed to the next level. While the p5 side is mostly complete, I would still need to update the code to communicate with the Arduino and determine sensors for which the Arduino can then communicate with p5.
My end goal is to hopefully build a live, immersive experience which two plays can maybe interact in some fashion to control the characters on screen, defeat the creatures, and proceed to the next level. At the very core, I could have the WASD and arrow keys map certain sensors. On the other hand, I am thinking about using the laser cutter or 3D print to make something visually appealing and fun.
For the reading this week, we had to read on the topic of the design of assistive technology and individuals’ opinions on its design and capabilities. Since I don’t typically think task that might be more difficult for someone with a disability, I feel reading on this topic was especially interesting and reminder that simple task may not always be simple. For example, tremors that typically affect the elder cause the involuntarily shaking of the joints. When effected individuals need to eat, write, shower, etc, it oftentimes become difficult because they cannot control the movement of their hands. One assistive technology that aims to tackle the issue is a reactive spoon that remains stable despite extensive shaking or movement.
In the reading, it had mentioned the drive to make assistive tool standard across all individuals with that disability. When I read it, I felt a strong disagreement with that end goal because a disability does not affect everyone the same. As such making a standard tool make not always end up with the right individual and making their lives exponentially better. Not to say tool will not aid them in their daily lives, however certain disabilities require more attention and customization to fit the individuals’ need.
I was reading Graham Pullin‘s “Design Meets Disability”, and found it pretty good. There were so many great examples and points showcased throughout it, in neat little sections, and I like how he treats disability in the context of design, not as something to deal with purely technically (entirely function, no form), but rather as a source of inspiration to create more personalised and human designs.
I was particularly fascinated with the example of eyeglasses. They were initially quite utilitarian objects, only serving to help correct someone’s vision. In line with this (“disability”), they were designed to not draw much attention, with the NHS using pink plastic to sort of camouflage against the skin. That didn’t work particularly well. Not only were they visible, but they also led to a stereotype and a dislike for wearing them, making those who need to wear them potentially feel ashamed and humiliated. Then, they were fashionised. By moving away from this utilitarian and functional only approach, into designing something that also looked great, the industry, and the perception of eyeglasses, were revolutionized.
“up to 20% of some brands of glasses are purchased with clear nonprescription lenses, […] wearing glasses has become an aspiration rather than a humiliation.”
While I do dislike the overly fashion focused industry that has propped up around this, I also recognise that it has made eyeglasses a lot more approachable and comfortable for those who need it, so I am genuinely curious as to why more industries and product design don’t adopt the philosophies mentioned here, particularly about design.
This also reminds me the phenomenon (which we have also previously seen), where things that look better, work better. As I’m most involved in the tech sphere, I can I’ve definitely seen and noticed this effect directly, where even though an app or website might have an amazing functionality, if it doesn’t look appealing, its likely going to be perceived as subpar, and likely isn’t going to go very far (though of course, there are exceptions). This is why in tech, there’s very often a designer on board each team as well. In some cases (such as in 37signals), there’s a 1:1 ratio, as 1 designer and 1 programmer are paired up to work on a feature, highlighting just how important design is.
I also found the story about prosthetics similarly inspiring. While I understand the motive behind not fracturing an already small market, there is something nice to know, that multiple, well designed options exist, allowing someone to be more expressive and confident (something people with disabilities might have a harder time with).
For my final project, I’m thinking of doing another glove based project. I’m picturing a gauntlet (black glove with metal attached on top, for that overlapping gauntlet effect), that can detect when a user closes their hand (instead of using flex sensors, I’ll use a stress ball with a pressure sensor inside), and the orientation of their hand (using an IMU). With these inputs, I can allow the player to “grab” and manipulate the objects on screen (sort of like a gravity gun), which they will use to solve puzzles and combat enemies.
However, this is a completely new project (with WEBGL in p5, which isn’t great to work with), so I’m not sure that’s a wise (or even feasible) idea so close to the finals. So, I’m also thinking about expanding upon my midterm, using either the same gauntlet or a sleeve (with a copper pad for the touch sensor, and still an IMU), to point and shoot at enemies or blast through a wall (enlarging the allowable area, as a limited special ability to survive a particularly difficult pose). Or, I might just expand upon HandiSynth.