Disclaimer: This aircraft is registered with Transport Canada under my name, and all flights were conducted on private property with permission in uncontrolled airspace, in accordance with my RPAS Pilot Certificate – Basic Operations.
This is the aerial robot I led the design of for SFU Ascension Robotics' RoboMaster 2026 competition. The role of the aircraft is as follows: carry a 17 mm projectile turret and a 750-round hopper, hover inside an indoor arena with no GPS, and stay stable enough that the turret gunner can actually shoot other robots. It also needed its propeller guard to collapse from a size of 1700 mm to 1400 mm, a rule that ended up challenging my mechanical design and control systems skills.
Sep 2025 – Jun 2026 (9 months)
SFU Ascension Robotics, co-leading team of 5
Designed for DJI RoboMaster University Championship 2026
28-inch propellers · 16 kg MTOW · 8 kg payload capacity
750-round turret · 25 m/s barrel velocity · 10 rounds/s shooting rate
17% shooting accuracy
First Place at ARCC Season One
I led the design of this aircraft from start to completion. On the mechanical side, I designed the core chassis, motor mounts, landing gear, and the carbon fiber structure throughout, and ran the design reviews, iterating 40+ times on the projectile indexer and 6 variants of the prop guard connectors. I manufactured some of the carbon plates on my home CNC router. On the electronics side, I wrote the STM32/FreeRTOS turret firmware, performing PID tuning and implementing gyro-stabilization on the pitch and yaw axes. On the controls side, I set up and tuned the Pixhawk 6C Mini in ArduPilot, including the GPS-less EKF sensor configuration, the harmonic notch filtering, and the manual PID tune that made it flyable with the heavy prop guard on. I acted as the pilot for every flight, with a gunner assisting me with targeting.
Complete CAD Model, isometric view.
Hover stability comes from a low disk loading, so I wanted the biggest props that would fit inside the 1700 × 1700 mm envelope. Four rotors is the minimum count that lets 28-inch props clear a perimeter guard; going to six or eight would have forced smaller discs and higher loading. So I determined the following configuration would be best: X-quad, T-Motor FA28×9.2 folding props on U8 II Lite 100 KV motors, yielding a maximum MTOW of 16 kg - well above the 13 kg competition limit.
However, a 28-inch prop quad doesn't fit in 1400 mm, so the prop guard has to fold. And once the guard has to fold, individual rings are no longer viable—the motor arms would have to pivot, which would require a hinge capable of taking the force of the motors without catastrophically failing.
My team and I went with a single square propeller guard around the whole perimeter instead, and worked out that pulling each side face inward by 480 mm shrinks the footprint by 300 mm in X and Y, which clears the rule with margin. The folding props are what make this possible without needing to shift the motors at all.
Initial rough sketches to see if folding is viable.
With my hands tied with the chassis design, embedded programming, and ArduPilot controls tuning, I tasked two of my team members to develop the prop guard.
Following at least 20 of my design reviews & revisions across 6 unique hinge components, the prop guard was complete. The final design of the prop guard came to be 14 mm CF tubes with printed corner and mid-span connectors, with 32 joints total so the sides can hinge inward. A chicken-wire net is ziptied to every face of the prop guard, protecting the rotors from foreign projectiles entering, per the RMUC 2026 rules.
Knowing our payload is ~6 kg, I computed that a 1:1 payload-to-weight ratio is required at minimum. Unlike existing teams which used aluminum brackets and tubes for structure, I decided that a full carbon fiber structure is necessary to take advantage of its high strength to weight ratio, especially if the pending prop guard design was to be used. Its superior ultimate strength compared to aluminum allowed me to subject the drone to crashes and impacts without a single broken carbon piece.
Three carbon fiber tube cross-sections were debated for the motor arms: square, round, octagonal. I chose the octagonal carbon tubes, finding that it gave most of the section efficiency of a round tube, but the flats let the chassis plates clamp it with easy rotational alignment and gave something to through-bolt against. Round tubes would spin in 3D-printed clamps; square tubes waste material in the corners.
For the landing gear and prop guard where nothing needs to be concentrically aligned, I chose plain round CF tubes: 16 mm for the landing gear, 14 mm for the prop guard. Round CF is a big weight saving over the aluminum tubing most teams default to, especially on a 1700 mm perimeter, where that mass is sitting at the worst possible distance from the CG.
Motor mount joined by an octagonal tube to chassis; 14 mm tube to prop guard.
All of the 14 mm carbon tubes cut for the prop guard.
First assembly of a barebones core chassis.
Like other competing teams, I followed their steps by designing a classic sandwich of two 3 mm carbon fibre plates to act as the core chassis. The plates were custom-ordered from overseas. This sandwich clamps the four arms, houses the flight controller, PDB, RX, in addition to the buck converter used to power the RoboMaster electronics. As I anticipated, the clamping force of 16× M3×55 mm screws on 3D-printed arm mounts proved to be extremely strong, exhibiting zero visible deflection even when the drone was held at the farthest point of its arm. The "ballpath" - the feeding mechanism to the turret, is centre-fed through the middle to maintain rotational symmetry, meaning there is no weaker direction to be firing in.
Motors are mounted underneath the arms in a pusher configuration, which keeps the prop plane clear of the chassis and puts the CG a bit above the thrust plane. I decided to add a 5° inward motor tilt, theorizing that prop wash would be reduced in a small warehouse arena. This decision was never experimentally tested, and its increased complexity in geometry is likely not worth the gains.
As opposed to the typical dual angled "T" shape landing gear of large commercial quadcopters, I designed a four-legged landing gear, connected by horizontal bars on each face. The heights of the horizontal bars were strategically placed to allow for turret clearance between ±90° in the yaw direction and up to +5° upwards. I originally intended for the four DJI Matrice 600 batteries to be mounted in the core chassis - but mounting them on the landing gear offered an easier assembly process and was more accessible for swaps. TPU feet were included to dampen landings.
The 14 mm CF tube connectors I designed were initially connected by through-screws, drilled through the carbon fiber tube. However, after testing a newer design using grub screws, I found that it was equivalent if not stronger than the through-holes, eliminating the need to drill through carbon fiber—which weakened its structure (in addition to creating lots of hazardous dust).
Landing gear CAD model.
At one point, four hands were needed to install the pitch thrust bearing.
CNC routing 2 mm CF plates.
The undercarriage turret features a "gooseneck" design that allows balls to smoothly transition from vertical to horizontal travel without the use of bearings. I decided to borrow the design from one of the club's ground robots, while introducing massive weight reductions by skeletonizing the carbon fiber plates, at the cost of unused strength. This later became a regrettable choice, as integrating an existing turret upside-down proved to be much more design and assembly work than using a purpose-built turret.
The pitch axis is behind and above the barrel, giving a +5° to −45° pitch range for engaging ground robots and the outpost from hover. Two GM6020s drive pitch and yaw, twin M3508 flywheels in the barrel fling projectiles, and an M2006 indexer feeds from the hopper. I used my home CNC Router with Autodesk Fusion CAM to manufacture many of its 2 mm and 3 mm CF plates, with PLA printed supplemental components.
With the completion of mechanical design and manufacturing, tuning the Pixhawk 6C mini flight controller was the next phase. This was a daunting task for me—as someone who's strictly familiar with Betaflight and INAV flight control software, I had some transferable skills in parameter setup and configuration, but the barebones GUI of Mission Planner proved to be completely unwelcoming to a user of Betaflight Configurator.
Pixhawk 6C mini mounted in the sandwich chassis.
I found that the typical workflow for setting up a new quad in ArduPilot is as follows:
Initial parameter setup – sensors like optical flow over DroneCAN, receiver over IBUS, and GPS for testing. Calibrating IMUs and compasses. Mapping motor PWM outputs. Configuring EKF sources.
Maiden Flight w/ no payload – tune RC rates, ensure all hardware is working. Verify POSHOLD/LOITER flight modes function.
Test flight w/ full payload – record onboard blackbox and get FFT of gyro data for filtering.
PID tuning of roll/pitch/yaw/throttle controllers.
Maiden flight.
In my research, I found that the primary source of gyroscope noise is from the vibration of the propellers throughout the airframe. Notch filters are commonly applied to the gyro data, targeting the specific frequency caused by the spinning propellers. In addition, 2nd- and higher order harmonics exist; as such, additional 'harmonic' notch filters are applied to attenuate the higher-order noise. The dynamic part of the filter is that it scales its centerpoint based on motor RPM so that it can follow the spike as the motors change speed.
The plot below is an FFT of gyro data from one of my test flights: pre-filtered on top, estimated post-filtering below. A dominant spike sits at about 70 Hz. At the ~2050 logged hover RPM, the props spin at 34 Hz, and with bi-blade props, that yields 68 Hz with higher-order harmonics at 140, 210 and 280 Hz. I centred the harmonic notch at 70 Hz with a 35 Hz bandwidth and tied it to ESC RPM telemetry. The result took roughly 40 dB out of the spectrum and flattened the fundamental and its harmonics peaks together. The noise below 40 Hz is the valuable data containing all of the movements of the entire airframe, which is fed into the roll/pitch/yaw/throttle PID controllers.
FFT frequency plot of gyro data from a test flight with pre- and post-filtering results.
Outdoor UAVs typically fly with a 3D position estimate of its position in space. This is generally acquired with a GPS, compass, and barometer. Any IMU drift is corrected by fusing the aforementioned sensors with an Extended Kalman Filter (EKF).
In my case, the competition arena is indoors, so the aircraft must fly fully GPS-denied, meaning the most reliable source of position is out. Instead, I configured ArduPilot's sensor fusing EKF with the optical flow and rangefinder as the xy & z position sources, respectively. Compasses are disabled entirely—I found that the RoboMaster turret motors make the local magnetic field unusable. Yaw is dead-reckoned with IMU instead. Getting this to pass all pre-arm checks and hold position reliably took time, but with this configuration of sensors, I eventually achieved an indoor 3D position hold as capable as GPS-stabilized flight.
I ran the first tune with the aircraft with no prop guard or payload, using ArduPilot's AUTOTUNE. It ended with excellent results: snappy without oscillations. Then, our team mounted the 2 kg prop guard and the aircraft became almost unflyable. The guard is a 1.7 m box around the entire perimeter, so nearly all of that mass sits at the maximum possible radius from the CG. Moment of inertia scales with the square of radius, so adding only about 12% to takeoff weight raised the MOI substantially on every axis. The autotuned gains no longer commanded enough torque to correct an attitude error, and the aircraft developed slow, large-amplitude oscillations, rocking like a ship. Attempting to autotune this resulted in a crash (see below).
Thus, I reluctantly began to tune it by hand, working through P, I, and D gains from 2 AM to 6 AM. The fix was counterintuitive: multiplying P gains by 10x from 0.035 to 0.350 stopped the rocking entirely. With inertia that high, the default conservative P term wasn't producing enough corrective torque to catch an error before it grew, and that lag between error and correction is what showed up as low-frequency oscillation. The final tune was still sluggish compared to the unloaded airframe, yet ultimately stable at hover. This was acceptable—since I needed it to stay still, not move fast.
Final tuned PID gains of all controllers.
The aircraft has two independent controllers which never communicate: a Pixhawk 6C Mini running ArduPilot for flight control, and a RoboMaster Development Board Type C running my own STM32/FreeRTOS firmware for the turret. The Type C board drives all five DJI motors over a single CAN bus and takes the gunner's stick inputs over the IBUS protocol. I split the firmware into two tasks: a high-priority task at 1 kHz that services the CAN bus and reads the gyros, and a 200 Hz main task handling everything the gunner controls. Supplemental libraries were used, courtesy of Liang Nie.
2-axis turret stabilization during firing demonstration.
A turret hanging under a hovering quadcopter is never still, so commanding the GM6020s to an encoder angle would just point the barrel wherever the airframe happens to be pointing. Instead, I implemented 2-axis gyro stabilization, so the turret points in the same direction no matter how the airframe moves. In code, I integrated gyro rate into an inertial angle estimate, and the gunner's sticks moved a setpoint in that same frame. A PD controller outputs a speed output to the GM6020 motor, holding the barrel on its heading while the airframe moves around it. Yaw rate comes from an external MPU6050 and pitch from the Type C's onboard BMI088, each smoothed with a low-pass filter.
A problem I encountered with an gyro-based setpoint is that the mechanical limits of the turret are set in physical encoder values, a different frame. To solve this, I re-mapped the encoder limits into the gyro frame every loop and clamp the setpoint to that window.
Overall, this yielded us the highest shooting accuracy of 17% out of the three teams performing the demonstration at the competition, out of 100 rounds.
See the open-source GitHub repo here for the gyro-stabilized turret code.
I implemented the twin M3508 flywheels to ramp up to its shooting speed of 6250 RPM rather than jumping straight to full speed; without the ramp, the 10-amp buck converter is overloaded, as the C620 ESCs spike up to 40 amps on abrupt speed changes.
The M2006 motor-driven indexer has jam detection: if its speed stays below 1000 RPM for >300 ms after spin-up, it reverses for 150 ms and tries again, in addition to a manual reverse override on the transmitter. This implementation was valuable as the indexer's mechanical design was never complete and would frequently jam. After iterating 40+ times, a reliable firerate of 10 rounds/sec was achieved.
Good engineering comes with some plastic waste...
Indexer jamming example.
Imperfect turret PID tune and endpoint jittering.
Tuning consumed far more of my time than writing the firmware did. I followed the standard tuning procedure: raise P until the axis oscillates, back off to roughly 80% of that, then raise D until the response is critically damped. Unfortunately, real life is far from ideal, which made this tuning process take months. One day, the turret would be stable, and the next, it would suddenly enter an unstable region.
Most of the instability was my own code. I had array-out-of-range bugs and truncation issues from multiplying tiny floats, which meant changing the gains would sometimes do nothing. I ended up refactoring the codebase five separate times, and every refactor further changed the previously stable tune.
In addition, the turret would change mechanically. The pitch thrust bearings worked themselves loose, and the single-bar pitch linkage carried enough friction and slop that the axis responded differently depending on which direction it was coming from. The PID controller can't compensate for this backlash, so increasing D-term by 10% would often make the turret vibrate in the dead zone.
I learned that PID tuning is only effective once the firmware and the hardware underneath it has stopped changing, and next time - I certainly would apply loctite and add spacers to minimize backlash before attempting to tune.
Of course, any UAV build can't be complete without some failures. Build – Crash – Repeat is a mantra I tell myself, every time a component snaps or when the entire chassis plummets out of the air.
First crash - prop guard protected us from being hit by the props.
Hovering over the grass field, I flew the drone rapidly to endurance test the optical flow and rangefinder-guided navigation. Optical flow operates similarly to a mouse sensor, which sees continuous still images of a surface and calculates velocity from the direction and magnitude of the change in the image. On a constantly moving grass field agitated by the dirty propeller air, this is essentially trying to use a mouse on a moving mousepad. As such, the drone lost visual tracking of the surface and begain drifting towards us; I was forced to plunge it into the ground. The propeller guard actually served its purpose, and allowed us to push it away before it hit the ground.
Following this, I set up a proper flight line where people should and should not stand, ensuring any flyaway events won't hit any of my observers.
During a routine tuning flight one week prior to competition, the quadcopter dropped out of the air at an altitude of ~10 meters. This was the result of me starting ArduPilot's AUTOTUNE algorithm, designed to find the unstable limits of the PID controllers for roll, pitch, and yaw. Upon starting, the algorithm incremented the D-term from 0.0079 to 0.0097—a 22% increase. The D-term amplifies noise in a system, which is fed into the output (motor throttle, in this case). With this increased D-term, the flight controller requested a more aggressive throttle ramp to stabilize the craft due to the amplified gyroscope noise. This abrupt throttle change would be no issue for a smaller drone, like a small FPV drone. However, on a large airscrew size like this drone, the motor's response time is slow, and an abrupt throttle input caused all four ESCs to desync with their motors. This triggered the ESCs' anti-desync protection—cutting all motor power in flight and sending the quadcopter into freefall.
Log analysis of second crash during ArduPilot's AUTOTUNE process.
And the result - most PLA components shattered upon impact. A saving grace, as the weakness of the PLA components meant that no carbon fiber hardware or electronics were damaged. Following this, I spent the next 24 hours reprinting reinforced versions of the 57 broken components on four printers—totalling in 1.5 spools of PETG consumed. Tuning must continue, especially with one week remaining before my flight to the competition.
80% of components in landing gear and prop guard broken after second crash.
Presenting during our club's Robot Reveal event.
Indoor test firing.
For anyone still reading: here's a detailed spec sheet.
Being my largest scale engineering project, it has been a delight to be able to share this experience with the friends in my club. This project was not done by myself—my team members have been wonderful in sharing the same excitement I have for this quadcopter, supporting me in every phase of the project. In addition, this drone could not have been developed without Ascension Robotics and its sponsors' assistance! See you next season in RMUC 2027!
Group photo of all attendees at the club's Robot Reveal.