Buttons are a ubiquitous user interface - of course, you've seen them everywhere! The humble button is often the fastest way to create a control interface for your project too. This guide will help you get started with a PiicoDev® Button - an intuitive input device that allows you to easily interact with your project. We'll walk through some examples to read from the Button and remix the example code to do something cool with our development board.
To get started - select your dev. board below.
Hardware Connections
To follow along you'll need:
- A Raspberry Pi Pico or Pico W (with Soldered Headers)
- A PiicoDev Button
- A PiicoDev Expansion Board for Raspberry Pi Pico (or recharging LiPo Expansion Board)
- A PiicoDev Cable
- (Optional) A PiicoDev Platform will keep everything mounted securely.
Plug your Pico into the Expansion Board. Make sure it is installed in the correct orientation - Pin 0 on the expansion board should be adjacent to the Pico's USB connector.
Connect your PiicoDev Button to the expansion board with a PiicoDev Cable.
To follow along you'll need:
- A Raspberry Pi single board computer (Pictured: Raspberry Pi 4 Model B)
- A PiicoDev Button
- A PiicoDev Adapter for Raspberry Pi
- A PiicoDev Cable (100mm or longer is best for Raspberry Pi)
- (Optional) A PiicoDev Platform will keep everything mounted securely.
Mount the Adapter onto your Pi's GPIO. Make sure it is plugged in the correct orientation - An arrow on the Adapter will point to the Pi's Ethernet connector (on a Pi 3 the Ethernet connector and USB ports are swapped.)
Connect your PiicoDev Button to the Adapter with a PiicoDev Cable.
To follow along you'll need:
- A micro:bit v2
- APiicoDev Button
- A PiicoDev Adapter for micro:bit
- A PiicoDev Cable
- (Optional) A PiicoDev Platform will keep everything mounted securely.
Plug your micro:bit into the Adapter, making sure the buttons on the micro:bit are facing up.
Connect your PiicoDev Button to the Adapter with a PiicoDev Cable.
The PiicoDev Button features four ID switches - ensure all the switches are OFF before proceeding. This will use the default device address.
Setup Thonny
Download / Install PiicoDev Modules
To work with PiicoDev hardware, we need to download some drivers. The drivers provide all the functions to easily connect and communicate with PiicoDev hardware. Select your dev board from the options above.
We will need these files to easily drive the PiicoDev Button:
- Save the following files to your preferred coding directory - In this tutorial, we save to My Documents > PiicoDev.
- Download the PiicoDev Unified Library: PiicoDev_Unified.py (right-click, "save link as").
- Download the device module: PiicoDev_Switch.py (right-click, "save link as"). This module is named "Switch" instead of "Button" because it could be used for other switching components too - not just buttons.
- Upload the files to your Pico. This process was covered in the Setup Thonny section.
The PiicoDev Unified Library is responsible for communicating with PiicoDev hardware, and the device module contains functions for driving specific PiicoDev devices.
We will need these files to easily drive the PiicoDev Button:
- Save the following files to your preferred coding directory - In this tutorial, we save to My Documents > PiicoDev.
- Download the PiicoDev Unified Library: PiicoDev_Unified.py (right-click, "save link as").
- Download the device module: PiicoDev_Switch.py (right-click, "save link as"). This module is named "Switch" instead of "Button" because it could be used for other switching components too - not just buttons.
- Upload the files to your Pico. This process was covered in the Setup Thonny section.
The PiicoDev Unified Library is responsible for communicating with PiicoDev hardware, and the device module contains functions for driving specific PiicoDev devices.
Check if the Button was / is pressed
We are ready to begin reading the state of the PiicoDev Button using two simple properties: .was_pressed and .is_pressed. Run the following example code to read the button using the .was_pressed property and observe the output. The program prints a zero to the shell unless a press is detected, then it prints a 1. Notice that holding the button down has no effect on the shell output?
# Check if a PiicoDev Button was pressed from PiicoDev_Switch import PiicoDev_Switch # Switch may be used for other types of PiicoDev Switch devices from PiicoDev_Unified import sleep_ms button = PiicoDev_Switch() # Initialise the module while True: if button.was_pressed: # was the button pressed since the last time we checked? print(1) # print a result that can be plotted else: print(0) sleep_ms(100)
.was_pressed only returns True if the button was pressed since the last time we checked. The program will print a 1 only on the first time we press the button - holding the button down has no effect.
Remix Idea: now change .was_pressed to .is_pressed which returns True if the button is being pressed right now. You ought to observe a different result when the Button is held down. The following figure summarises the behaviour of .was_pressed and .is_pressed.
Code Remix
Remix: Flash Rate Selector
Let's control something real now! This remix flashes the Pico's onboard LED. Pressing the button selects a new flash rate (from a list of delays). As we click the button the flashing becomes slower, until we hit the end of the list and wrap back to the start. This is a practical use for the .was_pressed property - even when a delay is quite long, the button press is still captured.
# Use a PiicoDev Button to control the flash-rate of RPi Pico's onboard LED from machine import Pin from PiicoDev_Switch import PiicoDev_Switch # Switch may be used for other types of PiicoDev Switch devices from PiicoDev_Unified import sleep_ms button = PiicoDev_Switch() # Initialise the module led = Pin('LED', Pin.OUT) # Initialise the onboard LED index = 0 delays = [100, 200, 500, 1000] # a pool of delay values while True: if button.was_pressed: # change the flash rate by index += 1 # incrementing the index index = index % len(delays) # wrap the index back to zero when it goes out of bounds print("index: ",index) led.toggle() delay = delays[index] # select the current delay duration sleep_ms(delay)
Remix: Run a program at the touch of a button!
Let's use the PiicoDev Button to trigger a system event. We can use the subprocess module to execute commands like we would in a terminal. This example will launch a new instance of the File Manager every time the button is pressed. The run() command accepts commands as a list, with the first item being the command (pcmanfm), and following items being the arguments (the directory to launch to manager at, /home/pi).
Read more about subprocess in the subprocess docs.
# Use a PiicoDev Button to launch a program on the Raspberry Pi # This can be used to launch any program or run a script. from PiicoDev_Switch import PiicoDev_Switch from PiicoDev_Unified import sleep_ms import subprocess # subprocess is used for executing shell commands button = PiicoDev_Switch() # Initialise the module while True: if button.was_pressed: # execute a command (open the File Manager at the user's home directory) p = subprocess.run(["pcmanfm", "/home/pi"]) sleep_ms(100)
It should be noted this example assumes the current user is called pi. If you're using a different username, you'll have to change this directory as appropriate.
Remix: Scroll through some images.
Let's control something real now! This remix flashes the Pico's onboard LED. Pressing the button selects a new flash rate (from a list of delays). As we click the button the flashing becomes slower, until we hit the end of the list and wrap back to the start. This is a practical use for the .was_pressed property - even when a delay is quite long, the button press is still captured.
# Use a PiicoDev Button to scroll images on the micro:bit display from microbit import display, Image from PiicoDev_Switch import PiicoDev_Switch from PiicoDev_Unified import sleep_ms button = PiicoDev_Switch() # Initialise the module index = 0 images = [Image.HEART, Image.HAPPY, Image.SAD] # a pool of images to display while True: if button.was_pressed: # change the image by index += 1 # incrementing the index if index >= len(images): # wrap the index back to zero when it goes out of bounds index=0 print("index: ",index) display.show( images[index] ) # Show the selected image sleep_ms(100)
Other Examples
The .press_count property is a counter that returns the number of button presses since the switch was last read. This is useful when polling the switch slowly, where multiple press events may occur between polls.
The following example will keep a running total of button-presses, even though the update rate is very slow (3 seconds). Try clicking the button rapidly and you ought to see the count remains accurate, even with a slow polling rate.
# Get an accurate press-count, even when the sample-rate is very slow (3 seconds) # Try clicking the button multiple times between updates. You ought to see that # every click is accounted for! from PiicoDev_Switch import PiicoDev_Switch # Switch may be used for other types of PiicoDev Switch devices from PiicoDev_Unified import sleep_ms button = PiicoDev_Switch() # Initialise the module total_presses = 0 while True: total_presses += button.press_count # press_count resets to zero after being read print('Total Presses:', total_presses) sleep_ms(3000)
The power-LED on the PiicoDev Button turns on every time power is applied. However, you can still take control of this LED using the .led property. The following example toggles the LED every time the button is clicked. The .led property is both read/write capable: you can use it to read the current state of the LED, and set the desired state.
# Toggle the Button's "Power" LED on every click from PiicoDev_Switch import PiicoDev_Switch from PiicoDev_Unified import sleep_ms button = PiicoDev_Switch() while True: if button.was_pressed: button.led = not button.led sleep_ms(100)
It is possible to connect to more than one PiicoDev Button at the same time. The following example uses two PiicoDev Buttons to create a simple controller. One Button increments a counter while the other decrements. The incrementing-Button is left in the default state (ID switches OFF), and the decrementing-Button will have its ID #1 switch set to ON. Referring to the example code, we can see each Button is initialised with an id argument which encodes the positions of the ID switches (1=ON, 0=OFF). This example also makes use of .press_count to ensure that no clicks are ever missed.
# Use multiple PiicoDev Buttons to increment / decrement a counter from PiicoDev_Switch import PiicoDev_Switch from PiicoDev_Unified import sleep_ms button_up = PiicoDev_Switch(id=[0,0,0,0]) # Initialise the 1st module button_down = PiicoDev_Switch(id=[1,0,0,0]) # Initialise the 2nd module count = 0 while True: count += button_up.press_count count -= button_down.press_count print("Count",count) sleep_ms(1000)
The following table describes the actual I2C addresses that are selected:
The PiicoDev Button can detect double presses too, though a single-press will still be detected for the first press. Detecting double presses is very similar to detecting single presses - use the .was_double_pressed attribute.
The window for a double press (milliseconds) is tunable with the double_press_duration property.
from PiicoDev_Switch import PiicoDev_Switch from PiicoDev_Unified import sleep_ms button = PiicoDev_Switch(double_press_duration=400) while True: print('Double Press:', button.was_double_pressed) sleep_ms(1000)
The PiicoDev Button can detect double presses too, though a single-press will still be detected for the first press. Detecting double presses is very similar to detecting single presses - use the .was_double_pressed attribute.
Up to 16 PiicoDev Buttons may share an I2C bus by selecting unique addresses with the ID switch (see the Multiple Buttons example). For advanced users, the device I2C address may be set explicitly using the .setI2Caddr() method which accepts the new desired address. This allows setting any address in the valid I2C address space (0x08-0x77). The address update is immediate and the device will only respond to the new address for subsequent commands. To use a user-defined software address, the Button must have all ID switches OFF.
# Set a new (software) address from PiicoDev_Switch import PiicoDev_Switch from PiicoDev_Unified import sleep_ms button = PiicoDev_Switch() # Initialise the module new_address = 0x70 print("Setting new address to {}".format(new_address)) button.setI2Caddr(new_address)
To reset the device to defaults, either use the .setI2Caddr() method to set the address back to 0x42 or perform the factory reset procedure:
- Remove power
- Select a hardware address by setting any of the ID switches ON
- Apply power
- Remove power
Now, the device will respond to address 0x42 again when all ID switches are OFF.
The PiicoDev Button features internal debouncing using an EMA filter which is pretuned by default to provide good results. The state of the button is polled rapidly, which updates the value of the moving average - the average increases when the button is closed, and decays when the button is open. A press is valid when the average is greater than some threshold.
The EMA tuning parameter can be read/write with the .ema_parameter property which accepts 0-255 and maps to 0->1.0. The polling period (milliseconds) can be read/write with the .ema_period property.
from PiicoDev_Switch import PiicoDev_Switch from PiicoDev_Unified import sleep_ms button = PiicoDev_Switch(ema_parameter=73, ema_period=30) # Initialise the module with 30ms poll period and EMA parameter = 73.0/255.0 => 0.286
Conclusion
There's more than meets the eye to the PiicoDev Button - a simple input device, yes - but capable and feature-rich. We can read the button status in a couple of ways, measure counts and connect many buttons together for more complex projects. The button really is the perfect, intuitive input device.
If you make something cool using the PiicoDev Button we'd love to see it! Leave a comment below to show us your projects or if you have any questions about this material.
- Happy Making!