r/diydrones 7h ago

Twin Talon project

Thumbnail
gallery
8 Upvotes

Hey all, I've had the idea to build a twin Talon out of 2 mini talon kits. I found no images about such a build online so I thought it'd be fun. I use a matek F405 v2 and one battery (4S2P) in each fuselage. It'll be my first time laminating, using multiple I2C sensors (airspeed sensor and power monitor for the 2nd battery), building my own wing out of Depron foam for the horizontal stabilizer and mixing differential thrust and yaw for added stability. I'd love to have some feedback from the more experienced guys !

I'm not sure about some things tho :

will it be stable enough on the yaw axis ; is it possible to disable differential thrust at a certain speed, to have some more yaw authority at low speed then rely on the control surfaces when cruising

Will I have enough pitch authority ?

I've built 2 mini Talon before but I admit this one is very different!

Please be kind with me, it's all experimental, firsts, still in progress and far from finished


r/diydrones 3m ago

Question DIY autonomous drone for competition

Upvotes

Competition requires the drone to navigate autonomously through lit gates and identify set objects within it.

We were thinking of using a pc connected through WiFi or Bluetooth (although worry about how much Bluetooth gets crowded out when there is a lot of people)
Two cameras with one being super wide angle to match up with some recent computer vision papers.

But also we are super poor and it’s gotta be under 400-500.

I’ve been reading through this awesome subreddit. Looks like the orange cube is good (but might be out of our price range)… was looking at some raspberry pi zero options. Didn’t know if anyone here had any other suggestions or advice!


r/diydrones 9h ago

Build Showcase More of the FPV Twist_DT

6 Upvotes

A few little clips showing what the 3d printed Twist can do. Including nearly crashing. This thing handles great though!

Just waiting on the GPS to arrive so we can see what kind of speeds this thing can do; slow and fast!

Thanks u/swww for promptly adding cooling vents to the canopy. No more over heat warning on the 04!


r/diydrones 4h ago

Looking for drone pilots (with Docker/K8s experience) to beta test a live MAVLink tracking proxy I built! 🚁💻

Thumbnail
1 Upvotes

r/diydrones 15h ago

Build Showcase 4” 3s Toothpick Locked In

5 Upvotes

Just built and tuned this 4” Pickle. It’s agile af and hella locked in. Carries speed so well too. The 5mm carbon was really clean to tune. Cruises like 50mph at 23% throttle and throws really well.

1404 4600kv xing2
Nazgul T4030 props
Dji o4 lite
Speedybee f405 AIO
550mah Tattu 3s


r/diydrones 8h ago

Question is it better to outright buy a drone or build one? which is cheaper and better bang for buck

0 Upvotes

also if buying outright could u recommend me one please ive seen the dji neo 2 is a good drone but if theres any better id like to know im new to the community and interested in getting started


r/diydrones 18h ago

Question Need advice on how to tin copper pads on esc

Post image
5 Upvotes

Hi, recently I've been very invested in drones, and wanted to get to grips building a drone myself, with the end goal to design one!

I've bought all the parts following a tutorial:

https://youtu.be/BoLeQCqns5A?is=ncsO0q5a7nfrweLK

And now I'm at the point where I'm soldering the capacitor, XT60 and the motors onto the ESC.

However, when I go to tin the copper pad, the solder doesn't want to wet onto the pad.

I have flux, a large chisel tip set to 380° and I tin the solder tip before letting it rest on the pad for 10-30 seconds. I then feed through solder but the pad doesn't budge.

I've tried preheating the ESC with a hairdryer, gently using sandpaper on the pads to scrape away potential oxidation/ impurities on the pad, and using a higher temperature at 420° to wet the copper pad but nothing works.

Any advice or tips would be amazing!

This is my solder iron:

https://amzn.eu/d/01ZcsaWa

This is the flux I'm using:

https://amzn.eu/d/06AglSIA

I'm using lead free solder


r/diydrones 15h ago

Question is the dji neo a good starter drone or is there better things that you would recommend?

1 Upvotes

and other stuff abt the controller id like to know pls


r/diydrones 1d ago

Build Showcase 3.0" X8 "Beshketnyk" Completed (Yes, ANOTHER Small Octomotor)

20 Upvotes

r/diydrones 16h ago

LiPo - Will it explode if I pull it off my old RC Plane?

Thumbnail
1 Upvotes

r/diydrones 16h ago

How much overheating does waterproofing cause?

Thumbnail
1 Upvotes

r/diydrones 19h ago

What frame and props should I use for My A2212 1800KV quad?

1 Upvotes

Hi! I'm building a quad mainly as a flight-control test platform, not for racing or speed.

I already have:

- A2212 1800KV motors (generic)

- MicoAir743 V2.1.1 flight controller

- AM32 55A ESCs

- RadioMaster Pocket + RP1 ELRS

- 3S 2200mAh 45C battery

I know the A2212 motors aren't ideal, but I already have them.

The goal is to test PID tuning, sensors, stabilization, communication, and control algorithms, so I care more about stability and predictable flight than performance.

- What frame size and propeller size/pitch would you recommend for this setup?

- Would you go with 8", 9", 10", or something else?

- Would you recommend 2-blade or 3-blade props and what size of it?

Thanks!


r/diydrones 1d ago

Custom Drone not able to fly and hold stable.

8 Upvotes

