lvgl_multi_screen

| Supported Targets | ESP32-P4 | ESP32-S3 | ESP32-C3 |
| ----------------- | -------- | -------- | -------- |

| Supported LCD Controller | GC9A01 |
| ------------------------ | ------ |

# LVGL Multi-Screen Example

This example demonstrates how to drive **two SPI LCD panels simultaneously** using LVGL with the `esp_lvgl_adapter` component. It shows how to share a single SPI bus between multiple displays and render independent content on each screen.

## Overview

The example showcases:
- **Dual SPI display support**: Two LCD panels on the same SPI bus
- **Independent rendering**: Each display has its own LVGL display object
- **Different content per screen**: First screen runs benchmark, second shows custom UI
- **Scalable design**: Can be extended to more displays (limited by CS pins)

## Use Cases

This multi-screen approach is ideal for:
- **Dashboard systems**: Multiple data displays (gauges, charts, status)
- **Gaming devices**: Main screen + secondary info display
- **Industrial HMI**: Primary control + auxiliary information panels
- **Wearables**: Multiple watch faces or data screens
- **Smart home controllers**: Different rooms or device controls
- **Medical devices**: Patient data + settings/alerts on separate screens

## How It Works

**Key points:**
- **Shared signals**: MOSI, SCLK, DC, RST, Backlight
- **Individual CS pins**: Each display has unique chip select
- **Bus arbitration**: SPI driver handles concurrent access automatically
- **Independent control**: Each display can be updated separately

## Hardware Requirements

### Required Components

* An ESP32 or ESP32-S3 development board (other ESP32 series also supported)
* **Two GC9A01 LCD panels** (240x240 round displays)
  - Operating voltage: 3.3V
  - Interface: SPI (4-wire)
  - Common on affordable development kits
* Jumper wires for connections
* USB cable for power and programming

**Reference Hardware (Single Display with Various LCD Interfaces):**

For single display examples with various LCD interfaces, please refer to:

| Chip | LCD Interface | Development Board |
|------|---------------|-------------------|
| ESP32-P4 | MIPI DSI | [ESP32-P4-Function-EV-Board](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/index.html) |
| ESP32-S3 | RGB | [ESP32-S3-LCD-EV-Board](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/index.html) |
| ESP32-S3 | QSPI | [EchoEar](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/echoear/index.html) |
| ESP32-S3 | SPI | [ESP32-S3-BOX-3](https://github.com/espressif/esp-box/blob/master/docs/hardware_overview/esp32_s3_box_3/hardware_overview_for_box_3.md) |
| ESP32-C3 | SPI | [ESP32-C3-LCDkit](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32c3/esp32-c3-lcdkit/index.html) |

## Hardware Connection

### Default GPIO Pin Mapping

| Target | MOSI | SCLK | DC | RST | CS_0 | CS_1 | BL |
|--------|------|------|----|-----|------|------|----|
| ESP32-S3 / ESP32-P4 | GPIO 38 | GPIO 45 | GPIO 47 | GPIO 21 | GPIO 41 | GPIO 48 | GPIO 39 |
| ESP32-C3 | GPIO 0 | GPIO 1 | GPIO 7 | GPIO 2 | GPIO 5 | GPIO 8 | GPIO 3 |

### Wiring Diagram

```
ESP32-S3 Dev Board          Display 0 (GC9A01)      Display 1 (GC9A01)
┌──────────────┐            ┌─────────────┐         ┌─────────────┐
│              │            │             │         │             │
│     3.3V ────┼────────────┤ VCC         │    ┌────┤ VCC         │
│              │            │             │    │    │             │
│     GND ─────┼────────────┤ GND         │────┼────┤ GND         │
│              │            │             │    │    │             │
│  GPIO45 (SCK)┼────────────┤ SCL         │────┼────┤ SCL         │
│              │            │             │    │    │             │
│ GPIO38 (MOSI)┼────────────┤ SDA         │────┼────┤ SDA         │
│              │            │             │    │    │             │
│  GPIO47 (DC) ┼────────────┤ DC          │────┼────┤ DC          │
│              │            │             │    │    │             │
│  GPIO21 (RST)┼────────────┤ RST         │────┼────┤ RST         │
│              │            │             │    │    │             │
│  GPIO39 (BL) ┼────────────┤ BL          │────┴────┤ BL          │
│              │            │             │         │             │
│  GPIO41 ─────┼────────────┤ CS          │         │             │
│              │            │             │         │             │
│  GPIO48 ─────┼────────────┼─────────────┼─────────┤ CS          │
│              │            │             │         │             │
└──────────────┘            └─────────────┘         └─────────────┘
```

**Notes:**
- All signals except CS are shared
- Each display must have a unique CS pin
- Both displays share the same backlight control
- Common ground is essential for reliable operation

### Changing GPIO Pins

Edit `hw_multi.h` to customize GPIO assignments:

```c
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4
#define BSP_LCD_DATA0   (GPIO_NUM_38)
#define BSP_LCD_PCLK    (GPIO_NUM_45)
#define BSP_LCD_DC      (GPIO_NUM_47)
#define BSP_LCD_RST     (GPIO_NUM_21)
#define BSP_LCD_CS_0    (GPIO_NUM_41)
#define BSP_LCD_CS_1    (GPIO_NUM_48)
#define BSP_LCD_BL      (GPIO_NUM_39)
#elif CONFIG_IDF_TARGET_ESP32C3
#define BSP_LCD_DATA0   (GPIO_NUM_0)
#define BSP_LCD_PCLK    (GPIO_NUM_1)
#define BSP_LCD_DC      (GPIO_NUM_7)
#define BSP_LCD_RST     (GPIO_NUM_2)
#define BSP_LCD_CS_0    (GPIO_NUM_5)
#define BSP_LCD_CS_1    (GPIO_NUM_8)
#define BSP_LCD_BL      (GPIO_NUM_3)
#endif
```

## Software Architecture

### Display Initialization Flow

```
1. Initialize shared SPI bus (SPI2_HOST)
   ↓
2. Configure backlight GPIO
   ↓
3. For each display (i = 0, 1):
   ├── Create panel IO with unique CS pin
   ├── Install GC9A01 panel driver
   ├── Reset panel
   ├── Initialize panel
   ├── Set rotation
   └── Turn on display
   ↓
4. Initialize LVGL adapter
   ↓
5. Register both displays to LVGL
   ↓
6. Create different UI for each display
```

### Multi-Display Management

**Key data structure:**
```c
typedef struct {
    esp_lcd_panel_handle_t panel;
    esp_lcd_panel_io_handle_t panel_io;
} hw_multi_panel_t;

hw_multi_panel_t panels[2];
lv_display_t *displays[2];
```

**Display selection in LVGL:**
```c
// Set default display before creating UI
lv_display_set_default(displays[0]);
lv_demo_benchmark();  // Runs on display 0

lv_display_set_default(displays[1]);
lv_label_create(...);  // Creates on display 1
```

## Build and Flash

### Configure the Project

Run `idf.py menuconfig` and navigate to `Hardware Configuration`:

**Display Rotation:**
- Choose rotation for both displays (0°/90°/180°/270°)
- Both displays use the same rotation setting
- Useful for different physical mounting orientations

### Build and Flash

1. Set the target chip:
```bash
idf.py set-target esp32s3
# or esp32, esp32c3, etc.
```

2. Build and flash:
```bash
idf.py -p PORT build flash monitor
```

(To exit the serial monitor, type ``Ctrl-]``.)

