Merge branch 'p3-features'

main
erikkaashoek 4 days ago
commit ab5ecd5b51

@ -1,3 +1,15 @@
V1.4302 2026-11-09 For all tinySA models
- Signal generator: PRESET in the generator menu loads and stores presets (on the tinySA Ultra also from and to SD). A loaded preset opens the menu of its own mode, and a signal generator preset loaded from the menu starts with the output off
- The SW button of the signal generator shows the start and stop frequency of the sweep (Ultra only)
- STORAGE > MORE > SD CARD ICON > REPEAT: with a repeat time set, touching the SD card icon starts saving every interval (with automatic file names) and touching it again stops, the icon is red while it repeats (Ultra only)
- MARKER > SEARCH MARKER > TRACKING L->R: the tracking markers stay on the highest peaks in frequency order from left to right instead of changing places (Ultra only)
- CONFIG > MORE > EXPERT CONFIG > SHOW BANDS: with MULTI BAND off the bands of the band table (e.g. loaded from a .bnd file) are highlighted with their names (Ultra only)
- File browser: REN renames a file, the name keypad starts with the current name (Ultra only)
- Menu buttons that open a submenu show a small arrow in the top right corner
- Fixed a crash when a band frequency went below 0 Hz, e.g. after entering only a SPAN for a new band: such a band now starts at 0 Hz. A band wider than 2.1 GHz also showed a wrong SPAN and changed its width when its CENTER was set (Ultra only)
- Screen capture ("capture" console command) no longer draws at the same time as the tinySA itself: capturing the VERSION screen hung the tinySA Ultra, the end screen of the level calibration gave "Command timeout"
- Level calibration: "Touch screen to continue" was drawn below the screen (tinySA basic only)
V1.4290 2026-11-02 For all tinySA models
- Recalling a preset turns NORMALIZE off: the normalization reference trace is not stored in a preset, so the normalized trace was wrong
- Trace CSV files load with their own start and stop frequency again: with CONFIG > MHz CSV (the default) the frequencies were read as Hz (Ultra only)

