Back to All Blog Posts
Tutorial EltroNerd Engineering

ESP32-Based Drone Controller with LoRa Transceiver

ESP32-Based Drone Controller with LoRa Transceiver

🛸 ESP32-Based Drone Controller with LoRa Transceiver

This project allows you to control a drone remotely using ESP32 and LoRa transceivers.
One ESP32 acts as the controller (ground station), and another ESP32 on the drone receives commands via LoRa.


⚡ Hardware Required

Drone Side:

  • ESP32 (controller on drone)
  • LoRa transceiver module (e.g., SX1278 / RFM95)
  • Brushless motors + ESCs (Electronic Speed Controllers)
  • Drone frame, propellers, battery
  • Optional sensors: MPU6050 (gyroscope/accelerometer), GPS

Controller Side:

  • ESP32 (ground station)
  • LoRa transceiver module (SX1278 / RFM95)
  • Joystick module or potentiometers for throttle, pitch, roll, yaw
  • Optional OLED display to show signal strength or battery

🔌 Connections

LoRa Module (Both Controller and Drone)

LoRa Pin ESP32 Pin Notes
VCC 3.3V Power
GND GND Ground
SCK GPIO 18 SPI Clock
MISO GPIO 19 SPI MISO
MOSI GPIO 23 SPI MOSI
NSS GPIO 5 SPI CS
RST GPIO 14 Reset
DIO0 GPIO 26 Interrupt

Joystick (Controller Side)

Joystick Pin ESP32 Pin Notes
VRx GPIO 34 X-axis (roll/pitch)
VRy GPIO 35 Y-axis (throttle/yaw)
SW GPIO 32 Button (optional)

🖥️ Full Code Example

Controller (Ground Station)

c
#include <SPI.h>
#include <LoRa.h>

#define SS 5
#define RST 14
#define DIO0 26

#define VRx 34 // Roll/Pitch
#define VRy 35 // Throttle/Yaw

void setup() {
  Serial.begin(115200);
  while(!Serial);

  // LoRa init
  LoRa.setPins(SS, RST, DIO0);
  if(!LoRa.begin(433E6)){ // 433MHz
    Serial.println("Starting LoRa failed!");
    while(1);
  }
  Serial.println("LoRa Ground Station ready");
}

void loop() {
  int roll = analogRead(VRx);    // 0-4095
  int throttle = analogRead(VRy); // 0-4095

  // Map to -100 to 100 for easier control
  roll = map(roll, 0, 4095, -100, 100);
  throttle = map(throttle, 0, 4095, 0, 100);

  // Send data
  LoRa.beginPacket();
  LoRa.print(roll);
  LoRa.print(",");
  LoRa.print(throttle);
  LoRa.endPacket();

  delay(50); // send every 50ms
}

Drone Side (Receiver)

c
#include <SPI.h>
#include <LoRa.h>

#define SS 5
#define RST 14
#define DIO0 26

int roll, throttle;

void setup() {
  Serial.begin(115200);
  while(!Serial);

  LoRa.setPins(SS, RST, DIO0);
  if(!LoRa.begin(433E6)){
    Serial.println("Starting LoRa failed!");
    while(1);
  }
  Serial.println("LoRa Drone Receiver ready");
}

void loop() {
  int packetSize = LoRa.parsePacket();
  if(packetSize){
    String received = "";
    while(LoRa.available()){
      received += (char)LoRa.read();
    }
    Serial.println("Received: " + received);

    int commaIndex = received.indexOf(',');
    if(commaIndex > 0){
      roll = received.substring(0, commaIndex).toInt();
      throttle = received.substring(commaIndex + 1).toInt();

      // TODO: Map roll/throttle to motor ESC PWM signals
      Serial.print("Roll: "); Serial.print(roll);
      Serial.print(" Throttle: "); Serial.println(throttle);
    }
  }
}

ESP32 Drone with MPU9260 + Camera + LoRa Control


Hardware Required

Drone Side:

  • ESP32 (ESP32-CAM or separate ESP32 + camera)
  • LoRa module (SX1278 / RFM95)
  • MPU9260 9-axis IMU
  • 4x ESC + brushless motors
  • Propellers + battery
  • Optional: GPS

Controller Side:

  • ESP32
  • LoRa module
  • Joystick or potentiometers
  • Optional: OLED display

🔌 Connections

MPU9260 (I2C)

Pin ESP32
VCC 3.3V
GND GND
SDA GPIO 21
SCL GPIO 22

LoRa (SPI)

