Foundations to FrontierPart XII: The Engineering LabBezhig

EL01: Why a drone hovers: forces, units, and your first design loop

26 exercises, each with a complete solution. 10 figures. 16 code listings.

On this page
  1. Prerequisites and observable goals
  2. Warm-up: spaced review (10 minutes, closed book)
  3. 1. Quick summary
  4. 2. Deep explanation
  5. 2.1. The question every drone design starts with
  6. 2.2. Quantities and units: unit algebra is exponent bookkeeping
  7. 2.3. Mass, weight, and the gram-force trap
  8. 2.4. Motion as derivatives (Module 12, just in time)
  9. 2.5. Newton’s second law and the free-body diagram
  10. 2.6. What an accelerometer measures
  11. 2.7. Thrust-to-weight: the control budget
  12. 2.8. The mass budget
  13. 2.9. Energy, power, and flight time
  14. 2.10. The mass spiral: why a bigger battery helps less and less
  15. 2.11. Requirements that can be verified
  16. 2.12. Your first part in code: build123d
  17. 2.13. The hardware engine, and the Python this lab adds
  18. 2.14. Cross-checks: how independent is the second opinion?
  19. 2.15. Design trade-offs: why this engine looks the way it does
  20. 2.16. Beginner mistakes: a field guide
  21. 3. The lab: build the first slice yourself
  22. 3.0. Setup and ground rules
  23. 3.1. Step 1: quantities, and mass is not weight (worked example)
  24. 3.2. Step 2: Newton’s second law (partly filled)
  25. 3.3. Step 3: the thrust-to-weight family (on your own)
  26. 3.4. Step 4: energy, power, and flight time (on your own)
  27. 3.5. Step 5: the mass budget (on your own)
  28. 3.6. Step 6: the arm in build123d, and its hand check (worked, then on your own)
  29. 3.7. Step 7: your first requirements, and your first report (on your own)
  30. 3.8. Step 8: the phone test (predict, measure, integrate)
  31. 3.9. Step 9: the tinker loop (three knobs)
  32. 3.10. Read and change the engine
  33. 4. Practice, code reading, and the gate
  34. 4.1. Practice set
  35. 4.2. Code-reading drill: PX4 estimates its own hover thrust
  36. 4.3. Mastery gate
  37. 4.4. Next steps
  38. 5. Development log entry
  39. 6. Stretch (optional)
  40. Hint ladders for the lab
  41. L1. Step 1: weight
  42. L2. Step 2: Newton’s second law
  43. L3. Step 3: thrust-to-weight
  44. L4. Step 4: energy and flight time
  45. L5. Step 5: the mass budget
  46. L6. Step 6: the arm and its hand check
  47. L7. Step 7: requirements
  48. L8. Step 8: integrating the phone data
  49. L9. Step 9: the tinker loop
  50. Complete solutions: EL01
  51. Retention and readiness

Prerequisites and observable goals#

Before

Calculus I through the chain rule (Modules 08–11). Module 04’s section on units helps. Module 12 (motion) is taught here where it is needed, so you can meet it here first. Python basics from C01–C02; section 2.13 covers the Python this lab adds (pint, TOML files, build123d), with C++ comparisons.

After

Carry units through every calculation and catch a units error by dimensional analysis; tell mass, weight, and gram-force apart; draw a free-body diagram and apply Newton’s second law along a line; predict what an accelerometer reads in a hover, a climb, and free fall, and test it with your phone; compute thrust-to-weight ratio, hover thrust fraction, and climb acceleration from real motor data; compute battery energy, hover power, and flight time, and explain why a bigger battery helps less and less; write requirements that can be verified; build your first parametric part in code and check its mass two independent ways; run, read, and change the first slice of your hardware engine.

Level: Engineering Lab, Module 1 of 29. About 20 hours in nine sessions: two to three weeks at the track’s pace of about 9 hours a week. Python 3.14 plus four pinned packages, installed into the lab’s virtual environment in section 3.0. No purchases: the physical test uses your phone.

Warm-up: spaced review (10 minutes, closed book)#

If your error log has no reviews due, use these three, drawn from the friction points described in LM. All three come back in this module.

  1. Rewrite 5x3\dfrac{5}{x^3} and 1x\dfrac{1}{\sqrt{x}} as powers of xx. (Notation disguises. Units use exactly this bookkeeping: section 2.2 writes m/s2^2 as m s−2^{-2}.)
  2. Differentiate h(t)=4.9t2+2th(t)=4.9t^2+2t, then differentiate the result. (Power rule. Section 2.4 reads these two derivatives as velocity and acceleration.)
  3. Compute 1−151-\tfrac15 and 1/141\big/\tfrac14. (Fractions under pressure. Section 2.7 needs both: a hover fraction and its reciprocal.)

The answers are at the very end of this chapter’s solutions.

1. Quick summary#

A quadcopter hovering, seen from the side, so each propeller you see hides a second one behind it. Each propeller throws air downward (blue): the stream it pushes speeds up and narrows below the disc. The air pushes back up on the propeller, and that reaction is the thrust (orange). Gravity pulls down on the whole drone as if at one point, its centre of mass (the quartered disc). In a hover the four thrusts add up to exactly the weight, so the forces balance and nothing accelerates. Every number in this module is about keeping that balance, breaking it on purpose, and paying for it with a battery. EL03 derives the thrust from the air’s change of momentum.

Five ideas carry this module.

  1. Every number is a quantity: a value times a unit. Unit algebra is exponent bookkeeping, the same bookkeeping as the power rule. An equation whose units do not match is wrong before you plug in a single number, and your hardware engine refuses to run it.
  2. Mass is not weight. Mass is how much stuff there is; weight is the force gravity puts on it, W=mgW=mg. The “grams” of thrust in a motor’s data table are a force in disguise: gram-force.
  3. Newton’s second law, ∑F=ma\sum F=ma, drawn as a free-body diagram. A hover is balance, and any imbalance becomes acceleration. An accelerometer feels every force except gravity, so on a hovering drone it reads the thrust per unit mass: +g+g, not zero.
  4. Thrust-to-weight is the control budget. A hover spends 1/(T/W)1/(T/W) of the motors’ maximum thrust. What remains is everything the drone has for climbing, turning, and fighting gusts.
  5. Energy is power times time, and mass feeds back. Flight time is usable battery energy divided by hover power. A bigger battery stores more energy but weighs more, which raises the hover power. That loop is the first design loop you will close.

What you will build. In your lab repository, your own copy of the companion code’s eng-lab folder, you write the physics core of a Python hardware engine: weight and Newton’s second law, the thrust-to-weight family, energy and flight time, a mass budget with growth allowances, your first parametric CAD part (a drone arm, checked against your own hand calculation), your first requirements, and the analysis for a real measurement you take with your phone. Eighty-two tests check them step by step. The companion code provides the rest of the engine around your code: the units system, the design-file loader, the report that reruns every analysis whenever you change the design, and the tinker command that logs your predictions next to the results. By the end of this module, changing one number in design/drone.toml changes the arm’s CAD mass, the thrust-to-weight ratio, the flight time, and every requirement’s pass or fail, in one report. The Hover Budget Explorer, on the companion website at bezhig.com/book/explorers/el01-hover-budget.html, runs the same model in your browser: drag the battery, payload, motor count, and arm thickness, and watch the thrust arrows, the flight time, the mass spiral, and the accelerometer’s reading move.

SessionWorkWhere
1Warm-up; units as exponent bookkeeping; mass, weight, gram-force1, 2.1–2.3; EL01.01–02
2Motion as derivatives; Newton’s second law and free-body diagrams; lab setup, steps 1–22.4–2.5; 3.0–3.2; EL01.03–04
3What an accelerometer feels; write your phone-test prediction2.6; 3.8 (predictions only); EL01.05
4Thrust-to-weight; lab step 3; the thrust table2.7; 3.3; EL01.06–07
5Mass budget, energy, flight time; lab steps 4–52.8–2.9; 3.4–3.5; EL01.08–09
6The mass spiral; requirements; lab step 7 and your first report2.10–2.11; 3.7; EL01.10–11
7Code-CAD: the arm and its hand check; lab step 6; the engine, its Python, and its trade-offs2.12–2.15; 3.6; EL01.12
8The phone test: measure, integrate, compare; the tinker loop; read and change the engine; the field guide of beginner mistakes2.16; 3.8–3.10; EL01.13–17
9Code-reading drill, mastery gate, development log; stretch if you want it4.2–4.4; 5; 6

2. Deep explanation#

2.1. The question every drone design starts with#

Before anyone draws a part, a drone designer asks two questions: will it lift itself with room to spare, and for how long? Both answers depend on the mass, and the mass depends on the answers. A longer flight needs a bigger battery; a bigger battery is heavier; a heavier drone needs more thrust; more thrust takes more power; more power drains the battery faster. Engineers call this a design loop, and the first job of a design is to close it: to find numbers that agree with each other all the way around.

The loop this module closes. Each box is a section of this chapter and a stage of your hardware engine. Change any number and every box downstream changes; your report reruns the whole loop in seconds.

This module gives you every box at its simplest: forces along one vertical line, efficiency read from a manufacturer’s test table, and a single part in CAD. Later modules replace each box with a better model: EL02 adds moments and balance, EL03 derives hover power from how propellers move air, EL04 adds the electrical side, and EL05 turns the loop into a full trade study.

2.2. Quantities and units: unit algebra is exponent bookkeeping#

A physical number is never just a number. “The arm is 150” means nothing until you know 150 what: millimetres, metres, or grams. A quantity is a value times a unit, and the unit is part of the quantity: 150 mm=0.15 m150\ \text{mm}=0.15\ \text{m} is one length written two ways.

Base units. The International System of Units (SI) builds everything this module needs from four base units: the metre (m) for length, the kilogram (kg) for mass, the second (s) for time, and the ampere (A) for electric current. Every other unit is a product of powers of these. Here are the ones you will use, each with its exponents written out:

QuantityUnitIn base unitsWhat it means
velocitym/sm s−1^{-1}metres gained per second
accelerationm/s2^2m s−2^{-2}velocity gained per second
forcenewton, Nkg m s−2^{-2}the push that gives 1 kg an acceleration of 1 m/s2^2
energy (work)joule, J == N mkg m2^2 s−2^{-2}a force of 1 N pushing through 1 m
powerwatt, W == J/skg m2^2 s−3^{-3}1 joule every second
chargecoulomb, C == A sA s1 ampere flowing for 1 second
voltagevolt, V == W/Akg m2^2 s−3^{-3} A−1^{-1}energy per unit charge

Unit algebra is the power rule’s cousin. Multiplying quantities multiplies their units, and multiplying powers of the same unit adds exponents. So acceleration times time is

(m s−2)⋅s=m s−2+1=m s−1,\bigl(\text{m}\,\text{s}^{-2}\bigr)\cdot\text{s}=\text{m}\,\text{s}^{-2+1}=\text{m}\,\text{s}^{-1},

a velocity, as it should be. Dividing subtracts exponents. This is the same slip that turns −48x−4-48x^{-4} into −48x4-48x^4 in a calculus answer: a dropped minus sign on an exponent. In units, a dropped sign turns seconds into per-second, and the answer is off by a factor of the time squared. Use the Parentheses Rewrite: write every unit as a product of powers before simplifying.

Converting is multiplying by 1. Since 1 h=3600 s1\ \text{h}=3600\ \text{s}, the fraction 3600 s1 h\dfrac{3600\ \text{s}}{1\ \text{h}} equals 1, and multiplying by it changes the units without changing the quantity. Chain as many of these “ones” as you need, and cancel units exactly like factors.

Worked example: the energy in a drone battery. A battery label says 6S (six cells in series) and 1300 mAh. A lithium-polymer (LiPo) cell has a nominal voltage of 3.7 V, so the pack’s nominal voltage is 6×3.7 V=22.2 V6\times3.7\ \text{V}=22.2\ \text{V}. The “mAh” is milliampere-hours: a charge, current times time. Convert it to coulombs, then multiply by the voltage:

Q=1300 mA h×1 A1000 mA×3600 s1 h=4680 A s=4680 C,E=Q V=4680 C×22.2 V=103,896 J=103,896 J×1 W h3600 J=28.86 W h.\begin{align*} Q&=1300\ \text{mA\,h}\times\frac{1\ \text{A}}{1000\ \text{mA}}\times\frac{3600\ \text{s}}{1\ \text{h}}=4680\ \text{A\,s}=4680\ \text{C},\\ E&=Q\,V=4680\ \text{C}\times22.2\ \text{V}=103{,}896\ \text{J}=103{,}896\ \text{J}\times\frac{1\ \text{W\,h}}{3600\ \text{J}}=28.86\ \text{W\,h}. \end{align*}

Check the units of the product: C V == (A s)(W/A) == W s == J. The amperes cancel, which is why charge times voltage is energy.

Your turn (faded). A 4S 5000 mAh pack. Pack voltage: 4×3.7=4\times3.7=\rule{1cm}{0.4pt} V. Charge: 5000 mA h=5000\ \text{mA\,h}=\rule{1cm}{0.4pt} A h. Energy: W h. Check: 14.8 V, 5 A h, 74 W h. The drone simulator in the author’s FateMMO engine stores exactly this number, batteryCapacityWh = 74.0; EL01.10 audits it.

Dimensional homogeneity. You can only add, subtract, or compare quantities of the same dimension (length with length, force with force). Every term of a correct equation has the same units. And the input of exe^x, ln⁡x\ln x, or sin⁡x\sin x must be a pure number, with no units at all. That gives you a free test of any formula: check its units before trusting its numbers.

A units error that destroyed a spacecraft. NASA lost the Mars Climate Orbiter on 23 September 1999. One team’s ground software reported thruster impulse in pound-force seconds; the navigation software that used it expected newton-seconds, as the interface specification required. The navigation software therefore underestimated the effect of every small thruster firing on the trajectory by a factor of 4.45, the conversion from pound-force to newtons; the spacecraft reached Mars about 170 km lower than planned, and it was destroyed. No single line of code was “wrong”; the units at an interface did not match. The investigation report is listed at the end of this chapter.

Units in code. In C++ you already use a units library without calling it one: std::chrono. You cannot add a std::chrono::seconds to a plain int without saying what the int means, and seconds(2) + milliseconds(500) converts automatically. The C++ library mp-units extends the same compile-time checking to every physical dimension. Your hardware engine uses pint, which does the same thing in Python at run time: every value carries its unit, conversions are automatic, and adding metres to seconds raises an error. Section 2.13 shows how.

Say it back: why does multiplying acceleration by time give a velocity? Answer in exponents.

2.3. Mass, weight, and the gram-force trap#

