Align touch and draw in one 320x240 space like CYD LVGL9.

Drop LVGL ROTATION_270 double-transform; SoftSPI rot3 maps directly to screen pixels.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-17 18:14:26 +08:00
parent 3768e545c0
commit 1381c221d9
7 changed files with 72 additions and 45 deletions

View File

@@ -1,7 +1,16 @@
# Changelog
## 2026-10-17 — Touch = draw coords (CYD LVGL9 official)
- Root cause: `LV_DISPLAY_ROTATION_270` double-transforms touch vs TFT_eSPI
(lvgl#7841 / CYD community).
- Fix per witnessmenow LVGL9 example: create display 320x240, rotation 0;
SoftSPI `setRotation(3)` map straight into the same 320x240 space.
- Red touch cursor follows reported point for visual check.
## 2026-10-17 — SoftSPI touch + LVGL inverse (fix mux)
- VSPI remux broke SD mount and froze touch at raw(0,4095).
- Touch back to SoftSPI on 25/32/39/33; SD keeps exclusive VSPI 18/19/23/5.
- Same temperature UI map, then invert LVGL ROTATION_270 for button hits.

View File

@@ -1,5 +1,5 @@
name: CYD TF card + touch speaker (cyd)
git: aa08b23
git: 3768e54
date: 2026-10-17
board: ESP32-2432S028R (ILI9341)
features: TF/SD info, SoftSPI touch, GPIO26 speaker
@@ -12,5 +12,5 @@ offsets:
sha256:
bootloader.bin 3D234A7471F67B013686DABD4DEE7C1FA915C9928463616A94BC9297ACF1ABF8
boot_app0.bin F94C5D786A7A8FAB06AC5D10E33BF37711A6697636DC037559EA19CC410A17F0
firmware.bin 3A23773415A312ABE3DAE81652F22C618A0839C8DCCA77B10AF16682DD5E2BD0
firmware.bin DC7BD1738756078B002918725F5CE6C6B349ADBF8E9ADC0A209668D07C0F00ED
partitions.bin 148B959CBFF1C38AA8E1D5C0BA9D612C54997B945E56A63F41223EEF650653A1

Binary file not shown.

View File

@@ -21,10 +21,8 @@
#define CYD_TOUCH_CLK 25
#define CYD_TOUCH_CS 33
// 物理竖屏分辨率(TFT 驱动)
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
// 横屏逻辑分辨率(LV_DISPLAY_ROTATION_270)
#define UI_HOR_RES 320
#define UI_VER_RES 240
// 横屏 UI(与 witnessmenow CYD LVGL9 官方例程一致:直接 320x240,不用 LVGL 二次旋转)
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define UI_HOR_RES SCREEN_WIDTH
#define UI_VER_RES SCREEN_HEIGHT

View File

@@ -45,10 +45,10 @@ void setup() {
lv_init();
// 官方 CYD LVGL9:直接 320x240,不用 LV_DISPLAY_ROTATION_*(避免触控二次变换错位)
// https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display/tree/main/Examples/LVGL9
lv_display_t *disp =
lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
// 与 temperature 相同:TFT rot3 + LVGL 270;触点先算 UI 坐标再反变换喂给 LVGL
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);

View File

@@ -4,9 +4,12 @@
#include <Arduino.h>
/**
* SoftSPI XPT2046(与 SD 的 VSPI 分脚,互不抢总线)
* 标定对齐 temperature(rot2 + map 到 UI 320x240),再反变换喂给 LVGL
* ROTATION_270(见 lv_indev.c):out=(y_in, 239-x_in)
* SoftSPI XPT2046 — 与 SD 分脚
*
* 对齐官方 CYD LVGL9 例程(witnessmenow):
* - 显示直接建为 320x240,LVGL rotation=0(触控不再被二次变换)
* - touch setRotation(3) + map 到 SCREEN_WIDTH/HEIGHT
* 参考:https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display/blob/main/Examples/LVGL9/
*/
int16_t g_dbg_phys_x = -1;
@@ -57,64 +60,60 @@ void touch_init() {
pinMode(CYD_TOUCH_IRQ, INPUT);
digitalWrite(CYD_TOUCH_CS, HIGH);
digitalWrite(CYD_TOUCH_CLK, LOW);
Serial.println("Touch: SoftSPI + temp map + LVGL inverse");
Serial.println("Touch: SoftSPI rot3 map 320x240 (no LVGL rot)");
}
void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) {
uint16_t raw_x = xpt_read(0x90);
uint16_t raw_y = xpt_read(0xD0);
raw_x = (uint16_t)((raw_x + xpt_read(0x90)) / 2);
raw_y = (uint16_t)((raw_y + xpt_read(0xD0)) / 2);
uint16_t adc_x = xpt_read(0x90);
uint16_t adc_y = xpt_read(0xD0);
adc_x = (uint16_t)((adc_x + xpt_read(0x90)) / 2);
adc_y = (uint16_t)((adc_y + xpt_read(0xD0)) / 2);
const bool irq = (digitalRead(CYD_TOUCH_IRQ) == LOW);
const bool in_range =
(raw_x > 80 && raw_x < 4000 && raw_y > 80 && raw_y < 4000);
(adc_x > 80 && adc_x < 4000 && adc_y > 80 && adc_y < 4000);
if (!in_range || (!irq && (raw_x < 200 || raw_y < 200))) {
if (!in_range || (!irq && (adc_x < 200 || adc_y < 200))) {
data->state = LV_INDEV_STATE_RELEASED;
g_dbg_touch_down = false;
return;
}
// XPT2046_Touchscreen setRotation(2)
const int16_t p_x = (int16_t)raw_x;
const int16_t p_y = (int16_t)(4095 - raw_y);
// XPT2046_Touchscreen setRotation(3):
// xraw = 4095 - Y_sample, yraw = 4095 - X_sample
const int16_t p_x = (int16_t)(4095 - adc_y);
const int16_t p_y = (int16_t)(4095 - adc_x);
// temperature 横屏 UI 坐标
int16_t lx = map(p_y, 240, 3800, 1, UI_HOR_RES);
int16_t ly = map(p_x, 200, 3700, UI_VER_RES, 1);
int16_t x = map(p_x, 200, 3700, 1, SCREEN_WIDTH);
int16_t y = map(p_y, 240, 3800, 1, SCREEN_HEIGHT);
if (lx < 0) {
lx = 0;
if (x < 0) {
x = 0;
}
if (ly < 0) {
ly = 0;
if (y < 0) {
y = 0;
}
if (lx >= UI_HOR_RES) {
lx = UI_HOR_RES - 1;
if (x >= SCREEN_WIDTH) {
x = SCREEN_WIDTH - 1;
}
if (ly >= UI_VER_RES) {
ly = UI_VER_RES - 1;
if (y >= SCREEN_HEIGHT) {
y = SCREEN_HEIGHT - 1;
}
// 反变换:让 LVGL ROTATION_270 之后落到 (lx, ly)
const int16_t x_in = (int16_t)((SCREEN_WIDTH - 1) - ly);
const int16_t y_in = lx;
g_dbg_phys_x = p_x;
g_dbg_phys_y = p_y;
g_dbg_logic_x = lx;
g_dbg_logic_y = ly;
g_dbg_logic_x = x;
g_dbg_logic_y = y;
g_dbg_touch_down = true;
static uint32_t last_log = 0;
if (millis() - last_log > 120) {
last_log = millis();
Serial.printf("Touch raw(%u,%u) UI(%d,%d) feed(%d,%d)\n", raw_x, raw_y, lx,
ly, x_in, y_in);
Serial.printf("Touch adc(%u,%u) p(%d,%d) -> (%d,%d)\n", adc_x, adc_y, p_x,
p_y, x, y);
}
data->point.x = x_in;
data->point.y = y_in;
data->point.x = x;
data->point.y = y;
data->state = LV_INDEV_STATE_PRESSED;
}

View File

@@ -14,6 +14,7 @@ static lv_obj_t *btn_play;
static lv_obj_t *lbl_speaker;
static lv_obj_t *lbl_play;
static lv_obj_t *lbl_touch_dbg;
static lv_obj_t *touch_cursor;
static lv_obj_t *make_row(lv_obj_t *parent, const char *title, lv_obj_t **value_out) {
lv_obj_t *row = lv_obj_create(parent);
@@ -155,6 +156,18 @@ void ui_create() {
lv_obj_set_style_text_font(lbl_touch_dbg, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(lbl_touch_dbg, lv_color_hex(0xFFB74D), 0);
// 触点光标:落在哪画在哪,验证与按钮重合
touch_cursor = lv_obj_create(scr);
lv_obj_set_size(touch_cursor, 16, 16);
lv_obj_set_style_radius(touch_cursor, LV_RADIUS_CIRCLE, 0);
lv_obj_set_style_bg_color(touch_cursor, lv_color_hex(0xFF5252), 0);
lv_obj_set_style_bg_opa(touch_cursor, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(touch_cursor, 2, 0);
lv_obj_set_style_border_color(touch_cursor, lv_color_hex(0xFFFFFF), 0);
lv_obj_add_flag(touch_cursor, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(touch_cursor, LV_OBJ_FLAG_CLICKABLE);
lv_obj_clear_flag(touch_cursor, LV_OBJ_FLAG_SCROLLABLE);
ui_refresh_audio_buttons();
}
@@ -164,11 +177,19 @@ void ui_set_touch_debug(int16_t lx, int16_t ly, bool down) {
}
if (!down) {
lv_label_set_text(lbl_touch_dbg, "Touch: up");
if (touch_cursor) {
lv_obj_add_flag(touch_cursor, LV_OBJ_FLAG_HIDDEN);
}
return;
}
char buf[40];
snprintf(buf, sizeof(buf), "Touch L:%d,%d", (int)lx, (int)ly);
lv_label_set_text(lbl_touch_dbg, buf);
if (touch_cursor) {
lv_obj_clear_flag(touch_cursor, LV_OBJ_FLAG_HIDDEN);
lv_obj_set_pos(touch_cursor, lx - 8, ly - 8);
lv_obj_move_foreground(touch_cursor);
}
}
void ui_update(const SdCardInfo &info) {