Fab Academy 2026

Final Project

Drone code: H.E.R.M.E.S

AI generated sketch for visual purposes


Slide and Edited Video


In this part is the promotional slide and video that shows in a picture and a minute the most important stuff of the project such as what does the project do, the creation of the parts, the programming, and other skills that I had learn in the FABACADMY course.


H.E.R.M.E.S. Slide

H.E.R.M.E.S. Slide.



Project Idea

My final project consists of a radio-controlled Drone vehicle built from scratch. It merges the physics of a drone with the agility of radio controlled waves (hopefully with a remote control). The propeller-powered brushless motors are mounted on a tilting mechanism (thrust vectoring), allowing the vehicle to do air movement.

The system is powered by an ESP32-C6 and controlled via custom-made control plate (With another ESP32-C6), providing a more intuitive pilot interface.

Initial Prototype Sketch

After various ideas, I made the first sketch of the prototype, detailing the propeller placement and the integration of the 3D-printed chassis.

Hand drawn sketch of the RC device
Fig 1. Functional diagram: 4-axis thrust vectoring and electronics layout.

Challenges

Building a vehicle from zero involves complex challenges in programming for stabilization, 3D design for mechanical endurance and post-processing. I am documenting every iteration, failure, and improvement in my repository to showcase the engineering cycle. Key challenges include:

"The goal is not just to build a flying machine, but to master the integration of 3D manufacturing, embedded systems and wireless control."

Project development


To start the making of the final project first of all I need to think, design and make a lot of things, and I'll be applying all the knowledge we have been learning throughout the course. I will be detailing everything later.


First Accurate Sketches


Once I started making the PCB (more details below), I drew a few sketches of the final drone according to my idea. I know that these aren't high-quality sketches but that's not my strong suit.


Servo module schematic

First real sketches of the final project.


Explaining the Name: H.E.R.M.E.S.


Before diving into the schedule and technical planning, it is important to explain the inspiration behind the name H.E.R.M.E.S. I named the drone after the ancient Greek messenger god, who was renowned for his unmatched speed and ability to travel seamlessly between worlds. I chose this name as a direct analogy for the core of the drone's communication system and what it does. By utilizing the ESP-NOW wireless protocol, the drone achieves blazing-fast, extremely low-latency communication between the custom remote control and the flight controller making it fly swiftly in the air, mirroring the swift and reliable delivery of messages by Hermes himself.


Also, the acronym for H.E.R.M.E.S. is High-speed ESP-NOW Remote Multicopter with Embedded Systems, so it fits perfectly.

Mid-Term Review: Project Schedule


For the Mid-Term review, I designed a comprehensive project schedule. Creating this timeline is crucial because it helps track technical dependencies and ensures that manufacturing bottlenecks do not halt the software development. A well-structured schedule allows me to visualize the milestones needed to complete H.E.R.M.E.S. successfully.


Timeline Task Distribution Related Week / Skills
Week 1 Hardware Foundation: PCB Designing, Milling, and 3D Design.
*Note: 3D printing runs in parallel with the PCB routing, as the printer can operate autonomously without requiring my full attention.
Electronics Production &
Computer-Aided Design
Week 2 Assembly & Debugging: Putting together the drone frame, mounting the electronics, and making initial error corrections (bug fixing). System Integration
Week 3 Remote Control: Creation of the custom RC controller interface. Input Devices
Week 4 Flight Software: Programming the PID control system for axis stabilization. Embedded Programming
Week 5 Wireless Link: Implementation of the low-latency ESP-NOW communication protocol. Networking and Communications

System Implementation & Finalization


According to the schedule, immediately after finishing the communication protocols, a strict timeframe of 3 days is dedicated exclusively to the Implementation of Everything. This phase is where the RC inputs, the ESP-NOW link, and the PID flight controller are merged into one cohesive system. Finally, the last step is the Project Finalization, which involves the final tuning, flight tests, and wrapping up this repository documentation.



PCB designing


This is the first thing I started to do, checking all the electronic components that I needed to use. After a deep research comparing a lot of components and prices, I was left with the following:


Qty Description Price (USD) Category
1 Microcontroller ESP32-C6 $ 18.00 Electronics
4 Brushless Motors Rs2205 + ESCs $ 90.00 Propulsion
1 BMI-160 Gyroscope $ 6.00 Sensors
1 4S LiPo Battery (14.8V) $ 25.00 Power
1 Energy Regulator and ESC holder $ 10.00 Power
1 Sunulu PLA filament coil $ 20.00 Structure
- Total $ 169.00 Investment

After buying every component, I started making the master PCB, and taking into account every component, the traces and the PINOUT were designed. I needed to put I2C communication for the BMI-160, a LED indicator, 4 ADC outputs for the brushless motors, a button, and a buzzer. For power, I added several pins with 5V inputs and outputs, 3.3V outputs, and a lot of GNDs.


If you want to check out with more detail how I made this PCB, go check out my Electronics Production Week →


Servo module schematic

Milled Death Star PCB.

Servo module schematic

Soldered Death Star PCB.


RC PCB


This is similar to the master PCB, but this one is a lot easier to make. With the knowledge of the Electronics Production Week, I made this RC controller that consists of a XIAO ESP32-C6, a Joystick, buttons, LEDs, and Resistors.

Because I wanted it to have the same theme as the Death Star PCB, I tried to make a circular shaped PCB (it is going to be in a Tie Fighter case):

Servo module schematic

Soldered Tie Fighter PCB.


Drone Case Designing


Once I had the measurements of my PCBs, I started to design the casings and 3D printed structures for making the drone and the RC controllers. SOLIDWORKS was the software I used for designing all the 3D models, but before that:


Casing Implications and System Integrations


In order to make a good quality case, I needed to think about the wiring placement and tolerances, and I formulated a plan for making everything fit properly.


If you want to know more about the Integration of all the final project components, you can check my System Integration Week →



3D printing


After designing and updating every part, I started the 3D printing process. It was a little bit slow because I ended up correcting the same part 5 times, but the result is pretty good.

3D Printed Chassis Parts

3D printed parts of the drone chassis and battery.

ESC Custom Cover

Custom top cover designed to hold the ESCs.


First mistakes


After printing all the parts and trying to assemble the first prototype, I encountered a few problems.


The first one was the ESC placing and wiring. Because I designed the top cover with space for 2 ESCs, the other two were getting in the way, so I had in mind changing the wiring distribution.


After taking the first problem into account, I printed all the parts I needed to assemble everything, but then something bad happened. The first prototype was useful and resistant for basic testing, but in actual practice, it was a little bit flexible and weak (SPOILER ALERT: It broke when it received an impact haha).



Drone Assembly


Once I printed the first prototype parts, I put it all together with screws and duct tape to start testing everything:


3D Printed Chassis Parts

First prototype assembly.


After assembling the first prototype I started to do tests, but during these tests is where it fell off the table and broke.


3D Printed Chassis Parts

The first prototype broken.


The Definitive Prototype


After the accident, I realized a lot of things. First of all, I needed to make stronger joints and structure for damage and impacts with the ground. Then, I had to modify all the wiring location for a more balanced and comfortable storage inside the drone, so I came up with this design:


3D Printed Chassis Parts

3D design with better ESC distribution and improved balance.

3D Printed Chassis Parts

3D design of the battery holder with holes for screws and a base for the Drone.


After printing the improved body of the Drone, I removed the support material and checked if everything fit properly for the final assembly.


3D Printed Chassis Parts

Final body of the drone.


Final Drone Assembly



3D Printed Chassis Parts

Final assembly of the drone.


RC Controller Designing


In this section, I'll be mentioning everything about the making process of the RC controller (besides the PCB making).


First, I took measurements of the PCB, the joystick, and the power switch, and then I designed the controller case in SOLIDWORKS.


3D Printed Chassis Parts

3D designed case for the controller.


After designing the part, I 3D printed it and then I placed the components that I would use on it, which are the Joystick with all its pinout, the XIAO ESP32-C6, the 3.7V rechargeable LiPo Battery, the power switch, some LEDs, and some resistors. Here in this video, I applied the knowledge that I obtained in the System Integration Week for making comfortable space to put the wires and components.




Now let's go to the most interesting and difficult part.