Mass mm, in kilograms, is the amount of matter in an object and its resistance to being accelerated. It is the same on Earth, on Mars, and in orbit. Weight WW is a force: the pull of gravity on that mass,

W=mg,W=mg,

where gg is the acceleration gravity would give the object if nothing else acted on it. Near Earth’s surface gg is about 9.81 m/s29.81\ \text{m/s}^2. For a definite number the world uses standard gravity, g0=9.80665 m/s2g_0=9.80665\ \text{m/s}^2 exactly, fixed by international agreement in 1901. Real local gravity varies by a few tenths of a percent, from about 9.780 m/s2^2 at the equator to 9.832 m/s2^2 at the poles. Weight is measured in newtons: a 1.5 kg drone weighs 1.5 kg×9.80665 m/s2=14.71 N1.5\ \text{kg}\times9.80665\ \text{m/s}^2=14.71\ \text{N}.

On Mars, where g=3.73 m/s2g=3.73\ \text{m/s}^2 (NASA’s mean surface value), the same 1.5 kg weighs only 5.595 N. The mass did not change; the weight did. NASA’s Ingenuity helicopter used that to fly in an atmosphere about one percent as dense as Earth’s (EL01.14).

Scales measure force. A kitchen scale measures how hard the object pushes down on it, then divides by an assumed gg and displays grams. That is why hobby thrust tables are written in grams: a motor on a thrust stand pushes up on a scale, and the scale reports the “grams” that would push down equally hard. Those grams are gram-force (gf), the weight of one gram under standard gravity:

1 gf=0.001 kg×9.80665 m/s2=9.80665 mN,1000 gf=1 kgf=9.80665 N.1\ \text{gf}=0.001\ \text{kg}\times9.80665\ \text{m/s}^2=9.80665\ \text{mN},\qquad 1000\ \text{gf}=1\ \text{kgf}=9.80665\ \text{N}.

Two ways to make a scale read “1500 g”. On the left, a mass; on the right, a motor mounted upside down on a thrust stand, blowing air upward so that its thrust presses the stand down. Both push with 14.71 N. The display says grams in both cases, but only the left one is a mass; the right one is 1500 gram-force.

So a motor “making 1500 g of thrust” makes 1500 gf=14.71 N1500\ \text{gf}=14.71\ \text{N}. Hobbyists like gram-force for a good reason: to hover, the total thrust in gf equals the mass in grams, so a 650 g drone hovers at 650 gf. The trap is the moment you put gram-force into a physics formula that expects newtons, such as power == force ×\times speed (section 2.9): the answer comes out 9.8 times too small, with no error message. The hardware engine’s units system is what stops that.

Check yourself: a motor table lists 2.1 kg of thrust. In newtons? (20.6 N.)

2.4. Motion as derivatives (Module 12, just in time)#

Let h(t)h(t) be the drone’s height above the ground, in metres, at time tt in seconds. Three quantities describe its motion, and each is the derivative of the one before:

v(t)=dhdt  (velocity, m/s),a(t)=dvdt=d2hdt2  (acceleration, m/s2).v(t)=\frac{dh}{dt}\ \ (\text{velocity, m/s}),\qquad a(t)=\frac{dv}{dt}=\frac{d^2h}{dt^2}\ \ (\text{acceleration, m/s}^2).

The units follow from the derivative: dh/dtdh/dt is a small change in height (metres) divided by a small change in time (seconds), so velocity is in m/s, and acceleration is in (m/s)/s == m s−2^{-2}. Positive velocity means rising; positive acceleration means the upward velocity is growing (or a downward velocity is shrinking). Throughout this module, up is positive.

Constant acceleration from rest. Suppose the drone starts at rest at h=0h=0 and accelerates upward at a constant aa. Which function has derivative aa? The function v(t)=atv(t)=at. Which function has derivative atat? Try h(t)=12at2h(t)=\tfrac12at^2: its derivative is 12a⋅2t=at\tfrac12a\cdot2t=at. It works, and both start at zero. So

v(t)=at,h(t)=12at2.v(t)=at,\qquad h(t)=\tfrac12at^2.

Finding a function from its derivative is antidifferentiation, the subject of Module 16. For now, guessing and checking with the power rule is enough.

Worked example. A drone with T/W=2T/W=2 (section 2.7) climbs from rest with a=9.81 m/s2a=9.81\ \text{m/s}^2 for 1.0 s. Then v=9.81×1.0=9.81v=9.81\times1.0=9.81 m/s and h=12×9.81×1.02=4.9h=\tfrac12\times9.81\times1.0^2=4.9 m. One second of full thrust, and it is climbing at 35 km/h. (Real drones meet air drag long before this, which EL19 adds.)

Walking the chain backwards with a computer. Your phone’s accelerometer gives you aa at hundreds of instants a second, not a formula. To recover vv and hh, add up the small changes: over a short step Δt\Delta t, velocity changes by about a Δta\,\Delta t, and height by about v Δtv\,\Delta t. That is exactly the update loop a game engine runs every frame, here in the grenade code of ZeroVector, the author’s tactical shooter:

C++
grenade.velocity.y -= 9.81f * dt;                  // gravity changes the velocity
const vec3 displacement = grenade.velocity * dt;   // the velocity changes the position

Averaging the acceleration at the start and end of each step, instead of using only the start, is called the trapezoid rule, and it is far more accurate for the same step size. Lab step 8 builds it; Module 23 proves how accurate it is.

A 2-second climb that speeds up and then slows to a stop 3 m higher (the shape your phone test will have). Top: height. Middle: velocity, the slope of height. Bottom: acceleration, the slope of velocity: positive while speeding up, negative while slowing down, and zero at the instant of fastest climb, where the height curve is steepest. Reading the chain upward is differentiation; reading it downward is integration.

Say it back: at the instant the climb is fastest, what is the acceleration, and why?

2.5. Newton’s second law and the free-body diagram#

Newton’s second law. The net force on a body equals its mass times its acceleration:

∑F=ma.\sum F=ma.

Read it as a statement about cause and effect. Forces that cancel produce no acceleration; whatever does not cancel, the net force, accelerates the body, and the heavier the body, the less it accelerates for the same net force. Force and acceleration have directions, so the law is a vector equation. In this module everything happens along one vertical line, so a sign carries the direction.

The free-body diagram: a four-step method.

  1. Isolate the body. Draw only the drone, cut free from everything else.
  2. Draw every external force where it acts: each thrust at its motor, the weight at the centre of mass, drag opposing any motion through the air, a push from the ground if the drone is sitting on it. Forces the drone’s parts exert on each other (a motor on its arm) are internal and are not drawn.
  3. Choose axes and a sign convention. Up is positive.
  4. Sum the forces and apply ∑F=ma\sum F=ma.

Worked example: hover. Four motors with thrusts T1,…,T4T_1,\dots,T_4 (up), weight mgmg (down), no drag while still:

T1+T2+T3+T4−mg=ma.T_1+T_2+T_3+T_4-mg=ma.

In a hover, a=0a=0, so T1+T2+T3+T4=mgT_1+T_2+T_3+T_4=mg. For a 1.5 kg drone, the four thrusts must total 14.7114.71 N, or 3.68 N (375 gf) each if they share it equally.

Worked example: climbing. A 1.2 kg drone runs all four motors at 5.0 N each. Net force: 4×5.0−1.2×9.80665=20.0−11.77=8.234\times5.0-1.2\times9.80665=20.0-11.77=8.23 N upward. Acceleration: a=8.23/1.2=6.86a=8.23/1.2=6.86 m/s2^2 upward.

Your turn (faded). A 0.80 kg drone; each motor makes 1.5 N. Net force: 4×1.5−=4\times1.5-\rule{1.2cm}{0.4pt}=\rule{1.2cm}{0.4pt} N. Acceleration: m/s2^2. Is it climbing? Check: 6.0−7.85=−1.856.0-7.85=-1.85 N; a=−2.31a=-2.31 m/s2^2. It accelerates downward: the thrust does not cover the weight. Whether it is moving up or down depends on its velocity, which the forces alone do not tell you.

This is the same equation as F01’s altitude model, mz′′=T−mg−cz′mz''=T-mg-cz', with drag added. F01’s lab drone has Tmax⁡=2mgT_{\max}=2mg; section 2.7 explains that choice.

Say it back: a drone rises at a constant 2 m/s. Draw its free-body diagram and say which force is larger.

2.6. What an accelerometer measures#

Your drone’s flight controller, and your phone, contain a MEMS accelerometer (micro-electro-mechanical system): a speck of silicon with a tiny proof mass held between springs. The chip measures how far the springs are stretched, which tells it the force the springs are exerting on the proof mass.

An accelerometer is a proof mass (blue) on springs inside a case. Gravity pulls the proof mass and the case equally, so gravity alone never stretches the springs: in free fall they relax and the reading is 0. The springs deflect only when another force (the table, the thrust) pushes the case, and the springs must pass that push on to the proof mass. The reading is that non-gravitational force per unit mass.

Why gravity is invisible to it. Gravity pulls on every gram of the phone equally, the case and the proof mass alike, so it accelerates them together and the springs never need to push. Only the other forces (the table, a hand, the thrust) act on the case alone, and the springs must pass them on to the proof mass. So an accelerometer measures the non-gravitational force per unit mass, called the specific force ff. From Newton’s second law along the vertical, with up positive: the non-gravitational forces are ma+mgma+mg (whatever is needed, beyond gravity, to produce the acceleration aa), so

f=ma+mgm=a+g.f=\frac{ma+mg}{m}=a+g.
  • On a table: a=0a=0, so f=+g=9.81f=+g=9.81 m/s2^2 upward. The phone reads “one gg up” while sitting still, because the table pushes it up.
  • In free fall: a=−ga=-g, so f=0f=0. A dropped phone reads zero, even though it is accelerating as hard as gravity can make it.
  • A drone: the only non-gravitational force is the thrust (plus drag), so f=T/mf=T/m. In a hover it reads gg; climbing with acceleration aa it reads g+ag+a.

That last line is worth a pause: a drone’s accelerometer measures its thrust per unit mass. Divide the reading by gg and you have the drone’s instantaneous thrust-to-weight ratio. The drone controller in the author’s FateMMO engine uses exactly this relation in reverse: it computes the thrust it needs as (accelerationSetpoint + gravity) * mass, which is T=m(a+g)=mfT=m(a+g)=mf. And PX4, the open-source autopilot your drone will run, estimates its own hover throttle from this equation while it flies; the code-reading drill (section 4.2) walks through it.

Say it back: in one sentence each, why an accelerometer reads +g+g on a table and 0 in free fall.

2.7. Thrust-to-weight: the control budget#

Let nn be the number of motors and Tmax⁡T_{\max} the most thrust one motor can make. The thrust-to-weight ratio compares the drone’s total maximum thrust with its weight:

TW=n Tmax⁡mg.\frac TW=\frac{n\,T_{\max}}{mg}.

It is a ratio of two forces, so it is a pure number with no units. Three quantities follow from it directly.

  • Hover thrust fraction ϕ\phi (the Greek letter phi): the share of the maximum thrust a hover uses, ϕ=mgnTmax⁡=1T/W\phi=\dfrac{mg}{nT_{\max}}=\dfrac{1}{T/W}.
  • What is left over, 1−ϕ1-\phi: the headroom for everything that is not hovering.
  • Maximum climb acceleration: with every motor at full thrust, section 2.5 gives amax⁡=nTmax⁡−mgm=g(TW−1)a_{\max}=\dfrac{nT_{\max}-mg}{m}=g\left(\dfrac TW-1\right). At T/W=2T/W=2 the drone can accelerate upward at exactly 1g1g. At T/W<1T/W<1 it cannot leave the ground.

Worked example: the drone in your design file. Its motor is a T-Motor AIR2216 II swinging a 10-inch propeller. The manufacturer’s bench test (the thrust table, below) gives Tmax⁡=1332T_{\max}=1332 gf at full throttle. Section 2.8 finds the predicted mass, m=1158.6m=1158.6 g, which weighs 1158.6 gf. So

TW=4×1332 gf1158.6 gf=4.60,ϕ=14.60=0.217,amax⁡=9.807×(4.60−1)=35.3 m/s2.\frac TW=\frac{4\times1332\ \text{gf}}{1158.6\ \text{gf}}=4.60,\qquad \phi=\frac1{4.60}=0.217,\qquad a_{\max}=9.807\times(4.60-1)=35.3\ \text{m/s}^2.

A hover uses 21.7% of the available thrust; 78% is left for manoeuvring.

Why designers ask for T/W≥2T/W\ge2. A multicopter steers by making some motors push harder than others: to pitch forward, the back motors speed up and the front ones slow down. If the hover already demands most of the maximum, the motors that need to speed up cannot, the controller saturates, and the drone stops obeying. Three more demands eat into the same headroom:

  • Tilting to move. A drone flies sideways by tilting. Its thrust then points partly sideways, and only the vertical part holds it up. Section 2.5’s balance at a tilt angle θ\theta needs a total thrust of mg/cos⁡θmg/\cos\theta: 15% more than a hover at 30∘30^\circ. EL01.13 shows that the steepest tilt that still holds altitude is arccos⁡(1/(T/W))\arccos\bigl(1/(T/W)\bigr), which is 60∘60^\circ at T/W=2T/W=2.
  • Climbing and gusts, which need a>0a>0 and therefore thrust above mgmg.
  • A sagging battery. The thrust table was measured with a bench power supply holding exactly 16 V. A real four-cell pack starts near 16.8 V and falls as it drains, and the motors’ maximum thrust falls with it. EL04 measures how much; until then, the margin covers it.

The usual answer to all four is a designers’ rule of thumb, not a law of physics: T/W≥2T/W\ge2, a hover at no more than half of maximum thrust. Your requirement SYS-001 states exactly that, and section 2.11 turns it into a mass limit.

BuildTmax⁡T_{\max} per motorMassT/WT/WHover uses
3-inch: T-Motor F1404 3800 KV, 3-inch prop, 4S324.5 gf350 g3.727%
5-inch freestyle: T-Motor F60PRO V 1950 KV, 5.1-inch prop, 6S1990 gf520 g15.37%
Your worked example: AIR2216 II, 10-inch prop, 4S1332 gf1159 g4.6022%
FateMMO’s simulated default quad (EL01.10)7.371 N1500 g2.0050%
PX4’s simulated x500 (Gazebo)8.549 N2064 g1.6959%

Masses: T-Motor’s recommended take-off weights for the first two motors; section 2.8 for yours; the simulator’s parameters for FateMMO (EL01.10); PX4’s model files for its x500, at the commit PX4 v1.17.0 uses (x500_base: a 2.0 kg body plus four 16 g rotors; x500: the motor constant and top speed). Maximum thrusts: the makers’ thrust tables; FateMMO’s 9.1×10−6 N/(rad/s)2×(900 rad/s)29.1\times10^{-6}\,\text{N/(rad/s)}^2\times(900\ \text{rad/s})^2; PX4’s 8.549×10−6 N/(rad/s)2×(1000 rad/s)28.549\times10^{-6}\,\text{N/(rad/s)}^2\times(1000\ \text{rad/s})^2. Sources at the end of the chapter.