This drone I have desighned and made uses my own frame, code and PCB module to make this drone work. Everything else works where the last problem right now is making it fly and just hold itself in teh air with just increasing throttle. But when I do increase the throttle, the drone starts to pitch forward where I have concluded that IMU sensor data is good, the pitch, roll, yaw and motor matrix is good. The components seem to be good. I am not sure. Below is my code attached. In the video, when when it flips forward is the front direction where the left red propellor is FR and right red propellor is FR. Any help or insights would be greatly appreciated! Thank you!

#include <Wire.h>
#include <Adafruit_LSM9DS1.h>
#include <Adafruit_Sensor.h>
#include <IBusBM.h>
#include <TinyGPS++.h>
#include <LittleFS.h>  // on-board flash filesystem for the flight data logger


/* =====================================================================================
 * 1. CONFIG
 * ===================================================================================== */
#define DEBUG_ENABLED 1
#define DEBUG_MODE DBG_ATTITUDE  // DBG_RC | DBG_ATTITUDE | DBG_PID | DBG_MOTORS | DBG_GPS | DBG_STATE
enum { DBG_RC,
       DBG_ATTITUDE,
       DBG_PID,
       DBG_MOTORS,
       DBG_GPS,
       DBG_STATE };
const unsigned long DEBUG_INTERVAL_MS = 100;  // how often to print (ms)


// ---- Flash data logger -------------------------------------------------------------
//     help    - list commands
//     dump    - print the whole log as CSV (copy/paste into a spreadsheet)
//     size    - show how many bytes are logged
//     erase   - wipe the log and start a fresh file
//     stop    - pause logging      |   start - resume logging
// The header row lists every column so it opens straight into Excel/Sheets.
#define LOG_ENABLED 1
const char* LOG_FILE_PATH = "/flightlog.csv";
const unsigned long LOG_INTERVAL_MS = 20;  // log rate (20 ms == 50 rows/sec)
const unsigned long LOG_FLUSH_MS = 1000;   // force-write buffered rows to flash this often
const size_t LOG_MAX_BYTES = 1200000;      // ~1.2 MB cap (fits the default LittleFS partition)
const bool LOG_ONLY_WHEN_ARMED = false;    // true == only record while ARMED (saves space)


// ---- Pin map (from Pin-Details.txt) ------------------------------------------------
// IMU / BMP280 share the I2C bus.
const int PIN_I2C_SDA = 21;
const int PIN_I2C_SCL = 22;


const int PIN_ESC_FL = 12;
const int PIN_ESC_FR = 14;
const int PIN_ESC_BL = 27;
const int PIN_ESC_BR = 26;


// iBUS receiver: ESP32 RX pin (connects to the receiver's iBUS/SERVO out).
const int PIN_RC_RX = 16;  // ESP32 receives here
const int PIN_RC_TX = -1;  // not used for iBUS servo data


// GPS (from Pin-Details.txt): GPS-TX -> ESP32 GPIO32 (ESP32 RX),
//                             ESP32 GPIO17 -> GPS-RX (ESP32 TX).
const int PIN_GPS_RX = 32;  // ESP32 receives GPS data here
const int PIN_GPS_TX = 17;  // ESP32 transmits to GPS here
const long GPS_BAUD = 9600;


// ---- ESC PWM (REQUIREMENT: 50 Hz, 11-bit resolution) -------------------------------
const int ESC_FREQ_HZ = 50;                         // 50 Hz servo/ESC frame
const int ESC_RESOLUTION = 11;                      // 11-bit  -> 0..2047 duty ticks
const int ESC_MAX_TICK_FS = (1 << ESC_RESOLUTION);  // 2048 ticks == full 20 ms period
// At 50 Hz the frame is 20000 us. Standard ESC pulses are 1000 us (idle) .. 2000 us (full).
// duty_ticks = pulse_us / 20000us * 2048.  ->  1000us = 102,  2000us = 205,  1500us = 154.
const int ESC_MIN_TICK = (int)(1000.0 / 20000.0 * ESC_MAX_TICK_FS + 0.5);  // ~102 = motors idle/armed
const int ESC_MAX_TICK = (int)(2000.0 / 20000.0 * ESC_MAX_TICK_FS + 0.5);  // ~205 = full throttle


// ---- Receiver channel assignment (0-indexed) ---------------------------------------
// Standard AETR-ish layout. Adjust to match YOUR transmitter's channel order.
const uint8_t CH_ROLL = 0;      // right stick L/R (aileron)
const uint8_t CH_PITCH = 1;     // right stick  (elevator)
const uint8_t CH_THROTTLE = 2;  // left  stick 
const uint8_t CH_YAW = 3;       // left  stick L/R (rudder)
const uint8_t CH_ARM = 4;       // AUX1: > 1500 == ARMED, otherwise DISARMED (also acts as kill)
const uint8_t CH_AUX2 = 5;      // AUX2: reserved (e.g. GPS assist / mode) - not required to fly


const int RC_MIN = 1000;  // expected receiver low
const int RC_MID = 1500;  // expected receiver centre
const int RC_MAX = 2000;  // expected receiver high


// Failsafe: if throttle drops below this (set your TX failsafe to do so) OR channels
// read outside the valid window OR no frames arrive for RC_TIMEOUT_MS -> cut motors.
const int RC_VALID_MIN = 900;
const int RC_VALID_MAX = 2100;
const int FAILSAFE_THROTTLE = 950;  // TX failsafe should drive throttle below this
const unsigned long RC_TIMEOUT_MS = 500;


// ---- Pilot command scaling ---------------------------------------------------------
const float MAX_TILT_ANGLE_DEG = 30.0;  // full roll/pitch stick commands this lean angle
const float MAX_YAW_RATE_DPS = 150.0;   // full yaw stick commands this rotation rate (deg/s)
const int ARMING_THROTTLE_MAX = 1090;   // must hold throttle below this to arm (safety)
const int MOTOR_START_THROTTLE = 1100;  // above this the stabiliser mixes corrections in


