This is a modified version of the MultiWii brushed drone flight controller, adapted for the ESP32 MH ET Live MiniKit development board with ESP-NOW wireless communication.
- ESP32 MH ET Live MiniKit (or compatible ESP32 DevKit)
- MPU6050 - 6-axis IMU (Gyroscope + Accelerometer)
- Connected via I2C
- SDA: GPIO21
- SCL: GPIO22
- VCC: 3.3V
- GND: GND
- AD0: Connect to GND (sets I2C address to 0x68)
IMPORTANT - MPU6050 AD0 Pin: The AD0 pin on the MPU6050 determines its I2C address:
- AD0 → GND: Address = 0x68 (default, used by this firmware)
- AD0 → VCC: Address = 0x69
If AD0 is left floating (unconnected), the address may be unstable and cause I2C communication failures. Always connect AD0 to GND.
- 4x Brushed DC motors with compatible ESCs
- Motor connections:
- Motor 1 (Rear Right): GPIO13
- Motor 2 (Front Right): GPIO25
- Motor 3 (Rear Left): GPIO14
- Motor 4 (Front Left): GPIO27
- Buzzer: GPIO32 (optional but recommended)
- LED: GPIO33
- Battery Voltage Monitor: GPIO34 (ADC input)
The ESP32 GPIO can only source ~12mA, which isn't enough for most buzzers. Use a 2N2222 NPN transistor to drive the buzzer:
Schematic:
+3.3V (or +5V)
│
│
[Buzzer]
│
├───────|◁────────┐ (1N4148 flyback diode, cathode to +V)
│ │
C │
┌─────┤ 2N2222 │
│ B │
│ │ │
│ [1kΩ] │
│ │ │
│ GPIO 32 │
│ │
E │
│ │
GND ─────────────────────┘
Components:
| Component | Value | Purpose |
|---|---|---|
| R1 | 1kΩ | Base current limiter |
| Q1 | 2N2222 | NPN switching transistor |
| D1 | 1N4148 | Flyback diode (optional, recommended for magnetic buzzers) |
| Buzzer | 3.3V-5V | Passive buzzer recommended (responds to 2kHz PWM) |
2N2222 Pinout (TO-92 package, flat side facing you):
E B C
│ │ │
└──┴──┘
- E (Emitter, left) → GND
- B (Base, center) → 1kΩ resistor → GPIO 32
- C (Collector, right) → Buzzer negative terminal
Notes:
- Use a passive buzzer for best results - it will produce a 2kHz tone from the PWM signal
- Active buzzers work but the PWM is unnecessary (simple HIGH/LOW would suffice)
- The flyback diode protects the transistor from back-EMF (only needed for magnetic buzzers, not piezo)
- Buzzer positive terminal connects to +3.3V or +5V depending on buzzer rating
| Component | GPIO Pin | Notes |
|---|---|---|
| MPU6050 SDA | 21 | I2C Data |
| MPU6050 SCL | 22 | I2C Clock |
| Motor 1 | 13 | PWM Output (LEDC Channel 0) - Safe pin |
| Motor 2 | 25 | PWM Output (LEDC Channel 1) - Safe pin |
| Motor 3 | 14 | PWM Output (LEDC Channel 2) - Safe pin |
| Motor 4 | 27 | PWM Output (LEDC Channel 3) - Safe pin |
| Buzzer | 32 | PWM Output (LEDC Channel 4) |
| LED | 33 | Digital Output - Safe pin |
| Battery Voltage | 34 | ADC1_CH6 (input only) |
The motors are arranged in an X configuration with the following positions:
| Motor # | GPIO Pin | Physical Position | Rotation Direction |
|---|---|---|---|
| Motor 1 | 13 | Rear Right | CW (Clockwise) |
| Motor 2 | 25 | Front Right | CCW (Counter-Clockwise) |
| Motor 3 | 14 | Rear Left | CCW (Counter-Clockwise) |
| Motor 4 | 27 | Front Left | CW (Clockwise) |
FRONT
↑
M4 ⟲ ⟳ M2
[27] [25]
\ ✈ /
/ \
[14] [13]
M3 ⟲ ⟳ M1
REAR
Legend:
- ⟳ = Clockwise (CW) rotation
- ⟲ = Counter-Clockwise (CCW) rotation
- Numbers in brackets [ ] = GPIO pins
Important: Diagonal motors spin in the same direction. This configuration provides yaw control through differential thrust.
| Power Source | Max Current | Suitable For |
|---|---|---|
| USB (5V) | ~500mA | Configuration, IMU calibration, communication testing |
| 1S LiPo (3.7V) | 5-10A+ | Full motor operation (REQUIRED) |
- ESP32 resets (brown-out detection triggered)
- Erratic behavior
- Potential damage to USB port
Always connect a LiPo battery before arming the drone.
- ✅ ESP-NOW communication testing
- ✅ IMU/gyro calibration
- ✅ MultiWiiConf GUI connection
- ✅ LED and buzzer testing
- ✅ Configuration changes
- ❌ Motor spinning (will reset ESP32)
4 brushed motors can draw 2-4A total at full throttle, with peak currents even higher during startup. A 1S LiPo with at least 25C rating is recommended.
The drone uses GPIO34 (ADC1_CH6) for battery voltage monitoring. For a 1S LiPo battery (3.0V - 4.2V):
Since 4.2V exceeds the ESP32's safe ADC range (3.6V max with 11dB attenuation), use a simple voltage divider:
Circuit:
Battery+ ----[R1: 10kΩ]----+----[R2: 10kΩ]---- GND
|
GPIO34
Components:
- R1: 10kΩ resistor (1% tolerance preferred)
- R2: 10kΩ resistor (1% tolerance preferred)
Result:
- 4.2V battery → 2.1V at GPIO34 ✓ Safe!
- 3.0V battery → 1.5V at GPIO34 ✓ Safe!
If you don't want a voltage divider, you can rely on the ESP32's 11dB attenuation, but:
⚠️ Max safe voltage: ~3.6V⚠️ Full charge (4.2V) will exceed this and may damage the ADC⚠️ Not recommended unless you strictly limit battery voltage to 3.5V
After building your voltage divider:
- Measure actual battery voltage with a multimeter (e.g., 4.15V)
- Upload code and read the reported voltage via serial monitor or configurator
- Calculate VBATSCALE:
VBATSCALE = (reported_value × 10) / actual_voltage - Update config.h line 925 with the new VBATSCALE value
- Re-upload and verify the voltage matches your multimeter
Default VBATSCALE: 42 (may need adjustment based on your voltage divider)
Update these values in config.h lines 927-929:
- VBATLEVEL_WARN1: 37 (3.7V - low battery warning)
- VBATLEVEL_WARN2: 35 (3.5V - very low battery)
- VBATLEVEL_CRIT: 33 (3.3V - critical, land immediately!)
Note: Values are in 0.1V units (e.g., 37 = 3.7V)
The MPU6050 communicates with the ESP32 via I2C. Proper wiring is critical for reliable gyro/accelerometer data.
Unlike differential signals (USB, Ethernet), I2C is single-ended. Twisting SDA and SCL together causes:
- Capacitive coupling between lines - clock edges induce noise on data
- Crosstalk - fast SCL transitions corrupt SDA readings
- Increased capacitance - slows signal edges, causes communication failures
- Keep wires short - under 10cm is ideal for 400kHz I2C
- Run SDA and SCL parallel with 1-2mm gap between them
- Include a ground wire running alongside (reduces noise pickup)
- Route away from motor wires and the ESP32 antenna area
- Use shielded cable for longer runs (shield grounded at one end only)
The MPU6050 module typically includes 4.7kΩ pull-ups. If using a bare chip:
- Add 4.7kΩ pull-up resistors from SDA and SCL to 3.3V
- For longer wires, use 2.2kΩ pull-ups
If mounting the IMU above the ESP32:
- Vibration isolation is critical - use foam tape or rubber standoffs
- Heat from the ESP32 can cause gyro drift - maximize airflow
- EMI from WiFi radio may affect readings - consider a ground plane between them
The motor PWM frequency affects performance characteristics of brushed motors.
#define PWM_FREQUENCY 16000 // 16kHz - balanced settingEdit in Output_ESP32.cpp:16
| Frequency | Pros | Cons |
|---|---|---|
| 8kHz | Maximum torque, best low-speed response | Audible whine, motor heating |
| 16kHz | Good torque, minimal audible noise | Slight efficiency loss vs 8kHz |
| 32kHz | Silent operation, less motor heating | Reduced low-speed torque |
Lower PWM frequencies allow higher peak currents during each pulse:
- More time for current to ramp up through motor inductance
- Higher peak current = stronger magnetic field = more torque
- Better responsiveness at low throttle (important for hover stability)
Higher frequencies spread current more evenly:
- Smoother motor operation
- Less audible noise (above human hearing at 20kHz+)
- Less efficient due to switching losses
- 16kHz - Best balance for most builds (current default)
- 8kHz - If you need more low-end punch and don't mind motor whine
- 32kHz - If noise is a priority and you have headroom on thrust
Choosing the right propeller affects flight characteristics significantly.
| Blades | Efficiency | Thrust | Response | Noise |
|---|---|---|---|---|
| 2-blade | Best | Lower | Slower | Quietest |
| 3-blade | Good | Medium | Medium | Medium |
| 4-blade | Lower | Highest | Fastest | Loudest |
2-blade props:
- Most efficient (less blade interference)
- Smoother, quieter operation
- Best for longer flight times
- Slower response to throttle changes
3-blade props:
- Good compromise between efficiency and thrust
- Popular for freestyle/general use
- Moderate noise levels
4-blade props:
- Maximum thrust from given diameter
- Fastest throttle response
- Best for heavy builds or aggressive flying
- Shortest flight times, loudest
Comparing 46mm 2-blade vs 31mm 3-blade:
| Property | 46mm 2-blade | 31mm 3-blade |
|---|---|---|
| Disc area | 1662 mm² | 755 mm² |
| Static thrust | Higher | Lower |
| Efficiency | Better | Worse |
| Response | Slower | Faster |
| Motor load | Higher | Lower |
| Noise | Quieter | Higher pitch |
46mm 2-blade advantages:
- 2.2x more disc area = more air moved = more lift
- Better hover efficiency
- Quieter at same thrust level
31mm 3-blade advantages:
- Lower rotational inertia = faster RPM changes
- Snappier response for acrobatic flying
- Less stress on motors (lower torque required)
- Fits in tighter spaces
For a brushed micro quad:
- Larger 2-blade (40-55mm) - Best for efficiency and flight time
- Smaller 3-blade (31-40mm) - Better for indoor flying and quick maneuvers
Match prop size to your motor's recommended range. Oversized props will overheat motors; undersized props waste motor capability.
Download and install the latest Arduino IDE from arduino.cc
Required Version: ESP32 Arduino Core 3.0.7 (or compatible 3.x version)
- Open Arduino IDE
- Go to File > Preferences
- Add this URL to "Additional Board Manager URLs":
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - Go to Tools > Board > Boards Manager
- Search for "ESP32" and install "ESP32 by Espressif Systems" version 3.0.7
Important: This code uses ESP32 Core 3.x API features:
ledcAttach(pin, frequency, resolution)- newer APIledcWrite(pin, duty_cycle)- uses pin numbers instead of channels- If using Core 2.x, you'll need to modify the PWM code in Output_ESP32.cpp
Go to Tools > Manage Libraries and install:
- Wire (usually pre-installed with ESP32)
- WiFi (included with ESP32 board support)
- Tools > Board > ESP32 Arduino > ESP32 Dev Module
- Tools > Upload Speed > 115200 (or higher if stable)
- Tools > CPU Frequency > 240MHz (recommended for flight controller)
- Tools > Flash Frequency > 80MHz
- Tools > Flash Size > 4MB (32Mb)
- Tools > Partition Scheme > Default 4MB with spiffs
- Tools > Port > Select your ESP32's COM port
ESP-NOW is a connectionless Wi-Fi communication protocol developed by Espressif. It provides:
- Low latency (1-10ms typical)
- No need for Wi-Fi router
- Up to 250-byte payloads
- Low power consumption
- Simple peer-to-peer communication
You'll need a separate ESP32 board configured as a transmitter. Two transmitter variants are available:
- NEW_FPV_Transmitter_Code_ESP32_ESPNOW - Standard spring-return joysticks
- Transmitter_ESP32_No_Spring_Joy - No-spring throttle (linear mapping)
The transmitter must send RC data in this structure (7 bytes):
typedef struct __attribute__((packed)) struct_message {
uint8_t throttle; // 0-255, mapped to 1000-2000µs
uint8_t yaw; // 0-255, mapped to 2000-1000µs (inverted)
uint8_t pitch; // 0-255, mapped to 1000-2000µs
uint8_t roll; // 0-255, mapped to 2000-1000µs (inverted)
uint8_t AUX1; // 0 or 1, flight mode switch
uint8_t AUX2; // 0 or 1, secondary function
uint8_t switches; // Bit flags: bit0=left btn, bit1=right btn
} struct_message;The drone sends telemetry back (11 bytes):
typedef struct __attribute__((packed)) struct_ack {
uint8_t vbat; // Battery voltage (0.1V units, 37 = 3.7V)
uint8_t rssi; // Signal strength (0-100%)
int16_t heading; // Compass heading (degrees)
int16_t pitch; // Pitch angle (0.1 degrees)
int16_t roll; // Roll angle (0.1 degrees)
int16_t alt; // Altitude (cm)
uint8_t flags; // bit0=armed, bit1=angle, bit2=horizon, bit3=baro
} struct_ack;CRITICAL: Both structures must use __attribute__((packed)) to ensure binary compatibility between transmitter and receiver.
When you first upload and run the code:
- Open Serial Monitor (Tools > Serial Monitor)
- Set baud rate to 115200
- The MAC address will be printed:
ESP32 Receiver MAC Address: XX:XX:XX:XX:XX:XX - Use this MAC address to pair your transmitter
Edit config.h to customize your drone:
-
Frame Type (Line 39):
#define QUADX // For X-configuration quadcopter
-
Motor Settings (Lines 63-72):
#define MINTHROTTLE 1000 #define MAXTHROTTLE 2000 #define MINCOMMAND 1000
-
PID Tuning: Adjust PID values for stable flight
Pin definitions are in config.h starting at line 501:
#define ESP32_I2C_SDA 21
#define ESP32_I2C_SCL 22
#define ESP32_MOTOR1_PIN 13
#define ESP32_MOTOR2_PIN 25
#define ESP32_MOTOR3_PIN 14
#define ESP32_MOTOR4_PIN 27
#define ESP32_BUZZER_PIN 32
// etc...The ESP32 uses LEDC (LED Controller) for PWM generation:
- Frequency: 16kHz (optimized for brushed motors - see PWM Frequency Tuning for details)
- Resolution: 10-bit (0-1023)
- Channels: 0-3 for motors, 4 for buzzer
To change PWM frequency, edit Output_ESP32.cpp:16:
#define PWM_FREQUENCY 16000 // 8kHz=more torque, 32kHz=quieter- Connect ESP32 to your computer via USB
- Select the correct COM port (Tools > Port)
- Press and hold the BOOT button on ESP32 (if upload fails)
- Click Upload button in Arduino IDE
- Wait for "Hard resetting via RTS pin..." message
- Open Serial Monitor to see initialization messages
- Check motor connections to correct GPIO pins
- Verify motor power supply is connected
- Check PWM frequency setting
- Ensure ESCs are compatible with signal
- Check I2C connections (SDA=21, SCL=22)
- Verify MPU6050 is powered with 3.3V (NOT 5V!)
- Run I2C scanner to find MPU6050 address
- Check pull-up resistors on I2C lines
- Verify both ESP32 boards are using same Wi-Fi channel
- Check MAC address is correctly set in transmitter
- Ensure transmitter is sending data in correct format
- Check Serial Monitor for "ESP-NOW Initialized Successfully"
- Ensure ESP32 board support is installed correctly
- Check that all header files are in the same folder
- Verify config.h has
#define ESP_NOW_RXenabled - Make sure you selected "ESP32 Dev Module" as board
Default channel mapping (defined in ESP_NOW_RX.cpp):
| Channel | Name | Range | Source |
|---|---|---|---|
| 1 | THROTTLE | 1000-2000 | Left stick Y |
| 2 | YAW | 2000-1000 (inverted) | Left stick X |
| 3 | PITCH | 1000-2000 | Right stick Y |
| 4 | ROLL | 2000-1000 (inverted) | Right stick X |
| 5 | AUX1 | 1000/2000 | Toggle switch 1 |
| 6 | AUX2 | 1000/2000 | Toggle switch 2 |
| 7 | AUX3 | 1000/2000 | Left joystick button |
| 8 | AUX4 | 1000/2000 | Right joystick button |
Joystick Buttons: The switches byte from the transmitter is mapped to AUX3/AUX4:
- bit0 (left button) → AUX3
- bit1 (right button) → AUX4 (can be used for BOXBEEPERON)
To invert a channel, swap the map() function parameters.
- If signal is lost for >1 second, all channels reset to safe values
- Throttle goes to 0
- All other channels center to 128 (1500µs equivalent)
Arming Thresholds:
- MINCHECK = 1100 (stick considered "low" below this)
- MAXCHECK = 1900 (stick considered "high" above this)
- Stick values are mapped from 0-255 (transmitter) to 1000-2000 (RC PWM)
To ARM the drone:
- Move the left joystick to the bottom-right corner
- Hold for ~0.4 seconds (20 cycles)
- This means: Throttle DOWN (low) + Yaw RIGHT (high)
- LED pattern will change and buzzer may beep to indicate armed state
To DISARM the drone:
- Move the left joystick to the bottom-left corner
- Hold for ~0.4 seconds
- This means: Throttle DOWN (low) + Yaw LEFT (low)
Why both DOWN and RIGHT? The joystick has two independent axes (two potentiometers):
- Y-axis (vertical) controls Throttle
- X-axis (horizontal) controls Yaw
Moving to the corner activates both axes simultaneously - they're independent measurements.
Configuration:
- Configured in
config.h - Default:
#define ALLOW_ARM_DISARM_VIA_TX_YAW(throttle down + yaw) - Alternative:
#define ALLOW_ARM_DISARM_VIA_TX_ROLL(throttle down + roll)
Safety: ONLYARMWHENFLAT prevents arming when the drone is tilted.
- Loop Time: ~2800µs (configured in config.h)
- ESP-NOW Latency: 1-10ms typical
- I2C Speed: 400kHz
- CPU Speed: 240MHz recommended
- MultiWii_RF24.ino - Main sketch file
- config.h - Main configuration file
- ESP_NOW_RX.cpp/.h - ESP-NOW receiver implementation
- Output_ESP32.cpp - ESP32 LEDC PWM output
- Sensors.cpp - Updated with ESP32 I2C support
- MultiWii.cpp - Flight controller main loop
- RX.cpp - Updated to support ESP-NOW
- Original MultiWii by Alexandre Dubus
- Modified for brushed drone by iforced2D
- NRF24 version by ELECTRONOOBS
- ESP32 adaptation (2026)
This project is licensed under GPL (GNU General Public License) as inherited from the original MultiWii project.
- Original Tutorial: http://www.electronoobs.com/eng_arduino_tut117.php
- MultiWii Wiki: http://www.multiwii.com/wiki
- ESP32 Documentation: https://docs.espressif.com/
- ESP-NOW Guide: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html
- Always remove propellers when testing on a bench
- Test in a safe environment away from people and obstacles
- Start with low throttle and gradually increase
- Have an emergency cutoff plan
- Never fly over people or near airports
- Follow local drone regulations
📝 Before First Flight:
- Calibrate IMU (level surface required)
- Test all motor directions
- Verify channel directions match your transmitter
- Test failsafe behavior
- Tune PID values for your specific setup
- Perform extensive ground testing
- 2026 - ESP32 MH ET Live MiniKit version with ESP-NOW
- 2020 - ELECTRONOOBS NRF24 version
- Original - MultiWii brushed drone by iforced2D