Racing and freestyle quads carry huge thrust surpluses because they are flown aggressively; camera and mapping drones sit near 2 to 3 because every gram of motor is a gram of battery they cannot carry. The PX4 simulation model is surprisingly heavy for its motors, which is worth remembering when you compare your simulator with it in EL23.

Reading a thrust table. A motor maker mounts the motor and propeller on a test stand, holds the voltage fixed, steps the throttle, and records thrust, current, electrical power, and efficiency:

The AIR2216 II with a 10-inch propeller at a fixed 16 V, from the manufacturer’s table (the dots are its rows; lines join them). Left: thrust grows faster than throttle, so half throttle gives only 34% of full thrust. Right: efficiency, thrust per watt, is best at low thrust and falls as thrust rises. Your drone hovers at 289.6 gf per motor (orange): about 38% throttle, but only 21.7% of full thrust, at 8.54 gf/W.

Two lessons hide in that figure. First, throttle is not thrust. A propeller’s thrust grows roughly with the square of its speed (EL03 derives why), so the throttle setting that hovers is much larger than the hover thrust fraction ϕ\phi. FateMMO’s drone simulator shows the same thing in its own terms: its hoverThrottle() (listed in EL01.10) returns the hover motor speed as a fraction of maximum, 0.706, while the hover thrust fraction is 0.499; and 0.499=0.706\sqrt{0.499}=0.706. Second, efficiency depends on where you read it. The table’s best number, 9.12 gf/W, is at 30% throttle; at full throttle it is 5.13 gf/W. Hover power must use the efficiency at the hover thrust, which the engine interpolates from the table.

Say it back: what is 1−ϕ1-\phi, and name two things the drone spends it on.

2.8. The mass budget#

A mass budget lists every component with its mass, grouped into subsystems, and adds them up. Two ideas turn that list into an engineering tool.

Numbers grow as a design matures. An early estimate forgets things: screws, wire, glue, a heavier connector than planned. Aerospace programs therefore add a mass growth allowance to each item according to how mature its number is. The standard most programs follow, ANSI/AIAA S-120A, gives ranges: roughly 10–35% for an estimate (up to 50–100% for estimated wiring harnesses), 3–25% for a preliminary design, and 1–5% for an item bought from a catalog. A measured part carries only its measurement uncertainty. Your design file uses 20% for estimates, 10% for CAD parts, 5% for catalog parts, and 1% for weighed parts:

mpredicted=∑imi (1+ai),m_{\text{predicted}}=\sum_i m_i\,(1+a_i),

where mim_i is the item’s mass and aia_i its allowance as a fraction.

Worked example: your design file’s budget.

ItemMaturityMassAllowancePredicted
Battery, 4S 5000 mAh LiPocatalog420.9 g5%442.0 g
Motors, 4 ×\times 64 gcatalog256.0 g5%268.8 g
Arms, 4 ×\times 28.75 g (your CAD, section 2.12)CAD115.0 g10%126.5 g
Body plates, standoffs, screwsestimated90.0 g20%108.0 g
Propellers, 4 ×\times 12.5 gcatalog50.0 g5%52.5 g
Wiring, connectors, battery strapestimated45.0 g20%54.0 g
Landing gearestimated40.0 g20%48.0 g
GPS and compasscatalog32.0 g5%33.6 g
Flight controller and 4-in-1 ESCcatalog21.8 g5%22.9 g
Radio receivercatalog2.2 g5%2.3 g
Total1072.9 g1158.6 g

The allowances add 85.6 g, 8% of the basic mass. The battery alone is 38% of the predicted mass, which is why section 2.10 exists.

From a requirement to a mass limit. Requirement SYS-001 says T/W≥2T/W\ge2. Solve the T/WT/W definition for the mass: mallow=nTmax⁡g (T/W)min⁡=4×1332 gf2=2664m_{\text{allow}}=\dfrac{nT_{\max}}{g\,(T/W)_{\min}}=\dfrac{4\times1332\ \text{gf}}{2}=2664 g. The mass margin is the share of the allowable mass not yet spent:

margin=mallow−mpredictedmallow=2664−1158.62664=56.5%.\text{margin}=\frac{m_{\text{allow}}-m_{\text{predicted}}}{m_{\text{allow}}}=\frac{2664-1158.6}{2664}=56.5\%.

A large margin this early is comfortable: it can pay for a camera, a bigger battery, or a heavier but stronger arm later.

2.9. Energy, power, and flight time#

Work and power. A force FF that pushes something through a distance dd along its line does work E=FdE=Fd (joules, N m). Power is work per unit time. If the force is constant while the distance changes, P=dEdt=Fdddt=FvP=\dfrac{dE}{dt}=F\dfrac{dd}{dt}=Fv: force times speed. (This is where the units trap of section 2.3 bites: put gram-force into P=FvP=Fv and the power comes out 9.8 times too small.) Energy is power accumulated over time; at constant power, E=PtE=Pt.

Battery energy is charge times voltage (section 2.2): your pack stores 5 A h×14.8 V=74 W h5\ \text{A\,h}\times14.8\ \text{V}=74\ \text{W\,h}. You should not use all of it. Makers recommend stopping at about 3.5 V per cell under load and never going below 3.0 V, where damage begins; and the last part of the charge comes out at a sagging voltage. So flight-time estimates use a usable fraction fuf_u of the stored energy; 0.8 is a common choice and your design file’s default.

Hover power from the thrust table. Each motor must make 289.6289.6 gf in a hover (section 2.7). The table’s efficiency at that thrust is ηT=8.54\eta_T=8.54 gf/W: every watt of electrical power buys 8.54 gram-force of thrust. So each motor draws 289.6/8.54=33.9289.6/8.54=33.9 W, and the four draw 135.7 W. The flight controller, GPS, and receiver add about 4 W, for a total of P=139.7P=139.7 W.

Flight time at constant power is usable energy over power:

tf=fu EP=0.8×74 W h139.7 W=0.424 h=25.4 min.t_f=\frac{f_u\,E}{P}=\frac{0.8\times74\ \text{W\,h}}{139.7\ \text{W}}=0.424\ \text{h}=25.4\ \text{min}.

Two second opinions, and only one of them is independent. First, the hover current is I=P/V=139.7 W/14.8 V=9.44I=P/V=139.7\ \text{W}/14.8\ \text{V}=9.44 A; the usable charge is 0.8×5 A h=40.8\times5\ \text{A\,h}=4 A h, and 4 A h/9.44 A=0.4244\ \text{A\,h}/9.44\ \text{A}=0.424 h. Same answer, but it had to be: the current came from the same power, so charge over current is energy over power in disguise. That catches an arithmetic slip, never a wrong model. Second, a route that really is different: the thrust table also records the current each motor drew, measured rather than computed. Read at the hover thrust, it is 2.13 A per motor; with 0.27 A for the electronics the total is 8.78 A, and 4 A h/8.78 A=27.34\ \text{A\,h}/8.78\ \text{A}=27.3 min. The two disagree by about 2 minutes, and the disagreement is information. The table was measured at 16 V, your pack sits near 14.8 V, and at the same thrust a motor needs about the same power, so it draws about 16/14.8=1.0816/14.8=1.08 times the table’s current. Energy over power accounts for that; the table’s current does not. Your report shows both checks, labelled by kind (section 2.14).

Your turn (faded). Add a 200 g camera. New predicted mass: 1158.6+200×1.2=1158.6+200\times1.2=\rule{1.2cm}{0.4pt} g. Hover thrust per motor: gf. Reading the table between its 309 gf and 373 gf rows, efficiency near that thrust is about 8.3 gf/W, so motor power is 4×/8.3=4\times\rule{1.2cm}{0.4pt}\big/8.3=\rule{1.2cm}{0.4pt} W; add 4 W. Flight time: min. Check: 1398.6 g; 349.6 gf; 4×349.6/8.3=168.54\times349.6/8.3=168.5 W, plus 4 W is 172.5 W; 0.8×74/172.50.8\times74/172.5 h =20.6=20.6 min. The engine interpolates the efficiency exactly (8.26 gf/W) and reports 20.5 min; run python -m hwe tinker "payload.mass=200 g" --predict "..." to see.

2.10. The mass spiral: why a bigger battery helps less and less#

A bigger battery stores more energy, but it also adds mass, and more mass raises the hover power. Where does that end?

A first model. Suppose efficiency did not depend on thrust. Let mbm_b be the battery’s mass and m0m_0 the mass of everything else, ebe_b the battery’s specific energy (energy per unit mass: your pack’s is 74 W h/0.421 kg=175.874\ \text{W\,h}/0.421\ \text{kg}=175.8 W h/kg), and ηT\eta_T the thrust efficiency. Hover power is the total weight divided by the efficiency, so

tf=fu eb mb(m0+mb) g/ηT=K mbm0+mb,K=fu eb ηTg.t_f=\frac{f_u\,e_b\,m_b}{(m_0+m_b)\,g/\eta_T}=K\,\frac{m_b}{m_0+m_b},\qquad K=\frac{f_u\,e_b\,\eta_T}{g}.

The fraction mbm0+mb\dfrac{m_b}{m_0+m_b} is the battery’s share of the whole drone. It grows when mbm_b grows, but it can never reach 1, so the flight time creeps toward a ceiling KK and never passes it. Each added gram of battery buys less flight time than the one before: diminishing returns. For your drone, K≈72K\approx72 minutes, which only an all-battery drone could reach.

With the real propeller, the curve turns over. Real efficiency falls as thrust rises (the thrust table’s right panel), so a heavier drone pays more watts for every gram. The engine sweeps the battery capacity from a quarter to eight times the design’s, holding the pack’s specific energy fixed, and reads the table’s efficiency at every new hover thrust:

Flight time against battery capacity for your design file’s drone, from the engine. Doubling the battery from 5000 to 10,000 mAh buys 37% more flight time, not 100%. The longest flight, 40.4 minutes, needs a 25,000 mAh pack weighing 2.1 kg, three times the rest of the drone, and by then T/WT/W has fallen to 1.82 (shaded: fails SYS-001). The largest battery that still meets T/W≥2T/W\ge2 is about 22,000 mAh.

That figure is the design loop of section 2.1, drawn. Engineering is choosing a point on it: the best flight time is not the best design, because it fails the control budget. EL03 derives the shape of this curve from how a propeller accelerates air, and uses Module 14’s optimization to find the peak with a pencil: for an ideal rotor, the best battery weighs twice everything else.

2.11. Requirements that can be verified#

A requirement states what the system shall do. The word matters: in engineering documents “shall” marks a binding requirement, “should” a goal, and “will” a statement of fact. A good requirement is one sentence, about one thing, with a number and a unit, that someone can check. Compare:

  • Bad: “The drone should fly for a long time.” (No number; “should”; how long is long?)
  • Good: “The drone shall hover for at least 12.0 minutes at sea level from a full charge to a resting voltage of 3.5 V per cell.” (One quantity, a unit, and the conditions under which it is measured.)

Every requirement names how it will be verified. NASA’s Systems Engineering Handbook, like most aerospace programs, uses four methods:

  • Inspection: look, weigh, or measure the thing itself (weigh the drone on a scale).
  • Analysis: calculations and models (the engine’s T/WT/W).
  • Demonstration: operate it and observe a result, without detailed measurement (it hovers).
  • Test: operate it under controlled conditions with instruments, and record data (a timed hover with a logged voltage).

Derived requirements and margins. Requirements form a tree: a top-level need (a flight time) produces derived requirements (a battery energy, a mass limit) that each part must meet. Section 2.8 derived the allowable mass, 2664 g, from SYS-001. A parent field records that link so that a change can be traced to everything it affects. The margin records how comfortably the design meets each requirement, and your report prints it for every one.

Regulations are requirements too. Your drone’s mass decides which rules apply to it. United States federal rules (14 CFR 48.15) require a drone flown only for recreation to be registered with the FAA if it weighs more than 0.55 pounds, about 249.5 g, at take-off, counting everything on board; once registered, it must broadcast Remote ID, and a home-built drone does that with an FAA-accepted broadcast module. A drone flown under Part 107, the rules for non-recreational flying, must be registered whatever it weighs, and flights that produce material for a professional portfolio are safest treated as Part 107. Your worked example weighs 1159 g, so it must be registered either way; a sub-250 g design would avoid registration only for purely recreational flying. EL06 covers these rules in full, with their sources, before you build or fly anything.

2.12. Your first part in code: build123d#

CAD (computer-aided design) programs build exact 3D models of parts. Most engineers use a graphical program such as Siemens NX, CATIA, SolidWorks, or Creo: sketch a profile with the mouse, constrain its dimensions, extrude it into a solid, cut holes, and the program records every step in a feature history. Code-CAD writes the same steps as a program. You will use build123d, a Python library on top of OpenCASCADE, an open-source geometry kernel. The model is a boundary representation: a solid is stored as its exact bounding surfaces, so a circle stays a true circle rather than becoming a polygon. That is why the volume build123d reports agrees with a hand formula to about sixteen significant digits, the limit of a double-precision number: your report shows a difference near 10−1610^{-16}.

Why code, when industry works in NX? Three reasons that matter for this project. A part written as a function of its dimensions is parametric: change one argument and the whole part regenerates, so your report can re-run the CAD whenever the design changes. Code can be tested, diffed, and reviewed like any other code. And every feature in a graphical CAD program has a counterpart in code, so what you learn here transfers: EL11 rebuilds this arm in NX and maps each click to a line of build123d.

The arm. Your first part is a flat arm like those cut from carbon-fibre plate for small quadcopters: a bar from the frame to the motor, a square pad under the motor with four screw holes and a centre hole, and two bolt holes where it clamps into the frame.

The arm from above, with the ten parameters its function takes: length LL, bar width ww, pad size aa, motor-hole spacing ss, the frame holes’ inset and pitch, the three hole diameters, and the thickness tt. The pad is centred on the motor. Drawn to scale for your design file’s arm (L=190L=190, w=16w=16, a=32a=32, s=19s=19, t=5t=5, in mm); the worked example below uses the lab tests’ smaller arm. Every dimension is a knob: EL07 will use this same function to find the thinnest arm that survives a crash load.

The same arm in an isometric view, drawn by the engine from the CAD solid: visible edges solid, hidden edges dashed, the way engineering drawings show a part. build123d projects every edge and removes the hidden ones; hwe/cad/drawing.py only draws the result.

