# RP2350 TFT Display with Touch and SD Card Demo A modular embedded application for RP2350 microcontrollers featuring display, touch, and SD card support with hardware abstraction layers. ## Features - **Display Abstraction Layer** - Support for multiple display types (ST7796, ST7789, E-Paper) - **Touch Abstraction Layer** - Extensible touch controller support (FT6336U) - **SD Card with FatFS** - File system support with board-aware initialization - **Multi-Board Support** - Single-file board configuration system for easy board switching - **1-bit Rendering** - Memory-efficient monochrome graphics with GUI widgets - **Automated Build Scripts** - Build for one board or all boards with single commands - **Hardware Abstraction** - Factory pattern for displays and touch controllers ## Supported Hardware Configurations ### Available Board Configurations 1. **BOARD_FEATHER_TFT** - Adafruit Feather RP2350 with 4.0" TFT ST7796 - Display: ST7796 (480x320 RGB TFT) - Touch: FT6336U capacitive touch - Features: High-speed refresh, backlight control, SD card support 2. **BOARD_PICO2_TFT** - Raspberry Pi Pico 2 with 4.0" TFT ST7796 - Display: ST7796 (480x320 RGB TFT) - Touch: FT6336U capacitive touch - Features: High-speed refresh, backlight control, SD card support 3. **BOARD_PICO2_EINK** - Raspberry Pi Pico 2 with E-Ink Display - Display: E-Paper (400x300 monochrome) - Touch: None (uses hardware buttons KEY0/KEY1) - Features: Ultra-low power, partial/full refresh modes ### Supported Displays - **ST7796** - 480x320 RGB TFT LCD (Fully implemented) - **E-Paper** - Various sizes, monochrome (Fully implemented) - **ST7789** - Ready for driver implementation ### Supported Touch Controllers - **FT6336U** - I2C capacitive touch with gesture support (Fully implemented) - Extensible architecture for additional controllers ## Board Configuration System ### Switching Between Boards The project uses a single-file configuration system. To switch boards, simply edit `board_config.h` and uncomment your target board: ```cpp // ---- SELECT YOUR BOARD HERE ---- // #define BOARD_FEATHER_TFT // Feather RP2350 + 4.0" TFT ST7796 // #define BOARD_PICO2_TFT // Pico 2 + 4.0" TFT ST7796 #define BOARD_PICO2_EINK // Pico 2 + E-Ink Display // -------------------------------- ``` **Important:** Only one board should be uncommented at a time! ### What Gets Configured Each board configuration includes: - Display type and resolution - All pin assignments (SPI, I2C, GPIO) - Touch controller configuration - Coordinate transformation (rotation/inversion) - SD card pins - Hardware button pins (e-ink boards) - Communication speeds (SPI/I2C baud rates) ### Board-Specific Files Board configurations are located in `board_configs/`: - `board_feather_tft.h` - Feather RP2350 + TFT configuration - `board_pico2_tft.h` - Pico 2 + TFT configuration - `board_pico2_eink.h` - Pico 2 + E-Ink configuration ## Building and Flashing ### Method 1: Build for Currently Selected Board The simplest method - builds for whatever board is selected in `board_config.h`: ```bash ./build_and_flash.sh ``` This script will: 1. Detect which board is selected in `board_config.h` 2. Configure CMake if needed 3. Build the project using Ninja 4. Flash to the board using picotool (if board is in BOOTSEL mode) To build without flashing: ```bash mkdir -p build cd build cmake -G Ninja .. ninja ``` The output file will be `build/basic1.uf2`. ### Method 2: Build for All Boards To create UF2 files for all supported boards in one command: ```bash ./build_all_boards.sh ``` This script will: 1. Backup your current `board_config.h` 2. Build each board configuration automatically 3. Generate board-specific output files: - `basic1_feather_tft.uf2` - `basic1_pico2_tft.uf2` - `basic1_pico2_eink.uf2` 4. Restore your original `board_config.h` This is useful for: - Creating releases for multiple hardware variants - Testing code on all board configurations - Batch building without manual configuration changes ### Manual Flashing 1. Hold the **BOOTSEL** button while connecting USB 2. The board appears as a USB drive (e.g., `RPI-RP2`) 3. Copy the appropriate `.uf2` file to the drive: ```bash cp basic1.uf2 /Volumes/RPI-RP2/ ``` 4. The board automatically reboots and runs the program ### Using picotool If picotool is installed at `~/.pico-sdk/picotool/`: ```bash # Load and auto-reboot picotool load build/basic1.uf2 -fx # Or load without auto-reboot picotool load build/basic1.uf2 ``` ## Project Structure ``` basic1/ ├── basic1.cpp # Main application ├── board_config.h # Board selector (edit this to switch boards!) ├── board_configs/ # Board-specific configurations │ ├── board_feather_tft.h # Feather RP2350 + TFT pin config │ ├── board_pico2_tft.h # Pico 2 + TFT pin config │ ├── board_pico2_eink.h # Pico 2 + E-Ink pin config │ └── README.md # Board config documentation │ ├── CMakeLists.txt # Build configuration ├── build_all_boards.sh # Build all boards automatically ├── build_and_flash.sh # Build and flash current board │ ├── display/ # Display and GUI abstraction layer │ ├── low_level_display.h # Display interface (factory pattern) │ ├── low_level_display_st7796.cpp # ST7796 TFT implementation │ ├── low_level_display_epaper.cpp # E-Paper implementation │ ├── low_level_touch.h # Touch interface (factory pattern) │ ├── low_level_touch_ft6336u.cpp # FT6336U touch implementation │ ├── low_level_render.h/cpp # 1-bit rendering engine │ └── low_level_gui.h/cpp # GUI widgets (buttons, gauges, etc.) │ ├── lib/ # Hardware drivers │ ├── ft6336u/ # FT6336U capacitive touch driver │ ├── st7796/ # ST7796 TFT display driver │ ├── epaper/ # E-Paper display driver │ ├── sd_card/ # SD card driver with FatFS test │ └── fatfs/ # FatFS filesystem │ ├── fonts/ # 25+ bitmap font definitions (5x5 to 8x8) └── fatfs_time.c # FatFS timestamp support ``` ## Adding a New Board Configuration 1. Create a new config file in `board_configs/`: ```bash cp board_configs/board_pico2_tft.h board_configs/board_myboard.h ``` 2. Edit the new file and update all pin definitions for your hardware 3. Add your board to `board_config.h`: ```cpp // ---- SELECT YOUR BOARD HERE ---- // #define BOARD_FEATHER_TFT // #define BOARD_PICO2_TFT // #define BOARD_PICO2_EINK #define BOARD_MYBOARD // Your new board // -------------------------------- ``` 4. Add the include section: ```cpp #ifdef BOARD_FEATHER_TFT #include "board_configs/board_feather_tft.h" // ... other boards ... #elif defined(BOARD_MYBOARD) #include "board_configs/board_myboard.h" #endif ``` 5. (Optional) Add to `build_all_boards.sh` for automated builds See `board_configs/README.md` for detailed configuration structure. ## Hardware Configuration Details ### Display Types Each board configuration specifies a display type via `DISPLAY_TYPE_SELECTED`: - `DISPLAY_TYPE_ST7796_VAL` (0) - ST7796 TFT (480x320) - `DISPLAY_TYPE_ST7789_VAL` (1) - ST7789 TFT (ready for implementation) - `DISPLAY_TYPE_EPAPER_VAL` (2) - E-Paper displays (various sizes) ### Touch Controller Types Each board configuration specifies a touch type via `TOUCH_TYPE_SELECTED`: - `TOUCH_TYPE_FT6336U_VAL` (0) - FT6336U capacitive touch with gesture support - `TOUCH_TYPE_NONE_VAL` (1) - No touch controller (use hardware buttons) ### Touch Coordinate Transformation Touch coordinates can be adjusted for different mounting orientations: ```c #define TOUCH_SWAP_XY true // Swap X/Y coordinates (for 90° rotation) #define TOUCH_INVERT_X true // Invert X axis (mirror horizontally) #define TOUCH_INVERT_Y false // Invert Y axis (mirror vertically) ``` These settings are in each board's config file under `board_configs/`. ### Pin Assignments Each board config file defines all hardware pin connections: **Display Pins:** - `DISPLAY_SPI_PORT` - SPI bus (spi0 or spi1) - `DISPLAY_SCK_PIN`, `DISPLAY_MOSI_PIN`, `DISPLAY_MISO_PIN` - SPI data lines - `DISPLAY_CS_PIN` - Chip select (active LOW) - `DISPLAY_DC_PIN` - Data/Command select - `DISPLAY_RST_PIN` - Hardware reset - `DISPLAY_BL_PIN` - Backlight control (TFT only, -1 for e-ink) - `DISPLAY_BUSY_PIN` - Busy signal (E-Paper only, -1 for TFT) **Touch Pins:** - `TOUCH_I2C_PORT` - I2C bus (i2c0 or i2c1) - `TOUCH_SDA_PIN`, `TOUCH_SCL_PIN` - I2C data lines - `TOUCH_INT_PIN` - Interrupt pin (active LOW when touch detected) - `TOUCH_RST_PIN` - Hardware reset **Button Pins (E-Ink boards):** - `BUTTON_KEY0_PIN` - First hardware button (active LOW) - `BUTTON_KEY1_PIN` - Second hardware button (active LOW) **SD Card Pins (optional):** - `SD_SPI_PORT`, `SD_CS_PIN`, `SD_MISO_PIN`, `SD_MOSI_PIN`, `SD_SCK_PIN` ### Communication Speeds Configured per board based on display type: - `SPI_BAUDRATE` - Display SPI speed (32MHz for TFT, 4MHz for e-ink) - `I2C_BAUDRATE` - Touch controller I2C speed (typically 400kHz) ## Architecture ### Display Abstraction Layer Provides a unified interface for different display types: ```cpp class LowLevelDisplay { public: virtual bool init() = 0; virtual void clear(bool white = true) = 0; virtual void draw_buffer(const uint8_t* bit_buffer) = 0; virtual void refresh() = 0; // ... more methods static LowLevelDisplay* create(DisplayType type, int width, int height); }; ``` Usage: ```cpp LowLevelDisplay* display = LowLevelDisplay::create( (DisplayType)DISPLAY_TYPE_SELECTED, V_WIDTH, V_HEIGHT); display->init(); display->draw_buffer(buffer); display->refresh(); ``` ### Touch Abstraction Layer Unified interface for touch controllers: ```cpp class LowLevelTouch { public: virtual bool init() = 0; virtual bool read_touch(TouchData* data) = 0; virtual bool is_touched() = 0; // ... more methods static LowLevelTouch* create(TouchType type, int width, int height, bool swap_xy, bool invert_x, bool invert_y); }; ``` Usage: ```cpp LowLevelTouch* touch = LowLevelTouch::create( (TouchType)TOUCH_TYPE_SELECTED, V_WIDTH, V_HEIGHT, TOUCH_SWAP_XY, TOUCH_INVERT_X, TOUCH_INVERT_Y); TouchData touch_data; if (touch->read_touch(&touch_data)) { int x = touch_data.points[0].x; int y = touch_data.points[0].y; // Handle touch } ``` ### SD Card Abstraction Board-aware initialization: ```cpp // Initialize with board configuration if (sd_card_init_with_board_config()) { // Test FatFS functionality sd_card_test_fatfs(); } ``` The test function: - Mounts FatFS - Lists directory contents - Creates and reads test file - Safely unmounts filesystem ## Pin Configurations Summary Actual pin assignments are defined in `board_configs/*.h` files. Here's a quick reference: ### BOARD_FEATHER_TFT (Adafruit Feather RP2350) - **Display (ST7796, SPI1):** SCK=18, MOSI=19, MISO=20, CS=17, DC=16, RST=15, BL=14 - **Touch (FT6336U, I2C0):** SDA=4, SCL=5, INT=6, RST=7 - **SD Card (SPI1):** CS=10 (shares SPI with display) ### BOARD_PICO2_TFT (Raspberry Pi Pico 2) - **Display (ST7796, SPI0):** SCK=2, MOSI=3, MISO=4, CS=5, DC=6, RST=7, BL=8 - **Touch (FT6336U, I2C0):** SDA=12, SCL=13, INT=14, RST=15 - **SD Card (SPI1):** CS=17 ### BOARD_PICO2_EINK (Raspberry Pi Pico 2 + E-Ink) - **Display (E-Paper, SPI0):** SCK=10, MOSI=11, MISO=12, CS=9, DC=8, RST=12, BUSY=13 - **Touch:** None (uses hardware buttons instead) - **Buttons:** KEY0=GP15 (active LOW), KEY1=GP17 (active LOW) See individual board config files in `board_configs/` for complete details. ## Extending the System ### Adding a New Display Driver 1. Create implementation files: ``` display/low_level_display_mynewdisplay.h display/low_level_display_mynewdisplay.cpp ``` 2. Inherit from `LowLevelDisplay` base class and implement all virtual methods 3. Add a new constant in `board_config.h`: ```cpp #define DISPLAY_TYPE_MYNEWDISPLAY_VAL 3 ``` 4. Update the factory in `display/low_level_display.cpp`: ```cpp case 3: // DISPLAY_TYPE_MYNEWDISPLAY display = new LowLevelDisplayMyNewDisplay(width, height); break; ``` 5. Update `CMakeLists.txt` to compile the new files ### Adding a New Touch Controller 1. Create implementation files: ``` display/low_level_touch_mynewtouch.h display/low_level_touch_mynewtouch.cpp ``` 2. Inherit from `LowLevelTouch` base class 3. Add constant and factory case similar to display drivers 4. Update CMakeLists.txt ### Workflow Summary The typical development workflow: 1. **Select target board:** Edit `board_config.h`, uncomment your board 2. **Build:** Run `./build_and_flash.sh` (builds and flashes current board) 3. **Test:** Connect to USB, monitor via serial terminal 4. **Switch boards:** Edit `board_config.h`, rebuild 5. **Release:** Run `./build_all_boards.sh` to create UF2s for all boards ## Memory Usage - **1-bit framebuffer:** Width×Height÷8 bytes - 480×320: 19.2 KB - 400×300: 15.0 KB - **Display conversion:** Automatic 1-bit → RGB565 (TFT) or native (E-Paper) - **Stack/heap:** Minimal, uses static buffers where possible - **Code size:** ~100-150 KB depending on features enabled ## Troubleshooting ### Build Issues **Error: "No board selected!"** - You must uncomment exactly one `BOARD_xxx` define in `board_config.h` **CMake configuration errors:** - Delete `build/` directory and reconfigure: `rm -rf build && mkdir build` ### Display Issues **Display stays blank:** - Check SPI wiring (especially CS, DC, SCK, MOSI pins) - Verify power supply (some displays need 5V logic level shifters) - Check `DISPLAY_TYPE_SELECTED` matches your hardware **Wrong colors or garbled display:** - Verify display driver type (ST7796 vs ST7789) - Check SPI baud rate (try reducing from 32MHz to 16MHz) ### Touch Issues **Touch not responding:** - Verify I2C wiring (SDA, SCL, INT, RST pins) - Check for pull-up resistors on I2C lines (typically 4.7kΩ) - Confirm touch controller type and I2C address - Enable debug output to see if touch data is being read **Touch coordinates inverted or swapped:** - Adjust `TOUCH_SWAP_XY`, `TOUCH_INVERT_X`, `TOUCH_INVERT_Y` in board config - These depend on physical mounting orientation **Interrupt not triggering:** - Verify `TOUCH_INT_PIN` is correctly defined - Check INT pin is pulled high (internal pull-up or external resistor) - Confirm touch controller is configured for interrupt mode ### SD Card Issues **SD Card not detected:** - Verify card is inserted and formatted (FAT32) - Check SPI wiring and CS pin - Ensure SD card shares SPI correctly with display (different CS pins) - Try different SD card (some old/large cards have compatibility issues) ### E-Paper Display Issues **Ghosting or partial images:** - Use `full_refresh()` periodically to clear ghosting - E-Paper retains previous image until fully refreshed **Slow refresh:** - Normal for e-paper (several seconds for full refresh) - Use partial refresh for faster updates (may cause ghosting) **BUSY pin timeout:** - Verify `DISPLAY_BUSY_PIN` is correctly connected - Increase timeout in e-paper driver if needed ## Development Tips ### Serial Debugging Connect via USB and monitor serial output: ```bash # macOS screen /dev/tty.usbmodem* 115200 # Linux screen /dev/ttyACM0 115200 # Exit screen: Ctrl+A, then K ``` The code includes `printf()` statements for debugging touch, display, and SD card operations. ### Power Optimization For battery-powered projects: - E-Paper displays use almost no power when idle - Use `__wfi()` (Wait For Interrupt) to sleep between inputs - Disable TFT backlight when idle - Lower SPI/I2C baud rates to reduce power consumption ### Performance Tips **TFT Displays:** - Increase SPI baud rate up to 62.5MHz if display supports it - Minimize full-screen refreshes (use partial updates if possible) - 1-bit rendering is much faster than RGB565 **E-Paper Displays:** - Use partial refresh for responsive UI (accepts some ghosting) - Full refresh only when needed (menu changes, game over, etc.) - Consider caching frequently used graphics ## Features by Component ### Display Features - 1-bit monochrome rendering - RGB565 color support (ST7796) - Drawing primitives (lines, rectangles, circles) - GUI widgets (windows, gauges, status bars) - Multiple font support ### Touch Features - Multi-touch support (up to 2 points) - Coordinate transformation - Touch debouncing - Event types (press, lift, contact) ### SD Card Features - SPI mode support - FatFS integration - Directory listing - File read/write - Automatic timestamps from compile time ## License Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. SPDX-License-Identifier: Apache-2.0 ## Dependencies - Raspberry Pi Pico SDK 2.2.0+ - FatFS (included) - TinyUSB (via SDK) - Hardware drivers (included in `lib/`) ## Contributing When adding new features: 1. Follow the abstraction layer pattern 2. Update `board_config.h` for hardware settings 3. Keep application code hardware-agnostic 4. Test on multiple boards if possible 5. Update this README