Detecting a Button Press

Created by Franck Albinet with source available here. Also presented by NSRC at APRICOT 2018.

Button: Detecting Pysense board button pressure

Introduction

In this example, we will detect the pressure of the button on the Pysense board. Because the board does not have a keyboard (or mouse), we will use the USB connection between the board and the development PC to detect the pressure of the button.

The Pysense board has one button as shown at the bottom in the picture below.

Learning outcomes

You will learn how to: detect the pressure of the button; implement Polling and Interrupts techniques * tackle iteratively a coding "challenge" (from naive but functioning programs to implementations fostering reusability and modularity)

Required Components

For this example you will need:

  • a LoPy module plugged into a Pysense board
  • a microUSB cable
  • a development PC

The source code is in the src/button directory. > Make sure you press the button on the Pysense board (highlighted by a red rectangle in image above) and not the one on the LoPy module (reset button).

boot.py

boot.py
from machine import UART
import os
uart = UART(0, 115200)
os.dupterm(uart)

The boot.py file should always start with the above code, so we can run our python scripts over Serial or Telnet. Newer Pycom boards have this code already in the boot.py file.

Polling button state

Even for simple program like this one, there are numerous possible implementations. We should aim for a straighforward solution first. Premature optimization or abstraction are common pitfalls.

The use case we want to address is the following: 1. when the button of the Pysense board is pressed, display a message "Button pressed" once in Atom's console; 2. when released, display a message "Button released" once.

main.py

main.py
from machine import Pin
import time

button = Pin("P14", mode=Pin.IN, pull=Pin.PULL_UP)

is_pressed = False

while True:
    if button() == 1 and not is_pressed:
        time.sleep(1)
    elif button() == 0 and not is_pressed:
        print("Button pressed")
        is_pressed = True
    elif button() == 1 and is_pressed:
        print("Button released")
        is_pressed = False
    else:
        pass

Let's go through the snippet code above:

# we import the Pin `class` from machine Pycom modules
from machine import Pin

# then we create a `pin` object
button = Pin("P14", mode=Pin.IN, pull=Pin.PULL_UP)

You notice that to create a Pin object we need to specify three arguments:

  1. pin's id "P14" - see Pysense board manual: https://docs.pycom.io/chapter/firmwareapi/pycom/machine/Pin.html
  2. pin's mode Pin.IN specifying that this is an input
  3. pull method Pin.PULL_UP specifying we want a pull-up resistor. Loosely speaking, a pull-up or pull-down resistor will ensure that the pin is in either a high or low state, while also using a low amount of current and as a result prevents unknown state of the input. You can consult this blog post for further information https://learn.sparkfun.com/tutorials/pull-up-resistors.

Now that we have a button object, let's read repeatedly the state of the input. In our case when not pressed the button's value button() should be 1 and 0 when pressed (pull-up resistor).

# flag saving previous state of the button
is_pressed = False

# we keep reading the input value until the program is terminated

while True:
	# we do nothing until the input value is changed for the first time
    if button() == 1 and not is_pressed:
        time.sleep(1)
	# when pressed and was not previously pressed
    elif button() == 0 and not is_pressed:
        print("Button pressed")
        is_pressed = True
	# if not pressed and was pressed previously
    elif button() == 1 and is_pressed:
        print("Button released")
        is_pressed = False
    else:
        pass

To read the value/state of the button, simply call button().

The flag is_pressed allows to display messages once otherwise the message would be printed as long as you press or release the button which is not the expected use case.

This is important to note that in the implementation above, we keep reading the state of the button potentially every 100μs given that the clock rate is 160MHz. This is most probably a waste of CPU time and energy (remember that low consumption is key in IoT world) given that button pressure might not be a so much frequent event and that we might not require a response time of micro seconds.

A first slight improvement could be to pause the execution for half a second for instance: time.sleep_ms(500) (insert it somewhere in the while loop into main.py).

Exercises

  1. Toggle the LED each time the button is pressed. If the LED is OFF, turn it ON by pressing the button. If the LED is ON, turn it OFF by pressing the button.

  2. Increase a counter every time the button is pressed and visualize it.

  3. Turn the red light on whenever the button is pressed for more than 3 seconds.