-
Notifications
You must be signed in to change notification settings - Fork 286
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds lessons and tutorials for teachers to the documentation.
- Loading branch information
Showing
19 changed files
with
1,393 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
Buttons | ||
------- | ||
|
||
So far we have created code that makes the device do something. This is called | ||
*output*. However, we also need the device to react to things. Such things are | ||
called *inputs*. | ||
|
||
It's easy to remember: output is what the device puts out to the world | ||
whereas input is what goes into the device for it to process. | ||
|
||
The most obvious means of input on the micro:bit are its two buttons, labelled | ||
``A`` and ``B``. Somehow, we need MicroPython to react to button presses. | ||
|
||
This is remarkably simple:: | ||
|
||
from microbit import * | ||
|
||
sleep(10000) | ||
display.scroll(str(button_a.get_presses())) | ||
|
||
All this script does is sleep for ten thousand milliseconds (i.e. 10 seconds) | ||
and then scrolls the number of times you pressed button ``A``. That's it! | ||
|
||
While it's a pretty useless script, it introduces a couple of interesting new | ||
ideas: | ||
|
||
#. The ``sleep`` *function* will make the micro:bit sleep for a certain number | ||
of milliseconds. If you want a pause in your program, this is how to do it. | ||
A *function* is just like a *method*, but it isn't attached by a dot to an | ||
*object*. | ||
#. There is an object called ``button_a`` and it allows you to get the number | ||
of times it has been pressed with the ``get_presses`` *method*. | ||
|
||
Since ``get_presses`` gives a numeric value and ``display.scroll`` only | ||
displays characters, we need to convert the numeric value into a string of | ||
characters. We do this with the ``str`` function (short for "string" ~ it | ||
converts things into strings of characters). | ||
|
||
The third line is a bit like an onion. If the parenthesis are the | ||
onion skins then you'll notice that ``display.scroll`` contains ``str`` that | ||
itself contains ``button_a.get_presses``. Python attempts to work out the | ||
inner-most answer first before starting on the next layer out. This is called | ||
*nesting* - the coding equivalent of a Russian Matrioshka doll. | ||
|
||
.. image:: matrioshka.jpg | ||
|
||
Let's pretend you've pressed the button 10 times. Here's how Python works out | ||
what's happening on the third line: | ||
|
||
Python sees the complete line and gets the value of ``get_presses``:: | ||
|
||
display.scroll(str(button_a.get_presses())) | ||
|
||
Now that Python knows how many button presses there have been, it converts the | ||
numeric value into a string of characters:: | ||
|
||
display.scroll(str(10)) | ||
|
||
Finally, Python knows what to scroll across the display:: | ||
|
||
display.scroll("10") | ||
|
||
While this might seem like a lot of work, MicroPython makes this happen | ||
extraordinarily fast. | ||
|
||
Event Loops | ||
+++++++++++ | ||
|
||
Often you need your program to hang around waiting for something to happen. To | ||
do this you make it loop around a piece of code that defines how to react to | ||
certain expected events such as a button press. | ||
|
||
To make loops in Python you use the ``while`` keyword. It checks if something | ||
is ``True``. If it is, it runs its *block of code*. If it isn't, it breaks out | ||
of the loop and the rest of the program can continue. | ||
|
||
Python makes it easy to define blocks of code. Say I have a to-do list written | ||
on a piece of paper. It probably looks something like this:: | ||
|
||
Shopping | ||
Fix broken gutter | ||
Mow the lawn | ||
|
||
If I wanted to break down my to-do list a bit further, I might write something | ||
like this:: | ||
|
||
Shopping: | ||
Eggs | ||
Bacon | ||
Tomatoes | ||
Fix broken gutter: | ||
Borrow ladder from next door | ||
Find hammer and nails | ||
Return ladder | ||
Mow the lawn: | ||
Check lawn around pond for frogs | ||
Check mower fuel level | ||
|
||
It's obvious that the main tasks are broken down into sub-tasks that are | ||
*indented* underneath the main task to which they are related. So ``Eggs``, | ||
``Bacon`` and ``Tomatoes`` are obviously related to ``Shopping``. By indenting | ||
things we make it easy to see, at a glance, how the tasks relate to each other. | ||
|
||
This is called *scoping*. We use scoping to define blocks of code like this:: | ||
|
||
from microbit import * | ||
|
||
while running_time() < 10000: | ||
display.show(Image.ASLEEP) | ||
|
||
display.show(Image.SURPRISED) | ||
|
||
The ``running_time`` function returns the number of milliseconds since the | ||
device started. | ||
|
||
The ``while running_time() < 10000:`` line checks if the running time is less | ||
than 10000 milliseconds (i.e. 10 seconds). If it is, *and this is where we can | ||
see scoping in action*, then it'll display ``Image.ASLEEP``. Notice how this is | ||
indented underneath the ``while`` statement *just like in our to-do list*. | ||
|
||
Obviously, if the running time is equal to or greater than 10000 milliseconds | ||
then the display will show ``Image.SURPRISED``. Why? Because the ``while`` | ||
condition will be False (``running_time`` is no longer ``< 10000``). In that | ||
case the loop is finished and the program will continue after the ``while`` | ||
loop's block of code. It'll look like your device is asleep for 10 | ||
seconds before waking up with a surprised look on its face. | ||
|
||
Try it! | ||
|
||
Handling an Event | ||
+++++++++++++++++ | ||
|
||
If we want MicroPython to react to button press events we should put it into | ||
an infinite loop and check if the button ``is_pressed``. | ||
|
||
An infinite loop is easy:: | ||
|
||
while True: | ||
# Do stuff | ||
|
||
(Remember, ``while`` checks if something is ``True`` to work out if it should | ||
run its block of code. Since ``True`` is obviously ``True`` for all time, you | ||
get an infinite loop!) | ||
|
||
Let's make a very simple cyber-pet. It's always sad unless you're pressing | ||
button ``A``. If you press button ``B`` it dies. (I realise this isn't a very | ||
pleasant game, so perhaps you can figure out how to improve it.):: | ||
|
||
from microbit import * | ||
|
||
while True: | ||
if button_a.is_pressed(): | ||
display.show(Image.HAPPY) | ||
elif button_b.is_pressed(): | ||
break | ||
else: | ||
display.show(Image.SAD) | ||
|
||
display.clear() | ||
|
||
Can you see how we check what buttons are pressed? We used ``if``, | ||
``elif`` (short for "else if") and ``else``. These are called *conditionals* | ||
and work like this:: | ||
|
||
if something is True: | ||
# do one thing | ||
elif some other thing is True: | ||
# do another thing | ||
else: | ||
# do yet another thing. | ||
|
||
This is remarkably similar to English! | ||
|
||
The ``is_pressed`` method only produces two results: ``True`` or ``False``. | ||
If you're pressing the button it returns ``True``, otherwise it returns | ||
``False``. The code above is saying, in English, "for ever and ever, if | ||
button A is pressed then show a happy face, else if button B is pressed break | ||
out of the loop, otherwise display a sad face." We break out of the loop (stop | ||
the program running for ever and ever) with the ``break`` statement. | ||
|
||
At the very end, when the cyber-pet is dead, we ``clear`` the display. | ||
|
||
Can you think of ways to make this game less tragic? How would you check if | ||
*both* buttons are pressed? (Hint: Python has ``and``, ``or`` and ``not`` | ||
logical operators to help check multiple truth statements (things that | ||
produce either ``True`` or ``False`` results). | ||
|
||
.. footer:: The image of Matrioshka dolls is licensed CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=69402 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
Direction | ||
--------- | ||
|
||
There is a compass on the BBC micro:bit. If you ever make a weather station | ||
use the device to work out the wind direction. | ||
|
||
Compass | ||
+++++++ | ||
|
||
It can also tell you the direction of North like this:: | ||
|
||
from microbit import * | ||
|
||
compass.calibrate() | ||
|
||
while True: | ||
needle = ((15 - compass.heading()) // 30) % 12 | ||
display.show(Image.ALL_CLOCKS[needle]) | ||
|
||
**You must calibrate the compass before taking readings.** Failure to do so | ||
will just produce garbage results. The ``calibration`` method runs a fun little | ||
game to help the device work out where it is in relation to the Earth's | ||
magnetic field. | ||
|
||
The program takes the ``compass.heading`` and, using some simple yet | ||
cunning maths (floor division ``//`` and modulo ``%`` ~ look up what these | ||
mean), works out the number of the clock hand to use to display on the screen | ||
so that it is pointing roughly North. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
Gestures | ||
-------- | ||
|
||
The really interesting side-effect of having an accelerometer is gesture | ||
detection. If you move your BBC micro:bit in a certain way (as a gesture) then | ||
MicroPython is able to detect this. | ||
|
||
MicroPython is able to recognise the following gestures: ``up``, ``down``, | ||
``left``, ``right``, ``face up``, ``face down``, ``freefall``, ``3g``, ``6g``, | ||
``8g``, ``shake``. Gestures are always represented as strings. While most of | ||
the names should be obvious, the ``3g``, ``6g`` and ``8g`` gestures apply when | ||
the device encounters these levels of g-force (like when an astronaut is | ||
launched into space). | ||
|
||
To get the current gesture use the ``accelerometer.current_gesture`` method. | ||
Its result is going to be one of the named gestures listed above. For example, | ||
this program will only make your device happy if it is face up:: | ||
|
||
from microbit import * | ||
|
||
while True: | ||
gesture = accelerometer.current_gesture() | ||
if gesture == "face up": | ||
display.show(Image.HAPPY) | ||
else: | ||
display.show(Image.ANGRY) | ||
|
||
Once again, because we want the device to react to changing circumstances we | ||
use a ``while`` loop. Within the *scope* of the loop the current gesture is | ||
read and put into ``gesture``. The ``if`` conditional checks if ``gesture`` is | ||
equal to ``"face up"`` (Python uses ``==`` to test for equality, a single | ||
equals sign ``=`` is used for assignment - just like how we assign the gesture | ||
reading to the ``gesture`` object). If the gesture is equal to ``"face up"`` | ||
then use the display to show a happy face. Otherwise, the device is made to | ||
look angry! | ||
|
||
Magic-8 | ||
+++++++ | ||
|
||
A Magic-8 ball is a toy first invented in the 1950s. The idea is to ask | ||
it a yes/no question, shake it and wait for it to reveal the truth. It's rather | ||
easy to turn into a program:: | ||
|
||
from microbit import * | ||
import random | ||
|
||
answers = [ | ||
"It is certain", | ||
"It is decidedly so", | ||
"Without a doubt", | ||
"Yes, definitely", | ||
"You may rely on it", | ||
"As I see it, yes", | ||
"Most likely", | ||
"Outlook good", | ||
"Yes", | ||
"Signs point to yes", | ||
"Reply hazy try again", | ||
"Ask again later", | ||
"Better not tell you now", | ||
"Cannot predict now", | ||
"Concentrate and ask again", | ||
"Don't count on it" | ||
"My reply is no", | ||
"My sources say no", | ||
"Outlook not so good" | ||
"Very doubtful" | ||
] | ||
|
||
while True: | ||
display.show("8") | ||
if accelerometer.was_gesture("shake"): | ||
display.clear() | ||
sleep(1000) | ||
display.scroll(random.choice(answers)) | ||
|
||
Most of the program is a list called ``answers``. The actual game is in the | ||
``while`` loop at the end. | ||
|
||
The default state of the game is to show the character ``"8"``. However, the | ||
program needs to detect if it has been shaken. The ``was_gesture`` method uses | ||
its argument (in this case, the string ``"shake"`` because we want to detect | ||
a shake) to return a ``True`` / ``False`` response. If the device was shaken | ||
the ``if`` conditional drops into its block of code where it clears the screen, | ||
waits for a second (so the device appears to be thinking about your question) | ||
and displays a randomly chosen answer. | ||
|
||
Why not ask it if this is the greatest program ever written? What could you do | ||
to "cheat" and make the answer always positive or negative? (Hint: use the | ||
buttons.) |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.