The units boundary. build123d, like most CAD kernels, takes plain numbers and assumes millimetres. The engine never uses a plain number. So there is exactly one border crossing, the function mm() in hwe/cad/boundary.py, which turns a length quantity into a bare number of millimetres. Hand build123d a length in metres (0.15) where it expects millimetres (150), and the part comes out 1000 times too small in every direction, with a volume 10910^9 times too small, and no error anywhere. Converting at one named boundary is how real systems keep that from happening; the Mars Climate Orbiter’s interface had no such guard.

Mass from geometry. For a part made of one material of uniform density ρ\rho (mass per unit volume), mass is density times volume, m=ρVm=\rho V. The volume is where CAD earns its keep on complicated parts. For this flat arm you can also get it by hand, which is the point: the top-view area is the bar plus the pad, minus the rectangle where they overlap, minus the seven holes, and the volume is that area times the thickness tt:

V=t(Lw+a2−a2 w−π4(4dm2+dc2+2df2)),V=t\left(Lw+a^2-\frac a2\,w-\frac\pi4\left(4d_m^2+d_c^2+2d_f^2\right)\right),

where LL is the length, ww the bar width, aa the pad size, and dmd_m, dcd_c, dfd_f the motor-hole, centre-hole, and frame-hole diameters. (The overlap is a2\frac a2 long because the pad is centred on the motor, so half of it lies over the bar.) For the arm the lab tests use, with L=100L=100, w=12w=12, t=5t=5, a=28a=28, dm=3.2d_m=3.2, dc=8d_c=8, and df=3.2d_f=3.2 (all in mm):

V=5(1200+784−168−π4(40.96+64+20.48))=5(1816−98.52)=8587.40 mm3.V=5\bigl(1200+784-168-\tfrac\pi4(40.96+64+20.48)\bigr)=5(1816-98.52)=8587.40\ \text{mm}^3.

build123d reports 8587.398 mm38587.398\ \text{mm}^3 for the same part. Two independent methods, one answer. At the density of a typical carbon-fibre plate, about 1.55 g/cm31.55\ \text{g/cm}^3 (one maker lists 1.44 to 1.61 g/cm3^3 across its solid plates; see the chapter’s sources), one arm is 8.587 cm3×1.55 g/cm3≈13.38.587\ \text{cm}^3\times1.55\ \text{g/cm}^3\approx13.3 g. Your design file’s longer, wider arm (190 by 16 mm, section 2.8) comes out at 18,547 mm3^3 and 28.75 g.

2.13. The hardware engine, and the Python this lab adds#

The hardware engine is a pipeline. Each stage reads the design files and the stages before it, and the report shows every result with its cross-check.

The EL01 slice of the engine. Every arrow carries quantities with units. A stage whose core code you have not written yet shows “not written” in the report instead of disappearing; a stage a later module adds shows “locked” with the module that unlocks it.

The Python this lab adds, with C++ comparisons.

C++Python in this lab
std::chrono::​duration<double>A pint Quantity: Q_(1300, "mA*h"). Arithmetic keeps units; .to("W*h") converts; .m_as("mm") gives a bare number in a named unit, used only at a boundary. Adding incompatible units raises DimensionalityError.
A wrapper template around a functionA decorator. The line @quantities(mass="[mass]", returns="[force]") above a function replaces it with a wrapper that checks every argument’s dimension and the result’s before and after your code runs. It is plain Python: read it in hwe/units.py.
A struct with named fieldsA @dataclass, such as Item(name, subsystem, mass, maturity, source) in hwe/design.py.
A config file parsed at start-upA TOML file read with the standard library’s tomllib: design/drone.toml holds every knob, each with its unit as a string (length = "100 mm").
An RAII scope objectA with block. In build123d, with BuildPart() as arm: opens a part under construction; shapes created inside join it, and leaving the block finishes it.
std::vector<double> plus a unitA pint Quantity wrapping a numpy array, as the phone data arrives: t = Q_(np.array([...]), "s"); t[k] is one Quantity.
std::accumulate(first, last, init)sum(items, start). Without a start, sum begins from the plain int 0. pint lets 0 + mass through as a special case, so a non-empty list works, but an empty list returns the bare int 0, with no unit, and the next line breaks. Give the start a unit, Q_(0, "g").

The commands (run from your eng-lab folder with the virtual environment’s Python): python -m hwe report runs every unlocked stage and writes out/report/index.html; python -m hwe watch reruns it whenever you save a design, data, or code file; python -m hwe tinker KEY=VALUE --predict "..." changes knobs without editing the design file, compares against the baseline, and logs your prediction beside the result; python -m hwe explain N "..." adds your explanation to entry NN.

2.14. Cross-checks: how independent is the second opinion?#

An engineer who reports one number with one method is asking to be trusted. An engineer who reports it three ways is showing why they should be. The three kinds of evidence, in rising order of authority:

  1. A hand calculation from first principles, on paper: the arm’s volume from its geometry, the flight time from energy over power.
  2. An independent model: a different method, program, or data set that shares as little as possible with the first: build123d’s exact solid against your hand formula; flight time from the thrust table’s measured current against energy over power from its efficiency (section 2.9).
  3. A measurement: the kitchen scale, the thrust stand, the flight log. Only measurement tells you whether your model matches reality.

Not every second calculation counts, so your report labels each check by kind:

  • Independent: a different method or different data. Only this kind can catch a wrong model.
  • Recomputed: the same formula and inputs worked a second way, like T/WT/W in plain gram-force arithmetic. It catches slips in code.
  • Identity: two results that must agree by algebra, like hover fraction times T/WT/W, or charge over a current computed from the same power. It catches arithmetic slips only.
  • Assumption: a check that an input suits the model, like the pack’s voltage against the thrust table’s test voltage.

Engineers split this into verification (did we solve the equations right?) and validation (did we solve the right equations?). Hand calculation against CAD is verification: both assume a solid, uniform arm, so they can agree perfectly and both be wrong about a 3D-printed part. Only weighing the part validates the model. Your report’s cross-check table shows each pair side by side with its relative difference, and flags any pair that disagrees beyond its tolerance.

Two cautions. Independence matters: two methods that read the same wrong input will agree on the wrong answer. The current-column check shares its bench test with the efficiency column, so it can expose a table that was misread or misapplied (the field guide’s U7), but not a table that is wrong; only your own thrust stand in EL15 can do that. And the tolerance must match the method: CAD against exact geometry should agree to about 10−610^{-6}; two models built on different assumptions, like the table’s 16 V and your pack’s 14.8 V, agree only as well as those assumptions allow; a model against a measurement agrees to a few percent at best. That disagreement is information, not failure.

2.15. Design trade-offs: why this engine looks the way it does#

  • Units at run time, not compile time. pint checks units while the program runs, which costs time and catches errors only on paths you execute. Measured on one desktop PC: multiplying two plain Python floats took about 11 ns, multiplying two pint quantities about 4.3 μ\mus, a few hundred times slower (between 340 and 420 times across three runs). C++ libraries such as mp-units check at compile time for free, but Python has no compiler to do it. For design analysis the cost is invisible; for a flight controller running a thousand times a second it would not be, which is why EL19’s C++ simulator uses plain doubles with units in the names and tests instead.
  • A text design file, not a GUI. Every knob lives in drone.toml, so a design change is a one-line diff your Git history records, reviewable like code. A GUI is faster to poke at but leaves no reviewable trail.
  • The report is rebuilt from scratch every run. No number is typed into the report by hand, so it can never disagree with the design: the same reason continuous integration beats a hand-updated spreadsheet. The price is that every analysis must be automated, including the ones you would rather eyeball.
  • Data tables versus physics models. The thrust table is measured, so it is accurate inside its tested range and silent outside it (the engine refuses to extrapolate it). Momentum theory (EL03) extrapolates anywhere but must be validated against tables and tests. Real engineering uses both and watches where they disagree.
  • Code-CAD versus industry CAD. Code is parametric, testable, and diffable, but slower for sculpting organic shapes and rarer in industry job postings: of the 45 postings in EL00, SolidWorks and CATIA appear in six each and NX in five. You learn the concepts here and the industry tool in EL11.

2.16. Beginner mistakes: a field guide#

These are the mistakes people really make in a first sizing calculation. Each has a symptom you can recognize and a way to catch it. For all but U7, a test in your lab catches it: that test was run against a copy of the code with the mistake planted in it, and the copy fails. U7 lives in the engine’s own code, so the report guards against it instead.

MistakeSymptomHow to spot it; which test catches it
U1mAh treated as energyThe pack with “more mAh” is assumed to fly longer, even at a lower voltageUnits: mAh is charge. pint refuses to return it as energy; step 4’s test_more_mah_can_mean_less_energy
U2Grams of thrust used as a mass, or put into a formula expecting newtonsPower or acceleration off by a factor of 9.8Step 3’s T/WT/W tests (dividing gram-force by grams fails nine of them); test_wrong_unit_for_thrust_is_refused shows the engine refusing g where gf belongs. Write gf, never g, for thrust
U3Forgetting gg: weight written as mm“Weight 1.5” where 14.7 N belongsThe decorator refuses a mass where a force is due; step 1
U4One motor’s thrust used as the total, or the weight not shared among the motorsT/WT/W four times too small, or hover thrust per motor four times too bigStep 3’s test_ttw_counts_every_motor and test_hover_thrust_per_motor
U5Growth allowance subtracted, applied twice, or quietly zero for an unknown maturityA mass budget that looks healthier than it isStep 5’s allowance tests and test_unknown_maturity_is_an_error
U6Full-charge voltage (4.2 V per cell) used for energyFlight time about 13% too optimisticStep 4’s energy tests, which use the default voltage: test_energy_of_a_6s_1300_pack gets 32.76 W h instead of 28.86
U7Efficiency read at the wrong throttleA table’s best efficiency is at low thrust; using it for a hover overstates flight timeThe report reads efficiency at the hover thrust and cross-checks it against the table’s measured current (section 2.9); EL01.07
U8A sign flip in the free-body diagramA climbing drone with negative acceleration, or weight added to thrustStep 2’s test_acceleration_sinking_is_negative
U9“In a hover the accelerometer reads zero”A phone at rest “should” read 0 but reads 9.8Step 2’s accelerometer tests; your phone (section 3.8)
U10Metres handed to CAD that expects millimetresA part 1000 times too small and 10910^9 times too lightStep 6’s test_cad_volume_matches_with_other_units
U11Stripping units early (.m in mid-calculation)Numbers in mixed units added silentlyCode review: .m appears only at a named boundary. At the CAD boundary, step 6’s test_cad_volume_matches_with_other_units catches it
U12Integrating accelerometer data without removing gravity and biasHeight grows like 12(g+b)t2\tfrac12(g+b)t^2: metres of drift in secondsStep 8’s lift tests; section 3.8

3. The lab: build the first slice yourself#

3.0. Setup and ground rules#

Everything lives in your own copy of the companion code’s eng-lab folder; put it under Git, because it is designed to become your public repository. If you do not have Python 3.14 yet, install it from python.org. Then, in PowerShell, go to that folder, create the virtual environment .venv once, install the four packages pinned in pyproject.toml (pint 0.26.1, numpy 2.5.3, matplotlib 3.11.2, and build123d 0.13.0), and run the first check:

cd path\to\eng-lab
py -3.14 -m venv .venv
.venv\Scripts\python.exe -m pip install -e .
.venv\Scripts\python.exe labs\el01_hover\check.py 1

On macOS or Linux the same steps are python3.14 -m venv .venv, .venv/bin/pip install -e ., and .venv/bin/python labs/el01_hover/check.py 1; this chapter writes the Windows forms. Four of step 1’s five tests stop at NotImplementedError: Step 1: weight, and the summary reports step 1 as “not written yet”. That is the starting line. The fifth, test_bare_number_is_refused, already passes: the decorator refuses a bare number before your function even runs. To type plain python instead of the long path, activate the environment first with .venv\Scripts\Activate.ps1; if PowerShell’s execution policy blocks the script, keep using the full path, which always works.

  • Your files are the five in hwe/core/ that say “YOU WRITE THIS FILE” (each starts as docstrings only), plus design/requirements.toml, labs/el01_hover/predictions.toml, your phone data, and your tinker log. The tests in labs/el01_hover/tests/ and everything else in hwe/ were written for you; read them freely, but don’t edit the tests.
  • Type the code yourself. Don’t paste from the book. Typing is how your hands learn the patterns.
  • Predict before every run, in writing. The orange boxes say what to predict.
  • When a test fails, read the test before your code. Each test’s comment shows the arithmetic it expected.
  • Stuck? Climb that step’s hint ladder (after the practice set) one rung at a time.
  • The tests are the judge. A step is done when check.py reports it passing on your own code. After an honest attempt, compare your code with the step’s worked listing or its hint ladder’s skeleton, and with the usual first failures in EL01.16’s solution; find your first incorrect step before you change anything.

3.1. Step 1: quantities, and mass is not weight (worked example)#

First, meet pint in the Python prompt. Start it with .venv\Scripts\python.exe, then type:

Python
from hwe.units import Q_, G0
m = Q_(1.5, "kg")
w = m * G0
print(w)                               # 14.709975, in kg times m/s^2: correct, not yet simplified
print(w.to("N"))                       # 14.709975 N
print(Q_(1500, "gf").to("N"))          # a thrust table's "1500 g"
print(Q_(1300, "mA*h").to("C"))        # charge, not energy
Q_(1, "m") + Q_(1, "s")                # DimensionalityError

Now open hwe/core/hover.py and replace the body of weight with one line:

Python
@quantities(mass="[mass]", g="[acceleration]", returns="[force]")
def weight(mass, g=G0):
    """The weight of a mass: W = m g, the force gravity exerts on it. ..."""
    return (mass * g).to("N")

Line by line: the decorator line (already in the file) states the contract: mass must be a mass, g an acceleration, and the result a force; it checks all three on every call. g=G0 makes standard gravity the default while letting a caller pass Mars gravity. mass * g multiplies the magnitudes and adds the unit exponents (kg times m s−2^{-2}); .to("N") expresses the result in newtons. Leave the docstring in place.

Run .venv\Scripts\python.exe labs\el01_hover\check.py 1.

3.2. Step 2: Newton’s second law (partly filled)#

Goal. net_force, acceleration, and accelerometer_reading. Here are the first two with their key pieces left blank; fill them from section 2.5:

Python
def net_force(thrusts, mass, g=G0):
    total = sum(thrusts, ____)            # a start value WITH units: zero newtons
    return ____ - weight(mass, g)

def acceleration(thrusts, mass, g=G0):
    return (____ / mass).to("m/s^2")      # Newton's second law, solved for a

