We can see wall-climbing robots from hobby-level to serious problem-solving ones that are used for inspection, surveillance, cleaning, etc. When we generally talk about robots, a robot can be a firefighting robot, a human-following robot, a self-balancing robot or can be of any type that has some specific features or solves a specific kind of problem. If we talk about wall-climbing robots, they rely on large drone motors, 5” propellers and bulky lipo batteries to generate enough holding power for climbing vertical surfaces.
In this project, we challenge ourselves to build a much smaller version. The goal is to create a lightweight wall-climbing robot that is small enough to fit in the palm of your hand while still generating sufficient suction to climb walls and crawl across ceilings. Instead of keeping it looking like another robot, we are going to design it to resemble a real beetle using a custom lightweight 3D-printed shell.
Components Required
Below is the list of components required to build this project.
| S.no | Component | Description | Quantity |
| 1 | 4300KV Brushless DC Motor | To provide the suction / holding power to the robot | 1 |
| 2 | 3-inch Drone Propeller | Acts as the main propeller | 1 |
| 3 | ESP32 Development Board | Main controller | 1 |
| 4 | 100 RPM N20 Motors | To provide movement for the robot | 2 |
| 5 | Lightweight Wheels | Wheels with rubber grips give better traction | 4 |
| 6 | 2S, 6-8A ESC | Drive the BLDC Motor | 1 |
| 7 | 7.4V 300mAh Li-Po Battery | Power the whole robot | 1 |
| 8 | MX1508 Motor Driver | Drive the N20 Motors | 1 |
| 9 | MP1584 Buck converter | Provide stable 5V to ESP32 | 1 |
How Does the Robot Stick to Walls?
Instead of using some adhesive pads or vacuum pumps, this robot uses a high-speed BLDC motor and propeller to generate a low-pressure region beneath the chassis.
As the propeller spins, it continuously pulls air from underneath the robot and throws it upwards. This creates a pressure difference between the inside of the enclosure and the surrounding atmosphere. The higher atmospheric pressure outside pushes the robot firmly against the wall or ceiling, creating the holding force required for climbing. The stronger the pressure difference, the greater the holding force.
Measuring the Holding Force
To verify whether the selected motor was capable of generating enough suction, I built a simple test setup. A small 3D-printed enclosure was designed to hold the BLDC motor and propeller. I’m using an SF-400 weighing machine to determine the holding force. This machine shows positive values when weight is on top of it and negative values when we pull/suck its weighing plate up. The negative value indicates the suction force generated by the motor.
The entire assembly was placed on a weighing machine. I’m using this weighing machine to measure the motor suction. This machine shows positive values when weight is on top of it and negative values when we pull/suck this plate up. After weighing all the electronic components, including the battery, ESP32 board, BLDC motor, ESC, and drive motors, the total weight came to approximately 137 grams.
For reliable climbing, the suction force should be significantly higher than the robot's own weight. A safe and optimal holding force is around two times the total weight.
Therefore, our target holding force is approximately 300 grams, which we got as well.
Circuit Diagram
Below is the circuit diagram for the Wall Climbing beetle robot.

The brain of the robot is an ESP32 development module. The NZO motors are connected to the output pins of the MX1508 motor driver. The 4 inputs of the motor driver are connected to GPIOs 16,17,18 and 19 of the ESP32, respectively. The motor ESC is connected to the output pins of the 2S ESC, which is connected to GPIO pin 4. If you like to see more ESP32-based projects, check out our collection of ESP32 projects.
The buck converter, which is MP1584, is connected directly to the 7.4V 350mAh LiPo battery. The 5V output is used to power the ESP32 as well as the motor driver. The 2S ESC is also connected directly to the battery so that we get the full potential of the battery.
Designing the Beetle Shell

Once the mechanical design was finalized, the next goal was to improve the appearance of the robot. I started looking for the various patterns that you can see on a real-life beetle. I found all these designs. Then I made a few hand sketches, took photos of them and asked ChatGPT to apply these designs in my proto so that I can visualise them before I even start modelling it. GPT gave me a wide range of options. I personally liked this one; the honeycomb-like patterns kind of look like a Voronoi pattern that a lot of designers and architects use in their designs. I had this ready-made plastic design with me, which I thought to use, but it's slightly oversized. So, I went ahead and started to modify the GPT-given design into a Voronoi pattern and make the beetle’s top design. Straight away, I went to this Voronoi sketch generator - an add-on to Autodesk Fusion. I adjusted parameters and exported this Voronoi as an SVG. I imported it into a Fusion sketch and extruded it into this hemispherical shell that I designed for the robot top. There we have the design. Let’s print it with supports
3D Printing
Below are the steps from start to end involved in design to fabrication to Modelling of the Beetle body.