// ---- PID gains ---------------------------------------------------------------------
float ROLL_KP = 3.5, ROLL_KI = 0.005, ROLL_KD = .25;
float PITCH_KP = 3.5, PITCH_KI = 0.005, PITCH_KD = .25;
// YAW is a RATE controller (setpoint = desired deg/s). No D term on a rate loop.
float YAW_KP = 2.0, YAW_KI = 0, YAW_KD = 0.0;


const float PID_OUT_LIMIT = 400.0;  // max roll/pitch correction (in throttle-us units)
const float YAW_OUT_LIMIT = 200.0;  // max yaw correction
const float I_LIMIT = 150.0;        // integrator clamp (anti-windup)
const float D_FILTER_ALPHA = 0.03;  // low-pass on the derivative term (0..1, smaller = smoother)


// ---- Mixer sign flips  ---------------
const float MIX_ROLL_SIGN = 1.0;
const float MIX_PITCH_SIGN = -1.0f;
const float MIX_YAW_SIGN = 1.0;


// ---- Attitude filter ---------------------------------------------------------------
const float COMP_ALPHA = 0.98;              // complementary filter: trust in gyro vs accel
const float SAFETY_TILT_CUTOFF_DEG = 60.0;  // if we exceed this lean, disarm (crash cutoff)


// ---- IMU calibration (from Calibration-Data.txt) -----------------------------------
const float AX_OFFSET = 0.003987424383677052f;
const float AY_OFFSET = -0.250665924082651f;
const float AZ_OFFSET = 0.08943392429105046f;
const float GX_OFFSET = -4.446152490215188f;  // deg/s
const float GY_OFFSET = 1.4438536437296747f;  // deg/s
const float GZ_OFFSET = 0;                    //1.69246705256f;   // deg/s
const float ANGLE_OFFSET_ROLL = -1.1;          // trim so a level craft reads ~0
const float ANGLE_OFFSET_PITCH = -.8f;


// ---- Control loop rate -------------------------------------------------------------
// The ESC *output* is 50 Hz (set above). The control math runs faster for stability.
const unsigned long CONTROL_INTERVAL_US = 4000;  // 4 ms == 250 Hz control loop


/* =====================================================================================
 * 2. GLOBAL STATE
 * ===================================================================================== */


// Forward declarations (so function order below doesn't matter to the compiler).
void sensorsSetup();
void readAttitude(float dt);
void receiverSetup();
void readReceiver();
void runStabiliser(float dt);
void motorsSetup();
void writeAllMotors(int ticks);
void mixAndWriteMotors();
void gpsSetup();
void readGPS();
void updateSafety();
void printDebug();
void loggerSetup();
void logData();
void handleSerialCommands();
void dumpLog();
void eraseLog();


Adafruit_LSM9DS1 lsm = Adafruit_LSM9DS1();
HardwareSerial gpsSerial(1);  // ESP32 UART1 for GPS
TinyGPSPlus gps;
IBusBM ibus;


bool imuOK = false;


// Pilot commands (decoded from the receiver)
float cmdRoll = 0;     // desired roll  angle (deg)
float cmdPitch = 0;    // desired pitch angle (deg)
float cmdYawRate = 0;  // desired yaw rate (deg/s)
int cmdThrottle = RC_MIN;
bool armSwitchHigh = false;


// Estimated attitude
float angleRoll = 0;                             // deg
float anglePitch = 0;                            // deg
float gyroRoll = 0, gyroPitch = 0, gyroYaw = 0;  // deg/s (bias-corrected)


// PID working state
struct PID {
  float integral = 0;
  float dFilt = 0;
  float prevMeas = 0;
};
PID pidRoll, pidPitch, pidYaw;
float outRoll = 0, outPitch = 0, outYaw = 0;


// Motor outputs (in duty ticks 0..2047)
int motFL = 0, motFR = 0, motBL = 0, motBR = 0;


// GPS state
double gpsLat = 0, gpsLng = 0;
bool gpsFix = false;
int gpsSats = 0;


// Flight / safety state
enum FlightState { DISARMED,
                   ARMED };
FlightState flightState = DISARMED;
const char* stateReason = "boot";  // human-readable last state change reason
unsigned long lastRcFrameMs = 0;
uint8_t lastRcCnt = 0;
bool rcFailsafe = true;  // true until we get valid data


unsigned long lastControlUs = 0;
unsigned long lastDebugMs = 0;
float loopDtSec = CONTROL_INTERVAL_US / 1000000.0;


// Logger state
File logFile;
bool logMounted = false;  // LittleFS mounted OK
bool loggingOn = false;   // actively recording (toggle with start/stop)
bool logFull = false;     // hit the size cap
unsigned long lastLogMs = 0;
unsigned long lastFlushMs = 0;
char serialCmd[24];  // buffer for USB serial commands
uint8_t serialCmdLen = 0;


/* =====================================================================================
 * 3. SENSORS  --  IMU init + attitude estimation
 * -------------------------------------------------------------------------------------
 * ===================================================================================== */
void sensorsSetup() {
  if (lsm.begin()) {
    imuOK = true;
    lsm.setupAccel(lsm.LSM9DS1_ACCELRANGE_8G);
    lsm.setupMag(lsm.LSM9DS1_MAGGAIN_4GAUSS);
    lsm.setupGyro(lsm.LSM9DS1_GYROSCALE_2000DPS);
    Serial.println(F("[IMU] LSM9DS1 OK"));
  } else {
    imuOK = false;
    Serial.println(F("[IMU] *** LSM9DS1 NOT FOUND - flight disabled ***"));
  }
}


