File: buttons.rst

package info (click to toggle)
firmware-microbit-micropython 1.0.1-4
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, sid
  • size: 25,448 kB
  • sloc: ansic: 83,496; cpp: 27,664; python: 2,475; asm: 274; makefile: 245; javascript: 41; sh: 25
file content (189 lines) | stat: -rw-r--r-- 7,015 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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 a *block of code* called the *body* of the loop.
If it isn't, it breaks out of the loop (ignoring the body) 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 *nesting*. We use nesting 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