Image

7
TIME TRAVEL

Okay, so we won’t actually build a time machine in this chapter, but you’ll measure time by making a binary clock and a clock that can speak. We’ll also conduct an experiment to test how well your micro:bit can keep time. All the experiments and projects in this chapter use MicroPython only.

EXPERIMENT 9: KEEPING TIME

Image

The aim of this experiment is to make a micro:bit clock that keeps good time. That means programming the micro:bit to tick at precisely one-second intervals.

Image

One way to do this would be to use the sleep function, as in the code shown here. Note that this code is not a full program, so don’t try to run it. The sleep command stops the micro:bit from doing anything for however long you specify. In our case, the delay is 1,000 milliseconds (1 second).

seconds = 0

while True:
    sleep(1000)
    seconds += 1

In this example, after each 1 second delay, the program adds 1 to the seconds variable, which counts the number of seconds that have passed. This loop repeats indefinitely and, as a way of marking time, works for a bit.

The problem is that the clock will gradually fall behind because we haven’t accounted for the time it takes the micro:bit to add 1 to the seconds variable. In this example, adding 1 to the variable won’t take much time at all, but if the program got any longer—for example, by telling the clock to also display the time or even speak the time, which we’ll try later—then the delay could become significant. It would also be unpredictable, as the time lost may not be the same every time the program loops.

Therefore, a better way to keep time is to use the running_time function. This function returns the number of milliseconds that have passed since the micro:bit was last reset, and it’s not affected by how long other parts of your code take to do things.

In this experiment, we’ll use the running_time function to calculate just how slow or fast our micro:bit clock runs.

What You’ll Need

To carry out this experiment, you just need two things:

Micro:bit

USB cable

Construction

  1. Find the code at https://github.com/simonmonk/mbms/. The Python file for this experiment is Experiment_09.py. Open the program in Mu and flash it onto your micro:bit.
  2. Once you’ve successfully programmed the micro:bit, press button B. You should find that the micro:bit sets the seconds to 0 and starts counting up from there.
  3. Set a timer on your phone or another device for exactly 16 minutes 40 seconds. Start the timer and, at the same time, press button B to reset the micro:bit’s second count. At the end of the timed period, press button A to freeze the clock and make a note of the number of seconds displayed.

Because this experiment is all about timing, it’s important to start the timer at exactly the same time that button B is pressed and to press button A as soon as the timer sounds. This will be easier if a friend helps: one of you can operate the timer, while the other operates the micro:bit.

The reason for setting the timer to 16 minutes 40 seconds is that this is 1,000 seconds. If the micro:bit’s second count is greater than 1,000, then the clock is running fast, and if the count is less than 1,000, the clock is running slow. My micro:bit’s second count was 989, indicating that the micro:bit’s internal clock was running about 11 parts in 1,000 too slow.

Make a note of your micro:bit’s second count. You’ll use it in the projects in this chapter to make your clock more accurate.

Code

Here is the MicroPython code for Experiment 9:

from microbit import *

seconds = 0
last_time = 0

while True:
    now = running_time()
    elapsed_ms = now - last_time
    if elapsed_ms >= 1000:
        seconds += 1
        last_time = now
    if button_a.was_pressed():
        display.scroll(str(seconds))
    if button_b.was_pressed():
        seconds = 0
        display.show("0")
        sleep(100)
        display.clear()

The program uses two variables:

last_time Keeps track of the last time that the clock ticked

seconds Keeps track of the number of seconds that have passed since the micro:bit was last reset and the clock started running

I find it useful to think of the clock ticking, like a regular clock. That is, it does something at regular intervals.

The main while loop uses the running_time function to find out how long the micro:bit has been running in milliseconds. It stores that number in a variable called now. It then calculates how many milliseconds have elapsed since the last tick by subtracting last_time from now.

If the number of milliseconds elapsed is greater than or equal to 1,000—in other words, greater than or equal to 1 second—then the seconds variable increases by 1. Then we reset the number of milliseconds elapsed to 0 so we can count elapsed time over again.

We use two if statements to program button A and button B. If you press button A, the micro:bit will display seconds, or the time that’s passed since the program started running. If you press button B, the seconds count will reset to 0.

How It Works: Keeping Time

The micro:bit’s processor uses a crystal oscillator (an electronic component used to keep time accurately) that should be accurate to better than 30 parts per million. However, for my micro:bit, it was inaccurate by 11,000 parts per million for some reason.