Making the PID controller


A Proportional, Integral, and Derivative (PID) controller is a fundamental feedback control loop mechanism in embedded systems engineering. It continuously calculates an error value as the difference between a desired setpoint and a measured process variable. For the creation of H.E.R.M.E.S., the PID is extremely important: it is literally the brain that keeps the drone's center of gravity stable in the air, compensating for external forces and successfully stabilizing both the pitch and roll axes so it doesn't crash instantly.


To tackle this massive challenge, I divided the programming and tuning process into two distinct phases: one using an isolated balancer rig, and a slightly more homemade setup, but testing with the complete drone.


Phase 1: The Balancer


First, I designed and 3D-printed a testing balancer. This allowed me to isolate a single axis of movement to deeply understand the mathematical concept of the PID loop and how to apply the correct PWM pulses to the brushless motors.


3D printing of balancer parts

3D printed parts for the testing balancer.

Assembled balancer rig

Fully assembled balancer ready for initial tuning.


If you want to dive into the technical details of how I learned and applied this calibration phase, you can visit my Week 16: Wildcard Week →


Phase 2: The Homemade Test Rig


Once I understood the theory, I moved on to a more homemade version with the fully assembled drone. Using two sticks and two heavy objects to hold the ends, I built a test rig to calculate and tune the variables (Kp, Ki, Kd) while the actual drone was attached. This gave me a much more precise range of how the system would behave with its total take-off weight.



PID Code Snippet


Below is the logic implemented for the PID control:



#include <stdio.h>
#include <string.h>
#include <math.h> 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c.h"
#include "driver/ledc.h"
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_wifi.h"
#include "esp_now.h"
#include "nvs_flash.h"

#define I2C_MASTER_SCL_IO           23
#define I2C_MASTER_SDA_IO           22
#define I2C_MASTER_NUM              0
#define I2C_MASTER_FREQ_HZ          100000
#define SENSOR_ADDR                 0x68

#define ESC_FL_GPIO                 21
#define ESC_FR_GPIO                 1 
#define ESC_RL_GPIO                 0 
#define ESC_RR_GPIO                 2 

#define LEDC_MODE                   LEDC_LOW_SPEED_MODE
#define LEDC_TIMER                  LEDC_TIMER_0
#define LEDC_DUTY_RES               LEDC_TIMER_14_BIT 
#define LEDC_FREQUENCY              50                

#define MIN_DUTY                    819   
#define MAX_DUTY                    1638  

#define MIN_FLIGHT_THROTTLE         0.01f 
#define MAX_FLIGHT_THROTTLE         0.75f 
#define RAMP_DURATION_SEC           2.0f  

float HOVER_POWER_FL = 0.37f;
float HOVER_POWER_FR = 0.45f;
float HOVER_POWER_RL = 0.32f; 
float HOVER_POWER_RR = 0.33f;

#define RAD_TO_DEG                  57.29577951f
#define ALPHA                       0.98f   
#define GYRO_SCALE                  16.4f   

float Kp_pitch = 0.00036f; 
float Ki_pitch = 0.0000003f;
float Kd_pitch = 0.00018f; // Punto de partida para el amortiguamiento

// Mismos valores para Roll por simetría, aunque estés probando Pitch
float Kp_roll = 0.00250f;
float Ki_roll = 0.0000003f;
float Kd_roll = 0.000008f;

float prev_error_pitch = 0.0f, integral_pitch = 0.0f;
float prev_error_roll = 0.0f,  integral_roll = 0.0f;

float prev_derivative_pitch = 0.0f;
float prev_derivative_roll = 0.0f;
float LPF_BETA = 0.4f; 

typedef struct struct_message {
    float joystick_x;       
    float joystick_y;       
    bool kill_switch_btn;   
} struct_message;

struct_message incoming_data;
volatile bool remote_button_pressed = false;
volatile bool flight_active = false; 

void on_data_recv(const esp_now_recv_info_t * esp_now_info, const uint8_t *data, int data_len) {
    if (data_len == sizeof(incoming_data)) {
        memcpy(&incoming_data, data, sizeof(incoming_data));
        remote_button_pressed = incoming_data.kill_switch_btn;
    }
}

