How the 160x80 LCD Display (ST7735S) Is Controlled in RPi Pico WAV Player

The RPi Pico WAV Player controls the 160×80 ST7735S LCD through the pico_st7735_80x160 submodule, which provides a low-level C API (LCD_*) that the LcdCanvas class wraps to handle initialization, configuration, and drawing operations.

The project uses a 0.96-inch 160×80 pixel ST7735S panel (referenced as I6Ox8O in the schematic) to render its user interface. All display operations are abstracted through a dedicated canvas layer that translates high-level UI commands into SPI transactions managed by an external driver library.

Selecting the ST7735S Driver

The target panel is selected at compile time in src/LcdCanvas.h through a conditional macro that includes the low-level driver headers:

#define USE_ST7735S_160x80
#if defined(USE_ST7735S_160x80)
#include "lcd_extra.h"   // Driver from the pico_st7735_80x160 submodule
#endif

This inclusion pulls in the pico_st7735_80x160 library, which is linked as a Git submodule under lib/pico_st7735_80x160. The library exposes a C-based API prefixed with LCD_ that handles all hardware-specific initialization and pixel pushing.

LCD Initialization and Configuration

Hardware initialization is centralized in LcdCanvas::configureLcd(), defined in src/LcdCanvas.cpp. This function maps the Raspberry Pi Pico’s GPIO pins to the display’s SPI interface and selects one of three predefined hardware configurations based on the board type and user settings.

The configuration structure pico_st7735_80x160_config_t defines SPI clock frequency, SPI instance (spi1), and pin assignments for CS, SCK, MOSI, DC, RST, and BLK (backlight):

pico_st7735_80x160_config_t lcd_cfg[3] = {
    { SPI_CLK_FREQ_DEFAULT, spi1, pin_spi_cs, pin_spi_sck, pin_spi_mosi,
      pin_lcd_dc, pin_lcd_rst, pin_lcd_blk, PWM_DUTY_DEFAULT,
      INVERSION_DEFAULT, RGB_ORDER_DEFAULT, ROTATION_DEFAULT,
      H_OFS_DEFAULT, V_OFS_DEFAULT, X_MIRROR_DEFAULT },
    { … },   // Alternative offset configuration
    { … }    // X-mirror configuration
};

After selecting the appropriate configuration entry, the function invokes the driver’s setup routines:

LCD_Config(&lcd_cfg[cfg_id]);   // Store configuration in driver
LCD_Init();                     // Execute hardware reset and initialization
LCD_Clear(BLACK);               // Clear framebuffer to black

LCD_Init() performs the hardware reset sequence, configures the ST7735S controller registers, and prepares the SPI interface for DMA-backed transfers.

High-Level Drawing API

The LcdCanvas class wraps the low-level LCD_* functions to provide a UI-friendly drawing interface. Key primitives include:

  • LCD_FillBackground() – Clears the entire screen, invoked by LcdCanvas::clear().
  • LCD_SetRotation() – Changes display orientation at runtime, wrapped by LcdCanvas::setRotation().
  • LCD_ShowIcon() – Renders bitmap icons from LcdCanvasIcon.h, used by BatteryIconBox::draw().
  • LCD_Fill() – Draws filled rectangles for UI elements like battery level bars and progress indicators.
  • LCD_ShowString() – Displays text strings, utilized internally by TextBox::draw().

The UI composition layer (LcdCanvas) manages screen states (opening, list view, play mode, power-off) by orchestrating these primitives. For example, drawing a battery icon with a dynamic charge level involves calling LCD_ShowIcon() for the battery outline and LCD_Fill() for the charge bar:

void BatteryIconBox::draw() {
    if (!isUpdated) return;
    isUpdated = false;
    clear();
    LCD_ShowIcon(pos_x, pos_y, icon, !bgOpaque, fgColor);
    // Draw charge level bar
    uint16_t color = (level >= 50) ? 0x0600 :
                     (level >= 20) ? 0xc600 : 0xc000;
    LCD_Fill(pos_x+4, pos_y+13-level/10,
             pos_x+4+8-1, pos_y+13-level/10+level/10+1-1, color);
}

Submodule Integration

The low-level driver is not maintained within the main repository. Instead, it is imported as a Git submodule defined in .gitmodules and linked via CMake in the root CMakeLists.txt:

add_subdirectory(pico_st7735_80x160)

This approach allows the project to track updates to the pico_st7735_80x160 library independently while ensuring the build system compiles the driver sources alongside the application code.

Summary

  • Driver Selection: The project targets the ST7735S panel using the USE_ST7735S_160x80 macro in src/LcdCanvas.h, which includes the pico_st7735_80x160 submodule.
  • Configuration: LcdCanvas::configureLcd() in src/LcdCanvas.cpp sets up SPI pins, clock, and panel-specific offsets, then calls LCD_Config() and LCD_Init() to activate the hardware.
  • Drawing API: The UI layer uses high-level primitives such as LCD_Fill(), LCD_ShowIcon(), and LCD_ShowString() to render screens, while the driver handles SPI DMA transfers to the ST7735S controller.
  • Submodule Architecture: The low-level driver resides in a separate repository linked as a Git submodule, integrated via CMake to keep the display logic modular and maintainable.

Frequently Asked Questions

How is the ST7735S LCD connected to the Raspberry Pi Pico?

The display connects via SPI1 with dedicated GPIO pins for chip select (CS), serial clock (SCK), master-out-slave-in (MOSI), data/command (DC), reset (RST), and backlight (BLK). The specific pin numbers are defined in src/LcdCanvas.cpp within the configureLcd() function and vary based on the detected board_type_t.

What is the purpose of the pico_st7735_80x160 submodule?

The submodule provides the low-level C driver (LCD_* API) that implements the SPI communication protocol and ST7735S initialization sequence. Keeping this code in a separate repository allows the WAV Player project to share the driver with other applications and receive bug fixes or optimizations without modifying the main player source code.

Can the display rotation be changed at runtime?

Yes. The driver supports runtime rotation through the LCD_SetRotation() function, which is wrapped by LcdCanvas::setRotation() in src/LcdCanvas.cpp. This updates the ST7735S memory access control registers to change the orientation between portrait and landscape modes without requiring a hardware reset.

How does the project handle different hardware revisions of the LCD?

The configureLcd() function defines three configuration presets in an array of pico_st7735_80x160_config_t structures. Each preset specifies different horizontal and vertical offsets (H_OFS, V_OFS) and mirroring options to accommodate variations in ST7735S panel manufacturers. The user selects the appropriate configuration via the cfg_id parameter, which is stored in the device configuration menu.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →