Become a Captain for a Day! (Final Project) – Darko Skulikj
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:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#include <Servo.h>
int serialVal0 = 0;
int serialVal1 = 1;
int previousButton2State = LOW;
// Define pins for buttons, servo, and DC motor
constint button1Pin = 2; // Pin for the first button
constint button2Pin = 3; // Pin for the second button
constint button3Pin = 4; // Pin for the third button
constint servoPin = 10; // Pin for the servo motor
constint 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;
voidsetup(){
// 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);
}
}
voidloop(){
// 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);
}
#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);
}
#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:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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;
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;
}
}
}
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!