Community project
ESP32 Quadcopter Drone Controller
Build a fully functional quadcopter flight controller powered by the ESP32 microcontroller. This project combines a 3-axis IMU for attitude sensing, RC receiver input for pilot commands, and four brushed DC motors with MOSFET gate drivers to create a stable flying platform. The guide provides a complete wiring diagram, detailed parts list, and step-by-step assembly instructions to get the drone airborne.
The controller reads gyroscope and accelerometer data from the MPU-6500 IMU via SPI, processes incoming RC commands through the SBUS/UART receiver, and outputs PWM signals to four motor driver circuits. Each motor is protected with a Schottky flyback diode and gate pull-down resistor to ensure safe switching. The LiPo battery is boosted to supply stable power throughout the system, and the provided firmware handles motor mapping, pin configuration, and real-time flight stabilization.
Wiring diagram

Gather all the parts
Assemble it in 10 steps
1. Prepare the LiPo battery & boost converter
Connect the LiPo battery positive (+) to the boost converter VIN+ and negative (−) to VIN−. Adjust the MT3608 trim pot with a multimeter to set VOUT to exactly 5.0V BEFORE connecting anything else. An over-voltage output will damage the ESP32.
- Always pre-set the boost converter output to 5V before connecting the ESP32.
- Never reverse polarity on the LiPo terminals.
2. Power the ESP32
Connect boost converter VOUT+ to ESP32 VIN pin and VOUT− to ESP32 GND. The ESP32's onboard 3.3V LDO will supply the IMU and RC receiver from its 3V3 pin.
- Add a 100µF electrolytic capacitor across VBAT (LiPo+/GND) close to the motor pads to absorb motor startup spikes.
3. Wire the IMU (MPU-6500, SPI mode)
Connect the MPU-6500 breakout to the ESP32: • VCC → 3.3V • GND → GND • SCL (SCK) → GPIO18 • SDA (MOSI) → GPIO23 • SAO (MISO) → GPIO19 • NCS → GPIO5 This uses the ESP32 default VSPI bus.
- Keep SPI wires short (<10 cm) to avoid signal integrity issues at high speeds.
4. Wire the RC receiver
Connect the RC receiver: • VIN → 3.3V (or 5V if your receiver requires it — check its datasheet) • GND → GND • Signal TX → GPIO4 If your receiver outputs 5V signal levels, add a 10kΩ/20kΩ voltage divider to GPIO4.
- Check your specific receiver's logic level. A 5V signal into GPIO4 (3.3V tolerant only) will damage the ESP32.
5. Build Motor 0 protection chain (Rear Left, GPIO12)
For Motor 0 (black & white wires, rear-left position): 1. Connect ESP32 GPIO12 → 100Ω resistor IN 2. Resistor OUT → MOSFET GATE 3. MOSFET SOURCE → GND 4. MOSFET DRAIN → Motor 0 M− wire 5. Motor 0 M+ wire → LiPo VBAT rail 6. 1N5822 diode: ANODE to DRAIN node, CATHODE to VBAT rail (stripe = cathode).
- The 100N03A is a DPAK SMD part — solder it to a small PCB pad or use a breakout. D=Drain (top tab), G=Gate, S=Source.
- Verify diode orientation: stripe (cathode) must face VBAT, not GND.
6. Build Motor 1 protection chain (Rear Right, GPIO13)
Repeat the same 6-step chain as Motor 0 for Motor 1 (blue & red wires, rear-right): GPIO13 → 100Ω → GATE | SOURCE → GND | DRAIN → M− | M+ → VBAT | diode across motor.
7. Build Motor 2 protection chain (Front Right, GPIO14)
Repeat for Motor 2 (black & white wires, front-right): GPIO14 → 100Ω → GATE | SOURCE → GND | DRAIN → M− | M+ → VBAT | diode across motor.
8. Build Motor 3 protection chain (Front Left, GPIO15)
Repeat for Motor 3 (blue & red wires, front-left): GPIO15 → 100Ω → GATE | SOURCE → GND | DRAIN → M− | M+ → VBAT | diode across motor.
9. Double-check motor wire polarity
Flix uses specific wire colour conventions: • Prop B motors (CCW — M0 rear-left, M2 front-right): Black & White wires • Prop A motors (CW — M1 rear-right, M3 front-left): Blue & Red wires Connect M+ to VBAT and M− to the MOSFET DRAIN as wired. Swapping polarity reverses spin direction.
- If a motor spins the wrong way, swap its two wires — brushed motors reverse direction by swapping polarity.
10. Power-on test
With propellers OFF: 1. Power up via LiPo + boost converter. 2. Open the Schematik serial monitor (115200 baud). 3. Confirm '[FLIX] MPU-6500 IMU ready' is printed. 4. Confirm no motor twitching at boot. 5. Replace the stub loop with the real Flix firmware once wiring is confirmed.
- Never attach propellers during bench testing.
- Ensure all motor GND connections share a common GND with the ESP32.
Review all connections
1. Connections between "battery" and "ESP32"
2. Connections between "boost" and "ESP32"
3. Connections between "imu" and "ESP32"
4. Connections between "rc_receiver" and "ESP32"
5. Connections between "mosfet_m0" and "ESP32"
6. Connections between "rgate_m0" and "ESP32"
7. Connections between "motor0" and "ESP32"
8. Connections between "diode_m0" and "ESP32"
9. Connections between "mosfet_m1" and "ESP32"
10. Connections between "rgate_m1" and "ESP32"
11. Connections between "motor1" and "ESP32"
12. Connections between "diode_m1" and "ESP32"
13. Connections between "mosfet_m2" and "ESP32"
14. Connections between "rgate_m2" and "ESP32"
15. Connections between "motor2" and "ESP32"
16. Connections between "diode_m2" and "ESP32"
17. Connections between "mosfet_m3" and "ESP32"
18. Connections between "rgate_m3" and "ESP32"
19. Connections between "motor3" and "ESP32"
20. Connections between "diode_m3" and "ESP32"
Deploy the firmware
/*
* Flix-compatible Quadcopter — ESP32 Pin Definitions
*
* Motor mapping (Flix convention):
* Motor 0 — Rear Left, CCW, Prop B → GPIO12
* Motor 1 — Rear Right, CW, Prop A → GPIO13
* Motor 2 — Front Right, CCW, Prop B → GPIO14
* Motor 3 — Front Left, CW, Prop A → GPIO15
*
* IMU (MPU-6500, SPI mode):
* SCK → GPIO18
* MOSI → GPIO23
* MISO → GPIO19
* CS → GPIO5
*
* RC Receiver:
* SIGNAL (TX) → GPIO4
*
* NOTE: GPIO12, 15 are boot-strapping pins on ESP32.
* They are used as outputs here (motor PWM). All motor
* PWM outputs are LOW at boot, so no strapping conflict occurs
* in normal use. Verified consistent with Flix hardware design.
*
* NOTE: GPIO5 is also a boot-strapping pin (SPI CS).
* It is driven HIGH at boot by the internal pull-up, which is
* correct SPI CS idle state (deasserted). Safe to use.
*/
#include <Arduino.h>
#include <SPI.h>
#include <MPU6500_WE.h>
// ── Pin definitions ──────────────────────────────────────────────
#define MOTOR0_PIN 12 // Rear Left, CCW, Prop B
#define MOTOR1_PIN 13 // Rear Right, CW, Prop A
#define MOTOR2_PIN 14 // Front Right, CCW, Prop B
#define MOTOR3_PIN 15 // Front Left, CW, Prop A
#define IMU_SCK 18
#define IMU_MOSI 23
#define IMU_MISO 19
#define IMU_CS 5
#define RC_SIGNAL_PIN 4
// ── LEDC (PWM) config ─────────────────────────────────────────────
#define PWM_FREQ 20000 // 20 kHz — above audible range
#define PWM_RES 8 // 8-bit: 0–255
#define CH_M0 0
#define CH_M1 1
#define CH_M2 2
#define CH_M3 3
// ── IMU ───────────────────────────────────────────────────────────
// Forward declarations
void setMotor(uint8_t channel, uint8_t throttle);
void setAllMotors(uint8_t throttle);
MPU6500_WE imu(IMU_CS); // SPI chip-select pin
// ── Helpers ──────────────────────────────────────────────────────
void setMotor(uint8_t channel, uint8_t throttle) {
ledcWrite(channel, throttle);
}
void setAllMotors(uint8_t throttle) {
setMotor(CH_M0, throttle);
setMotor(CH_M1, throttle);
setMotor(CH_M2, throttle);
setMotor(CH_M3, throttle);
}
// ── Setup ─────────────────────────────────────────────────────────
void setup() {
Serial.begin(115200);
Serial.println("[FLIX] Booting...");
// Motors — initialise to zero throttle immediately
ledcSetup(CH_M0, PWM_FREQ, PWM_RES);
ledcSetup(CH_M1, PWM_FREQ, PWM_RES);
ledcSetup(CH_M2, PWM_FREQ, PWM_RES);
ledcSetup(CH_M3, PWM_FREQ, PWM_RES);
ledcAttachPin(MOTOR0_PIN, CH_M0);
ledcAttachPin(MOTOR1_PIN, CH_M1);
ledcAttachPin(MOTOR2_PIN, CH_M2);
ledcAttachPin(MOTOR3_PIN, CH_M3);
setAllMotors(0);
Serial.println("[FLIX] Motors initialised (all OFF)");
// SPI bus for IMU
SPI.begin(IMU_SCK, IMU_MISO, IMU_MOSI, IMU_CS);
// IMU init
if (!imu.init()) {
Serial.println("[FLIX] ERROR: MPU-6500 not found — check SPI wiring!");
while (1) { delay(500); }
}
imu.enableGyrDLPF();
imu.setGyrDLPF(MPU6500_DLPF_6);
imu.setSampleRateDivider(4); // ~200 Hz
imu.setGyrRange(MPU6500_GYRO_RANGE_500);
imu.setAccRange(MPU6500_ACC_RANGE_4G);
imu.enableAccDLPF(true);
imu.setAccDLPF(MPU6500_DLPF_6);
Serial.println("[FLIX] MPU-6500 IMU ready");
// RC receiver pin
pinMode(RC_SIGNAL_PIN, INPUT);
Serial.println("[FLIX] RC signal pin ready (GPIO4)");
Serial.println("[FLIX] Init complete. Replace this stub with Flix firmware.");
}
// ── Loop — stub (replace with Flix PID + RC loop) ─────────────────
void loop() {
// Read IMU
xyzFloat gyr = imu.getGyrValues();
xyzFloat acc = imu.getGValues();
Serial.printf("[IMU] Gyr: %.2f %.2f %.2f | Acc: %.2f %.2f %.2f\n",
gyr.x, gyr.y, gyr.z, acc.x, acc.y, acc.z);
// ── Replace below with your Flix PID + RC input logic ──
// Motor 0 (rear-left, CCW): setMotor(CH_M0, throttle0);
// Motor 1 (rear-right, CW) : setMotor(CH_M1, throttle1);
// Motor 2 (front-right, CCW): setMotor(CH_M2, throttle2);
// Motor 3 (front-left, CW) : setMotor(CH_M3, throttle3);
setAllMotors(0); // safe default: motors off
delay(5); // ~200 Hz loop rate placeholder
}Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.



