Re-live your Roland 808 drum machine dreams with these step switches with LEDs. They're tactile, they're momentary, and they're awesome.

This guide shows how you can use them as simple inputs for an Arduino or CircuitPython-based project on Pico, but you can use these methods for any microcontroller board you like.

The basic examples let you make a desktop switcher, game item selector, or MIDI controller using a simple bit of code. You'll be reading switches and toggling their LEDs for feedback in no time!

Parts

Angled shot of three blue step-switches with LEDs.
These hinged momentary step switches are evocative of the 16-step sequencer keys on TR-808 sythesizers. Also sometimes...
$4.50
In Stock
Angle shot of Raspberry Pi Pico RP2040
The Raspberry Pi foundation changed single-board computing when they released the Raspberry Pi computer, now they're ready to...
$4.00
In Stock
Five pack of 20-pin 0.1 Female Header - Blue plastic
Female header is like the duct tape of electronics. It's great for connecting things together, soldering to perf-boards, sockets for wires or break-away header, etc. We go through...
$2.50
In Stock
Angled shot of single Universal Proto-Board PCB 5cm x 7cm
Here's a handy 3-pack of 5cm x 7cm Proto-board PCBs in green. Both sides have a green silkscreen with some row/column markings. Note that these don't...
$2.95
In Stock
Break-away 0.1 inch 36-pin strip male header - Blue plastic
In this world, nothing can be said to be certain, except we need headers, headers, and more headers!Each pack contains ten blue 36-pin 0.1" pitch...
$4.95
In Stock
Angled shot of 25 Through-Hole Resistors - 1.0K ohm 5% 1/4W.
ΩMG! You're not going to be able to resist these handy resistor packs! Well, axially, they do all of the resisting for you!This is a 25 Pack of...
$0.75
In Stock

Tools

  • soldering iron & solder
  • hookup wire
  • wire cutters

Flip over a step switch like a fat beetle taking a nap and have a look at the underbelly. 

First of all, you can see from the pin arrangement that this switch can't be used on a breadboard, perma proto, or stripboard due to the pin arrangement. However, the 0.1" spacing means you can use it on a perf board that is a free grid with no problems. You'll need to do point-to-point wiring (or make your own PCB) to hook things up.

LEDs

The top two pins are the LED legs. You'll use a 1K resistor to limit current. The leg labeled "-" goes to ground, while the other will run via the resistor to your digital output pin or PWM.

Switch Common

The switch is a momentary SPDT (single-pole, dual throw). This means the common pins are connected and can be wired to ground.

Throw Pins

The bottom two pins are the throws. You'' wire either the N.C. (normally closed) or N.O. (normally open) throw pin to a digital input pin on your microcontroller to read the state. When the switch is pressed the N.C. pin will change to open and the N.O. pin will change to closed. Just pick one to read and you'll be set!

Here's a view of the pinout from the top.

The Step Switch Part assembly uses uses eight GPIO pins on the Pico -- four to read the switches, and four to drive the LEDs.

The Fritzing diagram above show the connections, you'll use a gridded proto board to build it securely.

Switches in Board

Press the four switches into the proto board as shown, being careful not to bend any legs. Note there are two plastic feet that also fit in holes in the PCB.

Solder the pins to the board. Later you'll add thin wires to directly connect to the Pico.

Pico Headers

Next, solder in two rows of header sockets for the Pico. 

If the Pico came without header pins attached, solder those in now too. 

Solder the Circuit

Following the Fritzing diagra, solder the circuit. The photo below shows a false color overlay to make it easier to see each run of the circuit.

Start by running a ground rail wire across the switch common pins, and connect it to Pico GND. In the overlay this is the purple run.

Create short runs from the ground rail wire to the anode (negative) led of each LED. These are also purple in the overlay.

Next, run the cathode (positive) leg of each LED to their respective Pico pins via 1KΩ resistors. They are shown below with the blue overlay.

Finally, run the N.O. pin of each switch to its respective pin on the Pico. These are shown in yellow below.

Bottom Plate

Use M2 screws and standoffs to connect a proto PCB as a bottom plate, or simply use some rubber adhesive feet.

Next you'll code the Step Switch Pico and use it.

CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the CIRCUITPY drive to iterate.

CircuitPython Quickstart

Follow this step-by-step to quickly get CircuitPython working on your board.

Click the link above and download the latest UF2 file.

Download and save it to your desktop (or wherever is handy).

