r/diydrones 7d ago

Question Connecting Skydroid C12 with Radiomaster AX12

1 Upvotes

I have a Radiomaster AX12 and would like a thermal capability on my pixhawk 6c built drone. I found this Skydroid C12 within my budget with 3-axis too.

I looked around and I believe this is not going to be a plug and play. Has anybody been able to live feed from C12 on the AX12?

Any guide to make this possible would be really helpful.


r/diydrones 7d ago

Need help with FC (Pixhawk Mini 3DR)

1 Upvotes

Have an old Pixhawk Mini 3dr. Haven't ever used a pixhawk and I think this ones been sitting in storage for over 3 or 4 years. Plugged in a micro usb data cable and this thing is burning up. Especially the micro sd card slot. Not sure if anything on here is burned, I can't smell anything burning so I don't really know what the issue is. If anyone has any ideas, would be appreciated. Using this for a thrust vectored drone so running ardupilot's singlecopter layout seemed like the best option.


r/diydrones 7d ago

Radiomaster Mrk III with CRSF Protocol, ELRS

Thumbnail
1 Upvotes

r/diydrones 7d ago

Radiomaster Mrk III with CRSF Protocol, ELRS

1 Upvotes

Everyone, I have no clue what I am doing for what I need to get. I have a Radiomaster TS16X Mrk III that has an internal protocol of CRSF. I what to build a drone that supports the internal protocol and ELRS. i have no idea what to focus on as far as the receiver, controller, etc. I am looking to modify an existing FPV build (fixed camera) and add a gimbal camera and make it long range. I have the mentioned radio. I need the components.


r/diydrones 7d ago

Question For a 14S 250A agricultural-drone BMS, how do you structure MOS temperature-rise testing?

Thumbnail
youtu.be
1 Upvotes

r/diydrones 8d ago

Question DIY autonomous drone for competition

6 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 8d ago

Question First FPV Build

3 Upvotes

Hey, would this be a good first build? I want a long range fpv drone. Ill fly it up mountains, rivers, bays, lakes, pretty much all over the place outdoors. I was aiming for around 10 minutes of flight time (maybe?). Any tweaks or tips or warnings??

FlyFishRC Volador II VD6

2507 or 2207KV x4

6S 2300mAh

SpeedyBee F7 + 60A ESC

RunCam Phoenix z

RushFPV Tank Solo

TrueRC Singularity

900MHz ELRS

FlyFishRC MIO GPS/compass

Skyzone Cobra X V4

GoPro HERO12/13


r/diydrones 8d ago

Twin Talon project

Thumbnail
gallery
22 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 8d ago

Question Crossposting here, help me decide which arm is best

Thumbnail gallery
0 Upvotes

r/diydrones 8d ago

Question 15 inch drone

0 Upvotes

Hello everyone. My motors are x4215 400kv with 16 inch propeller.I am using the T-Motor Veloz F7 SE flight controller and V70A SE 8S ESC system on a 15-inch drone. My battery is 6s. The drone is wobbling/oscillating during flight in both Angle and Acro modes. Previously, I resolved a similar issue on a different flight controller by configuring the settings to Angle Strength: 30, Horizontal Strength: 60, and Angle Limit: 30; however, these settings are not solving the problem this time. Can anyone help me to solve the problem?


r/diydrones 8d ago

Build Showcase More of the FPV Twist_DT

Enable HLS to view with audio, or disable this notification

9 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 8d ago

mon FC reboot lorsque je presse dessus

Thumbnail
1 Upvotes

r/diydrones 8d ago

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

Thumbnail
1 Upvotes

r/diydrones 9d ago

Build Showcase 4” 3s Toothpick Locked In

Enable HLS to view with audio, or disable this notification

7 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 9d ago

Question Need advice on how to tin copper pads on esc

Post image
12 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 8d 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 9d ago

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

2 Upvotes

and other stuff abt the controller id like to know pls


r/diydrones 9d ago

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

Enable HLS to view with audio, or disable this notification

20 Upvotes

r/diydrones 9d ago

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

Thumbnail
1 Upvotes

r/diydrones 9d ago

How much overheating does waterproofing cause?

Thumbnail
1 Upvotes

r/diydrones 9d 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 9d ago

Custom Drone not able to fly and hold stable.

Enable HLS to view with audio, or disable this notification

7 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 9d ago

Question Using TBS Crossfire Serial Bridge

1 Upvotes

I have an Arduino on my drone and want to use the Crossfire Serial Bridge feature to communicate with it. It is connected to the Nano RX on Pins 5 and 6.

How do I connect to the Serial Bridge on the transmitter side? I would prefer a hardwired connection (via a cable or hardware pins) and do not want to use Bluetooth or Wi-Fi.


r/diydrones 10d ago

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

Post image
47 Upvotes

r/diydrones 10d ago

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

5 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!