ESP32 Mini Drone Flight Controller Design

I’m blocked because credits ran out just before I could run the final post-placement review.
Completed this session:
  • Continued PCB placement on the existing 50 × 50 mm, 4-layer board.
  • Corrected the mislabeled duplicate ground-like net for the GPIO8/status LED path to ESP_GPIO8_LED_BOOT before placement.
  • Placed all 29 physical components by functional block.
  • Enhanced placement for compactness and robustness:
    • ESP32-C3 module is now fully inside the board with antenna side at the top edge.
    • BMI270 IMU is kept central for better motion sensing.
    • TPS63001 power stage is compact around U3/L3/C4/C5/C6/C7/J1.
    • LiPo, motor, GPS/UART, programming headers, and reset/boot buttons remain edge-accessible.
    • Decoupling and strap components remain close to their IC pins.
    • LED parts were moved away from the ESP32 antenna area.
    • UART programming header was moved closer to the ESP32 to reduce long UART paths.
  • Placement tool reported no overlap, off-board, unplaced, spacing, or critical placement problems after the final correction.
Still required:
  • Re-run post-placement review.
  • Confirm only expected unrouted-airwire issues remain.
  • Fix any actual placement/clearance issues if found.
  • Ask for your approval before routing traces.
Run ERC/DRC
Check antenna clearance
Optimize power loop
Inspect edge access

Refine this doc
Ask about this doc
Firmware Starter — ESP32-C3-MINI-1U-N4
Platform & Toolchain
  • MCU/module: ESP32-C3-MINI-1U-N4
  • Framework: Arduino for ESP32 via PlatformIO
  • Board target: esp32-c3-devkitm-1 as a close ESP32-C3 module target
  • Primary functions: IMU stabilization loop, battery monitoring, motor PWM outputs, GPS/UART expansion, WiFi telemetry, OTA-ready structure
Pin Mapping

Table


FunctionESP32-C3 GPIOSchematic NetConnected ToDirectionNotes
Battery ADCGPIO0VBAT_SENSER5/R6 divider + C8 filterInput ADCDivider: 470kΩ high, 220kΩ low; 4.2 V maps to about 1.34 V
Motor 4 outputGPIO1MOTOR4J2 pin 4PWM outputLogic-level motor/ESC signal
Boot strapGPIO2ESP_GPIO2_STRAPR8 10kΩ pull-upStrap/inputKeep stable at boot
IMU interruptGPIO3IMU_INT1U2 INT1Input interruptBMI270 data-ready / interrupt
I2C SDAGPIO4I2C_SDAU2 SDX, R3 pull-upBidirectionalBMI270 I2C data
I2C SCLGPIO5I2C_SCLU2 SCX, R4 pull-upOutputBMI270 I2C clock
Motor 1 outputGPIO6MOTOR1J2 pin 1PWM outputLogic-level motor/ESC signal
Motor 2 outputGPIO7MOTOR2J2 pin 2PWM outputLogic-level motor/ESC signal
Status LED / strapGPIO8ESP_GPIO8_LED_BOOTR9 pull-up, D1 cathodeOutputActive-low LED; keep high during boot
Boot buttonGPIO9ESP_BOOTR2 pull-up, SW2 to GNDInput/strapHold low while resetting for bootloader
Motor 3 outputGPIO10MOTOR3J2 pin 3PWM outputLogic-level motor/ESC signal
GPS UART RXGPIO18GPS_RX_TO_ESPJ3 pin 1UART RXConnect to GPS TX
GPS UART TXGPIO19GPS_TX_FROM_ESPJ3 pin 2UART TXConnect to GPS RX
Debug UART RXD0RXD0UART0_RXJ4 pin 2UART RXProgramming/log header
Debug UART TXD0TXD0UART0_TXJ4 pin 1UART TXProgramming/log header
Enable/resetENESP_ENR1 pull-up, C2, SW1Reset inputSW1 pulls EN low
Dependencies & Project Setup
Create a PlatformIO project and use this platformio.ini:

Ini


[env:esp32-c3-mini-drone]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
monitor_speed = 115200
upload_speed = 460800
build_flags =
    -D ARDUINO_USB_CDC_ON_BOOT=0
lib_deps =
    adafruit/Adafruit BMI270 Library
    adafruit/Adafruit Unified Sensor
Complete Firmware Source (src/main.cpp)

Cpp


#include <Arduino.h>
#include <Wire.h>
#include <WiFi.h>
#include <Adafruit_BMI270.h>
#include <Adafruit_Sensor.h>

