Community project
Autonomous Surveillances Drone Manual Autopilot
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

Gather all the parts
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"
2. Connections between "tfmini_s" and "ESP32"
3. Connections between "oled" and "ESP32"
4. Connections between "buzzer" and "ESP32"
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.




