# How RPi Pico WAV Player Detects Raspberry Pi Pico vs Waveshare RP2040-LCD-0.96 Boards

> Learn how the RPi Pico WAV Player detects Raspberry Pi Pico vs Waveshare RP2040-LCD-0.96 boards by checking GPIO 25. Discover the simple yet effective detection method.

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

---

**The RPi Pico WAV Player distinguishes between Raspberry Pi Pico and Waveshare RP2040-LCD-0.96 boards by sampling the state of GPIO 25 at startup, which is pulled low on the Pico (on-board LED) and pulled high on the Waveshare board (LCD back-light control).**

The `elehobica/rpi_pico_wav_player` project is a lightweight WAV audio player designed for RP2040 and RP2350 microcontrollers. One of its key features is automatic hardware detection, allowing a single firmware binary to run on both the vanilla Raspberry Pi Pico and the Waveshare RP2040-LCD-0.96 display board without manual configuration. This article explains the precise mechanism used to distinguish these platforms at runtime.

## How GPIO 25 Enables Automatic Board Detection

The detection mechanism relies on the electrical differences of **GPIO 25** (`PICO_DEFAULT_LED_PIN`) between the two board variants.

### Electrical Differences Between Pico and Waveshare

On the **Raspberry Pi Pico**, GPIO 25 is wired to the on-board LED cathode. The LED is driven low by default, resulting in a logic **low** state at startup.

On the **Waveshare RP2040-LCD-0.96** (and its RP2350-based variant), GPIO 25 is wired to the LCD back-light control (BLK) pin. This pin is pulled up to 3.3 V through a resistor, resulting in a logic **high** state at startup.

### The Detection Algorithm in main.cpp

The firmware executes the detection routine in [`src/main.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/main.cpp) immediately after system initialization:

```cpp
// src/main.cpp (lines 30-40)
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_IN);
#if defined(RASPBERRYPI_PICO2)
    board_type_t board_type = gpio_get(PICO_DEFAULT_LED_PIN)
                              ? WAVESHARE_RP2350_LCD_096 : RASPBERRY_PI_PICO_2;
#else
    board_type_t board_type = gpio_get(PICO_DEFAULT_LED_PIN)
                              ? WAVESHARE_RP2040_LCD_096 : RASPBERRY_PI_PICO;
#endif

```

The `gpio_get(PICO_DEFAULT_LED_PIN)` call returns **true** when the pin reads high (Waveshare board) and **false** when low (Pico). The conditional compilation using `RASPBERRYPI_PICO2` ensures correct detection for both RP2040 and RP2350 silicon variants.

## Board Type Enum and Hardware Abstraction

Once detected, the board type propagates through the system via a strongly-typed enumeration.

### Defining Board Variants in common.h

The `board_type_t` enum in [`src/common.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/common.h) defines all supported hardware targets:

```cpp
// src/common.h (lines 11-16)
typedef enum {
    RASPBERRY_PI_PICO = 0,
    WAVESHARE_RP2040_LCD_096,
    RASPBERRY_PI_PICO_2,
    WAVESHARE_RP2350_LCD_096,
} board_type_t;

```

This enumeration allows subsystems to switch behavior based on the detected hardware without scattering magic numbers throughout the codebase.

### Propagating Board Type to Subsystems

The `main()` function passes the detected `board_type` to initialization routines:

```cpp
// Conceptual flow from src/main.cpp
pm_init(board_type);  // Power management initialization
ui_init(board_type);  // User interface initialization

```

Each module uses this parameter to configure board-specific pin assignments and calibration values.

## Hardware-Specific Configurations

Subsystems adapt their behavior based on the `board_type` value to account for differing pinouts and electrical characteristics.

### LCD Pin Mapping in LcdCanvas.cpp

