Community project

Autonomous Surveillances Drone Manual Autopilot

ESP32
Photo of Autonomous Surveillances Drone Manual Autopilot

Muhammad Ahtisham

Published September 26, 2026

This guide builds a companion computer system for autonomous surveillance drones using an ESP32 microcontroller. The system integrates GPS positioning, forward-looking LiDAR obstacle detection, real-time status display, and audio warnings to enhance drone autonomy and safety during flight operations.

Following this guide, makers will assemble a modular sensor suite that communicates with a flight controller via standard protocols. The build includes a complete wiring diagram, parts list with recommended suppliers, Arduino firmware with GPS and LiDAR drivers, and step-by-step assembly instructions to get the companion computer operational and tested.

Wiring diagram

Wiring diagram for Autonomous Surveillances Drone Manual Autopilot

Gather all the parts

QtyComponent
1

u-blox M10 GPS module

A satellite-position module that provides location and speed data to the ESP32.

1

Benewake TFmini-S LiDAR

A forward-facing laser distance sensor that reports the distance to objects in front of the aircraft.

1

SSD1306 OLED

0.96 inch 128x64 OLED display with I2C interface

1

Buzzer

Piezo buzzer for sound output

Assemble it in 6 steps

1. Keep the flight controller separate

Mount the ESP32 companion where it cannot touch the carbon frame or power wiring. This project only gives the pilot a screen and sound warning; do not connect any ESP32 pin to motor outputs, ESC signal wires, or Pixhawk control ports.

  • Use a small plastic case or heat-shrink sleeve, and secure it with soft mounting material.
  • Connecting this companion to motor or flight-control signals can create unsafe behavior; leave those systems electrically separate.

2. Connect the GPS module

Connect gps_m10 VCC to the ESP32 3V3 pin (power), gps_m10 GND to ESP32 GND (ground), gps_m10 TX to GPIO16 (location data), and gps_m10 RX to GPIO17 (optional configuration data). Put the GPS antenna high and away from the video transmitter, ESC, and battery leads.

  • Test outdoors with a clear view of the sky; satellite lock may take several minutes after first power-up.
  • Do not feed 5V into a GPS module that is marked 3.3V-only; excess voltage can damage it.

3. Connect the forward distance sensor

Mount tfmini_s facing straight forward with nothing blocking its small front window. Connect tfmini_s 5V to the ESP32 5V/VIN pin (power), tfmini_s GND to ESP32 GND (ground), tfmini_s TX to GPIO26 (distance data), and tfmini_s RX to GPIO25 (optional configuration data).

  • The sensor needs a stable 5V supply and its data wires use safe 3.3V logic.
  • Aim it away from propellers and keep its window clean.
  • The TFmini-S has no reverse-polarity protection: swapping its 5V and GND wires can damage the sensor.
  • A distance reading does not reliably detect thin wires, clear surfaces, moving objects, or every obstacle; it is only a warning aid.

4. Connect the small screen

Connect oled VCC to ESP32 3V3 (power), oled GND to ESP32 GND (ground), oled SDA to GPIO21 (screen data), and oled SCL to GPIO22 (screen clock).

  • The expected screen address is 0x3C. If it stays blank after deployment, first make sure its VCC and GND wires are not swapped.
  • Swapped screen power wires can damage the display.

5. Connect the warning buzzer

Connect buzzer SIGNAL to GPIO27 (warning signal) and buzzer GND to ESP32 GND (ground). Place it where you can hear it but where prop wash cannot pull on its wires.

  • When the forward distance is 3 metres or less, the buzzer stays on and the screen says STOP.
  • This buzzer is an attention signal only; it cannot safely control or stop the drone by itself.

6. Power and test the companion

Before fitting it to an aircraft, plug the ESP32 into USB on the bench. Confirm that the screen shows GPS status and a forward distance, then place a large solid object in front of the LiDAR to check that the buzzer and STOP message appear.

  • Keep propellers removed during every bench test.
  • After the bench test, secure every connector so vibration cannot loosen it.
  • Do not use this companion as the only collision-avoidance system or operate the aircraft without an attentive pilot and the autopilot’s independent safety settings.

Review all connections

1. Connections between "gps_m10" and "ESP32"

Functiongps_m10ESP32
powerVCC3V3
groundGNDGND
uartTXGPIO 16
uartRXGPIO 17

2. Connections between "tfmini_s" and "ESP32"