static esp_err_t i2c_master_init(void) {
    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_MASTER_FREQ_HZ,
    };
    i2c_param_config(I2C_MASTER_NUM, &conf);
    return i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
}

void set_motor_speed(ledc_channel_t channel, float throttle) {
    uint32_t duty = MIN_DUTY + (uint32_t)(throttle * (MAX_DUTY - MIN_DUTY));
    ledc_set_duty(LEDC_MODE, channel, duty);
    ledc_update_duty(LEDC_MODE, channel);
}

float clamp_throttle(float value) {
    if (value < MIN_FLIGHT_THROTTLE) return MIN_FLIGHT_THROTTLE;
    if (value > MAX_FLIGHT_THROTTLE) return MAX_FLIGHT_THROTTLE;
    return value;
}

void app_main(void) {
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        nvs_flash_init();
    }
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);
    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_start();
    esp_now_init();
    esp_now_register_recv_cb((esp_now_recv_cb_t)on_data_recv);
    i2c_master_init();

    ledc_timer_config_t ledc_timer = {
        .speed_mode = LEDC_MODE, .timer_num = LEDC_TIMER,
        .duty_resolution = LEDC_DUTY_RES, .freq_hz = LEDC_FREQUENCY, .clk_cfg = LEDC_AUTO_CLK
    };
    ledc_timer_config(&ledc_timer);

    int esc_pins[4] = {ESC_FL_GPIO, ESC_FR_GPIO, ESC_RL_GPIO, ESC_RR_GPIO};
    ledc_channel_t esc_channels[4] = {LEDC_CHANNEL_0, LEDC_CHANNEL_1, LEDC_CHANNEL_2, LEDC_CHANNEL_3};

    for (int i = 0; i < 4; i++) {
        ledc_channel_config_t ledc_channel = {
            .speed_mode = LEDC_MODE, .channel = esc_channels[i], .timer_sel = LEDC_TIMER,
            .intr_type = LEDC_INTR_DISABLE, .gpio_num = esc_pins[i], .duty = MIN_DUTY, .hpoint = 0
        };
        ledc_channel_config(&ledc_channel);
    }

    for (int i = 0; i < 4; i++) set_motor_speed(esc_channels[i], 0.0f);
    
    printf("[1/4] H.E.R.M.E.S. online. Waiting 2 seconds.\n");
    vTaskDelay(pdMS_TO_TICKS(2000));
    
    uint8_t cmd_reg = 0x7E;
    uint8_t write_accel[2] = {cmd_reg, 0x11};
    uint8_t write_gyro[2]  = {cmd_reg, 0x15};
    i2c_master_write_to_device(I2C_MASTER_NUM, SENSOR_ADDR, write_accel, 2, 1000 / portTICK_PERIOD_MS);
    vTaskDelay(50 / portTICK_PERIOD_MS);
    i2c_master_write_to_device(I2C_MASTER_NUM, SENSOR_ADDR, write_gyro, 2, 1000 / portTICK_PERIOD_MS);
    vTaskDelay(100 / portTICK_PERIOD_MS);

    printf("[2/4] Preparing IMU offset calibration. KEEP H.E.R.M.E.S. STILL.\n");
    printf("Starting scan in 2 seconds...\n");
    vTaskDelay(pdMS_TO_TICKS(2000)); 
    
    printf("Scanning for 7 seconds (700 samples)... Do not touch.\n");
    float offset_x = 0.0f, offset_y = 0.0f;
    int samples = 700; 
    uint8_t reg = 0x0C, memoria[12];

    for (int i = 0; i < samples; i++) {
        if (i2c_master_write_read_device(I2C_MASTER_NUM, SENSOR_ADDR, ®, 1, memoria, 12, 100 / portTICK_PERIOD_MS) == ESP_OK) {
            int16_t accel_x_raw = (memoria[7] << 8) | memoria[6];
            int16_t accel_y_raw = (memoria[9] << 8) | memoria[8];
            int16_t accel_z_raw = (memoria[11] << 8) | memoria[10];
            offset_x += atan2f(accel_x_raw, accel_z_raw) * RAD_TO_DEG;
            offset_y += atan2f(accel_y_raw, accel_z_raw) * RAD_TO_DEG;
        }
        vTaskDelay(10 / portTICK_PERIOD_MS); 
    }
    offset_x /= samples; offset_y /= samples;
    printf("Calibration complete. Offsets stored.\n");

    printf("[3/4] Sending armed idle pulse to ESCs... Waiting 3 seconds.\n");
    vTaskDelay(pdMS_TO_TICKS(3000)); 
    printf("[4/4] H.E.R.M.E.S. PID CONTROL ACTIVE.\n");
    
    float angle_x = 0.0f, angle_y = 0.0f;
    uint64_t last_time = esp_timer_get_time();
    
    uint64_t last_print_time = esp_timer_get_time();
    const uint64_t PRINT_INTERVAL_US = 250000; 
    
    bool last_btn_state = false;
    float ramp_progress = 0.0f; 
    
    while (1) {
        bool current_btn_state = remote_button_pressed;
        if (current_btn_state && !last_btn_state) {
            flight_active = !flight_active; 
            if (flight_active) {
                ramp_progress = 0.0f; 
                integral_pitch = 0.0f;
                integral_roll = 0.0f;
                prev_derivative_pitch = 0.0f;
                prev_derivative_roll = 0.0f;
            } else {
                printf("\n>>> STOP <<<\n\n");
            }
        }
        last_btn_state = current_btn_state;

        if (i2c_master_write_read_device(I2C_MASTER_NUM, SENSOR_ADDR, ®, 1, memoria, 12, 100 / portTICK_PERIOD_MS) == ESP_OK) {
            
            uint64_t current_time = esp_timer_get_time();
            float dt = (current_time - last_time) / 1000000.0f;
            if (dt <= 0.0f) dt = 0.001f; 
            last_time = current_time;

            int16_t gyro_x_raw = (memoria[1] << 8) | memoria[0];
            int16_t gyro_y_raw = (memoria[3] << 8) | memoria[2];
            int16_t accel_x_raw = (memoria[7] << 8) | memoria[6];
            int16_t accel_y_raw = (memoria[9] << 8) | memoria[8];
            int16_t accel_z_raw = (memoria[11] << 8) | memoria[10];

            float accel_angle_x = atan2f(accel_x_raw, accel_z_raw) * RAD_TO_DEG;
            float accel_angle_y = atan2f(accel_y_raw, accel_z_raw) * RAD_TO_DEG;
            float gyro_rate_x = gyro_x_raw / GYRO_SCALE;
            float gyro_rate_y = gyro_y_raw / GYRO_SCALE;

            angle_x = ALPHA * (angle_x + gyro_rate_x * dt) + (1.0f - ALPHA) * accel_angle_x;
            angle_y = ALPHA * (angle_y + gyro_rate_y * dt) + (1.0f - ALPHA) * accel_angle_y;

            float calibrated_pitch = angle_x - offset_x; 
            float calibrated_roll = angle_y - offset_y;  

            if (current_time - last_print_time >= PRINT_INTERVAL_US) {
                printf("Pitch: %6.2f° | Roll: %6.2f°\n", calibrated_pitch, calibrated_roll);
                last_print_time = current_time;
            }

            if (flight_active) {
                if (ramp_progress < 1.0f) {
                    ramp_progress += (dt / RAMP_DURATION_SEC);
                    if (ramp_progress > 1.0f) ramp_progress = 1.0f;
                }

                float base_FL = MIN_FLIGHT_THROTTLE + ((HOVER_POWER_FL - MIN_FLIGHT_THROTTLE) * ramp_progress);
                float base_FR = MIN_FLIGHT_THROTTLE + ((HOVER_POWER_FR - MIN_FLIGHT_THROTTLE) * ramp_progress);
                float base_RL = MIN_FLIGHT_THROTTLE + ((HOVER_POWER_RL - MIN_FLIGHT_THROTTLE) * ramp_progress);
                float base_RR = MIN_FLIGHT_THROTTLE + ((HOVER_POWER_RR - MIN_FLIGHT_THROTTLE) * ramp_progress);

                float error_pitch = 0.0f - calibrated_pitch;
                float error_roll = 0.0f - calibrated_roll;

                integral_pitch += error_pitch * dt;
                if(integral_pitch > 50.0f) integral_pitch = 50.0f; else if(integral_pitch < -50.0f) integral_pitch = -50.0f;
                
                integral_roll += error_roll * dt;
                if(integral_roll > 50.0f) integral_roll = 50.0f; else if(integral_roll < -50.0f) integral_roll = -50.0f;

                float raw_derivative_pitch = (error_pitch - prev_error_pitch) / dt;
                float raw_derivative_roll = (error_roll - prev_error_roll) / dt;

                float derivative_pitch = (LPF_BETA * raw_derivative_pitch) + ((1.0f - LPF_BETA) * prev_derivative_pitch);
                float derivative_roll = (LPF_BETA * raw_derivative_roll) + ((1.0f - LPF_BETA) * prev_derivative_roll);

                prev_derivative_pitch = derivative_pitch;
                prev_derivative_roll = derivative_roll;
                prev_error_pitch = error_pitch;
                prev_error_roll = error_roll;

                float pid_pitch_output = (Kp_pitch * error_pitch) + (Ki_pitch * integral_pitch) + (Kd_pitch * derivative_pitch);
                float pid_roll_output = (Kp_roll * error_roll) + (Ki_roll * integral_roll) + (Kd_roll * derivative_roll);

                float target_FL = base_FL - pid_pitch_output - pid_roll_output;
                float target_FR = base_FR - pid_pitch_output + pid_roll_output;
                float target_RL = base_RL + pid_pitch_output - pid_roll_output;
                float target_RR = base_RR + pid_pitch_output + pid_roll_output;

                target_FL = clamp_throttle(target_FL);
                target_FR = clamp_throttle(target_FR);
                target_RL = clamp_throttle(target_RL);
                target_RR = clamp_throttle(target_RR);

                set_motor_speed(esc_channels[0], target_FL);
                set_motor_speed(esc_channels[1], target_FR);
                set_motor_speed(esc_channels[2], target_RL);
                set_motor_speed(esc_channels[3], target_RR);

            } else {
                for (int i = 0; i < 4; i++) set_motor_speed(esc_channels[i], 0.0f);
            }
        }
        vTaskDelay(10 / portTICK_PERIOD_MS); 
    }
}