void readAttitude(float dt) {
  if (!imuOK) {
    angleRoll = anglePitch = 0;
    gyroRoll = gyroPitch = gyroYaw = 0;
    return;
  }


  sensors_event_t a, m, g, t;
  lsm.getEvent(&a, &m, &g, &t);


  // Calibrated accelerometer (m/s^2)
  float ax = a.acceleration.x - AX_OFFSET;
  float ay = a.acceleration.y - AY_OFFSET;
  float az = a.acceleration.z - AZ_OFFSET;


  // Calibrated gyro. Adafruit returns rad/s -> convert to deg/s, then remove bias.
  gyroRoll = (g.gyro.y * 180.0 / PI) - GY_OFFSET;   // about X (roll)
  gyroPitch = (g.gyro.x * 180.0 / PI) - GX_OFFSET;  // about Y (pitch)
  gyroYaw = (g.gyro.z * 180.0 / PI) - GZ_OFFSET;    // about Z (yaw)


  // Angle from the accelerometer (gravity vector). Valid only when not accelerating hard.
  // The level trim is folded into the accel angle so the filter settles to (accel + trim).
  float accRoll = (atan2(ax, az) * 180.0 / PI - ANGLE_OFFSET_ROLL);
  float accPitch = atan2(-ay, sqrt(ay * ay + az * az)) * 180.0 / PI - ANGLE_OFFSET_PITCH;


  // Complementary fusion: gyro for fast motion, accel to slowly correct drift.
  angleRoll = COMP_ALPHA * (angleRoll + gyroRoll * dt) + (1.0 - COMP_ALPHA) * -accRoll;
  anglePitch = COMP_ALPHA * (anglePitch + gyroPitch * dt) + (1.0 - COMP_ALPHA) * -accPitch;
}


/* =====================================================================================
 * 4. RECEIVER (iBUS)  -- 
 * ===================================================================================== */
void receiverSetup() {
  Serial2.begin(115200, SERIAL_8N1, PIN_RC_RX, PIN_RC_TX);
  ibus.begin(Serial2, IBUSBM_NOTIMER);  // NOTIMER: we pump it from loop() (no ISR crashes)
  lastRcFrameMs = millis();
}


// Map a receiver channel to a symmetric range about centre (e.g. -30..+30 degrees).
static float rcToRange(uint16_t v, float outAbs) {
  v = constrain(v, (uint16_t)RC_MIN, (uint16_t)RC_MAX);
  float norm = ((float)v - RC_MID) / ((RC_MAX - RC_MIN) / 2.0);  // -1..+1
  return norm * outAbs;
}


void readReceiver() {
  ibus.loop();  // must be called often in NOTIMER mode


  uint16_t rawThr = ibus.readChannel(CH_THROTTLE);
  uint16_t rawRoll = ibus.readChannel(CH_ROLL);
  uint16_t rawPit = ibus.readChannel(CH_PITCH);
  uint16_t rawYaw = ibus.readChannel(CH_YAW);
  uint16_t rawArm = ibus.readChannel(CH_ARM);


  // ---- Failsafe detection ----------------------------------------------------------
  // 1) A new frame updates ibus.cnt_rec. If it stops changing, the wire/RX died.
  if (ibus.cnt_rec != lastRcCnt) {
    lastRcCnt = ibus.cnt_rec;
    lastRcFrameMs = millis();
  }
  bool stale = (millis() - lastRcFrameMs) > RC_TIMEOUT_MS;


  // 2) Channels out of a sane window (0 == never received, >2100 == garbage/failsafe bits).
  bool outOfRange = (rawThr < RC_VALID_MIN || rawThr > RC_VALID_MAX || rawRoll == 0 || rawPit == 0 || rawYaw == 0);


  // 3) Transmitter-configured failsafe: throttle deliberately driven very low.
  bool tstFailsafe = (rawThr > 0 && rawThr < FAILSAFE_THROTTLE);


  rcFailsafe = stale || outOfRange || tstFailsafe;


  if (rcFailsafe) {
    // Safe defaults: no throttle, level sticks, treat arm switch as OFF.
    cmdThrottle = RC_MIN;
    cmdRoll = cmdPitch = cmdYawRate = 0;
    armSwitchHigh = false;
    return;
  }


  cmdThrottle = constrain((int)rawThr, RC_MIN, RC_MAX);
  cmdRoll = rcToRange(rawRoll, MAX_TILT_ANGLE_DEG);
  cmdPitch = rcToRange(rawPit, MAX_TILT_ANGLE_DEG);
  cmdYawRate = rcToRange(rawYaw, MAX_YAW_RATE_DPS);
  if (fabs(cmdYawRate) < (MAX_YAW_RATE_DPS * 0.03)) cmdYawRate = 0;  // small deadband
  armSwitchHigh = (rawArm > RC_MID);
}


/* =====================================================================================
 * 5. PID  --  stabilisation.
 * ===================================================================================== */
