Challenge 1
Fail each check on purpose. Hold the switch as you start. Turn the rotor off centre. Hold the tail rotor. Each should produce its own named message and refuse. If any of them starts anyway, that check is not doing anything.
EV3 Robotics›Level 3 · Advanced›Lesson 19
Level 3 · Lesson 19 · EV3-L03-1960 minutes · Ages 9–16 · Model: Helicopter 3
Helicopter 3: a main rotor on a Large Motor, a tail rotor on a Medium Motor, and a start switch — a machine with two things that must spin together and one button that starts them.
A helicopter with a stopped tail rotor spins on the spot. One whose main rotor is fouled tears at its own gearing. Both are conditions you can check before anything turns.
By the end of the lesson your helicopter will run a startup check and refuse to fly if it fails — and it will say, on the screen, exactly which check it failed.
No aircraft takes off without a checklist. It exists because of a specific 1935 accident: a Boeing prototype crashed on take-off with the gust locks still engaged, killing the test pilot — a highly experienced one. The aeroplane was not too complicated to fly. It was too complicated to remember.

The response was not more training. It was a written list of checks, done in order, every single time. That idea has since spread to surgery, where a two-minute checklist measurably reduces deaths, and to every industry where forgetting one thing is expensive.
Because the cost of checking is tiny and the cost of not checking is not. Ten seconds before start against a wrecked gearbox is an easy trade — and unlike skill or attention, a checklist works on a bad day.
And a check that fails must say what failed. A cockpit warning names the system. A modern car says “boot open”, not “error”. Refusing without a reason turns a helpful machine into an infuriating one.
The machine does exactly what it was told, in conditions where that was the wrong thing, and everyone blames the operator. Most “operator error” is a machine that let somebody make a mistake it could have caught.
Check before you act. If you refuse, say why — in words, where the person can see them.
A guard clause is a check at the top of a routine that stops it before it starts. Not a check woven through the middle — a gate at the front, passed or failed, before anything moves.
define preflight set [ok v] to (1) :: variables if <not <([A v] degrees counted) = (0)>> then write [ROTOR NOT CENTRED] at line (2) :: display set [ok v] to (0) :: variables end if <[1 v] is pressed? :: sensors> then write [RELEASE THE SWITCH] at line (3) :: display set [ok v] to (0) :: variables end
ok survives.when program starts :: events hat clear display :: display preflight :: custom if <(ok) = (0)> then set status light to [red v] :: display play sound [Mechanical / Error v] :: sound stop [this stack v] :: control end set status light to [green v] :: display broadcast [fly v] :: events
Run every check, then decide. Do not stop at the first failure. A user who fixes one problem, restarts, and is told about a second is being made to discover their situation one item at a time. Show all of it at once.
| Good check | Poor check | Why |
|---|---|---|
| Is the switch released? | Is the user ready? | The first is measurable; the second is not. |
| Is the rotor at its start position? | Is the model built correctly? | Check one specific thing you can actually detect. |
| Does the tail rotor turn freely? | Is everything fine? | A check with no failure mode is decoration. |
Some conditions cannot be read from a sensor and must be tested. Turn the tail rotor briefly and see whether its encoder moved: if it did not, the rotor is jammed. That is a check that does something small in order to learn something important.
[B v] reset degrees counted :: motors [B v] start motor at (25) % speed :: motors wait (0.4) seconds :: control [B v] stop motor :: motors if <([abs v] of ([B v] degrees counted)) < (30)> then write [TAIL ROTOR JAMMED] at line (4) :: display set [ok v] to (0) :: variables end
Up to now a robot has been able to wait for a sensor. Deciding is different: the robot checks the sensor and does one thing or another depending on the answer — and then carries on either way.
| Block | What it does |
|---|---|
if <> then end | Runs the blocks inside only when the condition is true. Otherwise skips them. |
if <> then else end | Runs one set of blocks when true and a different set when false. |
A decision made once, at the start, is almost never what you want. Here are two robots with the identical if-else, testing the identical sensor against the identical number — one inside a loop and one not.
decision inside a loop
the same decision, once
A decision is only worth as much as the last time it was made. Inside a loop, that is a few milliseconds ago.
Nothing is wrong with the right-hand program’s decision. It asked the question, got a truthful answer, and acted on it correctly. It simply never asked again, and the world moved on. Decisions belong inside a loop, so the robot keeps re-deciding as things change.
when program starts :: events hat
forever
if <([4 v] distance in [cm v] :: sensors) < (15)> then
stop moving :: movement
else
start moving [straight: 0] :: movement
end
endOnce there is more than one question, decisions can be arranged in four ways. They look nearly identical stacked up in the editor, which is exactly why they get muddled — the thing that differs is not what the blocks say, it is which routes through them exist.
One question sorts them almost completely:
| Are the questions… | Use | How many bodies can run |
|---|---|---|
| independent — any combination can be true | separate ifs | none, some, or all |
| one question, two answers | if / else | exactly one |
| the second only matters when the first is true | nested if | one, and only via the outer |
| mutually exclusive cases — exactly one should win | chained if / else | exactly one, the first that matches |
Each if is asked no matter what the others answered, so any number of them can fire on the same pass. That is the right shape when the conditions genuinely have nothing to do with each other.
forever
if <[3 v] is ambient light intensity [< v] (20) %? :: sensors> then
[A v] start motor [clockwise v] :: motors
end
if <[4 v] is distance [< v] (15) [cm v]? :: sensors> then
[D v] start motor [clockwise v] :: motors
end
endExactly one branch runs, every time. Reach for this whenever the robot must do something either way — and in preference to two ifs testing opposite conditions, which is the same idea written twice and can drift apart.
Putting one if inside another means the inner question is only ever asked when the outer one is true. Use it when the second question is meaningless otherwise: there is no point asking which side an obstacle is on when there is no obstacle.
if <[4 v] is distance [< v] (15) [cm v]? :: sensors> then
if <([2 v] angle :: sensors) < (0)> then
start moving [right: 50] :: movement
end
endWhen not to nest. If you only want “both true” and nothing happens at the outer level, an and says it in one block and reads better:
if <<[4 v] is distance [< v] (15) [cm v]? :: sensors> and <([2 v] angle :: sensors) < (0)>> then start moving [right: 50] :: movement end
Nesting earns its place when something happens at the outer level too, or when there is an else at each level and the two mean different things.
This is the shape for a list of cases where exactly one should win: colour bands, distance bands, speed ranges. EV3 Classroom has no else-if block, so you build a chain by putting the next if inside the else of the last one.
And here is why it matters, because this is the single commonest bug in this whole module. Three bands written as three separate ifs are each perfectly correct, and together they are wrong: a reading of 20 is under 30 and under 60 and under 90, so all three run and the last one to run is the one that sticks.
three separate ifs
chained — if / else / if
↑ the rest is inside the else — never asked
Separate ifs are not wrong here so much as unguarded: nothing stops a second one matching. Chaining is what makes “the first one wins” true.
The rule to carry away: if the cases are meant to be exclusive, they must be made exclusive. Chaining does it by construction. Separate ifs only work if you are careful to write non-overlapping bands yourself — light < 30, 30 to 60, 60 and over — which is more to get right and easy to break later.
This is the point at which a machine stops following a script and starts responding. A thermostat, an automatic door, a robot vacuum — all of them are a decision inside a loop.
Every EV3 motor contains a sensor that counts how far it has actually turned. That means a motor is not only an output — you can ask it where it is, and the answer describes what really happened rather than what you asked for.
| Block | What it does |
|---|---|
([A v] degrees counted :: sensors) | Reports how far this motor has actually turned since the count was last reset, in degrees. |
[A v] reset degrees counted :: motors | Sets the count back to zero, making right here the new reference point. |
([A v] speed :: sensors) | Reports how fast the motor is turning right now, as a percentage. A motor that is being driven but reads zero is a motor that is stuck. |
Those two are not always the same. Here are two identical motors, running the same program, with only their mechanisms different.
the same program, on two identical motors
Nothing on the Brick announces a stall. The only evidence is that the counter stopped changing while the motor was still being told to turn.
Nothing on the Brick announces the jam. The motor is still being driven, the program is still sitting on the same block, and the only trace of the problem anywhere is a counter that has stopped climbing. Comparing what you asked for with what was counted is how a robot notices — which is the whole of stall detection.
When the Brick powers on, the count is simply whatever it happens to be. It is not a position on the machine — it becomes one only when you tie it to something physical.
That is what reset degrees counted is for, and it is not just a tidy-up block for the top of a program. Where you put it decides what zero means, so putting it part-way through — after the mechanism has been driven somewhere known — is the normal way to use it, not an abuse of it.
A conveyor has no idea where it is. Give it a touch sensor at one end and it can find out: drive it until the sensor is pressed, and it is now at a place you can name. Only then reset the count, and that end becomes 0.
when program starts :: events hat [A v] start motor [counterclockwise v] :: motors [1 v] wait until [pressed v] :: sensors [A v] stop motor :: motors [A v] reset degrees counted :: motors
The order is the whole point. Reset before the sensor is pressed and you have zeroed a random spot; reset after it, and every later reading means “how far from home”. This is called homing, and it is why a printer rattles its head to one side when you switch it on.
Drive towards home gently. The mechanism is deliberately being run into its own end stop, so a slow speed saves the gears — and the touch sensor is what stops it, which means it stops in the same place every time regardless of where it started.
Home is often a corner, and a corner is an awkward place to measure from. Say the conveyor carries a chute that dispenses bricks, and you would rather describe its position as left and right of the middle. Then home once, drive the known distance to the middle, and reset again there:
when program starts :: events hat [A v] start motor [counterclockwise v] :: motors [1 v] wait until [pressed v] :: sensors [A v] stop motor :: motors [A v] reset degrees counted :: motors [A v] run [clockwise v] for (900) [degrees v] :: motors [A v] reset degrees counted :: motors
Now the middle is 0. Moving right counts up, moving left counts down past zero into negative numbers — the count is perfectly happy to go negative — and “go back to the middle” becomes the simplest instruction in the program: drive until the count reaches 0.
Both resets earn their place. The first one turns a meaningless number into a distance from a real, repeatable place. The second one moves zero to where the maths is easiest. A reset in the middle of a program is only a mistake when the mechanism is somewhere you cannot name.
Because the count is in degrees, and a wheel of known size travels a known distance per turn, the reading can be converted into how far the robot has actually driven. That is how a robot reports a distance in centimetres rather than in rotations — and it is why changing the wheels changes the answer.
This is how a printer knows the paper jammed, how a car window stops when it meets your hand, and how a robot arm knows it has reached its limit. A machine that can only give orders is fragile; one that can check what happened can recover.
The Brick’s screen is a small black-and-white whiteboard. You can wipe it, draw one of the built-in pictures on it, or write your own words at a spot you choose. It is how the robot tells a person what it is doing.
| Block | What it does |
|---|---|
clear display :: display | Wipes the screen blank, ready for something new. |
display [Eyes / Neutral v] :: display | Draws a built-in picture chosen from a list — eyes, faces, arrows, symbols. |
display [Eyes / Neutral v] for (2) seconds :: display | Draws the picture, holds the program for that long, and then carries on. Handy when a face should be seen before anything else happens. |
write [Hello] at line (1) :: display | Writes your own text on one of the screen’s eight lines. |
write [Hello] at x: (10) y: (40) with font [normal black v] :: display | Writes text at an exact spot instead of a line, and lets you choose the size. Use it when a message has to line up with something else on the screen. |
The screen has no memory of whose writing is whose. It keeps every mark until something wipes it — including the marks left by the last run. Watch the same program with and without its clear display block.
with clear display
without it
Let it loop two or three times. The left screen still reads cleanly; the right one is the same program with one block missing.
Both Bricks are told exactly the same thing. The left one wipes the screen first, so line 7 holds one message and reads correctly. The right one never wipes, so each new message is drawn over the last and the words turn into a smudge. This is why a program that seems to display nonsense is usually displaying the truth — several times over.
There are two blocks for writing text, and they describe where in two completely different ways. Watch one message move around the screen under both of them.
the screen is 178 pixels across and 128 down
The arrows are the two numbers. The one along the top is x; the one down the side is y, and it counts downwards from the top edge.
write [EV3] at line (1) is the simple one. You give it a line number and it puts the text there, starting hard against the left edge — you do not choose how far across, only how far down.
For most robots this is all you need: a status word on line 1, a reading on line 3, a warning on line 5. Pick your lines at the start and keep each one for one job, the way you would keep one colour of status light for one meaning.
write [EV3] at x: () y: () with font [] treats the screen as a grid of pixels — 178 across and 128 down — and lets you put the text anywhere on it.
The block ends with a font dropdown, and normal black is the usual choice. A larger font makes the writing easier to read from across the room but takes more room across the screen, so fewer characters fit before the text runs off the edge.
The text slot of either block will take a reporter, so a sensor value can be shown directly: drop (4 distance in cm) into the slot and the screen shows the reading. That is the EV3’s version of a print statement, and it is the fastest way to find out what a robot actually thinks it is seeing.
A bare number on its own is hard to read, though. To show DIST: 23 rather than 23, use the operator block join [DIST: ] () to glue the label to the value, and put the join into the write block’s text slot.
when program starts :: events hat clear display :: display forever write (join [DIST: ] ([4 v] distance in [cm v] :: sensors)) at line (1) :: display end
Note the forever: a value written once is a value from the start of the program. To watch a reading change, the write block has to be inside a loop.
Once one value is on the screen the rest follows, and the useful trick throughout is join: it glues two pieces of text together and hands the result to the write block. Anything that reports a value can go in either slot — a variable, a sensor, or another join.
Counting is the classic case. A variable counts how many times the Touch Sensor has been pressed, and a bare 5 on the screen tells nobody anything — PRESSED: 5 tells them everything:
when program starts :: events hat clear display :: display set [count v] to (0) :: variables forever wait until <[1 v] is pressed? :: sensors> change [count v] by (1) :: variables write (join [PRESSED: ] (count)) at line (1) :: display wait until <not <[1 v] is pressed? :: sensors>> end
The two wait until blocks are what make it count presses rather than counting as fast as the loop runs while your finger is down. That is the Touch Sensor’s own lesson, but it shows up here because a counter on the screen is where you first notice it going wrong.
Give every reading its own line and keep it. A line that changes meaning halfway through a program is unreadable at a glance, which is the only speed a screen on a moving robot gets read at.
when program starts :: events hat clear display :: display forever write (join [DIST: ] ([4 v] distance in [cm v] :: sensors)) at line (2) :: display write (join [DEG: ] ([A v] degrees counted :: sensors)) at line (5) :: display end
degrees counted is worth putting on the screen the first time you use it — it is the fastest way to find out whether a motor is turning as far as you think it is, and it is the block behind every “why did it stop early” question.
Sensors report numbers, and people read words. The Colour Sensor reports 5; the person watching wants RED. A chain of if … then … else blocks does the translation, and it is worth doing once into a variable rather than in every write block:
if <([3 v] color :: sensors) = (5)> then
set [name v] to [RED] :: variables
else
if <([3 v] color :: sensors) = (4)> then
set [name v] to [YELLOW] :: variables
else
set [name v] to [OTHER] :: variables
end
end
write (join [COLOR: ] (name)) at line (4) :: displayThe same shape turns a distance into a warning. Here the screen carries the number and what the number means, which is what lets somebody across the room tell whether the robot is about to hit something:
if <([4 v] distance in [cm v] :: sensors) < (10)> then
set [status v] to [TOO CLOSE!] :: variables
else
if <([4 v] distance in [cm v] :: sensors) < (30)> then
set [status v] to [OBSTACLE FOUND] :: variables
else
set [status v] to [NOTHING FOUND] :: variables
end
end
write (status) at line (3) :: displayDrive all four of those at once below. The screen is the real thing — five write blocks, five lines, and every one of them a join.
Drive the sensors and read the screen. Every line is one write block with a join in its text slot.
| Line | What goes in the write block | On the screen now |
|---|---|---|
| 1 | join "PRESSED: " (count) | PRESSED: 3 |
| 2 | join "DIST: " (4 distance in cm) | DIST: 24 |
| 3 | (status) — set by the if chain | OBSTACLE FOUND |
| 4 | join "COLOR: " (name) | COLOR: RED |
| 5 | join "DEG: " (A degrees counted) | DEG: 180 |
Line 4 says COLOR: RED, but the sensor only ever said 5. The if chain in between is what turns a number into a word.
The moment a reading goes inside a loop, one of two things goes wrong, and which one depends on where the clear display block went.
Almost everybody tries clear inside the loop first. It gives the right text — and a screen that blinks several times a second, because between the clear and the write there is genuinely nothing on the screen and the loop goes round faster than your eye.
So the clear gets moved to the start of the program, the blinking stops, and the words go wrong. Writing text paints only the characters it has and leaves everything past them exactly where it was. Write RED over YELLOW and the LOW is still there: the screen says REDLOW. Go the other way — yellow first, then red — and it looks fine, which is why this bug takes so long to pin down.
when program starts :: events hat clear display :: display forever write (name) at line (3) :: display end
Watch the bottom three rows rather than the Brick. They are what the screen is actually doing: old characters, new characters, and whatever survives.
The fix is to make the new text at least as long as the old one, and join already does that: pad it with spaces. RED plus three spaces is six characters — exactly enough to paint over YELLOW. Nothing is left behind, and nothing is ever blank.
write (join (name) [ ]) at line (3) :: display
Pad to the length of the longest thing that line can ever show. Six characters covers RED, BLUE and GREEN but not NOTHING FOUND — count the longest message, and add spaces to match. The same applies to numbers: DIST: 5 after DIST: 40 leaves a stray 0 on the end, so a line that shows a two-digit reading needs a trailing space.
Use clear-inside-the-loop when the whole screen changes at once and a flicker does not matter; use the padding trick whenever a value is being watched.
Once several values need showing, the line number becomes a counter and the values come out of a list: write item 1 at line 1, item 2 at line 2, and so on inside a repeat. That needs the list blocks rather than the display blocks, so it lives with them — Lists covers building one and reading it back item by item, and the loop that walks it is the same loop that fills the screen.
Machines in the real world tell you what they are doing: a microwave counts down, a lift shows its floor, a car dashboard warns you. A robot that shows WAITING and then OPEN is far easier to understand — and far easier to debug — than one that moves silently.
Run every check, name every failure, and only then decide whether to go.
Every EV3 motor contains a sensor that counts how far it has actually turned. That means a motor is not only an output — you can ask it where it is, and the answer describes what really happened rather than what you asked for.
| Block | What it does |
|---|---|
([A v] degrees counted :: sensors) | Reports how far this motor has actually turned since the count was last reset, in degrees. |
[A v] reset degrees counted :: motors | Sets the count back to zero, making right here the new reference point. |
([A v] speed :: sensors) | Reports how fast the motor is turning right now, as a percentage. A motor that is being driven but reads zero is a motor that is stuck. |
Those two are not always the same. Here are two identical motors, running the same program, with only their mechanisms different.
the same program, on two identical motors
Nothing on the Brick announces a stall. The only evidence is that the counter stopped changing while the motor was still being told to turn.
Nothing on the Brick announces the jam. The motor is still being driven, the program is still sitting on the same block, and the only trace of the problem anywhere is a counter that has stopped climbing. Comparing what you asked for with what was counted is how a robot notices — which is the whole of stall detection.
When the Brick powers on, the count is simply whatever it happens to be. It is not a position on the machine — it becomes one only when you tie it to something physical.
That is what reset degrees counted is for, and it is not just a tidy-up block for the top of a program. Where you put it decides what zero means, so putting it part-way through — after the mechanism has been driven somewhere known — is the normal way to use it, not an abuse of it.
A conveyor has no idea where it is. Give it a touch sensor at one end and it can find out: drive it until the sensor is pressed, and it is now at a place you can name. Only then reset the count, and that end becomes 0.
when program starts :: events hat [A v] start motor [counterclockwise v] :: motors [1 v] wait until [pressed v] :: sensors [A v] stop motor :: motors [A v] reset degrees counted :: motors
The order is the whole point. Reset before the sensor is pressed and you have zeroed a random spot; reset after it, and every later reading means “how far from home”. This is called homing, and it is why a printer rattles its head to one side when you switch it on.
Drive towards home gently. The mechanism is deliberately being run into its own end stop, so a slow speed saves the gears — and the touch sensor is what stops it, which means it stops in the same place every time regardless of where it started.
Home is often a corner, and a corner is an awkward place to measure from. Say the conveyor carries a chute that dispenses bricks, and you would rather describe its position as left and right of the middle. Then home once, drive the known distance to the middle, and reset again there:
when program starts :: events hat [A v] start motor [counterclockwise v] :: motors [1 v] wait until [pressed v] :: sensors [A v] stop motor :: motors [A v] reset degrees counted :: motors [A v] run [clockwise v] for (900) [degrees v] :: motors [A v] reset degrees counted :: motors
Now the middle is 0. Moving right counts up, moving left counts down past zero into negative numbers — the count is perfectly happy to go negative — and “go back to the middle” becomes the simplest instruction in the program: drive until the count reaches 0.
Both resets earn their place. The first one turns a meaningless number into a distance from a real, repeatable place. The second one moves zero to where the maths is easiest. A reset in the middle of a program is only a mistake when the mechanism is somewhere you cannot name.
Because the count is in degrees, and a wheel of known size travels a known distance per turn, the reading can be converted into how far the robot has actually driven. That is how a robot reports a distance in centimetres rather than in rotations — and it is why changing the wheels changes the answer.
This is how a printer knows the paper jammed, how a car window stops when it meets your hand, and how a robot arm knows it has reached its limit. A machine that can only give orders is fragile; one that can check what happened can recover.
Say this back before moving on: “Check everything, then say what failed.”
Turn both rotors by hand. Then think of three ways somebody could start this machine in a state where it should not run. Those are your checks.
| Part | What it is doing here |
|---|---|
| EV3 Intelligent Brick | The fuselage — and today, the warning panel. Its screen carries the check results, which is the entire user interface. |
| Large Motor — main rotor | Drives the main blades. Its encoder is also a sensor: it tells you where the rotor is and whether it moved when asked. |
| Medium Motor — tail rotor | Drives the tail. A helicopter without it yaws — which is why its freedom to turn is worth checking before the main rotor spins. |
| Touch Sensor — start switch | Starts the sequence. A switch already held down when the program starts is itself a failed check. |
| The rotor gearing (not electronic) | Both rotors must spin freely by hand. Anything stiff will be found by your active check — which is the point, but it is better to find it with fingers first. |
Sensors go in ports 1, 2, 3, 4. Motors go in ports A, B, C, D. They are not interchangeable, and nothing will tell you politely if you swap them.
| Part | Port | Why this one |
|---|---|---|
| Main rotor (Large) | A | The principal actuator. |
| Tail rotor (Medium) | B | The second motor. |
| Start switch (Touch) | 1 | Touch stays on 1 across the course. |
Check your own build now:
Two routes, and either is fine. USB is the reliable one and the one to fall back on when a room’s Bluetooth is busy; Bluetooth leaves the robot free to move, which some models need.
Do these in order. Naming the Brick after you go looking for it in the list is how groups end up driving each other’s robots.
EV3 until somebody changes it.EV3.The two failures, every class, every time. The Brick has gone to sleep while you were building — press the centre button to wake it. Or you have paired with the group at the next table, which is why the name matters.
The long version, including Port View and how to read the port tiles, is in the Brick & Bluetooth guide.
USB is fine. The helicopter stays put, and you will download repeatedly while testing checks — but keep the lead clear of both rotor discs.
Stuck? The long version, with a photograph of every screen, is in the Brick & Bluetooth guide.
The checks first, the flight second. The flight code below assumes every condition holds — because the gate above it guarantees they do.
define preflight set [ok v] to (1) :: variables clear display :: display write [PREFLIGHT] at line (1) :: display if <[1 v] is pressed? :: sensors> then write [RELEASE SWITCH] at line (2) :: display set [ok v] to (0) :: variables end if <([abs v] of ([A v] degrees counted)) > (20)> then write [ROTOR OFF CENTRE] at line (3) :: display set [ok v] to (0) :: variables end [B v] reset degrees counted :: motors [B v] start motor at (25) % speed :: motors wait (0.4) seconds :: control [B v] stop motor :: motors if <([abs v] of ([B v] degrees counted)) < (30)> then write [TAIL JAMMED] at line (4) :: display set [ok v] to (0) :: variables end when program starts :: events hat [A v] reset degrees counted :: motors preflight :: custom if <(ok) = (0)> then write [DO NOT FLY] at line (6) :: display set status light to [red v] :: display play sound [Mechanical / Error v] until done :: sound stop [this stack v] :: control end write [READY - PRESS TO FLY] at line (6) :: display set status light to [green v] :: display wait until <[1 v] is pressed? :: sensors> broadcast [fly v] :: events when I receive [fly v] :: events hat set [current v] to (0) :: variables repeat (40) change [current v] by (2) :: variables [A v] start motor at (current) % speed :: motors [B v] start motor at ((current) / (2)) % speed :: motors wait (0.05) seconds :: control end wait (3) seconds :: control repeat (40) change [current v] by (-2) :: variables [A v] start motor at (current) % speed :: motors [B v] start motor at ((current) / (2)) % speed :: motors wait (0.05) seconds :: control end [A v] stop motor :: motors [B v] stop motor :: motors
stop this stack is the refusal. Nothing after it runs, so the flight code below cannot be reached by accident.What success looks like: a clean machine passes all three checks, goes green and flies. Hold the switch down as you start and it refuses, in red, saying so.
If it always says the tail is jammed, your threshold is too high — check what a free tail actually reads in 0.4 seconds at 25% and set the number below that.
One change at a time. Predict, then run, then look. Today you are testing the checks by causing the failures they exist for.
A check must catch real failures and pass good machines. Getting either half wrong makes it worse than no check at all.
Your helicopter talks to the user — it says what is wrong and when it is ready. It does not yet listen to anything except one switch.
Every model so far does one thing when started. A machine that can do three different things needs a way for a person to choose, before the running begins.
The Brick has four buttons under your thumb and a screen above them, which is a menu waiting to happen. The next lesson uses them, and the model’s catalogue entry lists the EV3 buttons as its whole subject.
Today the machine spoke to the operator. Next the operator speaks back.

