Driving two SPI displays with different resolutions and timing on the same bus is a tricky problem that often trips up embedded developers. The M5Stack Cardputer ADV External Screen project tackles this challenge head-on by providing a minimal working example and wiring guide to run the built-in ST7789 display alongside an external ILI9341. The key insight is enforcing a strict initialization order and using sprite buffering to avoid flickering and bus conflicts.
managing dual displays on the m5stack cardputer adv
The project targets the M5Stack Cardputer ADV, an ESP32-S3 based development device that comes with a built-in ST7789 display at 240×135 resolution. The goal is to add a second external ILI9341 display (240×320) connected via the GPIO header and drive both simultaneously over the shared hardware SPI bus.
The repo provides the code and an interactive HTML wiring guide to connect the external display correctly. It’s built in C++ using the Arduino IDE and leverages M5Stack’s M5GFX and M5Unified libraries, which abstract much of the low-level display and SPI handling.
Under the hood, the main technical hurdle is that both displays share the same SPI bus but have different resolutions, orientations, and timing requirements. The ST7789 is smaller and built-in, while the ILI9341 is bigger and external. Without careful handling, SPI bus conflicts or flickering occur.
The architecture relies on controlling the initialization sequence strictly: the internal ST7789 must be initialized first, followed by a 100ms delay for power stabilization, then the external ILI9341 is initialized. This sequence avoids bus conflicts that otherwise cause white screens or corrupted images.
Rendering uses separate sprite buffers for each display at their native resolutions (240×135 for ST7789 and 320×240 for ILI9341). These off-screen buffers allow flicker-free and tear-free frame updates by drawing complete frames in memory before pushing them to the displays. This approach is essential given the asynchronous timing and different refresh needs.
hardware spi bus sharing and sprite buffering for smooth rendering
What sets this project apart is its handling of the shared SPI bus and the use of sprite buffers for independent frame rendering. Sharing a single SPI bus between two displays with different specs is unusual and requires explicit coordination.
The code enforces the mandatory initialization order to prevent bus lockup or conflicts. Initializing the internal ST7789 first ensures the SPI bus is correctly configured and stable before the external display joins. The 100ms delay after initializing the internal display is critical for power stabilization.
Sprite buffers act as independent framebuffers for each display, allowing the CPU to compose frames off-screen. When a frame is ready, it is pushed to the respective display. This buffering eliminates flickering and tearing that would occur if each pixel or partial frame were sent live over SPI.
The tradeoff is increased RAM usage on the ESP32-S3, which fortunately has enough memory and PSRAM options. The project even suggests enabling OPI PSRAM if needed for larger or more complex sprite buffers.
The code is surprisingly clean and straightforward, making good use of M5Stack’s libraries to abstract SPI communication and display driver details. The repo includes troubleshooting tips for common issues like white or mirrored screens and backlight problems, reflecting practical experience.
quick start
Download thsi repo
Wiring in docs .html file
1. Hardware Setup
Connect wires according to the table
2. Software Requirements
Arduino IDE Libraries Required:
- M5Cardputer (by M5Stack)
- M5GFX (by M5Stack)
- M5Unified (by M5Stack)
3. Upload Code
- Open
dua_screen_test.inoin Arduino IDE - Select M5Cardputer board
- Select your COM por
- CPU Frequency: 240mhz
- Flash mode: QIO 80mhz
- Flash size: 8mb
- PSRAM: OPI PSRAM (If you need in your firmware)
- Upload to your Cardputer
4. Critical Initialization Order
// ⚠️ THIS SEQUENCE IS MANDATORY!
// STEP 1: Initialize Cardputer (Internal First!)
M5Cardputer.begin(cfg, true);
// STEP 2: Initialize External ILI9341
delay(100); // Wait for power stabilization
externalDisplay.init();
externalDisplay.setRotation(7);
// STEP 3: Create Sprite Buffers
intSprite.createSprite(240, 135); // Internal resolution
extSprite.createSprite(320, 240); // External resolution
verdict
This repo is a solid resource if you want to drive two SPI displays with different specs on the M5Stack Cardputer ADV. The strict initialization order and sprite buffering are practical solutions to common SPI bus sharing and flicker issues.
It’s not a plug-and-play library but rather a minimal working example and wiring guide, so some familiarity with Arduino IDE and ESP32 development is expected. The tradeoff is increased complexity in wiring and initialization, but the payoff is smooth, flicker-free dual display output.
If you work with SPI displays on ESP32 or other microcontrollers and face bus sharing challenges, this repo is worth studying. The code clarity and documentation are good for understanding the patterns. However, the approach depends on the ESP32-S3’s memory and performance capabilities, so it may not be suitable for lower-end MCUs.
Overall, this project offers practical insights and a tested recipe for a tricky embedded graphics problem that many developers encounter but few solve cleanly.
→ GitHub Repo: guicmg/cardputer_adv_external_screen ⭐ 42 · C++