Chapter 8. Input and Sensing

It is important for a device to react to interactions from users or changes in its environment. Such interactions are at the core of the device’s nonprogrammer user experience. For example, shaking the device to reset the state of the program, using the light sensor to change the intensity of the LEDs, or pressing a button to cycle through a series of options are all interactions that rely on the device processing input or sensing changes in its environment.

The devices described in this book do not have keyboards, mice, or touchscreens like traditional computing devices. So how are we to interact with them? Typing Python commands into a REPL might be fun for programmers, but this doesn’t address the needs of nontechnical users who need to communicate intents, instructions, or decisions in a manner that is intuitive, context sensitive, and perhaps even entertaining. Therefore, it’s important to consider how MicroPython works with input and sensors.

Collectively, the devices covered in this book have a wide variety of input and sensing capabilities available to them. Some, like the ESP8266/32-based devices, are limited only to one or two buttons; others, like the Circuit Playground Express and micro:bit, have all sorts of components through which users can interact with the device. In all cases, it is possible to connect external sensors and peripherals to the boards via GPIO pins, and the general prinicples of working with such hardware can be transferred between devices.

We start our exploration of input and sensing with the ubiquitous button.

Buttons and Capacitative Touch

Buttons are interesting in an, “Ooh, I wonder what happens if I press this?” sort of a way. As programmers, it’s our job to make sure people are not disappointed with the result of pressing a button!

Every device has at least one reset button that will restart the board. Some devices have several more buttons.

The micro:bit has the simplest means of interacting with its two buttons (labelled A and B). These are represented by two objects found in the microbit module called button_a and button_b. Both button objects have the same methods, and in order for them to be useful, we need to consider the notion of an event loop and containing a short pause.

A fundamental requirement when dealing with input is waiting for something to happen. This is achieved with an event loop (code that continuously loops around waiting for and handling input events). Furthermore, when user-generated events occur, they often need to be handled in a way that works in human, rather than computer, time frames. For example, when you press a button, the event loop will have cycled lots of times in the fraction of a second you will have taken to actually press the button. What happens if we only want the button-press to be handled once, rather than on each of the numerous iterations of the event loop that occured while we pressed the button? The solution is to insert a short pause to the event loop to slow it down so multiple events don’t fire quickly when we only need to register a single event.

The next example illustrates both concepts:

from microbit import *

position = 2
while True:  # event loop
    sleep(60)  # pause 
    if button_a.is_pressed():
        display.clear()
        position = max(0, position - 1)
    elif button_b.is_pressed():
        display.clear()
        position = min(4, position + 1)
    display.set_pixel(position, 2, 9)

In order to wait for something to happen, we simply make an infinite loop around a piece of code that defines how to react to certain expected events (such as a button press). In the example, the infinite loop is achieved in the simplest possible manner with while True:. The resulting blocks of code are conditional on button presses. If a button is pressed, the code changes the position value and then displays a pixel in that position in the X-column and in Y-row three. It’s a very simple means of moving the pixel from left to right. Perhaps the best way to understand what the “pause” line is for is to remove it and try to use the buttons.

The problem is the event loop is way too quick for our human reflexes. If there were no pause line to slow down the cycle of the loop, then you would only be able to move the pixel to the extreme edges. Why? Because your reaction for pressing a button is slow enough that the event loop will cycle too many times (thus moving the pixel to the extreme left or extreme right). The pause gives you just enough time to tap the button to move the pixel by a single unit. An interesting way to explore this feature is to change the number of milliseconds the device sleeps as part of the pause operation and observe how this affects the usefulness of the buttons.

Given that a button can be in only two states (pressed or released), then the button’s is_pressed() method returns a Boolean value. Sometimes you need to know if a button was pressed while the device was doing something else (such as scrolling text along the display). In this case, use the was_pressed() method of a button object to return a Boolean indicating if it had been pressed since the device started or the last time the method was called. Finally, you may need to count the number of presses for a button. Use get_presses() to return the running total and reset the total to zero.