Start with your Pico unplugged from USB. Hold down the BOOTSEL button, and while continuing to hold it (don't let go!), plug the Pico into USB. Continue to hold the BOOTSEL button until the RPI-RP2 drive appears!

If the drive does not appear, unplug your Pico and go through the above process again.

A lot of people end up using charge-only USB cables and it is very frustrating! So make sure you have a USB cable you know is good for data sync.

You will see a new disk drive appear called RPI-RP2.

 

Drag the adafruit_circuitpython_etc.uf2 file to RPI-RP2.

The RPI-RP2 drive will disappear and a new disk drive called CIRCUITPY will appear.

That's it, you're done! :)

Flash Resetting UF2

If your Pico ever gets into a really weird state and doesn't even show up as a disk drive when installing CircuitPython, try installing this 'nuke' UF2 which will do a 'deep clean' on your Flash Memory. You will lose all the files on the board, but at least you'll be able to revive it! After nuking, re-install CircuitPython

Text Editor

Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.

Alternatively, you can use any text editor that saves simple text files.

Download the Project Bundle

Your project will use a specific set of CircuitPython libraries and the code.py file. To get everything you need, click on the Download Project Bundle link below, and uncompress the .zip file.

Drag the contents of the uncompressed bundle directory onto your board's CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.

# SPDX-FileCopyrightText: 2022 john park for Adafruit Industries
# SPDX-License-Identifier: MIT
# Pico Four Step Switch Keypad Demo
import time
import board
import keypad
from digitalio import Direction, DigitalInOut

board_led = DigitalInOut(board.LED)
board_led.direction = Direction.OUTPUT
board_led.value = True

switch_pins = (board.GP6, board.GP7, board.GP8, board.GP9)
keys = keypad.Keys(switch_pins, value_when_pressed=False, pull=True)

led_pins = (board.GP2, board.GP3, board.GP4, board.GP5)
leds = []
for led_pin in led_pins:
    tmp_led_pin = DigitalInOut(led_pin)
    tmp_led_pin.direction = Direction.OUTPUT
    tmp_led_pin.value = False
    leds.append(tmp_led_pin)

def blink_led(led_num, pause, repeat):
    for __ in range(repeat * 2):
        leds[led_num].value = not leds[led_num].value
        time.sleep(pause)

def blink_all_leds(pause, repeat):
    for __ in range(repeat * 2):
        for led in leds:
            led.value = not led.value
        time.sleep(pause)

blink_all_leds(0.1, 4)


mode_picked = False  # state of mode selection
mode_choice = 0  # MIDI mode, desk switcher mode, etc.
modes = (0, 1, 2, 3)
mode_names = ("MIDI", "DESK", "SELECTOR", "COPY-PASTE")

print("Select the mode by pressing a button: MIDI, DESK, SELECTOR, or COPY-PASTE")
while not mode_picked:  # program waits for a mode to be picked
    key = keys.events.get()
    if key:
        if key.pressed:
            mode_choice = key.key_number
            print(mode_names[mode_choice], "mode")
            mode_picked = True


if mode_choice == 0:  # MIDI mode
    import usb_midi
    import adafruit_midi
    from adafruit_midi.control_change import ControlChange
    midi = adafruit_midi.MIDI(
                            midi_in=usb_midi.ports[0],
                            in_channel=0,
                            midi_out=usb_midi.ports[1],
                            out_channel=0
    )
    cc_num = [16, 17, 18, 19]
    cc_state = [False, False, False, False]

else:  # HID modes
    import usb_hid
    from adafruit_hid.keyboard import Keyboard
    from adafruit_hid.keycode import Keycode

if mode_choice == 1:  # Mac Desktop switcher mode
    kpd = Keyboard(usb_hid.devices)
    MODIFIER = Keycode.CONTROL
    KEYMAP = (
        ("Desktop 1", [MODIFIER, Keycode.ONE]),
        ("Desktop 2", [MODIFIER, Keycode.TWO]),
        ("Desktop 3", [MODIFIER, Keycode.THREE]),
        ("Desktop 4", [MODIFIER, Keycode.FOUR]),
    )

if mode_choice == 2:  # SELECTOR mode for game weapon slot, Wirecast, etc.
    kpd = Keyboard(usb_hid.devices)
    MODIFIER = Keycode.SHIFT
    KEYMAP = (
        ("Selector 1", [MODIFIER, Keycode.ONE]),
        ("Selector 2", [MODIFIER, Keycode.TWO]),
        ("Selector 3", [MODIFIER, Keycode.THREE]),
        ("Selector 4", [MODIFIER, Keycode.FOUR]),
    )