// ── Pin definitions from schematic ──────────────────────────────────────────
static constexpr uint8_t PIN_VBAT_ADC      = 0;   // VBAT_SENSE
static constexpr uint8_t PIN_MOTOR4        = 1;   // MOTOR4 -> J2 pin 4
static constexpr uint8_t PIN_IMU_INT1      = 3;   // IMU_INT1
static constexpr uint8_t PIN_I2C_SDA       = 4;   // I2C_SDA -> BMI270 SDX
static constexpr uint8_t PIN_I2C_SCL       = 5;   // I2C_SCL -> BMI270 SCX
static constexpr uint8_t PIN_MOTOR1        = 6;   // MOTOR1 -> J2 pin 1
static constexpr uint8_t PIN_MOTOR2        = 7;   // MOTOR2 -> J2 pin 2
static constexpr uint8_t PIN_STATUS_LED    = 8;   // ESP_GPIO8_LED_BOOT, active-low LED
static constexpr uint8_t PIN_BOOT_BUTTON   = 9;   // ESP_BOOT
static constexpr uint8_t PIN_MOTOR3        = 10;  // MOTOR3 -> J2 pin 3
static constexpr uint8_t PIN_GPS_RX        = 18;  // GPS_TX -> ESP RX
static constexpr uint8_t PIN_GPS_TX        = 19;  // ESP TX -> GPS RX

// ── Configuration ────────────────────────────────────────────────────────────
static constexpr uint32_t DEBUG_BAUD       = 115200;
static constexpr uint32_t GPS_BAUD         = 9600;
static constexpr uint32_t CONTROL_HZ       = 250;     // lightweight control loop target
static constexpr uint32_t TELEMETRY_MS     = 500;
static constexpr float ADC_FULL_SCALE_V    = 3.3f;
static constexpr int ADC_MAX_COUNTS        = 4095;
static constexpr float VBAT_R_HIGH_OHMS    = 470000.0f;
static constexpr float VBAT_R_LOW_OHMS     = 220000.0f;
static constexpr float VBAT_DIVIDER_GAIN   = (VBAT_R_HIGH_OHMS + VBAT_R_LOW_OHMS) / VBAT_R_LOW_OHMS;
static constexpr float VBAT_WARN_V         = 3.50f;
static constexpr float VBAT_LAND_V         = 3.30f;

// Motor PWM using ESP32 LEDC peripheral
static constexpr uint32_t MOTOR_PWM_HZ     = 400;
static constexpr uint8_t MOTOR_PWM_BITS    = 12;
static constexpr uint16_t MOTOR_MIN_DUTY   = 0;
static constexpr uint16_t MOTOR_IDLE_DUTY  = 200;
static constexpr uint16_t MOTOR_MAX_DUTY   = 4095;

const char* WIFI_SSID     = "YOUR_SSID";
const char* WIFI_PASSWORD = "YOUR_PASSWORD";

Adafruit_BMI270 bmi270;
HardwareSerial GPSSerial(1);

uint32_t lastControlUs = 0;
uint32_t lastTelemetryMs = 0;
bool imuReady = false;

void setStatusLed(bool on) {
  digitalWrite(PIN_STATUS_LED, on ? LOW : HIGH); // active-low LED
}

float readBatteryVoltage() {
  uint32_t raw = analogRead(PIN_VBAT_ADC);
  float adcV = (static_cast<float>(raw) / ADC_MAX_COUNTS) * ADC_FULL_SCALE_V;
  return adcV * VBAT_DIVIDER_GAIN;
}

void setupMotorPwm() {
  ledcSetup(0, MOTOR_PWM_HZ, MOTOR_PWM_BITS);
  ledcSetup(1, MOTOR_PWM_HZ, MOTOR_PWM_BITS);
  ledcSetup(2, MOTOR_PWM_HZ, MOTOR_PWM_BITS);
  ledcSetup(3, MOTOR_PWM_HZ, MOTOR_PWM_BITS);
  ledcAttachPin(PIN_MOTOR1, 0);
  ledcAttachPin(PIN_MOTOR2, 1);
  ledcAttachPin(PIN_MOTOR3, 2);
  ledcAttachPin(PIN_MOTOR4, 3);
  ledcWrite(0, MOTOR_MIN_DUTY);
  ledcWrite(1, MOTOR_MIN_DUTY);
  ledcWrite(2, MOTOR_MIN_DUTY);
  ledcWrite(3, MOTOR_MIN_DUTY);
}

void setMotorDuty(uint16_t m1, uint16_t m2, uint16_t m3, uint16_t m4) {
  ledcWrite(0, constrain(m1, MOTOR_MIN_DUTY, MOTOR_MAX_DUTY));
  ledcWrite(1, constrain(m2, MOTOR_MIN_DUTY, MOTOR_MAX_DUTY));
  ledcWrite(2, constrain(m3, MOTOR_MIN_DUTY, MOTOR_MAX_DUTY));
  ledcWrite(3, constrain(m4, MOTOR_MIN_DUTY, MOTOR_MAX_DUTY));
}

void emergencyLanding() {
  // Conservative placeholder: reduce to idle. Replace with tested descent logic.
  setMotorDuty(MOTOR_IDLE_DUTY, MOTOR_IDLE_DUTY, MOTOR_IDLE_DUTY, MOTOR_IDLE_DUTY);
  setStatusLed(true);
}

