/* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * * 4.0" TFT ST7796 with Touch Screen and SD Card Demo */ #include "pico/stdlib.h" #include "pico/binary_info.h" #include "st7796.h" #include "ft6336u.h" #include "sd_card.h" #include "hardware/spi.h" #include "hardware/i2c.h" #include #include #include #include "low_level_render.h" #include "low_level_gui.h" #include "ff.h" // FatFS // Binary info for RP2350 - ensures proper boot image structure bi_decl(bi_program_description("4.0\" TFT ST7796 with Touch and SD Card Demo")); bi_decl(bi_program_version_string("0.1")); bi_decl(bi_program_build_date_string(__DATE__)); const int V_WIDTH = 480; const int V_HEIGHT = 320; // Feather RP2350 with 4.0" TFT (480x320) ST7796 configuration const struct st7796_config lcd_config = { .spi = spi1, .gpio_din = 11, // MOSI (D11) .gpio_clk = 10, // SCK (D10) .gpio_cs = 7, // CS (D13) .gpio_dc = 4, // DC (D4) .gpio_rst = 9, // RST (D9) .gpio_bl = 6, // Backlight (D6) }; // Touch screen configuration (adjust pins based on your wiring) // Feather RP2350 I2C pin options: // - I2C0: SDA can be GPIO0,4,8,12,16,20,24,28 / SCL can be GPIO1,5,9,13,17,21,25,29 // - I2C1: SDA can be GPIO2,6,10,14,18,22,26 / SCL can be GPIO3,7,11,15,19,23,27 // Note: GPIO2/3 are the Feather's default I2C pins (STEMMA QT connector) // Using I2C1 to match GPIO2/3 pins // // Coordinate transformation options - adjust based on your screen orientation: // Try different combinations if touch doesn't align: // swap_xy=false, invert_x=false, invert_y=false (default) // swap_xy=true, invert_x=false, invert_y=false (90° rotation) // swap_xy=false, invert_x=true, invert_y=true (180° rotation) // swap_xy=true, invert_x=true, invert_y=true (270° rotation) const ft6336u_config_t touch_config = { .i2c = i2c1, // Changed to i2c1 to match GPIO2/3 .gpio_sda = 2, // SDA (Feather I2C default, valid for I2C1) .gpio_scl = 3, // SCL (Feather I2C default, valid for I2C1) .gpio_rst = 28, // Touch RST (can use any free GPIO) .gpio_int = 25, // Touch INT (can use any free GPIO) .screen_width = V_WIDTH, .screen_height = V_HEIGHT, .swap_xy = true, // Try this first for landscape mode .invert_x = true, // Adjust if X is backwards .invert_y = false // Adjust if Y is backwards }; // SD Card configuration (shares SPI with display) const sd_card_config_t sd_config = { .spi = spi1, // Same SPI as display .gpio_cs = 5, // SD CS (adjust to your setup) .gpio_miso = 24, // MISO (adjust to your setup) .gpio_mosi = 11, // Same as display MOSI .gpio_sck = 10 // Same as display SCK }; const int lcd_width = V_WIDTH; const int lcd_height = V_HEIGHT; // RGB565 color definitions #define COLOR_BLACK 0x0000 #define COLOR_WHITE 0xFFFF #define COLOR_RED 0xF800 #define COLOR_GREEN 0x07E0 #define COLOR_BLUE 0x001F #define COLOR_YELLOW 0xFFE0 #define COLOR_CYAN 0x07FF #define COLOR_MAGENTA 0xF81F #define COLOR_ORANGE 0xFC00 #define COLOR_PURPLE 0x8010 // Touch indicator settings #define TOUCH_RADIUS 10 uint8_t bit_buffer[V_WIDTH * V_HEIGHT / 8]; /** * @brief Convert 1-bit buffer to RGB565 and refresh the screen * * Efficiently updates the entire display by: * 1. Converting the 1-bit buffer to RGB565 format * 2. Sending the entire frame in one bulk write operation * * @param buffer Pointer to 1-bit framebuffer (width*height/8 bytes) */ void refresh_screen(const uint8_t *buffer) { // Allocate RGB565 buffer (2 bytes per pixel) uint16_t *rgb_buffer = (uint16_t *)malloc(V_WIDTH * V_HEIGHT * sizeof(uint16_t)); if (!rgb_buffer) { printf("Error: Failed to allocate RGB buffer for screen refresh\n"); return; } // Convert bit buffer to RGB565 for (int y = 0; y < V_HEIGHT; y++) { for (int x = 0; x < V_WIDTH; x++) { int byte_index = (y * V_WIDTH + x) / 8; int bit_index = 7 - (x % 8); bool pixel_on = (buffer[byte_index] >> bit_index) & 0x01; rgb_buffer[y * V_WIDTH + x] = pixel_on ? COLOR_WHITE : COLOR_BLACK; } } // Draw entire buffer at once - MUCH faster than pixel-by-pixel! st7796_set_cursor(0, 0); st7796_write(rgb_buffer, V_WIDTH * V_HEIGHT); free(rgb_buffer); } /** * @brief Test SD card and FatFS functionality * * Initializes SD card, mounts FatFS, performs read/write tests, * and safely unmounts the filesystem. */ void test_sd_card_and_fatfs(void) { // Initialize SD card bool sd_ok = sd_card_init(&sd_config); if (!sd_ok) { printf("SD Card initialization failed or no card present\n"); return; } sd_card_info_t sd_info; sd_card_get_info(&sd_info); printf("SD Card initialized: Type=%d\n", sd_info.type); // Try to read first block uint8_t buffer[512]; if (sd_card_read_block(0, buffer)) { printf("Read block 0: "); for (int i = 0; i < 16; i++) { printf("%02X ", buffer[i]); } printf("\n"); } // Test FatFS filesystem printf("\n=== FatFS Test ===\n"); FATFS fs; FRESULT res = f_mount(&fs, "0:", 1); // Mount drive 0 if (res != FR_OK) { printf("✗ FatFS mount failed (error: %d)\n", res); printf(" Make sure SD card is formatted as FAT/FAT32\n"); printf("==================\n\n"); return; } printf("✓ FatFS mounted successfully\n"); // Get volume information DWORD fre_clust, fre_sect, tot_sect; FATFS *fs_ptr; res = f_getfree("0:", &fre_clust, &fs_ptr); if (res == FR_OK) { tot_sect = (fs_ptr->n_fatent - 2) * fs_ptr->csize; fre_sect = fre_clust * fs_ptr->csize; printf(" Total: %lu KB, Free: %lu KB\n", tot_sect / 2, fre_sect / 2); } // List root directory printf("\nRoot directory contents:\n"); DIR dir; FILINFO fno; res = f_opendir(&dir, "/"); if (res == FR_OK) { int file_count = 0; while (1) { res = f_readdir(&dir, &fno); if (res != FR_OK || fno.fname[0] == 0) break; printf(" %s %s (%lu bytes)\n", (fno.fattrib & AM_DIR) ? "[DIR]" : "[FILE]", fno.fname, fno.fsize); file_count++; if (file_count >= 10) { printf(" ... (showing first 10 entries)\n"); break; } } f_closedir(&dir); if (file_count == 0) { printf(" (empty)\n"); } } // Test file write printf("\nTesting file write...\n"); FIL fil; res = f_open(&fil, "test.txt", FA_CREATE_ALWAYS | FA_WRITE); if (res == FR_OK) { const char *test_str = "Hello from RP2350 with FatFS!\n"; UINT bytes_written; res = f_write(&fil, test_str, strlen(test_str), &bytes_written); f_close(&fil); if (res == FR_OK) { printf("✓ Wrote %u bytes to test.txt\n", bytes_written); // Read it back res = f_open(&fil, "test.txt", FA_READ); if (res == FR_OK) { char read_buffer[64]; UINT bytes_read; res = f_read(&fil, read_buffer, sizeof(read_buffer)-1, &bytes_read); f_close(&fil); if (res == FR_OK) { read_buffer[bytes_read] = '\0'; printf("✓ Read back: %s", read_buffer); } } } } else { printf("✗ Failed to create test.txt (error: %d)\n", res); } // Safely unmount filesystem printf("\nUnmounting filesystem...\n"); res = f_unmount("0:"); if (res == FR_OK) { printf("✓ Filesystem unmounted successfully\n"); } else { printf("✗ Unmount failed (error: %d)\n", res); } printf("==================\n\n"); } int main() { // Initialize standard I/O for debugging with timeout // This prevents hanging when USB is not connected stdio_init_all(); sleep_ms(5000); // Wait for USB connection (if present) printf("Initializing 4.0\" TFT with Touch and SD Card...\n"); // Initialize the LCD st7796_init(&lcd_config, lcd_width, lcd_height); st7796_fill(COLOR_BLACK); LowLevelRenderer renderer(bit_buffer, V_WIDTH, V_HEIGHT); renderer.set_font(&font_5x5_obj); LowLevelGUI gui = LowLevelGUI(&renderer, font_BMplain_obj); LowLevelWindow *w1 = gui.draw_new_window(15, 15, V_WIDTH - 30, V_HEIGHT - 30, "Main Window"); gui.draw_status_bar(w1, 10, 40, 200, "PANELS", "Weekly Average Charge", 65, "190KWH"); gui.draw_circular_gauge(w1, 10, 100 - 10, 200, "SYSTEM EFF.", 68); // Refresh the screen with the rendered GUI refresh_screen(bit_buffer); // Initialize touch screen bool touch_ok = ft6336u_init(&touch_config); if (touch_ok) { uint8_t chip_id = ft6336u_get_chip_id(); uint8_t fw_ver = ft6336u_get_firmware_version(); printf("Touch initialized: Chip ID=0x%02X, FW Ver=0x%02X\n", chip_id, fw_ver); // Run I2C communication test printf("\nRunning I2C reliability test...\n"); ft6336u_test_i2c(); printf("\n"); } else { printf("Touch initialization failed!\n"); } // Test SD card and FatFS test_sd_card_and_fatfs(); // Main loop - handle touch events int last_x = -1, last_y = -1; uint16_t current_color = COLOR_CYAN; int color_index = 0; uint16_t colors[] = {COLOR_CYAN, COLOR_YELLOW, COLOR_MAGENTA, COLOR_GREEN, COLOR_RED}; // Touch debouncing uint32_t last_touch_time = 0; const uint32_t debounce_ms = 20; // Minimum time between touch reads bool was_touched = false; int touch_fail_count = 0; int touch_success_count = 0; printf("Entering main touch loop...\n"); while (1) { uint32_t now = to_ms_since_boot(get_absolute_time()); // Check if enough time has passed since last touch check if (now - last_touch_time < debounce_ms) { sleep_ms(1); continue; } bool is_touched = touch_ok && ft6336u_is_touched(); // Only process touch if state changed or still touching if (is_touched) { ft6336u_touch_data_t touch_data; if (ft6336u_read_touch(&touch_data)) { touch_success_count++; if (touch_data.touch_count > 0) { int16_t x = touch_data.points[0].x; int16_t y = touch_data.points[0].y; // Only print occasionally to avoid flooding serial //if (touch_success_count % 5 == 0) { printf("Touch: X=%d, Y=%d, Event=%d [Success: %d, Fail: %d]\n", x, y, touch_data.points[0].event, touch_success_count, touch_fail_count); //} // Check if touch is in title area to change color if (y < 30) { if (!was_touched) { // Only on new touch color_index = (color_index + 1) % 5; current_color = colors[color_index]; // Clear drawing area st7796_fill_rect(11, 130, lcd_width - 11, lcd_height - 11, COLOR_BLACK); printf("Color changed to index %d\n", color_index); } } // Draw in touch area else if (y > 100) { // Draw line from last position (for smooth drawing) if (last_x >= 0 && last_y >= 0) { int dx = abs(x - last_x); int dy = abs(y - last_y); // Only draw line if movement is reasonable (filter noise) if (dx < 50 && dy < 50) { st7796_draw_line(last_x, last_y, x, y, current_color); } } last_x = x; last_y = y; } was_touched = true; last_touch_time = now; } } else { // Touch detected but read failed touch_fail_count++; if (touch_fail_count % 10 == 0) { printf("Touch read failed (count: %d)\n", touch_fail_count); } } } else { // Reset last position when not touching if (was_touched) { last_x = -1; last_y = -1; was_touched = false; } } sleep_ms(5); // Faster polling for better responsiveness } return 0; }