if mode_choice == 3:  # Copy/Paste mode
    kpd = Keyboard(usb_hid.devices)
    # Choose the correct modifier key for Windows or Mac.
    # MODIFIER = Keycode.CONTROL  # For Windows
    MODIFIER = Keycode.COMMAND
    KEYMAP = (
        ("Copy/Paste 1", [MODIFIER, Keycode.A]),  # select all
        ("Copy/Paste 2", [MODIFIER, Keycode.X]),  # cut
        ("Copy/Paste 3", [MODIFIER, Keycode.C]),  # copy
        ("Copy/Paste 4", [MODIFIER, Keycode.V]),  # paste
    )

blink_led(mode_choice, 0.1, 3)

while True:
    key = keys.events.get()
    if key:
        if key.pressed:
            i = key.key_number
            print(i, "pressed")
            if mode_choice == 0:
                leds[i].value = not leds[i].value
                if cc_state[i] is False:
                    midi.send(ControlChange(cc_num[i], 127))
                    cc_state[i] = True
                else:
                    midi.send(ControlChange(cc_num[i], 0))
                    cc_state[i] = False
            else:
                print(KEYMAP[i][0])
                kpd.send(*KEYMAP[i][1])
                for switch_led in leds:  # blank the LEDs first
                    switch_led.value = False
                leds[i].value = True  # light selected switch LED

Use It

On startup, the board waits for you to select a mode by pressing one of the four switches. From left-to-right the modes are "MIDI", "Desktop switcher", "Item selector", and "Copy-Paste".

  • In MIDI mode pressing each key toggles a MIDI CC on and off (127 or 0). You can see this in action in the video at the top of the page
  • Desktop switcher mode pick among four MacOS desk "Spaces". You can see a video of this mode demonstrated below.
  • Selector mode pick four main items in many video games, particularly first-person shooters
  • Copy-Paste mode invokes "select all", "cut", "copy", and "paste" shortcuts

How it Works

The code is doing a few basic tasks: checking for button presses, toggling the LEDs, and sending USB MIDI or USB HID messages.

To make the button reading simple, we use the adafruit_keypad library.

import time
import board
import keypad
from digitalio import Direction, DigitalInOut

Switch Setup

The setup for multiple switches if very simple when using the keypad library. You create a variable name for the group of pins, list the pins, and then create a keypad object (in this case named keys) that point to the switch pin list, set a value default, and choose a pull up or down resistor value.

switch_pins = (board.GP6, board.GP7, board.GP8, board.GP9)
keys = keypad.Keys(switch_pins, value_when_pressed=False, pull=True)

LED Setup

The LEDs are set up as a list so they are easy to call later using a list and index, e.g. leds[2]

led_pins = (board.GP2, board.GP3, board.GP4, board.GP5)
leds = []
for led_pin in led_pins:
    tmp_led_pin = DigitalInOut(led_pin)
    tmp_led_pin.direction = Direction.OUTPUT
    tmp_led_pin.value = False
    leds.append(tmp_led_pin)

Blink Functions

A couple of convenience functions are created to make it easy to blink one or all of the LEDs.

 

def blink_led(led_num, pause, repeat):
    for __ in range(repeat * 2):
        leds[led_num].value = not leds[led_num].value
        time.sleep(pause)

def blink_all_leds(pause, repeat):
    for __ in range(repeat * 2):
        for led in leds:
            led.value = not led.value
        time.sleep(pause)

Mode Selection

At startup, the code will wait for the user to select one of four modes by clicking one of the step switches.

To do so, we have a mode_picked state, a mode_choice variable, and four possible modes.

Using a while not mode_picked: loop, the program essentially checks for a button press before it will get out of the loop and move ahead.

mode_picked = False  # state of mode selection
mode_choice = 0  # MIDI mode, desk switcher mode, etc.
modes = (0, 1, 2, 3)
mode_names = ("MIDI", "DESK", "SELECTOR", "COPY-PASTE")

print("Select the mode by pressing a button: MIDI, DESK, SELECTOR, or COPY-PASTE")
while not mode_picked:  # program waits for a mode to be picked
    key = keys.events.get()
    if key:
        if key.pressed:
            mode_choice = key.key_number
            print(mode_names[mode_choice], "mode")
            mode_picked = True

Modes

Once a mode is selected, additional libraries are imported relevant to the mode selection, as well as some other specific setup.

For mode 0, MIDI is set up, with a list of CC numbers to use.

if mode_choice == 0:  # MIDI mode
    import usb_midi
    import adafruit_midi
    from adafruit_midi.control_change import ControlChange
    midi = adafruit_midi.MIDI(
                            midi_in=usb_midi.ports[0],
                            in_channel=0,
                            midi_out=usb_midi.ports[1],
                            out_channel=0
    )
    cc_num = [16, 17, 18, 19]
    cc_state = [False, False, False, False]