Functiontfmini_sESP32
power5V5V
groundGNDGND
uartTXGPIO 26
uartRXGPIO 25

3. Connections between "oled" and "ESP32"

FunctionoledESP32
powerVCC3V3
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22

4. Connections between "buzzer" and "ESP32"

FunctionbuzzerESP32
groundGNDGND
digitalSIGNALGPIO 27

Deploy the firmware

#include <Arduino.h>
#include <Wire.h>
#include <TinyGPS++.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>


// Forward declarations
bool readTfminiFrame();
void drawStatus(bool warning);

constexpr int GPS_RX_PIN = 16;
constexpr int GPS_TX_PIN = 17;
constexpr int LIDAR_RX_PIN = 26;
constexpr int LIDAR_TX_PIN = 25;
constexpr int BUZZER_PIN = 27;
constexpr int I2C_SDA_PIN = 21;
constexpr int I2C_SCL_PIN = 22;

constexpr uint16_t OBSTACLE_WARNING_CM = 300;
constexpr uint32_t DISPLAY_INTERVAL_MS = 500;
constexpr uint32_t GPS_STALE_MS = 3000;

HardwareSerial lidarSerial(1);
HardwareSerial gpsSerial(2);
TinyGPSPlus gps;
Adafruit_SSD1306 display(128, 64, &Wire, -1);

uint16_t distanceCm = 0;
bool distanceValid = false;
uint32_t lastDistanceMs = 0;
uint32_t lastGpsLocationMs = 0;
uint32_t lastDisplayMs = 0;
bool lastWarningState = false;

bool readTfminiFrame() {
  static uint8_t frame[9];
  static uint8_t index = 0;

  while (lidarSerial.available()) {
    uint8_t value = static_cast<uint8_t>(lidarSerial.read());
    if (index == 0 && value != 0x59) continue;
    if (index == 1 && value != 0x59) {
      index = 0;
      continue;
    }
    frame[index++] = value;
    if (index == 9) {
      index = 0;
      uint8_t checksum = 0;
      for (uint8_t i = 0; i < 8; i++) checksum += frame[i];
      if (checksum == frame[8]) {
        distanceCm = static_cast<uint16_t>(frame[2]) |
                     (static_cast<uint16_t>(frame[3]) << 8);
        distanceValid = distanceCm > 0 && distanceCm < 1200;
        lastDistanceMs = millis();
        return true;
      }
    }
  }
  return false;
}

void drawStatus(bool warning) {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println(F("DRONE SAFETY AID"));
  display.drawFastHLine(0, 10, 128, SSD1306_WHITE);

  display.setCursor(0, 16);
  if (gps.location.isValid() && millis() - lastGpsLocationMs < GPS_STALE_MS) {
    display.print(F("GPS: LOCK  Sats: "));
    display.println(gps.satellites.value());
    display.print(F("Speed: "));
    display.print(gps.speed.kmph(), 1);
    display.println(F(" km/h"));
  } else {
    display.println(F("GPS: WAITING FOR LOCK"));
    display.println(F("Keep antenna outdoors"));
  }

  display.setCursor(0, 42);
  if (distanceValid && millis() - lastDistanceMs < 1000) {
    display.print(F("Front: "));
    display.print(distanceCm);
    display.println(F(" cm"));
  } else {
    display.println(F("Front: NO LIDAR DATA"));
  }

  display.setTextSize(2);
  display.setCursor(0, 52);
  display.println(warning ? F("STOP") : F("CLEAR"));
  display.display();
}

void setup() {
  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW);

  Serial.begin(115200);
  gpsSerial.begin(9600, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
  lidarSerial.begin(115200, SERIAL_8N1, LIDAR_RX_PIN, LIDAR_TX_PIN);

  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("OLED not found"));
  }
  drawStatus(false);
}

void loop() {
  while (gpsSerial.available()) {
    gps.encode(gpsSerial.read());
    if (gps.location.isUpdated()) lastGpsLocationMs = millis();
  }
  readTfminiFrame();

  bool warning = distanceValid && (millis() - lastDistanceMs < 1000) &&
                 distanceCm <= OBSTACLE_WARNING_CM;
  digitalWrite(BUZZER_PIN, warning ? HIGH : LOW);

  if (warning != lastWarningState || millis() - lastDisplayMs >= DISPLAY_INTERVAL_MS) {
    drawStatus(warning);
    lastDisplayMs = millis();
    lastWarningState = warning;
  }
}

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.

Open in Schematik