Work through the challenges in order — each is harder than the last. The mission comes after all three, and it is meant to make you plan before you build.
Fail each check on purpose. Hold the switch as you start. Turn the rotor off centre. Hold the tail rotor. Each should produce its own named message and refuse. If any of them starts anyway, that check is not doing anything.
Fail two at once. Hold the switch AND jam the tail. Both messages must appear. If you only see one, your checks stop at the first failure — fix that, and say why showing everything at once is kinder.
Add a check of your own, and one that cries wolf. First add a fourth real check to your model. Then deliberately make one check far too strict, so a perfectly good machine is refused. Hand it to a partner without warning them. Their reaction is the argument against over-strict checks — a warning nobody believes is worse than no warning.
Write a startup procedure a stranger could follow. The helicopter must run a full check sequence and, when something is wrong, tell the user what to do about it — not just what is wrong. "TAIL JAMMED" is a diagnosis; "TAIL JAMMED - TURN IT BY HAND" is help. Requirements: - at least four checks, each testing something real and specific - at least one ACTIVE check that moves something briefly to find out - every failure names the problem AND the fix - all failures shown at once, not one per restart - red light and sound as well as text, so a refusal is obvious across the room - a clear READY state, so passing is as visible as failing Then the real test: sabotage the model in some way you have not planned for, hand it to somebody who has not seen it, and see whether they can get it flying using only what the screen tells them. Where they get stuck, the message was not good enough — and that, not the code, is what you are being marked on.
Build the model before you read any further. Everything after this is about making it do something, and none of it will make much sense with nothing on the table in front of you.
Use the viewer's own controls to zoom and turn pages. Fullscreen makes it big enough to build from.
Check the finished build against the picture before you switch anything on. A motor mounted the wrong way round is far easier to spot now than it is to debug later, when it looks like a program fault.