Bozoma Innovation Hub · Arduino Ladder · Band 5
Simon Says
The hardest band so far, and the most important one. You write your own instructions for the first time. And you find out that delay, which you have used in every band since Band 0, has been quietly holding you back the whole time.
About 8 hours. Finish Bands 3 and 4 first. This band uses buttons, arrays, the serial monitor and for loops as though you already know them, because you do.
Choose your build
- CoreSimon Saysthe memory game. This page. Everybody builds this one.
- SwapReaction raceEvery Band 5 idea with one light and one button. Functions, tone, random and millis, a quarter of the wiring.
- PracticeMorse codeSend a message somebody else can actually read. The test is not that it beeps; it is that a person holding the chart can write down what you sent.
- PracticeThe sirenBand 2's fade, heard instead of seen. Then finding out why even steps in the number are not even steps in the sound.
Swap means build it instead of the one on this page: it teaches the same things and leaves you just as ready for the next band. Everything else on this page still applies, including the Check yourself questions. Practice is short, comes after the core, and gives you no sketch: the job, the check, and the code is yours. Bench is not a build at all: it is an instrument or an idea, done once, and everything after it is easier for having done it.
Sketches for this band
- b5_01_tone_given.ino
- b5_02_functions_worked.ino
- b5_03_blocking_problem.ino
- b5_04_millis_fixed.ino
- b5_05_simon_worked.ino
Click one to save it, or get every sketch at once from the downloads page. Each file opens with a plain-English header telling you what it does and how to wire it.
Why this band matters more than the others
Everything you have built so far does one thing at a time, in order, and never has to pay attention to anything while it is busy. That has been fine. It stops being fine here.
A game has to watch for a button while a light is doing something else. As soon as you want two things happening at once, the way you have been writing code falls apart. Not slowly. Completely.
So this band does three things:
- You write your own instructions. Until now you have used instructions somebody else wrote:
digitalWrite,analogRead,map. Now you make your own and give them names. This is called writing a function, and it is the single most useful idea in programming. - You break
delayon purpose. You will build something that fails, understand exactly why, and then fix it properly. - You build a real game. Not a demonstration. A thing people will actually pick up and play, and lose at, and ask for another go.
What you need
- Your board, USB cable and breadboard
- Four LEDs, ideally four different colours
- Four 220 ohm resistors
- Four push buttons
- A passive buzzer. This is the one where you can see a small green circuit board underneath, rather than a solid black cover.
- About twenty jumper wires
If you only have small ones, split the build across two: lights on the first board, buttons and buzzer on the second. On the second board put the buttons at columns 1&3, 6&8, 11&13 and 16&18, each straddling the middle channel, and the buzzer with its long leg in
b21 and its short leg in b23. Use the bottom rails on both boards, the pair nearest row a. Then run one wire between the two − rails to join them, and take one wire from either rail to a GND pin. Until you run that joining wire the two boards' rails are separate, and a build split across two boards with unjoined rails looks exactly like a build with a bad wire.Important: if you split it this way, the hole names in the tables below no longer match your boards. The tables assume one long board. Use them for which pin goes to which part, which is the part that matters, and work out the hole names on your own two boards from the column numbers listed just above. Write your own version of the table on paper before you start wiring, or you will lose your place halfway through.
By the end you will be able to
- Write your own instruction, give it a name, and hand it values
- Get an answer back out of an instruction you wrote
- Make a sound at any pitch you choose
- Explain, with a real example, why
delaymakes a program deaf - Use
millisto make something happen on time without stopping everything else - Build a program long enough that it has to be organised, and organise it
Think first: counting to sixty
12 minIf you have someone with you
Give them two jobs at once.
- Job one: count out loud from 1 to 60, one number per second, slowly.
- Job two: if I clap, put your hand up straight away.
- The rule: you must finish counting to 60 before you are allowed to check whether I clapped.
Start them counting. Clap once when they reach about 12. Then wait and watch.
If you are on your own
You can run the same thing alone. You need a clock you can see, so use the clock on your phone, a wall clock with a second hand, or a watch.
Put a pen and paper in front of you, and put the clock where you can see it without turning your head.
Pick a number between 10 and 20 and say it out loud now. That is your signal number. Say it is 14.
Count out loud from 1 to 60, one number per second. To keep one per second, say “one and, two and, three and” in your head. Do not rush, and do not stop for anything.
When you say your signal number, that is the moment the clap happens. Notice the time on the clock as you say it, and keep counting anyway. Your rule is that you may not write anything down until you have finished saying “sixty”.
The instant you finish saying “sixty”, look at the clock and write down the time. Then write down the difference between the two times.
Now change one rule and run it again. This time you are allowed to stop and write the time down the very moment you say your signal number, then carry on counting to 60 from where you left off.
Write these down
- In the first run, how long was it between the signal and the response?
- Was the ear broken? Was the signal missed?
- In the second run, how long was it?
- In the second run, did the counting get noticeably slower, or was it about the same?
That is this whole band in one activity. Keep the paper.
The person was never deaf. They were busy, and the rule said they could not look up until they had finished. Change the rule so they look up constantly, and the response becomes instant while the counting carries on exactly as before.
Your board is that person. delay is that rule.
Making a sound
20 minSound is something being pushed back and forth very fast. Fast pushing makes air wobble, the wobble reaches your ear, and you hear a note.
How fast it wobbles is the pitch. Fast wobbling is a high note. Slow wobbling is a low note. We measure it in wobbles per second, and the unit is called hertz, written Hz.
Two kinds of buzzer, and why it matters here
| Kind | How to spot it | What it does |
|---|---|---|
| Active | Solid black cover on the bottom, usually with a sticker | Has its own wobbler inside. Switch it on with digitalWrite and it makes one fixed note. You used this in Band 3. |
| Passive | You can see a small green circuit board underneath | Has no wobbler of its own. You have to push it back and forth yourself, from the board. That means you choose the note. |
This band needs the passive one, because the game plays four different notes.
The instruction
tone(buzzerPin, 262); // start a note and keep it going
noTone(buzzerPin); // stop it
The second number is the pitch in hertz. Bigger number, higher note.
tone starts the note and then carries straight on to the next line. It does not wait. If you want a note to last, follow it with a delay, and then noTone to stop it.
Here are four notes that sound good together. You do not have to use these.
| Number | Roughly |
|---|---|
262 | a low note |
330 | a bit higher |
392 | higher again |
523 | the same as the first, but one octave up |
Notice 523 is almost exactly double 262. Doubling the number always gives you the same note, higher. That is not a coincidence, it is what an octave is.
Words to know
- pitch
- How high or low a note sounds.
- hertz
- Wobbles per second. The unit of pitch. Written Hz.
- tone
- Starts a note on a pin at a pitch you choose. Does not wait.
- noTone
- Stops the note on that pin.
Read the code before you run it
6 minint buzzerPin = 12;
void setup() {
pinMode(buzzerPin, OUTPUT);
}
void loop() {
tone(buzzerPin, 262);
delay(400);
tone(buzzerPin, 330);
delay(400);
tone(buzzerPin, 392);
delay(400);
noTone(buzzerPin);
delay(1200);
}
Write your answers down first
- How many separate notes will you hear each time round?
- Is there any silence between the notes? Look carefully.
- How long is the silence at the end?
- What would happen if you deleted every
delayline but left everything else?
An answer to think about, after you have guessed
Question 2 catches people. There is no silence between the three notes. tone replaces whatever note was playing, immediately. To get a gap you would have to put noTone and a short delay between them.
Question 4: you would hear only the last thing, and only for an instant, because all four lines would run in a few millionths of a second. Probably a faint click.
Build the four lights and the buzzer
40 minBoth ends of every wire. Pull them out of the Arduino header too, so that nothing is left in Arduino pins 2 to 12. A wire you forgot is invisible when you are looking at the breadboard, and one Arduino pin takes one wire, so it will stop you dead later and you will hunt for it in the wrong place.
Build in stages and test each stage. There are thirteen parts in the finished thing. If you wire all of it and it fails, you have thirteen suspects.
The pin plan
| Part | Pin | Holes | Wiring |
|---|---|---|---|
| Light 1 | 2 | long b3, short b4 | Resistor a3 to a1; wire b1 to pin 2; wire a4 to − rail |
| Light 2 | 3 | long b7, short b8 | Resistor a7 to a5; wire b5 to pin 3; wire a8 to − rail |
| Light 3 | 4 | long b11, short b12 | Resistor a11 to a9; wire b9 to pin 4; wire a12 to − rail |
| Light 4 | 5 | long b15, short b16 | Resistor a15 to a13; wire b13 to pin 5; wire a16 to − rail |
| Button 1 | 8 | legs in e19, e21, f19, f21 | Wire j19 to pin 8; wire a21 to − rail |
| Button 2 | 9 | legs in e24, e26, f24, f26 | Wire j24 to pin 9; wire a26 to − rail |
| Button 3 | 10 | legs in e29, e31, f29, f31 | Wire j29 to pin 10; wire a31 to − rail |
| Button 4 | 11 | legs in e34, e36, f34, f36 | Wire j34 to pin 11; wire a36 to − rail |
| Buzzer | 12 | long b40, short b42 | Wire a40 to pin 12; wire a42 to − rail. No resistor. |
| Ground | GND | — | One wire from the − rail to a GND pin |
Each light’s pin wire goes in row b, not row a, because the resistor leg is already in the row a hole and a hole takes one leg only. The five holes in a column are joined underneath, so b1 reaches the resistor in a1 perfectly well.
Everything shares the one − rail along the bottom edge. Buttons straddle the middle channel, so their pin wire comes from the top side and their ground wire from the bottom side. That is the diagonal rule from Band 3.
How each button sits on the board
You have done this once, in Band 3, and now you are doing it four times. It is worth a minute to get it right, because a button that is turned the wrong way is the single most common reason a build like this half works.
A push button has four legs, not two. They come out of the four corners of its square body, and they need four holes. The body sits over the middle channel of the breadboard, the groove running down the centre, so that two legs land in the top half and two land in the bottom half.
e and two into row f, in two columns. The two circled legs are a matching diagonal pair: one wire goes to the Arduino pin, the other to the minus rail.Take button 1 as the example. Its four legs go into e19, e21, f19 and f21. To seat it, line the four legs up over those four holes, then press straight down on the body with your thumb, not on a leg, until the body is almost touching the board. It will feel stiff and it may take real pressure. If it goes in crooked, pull it straight back out by the body, straighten any bent leg with your fingers, and try again.
Then the two wires. The pin wire goes into the top half in one column, and the ground wire into the bottom half in the other column. For button 1 that is j19 to pin 8, and a21 to the minus rail. Different column, different half. That is the diagonal.
Stage one: the four lights. Wire all four as in the table, then test them one at a time.
To test them, open b0_01_blink_given.ino from Band 0. That sketch has the number 13 written in it three times, on these three lines:
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
digitalWrite(13, LOW);
Change all three of those 13s to 2, upload, and watch light 1. Then change all three to 3, upload again, and watch light 2. Then 4, then 5. Miss one and nothing blinks, which is itself worth seeing once.
Stage two: the buzzer. Long leg towards the pin. Upload b5_01_tone_given.ino.
Stage three: the four buttons. Seat all four as described above, wire them as in the table, then test each one on its own.
To test them, open b3_02_serial_watch.ino from Band 3. Near the top it says int buttonPin = 2;. Change the 2 to 8, upload, then open the serial monitor: in the Arduino software click Tools, then Serial Monitor, and check the little box in the corner says 9600. Press button 1 and watch the numbers. Then change it to 9 for button 2, 10 for button 3, and 11 for button 4.
1 while you are not touching the button, changing to 0 for exactly as long as you hold it down, and back to 1 when you let go.Now upload b5_02_functions_worked.ino.
ledPins must list the four pins you actually used.Writing your own instruction
35 minLook at what that sketch would need without any new ideas. To play light 1 with its note, then light 2, then 3, then 4, you would write this five-line block four times over:
digitalWrite(ledPins[0], HIGH);
tone(buzzerPin, notes[0]);
delay(400);
digitalWrite(ledPins[0], LOW);
noTone(buzzerPin);
Twenty lines, nearly identical, differing by one number. And if you later decide the note should last 300 instead of 400, you have to find and change it in four places, and getting one wrong gives you a bug that is very hard to see.
Here is the alternative. Open b5_02_functions_worked.ino and find this:
void playStep(int which) {
digitalWrite(ledPins[which], HIGH);
tone(buzzerPin, notes[which]);
delay(400);
digitalWrite(ledPins[which], LOW);
noTone(buzzerPin);
delay(120);
}
And then, down in loop:
playStep(0);
playStep(1);
playStep(2);
playStep(3);
Twenty lines became four. And the 400 exists in exactly one place.
How to read a function, piece by piece
void playStep(int which) {
| Piece | What it means |
|---|---|
void | This instruction does not hand an answer back. It just does things. You have been writing this word since Band 0 without knowing why. Now you do. |
playStep | The name you chose. Any name you like, as long as it has no spaces and starts with a letter. Choose one that says what it does. |
(int which) | An empty box that gets filled in when the instruction is used. int says it holds a whole number, and which is the name that box has inside the instruction. |
{ ... } | The lines to run, exactly like setup and loop. |
What happens when you write playStep(2)
- The board stops what it is doing in
loopand remembers where it was. - It puts the number 2 into the box called
which. - It runs every line inside
playStep, and everywhere it seeswhich, it uses 2. - When it reaches the closing curly bracket, it goes back to
loopand carries on from where it stopped.
So the same seven lines do a different job each time, depending on the number you hand them.
Getting an answer back
Some instructions do hand something back. digitalRead does: you get a HIGH or a LOW. To write one of your own, you replace void with the type of thing you are handing back, and use the word return.
int waitForPress() {
...
return i;
}
Read it as: this instruction hands back a whole number. When it reaches return i, it stops immediately and gives back whatever is in i.
Then you can use it like any other instruction that produces a value:
int pressed = waitForPress();
return also ends the instruction there and then. Any lines below it do not run.
Three rules that will save you
- Write your functions above
setup. The Arduino software is clever enough to find them wherever you put them, so it will still work if you do not. Put them at the top anyway: a reader meets each tool before they meet the code that uses it, and that is much easier to follow. - A name made inside a function only exists inside it. If
playStepmakes a name calledx, thenloopcannot see it. That is a feature, not a problem: it means you can never accidentally break one part of your program by changing another. - One function, one job. If you cannot say what yours does in one short sentence, it is doing too much. Split it.
Try it now. In your copy of b5_02, change the delay(400) inside playStep to delay(150) and upload.
Now write one yourself. Add a function called flashAll that switches all four lights on, waits, and switches them all off. Then call it from loop before the four playStep lines, and upload.
flashAll(); really is inside loop.Check yours against this, after you have written it
void flashAll() {
for (int i = 0; i < 4; i = i + 1) {
digitalWrite(ledPins[i], HIGH);
}
delay(300);
for (int i = 0; i < 4; i = i + 1) {
digitalWrite(ledPins[i], LOW);
}
}
Yours does not have to match. If it works and you can say what it does in one sentence, it is right. Notice the empty round brackets: this one needs no values handed to it.
Words to know
- function
- A named group of instructions you can run from anywhere by using its name.
- call
- To run a function by writing its name, like
playStep(2); - parameter
- The box in the round brackets that gets filled in when the function is called.
- return
- Hands a value back to whatever called the function, and ends it immediately.
Now break it on purpose
25 minHere is a small thing that ought to be easy. A lamp fades slowly up and down, and a second light comes on whenever you hold a button. Two simple jobs you can already do.
Move four wires
You do not need to rebuild anything. Keep everything exactly where it is and move four wires. Unplug the USB first.
Work the table from the top down, in order. The order is not decoration. Only one wire fits in an Arduino pin, and pins 9 and 8 are both occupied right now, so the two wires that are in the way have to come out before the two new ones can go in.
| Wire | Was in | Move it to | What it becomes |
|---|---|---|---|
1. From j24 | pin 9 | nowhere — take it right out | Frees pin 9. Pull it out of the Arduino end only and leave the other end sitting in j24, so you cannot lose it. Before Part 9 you push that free end back into pin 9. |
2. From j19 | pin 8 | pin 6 | Frees pin 8. Button 1 is now the button. |
3. From b1 | pin 2 | pin 9 | Light 1 is now the fading lamp |
4. From b5 | pin 3 | pin 8 | Light 2 is now the button's answer light |
Pin 9 matters. It is the fading lamp, and only a pin with a ~ can fade. Pin 2, where that light was, cannot. Lights 3 and 4 and buttons 3 and 4 are simply not used in this part, so leave them exactly where they are. Button 2 is not used either, but its wire had to come out of pin 9 to make room.
b1 back to pin 2, b5 back to pin 3, j19 back to pin 8, and j24 back into pin 9. The pin table in Part 4 is the one to check against.Upload b5_03_blocking_problem.ino. Then press the button.
Before you read on, answer this
Look at the sketch. Nothing in it is wrong. The wiring is right. The button test is exactly the same one that worked perfectly in Band 3.
So why does the button not work? Write your answer down.
The answer
Count the delays. The two for loops run 128 times in total, each with delay(20). That is 2560 thousandths of a second, about two and a half seconds, spent doing nothing.
The button is checked once, at the very bottom, after all of that. So the board looks at the button for a few millionths of a second, once every two and a half seconds.
Your press has to still be held down at exactly that instant to be seen. A normal press lasts perhaps a fifth of a second out of every two and a half, so you get lucky about one time in twelve. That matches what you saw: mostly nothing, and then once in a while it works for no reason you could see.
The board is not broken and your code is not wrong. It is the person counting to sixty, from Part 1. It is busy, and the rule says it may not look up.
This is worth sitting with, because it is not a small technical detail. It is the reason your projects have all been simple so far.
Every delay you have ever written has stopped the entire board. Not the light. Not the line it is on. Everything. For that whole time the board cannot read a button, cannot check a sensor, cannot do anything at all.
Up to now that never mattered, because your projects only ever did one thing. From here it matters in everything you build.
millis: waiting without stopping
35 minHere is the change in thinking, and it is a real change, so go slowly.
Instead of stopping until it is time, keep running and keep asking whether it is time yet.
That is exactly the rule change in the last step of Part 1, the one where you were allowed to write the time down the moment you said your signal number instead of waiting until sixty. The person keeps counting and checks the door after every number.
delay the board spends nearly all its time frozen, so a press that lands during a wait is never noticed. With millis the loop keeps running the whole time, so the same press is caught within a few millionths of a second.The instruction
millis()
It hands back how many thousandths of a second the board has been running since it was switched on. That is all it does. It does not wait, it does not pause, it just tells you the time.
It is a clock that started when the board did. Not the time of day. The board has no idea what day it is.
Why it needs a strange type
After one minute, millis() is already 60 000. After an hour it is 3 600 000. An int cannot hold numbers that big.
So you store it in an unsigned long, which is a name for a number that can be very large but never negative.
unsigned long now = millis();
Use int here and your program will work for about half a minute and then behave very strangely. It is a horrible bug to find, so learn the type now.
The pattern
Every non-blocking timer you will ever write looks like this. Learn the shape, not the details. The sketch calls these two names lastFadeTime and fadeGap; here they are shortened to lastTime and gap so the shape is easier to see.
unsigned long lastTime = 0;
unsigned long gap = 20;
void loop() {
unsigned long now = millis();
if (now - lastTime >= gap) {
lastTime = now;
// it is time. Do the thing.
}
// everything else, every single round
}
Read it as three ideas:
lastTimeremembers when you last did the thing.now - lastTimeis how long it has been since. If that is at leastgap, it is time.- The moment you do the thing, write down the new time, so the count starts again.
And crucially: everything below the if runs on every single round, thousands of times a second, whether it was time or not.
Upload b5_04_millis_fixed.ino, with the same wiring as before.
Compare the two sketches side by side. The fade looks the same to a person watching. The code is completely different in shape.
In b5_03, the brightness lived in a for loop. In b5_04, the brightness has to be remembered in a name outside loop, because loop now only does one step of the fade each time it runs and then leaves.
Find the two lines that reverse the direction. They are the ones that check whether level has reached an end and flip step between 4 and −4.
A for loop could not do this, because a for loop insists on finishing before it lets go. Once you stop letting the loop control the timing, you have to control the counting yourself. That is the trade.
When delay is still fine
Do not now decide that delay is always wrong. It is not, and thinking so leads people to write complicated code for no reason.
delay is fine whenever the board genuinely has nothing else to do. A 50 thousandth of a second debounce is fine. Playing a note in the game, while the player is only watching, is fine.
The rule: delay is a problem only when something else needs to happen during it. Ask that question and the answer is usually obvious.
Words to know
- millis
- Gives you how many thousandths of a second the board has been running.
- unsigned long
- A name that holds a very large whole number that is never negative. Needed for
millis. - blocking
- Code that stops the whole board while it waits.
delayis blocking. - non-blocking
- Code that checks whether it is time yet and carries on regardless.
Making it unpredictable
15 minA memory game is no fun if it plays the same sequence every time. You need a number the player cannot predict.
int which = random(0, 4);
That gives you 0, 1, 2 or 3, chosen at random.
Read the two numbers carefully
random(0, 4) includes the first number and excludes the second. So you get 0, 1, 2 or 3. Never 4.
That is deliberate and it matches how arrays count. random(0, 4) gives you exactly the valid positions in a list of four, which is almost always what you want. But it surprises everybody the first time.
The problem with random on a small chip
The board has no real source of randomness. It runs a calculation that produces numbers that look random. And it starts that calculation from the same place every time it is switched on.
So without help, your game plays the identical "random" sequence every single time you power it up. Try it if you like: it is a strange thing to watch.
The fix is to start the calculation from somewhere different each time:
randomSeed(analogRead(A0));
You do not have to type this in anywhere. The finished game sketch you will upload in Part 9, b5_05_simon_worked.ino, already has that line in its setup, just below the loop that sets the pin modes. Your only job here is to understand why it is there, and to make sure nothing is wired to A0. It reads an analog pin with nothing connected to it, which, as you found out in Band 4, gives a meaningless drifting number. Meaningless is exactly what is wanted here. For once, a floating pin is useful.
Build the game
40 minEverything is wired from Part 4. Put your wiring back the way the pin table describes it if you moved anything for Part 6. All four lights, all four buttons and the buzzer need to be working.
Which button belongs to which light
The game pairs them up by position in the sketch's two lists. Button 1 answers light 1, button 2 answers light 2, and so on:
| When this light flashes | Press this button | Light pin | Button pin |
|---|---|---|---|
| Light 1 | Button 1 | 2 | 8 |
| Light 2 | Button 2 | 3 | 9 |
| Light 3 | Button 3 | 4 | 10 |
| Light 4 | Button 4 | 5 | 11 |
If you built the board left to right in the order the table in Part 4 gives, then light 1 is the leftmost light and button 1 is the leftmost button, and the pairing is simply straight down the board. If your parts ended up in a different order, that is fine, but write the pairing on a slip of paper and put it next to the board before you play, or every guess will be a guess.
Upload b5_05_simon_worked.ino and open the serial monitor.
Read it as a set of small jobs
This is the longest sketch in the course so far, at about ninety lines. It is not harder than what you have read before. It is organised, which is the whole point of this band.
Open it and find each of these. Notice that you can understand any one of them without reading the others.
| Function | Its one job |
|---|---|
playStep(which) | Show one light and play its note. |
allLights(state) | Switch all four lights on or off together. |
waitForPress() | Wait until a button is pressed, play its light and note, wait for release, and hand back which one it was. |
gameOver() | Print the score, flash and buzz, and reset the game. |
setup() | Set up all the pins, start the randomness, start the serial monitor. |
loop() | One round of the game: add a step, play the sequence, listen to the answer. |
Now look at loop on its own. Strip away the details and it says: add a new random step, play the whole sequence, then check the player's answer one press at a time. If any press is wrong, game over.
Nine lines of real work. Everything else has a name and lives somewhere else. That is what functions bought you.
Two things worth noticing in waitForPress
It waits for you to let go. There is a small loop that does nothing until the button is released. Without it, one long press would count as many presses, and the whole sequence would fail instantly. It is Band 3's problem in a new place.
It blocks, on purpose. It sits there and refuses to do anything else until a button is pressed. Ten minutes ago you learned that blocking is bad. Here it is fine, because while the game is waiting for the player there is genuinely nothing else it needs to do. In Part 7 you learned when delay causes trouble. Here it causes none, so delay is the right choice. A rule is worth more when you know when it does not apply.
randomSeed line is missing, or A0 has something wired to it. See Part 8.Look closer
30 mina. Follow the timing by hand
Fill this in for b5_04, assuming fadeGap is 20 and the board has been running a while. Do not run it.
| Round | now | lastFadeTime | now - lastFadeTime | Is it 20 or more? | Fade moves? | Button checked? |
|---|---|---|---|---|---|---|
| 1 | 1000 | 1000 | 0 | no | no | yes |
| 2 | 1005 | 1000 | ||||
| 3 | 1019 | 1000 | ||||
| 4 | 1020 | 1000 | ||||
| 5 | 1021 |
Check your table
Round 2: difference 5, not enough, fade does not move, button still checked. Round 3: difference 19, still not enough, button still checked. Round 4: difference 20, which is 20 or more, so the fade moves and lastFadeTime becomes 1020. Round 5: lastFadeTime is now 1020, so the difference is 1, not enough.
The column that matters is the last one. The button is checked on every single round, whether the fade moved or not. That is the whole difference from b5_03, and it is why the button feels instant.
b. Break it on purpose
b5_04 needs the Part 6 wiring, not the game wiring. Unplug the USB and work down the Part 6 table again, in order: j24 out of pin 9, j19 from pin 8 to pin 6, b1 from pin 2 to pin 9, b5 from pin 3 to pin 8. When you have finished this experiment, put all four back the way the Part 4 pin table says: b1 to pin 2, b5 to pin 3, j19 to pin 8, j24 to pin 9.In b5_04, delete the line lastFadeTime = now; from inside the if. Predict what will happen, then upload.
Answer
lastFadeTime stays at 0 forever. So now - lastFadeTime is just now, which is always far bigger than 20. The if is true on every round, so the fade advances thousands of times a second instead of fifty times a second.
The lamp will flicker or look oddly dim, and the fade will be far too fast to see as a fade.
The lesson: in this pattern, writing down the new time is not tidying up. It is the timer. Put it back.
c. Put the lines back in order
These lines from a function have been shuffled:
return total;
int addUp(int a, int b) {
int total = a + b;
}
Put them in order. Then answer two questions: why is the first word int and not void? And what would happen if return total; came before int total = a + b;?
Answer
Order: the header line, then make total, then return it, then the closing bracket.
It says int because this function hands a whole number back. void means it hands nothing back, and this one clearly does.
If return came first, the sketch would not compile, because total would not exist yet. And even if it did, return ends the function immediately, so the line below it would never run.
d. Explain one line
In b5_05, inside waitForPress, there is this:
while (digitalRead(buttonPins[i]) == LOW) {
delay(5);
}
It does nothing at all. It has no body worth speaking of. Why is it there, and what exactly breaks without it?
Answer
It waits for you to let go of the button. while means "keep doing this for as long as the test is true", so as long as the button is still down, it keeps doing nothing.
Without it, waitForPress would hand back an answer the instant you pressed. Then loop would call it again immediately for the next step in the sequence, your finger would still be on the button, and it would instantly count a second press of the same button. A three-step sequence would be "answered" in one press and you would lose straight away.
It is exactly the Band 3 edge detection problem, solved a different way. There, we compared with the last reading. Here, we simply refuse to move on until the button is released. Both are correct. The second is easier to read when the program has nothing else to do at that moment.
Change it
40 minWork on a copy of b5_05_simon_worked.ino.
- Bronze
- Make the game speed up as it gets longer. The sequence should play faster with every round. You will need to hand
playStepa second value: how long the step should last. - Silver
- Write a function called
playSequence()that plays the whole sequence, and use it inloopinstead of theforloop that callsplayStep(sequence[i]). Leave the secondforloop, the one that reads the player's answer, alone. Then say in one sentence whatloopreads like afterwards. - Gold
- Add a time limit. If the player does not press within three seconds, they lose. This one is hard, and it is hard for an interesting reason:
waitForPressblocks, so it can never notice time passing. You will have to rewrite it usingmillis, so that it watches the buttons and the clock at the same time.
A hint for Bronze
Change the header to void playStep(int which, int howLong) and use howLong instead of the fixed 400. Then work out the value where you call it, for example 400 - sequenceLength * 15. Do not let it go below about 120, or the notes become impossible to hear.
A hint for Gold, after you have tried it properly
The shape you want: record the start time before the waiting begins, then loop while checking two things each round. Have the buttons been pressed? Has too long passed?
Hand back the button number if one was pressed, and hand back something impossible, like −1, if the time ran out. Then in loop, check for that impossible value and treat it as a loss.
Using an impossible value to mean "nothing happened" is a very common trick, and you have now met it.
Make: a game somebody will play twice
120 minWhat to build
A game or toy that a person picks up, understands without instructions, and asks to play again. Pick one, or bring your own:
- Your own version of the memory game, with your own rules
- A reaction race for two players (needs a second person): a light comes on at a random moment, first to press wins
- A rhythm game: copy the pattern of gaps rather than the order of lights
- A one-button timing game: stop the moving light on the right one
- A simple instrument: four buttons, four notes, and a way to record and play back
It is finished when all of these are true
- Your sketch has at least four functions you wrote yourself, and each one does a single job you can say in one sentence
- Nothing important is missed because the board was stuck in a
delay - A player knows whose turn it is at all times, without being told
- Losing feels like something happened, not like the game just stopped
- Somebody works out how to play it with no more than one sentence from you. If you are working alone, this one counts as passed when you leave it for a full day, come back, pick it up cold, and know what to do without stopping to think.
The test that matters most in this band
Hand it to somebody and say one sentence, no more. Then be quiet and watch. Do not help. Do not explain. Let them be confused.
Write down every moment they did not know what to do, and every moment they thought it was their turn when it was not.
Then ask one question at the end: would you play it again? Write down the answer exactly, including the pause before it.
Show your work
Post a video of somebody else playing it, if you had somebody. If you tested it alone, post the video of yourself playing it cold the next day, and say so. Include your build log, your list of confusions, and a list of your functions with the one sentence each.
Then read one other person's sketch and tell them one place where a job could be pulled out into a function of its own.
b5_05 instead. It is somebody else's code, it is ninety lines long, and there is at least one job in loop that could still be pulled out into a function. Find it and write the function header you would give it.Check yourself
20 minCode you have not seen. Answer from reading only.
int lampPin = 5;
int buttonPin = 7;
int count = 0;
unsigned long lastBlink = 0;
int gap = 500;
void blinkOnce(int pin, int howLong) {
digitalWrite(pin, HIGH);
delay(howLong);
digitalWrite(pin, LOW);
}
void setup() {
pinMode(lampPin, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP);
Serial.begin(9600);
}
void loop() {
unsigned long now = millis();
if (now - lastBlink >= gap) {
lastBlink = now;
blinkOnce(lampPin, 300);
count = count + 1;
Serial.println(count);
}
if (digitalRead(buttonPin) == LOW) {
Serial.println("pressed");
}
}
- How many values does
blinkOnceneed when you call it, and what are they for? - Why does
blinkOncestart withvoidrather thanint? - How often does the lamp blink?
- Out of every 500 thousandths of a second, roughly how much of it is the board unable to see the button? Explain how you worked it out.
- What will the serial monitor show if you hold the button down for two seconds?
gapis anintbutlastBlinkis anunsigned long. Is that a problem here? What would happen iflastBlinkwere aninttoo?
Answers
1. Two: which pin to blink, and how long to keep it on.
2. Because it does not hand anything back. It just does something.
3. Every 500 thousandths of a second, so twice a second.
4. About 300 out of every 500, which is roughly three fifths of the time. The delay(300) inside blinkOnce is blocking, and it runs every time the lamp blinks. So although the outer timing uses millis properly, a blocking delay has been hidden inside a function and the same old problem is back. This is the question that matters most in this band. Using millis outside does not help if there is a delay inside.
5. The word pressed, printed thousands of times, flooding the window. There is no edge detection and no debounce, so it prints on every round of loop that the button is down. The counting numbers will still appear twice a second, mixed into the flood.
6. It is not a problem here. The subtraction happens on the unsigned long, and comparing the result against an int works fine for these sizes. If lastBlink were an int, it would work for about 32 seconds and then millis() would grow past what an int can hold, and the timing would go wrong in a way that looks random. Accept any answer that says the type matters because millis gets very large.
When something goes wrong
| What you see | What it usually is | What to do |
|---|---|---|
| The game says game over immediately | A button reads LOW when untouched | Two legs from the same side. Test each button on its own with the Band 3 serial watch sketch. |
| The same sequence every time you power up | No randomSeed, or A0 has something on it | See Part 8. The seeding pin must be genuinely unconnected. |
| One press counts as several steps | The wait-for-release loop is missing | See Part 10d. |
| The buzzer clicks instead of playing notes | It is an active buzzer | Passive buzzers show a green circuit board underneath. An active one still works, just with one fixed note. |
| Everything is far too fast | A missing lastTime = now; | See Part 10b. |
| Timing works for half a minute, then goes strange | An int where an unsigned long is needed | Any name holding a value from millis must be unsigned long. |
| "not declared in this scope" on a function you wrote | A spelling difference, or a name that only exists inside another function | Check the spelling and the capital letters match exactly, in both places. Then check you are not using a name that was made inside a different function, because those do not exist outside it. |
| A name inside a function is not visible outside it | That is correct behaviour | Names made inside a function only exist there. If two parts need it, make it at the top of the file. |
| A button works but the wrong light responds | Your button order and light order do not match | Buttons are pins 8 to 11 and lights are pins 2 to 5, in the same order. Check physically, left to right. |
| The board resets or behaves oddly under load | Too many lights on at once through the board | Check every LED has its own resistor. Four LEDs and a buzzer is fine; four LEDs with no resistors is not. |
Still stuck after 30 minutes?
Photograph the board from above, copy some serial output, and say which stage of Part 4 last worked. That last detail is the useful one: it tells whoever helps you where to start.
What you know now
This was the hinge of the course, and you are through it.
Before this band you could only write programs that were one long list. Now you can break a job into pieces, give each piece a name, and put the pieces together. That is the difference between programs that fit on a page and programs that do something real.
And you know why delay was never quite enough, because you built something that failed on account of it and then fixed it yourself.
Before you move on, make sure you have
- Your game, with a video of somebody else playing it, or of you playing it cold a day later
- Your list of the moments they were confused, and their answer to "would you play again"
- The list of your own functions with one sentence each
- Your completed timing table from Part 10a
- Your answers from the counting activity in Part 1
- Your build log for this band
- Five or six correct answers in Part 13, including question 4
Next is Band 6: Things That Move. Everything you have built so far makes light or sound. Next it moves, and you find out why a motor cannot simply be plugged into a pin.
Ship it
One video under sixty seconds, and one post of five lines. The video is the same one this band already asks you for as evidence, so this is not extra work. It is the same work, done once, somewhere a person can see it.
The four shots: three seconds of the thing sitting still, fifteen of you doing something to it, fifteen of it responding all the way to the end, and ten of the honest bit, which is your measurement or the wire that was wrong the first time.
The line worth writing in the post: why delay made the game miss presses, and what replaced it.
Show your work has the five-line template, a
worked example, and the three checks to make before anything goes public. Tag it
#BozomaBuilds so all nine of yours sit together.