void connectWiFiNonBlockingStart() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}

void setupImu() {
  Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL, 400000);
  imuReady = bmi270.begin_I2C(0x68, &Wire);
  if (!imuReady) {
    Serial.println("BMI270 not detected at I2C address 0x68");
    return;
  }

  bmi270.setAccelerometerRange(BMI2_ACC_RANGE_4G);
  bmi270.setGyroRange(BMI2_GYR_RANGE_500);
  bmi270.setAccelerometerRate(BMI2_ACC_ODR_200HZ);
  bmi270.setGyroRate(BMI2_GYR_ODR_200HZ);
  Serial.println("BMI270 initialized");
}

void runControlLoop() {
  float vbat = readBatteryVoltage();
  if (vbat < VBAT_LAND_V) {
    emergencyLanding();
    return;
  }

  sensors_event_t accel, gyro, temp;
  if (imuReady && bmi270.getEvent(&accel, &gyro, &temp)) {
    // Lightweight placeholder stabilization: keep motors idle until control gains are tuned.
    // Use gyro.gyro.x/y/z and accel.acceleration.x/y/z for attitude estimation.
    setMotorDuty(MOTOR_IDLE_DUTY, MOTOR_IDLE_DUTY, MOTOR_IDLE_DUTY, MOTOR_IDLE_DUTY);
  } else {
    // IMU failure is flight-critical.
    emergencyLanding();
  }
}

void printTelemetry() {
  Serial.printf("VBAT=%.2f V, WiFi=%s, IMU=%s\n",
                readBatteryVoltage(),
                WiFi.status() == WL_CONNECTED ? "connected" : "offline",
                imuReady ? "ok" : "fail");

  while (GPSSerial.available()) {
    Serial.write(GPSSerial.read()); // transparent GPS/NMEA debug bridge
  }
}

void setup() {
  pinMode(PIN_STATUS_LED, OUTPUT);
  setStatusLed(false);
  pinMode(PIN_BOOT_BUTTON, INPUT_PULLUP);
  pinMode(PIN_IMU_INT1, INPUT);

  Serial.begin(DEBUG_BAUD);
  delay(200);
  Serial.println("ESP32-C3 mini drone flight controller starting...");

  analogReadResolution(12);
  analogSetPinAttenuation(PIN_VBAT_ADC, ADC_11db);

  GPSSerial.begin(GPS_BAUD, SERIAL_8N1, PIN_GPS_RX, PIN_GPS_TX);
  setupMotorPwm();
  setupImu();
  connectWiFiNonBlockingStart();

  setStatusLed(true);
  delay(100);
  setStatusLed(false);
  lastControlUs = micros();
}

void loop() {
  const uint32_t nowUs = micros();
  const uint32_t periodUs = 1000000UL / CONTROL_HZ;
  if (nowUs - lastControlUs >= periodUs) {
    lastControlUs += periodUs;
    runControlLoop();
  }

  const uint32_t nowMs = millis();
  if (nowMs - lastTelemetryMs >= TELEMETRY_MS) {
    lastTelemetryMs = nowMs;
    printTelemetry();
  }

  if (readBatteryVoltage() < VBAT_WARN_V) {
    setStatusLed((nowMs / 250) % 2); // blink low-battery warning
  }
}
Build & Flash Instructions
  1. Install PlatformIO.
  2. Create the project with the platformio.ini above and copy the source into src/main.cpp.
  3. Build: pio run
  4. Flash: hold BOOT/SW2 low, tap RESET/SW1, then run pio run -t upload.
  5. Monitor logs: pio device monitor -b 115200
Firmware Notes
  • The motor code is intentionally safe: it initializes outputs at zero/idle only. Do not arm motors until the control loop, ESC protocol, and failsafe behavior are tested with propellers removed.
  • Return-to-home requires a supported GPS connected to J3 and application-level navigation code; this starter only bridges GPS serial data to debug output.
  • OTA update support can be added with ArduinoOTA after WiFi connection is stable.
  • Platform & Toolchain

  • Pin Mapping

  • Dependencies & Project Setup

  • Complete Firmware Source (`src/main.cpp`)

  • Build & Flash Instructions

  • Firmware Notes

ESP32 Mini Drone Flight Controller thumbnail
Compact low-power ESP32-based mini-drone flight controller with IMU stabilization, battery monitoring, wireless telemetry/OTA support, motor outputs, configurable flight modes, emergency landing, and optional return-to-home expansion.

Properties

Properties describe core aspects of the project.

Pricing & Availability

Distributor

Qty 1

Arrow

$3.49–$4.88

Digi-Key

$6.76

HQonline

$4.48

LCSC

$8.81

Mouser

$8.77

TME

$0.00

Verical

$3.59–$16.66

Controls