These three methods give you a remarkable amount of flexibility when using the micro:bit’s buttons. However, they hide some of the lower-level details of how such buttons work.

Buttons, in general, are digital in that they can only ever be either on or off. Obviously the microcontroller needs to detect the on or off state via the pin to which the button is connected. To differentiate the state, the microprocessor measures the voltage into the pin connected to the button.

A pin can be in three possible states: high, low, or floating.

If the pin detects some arbitrary signal, for example, 3.3 volts, then it is high; whereas if it can’t detect a signal (0 volts), then it is low. In order for the pin to behave in a well-defined manner under all conditions, it is necessary to set the pin to be either pulled up (where the default signal is high) or pulled down (where the default signal is low). If we don’t do this, the pin will be in the floating state: the microcontroller may unpredictably interpret the input as either high or low. By setting the pull we are setting, at the hardware level, a default value.

Armed with this information about the fundamental properties of pins, let’s examine how the Circuit Playground Express works with its two buttons. The following simple script demonstrates working with buttons at a lower level than on the micro:bit (where such implementation details are hidden from the user). It creates a light display: press the lefthand button to make the NeoPixels flash clockwise or press the righthand button to reverse direction:

import neopixel
import time
import digitalio
from board import NEOPIXEL, BUTTON_A, BUTTON_B


np = neopixel.NeoPixel(NEOPIXEL, 10, auto_write=False)
button_a = digitalio.DigitalInOut(BUTTON_A)
button_a.pull = digitalio.Pull.DOWN
button_b = digitalio.DigitalInOut(BUTTON_B)
button_b.pull = digitalio.Pull.DOWN


clockwise = True
while True:
    time.sleep(0.05)
    if button_a.value:
        clockwise = True
    elif button_b.value:
        clockwise = False
    for i in range(10):
        if clockwise:
            i = 9 - i
        for j in range(10):
            np[j] = tuple((max(0, val - 64) for val in np[j]))
        np[i] = (0, 0, 254)
        np.write()

This script should look familiar—it’s similar to the example used to demonstrate NeoPixels in the previous chapter. The most immediate difference is the use of the digitalio module to create two objects representing buttons A and B.

Both buttons are instantiated as DigitalInOut objects that represent digital pins that can act as both input or output. The button objects (button_a and button_b) are instantiated with a reference to the pin to use on the actual board. These are the BUTTON_A and BUTTON_B constants imported from the board module. The default state of such objects is to read input (this can be changed with the object’s switch_to_output and switch_to_input methods), so the default works for the purposes of this example. Once instantiated, the button objects have their pull set to DOWN. This means the default signal will be low (i.e., the same as False in Python), which makes sense, since a button’s default state is released: we only want the button to be “on” (or True in Python) if it is pressed. To get the state of the button, one simply reads the value attribute.

With this in mind, the example script sets the clockwise flag depending on which button has been pressed.

The Circuit Playground Express also has a switch that’s similar to a button insofar as you move it with your fingers. However, a switch remains in the state to which you set it, rather than reverting to a default state once you release it. The switch on the Circuit Playground Express can be in one of two states just like the clockwise flag in the original script. As a result, we could re-write the example to use the switch as follows:

import neopixel
import time
import digitalio
from board import NEOPIXEL, SLIDE_SWITCH


np = neopixel.NeoPixel(NEOPIXEL, 10, auto_write=False)
switch = digitalio.DigitalInOut(SLIDE_SWITCH)
switch.pull = digitalio.Pull.UP


while True:
    time.sleep(0.05)
    for i in range(10):
        if switch.value:
            i = 9 - i
        for j in range(10):
            np[j] = tuple((max(0, val - 64) for val in np[j]))
        np[i] = (0, 0, 254)
        np.write()

I will leave it as an exercise for the reader to work out how it works, although everything you need to understand the script was explained when describing the button-based version.

The PyBoard also has a button labelled USR (in addition to the reset button) and takes a slightly different approach to the micro:bit and Circuit Playground Express. For a start, and rather confusingly, it calls the button a switch. As a result, it’s controlled via a Switch object:

import pyb