The other three modes import USB hid libraries.

else:  # HID modes
    import usb_hid
    from adafruit_hid.keyboard import Keyboard
    from adafruit_hid.keycode import Keycode

HID Modes

The HID modes have their own specific setups to specify key combos to press.

if mode_choice == 1:  # Mac Desktop switcher mode
    kpd = Keyboard(usb_hid.devices)
    MODIFIER = Keycode.CONTROL
    KEYMAP = (
        ("Desktop 1", [MODIFIER, Keycode.ONE]),
        ("Desktop 2", [MODIFIER, Keycode.TWO]),
        ("Desktop 3", [MODIFIER, Keycode.THREE]),
        ("Desktop 4", [MODIFIER, Keycode.FOUR]),
    )

if mode_choice == 2:  # SELECTOR mode for game weapon slot, Wirecast, etc.
    kpd = Keyboard(usb_hid.devices)
    MODIFIER = Keycode.SHIFT
    KEYMAP = (
        ("Selector 1", [MODIFIER, Keycode.ONE]),
        ("Selector 2", [MODIFIER, Keycode.TWO]),
        ("Selector 3", [MODIFIER, Keycode.THREE]),
        ("Selector 4", [MODIFIER, Keycode.FOUR]),
    )

if mode_choice == 3:  # Copy/Paste mode
    kpd = Keyboard(usb_hid.devices)
    # Choose the correct modifier key for Windows or Mac.
    # MODIFIER = Keycode.CONTROL  # For Windows
    MODIFIER = Keycode.COMMAND
    KEYMAP = (
        ("Copy/Paste 1", [MODIFIER, Keycode.A]),  # select all
        ("Copy/Paste 2", [MODIFIER, Keycode.X]),  # cut
        ("Copy/Paste 3", [MODIFIER, Keycode.C]),  # copy
        ("Copy/Paste 4", [MODIFIER, Keycode.V]),  # paste
    )

Main Loop

When the main loop of the program is running the step switches are checked using the keys.events.get() command.

When a key is pressed, that switch's LED lights up and the associated command is sent.

while True:
    key = keys.events.get()
    if key:
        if key.pressed:
            i = key.key_number
            print(i, "pressed")
            if mode_choice == 0:
                leds[i].value = not leds[i].value
                if cc_state[i] is False:
                    midi.send(ControlChange(cc_num[i], 127))
                    cc_state[i] = True
                else:
                    midi.send(ControlChange(cc_num[i], 0))
                    cc_state[i] = False
            else:
                print(KEYMAP[i][0])
                kpd.send(*KEYMAP[i][1])
                for switch_led in leds:  # blank the LEDs first
                    switch_led.value = False
                leds[i].value = True  # light selected switch LED

The Arduino version works in nearly the same way as the fundamentals of the CircuitPython version (without the HID and MIDI output). First, set up the Arduino IDE with the Earle Philhower core for use with Pico RP2040 as shown in this guide.

Then, copy the code below and paste it into a new Arduino document. Save it in your Arduino project folder, and then upload it to the Pico, using the settings shown in the guide linked above.

// SPDX-FileCopyrightText: 2022 John Park for Adafruit Industries
// SPDX-License-Identifier: MIT
// Step Switch Party for Pico

// extra library used:
// Bounce2 -- https://github.com/thomasfredericks/Bounce2

#include <Wire.h>
#include <Bounce2.h>


const int num_buttons = 4;
const int led_pins[num_buttons] = {2, 3, 4, 5};
const int button_pins[num_buttons] = {6, 7, 8, 9}; 
Bounce buttons[num_buttons];
bool led_vals[num_buttons];


void setup() {
  Serial.begin(9600);
  delay(1000);
  Serial.println("Step Switch Pico Party");

  for (uint8_t i=0; i< num_buttons; i++){
    buttons[i].attach( button_pins[i], INPUT_PULLUP);
  }

  for (uint8_t i=0; i< num_buttons; i++){
      pinMode(led_pins[i], OUTPUT);
      digitalWrite(led_pins[i], HIGH);
      led_vals[i] = HIGH;
  }
}


void loop() {
  for (uint8_t i=0; i< num_buttons; i++){
    buttons[i].update();
    if( buttons[i].fell()) {
      // do something here, such as send MIDI, change a NeoPixel animation, etc.
      Serial.println(i);
      led_vals[i] = !led_vals[i];
      digitalWrite(led_pins[i], led_vals[i]);
    }
  }
  
}

This guide was first published on Aug 16, 2022. It was last updated on Aug 31, 2022.