To get a truly accurate clock, you’d need to use a dedicated RTC (Real Time Clock) chip and separate crystal oscillator. At the time of writing, no RTC chips are available specifically for the micro:bit. Although they can be made to work with the micro:bit, this is a fairly tricky process. Therefore, it’s probably best not to rely too much on either of the clocks you’ll build in this chapter, but these projects are fun and will teach you important skills.

PROJECT: BINARY CLOCK

Image

Difficulty: Easy

In this project, shown in Figure 7-1, you’ll create a clock that shows you the time in binary. Binary is a numbering system used in computers. You can learn more about it in the “How It Works: Telling the Time in Binary” on page 176. A binary clock displays hours, minutes, and seconds as separate binary numbers on the micro:bit’s LED display.

Image

Figure 7-1: The binary clock in the Kitronik MI:Pro case

Figure 7-2 shows the binary numbering system on the micro:bit. It might seem confusing at first, like a random pattern of LEDs, but I’ll explain how it works shortly. Plus, the Mad Scientist loves to show off their skill at reading binary clocks to impress their friends!

Image

Figure 7-2: Reading the binary clock

How to Read the Binary Clock

Our binary clock is a 24-hour clock. To read the clock, start at the top row of LEDs, shown in Figure 7-2. This row represents the hours. Each column of LEDs stands for a number. From right to left, the columns represent the numbers 1, 2, 4, 8, and 16. By adding these five numbers, you can create every possible value between 1 and 24. To work out the hour, add the column numbers for the LEDs that are lit. In the case of Figure 7-2, that’s 1, 2, and 8, which add up to 11. So, the hour is 11.

The next two rows represent the number of minutes, and the bottom two rows represent the number of seconds. Both minutes and seconds are indicated by a row with the same 1, 2, 4, 8, and 16 LEDs. However, since we need to be able to count all the way to 60 for minutes and seconds, these values are indicated by an additional LED (worth 32) on the preceding row. As shown in Figure 7-2, the 32 LED for minutes is the leftmost LED on the second row from the top, and the 32 LED for seconds is the leftmost LED on the fourth row from the top.

In Figure 7-2, the 4 and 1 minute LEDs are lit, indicating 5 minutes. The 2 and 1 second LEDs are lit, indicating 3 seconds. All together, this display is saying the time is 11:05:03.

Holding down button A will make the numbers on the clock advance very quickly, allowing you to set the time. You just need to be ready to stop the clock as soon as it reaches the correct time.

You can see a video of the clock in operation, including how the time is set, at https://www.youtube.com/watch?v=v26gYo5OG0g.

What You’ll Need

For this project, all you need is your micro:bit and a power source.

If you plan to keep your clock running constantly, then you should use a USB power adapter or other long-term power source for the micro:bit (see the appendix) to save on batteries. You might also want to get a case for the micro:bit to make your clock look nicer.

Remember that the clock won’t keep perfect time, so you’ll need to reset it fairly often.

Construction

  1. The code for this project is in MicroPython, because the math required would be extremely tricky to do in the Blocks code. Download the code from https://github.com/simonmonk/mbms/. The file for this project is ch_07_Binary_Clock.py.
  2. Before loading the program onto your micro:bit, open it in Mu and change the current time so it’s accurate. You should probably set it to a few minutes before the current time, just to be safe. You can adjust it later by holding down button A.

    Change the time by altering the following lines. Remember that this is a 24-hour clock, so, for example, 6:00 PM is 12 + 6 or 18 hours.

    hours = 8
    minutes = 25

    You should also change the value of the adjust variable to the number of parts per thousand by which your micro:bit’s clock is slow or fast. My micro:bit ran slow by 11 seconds in 1,000, so I set adjust to –11 to speed it up a little (note the negative sign). If the micro:bit had run fast by, say, 10 seconds per 1,000, then I would have set adjust to 10 to slow things down a tiny amount.

Code

This project involves quite a lot of math. We’ll also use some more advanced programming features, such as two-dimensional arrays, which are much easier to implement in Python than in Blocks code.

Let’s break the code into chunks, starting with the lines that assign the correct binary values to each LED:

# hhhhh
# m
# mmmmm
# s
# sssss
sec_leds = [[4, 4], [3, 4], [2, 4], [1, 4], [0, 4], [0, 3]]
min_leds = [[4, 2], [3, 2], [2, 2], [1, 2], [0, 2], [0, 1]]
hour_leds = [[4, 0], [3, 0], [2, 0], [1, 0], [0, 0]]
adjust = -11

The first four lines of code aren’t actually part of the program. They’re comment lines, or notes, reminding you which LEDs display the hours, minutes, and seconds. These can serve as a useful reference when setting the LED coordinates in the arrays that follow.

The next three lines are the two-dimensional arrays mentioned earlier. These assign the proper LED coordinates to the seconds, minutes, and hours. Remember that arrays are like variables, except they hold multiple elements. The arrays we’re using here are called two dimensional because their elements are also arrays. For example, the first element in the first array, sec_leds, is [4, 4]. This identifies the first LED used to display the number of seconds as the LED with an x coordinate of 4 and a y coordinate of 4. That’s the LED in the bottom right corner of the display. Figure 7-3 shows the coordinates of the individual LEDs that make up the display.

Image

Figure 7-3: LED display coordinates

As you can see, the top left LED has the coordinates [0, 0] and the bottom right [4, 4].

Image

Next, we have the hours, minutes, and seconds variables to keep the current time:

hours = 8
minutes = 25
seconds = 1
adjust = -11

We’ll use the adjust variable to correct the clock’s speed. See “Construction” on page 167 if you’re not sure how to do this.

Here is the function that turns the LEDs on or off to indicate a value in binary:

def display_binary(value, num_bits, leds):
    v = value
    for i in range(0, num_bits):
        v_bit = v % 2
        display.set_pixel(leds[i][0], leds[i][1], int(v_bit * 9))
        v = int(v / 2)

We have three separate binary numbers (hours, minutes, and seconds) to display, and the display_binary function works for all of them. It takes a number to display (value), the number of LEDs to use in displaying the number (num_bits), and an array of LEDs to use (leds). It uses these three values to display the three parts of the time—the seconds, minutes, and hours—on the micro:bit.

To keep track of the time, you use two variables:

last_time = 0
tick = 1000 + adjust

The variable last_time records the last time that the clock ticked, and the variable tick holds the duration of the clock’s tick in milliseconds. The default value for tick is 1,000 + adjust, but this value will change when you press button A to set the time.

Here is the code to update the time:

def update_time():
    global hours, minutes, seconds
    seconds += 1
    if seconds > 59:
        seconds = 0
        minutes += 1
        if minutes > 59:
            minutes = 0
            hours += 1
            if hours > 23:
                hours = 0

The function update_time adds 1 to the number of seconds every time it is called. When the seconds count gets past 59, it resets to 0 and increases the number of minutes. It does the same thing for the hours. We use nested if statements to accomplish this.

This is the code to display the hours, minutes, and seconds in binary:

def display_time():
    display_binary(seconds, 6, sec_leds)
    display_binary(minutes, 6, min_leds)
    display_binary(hours, 5, hour_leds)

We put this code inside the display_time function, which calls the display_binary function defined earlier.

Here is the main while loop that makes the clock run quickly when someone presses button A and normally otherwise. It also contains the code to keep time.

while True:
    if button_a.is_pressed():
        tick = 10
    else:
        tick = 1000 + adjust
    now = running_time()
    elapsed_ms = now - last_time
    if elapsed_ms >= tick:
        update_time()
        display_time()

The first part of the loop checks whether button A is being pressed. If it is, the code reduces tick to 10 milliseconds. Otherwise, it sets it to 1000 + adjust.

Finally, we write the code that allows the clock to keep time. The function running_time returns the number of milliseconds since you last reset your micro:bit. Each time the program loops, we calculate how much time has elapsed since the clock last ticked. The loop does the following:

  1. Gets the current running_time and stores it in a variable called now
  2. Calculates the value for elapsed_ms by figuring out the difference between the variables now and last_time
  3. Updates the time if elapsed_time is greater than our tick time of 1 second
  4. Sets last_time to now, resetting the millisecond count to zero

How It Works: Telling the Time in Binary

Using this binary clock to tell the time is a little tricky. It’s especially hard to figure out the seconds, which are likely to have changed before the code has calculated them. But there’s a reason the binary system exists.

Most of us are familiar with the decimal system of writing numbers. Decimal is the Latin word for 10, and in the decimal system, we use 10 different symbols (the digits 0 through 9). If we need to write a number greater than 9—say 15—then we use two digits. Because of the position of the 1 in the number 15, we know that it actually represents the number 10.