led = pyb.LED(1)
sw = pyb.Switch()
while True:
    pyb.delay(100)
    if sw():
        led.toggle()

This fragment of code is very close to how we treated buttons on the micro:bit and Circuit Playground Express (although the implementation details are different because we are using a version of MicroPython for a different device). As before, there is an event loop and delay. The Boolean value of the switch is determined by calling the sw object that represents the switch. From the user’s point of view, if you press the button, it toggles the PyBoard’s red LED on and off.

However, MicroPython on the PyBoard provides an interesting alternative way to interact with buttons through the use of a callback. A callback is a function that’s called when a certain event happens (such as a button press). To identify when a certain event has occurred, MicroPython uses an interrupt. An interrupt is simply a signal that something needs immediate action. In this case, MicroPython sets up an interrupt trigger on the pin to which the switch is connected.

When the button is pressed, the pin changes state from low to high, causing the microcontroller to register the change. It pauses what it’s doing by saving its current state and calls the interrupt handler associated with the button. The interrupt handler executes the callback function, and the microcontroller is notified that the interruption has been handled. At this point, the microcontroller restores its pre-interrupt state and continues as before. The code that was running doesn’t notice that it was interrupted.

Using this interrupt/callback method, we can simplify the LED toggling code:

import pyb


sw = pyb.Switch()

def my_callback():
    pyb.LED(1).toggle()

sw.callback(my_callback)

When the button is pressed, the my_callback function is called. This will interrupt any other code running at that moment in time. In case you were wondering, if more than one interrupt fires at the same time, then the one with the highest (pre-ordained) priority takes precedence, followed by any others in order of their priority. The interrupt for the button is set at the lowest priority.

To clear a callback, simply set it to None like this: sw.callback(None).

There’s one more type of finger-related interaction you can perform with the micro:bit and Circuit Playground Express: capacitative touch. Because the human body has quite a large capacitance (i.e., the ability to store electric charge), it’s possible to detect a change in the capacitance of the pin and whatever is connected to it. If you are touching a pin, you are connected to it, and it’s possible to detect differences due to the capacitance of your body.

This is only possible on the micro:bit with the large pins labelled, 0, 1, and 2. On the Circuit Playground Express, all the non-power or ground pins can detect capacitative touch.

As one might expect on the micro:bit, detecting touch is very simple:

from microbit import display, Image, pin0


whie True:
    display.show(Image.ASLEEP)
    if pin0.is_touched():
        display.show(Image.HAPPY)

The is_touched method returns a Boolean to indicate if it’s being touched. In the previous example, if pin 0 is touched, the sleeping face shown on the display is changed to a happy face. The Circuit Playground Express is only a little more complicated, but the effect is far more interesting:

import neopixel
import touchio
import digitalio
from board import *

# Stops the speaker crackling when touched.
spkr = digitalio.DigitalInOut(SPEAKER_ENABLE)
spkr.switch_to_output()
spkr.value = False


np = neopixel.NeoPixel(NEOPIXEL, 10, auto_write=False)
touch_a1 = touchio.TouchIn(A1)
touch_a3 = touchio.TouchIn(A3)
touch_a4 = touchio.TouchIn(A4)
touch_a6 = touchio.TouchIn(A6)


while True:
    if touch_a4.value:
        np[0] = (255, 0, 0)
        np[1] = (255, 0, 0)
    if touch_a6.value:
        np[3] = (0, 255, 0)
        np[4] = (0, 255, 0)
    if touch_a1.value:
        np[5] = (255, 255, 0)
        np[6] = (255, 255, 0)
    if touch_a3.value:
        np[8] = (0, 0, 255)
        np[9] = (0, 0, 255)
    for j in range(10):
        np[j] = tuple((max(0, val - 32) for val in np[j]))
    np.write()

