And here it is, the last project of the semester. Before saying anything about the project, I just want to express my gratefulness to all the students, instructors, professors and especially Professor Aaron for having an amazing, fun and challenging time during this class.
Concept
The concept of this project is pretty simple. I’m going to make you a captain of a boat for a day. SPOILER ALERT: You don’t need a license! To make this dream come true, I decided to conceptualize for the controls to be very simple, you just click to go left, right and to turn the motor boat on and off. This really takes us into intuitive design, and I believe that users would adapt very simply to this. Other that that I would need to think about the bed of the boat, the DC Motor, the Propeller, the Physics behind it and the whole placement of the Arduino. The rough sketch looked something like this:
Production:
For the boat model, I found this boat bed on Thingy Verse and adjusted the dimensions so that it can find the Arduino and the breadboard, as well as the batteries. Here is how the model looked before printing:
After printing it out, I looked into 3d printing a propeller which would actually be strong enough to pull the boat so I found this 3d model:
After that I placed all the parts and coded my logic for the user interaction. The code can be seen below:
#include <Servo.h> int serialVal0 = 0; int serialVal1 = 1; int previousButton2State = LOW; // Define pins for buttons, servo, and DC motor const int button1Pin = 2; // Pin for the first button const int button2Pin = 3; // Pin for the second button const int button3Pin = 4; // Pin for the third button const int servoPin = 10; // Pin for the servo motor const int motorPin = 11; // Pin for the DC motor // Define variables to store the state of buttons and motor int button1State = 0; int button2State = 0; int button3State = 0; bool motorState = false; // Motor state flag // Create a servo object Servo myServo; void setup() { // Initialize serial communication Serial.begin(9600); // Attach servo to its pin myServo.attach(servoPin); // Set motor pin as output pinMode(motorPin, OUTPUT); // Set button pins as inputs pinMode(button1Pin, INPUT); pinMode(button2Pin, INPUT); pinMode(button3Pin, INPUT); // Start the handshake while (Serial.available() <= 0) { Serial.println("0,0"); // Send a starting message delay(50); } } void loop() { // Read the state of buttons button1State = digitalRead(button1Pin); button2State = digitalRead(button2Pin); button3State = digitalRead(button3Pin); // If button 1 is pressed, turn servo left if (button1State == HIGH) { myServo.write(120); serialVal0 = 80; delay(100); // Add a delay to avoid sending data too fast } // Toggle motor based on button 2 state if (button2State == HIGH) { if (previousButton2State == LOW) { motorState = !motorState; // Toggle motor state only once when the button is released digitalWrite(motorPin, motorState); // Set motor state } } // Update serialVal1 based on motor state serialVal1 = motorState ? 1 : 0; // Update previous button state previousButton2State = button2State; // If button 3 is pressed, turn servo right if (button3State == HIGH) { myServo.write(80); serialVal0 = 140; delay(100); // Add a delay to avoid sending data too fast } // Return servo to neutral position if no buttons are pressed if (button1State == LOW && button3State == LOW) { myServo.write(100); // Neutral position serialVal0 = 115; delay(100); // Add a delay to avoid sending data too fast } // Send the values of serialVal0 and serialVal1 Serial.print(serialVal0); Serial.print(','); Serial.println(serialVal1); }
Of course this had to be connected to p5.js so I made a sketch which would provide a nice interface showing the speed and direction of where the boat is headed, it looks like this:
The p5.js code looks like this:
let servoPos; // Variable to store servo position let motorSpeed; // Variable to store motor speed let boatImage; // Variable to store boat image let islandsImage1, islandsImage2, islandsImage3; // Variables to store islands images let otherBoatsImage; // Variable to store other boats image let serialSetUp = false; // Variable to track if serial setup is done // Variables to store positions of objects let islands1Y, islands2Y, islands3Y, otherBoats1Y, otherBoats2Y; function preload() { // Load boat, islands, and other boats images boatImage = loadImage('boat.png'); islandsImage1 = loadImage('islands.png'); islandsImage2 = loadImage('islands.png'); islandsImage3 = loadImage('islands.png'); otherBoatsImage = loadImage('otherboats.png'); } function setup() { createCanvas(800, 600); // Larger canvas size textSize(24); // Bigger font size // Display initial message centered on the canvas textAlign(CENTER, CENTER); setGradient(0, 0, width, height, color(0, 191, 255), color(0, 0, 128)); // Background gradient fill(255); // White text color text("Press spacebar to turn the boat motor on", width / 2, height / 2); // Initialize positions of objects islands1Y = height / 2; islands2Y = height / 2; islands3Y = height / 2; otherBoats1Y = height / 2; otherBoats2Y = height / 2; } function readSerial(data) { if (data != null) { // Split the incoming data by comma let dataArray = split(trim(data), ","); // If the right length, then proceed if (dataArray.length == 2) { // Parse the values as integers and store them in servoPos and motorSpeed servoPos = int(dataArray[0]); motorSpeed = int(dataArray[1]); console.log("Servo position: " + servoPos + ", Motor speed: " + motorSpeed); } } ////////////////////////////////// //SEND TO ARDUINO HERE (handshake) ////////////////////////////////// let sendToArduino = 0 + "\n"; writeSerial(sendToArduino); } function draw() { // If serial setup is not done, return if (!serialSetUp) return; // Background gradient resembling water setGradient(0, 0, width, height, color(0, 191, 255), color(0, 0, 128)); // Display boat heading status centered above boat // Move and draw islands images islands1Y += 1; // Speed of islands movement if (islands1Y > height) { islands1Y = 0; // Reset when islands moves off the screen } image(islandsImage1, 140, islands1Y, 100, 100); // Islands image on the left side islands2Y += 1.5; // Speed of islands movement if (islands2Y > height) { islands2Y = 0; // Reset when islands moves off the screen } image(islandsImage2, 250, islands2Y, 50, 50); // Islands image on the left side islands3Y += 2; // Speed of islands movement if (islands3Y > height) { islands3Y = 0; // Reset when islands moves off the screen } image(islandsImage3, 0, islands3Y, 150, 150); // Islands image on the left side // Move and draw other boats images otherBoats1Y += 1.2; // Speed of other boats movement if (otherBoats1Y > height) { otherBoats1Y = 0; // Reset when other boats moves off the screen } image(otherBoatsImage, 500, otherBoats1Y, 90, 180); // Other boats image on the right side otherBoats2Y += 1.8; // Speed of other boats movement if (otherBoats2Y > height) { otherBoats2Y = 0; // Reset when other boats moves off the screen } image(otherBoatsImage, 600, otherBoats2Y, 90, 180); // Other boats image on the right side fill(255); // White text color textAlign(CENTER); if (servoPos == 115) text("The boat is heading Straight!", width / 2, boatImage.height / 2 - 20); // Adjusted position else if (servoPos == 80) text("The boat is heading to the Right!", width / 2, boatImage.height / 2 - 20); // Adjusted position else if (servoPos == 140) text("The boat is heading to the Left!", width / 2, boatImage.height / 2 - 20); // Adjusted position // Draw boat image with rotation based on servo position push(); translate(width / 2, height / 2); // Center of the screen rotate(radians(-90)); // Rotate to point upwards if (servoPos == 80) { rotate(radians(20)); // Rotate slightly to the right } else if (servoPos == 140) { rotate(radians(-20)); // Rotate slightly to the left } imageMode(CENTER); image(boatImage, 0, 0, 250, 150); // Draw boat image pop(); // Display motor speed centered below boat with larger font size textSize(24); // Larger font size textAlign(CENTER); if(motorSpeed ==0) text("Motor Speed: HIGH ", width / 2, height - 20); else if(motorSpeed == 1) text("Motor Speed: LOW ", width / 2, height - 20); } // Function to draw a gradient background function setGradient(x, y, w, h, c1, c2) { noFill(); for (let i = y; i <= y + h; i++) { let inter = map(i, y, y + h, 0, 1); let c = lerpColor(c1, c2, inter); stroke(c); line(x, i, x + w, i); } } function keyPressed() { if (key == " ") { if (!serialSetUp) { setUpSerial(); serialSetUp = true; } } }
I also added some styrofoam padding on the bottom for added support and easier floating.
Here are some pictures from the production process:
And finally, here is the final video Presentation of it working:
Conclusion:
Overall this project was very fun challenging and I really think I learned a lot during the making.
Even though this is the end of the road for this class, this is only the beginning for me in exploring this area and I’m really excited of what happens next!
Darko