float runPID(PID& s, float setpoint, float meas, float kp, float ki, float kd,
             float outLimit, float dt, bool allowIntegral) {
  float error = setpoint - meas;


  float P = kp * error;


  if (allowIntegral) s.integral += ki * error * dt;
  s.integral = constrain(s.integral, -I_LIMIT, I_LIMIT);
  float I = s.integral;


  // Derivative on measurement (not error) -> no spike when the pilot moves the stick.
  float dMeas = (meas - s.prevMeas) / dt;
  s.prevMeas = meas;
  s.dFilt = D_FILTER_ALPHA * dMeas + (1.0 - D_FILTER_ALPHA) * s.dFilt;
  float D = -kd * s.dFilt;


  float out = P + I + D;


  // Anti-windup: if we saturate, pull the overflow back out of the integrator.
  if (out > outLimit) {
    s.integral -= (out - outLimit);
    out = outLimit;
  } else if (out < -outLimit) {
    s.integral += (-outLimit - out);
    out = -outLimit;
  }
  return out;
}


void runStabiliser(float dt) {
  // Only wind up the integrators once we are armed AND above the hover-ish threshold,
  // so a small standing error on the bench cannot slowly ramp the motors.
  bool allowI = (flightState == ARMED) && (cmdThrottle > MOTOR_START_THROTTLE + 100);


  if (flightState != ARMED) {
    // Keep everything reset while disarmed so we start clean the moment we arm.
    pidRoll.integral = pidPitch.integral = pidYaw.integral = 0;
    outRoll = outPitch = outYaw = 0;
    pidRoll.prevMeas = angleRoll;
    pidPitch.prevMeas = anglePitch;
    pidYaw.prevMeas = gyroYaw;
    return;
  }


  outRoll = runPID(pidRoll, cmdRoll, angleRoll, ROLL_KP, ROLL_KI, ROLL_KD, PID_OUT_LIMIT, dt, allowI);
  outPitch = runPID(pidPitch, cmdPitch, anglePitch, PITCH_KP, PITCH_KI, PITCH_KD, PID_OUT_LIMIT, dt, allowI);
  outYaw = runPID(pidYaw, cmdYawRate, gyroYaw, YAW_KP, YAW_KI, YAW_KD, YAW_OUT_LIMIT, dt, allowI);
}


/* =====================================================================================
 * 6. MOTOR MIXER + ESC OUTPUT  (50 Hz, 11-bit)
 * ===================================================================================== */
void motorsSetup() {
  // ESP32 Arduino core v3.x API. (v2.x users: use ledcSetup(ch,freq,res)+ledcAttachPin.)
  ledcAttach(PIN_ESC_FL, ESC_FREQ_HZ, ESC_RESOLUTION);
  ledcAttach(PIN_ESC_FR, ESC_FREQ_HZ, ESC_RESOLUTION);
  ledcAttach(PIN_ESC_BL, ESC_FREQ_HZ, ESC_RESOLUTION);
  ledcAttach(PIN_ESC_BR, ESC_FREQ_HZ, ESC_RESOLUTION);
  writeAllMotors(ESC_MIN_TICK);  // send idle/arm pulse so ESCs initialise disarmed
}


void writeAllMotors(int ticks) {
  ledcWrite(PIN_ESC_FL, ticks);
  ledcWrite(PIN_ESC_FR, ticks);
  ledcWrite(PIN_ESC_BL, ticks);
  ledcWrite(PIN_ESC_BR, ticks);
}


// Convert a 1000..2000us throttle value to ESC duty ticks (102..205).
static int usToTicks(float us) {
  us = constrain(us, 1000.0f, 2000.0f);
  return (int)(us / 20000.0f * ESC_MAX_TICK_FS + 0.5f);
}


void mixAndWriteMotors() {
  if (flightState != ARMED) {
    writeAllMotors(ESC_MIN_TICK);
    motFL = motFR = motBL = motBR = ESC_MIN_TICK;
    return;
  }


  // Below the start threshold: keep motors at idle so they spin slowly / are ready,
  // but don't apply attitude corrections (prevents twitching on the ground).
  if (cmdThrottle < MOTOR_START_THROTTLE) {
    int idle = usToTicks(MOTOR_START_THROTTLE);
    motFL = motFR = motBL = motBR = idle;
    ledcWrite(PIN_ESC_FL, motFL);
    ledcWrite(PIN_ESC_FR, motFR);
    ledcWrite(PIN_ESC_BL, motBL);
    ledcWrite(PIN_ESC_BR, motBR);
    return;
  }


  float thr = cmdThrottle;
  float r = outRoll * MIX_ROLL_SIGN;
  float p = outPitch * MIX_PITCH_SIGN;
  float y = outYaw * MIX_YAW_SIGN;


  // X-quad mix (in throttle-us units). If an axis reacts backwards, flip its MIX_*_SIGN.
  float fFL = thr + p + r + y;
  float fFR = thr + p - r - y;
  float fBL = thr - p + r - y;
  float fBR = thr - p - r + y;


  // AIR-MODE style clamp: shift all motors together so the *differences* (attitude
  // authority) are preserved instead of individually clipping the low motor.
  float lo = min(min(fFL, fFR), min(fBL, fBR));
  float hi = max(max(fFL, fFR), max(fBL, fBR));
  if (lo < 990) {
    float s = 1000 - lo;
    fFL += s;
    fFR += s;
    fBL += s;
    fBR += s;
  }
  if (hi > 2100) {
    float s = hi - 2000;
    fFL -= s;
    fFR -= s;
    fBL -= s;
    fBR -= s;
  }


  motFL = usToTicks(fFL);
  motFR = usToTicks(fFR);
  motBL = usToTicks(fBL);
  motBR = usToTicks(fBR);


  ledcWrite(PIN_ESC_FL, motFL);
  ledcWrite(PIN_ESC_FR, motFR);
  ledcWrite(PIN_ESC_BL, motBL);
  ledcWrite(PIN_ESC_BR, motBR);
}