LoRa Pin ESP32 Pin
SCK 18
MISO 19
MOSI 23
NSS 5
RST 14
DIO0 26
VCC 3.3V
GND GND

Motors

Motor ESC PWM Pin
M1 12
M2 13
M3 14
M4 15

Drone Side Code (ESP32)

c
#include <Wire.h>
#include <SPI.h>
#include <LoRa.h>
#include <Adafruit_MPU6050.h> // Use MPU6050 library compatible with MPU9260
#include <Adafruit_Sensor.h>
#include "esp_camera.h"

#define SS 5
#define RST 14
#define DIO0 26

// Motor Pins
#define M1 12
#define M2 13
#define M3 14
#define M4 15

Adafruit_MPU6050 mpu;

// LoRa
void setupLoRa() {
  LoRa.setPins(SS, RST, DIO0);
  if (!LoRa.begin(433E6)) {
    Serial.println("LoRa init failed!");
    while(1);
  }
  Serial.println("LoRa ready");
}

// ESC PWM mapping
void setMotorPWM(int m1,int m2,int m3,int m4){
  ledcWrite(0,map(m1,1000,2000,0,255));
  ledcWrite(1,map(m2,1000,2000,0,255));
  ledcWrite(2,map(m3,1000,2000,0,255));
  ledcWrite(3,map(m4,1000,2000,0,255));
}

// Camera
void setupCamera() {
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer   = LEDC_TIMER_0;
  config.pin_d0 = 5;
  config.pin_d1 = 18;
  config.pin_d2 = 19;
  config.pin_d3 = 21;
  config.pin_d4 = 36;
  config.pin_d5 = 39;
  config.pin_d6 = 34;
  config.pin_d7 = 35;
  config.pin_xclk = 0;
  config.pin_pclk = 22;
  config.pin_vsync = 25;
  config.pin_href = 23;
  config.pin_sscb_sda = 26;
  config.pin_sscb_scl = 27;
  config.pin_pwdn = 32;
  config.pin_reset = -1;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_QVGA;
  config.jpeg_quality = 10;
  config.fb_count = 1;
  esp_camera_init(&config);
}

void setup() {
  Serial.begin(115200);

  // Motor PWM setup
  ledcSetup(0, 400, 8);
  ledcSetup(1, 400, 8);
  ledcSetup(2, 400, 8);
  ledcSetup(3, 400, 8);
  ledcAttachPin(M1,0);
  ledcAttachPin(M2,1);
  ledcAttachPin(M3,2);
  ledcAttachPin(M4,3);

  // MPU
  if (!mpu.begin()) {
    Serial.println("MPU failed");
    while(1);
  }
  Serial.println("MPU ready");

  // LoRa
  setupLoRa();

  // Camera
  setupCamera();
}

void loop() {
  // Read MPU data
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);
  float roll  = a.acceleration.x;
  float pitch = a.acceleration.y;
  float yaw   = a.acceleration.z;

  // Receive LoRa commands (throttle etc.)
  int throttle=1500;
  int rollInput=0;
  int pitchInput=0;
  int yawInput=0;

  int packetSize = LoRa.parsePacket();
  if(packetSize){
    String data="";
    while(LoRa.available()) data += (char)LoRa.read();
    int comma1=data.indexOf(',');
    int comma2=data.indexOf(',',comma1+1);
    int comma3=data.indexOf(',',comma2+1);
    if(comma1>0 && comma2>0 && comma3>0){
      throttle = data.substring(0,comma1).toInt();
      rollInput = data.substring(comma1+1,comma2).toInt();
      pitchInput= data.substring(comma2+1,comma3).toInt();
      yawInput  = data.substring(comma3+1).toInt();
    }
  }

  // Motor mixing formula (X-quad)
  int m1 = constrain(throttle + pitchInput + rollInput - yawInput,1000,2000);
  int m2 = constrain(throttle + pitchInput - rollInput + yawInput,1000,2000);
  int m3 = constrain(throttle - pitchInput + rollInput + yawInput,1000,2000);
  int m4 = constrain(throttle - pitchInput - rollInput - yawInput,1000,2000);

  setMotorPWM(m1,m2,m3,m4);

  // Optional: capture image every 5 seconds
  static unsigned long lastCapture=0;
  if(millis()-lastCapture>5000){
    camera_fb_t *fb = esp_camera_fb_get();
    if(fb){
      Serial.print("Captured image size: "); Serial.println(fb->len);
      esp_camera_fb_return(fb);
    }
    lastCapture = millis();
  }

  delay(50);
}

Try this project in your browser

Compile code and simulate hardware output with EltroNerd Cloud IDE.

Launch Cloud IDE