Networking and Communications (ESP-NOW)


To establish a wireless link between the controller and the drone, I used the ESP-NOW protocol. This is a connectionless communication technology developed by Espressif that allows microcontrollers to communicate directly with each other without needing a Wi-Fi router. It is incredibly fast, operates with very low latency, and has a range of approximately 200 meters in open spaces, making it ideal for RC vehicles.


In this setup, the remote control acts as the sender and H.E.R.M.E.S. as the receiver. Pressing the joystick button sends a signal to start (or arm) the drone's motors. Once active, the potentiometer values from the joystick are constantly transmitted to program and control the forward movement and rotation of the drone.


RC Controller Setup

Setup of the remote controller ready for transmission.


Code for the ESP-NOW Controller


Here is the code for the ESP-NOW Tie Fighter Rc Controller, sends constantly its values for turning On and Off the propellers of the Drone:



#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_wifi.h"
#include "esp_now.h"
#include "nvs_flash.h"

#define BUTTON_PIN GPIO_NUM_20
#define LED_PIN    GPIO_NUM_15

uint8_t hermes_mac[] = {0x98, 0xA3, 0x16, 0x8D, 0xF3, 0x28};

typedef struct struct_message {
    float joystick_x;       
    float joystick_y;       
    bool kill_switch_btn;   
} struct_message;