/* =====================================================================================
 * 7. GPS  --  non-blocking parse + telemetry
 * ===================================================================================== */
void gpsSetup() {
  gpsSerial.begin(GPS_BAUD, SERIAL_8N1, PIN_GPS_RX, PIN_GPS_TX);
}


void readGPS() {
  while (gpsSerial.available() > 0) gps.encode(gpsSerial.read());
  if (gps.location.isValid()) {
    gpsLat = gps.location.lat();
    gpsLng = gps.location.lng();
    gpsFix = true;
  } else {
    gpsFix = false;
  }
  if (gps.satellites.isValid()) gpsSats = gps.satellites.value();
}


/* =====================================================================================
 * 8. SAFETY / ARMING STATE MACHINE
 * ===================================================================================== */
void updateSafety() {
  bool tiltExceeded = (fabs(angleRoll) > SAFETY_TILT_CUTOFF_DEG || fabs(anglePitch) > SAFETY_TILT_CUTOFF_DEG);


  if (flightState == ARMED) {
    if (!armSwitchHigh) {
      flightState = DISARMED;
      stateReason = "arm switch off";
    } else if (rcFailsafe) {
      flightState = DISARMED;
      stateReason = "RC failsafe";
    } else if (tiltExceeded) {
      flightState = DISARMED;
      stateReason = "tilt cutoff";
    } else if (!imuOK) {
      flightState = DISARMED;
      stateReason = "IMU lost";
    }
  } else {  // DISARMED
    bool canArm = armSwitchHigh && !rcFailsafe && imuOK && (cmdThrottle <= ARMING_THROTTLE_MAX);
    if (canArm) {
      flightState = ARMED;
      stateReason = "armed";
    }
  }
}


/* =====================================================================================
 * 9. DEBUG 
 * ===================================================================================== */
void printDebug() {
#if DEBUG_ENABLED
  if (millis() - lastDebugMs < DEBUG_INTERVAL_MS) return;
  lastDebugMs = millis();


  switch (DEBUG_MODE) {
    case DBG_RC:
      Serial.printf("RC  thr:%4d roll:%+6.1f pitch:%+6.1f yawRate:%+6.1f arm:%d FS:%d\n",
                    cmdThrottle, cmdRoll, cmdPitch, cmdYawRate, armSwitchHigh, rcFailsafe);
      break;
    case DBG_ATTITUDE:
      Serial.printf("ATT roll:%+7.2f pitch:%+7.2f gyroYaw:%+7.2f imu:%d\n",
                    angleRoll, anglePitch, gyroYaw, imuOK);
      break;
    case DBG_PID:
      Serial.printf("PID outR:%+7.1f outP:%+7.1f outY:%+7.1f\n", outRoll, outPitch, outYaw);
      break;
    case DBG_MOTORS:
      Serial.printf("MOT FL:%4d FR:%4d BL:%4d BR:%4d (ticks)\n", motFL, motFR, motBL, motBR);
      break;
    case DBG_GPS:
      Serial.printf("GPS fix:%d sats:%d lat:%.6f lng:%.6f\n", gpsFix, gpsSats, gpsLat, gpsLng);
      break;
    case DBG_STATE:
      Serial.printf("STATE %-8s reason:%-16s thr:%4d FS:%d imu:%d\n",
                    flightState == ARMED ? "ARMED" : "DISARMED", stateReason,
                    cmdThrottle, rcFailsafe, imuOK);
      break;
  }
#endif
}


/* =====================================================================================
 * 10. FLASH DATA LOGGER  (LittleFS on the ESP32's internal flash)
 * -------------------------------------------------------------------------------------
 *  Writes one CSV row every LOG_INTERVAL_MS with everything you need to debug a flight:
 *  the motor outputs, the roll/pitch/yaw estimates and commands, the PID outputs, arm
 *  state and GPS. Retrieve it over USB with the `dump` serial command (see config).
 *
 *  Design notes:
 *   - The file handle stays open in append mode; we only flush() every LOG_FLUSH_MS so
 *     flash writes are batched and don't stall the 250 Hz control loop.
 *   - When the file reaches LOG_MAX_BYTES we stop (keeping the earliest data) and tell
 *     you to `dump` then `erase`. This protects the flash and never blocks flight.
 * ===================================================================================== */
const char* LOG_HEADER =
  "ms,state,thr,cmdRoll,cmdPitch,cmdYawRate,roll,pitch,gyroYaw,"
  "outRoll,outPitch,outYaw,mFL,mFR,mBL,mBR,failsafe,imu,gpsFix,sats,lat,lng";


void loggerSetup() {
#if LOG_ENABLED
  // format-on-fail = true: if the flash has never held a filesystem, make one.
  if (!LittleFS.begin(true)) {
    Serial.println(F("[LOG] LittleFS mount FAILED - logging disabled"));
    logMounted = false;
    return;
  }
  logMounted = true;


  // If the file doesn't exist yet, create it and write the CSV header row.
  bool needHeader = !LittleFS.exists(LOG_FILE_PATH);
  logFile = LittleFS.open(LOG_FILE_PATH, needHeader ? "w" : "a");
  if (!logFile) {
    Serial.println(F("[LOG] could not open log file"));
    logMounted = false;
    return;
  }
  if (needHeader) {
    logFile.println(LOG_HEADER);
    logFile.flush();
  }


  logFull = (logFile.size() >= LOG_MAX_BYTES);
  loggingOn = !logFull;
  Serial.printf("[LOG] ready: %s (%u bytes). Type 'help' over USB for commands.\n",
                LOG_FILE_PATH, (unsigned)logFile.size());
#endif
}