See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.

### Expected Output

**Display 0 (Primary):**
- Runs LVGL benchmark demo
- Cycles through various rendering tests
- Shows animations, shapes, text, gradients

**Display 1 (Secondary):**
- Shows simple text: "Second display"
- Centered on the screen
- Can be customized to show any LVGL content

**Serial Console:**
```
I (xxx) hw_multi: SPI bus initialized
I (xxx) hw_multi: Display 0 initialized (240x240, rotation 0)
I (xxx) hw_multi: Display 1 initialized (240x240, rotation 0)
I (xxx) main: Display 0 registered
I (xxx) main: Display 1 registered
```

**If FPS monitoring enabled:**
```
I (xxx) main: Current FPS: 30
```

### Synchronized Updates

If you need both displays to update simultaneously:

```c
if (esp_lv_adapter_lock(-1) == ESP_OK) {
    // Update display 0
    lv_display_set_default(displays[0]);
    lv_label_set_text(label0, "Updated");
    
    // Update display 1
    lv_display_set_default(displays[1]);
    lv_label_set_text(label1, "Updated");
    
    esp_lv_adapter_unlock();
}
```

## Technical Details

### SPI Bus Configuration

```c
spi_bus_config_t buscfg = {
    .sclk_io_num = BSP_LCD_PCLK,
    .mosi_io_num = BSP_LCD_DATA0,
    .miso_io_num = GPIO_NUM_NC,        // Not used for LCD
    .max_transfer_sz = 240 * 80 * 2,   // 80 lines buffer
};
spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
```

### Display Registration

Each display needs:
1. Panel IO handle (with unique CS)
2. Panel handle (driver instance)
3. LVGL display object

```c
esp_lv_adapter_display_config_t display_config = 
    ESP_LV_ADAPTER_DISPLAY_SPI_WITHOUT_PSRAM_DEFAULT_CONFIG(
        panel, panel_io, 240, 240, rotation);

lv_display_t *disp = esp_lv_adapter_register_display(&display_config);
```

### Thread Safety

LVGL is **not thread-safe**. Always use the mutex:

```c
if (esp_lv_adapter_lock(-1) == ESP_OK) {
    // All LVGL API calls here
    lv_obj_t *label = lv_label_create(screen);
    lv_label_set_text(label, "Safe");
    
    esp_lv_adapter_unlock();
}
```

## Troubleshooting

**Only one display works:**
- Verify both CS pins are different
- Check CS pin connections
- Ensure both panels have power and ground
- Check serial logs for initialization errors

**Second display blank:**
- Check that `panel_count` is 2 in logs
- Verify display initialization succeeded
- Check backlight is on (`gpio_set_level(BSP_LCD_BL, 1)`)
- Try swapping CS pins to isolate hardware issue

For any technical queries, please open an [issue](https://github.com/espressif/esp-iot-solution/issues) on GitHub. We will get back to you soon.

To create a project from this example, run:

idf.py create-project-from-example "espressif/esp_lvgl_adapter=0.1.0-beta.1:lvgl_multi_screen"

or download archive (~10.15 KB)