struct_message control_data;

void on_data_sent(const uint8_t *mac_addr, esp_now_send_status_t status) {
    if (status == ESP_NOW_SEND_SUCCESS) {
    } else {
        printf("Delivery failed. Is the drone powered off?\n");
    }
}

void app_main(void) {
    gpio_config_t btn_conf = {
        .pin_bit_mask = (1ULL << BUTTON_PIN),
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = 1,    
        .pull_down_en = 0,
        .intr_type = GPIO_INTR_DISABLE
    };
    gpio_config(&btn_conf);

    gpio_config_t led_conf = {
        .pin_bit_mask = (1ULL << LED_PIN),
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = 0,
        .pull_down_en = 0,
        .intr_type = GPIO_INTR_DISABLE
    };
    gpio_config(&led_conf);
    
    gpio_set_level(LED_PIN, 1);

    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_ERROR_CHECK(esp_wifi_set_max_tx_power(80));

    if (esp_now_init() != ESP_OK) {
        printf("Fatal Error: Could not initialize ESP-NOW\n");
        return;
    }
    ESP_ERROR_CHECK(esp_now_register_send_cb((esp_now_send_cb_t)on_data_sent));

    esp_now_peer_info_t peerInfo = {};
    memcpy(peerInfo.peer_addr, hermes_mac, 6);
    peerInfo.channel = 0; 
    peerInfo.encrypt = false;
    
    if (esp_now_add_peer(&peerInfo) != ESP_OK) {
        printf("Error: Could not add H.E.R.M.E.S. to the network\n");
        return;
    }

    printf("======================================\n");
    printf("XIAO REMOTE CONTROL INITIATED\n");
    printf("Searching for drone with MAC: 98:A3:16:8D:F3:28\n");
    printf("======================================\n");

    while (1) {
        int button_state = gpio_get_level(BUTTON_PIN);
        
        control_data.kill_switch_btn = (button_state == 0) ? true : false;
        
        gpio_set_level(LED_PIN, (button_state == 0) ? 0 : 1);

        control_data.joystick_x = 0.0f;
        control_data.joystick_y = 0.0f;

        esp_now_send(hermes_mac, (uint8_t *) &control_data, sizeof(control_data));

        if(control_data.kill_switch_btn) {
            printf("KILL SWITCH PRESSED! Sending emergency signal...\n");
            vTaskDelay(pdMS_TO_TICKS(100)); 
        } else {
            vTaskDelay(pdMS_TO_TICKS(20));
        }
    }
}

