Challenge 1
Prove the ratio is what matters. Draw a pattern, then halve BOTH speeds and draw it again on another part of the egg. Same shape, slower. Write one sentence explaining why the speeds did not matter but their ratio did.
EV3 Robotics›Level 3 · Advanced›Lesson 24
Level 3 · Lesson 24 · EV3-L03-2460 minutes · Ages 9–16 · Model: Easter egg machine
The Easter egg machine: an egg held between two rollers that spin it, and a pen on a carriage that travels along it — one Large Motor turning the egg, one Medium Motor moving the pen.
Every model so far has moved one motor, then another. This one moves both at once, and what appears on the egg is decided not by either speed but by the relationship between them.
By the end of the lesson changing one number will change the whole family of patterns the machine draws — and every idea from the last twenty-three lessons will be in the program.
A harmonograph is a Victorian drawing machine: a pen and a table, each hung from pendulums, swinging at the same time. Nobody guides the pen. The figure appears because two simple motions are happening together, and their ratio decides which figure you get.

Set the pendulums swinging at the same rate and you get an ellipse. Two-to-one gives a figure of eight. Three-to-two gives a rosette. Nothing about themachine changes — only the relationship between two rates.
Because that is how every machine that makes a shape works. A lathe turns the work while the tool advances; the ratio between spin and feed decides the thread pitch. A pen plotter moves in two axes at once. A CNC mill cuts a curve by coordinating three.
None of them draws a curve by making a series of straight moves. They move all the axes together, continuously, and the curve is what falls out of the combination.
A machine that moved one axis, stopped, then moved the other would produce a staircase instead of a diagonal. You can make the steps small enough to look smooth, but you are approximating a shape the coordinated machine simply draws.
Two motors moving together are not two movements. They are one movement in two dimensions.
Start both motors and leave them running. The pen traces a path on the egg’s surface, and the shape of that path depends only on how fast one turns compared with the other.
| Egg : pen | What appears | Why |
|---|---|---|
| Pen stopped | A ring round the egg. | Only one axis is moving. |
| Egg stopped | A line along the egg. | Only the other axis is moving. |
| 1 : 1 | A single spiral, edge to edge. | Equal rates, so the pen advances the same amount per turn of the egg. |
| 4 : 1 | A tight spiral — four rings per pen-width. | The egg turns four times for each step along. |
| 1 : 4 | A steep, stretched spiral. | The pen crosses the egg in a quarter of one rotation. |
define draw (ratio) (turns) [A v] start motor at (50) % speed :: motors [B v] start motor at ((50) / (ratio)) % speed :: motors wait until <([abs v] of ([A v] degrees counted)) > ((turns) * (360))> :: control [A v] stop motor :: motors [B v] stop motor :: motors
start motor, not run for — and this is the crux. A run for block waits until it has finished, so two of them run one after the other and you get a staircase. start motor sets a motor going and moves on immediately, which is the only way to have both turning at once. That is Level 2 Lesson 7, and this is what it was for.
Halve both speeds and the machine draws the same shape, slowly. Double both and it draws the same shape, quickly. The pattern does not care about either speed — only their ratio — which is why a machine like this is described by one number rather than two.
Two things ruin a drawing, and both are old friends. A motor that jumps straight to speed makes the pen jerk at the start of every pattern — that is Lesson 18’s ramp. And the height at which the pen just touches the egg differs with every egg and every pen — that is Lesson 12’s calibration.
A program does not have to be one long column of blocks. Several stacks can run at the same time, each doing its own job — one driving, one watching a sensor, one keeping the display up to date.
Give each stack its own hat block. Every stack beginning with when program starts starts at the same instant — not one after another — and from then on they run alongside each other.
Nor is it limited to two. Below, three stacks run together: a Medium Motor turning an attachment, the status light flashing, and the drive base rolling. Watch the arrows at the top — they all begin at once, and no stack waits for any other.
when program starts :: events hat [A v] start motor [clockwise v] :: motors when program starts :: events hat forever set status light to [green v] :: display wait (0.5) seconds set status light to [red v] :: display wait (0.5) seconds end when program starts :: events hat start moving [straight: 0] :: movement
Written down they have to go one under another, because a page is a column — but that is an accident of paper. On the Brick they sit side by side, and nothing in the first stack happens before anything in the third.
Parallel stacks go wrong when two of them try to control the same thing. Use the switch below to take the wheels away from the third stack and point it at motor A, which the first stack is already driving.
the program starts — all of these begin here
stack 1 · Medium Motor
stack 2 · status light
stack 3 · drive base
Every stack is highlighted at the same moment on purpose — that is what running in parallel looks like. The switch above changes only what the third stack controls.
With one owner each, all three stacks are highlighted at the same instant and all three jobs get done. With two owners, motor A is handed contradictory orders hundreds of times a second and shivers instead of turning — and notice the second cost, which is easy to miss: the wheels now have nobody driving them. A stack that goes to fight over someone else’s motor has abandoned its own job. Nothing reports an error either way; as far as the Brick is concerned every stack is working perfectly. The same happens to a display line or a variable that two stacks both write to.
The discipline is simple: give each stack sole ownership of what it controls. One stack owns the motors, another owns the screen, another watches the sensors and tells the others what it found — which is what broadcasting is for.
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.
A My Block is a block you make yourself. You take a group of blocks that do one job, give the group a name, and from then on that name is the block.
| Block | What it does |
|---|---|
define grab | The head of a definition. Whatever is attached under it is what the block does. There is exactly one of these per My Block. |
grab :: myblocks | Runs the definition. Use it as many times as you like. |
define turn (degrees) | A definition with an input. degrees is a name for whatever number the caller types. |
turn (90) :: myblocks | Runs it with 90 in place of that name. |
Copying blocks works perfectly — right up until the copied thing has to change. Here are two robots driving a square, one with a My Block and one with the blocks copied four times, and then somebody asks for a bigger square.
a My Block, used four times
the same thing copied
↑ the fourth copy still says 2
The bug is not in any block. It is in the fact that the same idea was written down four times, and only three of them were updated.
The right-hand robot is not running a broken program. It is running four programs that were supposed to be identical, and three of them were updated. Nothing is misspelled, nothing reports an error, and the only evidence is a square that does not close.
The left-hand program is these two listings, and nothing else:
define side move [forward v] for (3) [rotations v] :: movement move [right: 100 v] for (0.5) [rotations v] :: movement
when program starts :: events hat repeat (4) side :: myblocks end
A My Block can take a value. Turn-right is useful; turn (degrees) is far more useful, because one definition then serves every turn in the program. The value is given a name when the block is made, and that name is used inside the definition wherever the number belongs.
The name is the part that trips people up. Inside the definition, (cm) is not a variable you have to set anywhere — it is a hole, and it is filled in with whatever the caller typed, every time the block is called.
EV3 movement blocks count wheel turns, and nobody measures a table in wheel turns. One turn of a standard driving wheel carries the robot about 17.5 cm, so driving 50 cm means asking for 50 ÷ 17.5 rotations. Doing that sum in your head, at every movement block, all program long, is how mistakes get in.
Put the sum inside a My Block once and the whole program can be written in centimetres. Change the two numbers below and watch where they end up.
These two numbers are the whole interface. Everything below is what the definition does with them.
what you write in the program
drive (50) cm at (75) % :: myblocks
the definition — (cm) and (speed) are names, not values
define drive (cm) cm at (speed) % set movement speed to (speed) % :: movement move [forward v] for ((cm) / (17.5)) [rotations v] :: movement
the same blocks, with your two numbers put in
set movement speed to (75) % :: movement move [forward v] for ((50) / (17.5)) [rotations v] :: movement
The green division block is doing the only real work: the movement block underneath counts wheel turns, and you asked in centimetres. One turn of these wheels carries the robot 17.5 cm, so 50 cm is 50 ÷ 17.5 = 2.86 turns.
Set the distance to 17.5, 35 or 70 and the part turn disappears — those are whole numbers of wheel turns. Every other distance leaves a fraction, which is exactly why the division belongs inside the My Block and not in your head.
Two inputs, and the definition is three blocks. Note what has actually been gained: not fewer blocks — the same blocks run either way — but a program you can check against a ruler. drive (50) cm at (75) % is either right or wrong about the table, and you can tell which without running it.
Typing a fixed number inside the definition instead of using the input is the classic slip — the block then ignores whatever it is given and always drives the same distance:
define drive (cm) cm at (speed) % set movement speed to (speed) % :: movement move [forward v] for ((50) / (17.5)) [rotations v] :: movement
That block takes a distance and throws it away. It will pass every test you run at 50 cm.
Here are those two My Blocks and a 100 × 100 cm mat. Drag blocks into the script, set the numbers, and press Run — the base always starts in the left-hand corner facing up the mat. Say where you think it will finish before you press it; the ruler is there to settle the argument.
Two My Blocks, a 100 × 100 cm mat and a ruler. Drag blocks into the script — or press one to add it — set the numbers, then Run and see whether the base ends up where you said it would.
my blocks — drag or press
the script — 0 / 8 blocks
Empty. Drag a block in from above, or press one.
Nothing to run yet. A square is four drives and four turns — try predicting the distance before you press Run.
Eight blocks is enough for a square with 40 cm sides, and not enough for anything you cannot picture. If a shape needs more than that, it wants a repeat (4) around it — which is the next thing a My Block usually grows into.
The Gyro Sensor gives an accurate turn, and it costs five blocks every time: reset the angle, start turning, wait until the reading is a few degrees short of the target, stop. Those five blocks are about the sensor. What the program wants to say is turn 90.
define turn (degrees) [2 v] reset angle :: sensors start moving [right: 30 v] :: movement wait until <([2 v] angle :: sensors) > ((degrees) - (4))> stop moving :: movement
The − 4 is the overshoot allowance from the Gyro Sensor module: the robot carries on turning for a moment after the motors are told to stop, so the block asks for less than it wants and lets it coast in. Written here, it is decided once. Written out at every corner, it is eleven chances to forget it.
Now the input earns its keep — because the shape a robot draws is decided entirely by that one number. A closed shape turns through 360° in total, so a shape with n corners turns 360 ÷ n at each one.
the whole program
when program starts :: events hat repeat (4) move [forward v] for (2) [rotations v] :: movement turn (90) :: myblocks end
what turn is, once
define turn (degrees) [2 v] reset angle :: sensors start moving [right: 30] :: movement wait until <([2 v] angle :: sensors) > ((degrees) - (4))> stop moving :: movement
Nothing in the program mentions the gyro. It is all inside turn, which is the point: the sensor is an implementation detail of turning, and the program should be about the shape.
Same definition, same drive block, same length of side. The only edit between a triangle and a pentagon is turn (120) :: myblocks becoming turn (72) :: myblocks. That is what a well-chosen input does: it turns a program that draws one shape into a program that draws shapes.
The same block also turns left, if you let it. Give the definition an if … then … else and a negative input means anticlockwise:
define turn (degrees) [2 v] reset angle :: sensors if <(degrees) > (0)> then start moving [right: 30 v] :: movement wait until <([2 v] angle :: sensors) > ((degrees) - (4))> else start moving [left: 30 v] :: movement wait until <([2 v] angle :: sensors) < ((degrees) + (4))> end stop moving :: movement
One block, two directions, and every program that uses it gets the left turn for free the moment this definition is edited.
The signal is repetition: the same sequence appearing in more than one place. That is the moment to name it. Making a My Block for something used once adds a layer without adding clarity.
The second signal is a unit. If the blocks convert between what you think in (centimetres, degrees, seconds) and what the hardware counts (rotations, sensor readings), that conversion is worth a name — it is the thing you will otherwise get wrong.
Start both, wait for neither, and let the ratio draw the shape.
A program does not have to be one long column of blocks. Several stacks can run at the same time, each doing its own job — one driving, one watching a sensor, one keeping the display up to date.
Give each stack its own hat block. Every stack beginning with when program starts starts at the same instant — not one after another — and from then on they run alongside each other.
Nor is it limited to two. Below, three stacks run together: a Medium Motor turning an attachment, the status light flashing, and the drive base rolling. Watch the arrows at the top — they all begin at once, and no stack waits for any other.
when program starts :: events hat [A v] start motor [clockwise v] :: motors when program starts :: events hat forever set status light to [green v] :: display wait (0.5) seconds set status light to [red v] :: display wait (0.5) seconds end when program starts :: events hat start moving [straight: 0] :: movement
Written down they have to go one under another, because a page is a column — but that is an accident of paper. On the Brick they sit side by side, and nothing in the first stack happens before anything in the third.
Parallel stacks go wrong when two of them try to control the same thing. Use the switch below to take the wheels away from the third stack and point it at motor A, which the first stack is already driving.
the program starts — all of these begin here
stack 1 · Medium Motor
stack 2 · status light
stack 3 · drive base
Every stack is highlighted at the same moment on purpose — that is what running in parallel looks like. The switch above changes only what the third stack controls.
With one owner each, all three stacks are highlighted at the same instant and all three jobs get done. With two owners, motor A is handed contradictory orders hundreds of times a second and shivers instead of turning — and notice the second cost, which is easy to miss: the wheels now have nobody driving them. A stack that goes to fight over someone else’s motor has abandoned its own job. Nothing reports an error either way; as far as the Brick is concerned every stack is working perfectly. The same happens to a display line or a variable that two stacks both write to.
The discipline is simple: give each stack sole ownership of what it controls. One stack owns the motors, another owns the screen, another watches the sensors and tells the others what it found — which is what broadcasting is for.
Say this back before moving on: “Both at once, and the ratio is the pattern.”
Turn the egg by hand and watch it. Does it spin true, or does it wobble? A wobbling egg draws a wobbling pattern and no program can correct it.
| Part | What it is doing here |
|---|---|
| EV3 Intelligent Brick | The frame, and the menu — the catalogue calls for a screen that selects which pattern to draw, which is Lesson 20 returning. |
| Large Motor — the egg | Spins the egg through rollers. Large because it turns against the drag of the pen, and any variation in that drag shows up in the drawing. |
| Medium Motor — the pen carriage | Moves the pen along the egg. Medium because it wants smooth, fine control rather than force. |
| Touch Sensor — start / pen down | Starts a pattern. It also stops the machine drawing while somebody is changing the egg. |
| The rollers and pen arm (not electronic) | Where the quality of the drawing lives. The egg must spin without slipping, and the pen must rest with a steady, light pressure — too hard and it drags the egg, too light and the line breaks. |
Set the pen pressure before you program anything. Roll the egg by hand with the pen down and look at the line. If it is patchy the pen is too light; if the egg stalls or the line is a groove it is too heavy. This takes two minutes and saves the lesson.
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 |
|---|---|---|
| Egg rotation (Large) | A | The reference axis — its encoder is what the program counts turns against. |
| Pen carriage (Medium) | B | The second axis. |
| Start (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 — nothing moves but the egg. You will download many small ratio changes in a row, and a cable is the fastest way to do that.
Stuck? The long version, with a photograph of every screen, is in the Brick & Bluetooth guide.
Draw one spiral first, so you can see the ratio doing its work. Then add the menu.
define draw (ratio) (turns) [A v] reset degrees counted :: motors [A v] start motor at (50) % speed :: motors [B v] start motor at ((50) / (ratio)) % speed :: motors wait until <([abs v] of ([A v] degrees counted)) > ((turns) * (360))> :: control [A v] stop motor :: motors [B v] stop motor :: motors when program starts :: events hat wait until <[1 v] is pressed? :: sensors> wait until <not <[1 v] is pressed? :: sensors>> :: control draw (4) (8) :: custom play sound [Communication / Goodbye v] until done :: sound
Run it, then change the 4 to a 1 and run it again on the other side of the egg. Two completely different patterns, one number apart. That is the lesson, drawn rather than explained.
when program starts :: events hat
delete all of [names v] :: list
delete all of [ratios v] :: list
add [TIGHT SPIRAL] to [names v] :: list
add (6) to [ratios v] :: list
add [OPEN SPIRAL] to [names v] :: list
add (2) to [ratios v] :: list
add [DIAGONAL] to [names v] :: list
add (1) to [ratios v] :: list
add [RINGS] to [names v] :: list
add (20) to [ratios v] :: list
set [selection v] to (1) :: variables
forever
clear display :: display
write [PATTERN:] at line (1) :: display
write (item (selection) of [names v]) at line (3) :: display
write [UP/DOWN, THEN PRESS] at line (6) :: display
if <brick [up v] button pressed? :: sensors> then
change [selection v] by (-1) :: variables
wait (0.25) seconds :: control
end
if <brick [down v] button pressed? :: sensors> then
change [selection v] by (1) :: variables
wait (0.25) seconds :: control
end
if <(selection) < (1)> then
set [selection v] to (length of [names v]) :: variables
end
if <(selection) > (length of [names v])> then
set [selection v] to (1) :: variables
end
end
when program starts :: events hat
forever
wait until <[1 v] is pressed? :: sensors>
wait until <not <[1 v] is pressed? :: sensors>> :: control
set status light to [orange v] :: display
draw (item (selection) of [ratios v]) (8) :: custom
set status light to [green v] :: display
enddraw block, one number each. The menu from Lesson 20, the list from Lesson 8, the My Block with inputs from Lesson 2.draw block does all four patterns. They are not four routines; they are one routine and four numbers.What success looks like: a menu of four patterns, and an egg that carries four visibly different spirals — from a program with one drawing routine in it.
If the pattern is a staircase rather than a spiral, you have used run for somewhere instead of start motor — the two motors are taking turns rather than moving together.
One change at a time. Predict, then run, then look. Draw each on a fresh part of the egg so you can compare them side by side.
run for. The two motors now take turns and you get a staircase. This is the mistake this lesson exists to prevent, made deliberately.draw. The pen stops skipping at the beginning of each pattern — a Level 3 idea improving a Level 3 idea.The speeds decide how long it takes. The ratio decides what it is.
Look at what is in the last program you wrote. A My Block with two inputs. Two lists read at one index. A menu on the Brick buttons. An encoder deciding when to stop. Two motors running together.
Every one of those was a whole lesson, and now they are just how you write a program. That is what these twenty-four lessons were for.
| You can… | From |
|---|---|
| Name a routine, give it inputs, build routines from routines | Lessons 1, 2, 16 |
| Name a moment, and decide whether to wait for it | Lessons 3, 4 |
| Correct continuously — and know when you cannot | Lessons 5, 10, 22 |
| Combine conditions, remember a mode, cycle through modes | Lessons 6, 7, 21 |
| Record a run of values, read it back, store a routine as data | Lessons 8, 9, 15 |
| Trust a reading — calibrated, cross-checked, smoothed | Lessons 12, 13, 14 |
| Turn a measurement into a command, and limit how fast it changes | Lessons 17, 18 |
| Refuse to start, offer a choice, share a value safely | Lessons 19, 20, 11 |
| Keep time without going blind, and move two axes as one | Lessons 23, 24 |
The rest of Level 3 puts these together on harder models. Level 4 takes them to a competition mat, where the closed loop becomes a line follower, calibration is the first thing you do at every table, and My Blocks are the only reason a competition program stays readable.
Level 2 made machines that do the same thing every time. Level 3 makes machines that do the right thing when every time is different.
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.
Prove the ratio is what matters. Draw a pattern, then halve BOTH speeds and draw it again on another part of the egg. Same shape, slower. Write one sentence explaining why the speeds did not matter but their ratio did.
Build a ratio chart on one egg. Draw ratios 1, 2, 3, 6 and 20, labelling each one on the shell. Keep that egg. It is a better record of what a ratio means than anything you could write down.
Make the staircase on purpose. Replace one motor's "start motor" with "run for". The two motors now take turns and you get steps instead of a curve. Draw it next to a proper spiral, then explain the difference between the two blocks in one sentence.
Decorate an egg with a pattern nobody has drawn before. Design a pattern of your own, work out what it needs, and build the machine to draw it. It must combine at least three distinct sections — different ratios, or a reversed direction, or a band left blank. Requirements: 1. ONE drawing routine. Every section is that routine called with different numbers. If you write a second drawing routine, look again at what should have been an input. 2. A menu on the Brick, so somebody else can choose your pattern without a computer. 3. The pen ramps up and down so it never skips at the start of a section. 4. The pen height is calibrated for the egg actually in the machine, not typed for the one you tested with. Then hand the machine and a fresh egg to another group with no instructions and see whether they can produce the same pattern you did. If they can, you have built a machine rather than a demonstration — and that, after twenty-four lessons, is the difference worth caring about.

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.