Files
basic1/basic1.cpp
2026-01-28 17:22:15 -05:00

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;
}