# 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. **Now includes a reactive game template architecture!**

## Features

- **Reactive Game Template** - Event-driven architecture optimized for e-ink displays and power efficiency
- **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

## 🎮 Reactive Game Template

The project now uses a **clean, event-driven architecture** perfect for building games and interactive applications:

### Key Features
- ⚡ **Event-Driven**: Display only updates when input is received
- 🔋 **Power Efficient**: Uses `__wfi()` to sleep between inputs (< 1mA idle)
- 📄 **E-ink Optimized**: Minimizes screen refreshes for e-paper displays
- 🎯 **Interrupt-Driven**: Touch and button handling via hardware interrupts
- 🧩 **Modular**: Clear separation of input processing, game logic, and rendering

### Architecture Highlights

```
Interrupt → Set Flag → Wake CPU → Process Input → Update Game → Draw → Refresh → Sleep
```

**Before (Polling Loop):**
```cpp
while(1) {
    if (touch_interrupt_flag) {
        // Read touch data
        // Process coordinates
        // Draw directly
        // Handle gestures inline
        refresh_screen();
    }
}
```

**After (Reactive Template):**
```cpp
while(1) {
    __wfi();  // Sleep until interrupt
    
    InputEvent input = process_button_input(config);
    if (!input.valid) {
        input = process_touch_input(config, &last_touch_time);
    }
    
    if (input.valid && game_update(&game_state, input, config, &renderer)) {
        game_draw(&game_state, &renderer, &gui);
        refresh_screen(bit_buffer, display);
    }
}
```

### Creating Your Own Game

1. **Modify GameState** - Define your game variables
2. **Implement game_init()** - Set initial values
3. **Implement game_update()** - Handle input and update state
4. **Implement game_draw()** - Render your game graphics

The reactive loop and input system work automatically!

**📖 [Read the Full Template Usage Guide](TEMPLATE_USAGE.md)** for detailed examples and patterns.

### Example Game (Included)

The template includes a **Button Navigation Game** demonstrating:
- Hardware button input handling (KEY0 switches focus, KEY1 clicks)
- GUI component usage (buttons, radio buttons, status bars)
- State management (click counters, focus tracking)
- Visual feedback (filled buttons show focus)
- E-ink optimized refreshes

Perfect starting point for your own game!

## 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