The LCD controller configuration in [`src/LcdCanvas.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/LcdCanvas.cpp) selects SPI pins based on the board type:

```cpp
// src/LcdCanvas.cpp (lines 74-81)
case WAVESHARE_RP2040_LCD_096: // fallthrough
case WAVESHARE_RP2350_LCD_096:
    pin_spi_cs   = PIN_LCD_SPI1_CS_WAVESHARE;
    pin_spi_sck  = PIN_LCD_SPI1_SCK_WAVESHARE;
    pin_spi_mosi = PIN_LCD_SPI1_MOSI_WAVESHARE;
    pin_dc       = PIN_LCD_DC_WAVESHARE;
    pin_rst      = PIN_LCD_RST_WAVESHARE;
    pin_blk      = PIN_LCD_BLK_WAVESHARE;

```

This ensures the ST7735S LCD receives correct SPI signals and back-light control regardless of which board is detected.

### Battery Voltage Compensation in power_manage.cpp

The power management module adjusts battery monitoring calibration for Waveshare boards. In [`src/power_manage.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/power_manage.cpp), the firmware adds the forward voltage drop of the back-light diode:

```cpp
// src/power_manage.cpp (lines 25-27)
if (_board_type == WAVESHARE_RP2040_LCD_096 ||
    _board_type == WAVESHARE_RP2350_LCD_096) {
    voltage += 0.33;  // Forward voltage of D2 (MBR230LSFT1G)
}

```

This compensation ensures accurate battery level reporting on Waveshare hardware, where the back-light circuit affects the voltage divider network.

## Summary

- **GPIO 25 sampling** provides automatic detection by exploiting electrical differences between the Raspberry Pi Pico (LED cathode, logic low) and Waveshare RP2040-LCD-0.96 (back-light pull-up, logic high).
- **Zero configuration** is required; the firmware adapts pin assignments, LCD initialization, and battery calibration automatically based on the detected `board_type_t` value.
- **Modular design** allows subsystems like `LcdCanvas` and `power_manage` to switch behavior cleanly via the shared enumeration defined in [`src/common.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/common.h).

## Frequently Asked Questions

### How does the detection method handle the Raspberry Pi Pico 2 (RP2350)?

The firmware uses conditional compilation (`#if defined(RASPBERRYPI_PICO2)`) to distinguish between RP2040 and RP2350 silicon during the detection phase. When building for the Pico 2, a high reading on GPIO 25 maps to `WAVESHARE_RP2350_LCD_096`, while a low reading maps to `RASPBERRY_PI_PICO_2`. This ensures correct board type assignment across both microcontroller generations.

### Can the firmware run on other RP2040 boards without modification?

The detection logic specifically targets GPIO 25 states defined by the Raspberry Pi Pico and Waveshare RP2040-LCD-0.96 schematics. Other boards may wire GPIO 25 differently (e.g., floating or connected to different peripherals), which could cause misidentification. For unsupported boards, you would need to modify the detection logic in [`src/main.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/main.cpp) or explicitly set the `board_type` variable to match your hardware's pinout in [`src/common.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/common.h).

### Why does the Waveshare board require battery voltage compensation?

The Waveshare RP2040-LCD-0.96 routes the LCD back-light through a Schottky diode (D2, MBR230LSFT1G) connected to GPIO 25. When measuring battery voltage via the ADC, this diode introduces a forward voltage drop of approximately 0.33 V that affects the voltage divider network. The [`power_manage.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/power_manage.cpp) module adds this offset back to the calculated voltage only when a Waveshare board is detected, ensuring accurate battery level reporting across both hardware variants.

### Is GPIO 25 safe to sample as an input during startup?

Yes. The detection code configures GPIO 25 as a high-impedance input using `gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_IN)` before reading it. On the Raspberry Pi Pico, this captures the LED cathode's default low state without driving current. On the Waveshare board, it safely reads the pull-up voltage for the back-light control without interfering with the LCD initialization sequence. This approach avoids electrical conflicts or GPIO damage during the brief detection window.