Why give sum a start value? Unless told otherwise it begins from the plain integer 0. pint makes a special exception for adding or subtracting 0, so here the arithmetic would still work. But where sum’s result is returned directly, as in step 5’s mass budget, an empty list would hand back the bare int 0, a number with no unit, and the caller’s next .to("g") would fail. Writing the start as zero newtons states the unit and makes every case, even the empty one, a force: a habit worth keeping. Now write accelerometer_reading on your own from section 2.6: one line.

Run check.py 2.

3.3. Step 3: the thrust-to-weight family (on your own)#

Goal. thrust_to_weight, hover_thrust_per_motor, hover_thrust_fraction, and max_climb_acceleration, from section 2.7 and their docstrings. No code shown this time. Reuse weight; each function is one or two lines.

Run check.py 3.

3.4. Step 4: energy, power, and flight time (on your own)#

Goal. battery_energy, propulsion_power, and flight_time in hwe/core/energy.py, from section 2.9. flight_time must refuse nonsense: a usable fraction outside 0<fu≤10<f_u\le1, or a power that is not positive, raises ValueError (Python’s raise ValueError("...")).

Run check.py 4.

3.5. Step 5: the mass budget (on your own)#

Goal. The five functions in hwe/core/massbudget.py, from section 2.8. Read the module docstring first: it names the Item fields and the one Python trap in this file.

Run check.py 5.

3.6. Step 6: the arm in build123d, and its hand check (worked, then on your own)#

Goal. arm() builds the part in section 2.12’s plan view; arm_volume_by_hand() computes its volume from geometry alone. Type arm() from this listing:

Python
def arm(length, width, thickness, pad_size, motor_hole_spacing, motor_hole_diameter,
        center_hole_diameter, frame_hole_diameter, frame_hole_inset, frame_hole_pitch):
    from build123d import (BuildPart, BuildSketch, Circle, GridLocations, Locations,
                           Mode, Rectangle, extrude)
    L, w, t, a = mm(length), mm(width), mm(thickness), mm(pad_size)      # the units boundary
    s, dm, dc = mm(motor_hole_spacing), mm(motor_hole_diameter), mm(center_hole_diameter)
    df, x1, p = mm(frame_hole_diameter), mm(frame_hole_inset), mm(frame_hole_pitch)
    with BuildPart() as part:
        with BuildSketch():                                  # a flat profile on the XY plane
            with Locations((L / 2, 0)):
                Rectangle(L, w)                              # the bar, centred at L/2
            with Locations((L, 0)):
                Rectangle(a, a)                              # the pad, centred on the motor
            with Locations((L, 0)):
                with GridLocations(s, s, 2, 2):              # a 2 x 2 grid, s apart
                    Circle(dm / 2, mode=Mode.SUBTRACT)       # motor screw holes
                Circle(dc / 2, mode=Mode.SUBTRACT)           # centre clearance hole
            with Locations((x1, 0), (x1 + p, 0)):
                Circle(df / 2, mode=Mode.SUBTRACT)           # frame bolt holes
        extrude(amount=t)                                    # the profile, t thick, upward
    return part.part

Line by line: every length crosses the units boundary once, at the top, into plain millimetres. BuildPart opens a solid under construction and BuildSketch a flat profile inside it; a shape created inside a Locations block is centred at each listed point. Shapes add to the profile by default; mode=Mode.SUBTRACT cuts them out instead. Circle takes a radius, so every diameter is halved. extrude sweeps the finished profile upward into a solid, and part.part is that solid.

Now write arm_volume_by_hand() on your own from section 2.12’s formula. Keep the units: your function receives quantities and returns a volume.

Run check.py 6. These tests build real CAD, so the first run takes several seconds while build123d loads its geometry kernel.

3.7. Step 7: your first requirements, and your first report (on your own)#

Goal. At least four more requirements in design/requirements.toml, beside the worked SYS-001, following section 2.11 and the file’s own comments. Between them, use the verification methods Analysis, Test, and Inspection; derive at least one from another (its parent); give every threshold a unit when its result has one; and write a one-sentence rationale for each. A requirement the current design fails is fine: that is information, not a mistake.

Then run the whole engine for the first time and open the result in your browser:

.venv\Scripts\python.exe -m hwe report --open

Run check.py 7.

3.8. Step 8: the phone test (predict, measure, integrate)#

This is your first physical test, and it follows the rule every test in this track follows: the prediction is written, and saved, before the measurement is taken. check.py compares the files’ timestamps and reports whether your prediction came first.

(a) The prediction. Open labs/el01_hover/predictions.toml and fill in every value its comments ask for (you may have done this in session 3): the reading at rest, whether it rises or falls while you lift and while you stop, the height your double integration will report compared with the tape measure, and an honest error band.

(b) The measurement. Install phyphox, a free physics app from RWTH Aachen University, on your phone. Use its “Acceleration with g” experiment, which records the raw accelerometer, gravity included.

  1. Lay the phone screen up on a thick book on a table, next to a wall with a tape measure or ruler taped vertically. Start the recording and keep everything still for 2 seconds.
  2. Lift the book and phone smoothly straight up by about 30 cm, taking about a second, and hold it steady at the top for 2 seconds. Stop the recording. Read the lift height off the tape measure to the nearest half centimetre.
  3. Export the data as CSV (comma separator, decimal point), copy it to your computer, and save it as data/tests/el01_phone/lift.csv. Its columns are Time (s) and Acceleration x (m/s^2) through Absolute acceleration (m/s^2), so the engine reads the units straight from the header.
  4. Check the sign: look at the first second of Acceleration z. If it reads about −9.8-9.8 instead of +9.8+9.8, your phone reports the opposite convention; set sign = -1 under [tests.phone] in design/drone.toml.

(c) The analysis code. Write cumulative_trapezoid, remove_bias, and velocity_and_height in hwe/core/kinematics.py on your own, from section 2.4 and the docstrings. The tests use synthetic lifts with a known answer, including one with random sensor noise and one that ends while still moving. Run check.py 8.

(d) Predicted versus measured. Run python -m hwe report and open the phone section: your raw reading, the velocity and height you integrated, and the final height.

3.9. Step 9: the tinker loop (three knobs)#

Every concept in this track comes with a knob. The loop: predict what changing it will do, run it, then explain the result, especially when your prediction was wrong. The engine enforces the order: tinker refuses to run without a written prediction.

.venv\Scripts\python.exe -m hwe tinker "battery.capacity=8000 mA*h" --predict "flight time up, but less than the 60 percent the capacity grew: the pack gets heavier. T/W down"
.venv\Scripts\python.exe -m hwe explain 1 "..."

Turn at least three different knobs, one at a time. Good first choices, each tied to a section:

Each entry lands in logs/tinker.csv and in your report. Run check.py 9.

3.10. Read and change the engine#

The engine is yours to read and change, line by line. Three short tasks, in order:

  1. Read hwe/units.py (about 150 lines). Explain in writing what require does when it receives a bare float, and how the @quantities decorator finds the argument named mass among positional arguments. (Hint: inspect.signature(func).bind.)
  2. Read stage_hover and stage_energy in hwe/pipeline.py. For each cross-check they record, say what the two methods are and whether they are truly independent (section 2.14). Then compare your answers with the Kind column of your report’s cross-check table.
  3. Change the report: add “Hover current” (the result energy.hover_current, in amperes) to the tiles at the top of hwe/report/html.py. One line in KEY_RESULTS does it. Run python -m hwe watch in a second terminal first, and watch the report rebuild when you save.

When check.py reports all nine steps passing, you have the first slice of a hardware engine whose physics you wrote. Copy the summary line into your development log (section 5).

4. Practice, code reading, and the gate#

4.1. Practice set#

F = foundation; C = standard; T = transfer; S = optional stretch; L = lab; R = code reading; G = mastery gate. Follow the session plan in section 1. Attempt each problem completely before opening its solution, carry units through every line, and log every miss in your error log with its friction family (LM shows the columns).

EL01.01FSolution

Units as exponents. Write each in base units (kg, m, s, A) with exponents, then simplify and name the quantity: (a) N/kg; (b) W s; (c) (m/s)/s; (d) J/(A s); (e) C V/s.

EL01.02FSolution

Multiply by 1. Convert, showing each factor of 1: (a) 2200 mAh to coulombs; (b) the energy of a 6S 2200 mAh LiPo in W h and in kJ (3.7 V per cell); (c) 1.8 kgf to newtons; (d) a thrust-table entry of “350 g” to newtons; (e) is 100 W h the same as 360 kJ?

EL01.03FSolution

Mass, weight, gram-force. A 650 g quadcopter. (a) Its weight in newtons. (b) Each motor’s hover thrust, in gram-force and in newtons. (c) A motor’s data sheet lists “2.1 kg” of maximum thrust. What is that in newtons, and could four of them lift a 9 kg camera drone?

EL01.04CSolution

Motion from rest. A drone climbs from rest with a constant upward acceleration of 2.5 m/s2^2 for 4.0 s, then holds that climb speed. (a) Its speed and height at 4.0 s; check h(t)h(t) by differentiating it twice. (b) When does it reach 60 m?

EL01.05CSolution

Free-body diagram. A 2.0 kg drone’s motors push 6.0, 6.0, 5.0, and 5.0 N. (a) Draw the free-body diagram with a sign convention. (b) Net force. (c) Acceleration. (d) What total thrust holds a steady 3 m/s climb, ignoring drag? (EL02 will show that unequal thrusts also tip the drone; here only their sum matters.)

EL01.06CSolution

What the accelerometer reads. An up-pointing accelerometer, in m/s2^2: (a) a phone on a table; (b) in an elevator speeding up upward at 1.2 m/s2^2; (c) in the same elevator rising at a constant speed; (d) slowing to a stop on the way up at 1.0 m/s2^2; (e) a drone hovering on Mars (g=3.73g=3.73 m/s2^2).

EL01.07CSolution

Reading a thrust table. Use the AIR2216 II table (section 2.7’s figure; rows in data/catalog/). (a) Maximum thrust in newtons. (b) A 1.4 kg quad: each motor’s hover thrust, and its hover throttle by linear interpolation between rows. (c) The efficiency at that thrust, and each motor’s hover power. (d) Why is the table’s best efficiency, 9.12 gf/W, the wrong number for (c)?

EL01.08CSolution

The thrust-to-weight family. A hexacopter uses six AIR2216 II motors (1332 gf each) and weighs 2.4 kg. Find T/WT/W, the hover thrust fraction, the maximum climb acceleration, and the steepest tilt that still holds altitude (EL01.13’s result, or derive it now).

EL01.09CSolution

A mass budget by hand. Frame 250 g (estimated); four motors at 64 g (catalog); battery 421 g (catalog); four propellers at 12.5 g (catalog); avionics 56 g (catalog); wiring 60 g (estimated). Allowances: estimated 20%, catalog 5%. Find the basic mass, the predicted mass, the allowable mass for T/W≥2T/W\ge2 with four 1332 gf motors, and the mass margin.

EL01.10TSolution

A real simulator, audited: FateMMO’s drone simulator. The author’s FateMMO engine includes a quadcopter simulator in C++. Below are its vehicle parameters, the function that computes its hover command, and the lines that fill three fields of MAVLink’s BATTERY_STATUS telemetry message. (a) Compute the simulated quad’s T/WT/W from thrustCoefficient, maxMotorSpeed, and mass, and compare with the comment above the struct. (b) What does hoverThrottle() return, and why is it not 1/(T/W)1/(T/W)? (c) Check batteryCapacityWh = 74.0 against a 4S 5000 mAh pack. (d) The telemetry converts used energy back to charge with usedWh / (kNominalCellVoltage * params.batteryCells) * 1000.0. Check its units. What does usedWh * 36.0 produce, and why 36? (e) Audit the struct’s fields: which state no unit at all? Propose one change to the struct that would make a units slip in those fields a compile error.

C++
// Physical description of a quad-X airframe. Defaults describe a 1.5 kg,
// 450 mm (motor-to-motor) quad on 10-inch propellers and a 4S 5000 mAh LiPo,
// with a thrust-to-weight ratio of about 2.
struct VehicleParams {
    double mass = 1.5;
    double armLength = 0.225;                 // centre to motor axis
    Vec3 inertia{0.015, 0.015, 0.027};        // principal moments about the body axes
    double thrustCoefficient = 9.1e-6;        // N per (rad/s)^2
    double torqueCoefficient = 1.5e-7;        // N m per (rad/s)^2
    double motorTimeConstant = 0.03;          // first-order rotor speed lag, s
    double maxMotorSpeed = 900.0;             // rad/s at command 1
    double minMotorSpeed = 150.0;             // idle speed the controller holds while armed, rad/s
    double linearDragCoefficient = 0.15;      // N per (m/s) of air-relative velocity
    double quadraticDragCoefficient = 0.02;   // N per (m/s)^2
    double angularDragCoefficient = 0.003;    // N m per (rad/s)
    double batteryCapacityWh = 74.0;
    int batteryCells = 4;
    double batteryInternalResistance = 0.03;  // whole pack, ohm
    double powertrainEfficiency = 0.8;        // rotor shaft power / electrical power
    double avionicsPower = 3.0;               // constant electrical load, W
    // ... member functions
};

double VehicleParams::hoverThrottle(double gravity) const {
    // ... input checks
    const double hoverSpeed = std::sqrt(mass * gravity / (4.0 * thrustCoefficient));
    return std::clamp(hoverSpeed / maxMotorSpeed, 0.0, 1.0);
}

// ... in the battery telemetry
const double usedWh = std::max(0.0, params.batteryCapacityWh - s.batteryEnergyWh);
m.currentBattery = saturate<std::int16_t>(s.batteryCurrent * 100.0);
m.currentConsumed = saturate<std::int32_t>(usedWh / (kNominalCellVoltage * params.batteryCells) * 1000.0);
m.energyConsumed = saturate<std::int32_t>(usedWh * 36.0);
EL01.11TSolution

Frontier application: Falcon 9 at lift-off. SpaceX publishes Falcon 9’s total mass as 549,054 kg and the sea-level thrust of its first stage, nine Merlin engines, as 7607 kN. (a) The lift-off thrust-to-weight ratio. (b) The initial upward acceleration, ignoring drag. (c) What an accelerometer in the payload reads at lift-off, in m/s2^2 and in gg. (d) As the first stage burns its propellant, assuming constant thrust, what happens to T/WT/W and to the accelerometer’s reading? (e) Why can a rocket lift off with a T/WT/W that would leave a multicopter almost uncontrollable?

EL01.12TSolution

Requirements. Rewrite each as a verifiable requirement with a number, a unit, conditions, and a verification method: (a) “The drone should be light.” (b) “The drone shall fly for a long time and be easy to repair.” (c) “The arm shall be strong.” Then derive one child requirement from your (b) and name its parent.

