Press a button
The input monitor prints the name of every button and joystick direction as it is pressed and released. It is the first program to run on any adapter, and the 30 ms wait inside it is what turns a switch's chatter into one clean press.
Run it first
Plug one button into KEY1. Leave the coin acceptor and screen off for now; the development board's USB alone is enough, because the buttons run from its supply.
- Nano or ESP32-S3: open
InputMonitor.inoin the Arduino IDE, pick the board as its first comment says, upload, and open Serial Monitor at 115200. - Pico or Pico 2: flash MicroPython, then save
arcade.pyandinput_monitor.pyonto the board from Thonny and run the second. - Zero 2 W: turn off the serial port in
raspi-config, installpython3-gpiozeroandpython3-lgpio, clone the repository and runpython3 input_monitor.pyfromexamples/zero2w.
Press and release KEY1:
KEY1 pressed
KEY1 releasedThen plug in the other buttons one at a time and check each name. Do it before any of them go into a panel.
What the 30 ms is for
Metal contacts do not close cleanly. They meet, bounce apart and meet again several times in the first few milliseconds, and a program that reported every change would print a press for each bounce.
So the monitor reports a change only once the pin has held still for 30 ms. The cost is that every press is reported 30 ms after it settles, which is shorter than anyone can notice. These are the lines that do it:
for (uint8_t i=0; i<14; ++i) {
bool value = digitalRead(INPUT_PINS[i]) == HIGH;
if (value != raw[i]) { raw[i] = value; changed[i] = ms; }
if (value != stable[i] && ms - changed[i] >= 30) {
stable[i] = value;
Serial.print(NAMES[i]);
Serial.println(value ? " pressed" : " released");
}
}raw follows the pin as it bounces; stable only changes once raw has
stayed the same for 30 ms. Everything the kit's examples do with a button —
start a game, score a point — is built on that second value.
On a Zero 2 W
The same idea in Python, reading BCM numbers through gpiozero:
"""Run on Raspberry Pi OS. GPIO numbers are BCM, not header positions."""
from time import monotonic, sleep
from gpiozero import DigitalInputDevice
PINS = (4, 5, 6, 12, 13, 16, 17, 19, 14, 15, 20, 21, 22, 23)
NAMES = tuple("KEY%d" % n for n in range(1, 11)) + ("UP", "DOWN", "LEFT", "RIGHT")
def main():
buttons = []
try:
for pin in PINS:
buttons.append(DigitalInputDevice(pin, pull_up=False))
raw = [b.value for b in buttons]
stable = raw.copy()
changed = [monotonic()] * len(buttons)
print("Ready. Press a button or push the stick.")
while True:
now = monotonic()
for i, button in enumerate(buttons):
value = button.value
if value != raw[i]:
raw[i], changed[i] = value, now
if value != stable[i] and now - changed[i] >= 0.030:
stable[i] = value
print(NAMES[i], "pressed" if value else "released")
sleep(0.001)
except KeyboardInterrupt:
pass
finally:
for button in buttons:
button.close()
if __name__ == "__main__":
main()The code
One sketch for both Arduino adapters: it picks the Nano or ESP32-S3 pin list from the board you select in Tools. Open Serial Monitor at 115200 and press KEY1.
/* HotSwap Arcade Kit - input monitor: every button and joystick direction
Nano: Tools > Board Arduino Nano · Processor ATmega328P
(try "ATmega328P (Old Bootloader)" if the upload times out)
ESP32-S3: Tools > Board ESP32S3 Dev Module · USB CDC On Boot Enabled
when the cable is in the board's native USB port
Serial Monitor at 115200. Pressed reads HIGH: each input has a 10k
pull-down on the mainboard, and the button connects it to V(MCU).
Guide: https://learn.lonelybinary.com/manuals/arcade/press-a-button */
#include <Arduino.h>
#if defined(ARDUINO_AVR_NANO)
const uint8_t INPUT_PINS[] = {2,3,4,5,6,7,8,9,10,11,12,A4,A3,A2};
const uint8_t INPUT_MODE = INPUT; // 10k pull-downs on the mainboard
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
const uint8_t INPUT_PINS[] = {4,5,6,7,15,16,17,18,8,40,39,38,9,47};
const uint8_t INPUT_MODE = INPUT_PULLDOWN;
#else
#error "Select classic Arduino Nano or ESP32S3 Dev Module."
#endif
const char *NAMES[] = {"KEY1", "KEY2", "KEY3", "KEY4", "KEY5",
"KEY6", "KEY7", "KEY8", "KEY9", "KEY10",
"UP", "DOWN", "LEFT", "RIGHT"};
bool raw[14], stable[14];
unsigned long changed[14];
void setup() {
Serial.begin(115200);
delay(1000);
for (uint8_t i=0; i<14; ++i) {
pinMode(INPUT_PINS[i], INPUT_MODE);
raw[i] = stable[i] = digitalRead(INPUT_PINS[i]) == HIGH;
changed[i] = millis();
}
Serial.println("Ready. Press a button or push the stick.");
}
void loop() {
unsigned long ms = millis();
for (uint8_t i=0; i<14; ++i) {
bool value = digitalRead(INPUT_PINS[i]) == HIGH;
if (value != raw[i]) { raw[i] = value; changed[i] = ms; }
if (value != stable[i] && ms - changed[i] >= 30) {
stable[i] = value;
Serial.print(NAMES[i]);
Serial.println(value ? " pressed" : " released");
}
}
delay(1);
}The pin lists are the adapter tables from One header, four adapters, in KEY1 to RIGHT order. Change a pin here and you are no longer on the kit's wiring.
View on GitHub · examples/arduino/InputMonitor/InputMonitor.ino @ v1.0For the Pico and Pico 2. Save arcade.py from the same folder to the Pico first, then this file, and run it from Thonny.
"""Run on Pico / Pico 2 with arcade.py on the board."""
from time import sleep_ms
from arcade import Button, INPUT_PINS, INPUT_NAMES
buttons = [Button(gpio) for gpio in INPUT_PINS]
print("Ready. Press a button or push the stick.")
while True:
for name, button in zip(INPUT_NAMES, buttons):
event = button.poll()
if event:
print(name, "pressed" if event == 1 else "released")
sleep_ms(1)The Button class and the pin list are in arcade.py, which every Pico example imports. A ModuleNotFoundError for arcade means that file is not on the board.
View on GitHub · examples/pico/input_monitor.py @ v1.0When it does not work
Check the Serial Monitor is at 115200 and on the development board's port, not a different one. On an ESP32-S3 with the cable in its native USB socket, set USB CDC On Boot to Enabled in Tools, or nothing reaches that port.
Something has shortened the debounce, or the switch is worn. The examples wait 30 ms; the figure above shows what a shorter wait does. If the stock code double-counts one button and not the others, swap that button's cable to a second button to see which is at fault.
Many Nano-compatible boards ship with the older bootloader. In Tools, set Processor to ATmega328P (Old Bootloader) and upload again.
Thonny is running the file on your computer instead of the Pico. Set the interpreter to MicroPython (Raspberry Pi Pico) in the bottom right corner and run it again.
The serial console still owns BCM14 and BCM15. Turn off the serial login shell and serial hardware in raspi-config, then reboot.
Four microswitches, and why the one you wire to UP is the one nearest you.
Wire the joystick →Edit this page — content/books/arcade/press-a-button.mdx
Questions about this product
See what other owners have asked, and read their solutions.
HotSwap Arcade Kit: Buttons, Joystick and Coin Acceptor for ESP32-S3, Nano, Pico and Zero 2 W
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.