The following table shows the numbers from 0 to 10 in binary. Note that in decimal, we don’t write leading zeros. We wouldn’t write 15 as 0015, for example. In binary, however, it’s customary to write numbers with the leading zeros to give the numbers the same number of digits. That’s just the way computer scientists roll. So in this case, all the binary numbers are four digits long.

Decimal

Binary

0

0000

1

0001

2

0010

3

0011

4

0100

5

0101

6

0110

7

0111

8

1000

9

1001

10

1010

In theory, computers could store numbers like this by using 10 different voltages to represent the digits 0 to 9, but they don’t. Instead, computers use a system called binary. Rather than having 10 possible values for a digit, a binary digit (also called a bit) can represent either a 0 or a 1. Computers use binary because the transistors they are made with are really good at being either off (0) or on (1). That means they have only 2 possible states, which is much easier than giving them 10 possible states. Also, the math behind binary logic means that a computer can do reliable arithmetic on binary numbers far more easily than it were dealing with numbers in decimal.

Just as in the familiar decimal system, the binary system combines digits to represent bigger numbers. Whereas each digit position in a decimal number increases by a factor of 10—going from 1, to 10, to 100—each binary digit increases by a factor of 2—from 1, to 2, to 4, and so on. For example, the four-digit binary number 1010 has 1s in the 16- and 2-digit positions and 0s in the other positions. In decimal, it’s 16 + 2, or 18.

It turns out that you don’t need very many binary digits to represent some pretty big numbers. For example, eight binary digits put together (called a byte) can represent a decimal number anywhere between 0 and 255. Make that 16 bits, and the number goes up to 65,535. A computer with 64 bits is able to do everything using those binary 64 digits, and it can represent a number between 0 and 18,446,744,073,709,551,615. Incidentally, a micro:bit has a 32-bit processor, able to represent numbers between 0 and a very respectable 4,294,967,295. The MicroPython function running_time that we’ve been using uses a 32-bit number. This means that it will not run out of numbers for 4,294,967,295 ÷ 1,000 ÷ 60 ÷ 60 ÷ 24 = 49.7 days.

PROJECT: TALKING CLOCK

Image

Difficulty: Easy

Sometimes the Mad Scientist is so busy with test tubes and chemicals and alarming plumes of smoke that they can’t check the time. And then they forget to eat! That’s when a talking clock comes in handy. This project (Figure 7-4) announces the time every hour, or whenever you press button A.

Image

Figure 7-4: A talking clock

You can see this project in action at https://www.youtube.com/watch?v=iNjXEK8RUtU.

What You’ll Need

For this project, you’ll need the following items:

Micro:bit

3 × Alligator clip cables To connect the micro:bit to the speaker

Speaker for micro:bit To play the sounds (Use a Monk Makes Speaker or see Chapter 2 for other speaker options.)

Power adapter See the appendix for options on keeping your micro:bit powered without batteries.

If you’re planning to keep your clock running, then use a USB power adapter or another long-term power solution to save on batteries.

You may want to build a case for the clock or attach the micro:bit and speaker to the same piece of cardboard you used for the light-controlled guitar project from Chapter 3.

Construction

  1. The code for this project is in MicroPython, as the speech library is not currently available in the Blocks code. Download the code from https://github.com/simonmonk/mbms/. The file for this project is ch_07_Talking_Clock.py.
  2. Before loading the program onto your micro:bit, open the file in Mu and change the hours and minutes to your current time. Also change adjust to the amount by which you want to adjust your clock. See “Construction” on page 167 of the binary clock project if you’re not sure how to do this.
  3. Connect a speaker to the micro:bit with one alligator clip on pin 0 and the other alligator clip on the pin marked IN on the speaker. Use the other two clips to provide power to the speaker, as shown in Figure 7-4.
  4. Wait for the time you set in step 2 to arrive. Then connect the micro:bit to a power source. Note that for this project, there is no other mechanism to set the time.

Code

For clarity, we’ll work through this code in sections.

Here’s the code that does the time keeping—in other words, that makes sure the hours and minutes are correct. It’s almost the same as the time-keeping code from the previous project. The main difference is that, instead of showing the time on the LED display, the code will speak the current time.