EL01.13TSolution

Tilted flight. A drone tilted by an angle θ\theta from level has its total thrust TT tilted by the same angle. (a) Using right-triangle trigonometry (Module 04), write the vertical part of TT and the thrust needed to hold altitude. (b) Show that the steepest tilt that still holds altitude is θmax⁡=arccos⁡(1/(T/W))\theta_{\max}=\arccos\bigl(1/(T/W)\bigr). (c) Evaluate it for T/W=1.5T/W=1.5, 2, and 4.60.

EL01.14TSolution

Frontier application: Ingenuity on Mars. NASA’s Ingenuity helicopter had a mass of about 1.8 kg, and Mars’s surface gravity is 3.73 m/s2^2. (a) Its weight on Mars and on Earth. (b) A kitchen scale calibrated on Earth is carried to Mars: what does it display for Ingenuity, and why? (c) The thrust needed to hover on Mars, in newtons and in gram-force. (d) Mars’s air is about one hundredth as dense as Earth’s. Without equations yet, say why that might force a rotor 1.2 m across; EL03 answers it with momentum theory.

EL01.15SSolution

The best battery, with calculus (after Module 14). (a) For the constant-efficiency model t=K mbm0+mbt=K\,\dfrac{m_b}{m_0+m_b}, use the quotient rule to show dt/dmb>0dt/dm_b>0 for every mbm_b: no interior maximum. (b) Momentum theory (EL03) will show that hover power grows like mass to the power 32\tfrac32, so t=C mb (m0+mb)−3/2t=C\,m_b\,(m_0+m_b)^{-3/2}. Find the mbm_b that maximizes tt. Watch the exponent: −32−1-\tfrac32-1. (c) Compare with section 2.10’s figure.

EL01.16LSolution

Complete lab steps 1–9 until check.py reports every step passing on your own code. Record the first test that failed and its first incorrect step in your error log.

EL01.17LSolution

Mutation hunt. Make each change, predict which step fails first and what the message says, run check.py, and undo it: (a) weight returns mass; (b) net_force adds the weight instead of subtracting it; (c) battery_energy’s default cell voltage becomes 4.2 V; (d) arm converts lengths with .m_as("m"); (e) remove_bias averages the whole record instead of the still window. One sentence each on what the failing test’s name told you.

4.2. Code-reading drill: PX4 estimates its own hover thrust#

PX4, the open-source autopilot your drone will run, learns its hover throttle in flight. It watches the vertical acceleration and the thrust it is commanding, and fits the one number that makes Newton’s second law agree with them. The code is C++ written by other people, and reading someone else’s code is a skill of its own; its physics is exactly this module’s. Read these three files at this pinned commit (tag v1.17.0); the line numbers below refer to it:

Protocol. Read the header comment of the .hpp (lines 34–62) and the whole .cpp once, top to bottom, without stopping. Words you have not met yet (Kalman gain, covariance, innovation variance) belong to EL22; for now, treat them as machinery that adjusts one number. Then write your explanation for each question before opening its solution.

EL01.18RSolution

.hpp lines 46–48 and .cpp lines 102–105. Here thrust and _hover_thr are fractions of the maximum thrust, between 0 and 1. Show that g * thrust / _hover_thr - g is Newton’s second law for a multicopter, as in section 2.5. What does a positive value mean?

EL01.19RSolution

.cpp lines 84–87. computeH returns the derivative of the predicted acceleration with respect to the hover thrust. Rewrite the prediction as g T Th−1−gg\,T\,T_h^{-1}-g and differentiate it with respect to ThT_h yourself; compare your answer with line 86. Why is it negative, physically?

EL01.20RSolution

MulticopterHoverThrustEstimator.cpp, line 157 and lines 181–192. (a) What unit must local_pos.timestamp be in, given * 1e-6f? (b) PX4’s position estimate uses a north-east-down frame, in which zz points down. Explain both minus signs in fuseAccZ(-local_pos.az, -thrust_allocated(2)) using the comment on lines 183–184.

EL01.21RSolution

.hpp lines 81 and 97–99. The hover-thrust estimate starts at 0.5 and is kept between 0.1 and 0.9. What thrust-to-weight ratio does each of those three numbers correspond to? Why might a designer refuse estimates outside that range?

EL01.22RSolution

.hpp lines 102–109: a dimensional audit. The innovation (line 108) is an acceleration. The innovation variance is computed in .cpp line 93 as HPH+RHPH+R, where RR is _acc_var. A variance has the square of its quantity’s unit. Which unit comments are inconsistent with that, and what should they say? (This comment is unchanged on PX4’s main branch as of 26 September 2026: see stretch S4.)

4.3. Mastery gate#

EL01.23GSolution

A 3S 2200 mAh LiPo (3.7 V per cell). Its energy in W h and in joules, showing every factor of 1. Then show, with exponents, that J/s is a watt.

EL01.24GSolution

A 1.6 kg drone runs four motors at 4.5 N each. Draw the free-body diagram; find the net force, the acceleration, and the accelerometer’s reading; then find the thrust per motor that would hold a hover, in newtons and gram-force.

EL01.25GSolution

Four motors, each 1100 gf maximum; predicted mass 1.3 kg; a 4S 4000 mAh LiPo (3.7 V per cell, 80% usable); hover efficiency 7.5 gf/W; electronics 5 W. Find T/WT/W, the hover thrust fraction, the maximum climb acceleration, the hover power, and the flight time.

EL01.26GSolution

Each snippet has one bug. For each: name it (field-guide number from section 2.16, or describe it if none fits), give the smallest input that exposes it and the wrong result, and fix it.

Python
# (a)
def thrust_to_weight(max_thrust_per_motor, motor_count, mass, g=G0):
    return max_thrust_per_motor / (mass * g)

# (b)
def battery_energy(capacity, cells, cell_voltage=Q_(3.7, "V")):
    return (capacity * cell_voltage).to("W*h")

# (c)
def velocity_and_height(t, accel, still_until):
    v = cumulative_trapezoid(t, accel)
    return v, cumulative_trapezoid(t, v)

4.4. Next steps#

  • Record the module. Add an EL01 row to your progress record, a spreadsheet with one row per module (first attempt, gate score, teach-back, tests passing, first error, next review in seven days), and log every miss in your error log.
  • The calculus modules. Module 12 is section 2.4 with more examples; Module 14’s optimization is EL01.15; Module 16’s antiderivatives and Module 23’s trapezoid rule are lab step 8.
  • EL02 turns the vertical line into a plane: moments, the centre of mass, why unequal thrusts tip the drone, and the matrix that turns “roll, pitch, yaw, thrust” into four motor commands. Your arm gains a place on a frame.

5. Development log entry#

Keep a running DEVLOG.md at the top of your eng-lab repository. It becomes public evidence of what you built, so write every claim so that a stranger could check it. For this module, one entry:

6. Stretch (optional)#

  • S1. The best battery. EL01.15, after Module 14: the calculus behind section 2.10’s curve.
  • S2. Drag in a climb. Add quadratic drag, D=kv2D=kv^2, to section 2.5’s climb and find the steady climb speed at full thrust for k=0.02k=0.02 N s2^2/m2^2 (the value FateMMO’s drone simulator uses, EL01.10). EL19 builds the full model.
  • S3. Units at compile time, in C++23 (portfolio). Write a tiny Quantity<M, L, T> template whose exponents are template parameters, so that mass * accel has type Quantity<1, 1, -2> and adding it to a length does not compile. Port weight and thrust_to_weight to it, then compare your design with mp-units. It is exactly the tool the VehicleParams struct of EL01.10 is missing.
  • S4. Your first open-source contribution. EL01.22 finds unit comments in PX4 that are dimensionally inconsistent, still present on the main branch as of 26 September 2026. Check the current main branch first; if they are still there, read PX4’s contribution guide and open a one-line pull request that corrects them. A merged fix, however small, is public evidence of careful reading.
  • S5. How good is your phone’s accelerometer? Record 60 seconds with the phone perfectly still. Compute the mean and standard deviation of the vertical reading. Predict the height error after 10 seconds of double integration from a bias error of one standard deviation divided by the square root of the number of still samples, then measure it by integrating the still record itself. This is the first step toward EL16 (sensor noise) and EL22 (Kalman filtering).

Frontier connections: where this module’s ideas show up at scale#

Read these after the gate, for orientation.

Sources for this chapter (checked 26 September 2026)#

Thrust tables and recommended take-off weights: uav-en.tmotor.com/2022/Air-Gear_0510/798.html (AIR2216 II, the same sheet Holybro links as its X500 V2 motor specification: holybro.com/products/x500-v2-kits); www.t-hobby.com/products/micro-fpv-drones-brushless-motor (F1404); www.t-hobby.com/products/brushless-motor-for-fpv-drones-60pro-v-2207-5 (F60PRO V). Components: gensace.de/products/gens-ace-g-tech-5000mah-14-8v-4s1p-60c-lipo-battery-pack-with-xt90-plug-bashing-series; holybro.com/products/kakute-h7-v2-stacks; docs.holybro.com/gps-and-rtk-system/m8n-m9n-m10-gps/standard-m10-m9n-m8n-gps/overview; radiomasterrc.com/products/rp1-expresslrs-2-4ghz-nano-receiver; PX4’s simulated x500, at the PX4-gazebo-models commit PX4 v1.17.0 pins: github.com/PX4/PX4-gazebo-models/blob/b6127f4ec20de867e215fb5f78ae88b80f371909/models/x500_base/model.sdf (masses) and github.com/PX4/PX4-gazebo-models/blob/b6127f4ec20de867e215fb5f78ae88b80f371909/models/x500/model.sdf (motor constant, top speed). LiPo voltages (3.7 V nominal, 4.2 V full, 3.5 V recommended cut-off): genstattu.com/bw. Constants and standards: standard gravity, physics.nist.gov/cgi-bin/cuu/Value?gn; Mars’s mean surface gravity, 3.73 m/s2^2, NASA’s Mars Fact Sheet (last updated 19 May 2025), nssdc.gsfc.nasa.gov/planetary/factsheet/marsfact.html; gram-force and kilogram-force, www.nist.gov/pml/special-publication-811/nist-guide-si-appendix-b-conversion-factors/nist-guide-si-appendix-b9; mass growth allowance, the NASA technical memorandum in Frontier connections; carbon-fibre plate density, dragonplate.com/solid-carbon-fiber-sheets-plates (1.44 to 1.61 g/cm3^3 across that maker’s solid plates). Regulations: www.ecfr.gov/current/title-14/section-48.15; www.faa.gov/uas/recreational_flyers; www.faa.gov/uas/getting_started/remote_id; www.faa.gov/uas/commercial_operators. Frontier examples: Falcon 9, www.spacex.com/vehicles/falcon-9; Mars Climate Orbiter, as in Frontier connections; Ingenuity’s mass, rotor span, and air density, JPL’s quick facts, www.jpl.nasa.gov/news/press_kits/ingenuity/landing/quick_facts/. Software: phyphox, phyphox.org/; MAVLink’s BATTERY_STATUS units, at the commit PX4 v1.17.0 pins, github.com/mavlink/mavlink/blob/33af200d25ec6f0925b49b1ba82bbf1294ea5f72/message_definitions/v1.0/common.xml; pint, pint.readthedocs.io/; build123d, build123d.readthedocs.io/. The author’s own code: the drone simulator in the FateMMO engine and the grenade system of ZeroVector, read 26 September 2026.

Hint ladders for the lab#

Climb one rung at a time, and try again after each one. Rung 1 is a question, rung 2 points to the idea, rung 3 is a skeleton with the key parts left for you.

L1. Step 1: weight#

Rung 1. Which two quantities multiply to give a weight, and which unit should the answer be expressed in?

Rung 2. Section 2.3, W=mgW=mg; section 3.1’s pint tour for .to().

Rung 3. return (____ * ____).to("N")

L2. Step 2: Newton’s second law#

Rung 1. In section 2.5’s free-body diagram, which forces point up and which point down? Which of them does thrusts hold?

Rung 2. Section 2.5 for the net force and a=Fnet/ma=F_{\text{net}}/m; section 2.6 for the reading, f=a+gf=a+g; section 2.13’s table for sum with a start value.

Rung 3.

Python
    total = sum(thrusts, Q_(0, "N"))
    return total - ____                     # net_force
    return (net_force(____, ____, g) / ____).to("m/s^2")      # acceleration
    return (____ + g).to("m/s^2")           # accelerometer_reading

L3. Step 3: thrust-to-weight#

Rung 1. How much thrust can all the motors make together? How much does the drone weigh? Which of those is on top?

Rung 2. Section 2.7: T/WT/W, ϕ=1/(T/W)\phi=1/(T/W), and amax⁡=g(T/W−1)a_{\max}=g(T/W-1). Reuse weight.

Rung 3.

Python
    return motor_count * ____ / weight(____, g)          # thrust_to_weight
    return weight(mass, g) / ____                        # hover_thrust_per_motor
    return 1 / thrust_to_weight(____)                    # hover_thrust_fraction
    return g * (thrust_to_weight(____) - ____)           # max_climb_acceleration

L4. Step 4: energy and flight time#

Rung 1. What is the pack’s voltage when cells cells of cell_voltage each sit in series? What is charge times voltage?

Rung 2. Section 2.2’s worked example and section 2.9’s hover power and flight time. raise ValueError("...") rejects bad inputs.

Rung 3.

Python
    return (capacity * cells * ____).to("W*h")             # battery_energy
    return motor_count * (____ / ____)                     # propulsion_power
    if not (0 < usable_fraction <= 1): raise ValueError("usable_fraction must be in (0, 1]")
    if power <= Q_(0, "W"): raise ValueError("power must be positive")
    return (____ * ____ / ____).to("min")                  # flight_time

L5. Step 5: the mass budget#

Rung 1. For one item, what is its predicted mass? How do you look up its allowance?

Rung 2. Section 2.8, mpredicted=∑mi(1+ai)m_{\text{predicted}}=\sum m_i(1+a_i); the module docstring’s Python traps.

Rung 3.

Python
    return sum((item.mass * (1 + allowances[____]) for item in items), Q_(0, "g")).to("g")
    split = {}
    for item in items:
        split[item.subsystem] = split.get(item.subsystem, Q_(0, "g")) + ____

For allowable_mass, solve n Tmax⁡/(mg)=(T/W)min⁡n\,T_{\max}/(mg)=(T/W)_{\min} for mm on paper first.

L6. Step 6: the arm and its hand check#

Rung 1. (For arm_volume_by_hand.) Sketch the top view. Which rectangles add, which rectangle is counted twice, and which circles come out?

Rung 2. Section 2.12’s formula. Circles take radii in build123d but your formula uses diameters: πd2/4\pi d^2/4.