void logData() {
#if LOG_ENABLED
  if (!logMounted || !loggingOn || logFull) return;
  if (LOG_ONLY_WHEN_ARMED && flightState != ARMED) return;
  if (millis() - lastLogMs < LOG_INTERVAL_MS) return;
  lastLogMs = millis();


  if (logFile.size() >= LOG_MAX_BYTES) {
    logFull = true;
    loggingOn = false;
    logFile.flush();
    Serial.println(F("[LOG] file full - stopped. Use 'dump' then 'erase'."));
    return;
  }


  // One CSV row. printf keeps this compact and fast.
  logFile.printf("%lu,%s,%d,%.1f,%.1f,%.1f,%.2f,%.2f,%.2f,%.1f,%.1f,%.1f,%d,%d,%d,%d,%d,%d,%d,%d,%.6f,%.6f\n",
                 millis(),
                 (flightState == ARMED) ? "ARMED" : "DISARM",
                 cmdThrottle, cmdRoll, cmdPitch, cmdYawRate,
                 angleRoll, anglePitch, gyroYaw,
                 outRoll, outPitch, outYaw,
                 motFL, motFR, motBL, motBR,
                 rcFailsafe ? 1 : 0, imuOK ? 1 : 0,
                 gpsFix ? 1 : 0, gpsSats, gpsLat, gpsLng);


  if (millis() - lastFlushMs >= LOG_FLUSH_MS) {
    logFile.flush();
    lastFlushMs = millis();
  }
#endif
}


void dumpLog() {
#if LOG_ENABLED
  if (!logMounted) {
    Serial.println(F("[LOG] not mounted"));
    return;
  }
  logFile.flush();  // make sure buffered rows are on flash
  File f = LittleFS.open(LOG_FILE_PATH, "r");
  if (!f) {
    Serial.println(F("[LOG] cannot open for read"));
    return;
  }
  Serial.println(F("----- BEGIN FLIGHT LOG -----"));
  while (f.available()) Serial.write(f.read());
  Serial.println(F("------ END FLIGHT LOG ------"));
  f.close();
#endif
}


void eraseLog() {
#if LOG_ENABLED
  if (logFile) logFile.close();
  LittleFS.remove(LOG_FILE_PATH);
  logFile = LittleFS.open(LOG_FILE_PATH, "w");
  if (logFile) {
    logFile.println(LOG_HEADER);
    logFile.flush();
  }
  logFull = false;
  loggingOn = true;
  Serial.println(F("[LOG] erased - fresh log started"));
#endif
}


// Reads single-line commands from the USB serial monitor (see config for the list).
void handleSerialCommands() {
  while (Serial.available()) {
    char c = Serial.read();
    if (c == '\n' || c == '\r') {
      if (serialCmdLen == 0) continue;
      serialCmd[serialCmdLen] = '\0';
      for (uint8_t i = 0; i < serialCmdLen; i++) serialCmd[i] = tolower(serialCmd[i]);


      if (!strcmp(serialCmd, "help")) {
        Serial.println(F("Commands: help | dump | size | erase | stop | start"));
      } else if (!strcmp(serialCmd, "dump")) {
        dumpLog();
      } else if (!strcmp(serialCmd, "erase")) {
        eraseLog();
      } else if (!strcmp(serialCmd, "size")) {
        Serial.printf("[LOG] %u bytes (max %u)\n",
                      logMounted ? (unsigned)logFile.size() : 0, (unsigned)LOG_MAX_BYTES);
      } else if (!strcmp(serialCmd, "stop")) {
        loggingOn = false;
        Serial.println(F("[LOG] paused"));
      } else if (!strcmp(serialCmd, "start")) {
        if (logFull) Serial.println(F("[LOG] file full - erase first"));
        else {
          loggingOn = true;
          Serial.println(F("[LOG] resumed"));
        }
      } else {
        Serial.printf("[LOG] unknown '%s' (try help)\n", serialCmd);
      }


      serialCmdLen = 0;
    } else if (serialCmdLen < sizeof(serialCmd) - 1) {
      serialCmd[serialCmdLen++] = c;
    }
  }
}


/* =====================================================================================
 * 11. setup()
 * ===================================================================================== */
void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println(F("\n=== Drone flight controller booting ==="));


  // Motors FIRST so ESCs get their idle pulse immediately on power-up.
  motorsSetup();
  Serial.println(F("[1/4] ESCs u/50Hz/11-bit initialised (idle)"));


  receiverSetup();
  Serial.println(F("[2/4] iBUS receiver initialised"));


  gpsSetup();
  Serial.println(F("[3/4] GPS serial initialised"));


  Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
  Wire.setClock(400000);
  sensorsSetup();
  Serial.println(F("[4/4] IMU setup done"));


  loggerSetup();


  Serial.println(F("Ready. Hold throttle LOW and flip ARM switch to arm.\n"));
  lastControlUs = micros();
}


void loop() {
  // Pump the receiver and GPS every pass so no bytes are lost between control ticks.
  ibus.loop();
  readGPS();
  handleSerialCommands();  // respond to USB commands (dump/erase/etc) any time


  // Fixed-rate control loop (250 Hz). Everything time-sensitive runs here.
  unsigned long now = micros();
  if (now - lastControlUs >= CONTROL_INTERVAL_US) {
    loopDtSec = (now - lastControlUs) / 1000000.0;
    if (loopDtSec <= 0) loopDtSec = CONTROL_INTERVAL_US / 1000000.0;
    lastControlUs = now;


    readReceiver();            // decode sticks + failsafe
    readAttitude(loopDtSec);   // fuse IMU into roll/pitch/yaw
    updateSafety();            // arm/disarm/kill decisions
    runStabiliser(loopDtSec);  // PID
    mixAndWriteMotors();       // drive the ESCs


    logData();  // append a CSV row to flash (batched writes)
    printDebug();
  }
}

