Final Project¶
The Idea¶
Since Armenia has many mountains, there are numerous unexplored lakes both on mountain peaks and in other areas. My idea is to create a Submarine for small lakes to explore the local fauna. The most important thing for me is to study the inhabitants of the lakes. The submarine can be used by limnologists to explore freshwater ecosystems and study fish populations. With the help of its AI-powered camera, it can detect and classify different fish species. While researching, I realized that making a submarine is not so easy, so I will try to use the weekly assignments to help build it.
After explaining my idea to our instructor Elen, she helped me create a sketch, which I want to share with you:
The submarine will have:
- A camera (maybe AI) for recording fish, or if there’s AI, it will detect the type of fish.
- A pressure sensor to measure the pressure at the current depth.
- A temperature sensor to measure the water temperature, as the temperature varies at different depths.
- An oxygen sensor in the water (This is not certain :D) since this sensor is quite expensive, but maybe I will figure something out.
- A motor and servo motors for controlling the submarine.
- Electronic valves for loading water to dive.
Researching¶
How a submarine works¶
After researching, I understood how a submarine works, and the most interesting part was learning how it dives.
This image was taken from an article on Wikimedia, which are free of charge to use.
As seen in the picture, the submarine increases its weight by taking in water, which is a very interesting solution in my opinion. At the top, you can see valves that open, allowing water to enter a special tank, while air exits through the valves.
Archimedes’ principle¶
At first, I thought the depth of the submarine’s immersion depended on how much weight it gained with water, but after further research, I finally understood that Archimedes principle is at work here. In order for the submarine to dive, its weight must balance with the buoyant force.
Let’s look at this image.
This image was taken from an article on Wikipedia, which are free of charge to use.
This shows the buoyancy of an object. The buoyant force (Fₐ) balances the weight of the object (Fᵖ), Were (Fᵖ). So as we know Fa = ρgV, were:
- Fa is the buoyant force.
- ρ is the density of the fluid.
- g is the acceleration due to gravity.
- V is the volume of the fluid displaced by the object.
So, we need to balance the weight of the submarine with the buoyant force, and with the fins and motor, it should be easy and simple to dive.
Choosing Material¶
As the body where the water will be taken in, I think I will use 2 sizes acrylic tubes.
On both sides, there will be plugs with seals to ensure tightness, and the water will flow through two tubes.
❗The most important part is ensuring tightness, which I will focus on during Fab Academy.
Designing¶
I have already built the Stand For testing different Props on 3D Printing Week.
First Prop Test
On Input Devices Week I added the Load Sensor to Measure the force and here is the Result:
During the Input Devices week, I finally added it and measured the force of the propeller.
System Integration¶
One of the most important parts of the project is system integration, because it connects all the parts of the project into one working system. At this point, it is important to check that the hardware (mechanical and electronic parts) and the software work well together. If there are any mistakes from before, they will appear during this step.
In my project, I use two Heltec V3 boards. One is on the submarine. I made a custom board (like a shield) for it, where all input and output devices are connected — like sensors, motors, and communication lines. The second Heltec board is outside the submarine. It receives LoRa signals and uses serial communication to send and receive data to my Windows Forms application.
During integration, I will check:
- If the LoRa communication works between the two Heltec boards,
- If the submarine reacts correctly to the commands from the Windows Forms app,
- If the stabilization system works using sensor data,
- If all motors and sensors are connected and working correctly,
- If the data from the submarine is received and shown in the app without delays.
Wiring¶
On my custom board connected to the first Heltec, I made pins for:
1. controlling servo motors (using PWM),
2. connecting sensors (for example, an accelerometer via I2C),
3. connecting the ESC for the main motor,
4. buttons and LEDs,
5. communication with the second Heltec board using LoRa (with built-in antenna on Heltec).
Because my submarine has many parts (I mean the body parts), the most important thing is waterproofing. I will use seals everywhere — for example, O-rings with springs (for motor shafts) to stop water from getting inside.
Also, the water tank for diving is placed in the center of the submarine. But I need to have wires going to the front and back. To solve this, I put a tube inside the water tank, and I will pass the wires through the tube to connect everything.
This way, I can keep everything connected and still protect the electronics from water.
Project Management¶
Project Plan
I organized my submarine development into three clear phases:
-
Research & Concept Defined requirements, studied designs, created sketches, and began 3D modeling.
-
3D Modeling Designed nose/tail sections, tube holders, and internal mounts.
-
Refinement Assembled full model, added details, checked tolerances, and prepared final files.
Key milestones marked progress at each phase completion.
Design Part¶
As I mentioned, the submarine has three main parts: the back, the front, and the middle.
In the front, there is a water temperature sensor, a servo motor for diving, the mainboard, and a battery box.
In the back, there will be a DC motor for the propeller, 2 servo motors for steering and diving, and of course, fins with sealing insulation.
In the center, there is an acrylic tube where a pipe will be placed to pass all the wirings from the mainboard to the servos and DC motor. There will also be Babbitt weights to make it heavy enough to reach Archimedes’ balance (neutral buoyancy).
3D Printing Part¶
After finishing the design, I started printing the parts.
During printing, I set the infill to 100% to make sure there was a very low chance of water leaks.
I used Qidi Q1 Printer and Elego Neptune 4 Max For printing.
Electronics Part¶
The initial idea was to make a Mainboard that included a PCB and a DC-DC converter. The board was planned to be controlled using a Heltec V3 with a LoRa module.
So I started designing the schematic part.
Here is the PCB Design.
3D Model of Board.
Soldering Part.
Final View.
Here is how it should work:
-
There should be 2 Heltec modules: one in the submarine and one with the operator.
-
The first module, which is inside the submarine, should be connected by a coaxial cable that goes to the surface of the water, where there will be a floating foam plate with the antenna.
-
The second module should be connected to a laptop by USB and send and receive signals through LoRa.
But…
What did I do??
– I forgot to set up the DC-DC converter and burned the board.
After that, I started looking for this module… I couldn’t find it anywhere in the whole country. So I decided not to waste time and chose to make a new board with a LAN module, so I will control it with a wired connection.
Here is PCB Design:
Final View:
Assembling Part.¶
So I started assembling from acrylic tube.
Before connecting the back part, I installed the seals, bearings, motor, as well as the servo motors and wires.
Here is the sealing and waterproofing.
Then I Assembled the motor.
Then I passed the cables through the pipe to the front part.
Next step was to assembly the servos with their mounts to shafts.
Here is how I assembled the shaft joint.
After finishing assembling all the parts in the back part, I started closing it using the thread.
Then I Tested the motor.
After I Assembled the Seal of front part using car Silicone.
To add weight, I cut a metal pipe and made a mold that fit perfectly inside the acrylic tube. Then I poured Babbitt metal into the mold.
Here are the filled ones.
Assembling the submarine was a very important step in the project. It helped connect all the main parts—front, center, and back—into one strong and working system. During this process, we made sure that all the components fit correctly and that the wires from the Mainboard reached the motors and sensors through the center pipe. Special attention was given to sealing and securing each part to make the submarine ready for underwater use. This step showed us how all the design, manufacturing, and electronics come together in a real, working product.
Programming Part.¶
To control the submarine, I decided to use a Windows Forms application as the interface program.
So I designed a small app to control it.
Here is the code of Form1.cs
using System;
using System.Drawing;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
namespace SubControlSystem
{
public partial class MainForm : Form
{
private string espIP = "192.168.1.177";
private int espPort = 80;
private bool keyPressed = false;
public MainForm()
{
InitializeComponent();
this.KeyDown += MainForm_KeyDown;
this.KeyUp += MainForm_KeyUp;
buttonForward.MouseDown += buttonForward_MouseDown;
buttonForward.MouseUp += buttonForward_MouseUp;
buttonLeft.Click += buttonLeft_Click;
buttonRight.Click += buttonRight_Click;
buttonDive.Click += buttonDive_Click;
buttonRise.Click += buttonRise_Click;
buttonConnect.Click += buttonConnect_Click;
buttonCalibrate.Click += buttonCalibrate_Click;
buttonReadTemp.Click += buttonReadTemp_Click;
textBoxLog.TabStop = false;
textBoxLog.ReadOnly = true;
}
private void SendCommand(string command)
{
try
{
using (TcpClient client = new TcpClient())
{
client.Connect(espIP, espPort);
NetworkStream stream = client.GetStream();
byte[] data = Encoding.ASCII.GetBytes(command + "\n");
stream.Write(data, 0, data.Length);
byte[] response = new byte[256];
int bytes = stream.Read(response, 0, response.Length);
string responseData = Encoding.ASCII.GetString(response, 0, bytes);
textBoxLog.AppendText("ESP32: " + responseData + Environment.NewLine);
}
}
catch (Exception ex)
{
textBoxLog.AppendText("Error: " + ex.Message + Environment.NewLine);
}
}
private void buttonConnect_Click(object sender, EventArgs e) => SendCommand("RESET");
private void buttonCalibrate_Click(object sender, EventArgs e) => SendCommand("CALIBRATE");
private void buttonReadTemp_Click(object sender, EventArgs e) => SendCommand("READ_TEMP");
private void buttonForward_MouseDown(object sender, MouseEventArgs e)
{
if (!keyPressed)
{
SendCommand("FORWARD");
buttonForward.BackColor = Color.LightGreen;
}
}
private void buttonForward_MouseUp(object sender, MouseEventArgs e)
{
SendCommand("FORWARD_STOP");
buttonForward.BackColor = DefaultBackColor;
}
private void buttonLeft_Click(object sender, EventArgs e)
{
SendCommand("LEFT");
buttonLeft.BackColor = Color.LightGreen;
}
private void buttonRight_Click(object sender, EventArgs e)
{
SendCommand("RIGHT");
buttonRight.BackColor = Color.LightGreen;
}
private void buttonDive_Click(object sender, EventArgs e)
{
SendCommand("DIVE_MAX");
buttonDive.BackColor = Color.LightGreen;
}
private void buttonRise_Click(object sender, EventArgs e)
{
SendCommand("RISE_MAX");
buttonRise.BackColor = Color.LightGreen;
}
private void MainForm_KeyDown(object sender, KeyEventArgs e)
{
if (!checkBoxKeyboardControl.Checked || keyPressed) return;
keyPressed = true;
switch (e.KeyCode)
{
case Keys.NumPad8: SendCommand("FORWARD"); break;
case Keys.Add: SendCommand("DIVE"); break;
case Keys.Subtract: SendCommand("RISE"); break;
case Keys.NumPad4: SendCommand("LEFT"); break;
case Keys.NumPad6: SendCommand("RIGHT"); break;
case Keys.NumPad0: SendCommand("DIVE_RESET"); break;
case Keys.NumPad9: SendCommand("STEERING_RESET"); break;
}
}
private void MainForm_KeyUp(object sender, KeyEventArgs e)
{
keyPressed = false;
switch (e.KeyCode)
{
case Keys.NumPad8: SendCommand("FORWARD_STOP"); break;
case Keys.Add:
case Keys.Subtract:
// Optional: No separate stop needed
break;
}
}
}
}
Here is the code of ESP32C3
#include <SPI.h>
#include <Ethernet.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <ESP32Servo.h>
// WIZ820io Ethernet Module Pins for ESP32-C3
#define SS_PIN 2 // Chip Select
#define MOSI_PIN 10
#define MISO_PIN 9
#define SCK_PIN 8
// Servo and Sensor Pins
#define SERVO_DIVE_PIN 3
#define SERVO_TURN_PIN 4
#define WATER_TEMP_PIN 20
const int relayPin = 5;
// Temperature Sensor Setup
OneWire oneWire(WATER_TEMP_PIN);
DallasTemperature sensors(&oneWire);
// Servo Setup
Servo diveServo;
Servo turnServo;
// Ethernet Settings (Static IP)
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 1, 177); // ESP32-C3 Static IP
IPAddress subnet(255, 255, 255, 0); // Subnet Mask
EthernetServer server(80);
// Servo Positions
int divePos = 92;
int turnPos = 104;
const int DIVE_MIN = 50, DIVE_MAX = 120;
const int TURN_MIN = 74, TURN_MAX = 134;
bool calibrating = false;
void setup() {
Serial.begin(115200);
SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, SS_PIN);
Ethernet.init(SS_PIN);
Ethernet.begin(mac, ip, subnet);
server.begin();
Serial.print("ESP32-C3 IP Address: ");
Serial.println(Ethernet.localIP());
sensors.begin();
diveServo.attach(SERVO_DIVE_PIN);
turnServo.attach(SERVO_TURN_PIN);
pinMode(relayPin, OUTPUT);
resetServos();
Serial.println("READY");
}
void loop() {
EthernetClient client = server.available();
if (client) {
Serial.println("Client connected!");
String command = "";
while (client.connected()) {
if (client.available()) {
char c = client.read();
if (c == '\n') break;
command += c;
}
}
command.trim();
Serial.printf("Received Command: %s\n", command.c_str());
processCommand(command);
client.println("OK:" + command);
delay(50);
while (client.available()) client.read();
Serial.println("Waiting for next command...");
}
}
void processCommand(String cmd) {
if (cmd == "CALIBRATE") {
if (!calibrating) {
calibrating = true;
calibrateServos();
calibrating = false;
}
} else if (cmd == "RESET") {
resetServos();
} else if (cmd == "DIVE_MAX" || cmd == "DIVE") {
divePos = constrain(divePos + 5, DIVE_MIN, DIVE_MAX);
diveServo.write(divePos);
Serial.printf("OK:DIVE (%d)\n", divePos);
} else if (cmd == "RISE_MAX" || cmd == "RISE") {
divePos = constrain(divePos - 5, DIVE_MIN, DIVE_MAX);
diveServo.write(divePos);
Serial.printf("OK:RISE (%d)\n", divePos);
} else if (cmd == "LEFT") {
turnPos = constrain(turnPos - 5, TURN_MIN, TURN_MAX);
turnServo.write(turnPos);
Serial.printf("OK:LEFT (%d)\n", turnPos);
} else if (cmd == "RIGHT") {
turnPos = constrain(turnPos + 5, TURN_MIN, TURN_MAX);
turnServo.write(turnPos);
Serial.printf("OK:RIGHT (%d)\n", turnPos);
} else if (cmd == "STEERING_RESET") {
turnPos = 104;
turnServo.write(turnPos);
Serial.printf("OK:STEERING RESET (%d)\n", turnPos);
} else if (cmd == "FORWARD") {
digitalWrite(relayPin, HIGH);
Serial.println("OK:FORWARD (Motor ON)");
} else if (cmd == "FORWARD_STOP") {
digitalWrite(relayPin, LOW);
Serial.println("OK:FORWARD_STOP (Motor OFF)");
} else if (cmd == "DIVE_RESET") {
divePos = 50;
diveServo.write(divePos);
delay(500);
divePos = 92;
diveServo.write(divePos);
Serial.printf("OK:DIVE RESET (%d)\n", divePos);
} else if (cmd == "READ_TEMP") {
sensors.requestTemperatures();
float temp = sensors.getTempCByIndex(0);
Serial.printf("TEMP: %.2fC\n", temp);
} else {
Serial.printf("ERROR: Unknown command (%s)\n", cmd.c_str());
}
}
void resetServos() {
divePos = 92;
turnPos = 104;
diveServo.write(divePos);
turnServo.write(turnPos);
Serial.printf("OK:RESET (Dive %d, Turn %d)\n", divePos, turnPos);
}
void calibrateServos() {
Serial.println("CALIBRATION START...");
diveServo.write(120); delay(500);
diveServo.write(50); delay(500);
diveServo.write(92); delay(500);
turnServo.write(134); delay(500);
turnServo.write(74); delay(500);
turnServo.write(104); delay(500);
Serial.println("CALIBRATION DONE");
}
First Test of running Servo Drives:
Water Temperature Sensor Test:
Explanation of Code.¶
I used an ESP32-C3 to control my small submarine. It connects to the Ethernet with a static IP, so I can control it from my computer.
What the ESP32-C3 does¶
- It controls two servo motors:
- One motor for diving (up and down).
- One motor for turning (left and right).
- It turns the main motor on and off using a relay.
- It reads water temperature with a temperature sensor.
- It waits for commands like
"DIVE"
,"LEFT"
, or"FORWARD"
from my PC. - After a command, it moves the servos, turns on the motor, or reads the temperature.
What the Windows Program does¶
-
I made a Windows Forms application with buttons.
-
When I press a button (for example: Forward, Left, Dive), the program sends a command to the ESP32-C3.
- It also shows the response from ESP32 in a text box.
- I can also use the keyboard to control the submarine:
- NumPad8 = forward
- NumPad4 = left
- NumPad6 = right
- Add/Subtract = dive or rise
Testing Part¶
Balancing¶
I did the first test before adding the babbitts because I thought it would sink.
But…
As you can see, it’s not only too light but also not balanced. That’s why I put babbitt inside.
Here is how it looks when it’s fully balanced.
First Test¶
During first test something went wrong and it start sink.
The issue was in waterproofing of nose part the thread tape was not enough and water start go inside.
Second Try¶
This time everything was wonderful and it run very good!!
Here is how it dives.
And here is how it goes up.
Final Video¶
Here is the final video with all preparations.
BOM¶
№ | Component | Quantity | Unit Price (USD) | Total Price (USD) | Source |
---|---|---|---|---|---|
1 | Heltec ESP32 LoRa V3 | 1 | 35.00 | 35.00 | AliExpress |
2 | ESP32-S3 CAM | 1 | 45.00 | 45.00 | AliExpress |
3 | 26850 Li-ion Battery | 4 | 12.00 | 48.00 | Local Supplier |
4 | BMS Board (with charging) | 1 | 10.00 | 10.00 | AliExpress |
5 | DC-DC Converter (5V/12V) | 2 | 5.00 | 10.00 | AliExpress |
6 | Water Pump (12V) | 1 | 15.00 | 15.00 | Local Store |
7 | MPU6050 (Accelerometer) | 1 | 3.00 | 3.00 | AliExpress |
8 | INA219 Current Sensor | 1 | 3.00 | 3.00 | AliExpress |
9 | Bearings (various sizes) | 6 | 2.00 | 12.00 | Local Hardware Shop |
10 | Seals and O-rings | 10 | 1.00 | 10.00 | Local Hardware Shop |
11 | LAN Cable | 10 m | 1.50 / m | 15.00 | Local Electronics |
12 | PLA Filament (1kg) | 3 | 15 | 45.00 | Local Store |
13 | PETG Filament (0.5kg) | 1 | 25.00 | 25.00 | Local Store |
14 | PCB Milling Materials | - | 10.00 | 10.00 | Fab Lab / Supplier |
Total Estimated Cost | 271.00 USD |
Final Conclusion¶
My final project was a submarine for small lakes called SubSevan. I wanted to build something that could help study fish and underwater life in Armenia. At first, it looked difficult, but step by step, I used what I learned each week to make it real.
I used:
-
3D printing to make the body,
-
Sensors to measure water temperature and movement,
-
Motors and servos to move the submarine,
-
Custom PCB boards to control everything,
-
And a Windows program I made to send commands.
Some things went wrong — like when my first PCB burned — but I fixed the problems and kept going. I also learned a lot about buoyancy and waterproofing, which were very important for the project to work.
The best part was when the submarine went underwater and came back up. It showed me that I can take an idea and turn it into something real.
During Fab Academy, I learned how to:
-
Design and make electronics,
-
Build working systems,
-
Write and use code,
-
And most of all — how to solve problems.
I’m very thankful to my instructors and my lab in Dilijan. This course helped me grow as a maker and now I feel ready to work on even bigger ideas.
Final Project Files¶
SubSevan 3D Model SubBoard (Heltec V3 Lora) SubBoard (LAN) ESP32C3 Code SubControlSystem(Interface App)