The end result is differently coloured NeoPixels light up if an adjacent pin is touched. To make the effect feel more “alive”, the NeoPixels gradually dim when you stop touching. As with the buttons on the Circuit Playground Express, you have to instantiate an object to represent the pin in the right sort of a way—in this case, we use the TouchIn class found in the touchio module. As is always the case, you need to pass in a reference to the physical pin via an object imported from the board module (in this case, the objects, A1, A3, and so on). Inside the event loop are some conditionals to check if the pin objects are registering high. If they are, the appropriately close NeoPixels are lit.

Pin A0 is also attached to the speaker. If touched (and this is likely in this example), it will cause the speaker to crackle. This behaviour is undesirable, so towards the start of the code a DigitalInOut object is created with reference to the SPEAKER_ENABLE pin. Setting the output of the resulting spkr object to False turns off the speaker to solve the crackling speaker problem.

This example reminds me of the classic electronic game Simon Says. How could you create a capacitive touch version with the Circuit Playground Express?

If the LCD screen is attached to the PyBoard, it can also be used to respond to touch. The most useful methods are is_touched, which returns a Boolean to indicate if the screen is currently touched, and get_touch, which returns a tuple of three values representing active (i.e., the screen is currently being touched), X and Y (the coordinates of the touch). With only a few lines of code, it is possible to create a simple finger-painting program:

import lcd160cr

lcd = lcd160cr.LCD160CR('X')
lcd.erase()
while True:
    a, x, y = lcd.get_touch()
    if a:
        lcd.set_pixel(x, y, lcd.rgb(255, 255, 255))

While it’s hard to be accurate when painting on such a small device, and you’re only limited to black and white (how would you improve the script?), I believe the results are quite impressive for only seven lines of code (see Figure 8-1).

A portrait of the 45th President of the United States
Figure 8-1. A presidential portrait, but of which president?

Accelerometers, Gestures, and Compasses

When you use a modern mobile phone, it is able to detect how it is oriented, the direction it is pointing, and sometimes how to react to gestures (such as a shake to cancel a certain operation). This is remarkably useful as a means of user interaction. For example, as you rotate your phone, the display is flipped from portrait to landscape mode. Certain gestures may also indicate certain states, such as the aforementioned shaking or placing the phone face down to turn off audible alerts. Finally, if the phone can detect its heading, this information can be used in conjunction with GPS signals in a mapping application to give you directions or, on its own, so your phone becomes a compass.

Such attributes rely on readings from relatively simple components: the accelerometer and magnetometer (compass).

An accelerometer is an electromechanical device consisting of a mass on a spring. As the mass moves in a certain direction due to force of gravity or an acceleration,1 the capacitance changes between the moving mass and a fixed plate, allowing the mechanical movement of the mass to be represented by changes in electrical current. As with the touch-related interactions described, we use capacitance to measure things, be it the capacitive properties of the human body (for touching pins) or the movement of a mass adjacent to a fixed plate to measure gravity.

The accelerometers used in the PyBoard, micro:bit, and Circuit Playground Express actually consist of three seperate sensors to detect gravitational force along three perpendicular axes called X (left to right), Y (forwards and backwards), and Z (up and down).

In contrast, a magnetometer measures magnetic fields (a compass is a very simple example of this sort of device). In the case of the magnetometer on the micro:bit, it uses a miniature Hall-effect sensor that detects the Earth’s magnetic field along the same X, Y, and Z axes as the accelerometer. The sensor produces voltage proportional to the strength and polarity of the magnetic field along each axis. This, in turn, is converted to digital signals representing the magnetic field intensity along each axis. By calibrating the compass and taking measurements along these axes, it is possible to determine a heading (turning the micro:bit into a compass). Alternatively, such measurements can be used as a very basic metal detector.

The APIs for the accelerometers on the PyBoard, micro:bit, and Circuit Playground Express are very similar: you get back measurements for the X, Y, and Z axes. For example, the following fragment of code for the micro:bit uses the accelerometer to steer a pixel with a glowing tail around the display (moving pixels around on the screen like this is a fundamental feature of many games on the device):

from microbit import *

x = 2
y = 2
sensitivity = 50
pause = 90
fade = 2