And here we have a test showing the reliability of the controller and its fast connection with the drone:



Flight Tests


Once the baseline PID was calculated, it was time to fly! However, getting a vehicle built from scratch to take off and hover stably is an incredibly complicated process for a homemade project in such a short amount of time. It requires surgical precision in the code tuning to get a satisfactory result.


First take-off attempt (Fail).

Second flight attempt (Fail).


After several attempts, broken props, and adjusting variables, I managed to make the drone take off. Unfortunately, because the system still requires a much better and finer tuning, it lost stability and fell down rather quickly.


"Even though the Fab Academy program is coming to an end, the development of H.E.R.M.E.S. continues. I will take several weeks to keep refining the software, but I am committed to making a perfectly balanced drone ready for racing."

Vinyl Cutting (Subtractive Manufacturing)


To fulfill the requirement of including a subtractive manufacturing method, I decided to add some personalization to the drone. Thanks to the skills I learned during Week 3: Computer Controlled Cutting →, I designed and cut a Star Wars figure out of vinyl to decorate the chassis, perfectly matching the theme of the electronics.


Star Wars vinyl design

Star Wars vinyl figure design.



Vinyl application result

The final vinyl cut applied to the project.


Hero Shots


Here is the final visual result of my Fab Academy project, showcasing both H.E.R.M.E.S. and its custom RC controller.


H.E.R.M.E.S. Drone

H.E.R.M.E.S. fully assembled.

Custom RC Controller

The custom ESP32-C6 remote controller.


And here is the final take-off video:



*Note: I am still working on tuning the PID controller for perfect flight. If the repository can be edited in the future, I will update this section with a video of the drone flying perfectly!


Original Files (Downloads)


Here you can find all the original files created during the development of H.E.R.M.E.S. Feel free to download, study, and replicate the project for your own builds.


Category Description Download Link
3D Design (CAD) Chassis, battery holder, top cover, and RC controller case files (STL / SLDPRT). Download 3D Files ↓
Electronics (PCB) Schematics, board layouts, and Gerber files for the Master Drone board and the RC Controller. Download PCB Files ↓
Software (Code) ESP-IDF C code for the flight controller, PID tuning loop, and ESP-NOW remote link. Download Source Code ↓