From 83aeea8ce7cfcccefae5a7c727d2bc9a4e2903e0 Mon Sep 17 00:00:00 2001 From: erikkaashoek Date: Tue, 27 Oct 2026 15:48:13 +0100 Subject: [PATCH 1/4] Text keypad: touch the text to place the cursor (tinySA ULTRA) File, band and preset names could only be typed and erased at the end. The text keypad now has a cursor where characters are inserted and deleted. - Touching the text line moves the cursor to the nearest character boundary and it follows the finger while touched (remote touch too). - Keys insert at the cursor (still at most TXTINPUT_LEN characters), backspace deletes the character before it and does nothing at the start; a long backspace still erases the whole name and backspace on an empty name still cancels. - The cursor is a blue 2 pixel bar in its own 3 pixel gap that sticks out 4 pixels above and below the letters, so it is never read as an I (the glyphs are only 1 pixel apart). The input line is now cleared up to the screen edge, so a shorter name leaves nothing behind. - The text keypad exists only with __USE_SD_CARD__: IS_TXT_KEYBOARD is a constant false without it, which drops the text keypad code from the tinySA (BASIC). Tested on HW V0.4.6 with remote touch and screen captures: insert, backspace, drag to the end, edits at the start, long press, cancel and a full 27 character name edited in the middle; the edit logic also in a host build with AddressSanitizer. Flash: ULTRA +376 bytes, BASIC -164. Co-Authored-By: Claude Opus 5.5 (1M context) --- ui.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/ui.c b/ui.c index c17e907..495334a 100644 --- a/ui.c +++ b/ui.c @@ -1205,6 +1205,11 @@ const uint8_t right_icons [] = #define KP_500 40 enum {NUM_KEYBOARD, TXT_KEYBOARD}; +#ifdef __USE_SD_CARD__ +#define IS_TXT_KEYBOARD (keypads[0].c == TXT_KEYBOARD) +#else +#define IS_TXT_KEYBOARD false // keypads_text needs the SD card, the text keypad code is left out +#endif typedef struct { uint16_t x_offs; @@ -7138,15 +7143,47 @@ draw_numeric_input(const char *buf) } } +#define TXT_INPUT_X (14 + 10 * FONT_WIDTH) +#define TXT_CURSOR_OUT 4 // the cursor sticks out above and below the letters, so it is no I + +static uint8_t kp_cursor; // text keypad: characters are inserted and deleted here + +// Text with the cursor as a blue 2 pixel bar in its own gap (the glyphs are only 1 pixel apart) static void draw_text_input(const char *buf) { ili9341_set_foreground(LCD_INPUT_TEXT_COLOR); ili9341_set_background(LCD_INPUT_BG_COLOR); - uint16_t x = 14 + 10 * FONT_WIDTH; + int x = TXT_INPUT_X; uint16_t y = LCD_HEIGHT-(wFONT_GET_HEIGHT + NUM_INPUT_HEIGHT)/2; - ili9341_fill(x, y, wFONT_MAX_WIDTH * 20, wFONT_GET_HEIGHT); - ili9341_drawstring_10x14(buf, x, y); + ili9341_fill(x, y - TXT_CURSOR_OUT, LCD_WIDTH - x, wFONT_GET_HEIGHT + 2 * TXT_CURSOR_OUT); + char txt[2] = {0, 0}; + for (int i = 0; ; i++) { + if (i == kp_cursor) { + ili9341_set_background(LCD_BRIGHT_COLOR_BLUE); + ili9341_fill(x, y - TXT_CURSOR_OUT, 2, wFONT_GET_HEIGHT + 2 * TXT_CURSOR_OUT); + ili9341_set_background(LCD_INPUT_BG_COLOR); + x += 3; + } + if ((txt[0] = buf[i]) == 0) break; + ili9341_drawstring_10x14(txt, x, y); + x += wFONT_GET_WIDTH(txt[0]); + } +} + +// Touch on the text: the cursor goes to the character boundary nearest to touch_x +static void +text_cursor_touch(int touch_x) +{ + int x = TXT_INPUT_X, i; + for (i = 0; kp_buf[i]; i++) { + int w = wFONT_GET_WIDTH(kp_buf[i]); + if (touch_x < x + w / 2) break; + x += w; + } + if (i == kp_cursor) return; + kp_cursor = i; + draw_text_input(kp_buf); } static void @@ -8005,15 +8042,21 @@ full_keypad_click(int c, int kp_index) set_numeric_value(); // Also for text values return KP_DONE; } - if (c == S_LARROW[0]) { // Backspace + if (kp_cursor > kp_index) // kp_buf replaced by the text command + kp_cursor = kp_index; + if (c == S_LARROW[0]) { // Backspace: the character before the cursor if (kp_index == 0) return KP_CANCEL; if (keypad_long_press) // long press erases the whole input, stay in the keypad - kp_index = 0; - else - --kp_index; - } else if (kp_index < TXTINPUT_LEN) { // any other text input - kp_buf[kp_index++] = c; + kp_index = kp_cursor = 0; + else if (kp_cursor > 0) { + memmove(&kp_buf[kp_cursor - 1], &kp_buf[kp_cursor], kp_index - kp_cursor); + kp_cursor--, kp_index--; + } + } else if (kp_index < TXTINPUT_LEN) { // any other text input, inserted at the cursor + memmove(&kp_buf[kp_cursor + 1], &kp_buf[kp_cursor], kp_index - kp_cursor); + kp_buf[kp_cursor++] = c; + kp_index++; } kp_buf[kp_index] = '\0'; draw_text_input(kp_buf); @@ -8024,7 +8067,7 @@ static int keypad_click(int key) { int c = keypads[key+1].c; // !!! Use key + 1 (zero key index used or size define) int index = strlen(kp_buf); - int result = keypads[0].c == NUM_KEYBOARD ? num_keypad_click(c, index) : full_keypad_click(c, index); + int result = IS_TXT_KEYBOARD ? full_keypad_click(c, index) : num_keypad_click(c, index); return result; } @@ -8033,6 +8076,13 @@ keypad_apply_touch(void) { int touch_x, touch_y; touch_position(&touch_x, &touch_y); + if (IS_TXT_KEYBOARD && touch_y >= LCD_HEIGHT - NUM_INPUT_HEIGHT) { + do { // touch on the text moves the cursor, follow the finger + touch_position(&touch_x, &touch_y); + text_cursor_touch(touch_x); + } while (touch_check() != EVT_TOUCH_RELEASED); + return -1; + } const keypad_pos_t *p = &key_pos[keypads[0].c]; if (touch_x < p->x_offs || touch_y < p->y_offs) return -1; // Calculate key position from touch x and y @@ -8069,12 +8119,13 @@ ui_process_keypad(void) keypad_long_press = false; if (!preset_text) kp_buf[0] = 0; + kp_cursor = strlen(kp_buf); if (in_menu_command) return; if (preset_text) { // show the default, ENTER accepts it as is - if (keypads[0].c == NUM_KEYBOARD) - draw_numeric_input(kp_buf); - else + if (IS_TXT_KEYBOARD) draw_text_input(kp_buf); + else + draw_numeric_input(kp_buf); } while (TRUE) { status = btn_check(); From 4bfb7558dc8d7cbaa251824c8b9e1c937b1e6214 Mon Sep 17 00:00:00 2001 From: erikkaashoek Date: Tue, 27 Oct 2026 15:49:19 +0100 Subject: [PATCH 2/4] Answer sweep-thread commands at once while a keypad is open Remote editing of a file name with tinySA-App became very slow after a character or two, and the shell could end up not reading any input. CMD_WAIT_MUTEX commands are run by the sweep thread, but while a keypad is open that thread stays in ui_process_keypad() and never picks them up, so the shell waited 4 x 5 s for each before "Command timeout". The app polls vbat every 6 s outside live remote control, and every touch queued behind those waits; local editing was not affected. ui_process_keypad() now sets keypad_open while it waits for input, and VNAShell_executeLine() answers a CMD_WAIT_MUTEX command with "keypad active" instead of waiting, like "usb disk mode active". touch, release, text, refresh, version and capture (CMD_RUN_IN_UI) run as before. Tested on HW V0.4.5.1: with a keypad open vbat, pause and sweep answer in 1 ms, capture rle and remote typing work, and after closing vbat and sweep answer normally. tinySA-App clicking the screen without live remote control (vbat polls every 6 s) typed all 8 characters, each poll answered within 3 ms; before, the first poll stalled the rest. Flash: ULTRA +44 bytes, BASIC +52. Co-Authored-By: Claude Opus 5.5 (1M context) --- main.c | 6 ++++++ ui.c | 3 +++ 2 files changed, 9 insertions(+) diff --git a/main.c b/main.c index 65e2a68..0411551 100644 --- a/main.c +++ b/main.c @@ -962,6 +962,7 @@ VNA_SHELL_FUNCTION(cmd_menu) } uint8_t remote_text = false; +uint8_t keypad_open = false; // set by ui.c while the sweep thread waits for keypad input VNA_SHELL_FUNCTION(cmd_text) { if (argc!= 1) @@ -3212,6 +3213,11 @@ static void VNAShell_executeLine(char *line) return; } #endif + // The sweep thread stays in an open keypad until it closes: answer at once instead of a 20 s timeout + if ((cmd_flag & CMD_WAIT_MUTEX) && keypad_open) { + shell_printf("keypad active" VNA_SHELL_NEWLINE_STR VNA_SHELL_PROMPT_STR); + return; + } if (cmd_flag & CMD_WAIT_MUTEX) { chSysLock(); shell_function = scp->sc_function; diff --git a/ui.c b/ui.c index 495334a..22d66a3 100644 --- a/ui.c +++ b/ui.c @@ -8106,6 +8106,7 @@ keypad_apply_touch(void) extern uint8_t in_menu_command; extern uint8_t remote_text; +extern uint8_t keypad_open; static void ui_process_keypad(void) @@ -8127,6 +8128,7 @@ ui_process_keypad(void) else draw_numeric_input(kp_buf); } + keypad_open = true; // shell commands for the sweep thread are refused now while (TRUE) { status = btn_check(); if (status & (EVT_UP|EVT_DOWN)) { @@ -8160,6 +8162,7 @@ ui_process_keypad(void) break; } } + keypad_open = false; kp_help_text = NULL; redraw_frame(); if (current_menu_is_form()) { From 21a6f96e2e5010b4cb6f29985255b307684e6452 Mon Sep 17 00:00:00 2001 From: erikkaashoek Date: Tue, 27 Oct 2026 16:55:41 +0100 Subject: [PATCH 3/4] No white flash at power-on on the tinySA ULTRA (ZS405) Report: switching an Ultra (ZS405) on showed a momentary all white screen, an Ultra+ (ZS406, ZS407) did not. - The backlight came on before ili9341_init(), so the panel was lit in reset and during its init; the ZS405 panel shows white then (the ZS406/ZS407 panels, the ones with display inversion, stay black). It now comes on at DEFAULT_BRIGHTNESS once the screen is cleared. - That left a very brief flash: the backlight came on before the panel had settled. The init now waits the 120 ms the controller needs after SLEEP_OUT before DISPLAY_ON (LCD_WAKE_UP_MS). Only with __LCD_BRIGHTNESS__: without backlight control a longer wait would only show the uninitialised panel longer, so the BASIC is unchanged. Tested on a ZS405 (HW V0.4.5.1) with the ST-Link: DAC2 at 0 darkens the backlight, the backlight is dark with the CPU held in reset, the backlight switched on 2 s after the init shows no flash, and with this build power-ons show no flash. Start-up 120 ms longer; ULTRA +12 bytes, BASIC binary unchanged in size. Co-Authored-By: Claude Opus 5.5 (1M context) --- ili9341.c | 8 +++++++- main.c | 8 +++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ili9341.c b/ili9341.c index 6986649..a2ef567 100644 --- a/ili9341.c +++ b/ili9341.c @@ -504,6 +504,12 @@ void lcd_setBrightness(uint16_t b){ } #endif +#ifdef __LCD_BRIGHTNESS__ +#define LCD_WAKE_UP_MS 120 // after sleep out the panel supplies settle, the backlight comes on after the init +#else +#define LCD_WAKE_UP_MS 2 // backlight not controlled: a longer wait only shows the uninitialised panel longer +#endif + void ili9341_init(void) { LCD_DC_DATA; @@ -513,8 +519,8 @@ void ili9341_init(void) const uint8_t *p; for (p = LCD_INIT; *p; ) { send_command(p[0], p[1], &p[2]); + chThdSleepMilliseconds(p[0] == ILI9341_SLEEP_OUT ? LCD_WAKE_UP_MS : 2); p += 2 + p[1]; - chThdSleepMilliseconds(2); } #ifdef TINYSA4 if ((hwid % 100) >= 2) { diff --git a/main.c b/main.c index 0411551..fe79846 100644 --- a/main.c +++ b/main.c @@ -3550,9 +3550,6 @@ int main(void) adc_init(); PULSE DAC->CR|= DAC_CR_EN1 | DAC_CR_EN2; // Use DAC: CH1 and CH2 - #ifdef __LCD_BRIGHTNESS__ - lcd_setBrightness(DEFAULT_BRIGHTNESS); - #endif DAC->DHR12R1 = 0; // Setup DAC: CH1 value /* @@ -3563,6 +3560,11 @@ int main(void) #endif ili9341_init(); PULSE + #ifdef __LCD_BRIGHTNESS__ + // Backlight on only now the screen is cleared: a panel in reset or before its init + // shows white (the ZS405 panel, ZS406/ZS407 stay black) + lcd_setBrightness(DEFAULT_BRIGHTNESS); + #endif #ifdef TINYSA4 ili9341_set_foreground(LCD_FG_COLOR); From c5b0eda0e537459531a3a78b9228717fc3dd8413 Mon Sep 17 00:00:00 2001 From: erikkaashoek Date: Tue, 27 Oct 2026 16:56:40 +0100 Subject: [PATCH 4/4] Changelog: V1.4260 keypad cursor, keypad active, ZS405 power-on flash Co-Authored-By: Claude Opus 5.5 (1M context) --- changelog.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.txt b/changelog.txt index 773fff1..43cc81b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +V1.4260 2026-10-27 For all tinySA models +- File, band and preset names can be edited anywhere: touch the name to place the cursor, keys insert and backspace deletes at the cursor (Ultra only) +- While a keypad is open, console commands that cannot run (e.g. vbat, sweep) are answered "keypad active" at once instead of after 20 s, so remote editing with tinySA-App no longer slows down +- No more white flash when switching on a tinySA Ultra (ZS405) (Ultra only) + V1.4255 2026-10-23 For all tinySA Ultra models - "refresh rle" remote control: compressed screen updates and a scrolling waterfall, 8x to 16x less USB data (tinySA-App uses it automatically)