while True:
    roll = accelerometer.get_x()
    yaw = accelerometer.get_y()
    if roll < -sensitivity:
        x = max(0, x - 1)
    elif roll > sensitivity:
        x = min(4, x + 1)
    if yaw < -sensitivity:
        y = max(0, y - 1)
    elif yaw > sensitivity:
        y = min(4, y + 1)
    for i in range(5):
        for j in range(5):
            brightness = max(0, display.get_pixel(i, j) - fade)
            display.set_pixel(i, j, brightness)
    display.set_pixel(x, y, 9)
    sleep(pause)

The important lines are where we call accelerometer.get_x and accelerometer.get_y. There is also a notion of sensitivity such that movement won’t register in a direction until some threshold is reached (in the example, this is given the arbitrary value 50, arrived at through experimentation). This is because the accelerometer is very sensitive, and humans do not have such fine-grained motor skills to work with such sensitive devices. The threshold means we have to tip the device in one direction or another more than enough for us humans to be able to register the difference. When some aspect of the hardware is used to interact with humans, there often needs to be some sort dampening of sensitivity via thresholds or bucketing values so the device is both manageable and usable.

In the case of an accelerometer, such interactions are reminiscent of the Wii remote and other similar peripherals for console games used to control player characters and other game-related assets. The tilting and movement of an accelerometer is a useful metaphor for controlling an aspect of some other thing (such as the position of a pixel). An accelerometer is also useful if you need to log the forces applied to an object, such as a model rocket.

Another use of the accelerometer is to detect gestures, such as shake, freefall, or face up. Such gestures are useful from a user interaction point of view since they can also represent states or instructions. For example, shake may mean something negative like “cancel and restart the game”; freefall is probably an indication that the device is in the process of being dropped, so prepare for a crash landing; and face up may just mean display something (since you look down upon the display of the micro:bit) in a similar manner to the way mobile phones activate their screens when you take them away from your ear to look at them.

Currently, only the micro:bit has built-in support for gestures. The following example demonstrates how they can be used:

from microbit import *

while True:
    if accelerometer.was_gesture('shake'):
        display.show(Image.ANGRY)
    elif accelerometer.was_gesture('face up'):
        display.show(Image.ASLEEP)
    elif accelerometer.was_gesture('up'):
        display.show(Image.HAPPY)
    sleep(100)

If the device is shaken, it displays an angry face; if it is flat but face up, it appears asleep; and if it is held upright, it’s happy to see you. The micro:bit can recognise the following list of gestures: up, down, left, right, face up, face down, freefall, 3g, 6g, 8g, and shake.

The micro:bit is the only device with an onboard magnetometer. It’s not very accurate and requires calibration before use. Calibration is achieved via the compass.calibrate method, which causes the device to wait until you’ve drawn a blocky circle on the display by rotating the device to move a pixel around the screen. Once calibrated, the micro:bit is able to report a heading with 0 as “north” (or some other strong magnetic field). Here’s how to turn the device into a compass that displays where the micro:bit thinks north is:

from microbit import *