Printing the curved pattern is a bit complicated as it’s only 0.5mm thick and requires a lot of supports to hold the curves. Also, the chances are high that we are going to break the design trying to remove the supports. So, I planned to flatten the design for printing and, after the print is done, reshape it into the actual design by heating it. After a lot of research, I downloaded this software called “Meshmixer”, which is also from the Autodesk Family. I converted the 3d model into a surface model and imported it into Meshmixer. Using it, I unfolded the 3d shape to a flat 2D design. After exporting it as SVG, I imported it to Fusion, resized it, extruded and cut the design onto the body. To make it fit the print bed, I sliced the flat pattern into 2 halves, printed them, then joined them together using glue.
Assembly
Below are the robot assembly details for reference.

As you can see, the above is the full assembly of the Beetle. At the heart is the BLDC motor with propeller. I have mounted the ESP32 and the Battery to the centre as much as possible to reduce tripping torque. The buck converter and the motor driver are mounted on top of the N20 motor mounting clamps. The jumper wires from the previous version were replaced with normal wires and organised properly
After properly assembling all the parts, the robot closely resembled a beetle while remaining lightweight enough for wall-climbing. The completed robot successfully climbed vertical walls, crawled across ceilings, and demonstrated how careful optimization of weight distribution, structural design and airflow adjustment can produce an effective miniature wall-climbing beetle robot.
Web Application
Below is a detailed description of the mobile web app that we use to control the beetle.

After the code is flashed into the ESP32 board using Arduino IDE, the serial monitor displays the IP Address of the ESP32. Enter it in your browser, and you’ll be taken to the following Web page. It has a really simple interface. starts with the BLDC motor control. Then you have the controls for the N20 motors as well. For Forward, both the motors spin in the CCW direction; for Backwards, both spin in the CW direction; for Left, the left motor spins in CCW and the right motor in the CW direction and vice versa for Right. For Stop, both motors stop spinning. There are several other similar kinds of IoT based projects that we have made. All of them are listed on our website as IoT projects.
Code Explanation
Below is the detailed code explanation for this project.
#include <WiFi.h>
#include <WebServer.h>
const char* ssid = "wifi_name";
const char* password = "wifi_password7";
WebServer server(80);
// ESC
const int escPin = 4;
const int pwmFreq = 50;
const int pwmResolution = 16;
// MX1508
const int L1 = 16;
const int L2 = 17;
const int R1 = 18;
const int R2 = 19;
bool bldcEnabled = false;
int throttle = 0;
uint32_t usToDuty(uint16_t us)
{
uint32_t maxDuty = (1UL << pwmResolution) - 1;
return (uint32_t)((us / 20000.0) * maxDuty);
}Libraries initialized, WiFi and GPIO pins configured for the BLDC motor and the N20 Motors.
This function “uint32_t usToDuty” converts the pulse width (in microseconds) into the corresponding PWM duty cycle required by the ESC.
void setThrottle(int percent)
{
if (!bldcEnabled)
{
ledcWrite(escPin, usToDuty(1000));
return;
}
percent = constrain(percent, 0, 100);
uint16_t pulse =
map(percent, 0, 100, 1000, 2000);
ledcWrite(escPin, usToDuty(pulse));
}This function controls the BLDC motor speed from 0 to 100%
void stopRobot()
{
digitalWrite(L1, LOW);
digitalWrite(L2, LOW);
digitalWrite(R1, LOW);
digitalWrite(R2, LOW);
}
void forward()
{
digitalWrite(L1, HIGH);
digitalWrite(L2, LOW);
digitalWrite(R1, HIGH);
digitalWrite(R2, LOW);
}
void backward()
{
digitalWrite(L1, LOW);
digitalWrite(L2, HIGH);
digitalWrite(R1, LOW);
digitalWrite(R2, HIGH);
}
void left()
{
digitalWrite(L1, LOW);
digitalWrite(L2, HIGH);
digitalWrite(R1, HIGH);
digitalWrite(R2, LOW);
}
void right()
{
digitalWrite(L1, HIGH);
digitalWrite(L2, LOW);
digitalWrite(R1, LOW);
digitalWrite(R2, HIGH);
}The above functions are responsible for stopping the robot, moving forward, backwards, left and right. It functions by turning the corresponding GPIO pins High/Low.
void handleRoot()
{
}This function generates the HTML web page that contains the clickable buttons for controlling the Beetle Robot.
void handleSpeed()
{
throttle =
server.arg("value").toInt();
setThrottle(throttle);
server.send(200, "text/plain", "OK");
}
void handleBLDCOn()
{
bldcEnabled = true;
setThrottle(throttle);
server.send(200, "text/plain", "ON");
}
This function executes whenever the browser sends a request to /speed and the speed value is extracted, converted into an integer and passed to setThrottle() to update the ESC throttle.
When the ON button is pressed, the variable “bldcEnabled” allows the ESC to receive throttle values.
void setup()
{
}
The setup() initialises serial communication, configures the motor pins as outputs, prints the assigned IP address on the serial monitor and starts the web server.
void loop()
{
server.handleClient();
}The function of loop() here is to continuously check whether any browser has sent a new HTTP request.
Final Result
Below is how we got the final results and how efficiently it operates.