Rung 3.

Python
    area = length * width + pad_size**2 - (pad_size / 2) * ____
    holes = math.pi / 4 * (4 * ____**2 + ____**2 + 2 * ____**2)
    return ((area - holes) * ____).to("mm^3")

L7. Step 7: requirements#

Rung 1. For each requirement: could someone who has never met you check it, and how? Does it name one quantity, with a unit?

Rung 2. Section 2.11 and the file’s comments; the list of results in tests/test_requirements.py.

Rung 3. A derived requirement in the file’s format: id = "SYS-002", text “The drone’s predicted take-off mass shall not exceed 2664 g.”, verification = "Analysis", result = "mass.predicted", op = "<=", threshold = "2664 g", parent = "SYS-001". Now write one of your own that is verified by Test and one by Inspection.

L8. Step 8: integrating the phone data#

Rung 1. If you know the acceleration at two instants Δt\Delta t apart, by how much did the velocity change between them? Where does the running total start?

Rung 2. Section 2.4’s trapezoid rule and section 3.8’s bias warning. A boolean mask, t < still_until, selects the still samples.

Rung 3.

Python
    total = 0 * y[0] * t[0]                    # zero, already in y's unit times t's unit
    values = [total]
    for k in range(1, len(t)):
        dt = t[k] - t[k - 1]
        total = total + ____                   # one trapezoid: the average of its ends, times dt
        values.append(total)
    return Q_.from_list(values)                # one Quantity array; the units never left

    still = accel[t < still_until]
    return accel - ____                        # remove_bias

L9. Step 9: the tinker loop#

Rung 1. Which section does each knob belong to, and what does that section’s equation say will happen?

Rung 2. Section 3.9’s knob list; write the prediction as a number with a direction (“flight time up about 30%”), not a feeling.

Rung 3. After each run, answer in the explanation: which equation explains the result, and where did your prediction differ, and why?

Complete solutions: EL01#

Open a solution only after you have written your own attempt. Compare at the first step where your work differs. A corrected answer is not yet an independent success.

EL01.01Show solution

Exponents in, quantity out

(a) kg m s−2kg=m s−2\dfrac{\text{kg\,m\,s}^{-2}}{\text{kg}}=\boxed{\text{m\,s}^{-2}}, an acceleration. That is why gravity is sometimes written 9.81 N/kg: newtons of weight per kilogram of mass. (b) (kg m2s−3)(s)=kg m2s−2(\text{kg\,m}^2\text{s}^{-3})(\text{s})=\boxed{\text{kg\,m}^2\text{s}^{-2}}, a joule: energy. (c) m s−1⋅s−1=m s−2\text{m\,s}^{-1}\cdot\text{s}^{-1}=\boxed{\text{m\,s}^{-2}}, an acceleration; the exponent is −1+(−1)=−2-1+(-1)=-2. (d) kg m2s−2A s=kg m2s−3A−1\dfrac{\text{kg\,m}^2\text{s}^{-2}}{\text{A\,s}}=\boxed{\text{kg\,m}^2\text{s}^{-3}\text{A}^{-1}}, a volt: energy per unit charge. (e) (A s)(kg m2s−3A−1)s=kg m2s−3\dfrac{(\text{A\,s})(\text{kg\,m}^2\text{s}^{-3}\text{A}^{-1})}{\text{s}}=\boxed{\text{kg\,m}^2\text{s}^{-3}}, a watt: power. Final sweep: every negative exponent kept its sign.

Back to EL01.01
EL01.02Show solution

A chain of ones

(a) 2200 mA h×1 A1000 mA×3600 s1 h=7920 C2200\ \text{mA\,h}\times\dfrac{1\ \text{A}}{1000\ \text{mA}}\times\dfrac{3600\ \text{s}}{1\ \text{h}}=\boxed{7920\ \text{C}}. (b) Pack voltage 6×3.7=22.26\times3.7=22.2 V; E=2.2 A h×22.2 V=48.84 W hE=2.2\ \text{A\,h}\times22.2\ \text{V}=\boxed{48.84\ \text{W\,h}}, and 48.84 W h×3600 J1 W h=175,824 J=175.8 kJ48.84\ \text{W\,h}\times\dfrac{3600\ \text{J}}{1\ \text{W\,h}}=175{,}824\ \text{J}=\boxed{175.8\ \text{kJ}}. (c) 1.8 kgf×9.80665 N1 kgf=17.65 N1.8\ \text{kgf}\times\dfrac{9.80665\ \text{N}}{1\ \text{kgf}}=\boxed{17.65\ \text{N}}. (d) “350 g” of thrust is 350 gf =0.350 kgf=3.432 N=0.350\ \text{kgf}=\boxed{3.432\ \text{N}}. (e) 100 W h×3600 s/h=360,000 W s=360 kJ100\ \text{W\,h}\times3600\ \text{s/h}=360{,}000\ \text{W\,s}=360\ \text{kJ}: yes.

Back to EL01.02
EL01.03Show solution

Grams of thrust are gram-force

(a) W=0.650×9.80665=6.374 NW=0.650\times9.80665=\boxed{6.374\ \text{N}}. (b) Each motor carries a quarter: 650/4=162.5 gf=0.1625×9.80665=1.594 N650/4=\boxed{162.5\ \text{gf}}=0.1625\times9.80665=\boxed{1.594\ \text{N}}. (c) “2.1 kg” is 2.1 kgf=20.59 N2.1\ \text{kgf}=\boxed{20.59\ \text{N}}. Four make 8.4 kgf, but a 9 kg drone weighs 9 kgf: T/W=8.4/9=0.93<1T/W=8.4/9=0.93<1. No: it cannot even hover.

Back to EL01.03
EL01.04Show solution

Constant acceleration, then constant speed

(a) v=at=2.5×4.0=10 m/sv=at=2.5\times4.0=\boxed{10\ \text{m/s}} and h=12at2=12(2.5)(16)=20 mh=\tfrac12at^2=\tfrac12(2.5)(16)=\boxed{20\ \text{m}}. Check: h(t)=1.25t2h(t)=1.25t^2 gives h′(t)=2.5th'(t)=2.5t (velocity) and h′′(t)=2.5h''(t)=2.5 (acceleration). (b) The last 40 m at 10 m/s take 4.0 s, so it reaches 60 m at t=8.0 st=\boxed{8.0\ \text{s}}.

Back to EL01.04
EL01.05Show solution

Sum the forces, then divide by the mass

(a) Up positive; four thrusts up at the motors; the weight mg=2.0×9.80665=19.61mg=2.0\times9.80665=19.61 N down at the centre of mass. (b) ∑F=(6+6+5+5)−19.61=2.39 N\sum F=(6+6+5+5)-19.61=\boxed{2.39\ \text{N}} upward. (c) a=2.39/2.0=1.19 m/s2a=2.39/2.0=\boxed{1.19\ \text{m/s}^2} upward. (d) Steady climb means a=0a=0, so the thrusts total exactly the weight, 19.61 N\boxed{19.61\ \text{N}} (4.90 N each if shared). The 3 m/s does not enter; only a change of speed needs a net force.

Back to EL01.05
EL01.06Show solution

Reading =a+g=a+g

(a) 0+9.81=9.810+9.81=\boxed{9.81}. (b) 1.2+9.81=11.011.2+9.81=\boxed{11.01}. (c) Constant speed: a=0a=0, so 9.81\boxed{9.81}. (d) Slowing on the way up is a downward acceleration: −1.0+9.81=8.81-1.0+9.81=\boxed{8.81}. (e) 0+3.73=3.730+3.73=\boxed{3.73}. In an elevator you feel heavier in (b) and lighter in (d) for the same reason the accelerometer reads more and less: your body, like the proof mass, feels only the floor’s push.

Back to EL01.06
EL01.07Show solution

Interpolate, and read efficiency where you operate

(a) 1332 gf×9.80665 mN/gf=13.06 N1332\ \text{gf}\times9.80665\ \text{mN/gf}=\boxed{13.06\ \text{N}}. (b) 1400/4=3501400/4=350 gf per motor, between the 40% row (309 gf) and the 45% row (373 gf): 40+5×350−309373−309=40+5×4164=43.2%40+5\times\dfrac{350-309}{373-309}=40+5\times\dfrac{41}{64}=\boxed{43.2\%} throttle. (c) ηT=8.45+(8.15−8.45)×4164=8.26 gf/W\eta_T=8.45+(8.15-8.45)\times\dfrac{41}{64}=\boxed{8.26\ \text{gf/W}}, so each motor draws 350/8.26=42.4 W350/8.26=\boxed{42.4\ \text{W}} (169.5 W for four). (d) 9.12 gf/W is the efficiency at 210 gf. At 350 gf the motor works harder and less efficiently; using 9.12 would understate the power by about 9% and overstate the flight time by about 10%. This is field-guide mistake U7.

Back to EL01.07
EL01.08Show solution

More motors, more margin

T/W=6×13322400=3.33T/W=\dfrac{6\times1332}{2400}=\boxed{3.33}; ϕ=0.300\phi=\boxed{0.300}; amax⁡=9.807×(3.33−1)=22.8 m/s2a_{\max}=9.807\times(3.33-1)=\boxed{22.8\ \text{m/s}^2}; θmax⁡=arccos⁡(0.300)=72.5∘\theta_{\max}=\arccos(0.300)=\boxed{72.5^\circ}.

Back to EL01.08
EL01.09Show solution

Basic, predicted, allowable, margin

Basic: 250+256+421+50+56+60=1093 g250+256+421+50+56+60=\boxed{1093\ \text{g}}. Predicted: 250(1.2)+256(1.05)+421(1.05)+50(1.05)+56(1.05)+60(1.2)=300+268.8+442.05+52.5+58.8+72=1194.2 g250(1.2)+256(1.05)+421(1.05)+50(1.05)+56(1.05)+60(1.2)=300+268.8+442.05+52.5+58.8+72=\boxed{1194.2\ \text{g}}. Allowable: 4×13322=2664 g\dfrac{4\times1332}{2}=\boxed{2664\ \text{g}}. Margin: 2664−1194.22664=55.2%\dfrac{2664-1194.2}{2664}=\boxed{55.2\%}.

Back to EL01.09
EL01.10Show solution

Your simulator, audited

(a) Tmax⁡=kω2=9.1×10−6×9002=7.371T_{\max}=k\omega^2=9.1\times10^{-6}\times900^2=7.371 N per motor, 29.48 N for four; the weight is 1.5×9.80665=14.711.5\times9.80665=14.71 N; T/W=2.004T/W=\boxed{2.004}, matching the comment’s “about 2”. (b) hoverThrottle() returns the hover speed as a fraction of the maximum: mg/(4k)/ωmax⁡=14.71/(3.64×10−5)/900=635.7/900=0.706\sqrt{mg/(4k)}\big/\omega_{\max}=\sqrt{14.71/(3.64\times10^{-5})}\big/900=635.7/900=\boxed{0.706}. Thrust grows as ω2\omega^2, so the hover thrust fraction is 0.7062=0.499=1/(T/W)0.706^2=0.499=1/(T/W). Both are correct; they answer different questions. (c) 4×3.7 V×5 A h=74 W h4\times3.7\ \text{V}\times5\ \text{A\,h}=\boxed{74\ \text{W\,h}}. (d) W h divided by volts is A h, and ×1000\times1000 makes mA h: exactly what MAVLink’s BATTERY_STATUS.current_consumed field is defined in. usedWh * 36.0: 1 W h=3600 J=361\ \text{W\,h}=3600\ \text{J}=36 hectojoules, the unit of energy_consumed; the * 100.0 in the currentBattery line is there because current_battery is in centiamperes. (e) mass (kg), armLength (m), and inertia (kg m2^2) state no unit. A fix that makes a slip a compile error: give each a type that carries its unit, as std::chrono does for time, either with a small template (stretch S3) or with mp-units, for example quantity<si::kilogram> mass = 1.5 * si::kilogram;. Then passing a length where a mass belongs does not compile. Even renaming the fields massKg and armLengthM is a cheap, partial improvement.

Back to EL01.10
EL01.11Show solution

A rocket’s thrust-to-weight

(a) T=7607T=7607 kN; W=549,054×9.80665=5.384×106W=549{,}054\times9.80665=5.384\times10^6 N =5384=5384 kN; T/W=7607/5384=1.41T/W=7607/5384=\boxed{1.41}. (b) a=g(T/W−1)=9.807×0.413=4.05 m/s2a=g(T/W-1)=9.807\times0.413=\boxed{4.05\ \text{m/s}^2}. (c) The reading is the thrust per unit mass, f=T/m=7,607,000/549,054=13.85 m/s2=1.41 gf=T/m=7{,}607{,}000/549{,}054=\boxed{13.85\ \text{m/s}^2}=\boxed{1.41\,g}: section 2.6’s rule, on a rocket. (d) The mass falls while the thrust holds, so both T/W=T/(mg)T/W=T/(mg) and f=T/mf=T/m rise; at half the mass they double. The crew feels heavier and heavier as the stage empties. (e) A multicopter must hover and steers by making some motors push harder than others, so it needs thrust in reserve (section 2.7). A rocket only has to climb away; it steers by swivelling (gimballing) its engines rather than by throttling some against others, and every kilogram of extra engine is a kilogram less payload. Different missions, different T/WT/W requirements: the same law.

Back to EL01.11
EL01.12Show solution

Verifiable rewrites

Model answers; yours may choose other numbers, but each must be one quantity, with a unit, conditions, and a method. (a) “SYS-010: The drone’s take-off mass, battery included, shall not exceed 1500 g.” Verification: Inspection (weigh on a calibrated scale). (b) Two requirements, because it held two: “SYS-011: The drone shall hover for at least 15.0 minutes at sea level in still air, from a full charge until any cell reaches 3.5 V under load.” Verification: Test (a timed hover with the voltage logged). “SYS-012: One arm shall be replaceable in no more than 10 minutes with only a 2.5 mm hex driver.” Verification: Demonstration. A child of SYS-011: “PWR-001: The battery shall store at least 43.7 W h.” (At 139.7 W, 15 minutes needs 139.7×0.25=34.9139.7\times0.25=34.9 W h usable, and 34.9/0.8=43.734.9/0.8=43.7 W h stored.) Verification: Analysis; parent SYS-011. (c) “STR-001: Each arm shall carry a static tip load of 26.1 N, twice the motor’s maximum thrust, without breaking or bending permanently.” Verification: Test (a load test, EL07). The words “strong” and “light” are not requirements until they become numbers.

Back to EL01.12
EL01.13Show solution

The tilt budget

(a) The thrust’s vertical part is Tcos⁡θT\cos\theta. Holding altitude needs Tcos⁡θ=mgT\cos\theta=mg, so T=mg/cos⁡θ\boxed{T=mg/\cos\theta}. (b) The motors can give at most nTmax⁡nT_{\max}, so mg/cos⁡θ≤nTmax⁡mg/\cos\theta\le nT_{\max}, which rearranges to cos⁡θ≥mgnTmax⁡=1T/W\cos\theta\ge\dfrac{mg}{nT_{\max}}=\dfrac1{T/W}, that is, θ≤arccos⁡(1/(T/W))\boxed{\theta\le\arccos\bigl(1/(T/W)\bigr)}. (c) T/W=1.5T/W=1.5: arccos⁡(0.667)=48.2∘\arccos(0.667)=\boxed{48.2^\circ}; T/W=2T/W=2: 60.0∘\boxed{60.0^\circ}; T/W=4.60T/W=4.60: arccos⁡(0.217)=77.4∘\arccos(0.217)=\boxed{77.4^\circ}.

Back to EL01.13
EL01.14Show solution

Same mass, different weight

(a) Mars: 1.8×3.73=6.71 N1.8\times3.73=\boxed{6.71\ \text{N}}. Earth: 1.8×9.80665=17.65 N1.8\times9.80665=\boxed{17.65\ \text{N}}. (b) The scale measures a force and divides by the gg it was calibrated with: 6.714 N/9.80665 m/s2=0.6856.714\ \text{N}\big/9.80665\ \text{m/s}^2=0.685 kg, so it displays 685 g\boxed{685\ \text{g}}. The mass is still 1.8 kg. (c) 6.71 N=685 gf\boxed{6.71\ \text{N}}=\boxed{685\ \text{gf}}. (d) A rotor makes thrust by pushing air downward. With a hundredth of the air in every cubic metre, it must push a far larger volume of air, faster, to get the same thrust: a bigger disk, spinning faster. EL03’s momentum theory puts numbers on it.

Back to EL01.14
EL01.15Show solution

The calculus behind the mass spiral

(a) With the Modular Workbench, u=mbu=m_b, u′=1u'=1, v=m0+mbv=m_0+m_b, v′=1v'=1: dtdmb=K (m0+mb)−mb(m0+mb)2=K m0(m0+mb)2>0\dfrac{dt}{dm_b}=K\,\dfrac{(m_0+m_b)-m_b}{(m_0+m_b)^2}=\boxed{\dfrac{K\,m_0}{(m_0+m_b)^2}>0}. The slope never reaches zero: no interior maximum, only diminishing returns. (b) Product rule on mb (m0+mb)−3/2m_b\,(m_0+m_b)^{-3/2}; the new exponent is −32−1=−3−22=−52-\tfrac32-1=\tfrac{-3-2}{2}=-\tfrac52:

dtdmb=C[(m0+mb)−3/2−32 mb (m0+mb)−5/2]=C (m0+mb)−5/2[(m0+mb)−32mb].\frac{dt}{dm_b}=C\Bigl[(m_0+m_b)^{-3/2}-\tfrac32\,m_b\,(m_0+m_b)^{-5/2}\Bigr]=C\,(m_0+m_b)^{-5/2}\Bigl[(m_0+m_b)-\tfrac32m_b\Bigr].

It is zero when m0=12mbm_0=\tfrac12m_b, so mb=2m0\boxed{m_b=2m_0}; the slope is positive before and negative after, so this is a maximum. (c) Your drone’s other components weigh about 717 g with allowances, so an ideal rotor would peak with a battery near 1.4 kg; your table-based curve peaks near 2.1 kg, about three times m0m_0. The real motor’s efficiency falls more slowly with thrust than an ideal rotor’s, because at low thrust the motor’s own fixed losses already waste a sizeable share. EL03 and EL04 separate those effects.

Back to EL01.15
EL01.16Show solution

The usual first failures

The tests are the judge: the lab is done when check.py reports every step passing on your own code. If a step will not pass after an honest attempt, check this list before anything else. The usual first failures, in order: stripping units with .m to “make the numbers work” (step 1, U11); adding the weight in net_force (step 2, U8); forgetting motor_count (step 3, U4); energy from capacity alone (step 4, U1); a sum with no start value (step 5); diameters passed where radii belong (step 6); a requirement with no unit on its threshold (step 7); and integrating without removing the bias (step 8, U12). Log the category and the first incorrect step, not just “fixed.”

Back to EL01.16
EL01.17Show solution

What each mutation breaks

(a) Step 1 fails at once, and not in your arithmetic: the @quantities wrapper refuses the result. check.py names test_weight_accepts_grams first, and its message ends Cannot convert from 'g' to '[force]' (weight() result); the 1.5 kg tests say 'kg'. Steps 2 and 3 fail too, because net_force and the thrust-to-weight family call weight: one wrong function breaks everything built on it. The unit system caught the bug before any number could. (b) Step 2 fails. check.py lists tests alphabetically, so it names test_acceleration_climbing first: 26.47 m/s2^2 instead of 6.86. test_net_force_four_motors shows 30.71 N instead of 1.29 N. Both are off by exactly twice the weight (2×14.71=29.422\times14.71=29.42 N, and 2g=19.612g=19.61 m/s2^2): the fingerprint of a sign flip. (c) Step 4 fails. check.py names test_energy_in_joules first (117,936 J instead of 103,896 J), and test_energy_of_a_6s_1300_pack shows 32.76 W h instead of 28.86: every energy is 4.2/3.7=1.1354.2/3.7=1.135 times too big. (d) Step 6 fails, and check.py names test_bounding_box: the arm is 0.114 long where 114 belongs. Every length is 1000 times too small, so the volume is 10910^9 times too small. (e) The surprise: every smooth-lift test still passes. A lift that starts and ends at rest has zero average acceleration, so the average over the whole record happens to equal gg plus the bias. Two other tests fail. test_bias_is_the_average_of_the_whole_still_window, named first, feeds a still record with one spike at its start; averaging all 200 samples instead of the window’s 100 halves the spike’s pull on the bias, so every corrected sample is off by 0.005 m/s2^2. test_bias_must_come_from_the_still_window ends its record while the phone is still speeding up, so the whole-record average is no longer gg plus the bias, and the final speed comes out near 0 instead of about 1 m/s. Test at the edges of a method’s assumptions, not only in its comfortable middle.

Back to EL01.17
EL01.18Show solution

Newton’s second law, normalized

Section 2.5 gives a=TNm−ga=\dfrac{T_N}{m}-g, with TNT_N the total thrust in newtons. In a hover TN,hover=mgT_{N,\text{hover}}=mg, so 1m=gTN,hover\dfrac1m=\dfrac{g}{T_{N,\text{hover}}} and a=g TNTN,hover−ga=g\,\dfrac{T_N}{T_{N,\text{hover}}}-g. Dividing the top and bottom of that ratio by the maximum thrust turns it into a ratio of fractions: a=g TTh−ga=g\,\dfrac{T}{T_h}-g, which is line 104 exactly. TT is the commanded thrust as a fraction of maximum; ThT_h is the fraction that would hover. A positive result means the drone is being pushed upward faster: the command exceeds the hover thrust. (The model assumes the actual thrust is proportional to the command; section 2.7’s thrust table shows why that needs care, and EL04 returns to it.)

Back to EL01.18
EL01.19Show solution

A negative exponent in flight code

Parentheses Rewrite first: h=g T Th−1−gh=g\,T\,T_h^{-1}-g. Freeze TT and differentiate in ThT_h: dhdTh=g T⋅(−1) Th−2=−g TTh2\dfrac{dh}{dT_h}=g\,T\cdot(-1)\,T_h^{-2}=\boxed{-\dfrac{g\,T}{T_h^2}}, and line 86 computes exactly that: -CONSTANTS_ONE_G * thrust / (_hover_thr * _hover_thr). It is negative because a drone that needs a larger share of its thrust just to hover (a heavier one) gets less upward acceleration from the same command. The exponent went from −1-1 to −2-2: the same bookkeeping as section 2.2’s −48x−4-48x^{-4}.

Back to EL01.19
EL01.20Show solution

Microseconds and a frame that points down

(a) * 1e-6f converts to seconds, so the timestamps are in microseconds (PX4 stamps every message in microseconds since boot). (b) In north-east-down coordinates, zz points down, so local_pos.az is positive when the drone accelerates downward, and the thrust vector’s zz component is negative when it pushes up. The estimator’s model is written up-positive, as the comment on lines 183–184 says, so both are negated. A sign convention chosen once and converted at the boundary is the free-body-diagram rule of section 2.5, applied to software.

Back to EL01.20
EL01.21Show solution

Hover thrust is 1/(T/W)1/(T/W)

Since Th=ϕ=1/(T/W)T_h=\phi=1/(T/W): the initial guess 0.5 means T/W=2\boxed{T/W=2}; the bounds mean T/W=1/0.1=10T/W=1/0.1=\boxed{10} and 1/0.9=1.111/0.9=\boxed{1.11}. A multicopter with T/WT/W near 1.1 has almost no control headroom, and one above 10 is rare outside racing, so estimates outside the range are more likely a disturbance (ground effect, a gust, a transient) than the truth. Clamping keeps one bad estimate from corrupting the controller that uses it.

Back to EL01.21
EL01.22Show solution

A dimensional audit

The innovation is an acceleration (m/s2^2), so its variance is in (m/s2^2)2^2 =m2/s4=\boxed{\text{m}^2\text{/s}^4}. In HPH+RHPH+R, HH is in (m/s2^2) per unit of normalized thrust and PP in (normalized thrust)2^2, so HPHHPH is in m2^2/s4^4, and RR, _acc_var, must match it. Lines 104 and 109 say m2^2/s3^3: both comments are wrong by one power of seconds. The others check out: _innov in m/s2^2; _state_var in thrust2^2; and _process_var in thrust2^2/s2^2, consistent with line 48’s _process_var * dt * dt. The code is right; only the documentation of units is not.

Back to EL01.22
EL01.23Show solution

Gate: energy with every factor shown

3×3.7=11.13\times3.7=11.1 V. 2200 mA h×1 A1000 mA=2.22200\ \text{mA\,h}\times\dfrac{1\ \text{A}}{1000\ \text{mA}}=2.2 A h; E=2.2×11.1=24.42 W hE=2.2\times11.1=\boxed{24.42\ \text{W\,h}}; ×3600 J1 W h=87,912 J\times\dfrac{3600\ \text{J}}{1\ \text{W\,h}}=\boxed{87{,}912\ \text{J}}. J/s=kg m2s−2⋅s−1=kg m2s−3=W\text{J/s}=\text{kg\,m}^2\text{s}^{-2}\cdot\text{s}^{-1}=\text{kg\,m}^2\text{s}^{-3}=\text{W}.

Back to EL01.23
EL01.24Show solution

Gate: free-body diagram, Newton, accelerometer

Four 4.5 N thrusts up; weight 1.6×9.80665=15.691.6\times9.80665=15.69 N down. Net: 18−15.69=2.31 N18-15.69=\boxed{2.31\ \text{N}} up; a=2.31/1.6=1.44 m/s2a=2.31/1.6=\boxed{1.44\ \text{m/s}^2} up; reading a+g=11.25 m/s2a+g=\boxed{11.25\ \text{m/s}^2}, which is exactly 18 N/1.6 kg18\ \text{N}/1.6\ \text{kg}: thrust per unit mass. Hover needs 15.69/4=3.92 N=400 gf15.69/4=\boxed{3.92\ \text{N}}=\boxed{400\ \text{gf}} per motor.

Teach-back rubric (gate part 4). A complete explanation (i) draws the thrusts up at the motors and the weight down at the centre of mass, with a sign convention; (ii) says that T/W≥2T/W\ge2 means a hover uses at most half of the available thrust, and names at least two uses of the rest: differential thrust for control (and what saturation does), tilting to move (mg/cos⁡θmg/\cos\theta), climbing and gusts (amax⁡=g(T/W−1)a_{\max}=g(T/W-1)), and battery sag; (iii) says a hovering drone’s accelerometer reads +g+g, its thrust per unit mass, because an accelerometer measures every force except gravity, and that 0 would mean free fall; (iv) uses “specific force” and “hover thrust fraction” correctly.

Back to EL01.24
EL01.25Show solution

Gate: the whole chain

T/W=4×11001300=3.38T/W=\dfrac{4\times1100}{1300}=\boxed{3.38}; ϕ=0.295\phi=\boxed{0.295}; amax⁡=9.807×2.385=23.4 m/s2a_{\max}=9.807\times2.385=\boxed{23.4\ \text{m/s}^2}. Each motor hovers at 1300/4=3251300/4=325 gf and draws 325/7.5=43.3325/7.5=43.3 W; four draw 173.3 W; with 5 W of electronics, P=178.3 WP=\boxed{178.3\ \text{W}}. Energy: 4×3.7 V×4 A h=59.24\times3.7\ \text{V}\times4\ \text{A\,h}=59.2 W h. Flight time: 0.8×59.2178.3\dfrac{0.8\times59.2}{178.3} h =0.2656=0.2656 h =15.9 min=\boxed{15.9\ \text{min}}.

Back to EL01.25
EL01.26Show solution

Gate: find the bugs

(a) U4, the missing motor count. Smallest exposing input: any drone with more than one motor, such as four 1500 gf motors on 650 g: it returns 2.31 instead of 9.23. Fix: motor_count * max_thrust_per_motor / (mass * g). (b) Cell voltage used as the pack voltage. Any pack with more than one cell exposes it: 6S 1300 mAh gives 4.81 W h instead of 28.86. Fix: (capacity * cells * cell_voltage).to("W*h"). The units cannot catch this one, because a cell’s voltage and a pack’s voltage have the same unit: only a test with known numbers can. (c) U12, gravity and bias never removed. Any record exposes it: a phone lying still for 1 s “reaches” 9.8 m/s and 4.9 m. Fix: accel = remove_bias(t, accel, still_until) before the first integration.

Back to EL01.26

Warm-up answers#

1. 5x3=5x−3\dfrac5{x^3}=\boxed{5x^{-3}} and 1x=x−1/2\dfrac1{\sqrt x}=\boxed{x^{-1/2}}. 2. h′(t)=9.8t+2h'(t)=\boxed{9.8t+2} and h′′(t)=9.8h''(t)=\boxed{9.8}. 3. 1−15=5−15=451-\tfrac15=\tfrac{5-1}{5}=\boxed{\tfrac45} and 1/14=41\big/\tfrac14=\boxed{4}.

Retention and readiness#

In two days, redo EL01.05 and EL01.10(b) without looking. In seven days, write and solve a fresh thrust-to-weight and flight-time problem in the style of EL01.25, with numbers of your own, and redo EL01.17(e) from memory. Log every miss under its friction family. Move on to EL02 only after the mastery gate in section 4.3 passes.