Program one or multiple buttons
This tutorial explains how to interact with buttons, including those of the NUCLEO-WB55. This example is also an opportunity to introduce the GPIO, the acronym for General Purpose Input Output. General purpose input-output ports refer to the set of (programmable) functions that can be performed by the microcontroller pins. They allow you to select the circuits internal to the microcontroller that you want to use in order to interact with external as well as connected components such as memories, motors, sensors, etc.
A Few Insights on GPIO
In this case, the buttons SW1, SW2, SW3 (as well as the light emitting diodes LED1, LED2, LED3) of the NUCLEO-WB55 board are connected to pins of the STM32WB55 and require the configuration of the corresponding GPIO, an operation simplified by the MicroPython language. Here we will add some additional information about GPIO in microcontrollers so that the syntax of MicroPython instructions seems less enigmatic to you.
The following figure summarizes the possible GPIO parameters and configurations :

A pin can be configured as input, output or alternative function:
- Configured as input, the pin transmits to the microcontroller a signal from outside. The microcontroller will then analyze this signal, for example read the status (pressed or released) of a button.
- Configured as an output, the pin transmits a out signal from the microcontroller. For example, turn on or off an LED connected to it.
- Configured as an alternative, the pin is connected to an internal device of the microcontroller such as a USB bus, a serial port controller (USART/UART), a timer (for example to generate or analyze PWM signals) or any other advanced function. See this tutorial for an application of the PWM function, among other alternative functions.
Input pins can operate in four modes:
- Floating input: floating input, the level will be determined by the components connected to this input.
- Pull-Down input: input polarized to 0V via an integrated resistance.
- Pull-Up input: input polarized to +3.3V via an integrated resistance.
- Analog input: input connected to an analog-to-digital converter (ADC). This is the default case for pins connected to Arduino connectors A0 to A5.
Output or alternate function pins can work in two modes:
- The Push-Pull mode in which the pin can force a logical state of 0 or 1.
- The Open-Drain mode in which the pin can force a low logical state, or place itself in a high impedance state, is particularly useful for certain communication protocols. In general, an external circuit consisting of a pull-up resistor is used, which will allow the output to be polarized at +VDC.
First example: turn on or off a LED with a button
The following example shows how to configure the GPIO to control a LED with a button. This code is an example of programming by polling: an infinite loop occupies the microprocessor at 100% and checks or modifies at very high frequency the state of GPIO.
Required Tools
- NUCLEO-WB55 board
- A Grove Base Shield
- A Grove LED module
- A Grove Button module

Image credit : Seeed Studio
Connect the LED module to D2 and the button module to D4.
Attention Reminder that LEDs are polarized; if you plug them incorrectly, you will probably destroy them. The longest leg of the LED you will use will need to be inserted into the “+” terminal of the Grove module and the shortest leg in its “-“ terminal.
MicroPython code
The following scripts are available in the download area.
Edit the main.py script contained in the NUCLEO-WB55 PYBFLASH virtual USB drive directory:
# Object of the script: Turn on a LED by holding down a button.
# The button is managed in "polling": an infinite loop
# monitors the status of the pin to which it is connected.
# Hardware required in addition to NUCLEO-WB55: one button connected to the
# D4 pin and one LED connected to D2 pin.
from machine import Pin # Pour gérer les GPIO
# Configure the input button (IN) on the D4 pin.
# The chosen mode is PULL UP: the potential of D4 is forced to +3.3V
# when the button is not pressed.
bouton_in = Pin('D4', Pin.IN, Pin.PULL_UP)
# Set the LED on push-pull output (OUT_PP) on the D2 pin.
# The chosen mode is PULL NONE: the potential of D2 is not fixed.
led_out = Pin('D2', Pin.OUT_PP, Pin.PULL_NONE) # LED pin
ancien_niveau_bouton = 1
while True : # Boucle sans clause de sortie ("infinie")
# If the button is pressed, button_in.value() = 1 => the LED lights up
# If the button is released, button_in.value() = 0 => the LED goes out
niveau_bouton = bouton_in.value()
if niveau_bouton != ancien_niveau_bouton:
print("Niveau logique du bouton :", niveau_bouton)
ancien_niveau_bouton = niveau_bouton
led_out.value(niveau_bouton)
The following drawings summarize what this program does:
When the button is released, the potential of pin D4 is +3.3V (Pull-Up) and bouton_in is set to 0. A pin of the LED is connected to the ground, thus to the 0V potential and the other pin, D4, is controlled by the STM32WB55. It is also at 0V potential because of the led_out.value(0). The STM32WB55 (CPU) microprocessor is 100% busy to run the while True loop and its instructions.