compass.calibrate()
while True:
    sleep(100)
    needle = ((15 - compass.heading()) // 30) % 12
    display.show(Image.ALL_CLOCKS[needle])

Sound, Light, and Temperature

Sound, light, and temperature-sensing roughly translate to the human senses of hearing, sight, and touch (although, such sensing is nowhere near as good as the human equivalent). The Circuit Playground Express has such sensors built in.

Sound sensing is done with a microphone, a device that turns sound waves into electrical signals. In the case of the part on the Circuit Playground Express, it’s another small electromechanical device that works in a way that is similar to the accelerometer: there’s a membrane etched into the device that vibrates in response to the changes in air pressure caused by sound waves. As the membrane vibrates, the capacitance between the membrane and a fixed plate changes, allowing the mechanical movement of the vibrating membrane to become changes in electrical current and eventually a digital signal.

Light sensing is essentially an LED in reverse. If you remember, when an electrical current is is applied to an LED, an electroluminescent semiconductor emits light. However, if light is shone onto an LED, an electrical current will flow through the LED but in the opposite direction to the flow when the LED emits light. By measuring this current we can use an LED to detect light. This is how light detection works on the micro:bit. The light-detecting capabilities of the Circuit Playground Express are provided by a phototransistor built into the board. A phototransistor is a component that specialises in detecting light (rather than re-using the characteristics of LEDs) and, just like the reverse LED trick, turns light energy into electrical current.

Temperature sensing is done with a thermistor, a component whose resistance changes with temperature. This is how the temperature sensor on the Circuit Playground Express works. However, many chips, including the microcontrollers that run MicroPython, have a minute on-chip thermal diode that’s used to monitor the temperature of the chip. A thermal diode changes voltage across it according to temperature: as the temperature increases, the voltage decreases. This change is used to measure the temperature of the chip, and it is how temperature is read on the micro:bit.

Given the physical properties of such sensors, how can we use them?

Warning

The following example of how to use the microphone on the Circuit Playground Express makes use of an API that is not yet available at the time of publication.

However, given the expected speed of development, chances are the API will be in the latest version of CircuitPython very soon (look for a version 2.0 or greater).

While the following example demonstrates the basics, the final version will also include more capabilities, such as streaming audio data onto the filesystem, so you’ll be able to record much longer fragments of sound.

The simplest way to use the onboard microphone of the Circuit Playground Express is to record short snippets of audio into a buffer. The following example is a magic echo machine. When the device starts, the NeoPixels around the edge of the board sequentially light up to indicate a sort of countdown. When the microphone is listening, all the NeoPixels are at a full green brightness. You have about a second’s worth of time. The NeoPixels switch off, and the device plays back what it recorded via the onboard speaker. If you hold down button A during this process, the device will play back the recording at “chipmunk” speed. Holding down button B has the opposite effect: the audio is slowed down and lowered to mimic Barry White. Of course, if you don’t press any of the buttons, you’ll hear the audio played back at the correct pitch.

import neopixel
import audiobusio
import digitalio
import audioio
import time
from board import *


def countdown(np):
    """ Uses the NeoPixels to display a countdown."""
    # Start from an "off" state.
    np.fill((0, 0, 0))
    np.write()
    for i in range(10):
        np[i] = (0, 20, 0)
        np.write()
        time.sleep(0.5)
    np.fill((0, 128, 0))
    np.write()


def record():
    """ Returns a buffer of recorded sound."""
    buf = bytearray(8000)      
    with audiobusio.PDMIn(MICROPHONE_CLOCK, MICROPHONE_DATA) as mic:
        mic.record(buf, len(buf))
    return buf


def play(buf, freq):
    """
    Play the referenced buffer of recorded sound at a certain
    frequency.
    """
    # Set the speaker ready for output.
    speaker_enable = digitalio.DigitalInOut(SPEAKER_ENABLE)
    speaker_enable.switch_to_output(value = True)
    # Play the audio buffer through the speaker.
    with audioio.AudioOut(SPEAKER, buf) as speaker:
        speaker.frequency = freq
        speaker.play()
        # Block while the speaker is playing.
        while speaker.playing:
            pass


neopixels = neopixel.NeoPixel(NEOPIXEL, 10, auto_write=False)
button_a = digitalio.DigitalInOut(BUTTON_A)
button_a.pull = digitalio.Pull.DOWN
button_b = digitalio.DigitalInOut(BUTTON_B)
button_b.pull = digitalio.Pull.DOWN


countdown(neopixels)
audio_buffer = record()
neopixels.fill((0, 0, 0))
neopixels.write()

freq = 8000  # Default = normal speed.
if button_a.value:
    freq = 12000  # Button A = chipmunk. 
elif button_b.value:
    freq = 6000  # Button B = Barry White.

play(audio_buffer, freq)

The block of code of interest to us is in the record function. A bytearray buffer is created and used by an instance of the PDMIn class found within the audiobusio module. The class is instantiated with references to the microphone clock and data pins needed to perform any recording. The “PDM” in PDMIn is pulse density modulation, a method of representing an analog signal with binary (on/off) data. The relative density of pulses in the binary data corresponds to the analog signal’s amplitude. Put (very) simply, a higher density of “on” values occurs at the peaks of a wave, whereas a lower density occurs in the troughs. In any case, the resulting mic class has a record method that fills the buffer buf with bytes representing recorded sound measured using pulse density modulation. At the end of the function, the buffer is returned for further processing.

The record function, used in concert with the countdown and play functions, turns the Circuit Playground Express into a silly sound-based toy. (We’ll cover the speaker on the Circuit Playground Express in some detail in Chapter 11.)

From a programmatic point of view, the light sensor is used in a different way to the microphone: it is an analog input pin whose value relates to the amount of light detected by the physical sensor. The higher the number, the more light is detected. The same is true of the temperature sensor, with the resulting numbers reflecting changes in the temperature. The following REPL session demonstrates:

>>> import analogio 
>>> from board import * 
>>> light = analogio.AnalogIn(LIGHT)
>>> light.value  # in a dark place
152
>>> light.value  # held up at daylight
12037
>>> temp = analogio.AnalogIn(TEMPERATURE)
>>> temp.value  # ambient room temperature
30075
>>> temp.value  # after blowing warm air on the sensor
36405
>>> temp.value  # waiting a few seconds for it to cool down
34208

You are probably wondering how such raw analog readings from a pin can be turned into something useful. While such cute REPL-based demonstrations illustrate a point, they are not that useful in terms of getting a reading expressed in a meaningful unit of measurement. For this to happen, you will need to use the libraries currently in development created by Adafruit. Development is ongoing and fast moving (hence my reticence to write about them at this moment in time since they are likely to change); however, using them is as simple as downloading the latest release and copying the modules over to the flash-based filesystem of the Circuit Playground Express.

A quick example will suffice to demonstrate such libraries in action. Here’s how to get the current temperature in degrees Celsius from the Circuit Playground’s thermistor:

>>> import adafruit_thermistor
>>> import board
>>> thermistor = adafruit_thermistor.Thermistor(board.TEMPERATURE, 10000, 10000, 
                                                25, 3950)
>>> thermistor.temperature
26.60413

The arguments used by the Thermistor class relate to settings dependent on the model of thermistor in use. The ones used in the preceding example are correct for the Circuit Playground Express.

In the case of the micro:bit, the temperature is expressed in degrees Celcius. It represents the current temperature of the microcontroller rather than the ambient temperature:

>>> from microbit import temperature
>>> temperature()
24

Sensing with Peripherals

While the micro:bit, Circuit Playground Express and, to a lesser extent, PyBoard have inputs and sensors built into the boards, it is possible to connect such peripherals to the GPIO pins of any of the devices running MicroPython. The important thing to remember is that you access the device in exactly the way that has been demonstrated throughout this chapter: via pins. In the case of external peripherals, they will use the externally available pins rather than “pins” directly attached to built-in components attached to the board.

A very simple example using ESP8266-based boards (as yet, unused in this chapter) will be sufficient to demonstrate the general principal (see Figure 8-2).

Simple sensing with two wires
Figure 8-2. Simple sensing with two wires

Very carefully connect wires to the GND and pin labelled D5 on the physical board. We’re going to simulate a simple digital signal that could be created by an external button. The following code illuminates the onboard LED every time you touch the wires together:

from machine import Pin

led = Pin(2, Pin.OUT)
button = Pin(14, Pin.IN, Pin.PULL_UP)
while True:
    led.value(button.value())

Both the led and button objects are instances of the Pin class, which is instantiated with a pin number and an indication of whether it is to be used for output (as the led is) or input (as the button is). Since we need to take a reading from the button, we ensure that it’s not in a floating state by passing in a third PULL_UP argument. Finally, the value set for the LED is whatever the value of the button is read to be. As the wires touch, changing the value of the input into the button pin, so the output is changed to the led pin.

Given such playful experiments with both visual output, inputs, and sensors, we are in a good position to take a detailed look at not only how the GPIO pins work but also at the various protocols you might use to communicate with attached devices.

1 Einstein famously showed that these two effects are equivalent.

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

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