From 4fc70f13d09a9e1f61121c7079643c3c1cce2f35 Mon Sep 17 00:00:00 2001 From: patri Date: Fri, 23 Oct 2026 18:20:35 +0200 Subject: [PATCH] 12-OBD-Speed: settings page with screen rotation (0/90/180/270) - third tile (swipe left from the menu): rotation buttons, saved in NVS, applied at boot - shared lib: setRotation() rotates only the panel and redraws; touch points are converted in touchpad_read() for the current rotation; getRotation() added Co-Authored-By: Claude Opus 5.5 --- 12-OBD-Speed/README.md | 1 + 12-OBD-Speed/src/main.cpp | 69 +++++++++++++++++++++++- CLAUDE.md | 2 +- shared/lib/JC3636W518/src/jc3636w518.cpp | 53 ++++++++++++------ shared/lib/JC3636W518/src/jc3636w518.h | 3 +- 5 files changed, 107 insertions(+), 21 deletions(-) diff --git a/12-OBD-Speed/README.md b/12-OBD-Speed/README.md index 1145331..87927f0 100644 --- a/12-OBD-Speed/README.md +++ b/12-OBD-Speed/README.md @@ -9,6 +9,7 @@ Uses the shared board library and LVGL 9.6 (`../shared`), NimBLE-Arduino 2.5.1. - **Swipe left → adapter menu** — scans while open. OBD-like adapters (name OBD/ELM/…, or service FFF0/FFE0/18F0) are listed first in green. **Tap one** to select it: it is remembered (NVS) and connected immediately. **Forget** clears the remembered adapter, **Done** (or swipe right) returns to the speed page. +- **Swipe left again → settings** — screen rotation 0 / 90 / 180 / 270 degrees (tap to apply; remembered in NVS, applied at boot). - On power-up the remembered adapter is connected automatically; if the link drops or the adapter is not there (car off), it retries every 3 s. With no adapter remembered, the device starts on the menu page. diff --git a/12-OBD-Speed/src/main.cpp b/12-OBD-Speed/src/main.cpp index 18ab26b..8212965 100644 --- a/12-OBD-Speed/src/main.cpp +++ b/12-OBD-Speed/src/main.cpp @@ -1,6 +1,8 @@ // OBD speed display for the JC3636W518: vehicle speed from an ELM327 over BLE on top of an animated GIF. // Swipe left for the adapter menu (scan, select, forget); the selected adapter is remembered and reconnected. +// Swipe left again for settings (screen rotation, remembered). #include +#include #include "jc3636w518.h" #include "obd_link.h" @@ -19,7 +21,8 @@ static const lv_color_t COLOR_ARC = lv_color_hex(0x00C8FF); static const lv_color_t COLOR_OBD = lv_color_hex(0x4CD964); static const lv_color_t COLOR_PANEL = lv_color_hex(0x161C22); -static lv_obj_t *tileview, *tile_speed, *tile_menu; +static lv_obj_t *tileview, *tile_speed, *tile_menu, *tile_settings; +static lv_obj_t *rot_buttons[4]; static lv_obj_t *speed_arc, *speed_label, *speed_shadow, *status_label; static lv_obj_t *menu_saved, *menu_status, *menu_list; static lv_obj_t *menu_items[MAX_MENU_ITEMS]; @@ -79,6 +82,33 @@ static void on_done_clicked(lv_event_t *e) show_tile(tile_speed); } +// ---------- settings: screen rotation ---------- +static uint8_t load_rotation() +{ + Preferences p; + p.begin("ui", true); + uint8_t r = p.getUChar("rot", 0); + p.end(); + return r > 3 ? 0 : r; +} + +static void highlight_rotation(uint8_t rot) +{ + for (int i = 0; i < 4; i++) + lv_obj_set_style_bg_color(rot_buttons[i], i == rot ? COLOR_ARC : lv_color_hex(0x222A33), 0); +} + +static void on_rotation_clicked(lv_event_t *e) +{ + uint8_t rot = (uint8_t)(intptr_t)lv_event_get_user_data(e); + setRotation(rot); + highlight_rotation(rot); + Preferences p; + p.begin("ui", false); + p.putUChar("rot", rot); + p.end(); +} + // ---------- speed page ---------- static void build_speed_tile() { @@ -178,6 +208,38 @@ static void build_menu_tile() make_button(tile_menu, "Done", 50, 296, 92, on_done_clicked); } +static void build_settings_tile() +{ + lv_obj_t *title = lv_label_create(tile_settings); + lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0); + lv_obj_set_style_text_color(title, COLOR_TEXT, 0); + lv_label_set_text(title, "Settings"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20); + + lv_obj_t *label = lv_label_create(tile_settings); + lv_obj_set_style_text_font(label, &lv_font_montserrat_16, 0); + lv_obj_set_style_text_color(label, COLOR_DIM, 0); + lv_label_set_text(label, "Screen rotation (degrees)"); + lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 86); + + static const char *names[4] = {"0", "90", "180", "270"}; + for (int i = 0; i < 4; i++) + { + lv_obj_t *b = lv_button_create(tile_settings); + lv_obj_set_size(b, 104, 48); + lv_obj_align(b, LV_ALIGN_TOP_MID, (i % 2) ? 58 : -58, 118 + (i / 2) * 60); + lv_obj_add_event_cb(b, on_rotation_clicked, LV_EVENT_CLICKED, (void *)(intptr_t)i); + lv_obj_t *l = lv_label_create(b); + lv_label_set_text(l, names[i]); + lv_obj_set_style_text_font(l, &lv_font_montserrat_20, 0); + lv_obj_center(l); + rot_buttons[i] = b; + } + highlight_rotation(getRotation()); + + make_button(tile_settings, "Done", 0, 296, 110, on_done_clicked); +} + static void build_ui() { lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x000000), 0); @@ -185,10 +247,12 @@ static void build_ui() lv_obj_set_style_bg_color(tileview, lv_color_hex(0x000000), 0); lv_obj_set_scrollbar_mode(tileview, LV_SCROLLBAR_MODE_OFF); tile_speed = lv_tileview_add_tile(tileview, 0, 0, LV_DIR_RIGHT); - tile_menu = lv_tileview_add_tile(tileview, 1, 0, LV_DIR_LEFT); + tile_menu = lv_tileview_add_tile(tileview, 1, 0, (lv_dir_t)(LV_DIR_LEFT | LV_DIR_RIGHT)); + tile_settings = lv_tileview_add_tile(tileview, 2, 0, LV_DIR_LEFT); lv_obj_add_event_cb(tileview, on_tile_changed, LV_EVENT_VALUE_CHANGED, NULL); build_speed_tile(); build_menu_tile(); + build_settings_tile(); } // ---------- periodic updates ---------- @@ -279,6 +343,7 @@ void setup() delay(200); Serial.begin(115200); scr_lvgl_init(); + setRotation(load_rotation()); build_ui(); obd_begin(); if (obd_saved_address().length() == 0) diff --git a/CLAUDE.md b/CLAUDE.md index f965324..7275f01 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,7 +70,7 @@ If upload fails: hold BOOT (GPIO0) while plugging in / resetting. ## Display driver pattern (shared board library `shared/lib/JC3636W518`, was the vendor `scr_st77916.h`) `scr_lvgl_init()` does everything: LEDC backlight → CST816S on I2C @400 kHz → QSPI bus @**80 MHz** (vendor: 50) → `ESP_PanelLcd_ST77916(bus, 16bpp, RST)` → `invertColor(true)` → `lv_init()` + tick → LVGL display with **two 72-row draw buffers** (2 × ~52 KB internal RAM) → flush callback with DMA-done callback → touch indev. Returns the `lv_display_t*`. -Helpers: `setRotation(0..3)`, `set_brightness(0..100)`, `screen_switch(bool)`; `perf_monitor.h` prints FPS/refresh/other/CPU once per second. +Helpers: `setRotation(0..3)` / `getRotation()` (panel rotates via MADCTL swap/mirror like the vendor table; touch converted in software in `touchpad_read`), `set_brightness(0..100)`, `screen_switch(bool)`; `perf_monitor.h` prints FPS/refresh/other/CPU once per second. Display color format is `RGB565_SWAPPED` (ST77916 byte order, set in `lv_conf.h`). ## Folder contents diff --git a/shared/lib/JC3636W518/src/jc3636w518.cpp b/shared/lib/JC3636W518/src/jc3636w518.cpp index 28f815c..1a594a5 100644 --- a/shared/lib/JC3636W518/src/jc3636w518.cpp +++ b/shared/lib/JC3636W518/src/jc3636w518.cpp @@ -272,11 +272,15 @@ IRAM_ATTR bool onRefreshFinishCallback(void *user_data) return false; } +static uint8_t rotation = 0; // 0..3, clockwise in 90° steps + +// The panel rotates the image itself (MADCTL swap/mirror, same combinations as the vendor code); +// touch points are converted in touchpad_read() so they always match what is shown. void setRotation(uint8_t rot) { if (rot > 3) return; - if (lcd == NULL || touch == NULL) + if (lcd == NULL) return; switch (rot) @@ -285,35 +289,31 @@ void setRotation(uint8_t rot) lcd->swapXY(true); lcd->mirrorX(true); lcd->mirrorY(false); - touch->swapXY(true); - touch->mirrorX(true); - touch->mirrorY(false); break; - case 2: + case 2: // 180° lcd->swapXY(false); lcd->mirrorX(true); lcd->mirrorY(true); - touch->swapXY(false); - touch->mirrorX(true); - touch->mirrorY(true); break; - case 3: + case 3: // 270° clockwise lcd->swapXY(true); lcd->mirrorX(false); lcd->mirrorY(true); - touch->swapXY(true); - touch->mirrorX(false); - touch->mirrorY(true); break; default: lcd->swapXY(false); lcd->mirrorX(false); lcd->mirrorY(false); - touch->swapXY(false); - touch->mirrorX(false); - touch->mirrorY(false); break; } + rotation = rot; + if (disp) + lv_obj_invalidate(lv_display_get_screen_active(disp)); // redraw everything in the new orientation +} + +uint8_t getRotation() +{ + return rotation; } void screen_switch(bool on) @@ -341,8 +341,27 @@ static void touchpad_read(lv_indev_t *indev, lv_indev_data_t *data) int read_touch_result = tp->readPoints(&point, 1); if (read_touch_result > 0) { - data->point.x = point.x; - data->point.y = point.y; + // physical panel coordinates -> coordinates of the rotated image + const int max = SCREEN_RES_HOR - 1; // square display + switch (rotation) + { + case 1: + data->point.x = point.y; + data->point.y = max - point.x; + break; + case 2: + data->point.x = max - point.x; + data->point.y = max - point.y; + break; + case 3: + data->point.x = max - point.y; + data->point.y = point.x; + break; + default: + data->point.x = point.x; + data->point.y = point.y; + break; + } data->state = LV_INDEV_STATE_PRESSED; } else diff --git a/shared/lib/JC3636W518/src/jc3636w518.h b/shared/lib/JC3636W518/src/jc3636w518.h index ef98726..039ed7e 100644 --- a/shared/lib/JC3636W518/src/jc3636w518.h +++ b/shared/lib/JC3636W518/src/jc3636w518.h @@ -17,8 +17,9 @@ // Initializes backlight, touch, LCD and LVGL (lv_init, tick, display, touch input). Returns the display. lv_display_t *scr_lvgl_init(); -// 0 = normal, 1 = 90° clockwise, 2 = 180°, 3 = 270° (LCD and touch together) +// 0 = normal, 1 = 90° clockwise, 2 = 180°, 3 = 270° (LCD and touch together; call after scr_lvgl_init) void setRotation(uint8_t rot); +uint8_t getRotation(); void screen_switch(bool on);