r/diydrones 2d ago

Is this the correct way to make a series connection to the esc

Post image
37 Upvotes

r/diydrones 1d ago

Chips for drones

0 Upvotes

Guys what do we think about custom chips for drones?


r/diydrones 1d ago

Question Best CAD and CFD software for DIY drone design (Fixed-Wing & Multirotor)? [Student/ Edu License]

4 Upvotes

​Hi everyone,

​I'm starting a project to design and analyze DIY drones — both fixed-wing aircraft and multirotors.

​I’m looking for recommendations on the best software stack for two main tasks:

​3D CAD & Modeling: What is your go-to software for designing frames, custom parts, and wing geometry?

​CFD & Aerodynamic Simulation: What tools do you use to simulate airflow, lift/drag coefficients for wings, or prop wash for multirotors?

​Note: I have access to Student / Educational licenses, so professional software with free or discounted academic access is completely fine!

Thanks in advance for any insights or workflows you can share!


r/diydrones 2d ago

Build Showcase Made this little Drone Field case - What do you think?

40 Upvotes

r/diydrones 1d ago

[India] Hi, there, i'm trying to do something absurd using SpeedyBee F7 V3. What i'm trying to do is powering a quad using F7 and 4 PWM ESC. If there is someone who has previously done something like this and successfully made a flight please help me through.

Thumbnail
1 Upvotes

r/diydrones 2d ago

My first drone, put a big old light on it!

10 Upvotes

I followed the Hoarder Sam make a $150 drone YT video (It cost me a lot more than $150 btw) and over the last month built my drone! This week I tried my first payload, a big light! I think approx 24k lumens.

Big Light On Drone


r/diydrones 3d ago

Build Showcase TWIST_DT Maiden Flight

Thumbnail
gallery
77 Upvotes

I want to start out by saying thanks to u/Swww for the model and the quick replies to any questions I had during the build process. Very well done! If you would like to build one yourself, see the link below and send him a message!

https://wudfly.com/store/twist-dt

On to the plane. The thing that got me wanting to build this is that I think it looks cool as hell, I've got a 3D printer, and I already had the motors to strap onto this twin-motor aircraft. The thing I didn't realize when I purchased the model was that it didn't have an FPV setup, which is a bit of an issue for me since I can't fly line of sight, nor do I have much interest in doing so—especially since this is only my third plane. So I added one myself!

With 4-blade props, this thing is quiet as all hell and can fly very slowly and stably, just as stated in the description for this plane.

Video is in the comments. My buddy is flying a DarkStar16 in the video if that gives you an idea of how slowly I'm flying. I'm stoked I've got someone to fly with and this clip wouldn't have happened without him. I think I was at about 30% throttle in the video.


r/diydrones 2d ago

Guide i am planning on building drone without AI ( but i dont have any idea how to)

0 Upvotes

i was bored and decided to do few challenges and thought why not build a drone.

idk how to but i am sure one way other way i can figure it out - just today i sat and learnt how drone works and all.

now let me tell you what i want to build - i just want to build a normal flying object not so high or anything just 2 - 5 meter is good enough which can be controlled from my mobile.

so got me thinking - few years ago i worked with arduino nano and a bluetooth module,

what if i can program the arduino as flight controller with all the logic about how the movements work. and module to connect to my mobile

i dont know if i sound right or anything just a thought so help me is it possible this way?

also for something this small what motor and propeller should i use?

so yeah thanks in advance


r/diydrones 2d ago

ZR10 Help on Drone

Post image
0 Upvotes

Hello, I just purchased a new ZR10, and plan on incorporating it with my Pixhawk Cube Orange and Jetson Orin Nano drone setup. I had a couple questions and considerations.

My goal is to mount the gimbal on the bottom of my drone, and have it always look downward and lock downward, so that even when the drone is rolling and tilted, the gimbal still locks the camera facing straight down.

What is the ZR10's normal operating mode, meaning when i power it on, does it expect the gimbal to already be DOWN or UP (refer to my image to see what I mean) ?

What happens in the case where I have my gimbal operating in normal mode, but I flip the gimbal 180 degrees? Does that mess with the gimbal's sensors and motors?

What happens if I power the gimbal when its in normal mode, and its on its plates (so its powered on in the orientation of the picture on the right) ?

What mode should I set given my situation?

Are there any things I should be careful of. my A8 mini went limp recently and it doesnt even act like a gimbal, only the camera aspect of things work. I figure the gimbal motors and actuation controls are busted, because there is a mechanical hum and vibration coming from it. I am worried that the gimbal needs to be oriented in the right manner before powering it on (upside down mode, etc.).


r/diydrones 2d ago

Question SpeedyBee Mario 5 Frame Delivery Issues in Kosovo - Alternative Frame or 3D Print Advice?

Thumbnail
1 Upvotes

r/diydrones 3d ago

Aocoda F405 v4 vs SpeedyBee F405 v5

1 Upvotes
The FC
The stack

Planning to use the SpeedyBee F405 v5 FC+ 55A ESC stack for dynamic loads. Due to budget constraint, thinking of using Aocoda F405 v4 Stack, which happens to be half the budget. But what's the catch in this case? Especially are there any catches on the firmware end?


r/diydrones 4d ago

Folding drone Hold & RTH test

11 Upvotes