Despite its compact size, the robot generates more than 360 grams of holding force, weighs only around 104 grams, and is capable of climbing walls and ceilings using only a single BLDC motor for suction. The project also demonstrates an iterative engineering approach, where multiple failed prototypes ultimately led to a successful design. Along the way, techniques such as suction testing, lightweight optimization, AI-assisted concept visualization, Voronoi modelling, and mesh unfolding were combined to create a functional and visually unique beetle-inspired robot. In addition to this Beetle project, here is a collection related to robotics projects for reference.
Troubleshooting
Robot vibrating too much on the ceiling.
The cause is that the suction is not enough. You need to increase the suction by increasing the slider value in the web app, which keeps the beetle firm to the ceiling.
The BLDC motor restarts continuously and makes startup beep sequences and reboots.
The battery should be low, which is not able to provide enough power for the motor operation. Recharge your battery.
I built the robot, but it’s not sticking to walls or ceilings.
Check your wheel clearances. Keep the wheel diameter minimum so that the body to wall/ceiling distance is minimum.
Conclusion
This project shows that building a compact wall-climbing robot is not just about using a powerful motor, but it requires careful balancing of airflow, structural weight and mechanical design. By testing multiple enclosure designs, optimizing the body thickness, and developing a lightweight Voronoi-inspired shell, we were able to build a palm-sized robot capable of climbing walls and crawling across ceilings.
While there is still room for improvement, such as using a custom PCB, lighter electronics, and more advanced controls, this prototype serves as a solid foundation for future versions. It also demonstrates how we can turn a challenging idea into a working engineering project. Further, one can extend this project to make it a floor cleaning robot or a ceiling cleaning robot, just like this Arduino-based Vacuum Cleaner Robot that we have built some time back.
ESP32 Wall Climbing Robot GitHub
This repository provides the complete source code and documentation for building an ESP32-based wall-climbing robot. It enables users to replicate, modify, and enhance the robot's design and functionality for research and development.
Frequently Asked Questions
⇥ Can I add a battery with a much higher capacity?
Yes, but you need to maintain a balance between the battery weight and the max suction that the motor provides.
⇥ How long can I run the robot?
Currently, this robot gives a runtime of around 8 minutes. It can be further enhanced by a much optimised design, a better battery or a BLDC motor.
⇥ Can it climb up rough terrains?
Yes, it can climb up rough or normal terrains with ease.
DIY Robotics Projects You Can Build Today
Take your robotics skills to the next level with step-by-step guides for building a spider robot, a biped walking robot, and a Bluetooth robot car using Arduino and ESP32.
Designing and Controlling Quadruped Spider Robot with ESP32
One such remarkable creation is the spider robot. It can also known as a quadruped robot due to its four legs. This innovative robotic project aims to emulate the locomotion and behavior of spiders.
Biped Catbot: Walking and Dancing Robot using Arduino
Catbot, the star of our project, is a remarkable two-legged robot built on the Arduino Nano. It utilizes five servo motors, with two dedicated to each leg and one controlling its head movements. Additionally, it employs an ultrasonic sensor to detect obstacles and navigate around them.
DIY Arduino Bluetooth Car Controlled by Mobile Application
This robot car can be controlled wirelessly with Bluetooth and an Android app, and other than that, we have placed RGB Neopixel LEDs on the front, back, and bottom of the robot to make it look cool.
Complete Project Code
#include <WiFi.h>
#include <WebServer.h>
const char* ssid = "your_ssid";
const char* password = "your-password";
WebServer server(80);
// ESC
const int escPin = 4;
const int pwmFreq = 50;
const int pwmResolution = 16;
// MX1508
const int L1 = 16;
const int L2 = 17;
const int R1 = 18;
const int R2 = 19;
bool bldcEnabled = false;
int throttle = 0;
uint32_t usToDuty(uint16_t us)
{
uint32_t maxDuty = (1UL << pwmResolution) - 1;
return (uint32_t)((us / 20000.0) * maxDuty);
}
void setThrottle(int percent)
{
if (!bldcEnabled)
{
ledcWrite(escPin, usToDuty(1000));
return;
}
percent = constrain(percent, 0, 100);
uint16_t pulse =
map(percent, 0, 100, 1000, 2000);
ledcWrite(escPin, usToDuty(pulse));
}
// --------------------
// Robot Movement
// --------------------
void stopRobot()
{
digitalWrite(L1, LOW);
digitalWrite(L2, LOW);
digitalWrite(R1, LOW);
digitalWrite(R2, LOW);
}
void forward()
{
digitalWrite(L1, HIGH);
digitalWrite(L2, LOW);
digitalWrite(R1, HIGH);
digitalWrite(R2, LOW);
}
void backward()
{
digitalWrite(L1, LOW);
digitalWrite(L2, HIGH);
digitalWrite(R1, LOW);
digitalWrite(R2, HIGH);
}
void left()
{
digitalWrite(L1, LOW);
digitalWrite(L2, HIGH);
digitalWrite(R1, HIGH);
digitalWrite(R2, LOW);
}
void right()
{
digitalWrite(L1, HIGH);
digitalWrite(L2, LOW);
digitalWrite(R1, LOW);
digitalWrite(R2, HIGH);
}
void handleRoot()
{
String page = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body{
font-family:Arial;
background:#f4f4f4;
text-align:center;
}
h2{
margin-top:20px;
}
.blueBtn{
background:#007BFF;
color:white;
border:none;
padding:15px 28px;
font-size:18px;
border-radius:8px;
margin:8px;
cursor:pointer;
}
.blueBtn:hover{
background:#0056b3;
}
.slider{
width:85%;
margin-top:10px;
}
#val{
color:#007BFF;
}
.controller{
width:260px;
margin:25px auto;
}
.row{
display:flex;
justify-content:center;
margin:8px 0;
}
.ctrlBtn{
width:80px;
height:80px;
font-size:18px;
border:none;
border-radius:12px;
background:#4CAF50;
color:white;
cursor:pointer;
margin:5px;
}
.stopBtn{
background:#E53935;
}
.ctrlBtn:hover{
opacity:0.85;
}
</style>
</head>
<body>
<h2>Wall Climbing Robot</h2>
<h3>BLDC Suction Control</h3>
<button class="blueBtn"
onclick="fetch('/bldcOn')">
ON
</button>
<button class="blueBtn"
onclick="fetch('/bldcOff')">
OFF
</button>
<br><br>
<input
type="range"
min="0"
max="100"
value="0"
class="slider"
id="speed">
<h2 id="val">0%</h2>
<hr>
<h3>Movement Control</h3>
<div class="controller">
<div class="row">
<button class="ctrlBtn"
onclick="fetch('/forward')">
FWD
</button>
</div>
<div class="row">
<button class="ctrlBtn"
onclick="fetch('/left')">
LEFT
</button>
<button class="ctrlBtn stopBtn"
onclick="fetch('/stop')">
STOP
</button>
<button class="ctrlBtn"
onclick="fetch('/right')">
RIGHT
</button>
</div>
<div class="row">
<button class="ctrlBtn"
onclick="fetch('/backward')">
BKWD
</button>
</div>
</div>
<script>
var slider=document.getElementById("speed");
var val=document.getElementById("val");
slider.oninput=function(){
val.innerHTML=this.value+" %";
fetch("/speed?value="+this.value);
}
</script>
</body>
</html>
)rawliteral";
server.send(200, "text/html", page);
}
// --------------------
// Web Requests
// --------------------
void handleSpeed()
{
throttle =
server.arg("value").toInt();
setThrottle(throttle);
server.send(200, "text/plain", "OK");
}
void handleBLDCOn()
{
bldcEnabled = true;
setThrottle(throttle);
server.send(200, "text/plain", "ON");
}
void handleBLDCOff()
{
bldcEnabled = false;
setThrottle(0);
server.send(200, "text/plain", "OFF");
}
// --------------------
// Setup
// --------------------
void setup()
{
Serial.begin(115200);
Serial.println("Serial connected successfully!");
pinMode(L1, OUTPUT);
pinMode(L2, OUTPUT);
pinMode(R1, OUTPUT);
pinMode(R2, OUTPUT);
stopRobot();
ledcAttach(
escPin,
pwmFreq,
pwmResolution);
ledcWrite(
escPin,
usToDuty(1000));
delay(5000); // arm ESC
WiFi.begin(ssid, password);
while(WiFi.status()!=WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.on("/speed", handleSpeed);
server.on("/bldcOn", handleBLDCOn);
server.on("/bldcOff", handleBLDCOff);
server.on("/forward",
[](){ forward(); server.send(200); });
server.on("/backward",
[](){ backward(); server.send(200); });
server.on("/left",
[](){ left(); server.send(200); });
server.on("/right",
[](){ right(); server.send(200); });
server.on("/stop",
[](){ stopRobot(); server.send(200); });
server.begin();
}
void loop()
{
server.handleClient();
}