When the button is pressed, the potential of pin D4 changes to 0V and bouton_in is set to 1. Note: the value and potential are not linked, the function bouton_in.value() always returns “1” when the button is pressed. The led_out.value(1) instruction changes the potential of pin D2 to +3.3V and the LED, which sees a non-zero potential difference at its terminals, lights up. The STM32WB55 (CPU) microprocessor is 100% busy running the while True loop and its instructions.

Second example: managing the three buttons of the NUCLEO-WB55
We will see in this subpart how to initialize a pin in “Enter” mode and display a message when pressing one of the 3 buttons using pyb. Pin.
We will use the “polling” method to ask the MicroPython system for the status of the pin (1 or 0).
For reasons of electronic design, the state of the pin at rest, released button, is “1” (potential fixed at +3.3V, because pin configured as pull-up) while the state when pressing button is “0” (potential fixed at 0V). This item is then addressed (section Class Signal)
Required Tools
The NUCLEO-WB55 board and its integrated buttons SW1, SW2 and SW3:

MicroPython code
The following scripts are available in the download area.
Edit the main.py script contained in the NUCLEO-WB55 PYBFLASH virtual USB drive directory:
# Purpose of the script:
# Example of GPIO configuration for managing NUCLEO-WB55 buttons
from machine import Pin # Pin control
from time import sleep_ms # To take system breaks
print( "Les GPIO avec MicroPython c'est facile" )
# Initialization of input pins for buttons (SW1, SW2, SW3)
# The potential of the pins will be +3.3V when the buttons are released (Pull up)
# The potential of the pins will be at 0V when the buttons are pressed
# The parameter 'af = -1' means that we do not want to assign an alternative function to the pin.
sw1 = Pin('SW1' , Pin.IN)
sw1.init(Pin.IN, Pin.PULL_UP, af=-1)
sw2 = Pin('SW2' , Pin.IN)
sw2.init(Pin.IN, Pin.PULL_UP, af=-1)
sw3 = Pin('SW3' , Pin.IN)
sw3.init(Pin.IN, Pin.PULL_UP, af=-1)
# Initialization of variables
ancienne_valeur_sw1 = 0
ancienne_valeur_sw2 = 0
ancienne_valeur_sw3 = 0
while True: # Loop without output clause ("infinite")
# 300ms Timeout
sleep_ms(300)
#Retrieving state of buttons 1,2 and 3
valeur_sw1 = sw1.value()
valeur_sw2 = sw2.value()
valeur_sw3 = sw3.value()
#Is the current state different from the previous state?
if valeur_sw1 != ancienne_valeur_sw1:
if valeur_sw1 == 0:
print( "Button 1 (SW1) is pressed" )
else :
print( "Button 1 (SW1) is released" )
ancienne_valeur_sw1 = valeur_sw1
if valeur_sw2 != ancienne_valeur_sw2:
if valeur_sw2 == 0:
print( "Button 2 (SW2) is pressed" )
else :
print( "Button 2 (SW2) is released" )
ancienne_valeur_sw2 = valeur_sw2
if valeur_sw3 != ancienne_valeur_sw3:
if valeur_sw3 == 0:
print( "Button 3 (SW3) is pressed" )
else :
print( "Button 3 (SW3) is released" )
ancienne_valeur_sw3 = valeur_sw3
Save the changes with (CTRL + S on Notepad++). You can start the script with Ctrl + D on the PuTTY terminal and observe the messages it returns when you press the different buttons:

Third example : The Signal class
You might find it disconcerting that the logic of the button is reversed. The low level of the pin corresponds to the pressed button, so we get a signal at “0” in this case. Perhaps you would prefer that a low voltage level on the pin corresponds to a “1” value returned by the button (and vice versa)?
MicroPython offers a solution to this problem (which I find unnecessarily complicated!) with the Signal class. The following script uses the Signal class (b1, b2, b3 instances) to reverse the signal of the pins (sw1, sw2 and sw3 instances):
# Object of the script:
# Example of GPIO configuration for managing NUCLEO-WB55 buttons
# Logical state inversion using the Signal class.
from machine import Pin, Signal # Pin control
from time import sleep_ms # To take system breaks
print( "GPIO with MicroPython is easy" )
# Initialization of input pins for buttons (SW1, SW2, SW3)
# The potential of the pins will be +3.3V when the buttons are released (Pull up)
# The potential of the pins will be at 0V when the buttons are pressed
# The parameter 'af = -1' means that we do not want to assign an alternative function to the pin.
sw1 = Pin('SW1', Pin.IN)
sw1.init(Pin.IN, Pin.PULL_UP, af=-1)
# Inverts the logical state of the button
b1 = Signal(sw1, invert = True)
sw2 = Pin('SW2', Pin.IN)
sw2.init(Pin.IN, Pin.PULL_UP, af=-1)
b2 = Signal(sw2, invert = True)
sw3 = Pin('SW3', Pin.IN)
sw3.init(Pin.IN, Pin.PULL_UP, af=-1)
b3 = Signal(sw3, invert = True)
# Initialization of variables
ancienne_valeur_sw1 = 1
ancienne_valeur_sw2 = 1
ancienne_valeur_sw3 = 1
while True: # Loop without output clause ("infinite")
# 300ms Timeout
sleep_ms(300)
#Retrieving state of buttons 1,2 and 3
valeur_sw1 = b1.value()
valeur_sw2 = b2.value()
valeur_sw3 = b3.value()
#Is the current state different from the previous state?
if valeur_sw1 != ancienne_valeur_sw1:
if valeur_sw1: # equivalent to valeur_sw1 == 1
print( "Button 1 (SW1) is pressed" )
else :
print( "Button 1 (SW1) is released" )
ancienne_valeur_sw1 = valeur_sw1
if valeur_sw2 != ancienne_valeur_sw2:
if valeur_sw2: # equivalent to valeur_sw2 == 1
print( "Button 2 (SW2) is pressed" )
else :
print( "Button 2 (SW2) is released" )
ancienne_valeur_sw2 = valeur_sw2
if valeur_sw3 != ancienne_valeur_sw3:
if valeur_sw3: # equivalent to valeur_sw3 == 1
print( "Button 3 (SW3) is pressed" )
else :
print( "Button 3 (SW3) is released" )
ancienne_v
To go further
-
Buttons can be better managed using the interruptions mechanism, discussed in this tutorial.
-
You will find on this page the description of the methods of the class Pin for the Pyboard, also valid for the NUCLEO-WB55. For example, this script returns a list of all alternative functions associated with the A0 pin:
# Purpose of the script:
# List of all alternative functions associated with the 'A0' pin:
import pyb # MicroPython library allowing access to devices (GPIO, LED, etc.)
a0_pin = pyb.Pin('A0')
liste_alternate_functions = a0_pin.af_list()
for val in liste_alternate_functions:
print(val)
- Buttons of poor quality often pose a bounce problem. When you press such a button, it does not immediately change from the “open” state to the “closed” state but can oscillate several times between the two and generate random behaviors on your montage. This classic problem can be solved by waiting a few milliseconds for the button state to be stable. This topic is covered in this tutorial.