digits = ["no", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
"sixteen", "seventeen", "eighteen", "nienteen"]
tens = ["no", "no", "twenty", "thirty", "forty", "fifty"]
preamble = "The time is "
am = "aye em"
pm = "pee em"

We use several variables and arrays to hold a set of words that the micro:bit will speak.

The speech library contains recordings of some common words. It can say single digits like 1, 2, or 3, but for numbers 10 and over, it pronounces each digit separately, which is not what we want our talking clock to do. That’s why we have to spell out numbers greater than 10. Notice that nineteen is misspelled as nienteen to make it sound right when the synthesized voice says it. The array called digits holds the text for each number up to 19. The clock should never speak the 0 digit, so we just set it to the word no.

The tens array does a similar job with numbers that are multiples of 10. We already accounted for all the numbers up to 19 with the digits array, so we don’t need to worry about the first two elements in the tens array, which will never be spoken. We set these to no as well.

The preamble variable contains the text that the micro:bit will speak before it announces each time. The am and pm variables contain phonetic versions of the AM/PM indicator. The micro:bit will speak one of these after reading the time.

Here’s the code with the function that actually speaks the time. Appropriately enough, it’s called speak_the_time.

def speak_the_time():
    h = hours
    am_pm = am
    if h >= 12:
        am_pm = pm
    if h > 12:
        h = h - 12
    if minutes == 0:
        # The time is twelve pm exactly
        speech.say(preamble + digits[h] + " "
                            + am_pm + " exactly")
    else:
        if minutes < 10:
            # The time is twelve o four pm
            speech.say(preamble + digits[h] + " o "
                  + digits[minutes] + " " + am_pm)
        elif minutes < 20:
            # The time is twelve eighteen pm
            speech.say(preamble + digits[h] + " "
                  + digits[minutes] + " " + am_pm)
        else:
            mins_tens = int(minutes / 10)
            mins_units = minutes % 10
            if mins_units == 0:
                # The time is twelve twenty pm
                speech.say(preamble + digits[h] + " "
                      + tens[mins_tens] + " " + am_pm)
            else:
                # The time is twelve twenty four pm
                speech.say(preamble + digits[h] + " "
      + tens[mins_tens] + " " + digits[mins_units] + " " + am_pm)

This function is fairly complex, as it has to account for our different ways of expressing the time.

This clock speaks in the 12-hour format but stores the hours in 24-hour format, so the first thing speak_the_time does is decide whether the time is AM or PM. It subtracts 12 from the hour variable once hour reaches 13.

Next, the nested if statements cover the following possible cases:

Image  If the time is exactly on the hour, say something like The time is twelve pm exactly.

Image  Otherwise, if the minutes are less than 10, add an o, to say something like The time is twelve o four pm.

Image  For two-digit minutes less than 20, use the digits array and say something like The time is twelve eighteen pm.

Image  For other two-digit minutes of 20 or over that are multiples of 10, use the tens array to say something like The time is twelve twenty pm.

Image  And, where the minutes are not multiples of 10, say something like The time is twelve twenty four pm.

Last comes the main while loop:

while True:
    if button_b.is_pressed():
        speak_the_time()
    now = running_time()
    elapsed_ms = now - last_time
    if elapsed_ms >= tick:
        elapsed_seconds = int(elapsed_ms / tick)
        update_time(elapsed_seconds)
        blink()
        last_time = now

This loop checks whether button B has been pressed or an hour has passed and speaks the time if either event has occurred. It also calls a function called blink. That flashes the heart icon on the screen to reassure you that the clock is working, even if it is silent most of the time.

How It Works: Teaching the Micro:bit to Speak

The MicroPython speech library opens up all sorts of possibilities in your projects, as you saw back in Chapter 6. The sound quality isn’t perfect, but it does add loads of fun to your projects.

The speech library itself is based on the concept of phonemes: building blocks of sound. When you use the say function, the text to be spoken is first translated into a series of phonemes. Because of the strangeness of spoken language, this often doesn’t work perfectly—hence the misspelling of nineteen in the code for this project to help the say function pronounce the word more accurately.

You can read much more about this speech library at https://microbit-micropython.readthedocs.io/en/latest/tutorials/speech.html.

SUMMARY

Hopefully you now have a good sense of how to make a clock using a micro:bit and use it to display or literally tell you the time.

In the next chapter, the Mad Scientist turns their attention to psychological experiments.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.159.82