@ -87,6 +87,11 @@ static char *long_to_string_with_divisor(char *p,
// default prescision = 13
// g.mmm kkk hhh
#define MAX_FREQ_PRESCISION 15
#ifdef TINYSA4
#define FREQ_DIGITS 23 // any 64 bit value: 20 digits and 3 separators, fits tmpbuf[2*MAX_FILLER + 1]
#else
#define FREQ_DIGITS MAX_FREQ_PRESCISION
#endif
#define FREQ_PSET 1
#define FREQ_NO_SPACE 2
#define FREQ_PREFIX_SPACE 4
@ -99,7 +104,7 @@ ulong_freq(char *p, ulong_t freq, uint32_t width, uint32_t precision)
if (/* precision == 0 || */ precision > MAX_FREQ_PRESCISION)
precision = MAX_FREQ_PRESCISION;
char *q = p + MAX_FREQ_PRESCISION;
char *q = p + FREQ_DIGITS; // digits are written backwards from here
char *b = q;
// Prefix counter
uint32_t s = 0;

@ -63,6 +63,7 @@ char shell_line[VNA_SHELL_MAX_LENGTH];
char *shell_args[VNA_SHELL_MAX_ARGUMENTS + 1];
uint16_t shell_nargs;
static volatile vna_shellcmd_t shell_function = 0;
static volatile bool shell_function_ui; // shell_function may also run in a UI wait loop (CMD_RUN_IN_UI)
//#define ENABLED_DUMP
// Allow get threads debug info
@ -143,6 +144,49 @@ systime_t restart_interval = 0;
static THD_WORKING_AREA(waThread1, 768);
bool has_esd = false;
#endif
// Run the console command the shell thread handed over. Always in the sweep thread, so a command
// never uses the LCD at the same time as the screen (two threads on the SPI DMA hang the tinySA).
// ui_wait: called from a loop that waits for the user (keypad, version screen, "touch to continue"),
// there only a CMD_RUN_IN_UI command (capture) runs, the others wait for the sweep loop.
void shell_run_pending(bool ui_wait)
{
chSysLock();
vna_shellcmd_t local_func = shell_function;
if (ui_wait && !shell_function_ui)
local_func = 0;
else {
shell_function = 0;
shell_function_ui = false;
// The shell sets OP_CONSOLE together with shell_function, a request without a
// command (direct command, timed out command) has done its job: the sweep stopped
operation_requested &= ~OP_CONSOLE;
}
chSysUnlock();
if (local_func) {
if (!ui_wait) // a UI wait loop keeps a pending touch or lever event
operation_requested = OP_NONE; // otherwise commands will be aborted
local_func(shell_nargs - 1, &shell_args[1]);
if (ui_wait || operation_requested == OP_NONE) // Don't prompt if aborted
shell_printf(VNA_SHELL_PROMPT_STR);
// Resume shell thread
if (!abort_enabled) {
chSysLock();
osalThreadDequeueNextI(&shell_thread, MSG_OK);
chSysUnlock();
}
if (dirty && !ui_wait) { // not over the screen that waits
if (MODE_OUTPUT(setting.mode))
draw_menu(); // update screen if in output mode and dirty
else
redraw_request |= REDRAW_CAL_STATUS | REDRAW_AREA | REDRAW_FREQUENCY;
}
}
}
static THD_FUNCTION(Thread1, arg)
{
(void)arg;
@ -262,38 +306,7 @@ static THD_FUNCTION(Thread1, arg)
__WFI();
}
// STOP_PROFILE
// Run Shell command in sweep thread
vna_shellcmd_t local_func;
chSysLock();
local_func = shell_function;
shell_function = 0;
// The shell sets OP_CONSOLE together with shell_function, a request without a
// command (direct command, timed out command) has done its job: the sweep stopped
operation_requested &= ~OP_CONSOLE;
chSysUnlock();
if (local_func) {
operation_requested = OP_NONE; // otherwise commands will be aborted
local_func(shell_nargs - 1, &shell_args[1]);
if (operation_requested == OP_NONE) // Don't prompt if aborted
shell_printf(VNA_SHELL_PROMPT_STR);
// Resume shell thread
if (!abort_enabled) {
chSysLock();
osalThreadDequeueNextI(&shell_thread, MSG_OK);
chSysUnlock();
}
if (dirty) {
if (MODE_OUTPUT(setting.mode))
draw_menu(); // update screen if in output mode and dirty
else
redraw_request |= REDRAW_CAL_STATUS | REDRAW_AREA | REDRAW_FREQUENCY;
}
}
shell_run_pending(false);
#ifdef __USE_USB_MSC__
// "usbdisk on" from the shell
if (msc_enter_request) {
@ -304,11 +317,8 @@ static THD_FUNCTION(Thread1, arg)
#endif
// START_PROFILE
// Process UI inputs
if (!(sweep_mode & SWEEP_SELFTEST)) {
sweep_mode|= SWEEP_UI_MODE;
if (!(sweep_mode & SWEEP_SELFTEST))
ui_process();
sweep_mode&=~SWEEP_UI_MODE;
}
// Process collected data, calculate trace coordinates and plot only if scan
// completed
if (completed) {
@ -3193,8 +3203,6 @@ static void VNAShell_executeLine(char *line)
const VNAShellCommand *scp = VNAShell_parceLine(line);
if (scp) {
uint16_t cmd_flag = scp->flags;
// Skip wait mutex if process UI
if ((cmd_flag & CMD_RUN_IN_UI) && (sweep_mode&SWEEP_UI_MODE)) cmd_flag&=~CMD_WAIT_MUTEX;
#ifdef __USE_USB_MSC__
// In USB DISK mode SPI1 and spi_buffer belong to the mass storage, only run commands that do not use them.
// shell_direct_cmd tells usb_disk_mode() to wait for a command already running in this thread.
@ -3217,13 +3225,15 @@ 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) {
// The sweep thread stays in an open keypad until it closes: answer at once instead of a 20 s timeout.
// A CMD_RUN_IN_UI command (capture) runs there in shell_run_pending().
if ((cmd_flag & (CMD_WAIT_MUTEX | CMD_RUN_IN_UI)) == 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_ui = cmd_flag & CMD_RUN_IN_UI;
shell_function = scp->sc_function;
operation_requested|=OP_CONSOLE; // this will abort current sweep to give priority to the new request
chSysUnlock();

@ -293,6 +293,7 @@ void my_veryfast_delay(int t);
float my_atof(const char *p);
freq_t my_atoui(const char *p);
int shell_printf(const char *fmt, ...);
void shell_run_pending(bool ui_wait); // run a console command in the sweep thread
int usage_printf(const char *fmt, ...);
void clear_backup(void);
const char *get_hw_version_text(void);
@ -380,7 +381,6 @@ enum {
//#define SWEEP_FACTORY 0x20
#endif
#define SWEEP_CALIBRATE_HARMONIC 0x40
#define SWEEP_UI_MODE 0x80
extern uint8_t sweep_mode;
extern uint8_t completed;
@ -818,6 +818,8 @@ float marker_to_value(const int i);
// auto name
#define _MODE_AUTO_FILENAME 0x10
#define _MODE_MHZ_CSV 0x20
// tracking markers left to right
#define _MODE_MARKER_ORDER 0x40
#ifdef __USE_SD_CARD__
// SD Icon Save (SDIS) configuration
@ -829,6 +831,7 @@ float marker_to_value(const int i);
#define SDIS_VALID_MASK (1 << 7)
#define SDIS_DEFAULT (SDIS_CAPTURE | SDIS_VALID_MASK)
#define SDIS_IS_ENABLED ((config.sd_icon_save & ~SDIS_VALID_MASK) != 0)
extern bool sd_repeat;
#endif // __USE_SD_CARD__
#pragma pack(push, 4)
@ -1909,7 +1912,6 @@ extern int current_index;
extern uint16_t actual_rbw_x10;
void toggle_tracking(void);
void toggle_hambands(void);
void reset_calibration(void);
void set_reflevel(float);
void set_external_gain(float);

@ -255,7 +255,16 @@ const int ham_band_max = 18;
int ham_band(int x) // Search which index in the frequency tabled matches with frequency f using actual_rbw
{
freq_t f = getFrequency(x) + (setting.frequency_offset - FREQUENCY_SHIFT);
freq_t f = getFrequency(x);
#ifdef __BANDS__
if ((config.hambands & 2) && !setting.multi_band) // band table
for (int i = 0; i < BANDS_MAX; i++)
if (setting.bands[i].enabled && setting.bands[i].start <= f && f <= setting.bands[i].end)
return true;
if (!(config.hambands & 1))
return false;
#endif
f += (setting.frequency_offset - FREQUENCY_SHIFT);
int L = 0;
int R = ham_band_max - 1;
while (L <= R) {
@ -1235,6 +1244,20 @@ draw_cell(int m, int n)
// PULSE;
#endif
#ifdef __BANDS__
// Band table highlights (ham_band): name at the bottom left of each band
if ((config.hambands & 2) && !setting.multi_band && y0 + h >= HEIGHT - 12) {
ili9341_set_foreground(LCD_FG_COLOR);
for (int band = 0; band < BANDS_MAX; band++) {
band_t *b = &setting.bands[band];
if (!b->enabled || b->start > getFrequency(sweep_points - 1) || b->end < getFrequency(0)) continue;
i = 0;
while (getFrequency(i) < b->start) i++;
cell_printf(trace_index_x[i] + 2 - x0, HEIGHT - 12 - y0, "%s", b->name);
}
}
#endif
// Draw trigger and band splitter lines
bool draw_trigger = (setting.trigger != T_AUTO
#ifdef __TRIGGER_TRACE__
@ -2131,7 +2154,7 @@ static const uint8_t sd_icon [] = {
_BMP16(0b0111111111111000) //
};
if (SD_Inserted() && SDIS_IS_ENABLED) {
ili9341_set_foreground(LCD_BRIGHT_COLOR_GREEN);
ili9341_set_foreground(sd_repeat ? LCD_BRIGHT_COLOR_RED : LCD_BRIGHT_COLOR_GREEN);
ili9341_blitBitmap(4, SD_CARD_START, 16, 16, sd_icon);
// ili9341_drawstring("-SD-", x, SD_CARD_START);
}

@ -1115,12 +1115,6 @@ void toggle_mute(void)
dirty = true;
}
void toggle_hambands(void)
{
config.hambands = !config.hambands;
dirty = true;
}
void toggle_below_IF(void)
{
if (S_IS_AUTO(setting.below_IF ))
@ -5769,6 +5763,23 @@ static volatile int dummy;
if (MODE_INPUT(setting.mode)) { // Assign maxima found to tracking markers
int i = 0;
int m = 0;
#ifdef TINYSA4
if (config._mode & _MODE_MARKER_ORDER) { // The highest maxima, one per tracking marker, left to right
for (; m < MARKERS_MAX; m++)
if (markers[m].enabled && markers[m].mtype & M_TRACKING && markers[m].trace == t)
i++;
if (i > cur_max) i = cur_max;
while (--i > 0) { // Bubble sort these first maxima on frequency
for (m = 0; m < i; m++)
if (max_index[m] > max_index[m+1]) {
uint16_t s = max_index[m];
max_index[m] = max_index[m+1];
max_index[m+1] = s;
}
}
i = m = 0;
}
#endif
while (i < cur_max) { // For all maxima found
while (m < MARKERS_MAX) {
if (markers[m].enabled && markers[m].mtype & M_TRACKING && markers[m].trace == t) { // Available marker found
@ -7946,6 +7957,14 @@ void calibrate_harmonic(void)
}
#endif
// First of the three 20 pixel message lines at the end of the calibration, the last one
// ("Touch screen to continue") must be on the 240 pixel screen of the tinySA
#ifdef TINYSA4
#define CAL_MSG_Y 200
#else
#define CAL_MSG_Y 180
#endif
void calibrate(void)
{
int local_test_status;
@ -8198,14 +8217,14 @@ void calibrate(void)
low_level:
#endif
ili9341_set_foreground(LCD_BRIGHT_COLOR_RED);
ili9341_drawstring_7x13("Signal level incorrect", 30, 200);
ili9341_drawstring_7x13("Check cable between connectors", 30, 220);
ili9341_drawstring_7x13("Signal level incorrect", 30, CAL_MSG_Y);
ili9341_drawstring_7x13("Check cable between connectors", 30, CAL_MSG_Y + 20);
goto quit;
}
// chThdSleepMilliseconds(1000);
if (local_test_status != TS_PASS) {
ili9341_set_foreground(LCD_BRIGHT_COLOR_RED);
ili9341_drawstring_7x13("Calibration failed", 30, 220);
ili9341_drawstring_7x13("Calibration failed", 30, CAL_MSG_Y + 20);
goto quit;
}
#ifdef TINYSA4
@ -8285,12 +8304,12 @@ low_level:
#endif
config_save();
ili9341_set_foreground(LCD_BRIGHT_COLOR_GREEN);
ili9341_drawstring_7x13("Calibration complete", 40, 220);
ili9341_drawstring_7x13("Calibration complete", 40, CAL_MSG_Y + 20);
quit:
#ifdef TINYSA4
config.ultra = old_ultra;
#endif
ili9341_drawstring_7x13("Touch screen to continue", 40, 240);
ili9341_drawstring_7x13("Touch screen to continue", 40, CAL_MSG_Y + 40);
wait_user();
ili9341_clear_screen();
set_sweep_points(old_sweep_points);

157
ui.c

@ -212,6 +212,7 @@ int btn_side(void)
static int btn_check(void)
{
systime_t ticks;
shell_run_pending(true); // capture while a screen waits for the jog button
#ifdef __WATCHDOG__
wdgReset(&WDGD1);
#endif
@ -402,6 +403,7 @@ static void touch_init(void){
static int
touch_check(void)
{
shell_run_pending(true); // capture while a screen waits for a touch
#ifdef TINYSA4
if (setting.lock_display)
return EVT_TOUCH_NONE;
@ -1566,7 +1568,7 @@ static const struct {
#ifdef __USE_RTC__
[KM_RTC_DATE] = {keypads_positive , "SET DATE\n YYMMDD"}, // Date
[KM_RTC_TIME] = {keypads_positive , "SET TIME\n HHMMSS"}, // Time
[KM_INTERVAL] = {keypads_positive , "SET INTERVAL\n HHMMSS"}, // Interval
[KM_INTERVAL] = {keypads_positive , "REPEAT\nHHMMSS"}, // Interval
#endif
[KM_PRESET_NAME] = {keypads_text , "NAME"}, // KM_PRESET_NAME
#endif
@ -1690,8 +1692,13 @@ static UI_FUNCTION_ADV_CALLBACK(menu_sweep_acb)
(void)item;
if (b){
if (setting.level_sweep != 0 || get_sweep_frequency(ST_SPAN) != 0) {
#ifdef TINYSA4
plot_printf(b->text, sizeof b->text, "SW:%.3Q-%.3QHz %+ddB %.3Fs",
get_sweep_frequency(ST_START), get_sweep_frequency(ST_STOP),
#else
plot_printf(b->text, sizeof b->text, "SW:%3.2fMHz %+ddB %.3Fs",
get_sweep_frequency(ST_SPAN) / 1000000.0,
#endif
(int)setting.level_sweep,
setting.sweep_time_us/(float)ONE_SECOND_TIME);
}
@ -2200,6 +2207,23 @@ static UI_FUNCTION_ADV_CALLBACK(menu_mode_acb)
redraw_request |= REDRAW_CAL_STATUS;
}
// After a preset load: a generator preset starts with the output off, a mode change shows the menu of the new mode
static void menu_preset_loaded(int old_mode)
{
bool output = MODE_OUTPUT(setting.mode);
if (output)
setting.mute = true;
if (setting.mode == old_mode) {
menu_move_back(!output); // a generator returns to its form menu
return;
}
menu_move_top();
if (output)
menu_push_submenu(setting.mode == M_GENLOW ? menu_lowoutputmode : menu_highoutputmode);
else
ui_mode_normal();
}
static UI_FUNCTION_ADV_CALLBACK(menu_load_preset_acb)
{
(void)item;
@ -2216,11 +2240,12 @@ static UI_FUNCTION_ADV_CALLBACK(menu_load_preset_acb)
plot_printf(b->text, sizeof(b->text), "EMPTY %d", (int)data);
return;
}
int old_mode = setting.mode;
if (caldata_recall(data) == -1) {
if (data == 0)
reset_settings(setting.mode); // Restore factory defaults
}
menu_move_back(true);
menu_preset_loaded(old_mode);
}
static UI_FUNCTION_ADV_CALLBACK(menu_store_preset_acb)
@ -4111,14 +4136,14 @@ static UI_FUNCTION_ADV_CALLBACK(menu_settings_draw_line_acb){
}
#endif
#ifdef __HAM_BAND__
// data: 1 = HAM bands, 2 = band table (multi band off)
static UI_FUNCTION_ADV_CALLBACK(menu_settings_ham_bands){
(void)item;
(void)data;
if(b){
b->icon = config.hambands ? BUTTON_ICON_CHECK : BUTTON_ICON_NOCHECK;
b->icon = config.hambands & data ? BUTTON_ICON_CHECK : BUTTON_ICON_NOCHECK;
return;
}
toggle_hambands();
config.hambands ^= data;
redraw_request|= REDRAW_AREA;
}
#endif
@ -4592,27 +4617,15 @@ __attribute__((noinline)) static void fw_update_ui(const char *fname) {
static UI_FUNCTION_ADV_CALLBACK(menu_autoname_acb)
// Toggle the config._mode bit in data
static UI_FUNCTION_ADV_CALLBACK(menu_config_mode_acb)
{
(void)item;
(void)data;
if (b){
b->icon = config._mode & _MODE_AUTO_FILENAME ? BUTTON_ICON_CHECK : BUTTON_ICON_NOCHECK;
b->icon = config._mode & data ? BUTTON_ICON_CHECK : BUTTON_ICON_NOCHECK;
return;
}
config._mode^= _MODE_AUTO_FILENAME;
config_save();
}
static UI_FUNCTION_ADV_CALLBACK(menu_mhz_csv_acb)
{
(void)item;
(void)data;
if (b){
b->icon = config._mode & _MODE_MHZ_CSV ? BUTTON_ICON_CHECK : BUTTON_ICON_NOCHECK;
return;
}
config._mode^= _MODE_MHZ_CSV;
config._mode^= data;
config_save();
}
@ -4758,6 +4771,7 @@ const menuitem_t menu_lowoutputmode[] = {
#ifdef TINYSA4
{ MT_FORM | MT_ADV_CALLBACK, 255, "OUTPUT: %s", menu_lowoutput_settings_acb},
#endif
{ MT_FORM | MT_SUBMENU, 0, "PRESET", menu_load_preset},
{ MT_FORM | MT_CANCEL, 0, "MODE", NULL },
{ MT_FORM | MT_NONE, 0, NULL, NULL } // sentinel
};
@ -4775,6 +4789,7 @@ const menuitem_t menu_highoutputmode[] = {
#ifdef TINYSA4
{ MT_FORM | MT_SUBMENU, 255, S_RARROW" Settings", menu_settings3},
#endif
{ MT_FORM | MT_SUBMENU, 0, "PRESET", menu_load_preset},
{ MT_FORM | MT_CANCEL, 0, "MODE", NULL },
{ MT_FORM | MT_NONE, 0, NULL, NULL } // sentinel
};
@ -4846,6 +4861,7 @@ const menuitem_t menu_marker_search[] = {
{ MT_ADV_CALLBACK, 0, "ENTER\n%s", menu_enter_marker_acb},
{ MT_ADV_CALLBACK, M_TRACKING, "TRACKING",menu_marker_modify_acb },
#ifdef TINYSA4
{ MT_ADV_CALLBACK, _MODE_MARKER_ORDER, "TRACKING\nL"S_RARROW"R", menu_config_mode_acb },
{ MT_ADV_CALLBACK, 0, "JOG JUMP\nMAX",menu_jog_jump_acb },
{ MT_KEYPAD, KM_NOISE, "PEAK\n\b%s", "2..120 dB"},
#endif
@ -5254,7 +5270,8 @@ static const menuitem_t menu_settings[] =
{ MT_CALLBACK, FMT_BIN_FILE, "DUMP\nFIRMWARE", menu_sdcard_cb},
#endif
#ifdef __HAM_BAND__
{ MT_ADV_CALLBACK, 0, "HAM\nBANDS", menu_settings_ham_bands},
{ MT_ADV_CALLBACK, 1, "HAM\nBANDS", menu_settings_ham_bands},
{ MT_ADV_CALLBACK, 2, "SHOW\nBANDS", menu_settings_ham_bands},
#endif
#ifdef __SD_CARD_LOAD__
{ MT_CALLBACK, 0 , "LOAD\nCONFIG.INI", menu_load_config_cb},
@ -5428,7 +5445,7 @@ static const menuitem_t menu_config2[] =
{ MT_SUBMENU, 0, "EXPERT\nCONFIG", menu_settings},
#else
#ifdef __HAM_BAND__
{ MT_ADV_CALLBACK, 0, "HAM\nBANDS", menu_settings_ham_bands},
{ MT_ADV_CALLBACK, 1, "HAM\nBANDS", menu_settings_ham_bands},
#endif
{ MT_SUBMENU, 0, "EXPERT\nCONFIG", menu_settings2},
#endif
@ -5610,12 +5627,13 @@ static const menuitem_t menu_stimulus[] = {
static const menuitem_t menu_storage_sd_icon[] = {
{ MT_ADV_CALLBACK, 0, "SAVE\nCAPTURE", menu_sd_icon_save_capture_acb },
{ MT_ADV_CALLBACK, 0, "SAVE\nTRACES", menu_sd_icon_save_traces_acb },
{ MT_KEYPAD, KM_INTERVAL, "REPEAT\n\b%s", "0 = single save" },
{ MT_NONE, 0, NULL, menu_back }
};
static const menuitem_t menu_storage_more[] = {
{ MT_ADV_CALLBACK, 0, "AUTO NAME", menu_autoname_acb },
{ MT_ADV_CALLBACK, 0, "MHz\nCSV", menu_mhz_csv_acb },
{ MT_ADV_CALLBACK, _MODE_AUTO_FILENAME, "AUTO NAME", menu_config_mode_acb },
{ MT_ADV_CALLBACK, _MODE_MHZ_CSV, "MHz\nCSV", menu_config_mode_acb },
{ MT_SUBMENU, 0, "SD CARD\nICON", menu_storage_sd_icon },
#ifdef __USE_USB_MSC__
{ MT_CALLBACK, 0, "USB\nDISK", menu_usb_disk_cb },
@ -5929,7 +5947,7 @@ static void fetch_numeric_target(uint8_t mode)
plot_printf(uistat.text, sizeof uistat.text, out_format, uistat.freq_value);
break;
case KM_BAND_SPAN:
uistat.freq_value = abs((long)setting.bands[active_band].end-(long)setting.bands[active_band].start);
uistat.freq_value = setting.bands[active_band].end - setting.bands[active_band].start;
#ifdef TINYSA4
if (uistat.freq_value > 990000000UL)
out_format = "%.6Q";
@ -6217,28 +6235,21 @@ set_numeric_value(void)
// BANDs_update();
break;
case KM_BAND_CENTER:
case KM_BAND_SPAN:
{
freq_t span = abs((long)setting.bands[active_band].end - (long)setting.bands[active_band].start);
freq_t center = uistat.freq_value - (setting.frequency_offset - FREQUENCY_SHIFT);
setting.bands[active_band].start = center - span/2;
setting.bands[active_band].end = center + span/2;
band_t *b = &setting.bands[active_band];
freq_t center = (b->end + b->start)/2;
freq_t span = b->end - b->start; // START and STOP keep start <= end
if (keypad_mode == KM_BAND_CENTER)
center = uistat.freq_value - (setting.frequency_offset - FREQUENCY_SHIFT);
else
span = uistat.freq_value;
b->start = center > span/2 ? center - span/2 : 0; // unsigned, a new band (0..0) with a span went "below 0 Hz"
b->end = b->start + span;
update_frequencies();
dirty = true;
update_grid();
}
// BANDs_update();
break;
case KM_BAND_SPAN:
{
freq_t span = uistat.freq_value;
freq_t center = (setting.bands[active_band].end + setting.bands[active_band].start)/2;
setting.bands[active_band].start = center - span/2;
setting.bands[active_band].end = center + span/2;
update_frequencies();
dirty = true;
update_grid();
// BANDs_update();
}
break;
case KM_BAND_LEVEL:
setting.bands[active_band].level = to_dBm(uistat.value);
@ -7427,6 +7438,11 @@ draw_menu_buttons(const menuitem_t *menu, uint32_t mask)
#else
ili9341_drawstring(text, text_offs, y+(button_height-linesFONT_GET_HEIGHT)/2);
#endif
if (MT_MASK(m->type) == MT_SUBMENU) { // Opens a submenu: small arrow in the top right corner
ili9341_set_background(LCD_MENU_TEXT_COLOR);
for (int r = -2; r <= 2; r++)
ili9341_fill(LCD_WIDTH - 6, y + 4 + r, 3 - (r < 0 ? -r : r), 1);
}
}
}
// Cleanup other buttons (less flicker)
@ -8351,7 +8367,7 @@ static void sa_save_file(uint8_t format) {
#endif
// Prepare filename and open for write
if (config._mode & _MODE_AUTO_FILENAME) { // Auto name, use date / time
if ((config._mode & _MODE_AUTO_FILENAME) || sd_repeat) { // Auto name, use date / time
#if FF_USE_LFN >= 1
uint32_t tr = rtc_get_tr_bcd(); // TR read first
uint32_t dr = rtc_get_dr_bcd(); // DR read second
@ -8521,6 +8537,17 @@ void save_csv(uint8_t mask) {
menu_sdcard_cb(0, FMT_CSV_FILE);
}
bool sd_repeat; // SD icon saves every setting.interval seconds
static systime_t sd_repeat_start;
static void sd_icon_save(void) {
sd_repeat_start = chVTGetSystemTimeX();
if (config.sd_icon_save & SDIS_CAPTURE)
menu_sdcard_cb(0, FMT_BMP_FILE);
if (config.sd_icon_save & SDIS_TRACES)
menu_save_traces_cb(0, 0);
}
static bool
made_screenshot(int touch_x, int touch_y) {
if (touch_y < SD_CARD_START || touch_y > SD_CARD_START + 20 || touch_x > OFFSETX)
@ -8528,10 +8555,12 @@ made_screenshot(int touch_x, int touch_y) {
ili9341_set_background(LCD_BG_COLOR);
ili9341_fill(4, SD_CARD_START, 16, 16);
touch_wait_release();
if (config.sd_icon_save & SDIS_CAPTURE)
menu_sdcard_cb(0, FMT_BMP_FILE);
if (config.sd_icon_save & SDIS_TRACES)
menu_save_traces_cb(0, 0);
if (setting.interval) { // Repeat set: the icon starts and stops saving every interval
sd_repeat = !sd_repeat;
redraw_request |= REDRAW_BATTERY; // icon color
if (!sd_repeat) return TRUE;
}
sd_icon_save();
return TRUE;
}
#endif
@ -8691,36 +8720,14 @@ void ui_process_touch(void)
static uint16_t previous_button_state = 0;
#ifdef TINYSA4
uint64_t time_since_start = 0;
uint64_t interval_stop_time = 0;
systime_t old_ticks;
void set_interval_stop_time(void)
{
if (setting.interval == 0) {
interval_stop_time = 0;
} else {
interval_stop_time = time_since_start + CH_CFG_ST_FREQUENCY * (uint64_t)setting.interval;
}
}
#endif
void
ui_process(void)
{
#ifdef TINYSA4
systime_t ticks = chVTGetSystemTimeX();
if (ticks < old_ticks)
time_since_start += 0x100000000;
old_ticks = ticks;
time_since_start = (time_since_start & 0xffffffff00000000) | ticks;
if (interval_stop_time != 0 && interval_stop_time < time_since_start) {
set_interval_stop_time();
}
#ifdef __USE_SD_CARD__
// SD icon repeat, only from the normal screen: a capture would close an open menu
if (sd_repeat && setting.interval && ui_mode == UI_NORMAL &&
chVTGetSystemTimeX() - sd_repeat_start >= (systime_t)setting.interval * CH_CFG_ST_FREQUENCY)
sd_icon_save();
#endif
int button_state = READ_PORT() & BUTTON_MASK;

@ -4,7 +4,7 @@ static uint16_t current_page;
static uint16_t sel_mode;
// Buttons in browser
enum {FILE_BUTTON_LEFT = 0, FILE_BUTTON_RIGHT, FILE_BUTTON_EXIT, FILE_BUTTON_DEL, FILE_BUTTON_FILE};
enum {FILE_BUTTON_LEFT = 0, FILE_BUTTON_RIGHT, FILE_BUTTON_EXIT, FILE_BUTTON_DEL, FILE_BUTTON_REN, FILE_BUTTON_FILE};
// Button position on screen
typedef struct {
uint16_t x;
@ -14,10 +14,11 @@ typedef struct {
uint8_t ofs;
} browser_btn_t;
static const browser_btn_t browser_btn[] = {
[FILE_BUTTON_LEFT] = { 0 + 40, LCD_HEIGHT - FILE_BOTTOM_HEIGHT, LCD_WIDTH/2 - 80, FILE_BOTTOM_HEIGHT, (LCD_WIDTH/2 - 80 - FONT_WIDTH)/2}, // < previous
[FILE_BUTTON_LEFT] = { 0 + 80, LCD_HEIGHT - FILE_BOTTOM_HEIGHT, LCD_WIDTH/2 -120, FILE_BOTTOM_HEIGHT, (LCD_WIDTH/2 -120 - FONT_WIDTH)/2}, // < previous
[FILE_BUTTON_RIGHT]= {LCD_WIDTH/2 + 40, LCD_HEIGHT - FILE_BOTTOM_HEIGHT, LCD_WIDTH/2 - 80, FILE_BOTTOM_HEIGHT, (LCD_WIDTH/2 - 80 - FONT_WIDTH)/2}, // > next
[FILE_BUTTON_EXIT] = {LCD_WIDTH - 40, LCD_HEIGHT - FILE_BOTTOM_HEIGHT, 40, FILE_BOTTOM_HEIGHT, ( 40 - FONT_WIDTH)/2}, // X exit
[FILE_BUTTON_DEL] = { 0 + 0, LCD_HEIGHT - FILE_BOTTOM_HEIGHT, 40, FILE_BOTTOM_HEIGHT, ( 40 - 3*FONT_WIDTH)/2}, // DEL
[FILE_BUTTON_REN] = { 0 + 40, LCD_HEIGHT - FILE_BOTTOM_HEIGHT, 40, FILE_BOTTOM_HEIGHT, ( 40 - 3*FONT_WIDTH)/2}, // REN
// File button, only size and start position, must be idx = FILE_BUTTON_FILE
[FILE_BUTTON_FILE] = { 0, 0, LCD_WIDTH/FILES_COLUMNS, FILE_BUTTON_HEIGHT, 5},
};
@ -45,8 +46,8 @@ static void browser_draw_button(int idx, const char *txt) {
ui_button_t b;
browser_btn_t btn;
browser_get_button_pos(idx, &btn);
// Mark DEL button in file delete mode
b.bg = (idx == FILE_BUTTON_DEL && sel_mode) ? LCD_LOW_BAT_COLOR : LCD_MENU_COLOR;
// Mark DEL button in file delete mode, REN in rename mode
b.bg = (sel_mode && idx == FILE_BUTTON_DEL - 1 + sel_mode) ? LCD_LOW_BAT_COLOR : LCD_MENU_COLOR;
b.fg = LCD_MENU_TEXT_COLOR;
b.border = (idx == selection) ? BROWSER_BUTTON_BORDER|BUTTON_BORDER_FALLING : BROWSER_BUTTON_BORDER|BUTTON_BORDER_RISE;
if (txt == NULL) b.border|= BUTTON_BORDER_NO_FILL;
@ -103,7 +104,22 @@ repeat:
if (cnt != 0) return;
// Delete file if in delete mode
if (sel_mode) {f_unlink(fno.fname); return;}
if (sel_mode == 1) {f_unlink(fno.fname); return;}
// Rename mode: edit the name without extension in the text keypad
if (sel_mode == 2) {
int format = keypad_mode;
ui_preset_keypad_text(fno.fname, strlen(fno.fname) - strlen(file_ext[format]) - 1);
ui_mode_keypad(KM_FILENAME);
if (kp_buf[0]) {
char new_name[FF_LFN_BUF];
plot_printf(new_name, FF_LFN_BUF, "%s.%s", kp_buf, file_ext[format]);
f_rename(fno.fname, new_name);
}
ui_mode = UI_BROWSER; // the keypad left to the normal screen
area_width = area_height = 0;
keypad_mode = format;
return;
}
run_file:
{}
const char *error = NULL;
@ -401,6 +417,7 @@ static char cmd_buffer[256+128];
static void browser_draw_buttons(void) {
browser_draw_button(FILE_BUTTON_DEL, "DEL");
browser_draw_button(FILE_BUTTON_REN, "REN");
browser_draw_button(FILE_BUTTON_LEFT, "<");
browser_draw_button(FILE_BUTTON_RIGHT, ">");
browser_draw_button(FILE_BUTTON_EXIT, "X");
@ -462,13 +479,22 @@ static void browser_key_press(int key) {
case FILE_BUTTON_EXIT: //Exit
ui_mode_normal();
break;
case FILE_BUTTON_DEL: // Toggle delete mode
sel_mode^= 1;
case FILE_BUTTON_DEL: // Toggle delete mode (1) or rename mode (2)
case FILE_BUTTON_REN:
key = key - FILE_BUTTON_DEL + 1;
sel_mode = sel_mode == key ? 0 : key;
browser_draw_buttons();
break;
case FILE_BUTTON_FILE: // Open or delete file
default:
{
int old_mode = setting.mode;
browser_open_file(key - FILE_BUTTON_FILE + (current_page - 1) * FILES_PER_PAGE);
if (!sel_mode && keypad_mode == FMT_PRS_FILE) {
menu_preset_loaded(old_mode);
return;
}
}
if (sel_mode) {
file_count = 0; // Reeset file count (recalculate on draw page)
selection = -1; // Reset delection

Loading…
Cancel
Save

Powered by TurnKey Linux.