395 lines
13 KiB
C++
395 lines
13 KiB
C++
/*
|
|
* 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 <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#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;
|
|
}
|