# How to Interface with and Control the RPi Pico WAV Player Using the 3 Push Buttons

> Control the RPi Pico WAV Player with 3 push buttons. Learn how to interface with its UI using the ui_control.h API to get button events like clicks and long presses.

- Repository: [Elehobica/rpi_pico_wav_player](https://github.com/elehobica/rpi_pico_wav_player)
- Tags: how-to-guide
- Published: 2026-03-01

---

**The RPi Pico WAV Player exposes two physical button groups (GPIO switches and an ADC-sensed headphone remote) through the [`ui_control.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/ui_control.h) API, where `ui_get_btn_evt()` delivers debounced, high-level events like single clicks, double clicks, and long presses to your application code.**

The `elehobica/rpi_pico_wav_player` firmware abstracts the three physical buttons into a robust event system that handles hardware debouncing, repeat detection, and power management. Whether you are building a custom UI or integrating the player into a larger embedded project, understanding the button interface allows you to capture user input reliably without managing raw GPIO or ADC reads directly.

## Hardware Architecture of the Three-Button Interface

The firmware treats the three physical buttons as two distinct input groups: direct GPIO push-buttons and a voltage-divided headphone remote.

### Push-Button Group (GPIO 22 and GPIO 20)

Two momentary switches on the PCB connect to **GPIO 22** (Minus) and **GPIO 20** (Plus). These lines are configured as digital inputs with internal pull-ups. In [`src/ui_control.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ui_control.cpp), the constants `PIN_SW_PLUS` and `PIN_SW_MINUS` define these mappings, and the function `get_sw_status()` reads both pins to return a `button_status_t` of `Plus`, `Minus`, or `Open`.

### Headphone Remote Group (ADC0 on GPIO 26)

The third button channel is the 3-wire headset jack, which uses a resistor ladder to encode four possible states (Center, D, Plus, Minus) into a single analog voltage on **GPIO 26** (ADC0). The constant `PIN_HP_BUTTON` references this pin. The `adc0_get_hp_button()` function samples the ADC, converts the raw value to millivolts, and maps specific voltage ranges to the logical states `Center`, `D`, `Plus`, `Minus`, or `Open`.

## Low-Level Input Handling in ui_control.cpp

The low-level driver in [`src/ui_control.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ui_control.cpp) merges both input groups into a unified state machine.

### Pin Definitions and Initialization

At startup, `ui_init()` configures the GPIO pull-ups and initializes the ADC channel. The pin constants are defined near the top of the file:

```cpp
// From src/ui_control.cpp
#define PIN_SW_PLUS   20
#define PIN_SW_MINUS  22
#define PIN_HP_BUTTON 26  // ADC0

```

### Digital GPIO Reading

The `get_sw_status()` function (lines 52-62) performs a simple digital read on the two push-button pins. Because the pins are pulled high internally, a pressed button reads low, which the function translates into the corresponding `button_status_t` enumeration value.

### ADC Voltage Ladder Decoding

For the headphone remote, `adc0_get_hp_button()` (lines 65-86) samples ADC0 and compares the millivolt reading against threshold constants to determine which resistor path is active. This allows a single wire to represent up to four distinct button signals through voltage division.

## Event Generation and Debouncing Logic

Rather than exposing raw pin states, the firmware runs a 20 Hz background timer that processes history buffers and emits high-level actions.

### 20 Hz Polling Timer

A repeating timer initialized in `ui_init()` calls `update_button_action()` at 20 Hz. This frequency provides responsive user feedback while maintaining low CPU overhead.

### State Machine and Action Detection

Inside `update_button_action()`, the firmware:

1. Reads both input groups (`get_sw_status()` and `adc0_get_hp_button()`).
2. Maintains a history buffer (`button_prv[]`) to implement software debouncing.
3. Tracks press duration and repeat counts to distinguish **single click**, **double/triple click**, **short press**, **long press**, and **very long press**.
4. Assigns a `button_unit_t` (`PushButtons` or `HpButtons`) to identify the physical source.

### The Event Queue

Detected actions are packaged as `button_action_t` values and pushed into a small FIFO queue named `btn_evt_queue`. This decouples the interrupt-driven timer from the main application loop, preventing event loss during heavy audio decoding tasks.

## Consuming Button Events in Your Application

The public API in [`src/ui_control.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ui_control.h) provides a simple polling interface for your main loop or UI state machine.

### The Public API

Two functions expose the queue to higher layers:

```cpp
bool ui_get_btn_evt(button_action_t& btn_act, button_unit_t& btn_unit);
void ui_clear_btn_evt();

```

`ui_get_btn_evt()` dequeues the next event, populating the reference arguments with the action type and originating unit. It returns `true` if an event was available, `false` otherwise.

### Polling Example

The following pattern, adapted from [`src/UIMode.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.cpp), shows how to react to navigation commands:

```cpp
#include "ui_control.h"

while (true) {
    button_action_t act;
    button_unit_t   unit;
    
    if (ui_get_btn_evt(act, unit)) {
        switch (act) {
        case button_action_t::PlusSingle:
            // Trigger next track or increase volume
            break;
        case button_action_t::MinusSingle:
            // Trigger previous track or decrease volume
            break;
        case button_action_t::CenterLong:
            // Toggle play/pause
            break;
        case button_action_t::PlusDouble:
            // Fast-forward or next album
            break;
        default:
            break;
        }
    }
    
    // Audio decoding and UI rendering logic here
}

```

### Differentiating Button Sources

If your application requires different behavior for the PCB buttons versus the headphone remote, inspect the `button_unit_t` value:

```cpp
if (ui_get_btn_evt(act, unit)) {
    if (unit == button_unit_t::HpButtons) {
        // Handle headphone remote specifically
        if (act == button_action_t::CenterSingle) {
            // Accept call or play/pause
        }
    } else {
        // Handle PCB push-buttons
    }
}

```

## Advanced: Wake-on-Button and Power Management

The firmware supports waking the RP2040 from deep sleep using the headphone remote’s center button. Before entering low-power mode, call `ui_set_center_switch_for_wakeup(true)`, which reconfigures the HP pin from ADC mode to a plain GPIO input capable of generating wake-up interrupts. Upon resume, `ui_init()` automatically restores the ADC configuration. This functionality is integrated with the power management utilities in [`src/power_manage.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/power_manage.cpp).

## Summary

- The three physical buttons map to **two GPIO pins** (22 and 20) for the PCB switches and **one ADC pin** (26) for the headphone remote resistor ladder.
- The [`src/ui_control.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ui_control.cpp) driver handles raw reads, **20 Hz debouncing**, and history tracking, emitting high-level `button_action_t` events.
- Use `ui_get_btn_evt()` in your main loop to poll for **single clicks, double clicks, long presses, and very long presses** without managing hardware details.
- Inspect `button_unit_t` to differentiate between `PushButtons` and `HpButtons` when implementing context-sensitive controls.
- Call `ui_set_center_switch_for_wakeup()` to enable **deep-sleep wake-on-button** functionality for battery-powered applications.

## Frequently Asked Questions

### How do I read the button states without missing events during audio playback?

The firmware uses a FIFO queue (`btn_evt_queue`) fed by a 20 Hz background timer in [`src/ui_control.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ui_control.cpp). By calling `ui_get_btn_evt()` each frame in your main loop—as demonstrated in [`src/UIMode.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.cpp)—you dequeue events asynchronously from the audio decoder, ensuring no input is lost during CPU-intensive operations.

### Can I distinguish between the PCB buttons and the headphone remote buttons?

Yes. The `ui_get_btn_evt()` function returns a `button_unit_t` parameter that is set to either `PushButtons` (for GPIO 20/22) or `HpButtons` (for the ADC-sensed remote). Check this value after polling to apply different logic for in-line remote controls versus onboard switches.

### What types of button presses can the firmware detect?

According to the state machine in [`src/ui_control.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ui_control.cpp), the firmware recognizes **single click**, **double click**, **triple click**, **short press**, **long press**, and **very long press**. These map to enumerations like `PlusSingle`, `CenterLong`, or `MinusDouble`, allowing complex UI navigation with only three physical inputs.

### How do I use the center button to wake the device from sleep?

Before entering deep sleep, call `ui_set_center_switch_for_wakeup(true)` from [`ui_control.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/ui_control.h). This function reconfigures the headphone remote pin (GPIO 26) as a GPIO wake source. When the center button is pressed, it triggers a hardware interrupt to resume the RP2040, after which `ui_init()` restores normal ADC operation for button sensing.