From 80292faa43c5924c5b0263aed87c24e2c298ffd7 Mon Sep 17 00:00:00 2001 From: erikkaashoek Date: Tue, 10 Nov 2026 12:26:29 +0100 Subject: [PATCH] Add BURST modulation to the generator (tinySA ULTRA) MODULATION -> BURST keys the generator output on and off: ON TIME of every PERIOD, continuously or COUNT bursts. In burst mode the FREQ / AM DEPTH / FM DEVIATION buttons set PERIOD / ON TIME / COUNT, START SWEEP becomes START BURST (fires COUNT bursts, restarts continuous bursts) and the CTCSS button is blank. Console: "modulation burst|period|ontime|count|trigger". The Si4468 stays in TX with its PLL untouched: it runs OOK in asynchronous TX direct mode from GPIO1, driven by TIM8_CH3 PWM on PB9 (one pulse mode with the repetition counter for COUNT bursts). GPIO1 is normally the CTS line to PB9, so while burst is selected GPIO1 is an input and CTS is polled over SPI (READ_CMD_BUFF, the reply is read in the same transaction). PB9 only drives while GPIO1 is an input and is released before every Si4468 reset. MODEM_FREQ_DEV is set to 0 in burst mode, OOK otherwise shifts the carrier by the deviation of the RX configuration. - Burst works where the Si4468 makes the output (up to 1.13 GHz and the mixer path above it), not from the ADF directly ("MOD: BURST N/A"). - Period 2us..89s, resolution about period/65536, timing from the MCU clock (about 1%). - The new settings fill alignment holes of setting_t: its size and the stored presets stay valid. F072 builds are unchanged. Tested on HW V0.5.4 with a tinySA basic in zero span: ON level and frequency equal to CW, about 48 dB on/off ratio, COUNT bursts exact, pulses from about 2 us. --- nanovna.h | 34 ++++++++++++++- sa_cmd.c | 26 ++++++++++-- sa_core.c | 84 +++++++++++++++++++++++++++++++++++- si4468.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ ui.c | 108 ++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 368 insertions(+), 9 deletions(-) diff --git a/nanovna.h b/nanovna.h index 03b06f0..46a7c5a 100644 --- a/nanovna.h +++ b/nanovna.h @@ -118,6 +118,7 @@ #define __NOISE_FIGURE__ #define __VBW__ #define __SWEEP_RESTART__ +#define __BURST__ // Burst modulation: TIM8 on PB9 keys the OOK of the Si4468 (GPIO1, normally CTS) // #define DIRECT_CORRECTION // Not enough space for config in one flash page. #define DB_PER_DEGREE_BELOW 0.056 #define DB_PER_DEGREE_ABOVE 0.069 @@ -349,7 +350,11 @@ enum { #ifdef TINYSA4 MO_NFM2, MO_NFM3, #endif - MO_WFM, MO_EXTERNAL, MO_MAX + MO_WFM, MO_EXTERNAL, +#ifdef __BURST__ + MO_BURST, +#endif + MO_MAX }; #define MODE_OUTPUT(x) ((x) == M_GENLOW || (x) == M_GENHIGH ) @@ -507,6 +512,16 @@ void toggle_tracking_output(void); extern int32_t frequencyExtra; void set_modulation(int); void set_modulation_frequency(float); +#ifdef __BURST__ +#define BURST_MIN_TIME 1e-6 // shortest ON and OFF time [s] +#define BURST_MAX_PERIOD 89.0 // TIM8: 65536 * 65536 ticks at 48 MHz +#define BURST_DEFAULT_PERIOD 1e-3 +#define BURST_DEFAULT_ON_TIME 100e-6 +void set_burst_period(float); +void set_burst_on_time(float); +void set_burst_count(int); +void burst_trigger(void); +#endif int search_maximum(int m, freq_t center, int span); //extern int setting.modulation; void set_measurement(int); @@ -1281,6 +1296,9 @@ typedef struct setting bool pulse; // bool bool stored[TRACES_MAX]; // enum bool normalized[TRACES_MAX]; // enum +#ifdef __BURST__ + float burst_period; // seconds, fills an alignment hole so old presets stay valid +#endif #ifdef __BANDS__ band_t bands[BANDS_MAX]; #endif @@ -1351,6 +1369,9 @@ typedef struct setting #ifdef TINYSA4 uint16_t modulation_depth_x100; // AM (30% - 100%) multiplied by 100 uint16_t modulation_deviation_div100; // FM (2.5kHz to 100kHz) divided by 100 +#endif +#ifdef __BURST__ + uint16_t burst_count; // 0 = continuous, fills an alignment hole #endif int decay; // KM_DECAY < 1000000 int attack; // KM_ATTACK < 20000 @@ -1372,6 +1393,9 @@ typedef struct setting float unit_scale; float normalize_level; // Level to set normalize to, zero if not doing anything +#ifdef __BURST__ + float burst_on_time; // seconds, fills an alignment hole +#endif freq_t frequency_step; freq_t frequency0; @@ -1490,6 +1514,9 @@ typedef char assert_config[sizeof(config_t)> SAVE_CONFIG_SIZE ? -1 : 1]; #endif typedef char assert_setting[sizeof(setting_t)> SAVE_PROP_CONFIG_SIZE ? -1 : 1]; // Check setting size +#ifdef __BURST__ +typedef char assert_burst_setting[sizeof(setting_t) == 1584 ? 1 : -1]; // The burst fields fill alignment holes: presets saved before them stay valid +#endif // Should include all save slots #define SAVE_CONFIG_AREA_SIZE (SAVE_CONFIG_SIZE + SAVEAREA_MAX * SAVE_PROP_CONFIG_SIZE) @@ -1993,6 +2020,11 @@ extern bool ADF4351_frequency_changed; extern bool SI4463_frequency_changed; extern bool SI4463_offset_changed; extern int SI4463_is_in_tx_mode(void); +#ifdef __BURST__ +extern void SI4463_burst_mode(int on); +extern void SI4463_burst_start(float period, float on_time, uint16_t count); +extern void SI4463_burst_stop(void); +#endif extern int16_t SI4463_noise_correction_x10; void switch_SI4463_RSSI_correction(bool); extern int old_R; diff --git a/sa_cmd.c b/sa_cmd.c index c09c070..56a2eab 100644 --- a/sa_cmd.c +++ b/sa_cmd.c @@ -84,17 +84,25 @@ VNA_SHELL_FUNCTION(cmd_mode) VNA_SHELL_FUNCTION(cmd_modulation ) { -#ifdef TINYSA4 +#ifdef __BURST__ + static const char cmd_mod[] = "off|am|fm|freq|depth|deviation|burst|period|ontime|count|trigger"; +#elif defined(TINYSA4) static const char cmd_mod[] = "off|am|fm|freq|depth|deviation"; #else static const char cmd_mod[] = "off|am|nfm|wfm|extern|freq"; #endif if (argc < 1) { usage: +#ifdef __BURST__ + usage_printf("modulation %s [value]\r\n", cmd_mod); +#else usage_printf("modulation %s 100..6000\r\n", cmd_mod); +#endif return; } -#ifdef TINYSA4 +#ifdef __BURST__ + static const int cmd_mod_val[] = { MO_NONE, MO_AM, MO_WFM, -1, -1, -1, MO_BURST, -1, -1, -1, -1}; +#elif defined(TINYSA4) static const int cmd_mod_val[] = { MO_NONE, MO_AM, MO_WFM, -1, -1, -1}; #else static const int cmd_mod_val[] = { MO_NONE, MO_AM, MO_NFM, MO_WFM, MO_EXTERNAL, -1}; @@ -104,6 +112,10 @@ VNA_SHELL_FUNCTION(cmd_modulation ) goto usage; if (cmd_mod_val[m] >=0) set_modulation(cmd_mod_val[m]); +#ifdef __BURST__ + else if (m == 10) + burst_trigger(); +#endif else { if (argc != 2) goto usage; @@ -113,8 +125,16 @@ VNA_SHELL_FUNCTION(cmd_modulation ) case 3: set_modulation_frequency(a); break; case 4: set_depth(a); break; case 5: set_deviation(a); break; - +#ifdef __BURST__ + case 7: set_burst_period(my_atof(argv[1])); break; // seconds, m/u suffix allowed + case 8: set_burst_on_time(my_atof(argv[1])); break; + case 9: set_burst_count(a); break; +#endif } +#ifdef __BURST__ + if (m >= 7 && MODE_OUTPUT(setting.mode) && !msc_disk_mode) + draw_menu(); // burst settings do not set dirty: update the MOD button +#endif #else set_modulation_frequency(a); #endif diff --git a/sa_core.c b/sa_core.c index f022447..4d3a8f6 100644 --- a/sa_core.c +++ b/sa_core.c @@ -343,6 +343,10 @@ void set_output_path(freq_t f, float level) signal_path = PATH_OFF; set_path: +#ifdef __BURST__ + // Before the TX (re)start: burst only where the Si4468 makes the output, not from the ADF directly + SI4463_burst_mode(setting.modulation == MO_BURST && (signal_path == PATH_LOW || signal_path == PATH_DIRECT || signal_path == PATH_ULTRA)); +#endif switch(signal_path) { case PATH_OFF: if (SI4463_is_in_tx_mode()) @@ -686,6 +690,11 @@ void reset_settings(int m) #ifdef TINYSA4 setting.modulation_depth_x100 = 80; // %80 setting.modulation_deviation_div100 = 30; // 3kHz +#endif +#ifdef __BURST__ + setting.burst_period = BURST_DEFAULT_PERIOD; + setting.burst_on_time = BURST_DEFAULT_ON_TIME; + setting.burst_count = 0; // continuous #endif setting.step_delay = 0; setting.offset_delay = 0; @@ -1129,9 +1138,65 @@ void toggle_below_IF(void) void set_modulation(int m) { setting.modulation = m; +#ifdef __BURST__ + if (m == MO_BURST && setting.burst_period == 0) { // preset saved before burst existed + setting.burst_period = BURST_DEFAULT_PERIOD; + setting.burst_on_time = BURST_DEFAULT_ON_TIME; + } +#endif dirty = true; } +#ifdef __BURST__ +enum { BURST_CHANGED = 1, BURST_TRIGGER }; +static volatile uint8_t burst_request; // handled by the modulation loop in perform(), no RF re-setup + +void set_burst_period(float p) +{ + if (p < 2 * BURST_MIN_TIME) + p = 2 * BURST_MIN_TIME; + if (p > BURST_MAX_PERIOD) + p = BURST_MAX_PERIOD; + setting.burst_period = p; + set_burst_on_time(setting.burst_on_time); // shorten the ON time when it no longer fits +} + +void set_burst_on_time(float t) +{ + if (t > setting.burst_period - BURST_MIN_TIME) + t = setting.burst_period - BURST_MIN_TIME; + if (t < BURST_MIN_TIME) + t = BURST_MIN_TIME; + setting.burst_on_time = t; + burst_request = BURST_CHANGED; +} + +void set_burst_count(int c) +{ + if (c < 0) + c = 0; + if (c > 65535) + c = 65535; + setting.burst_count = c; + burst_request = BURST_CHANGED; +} + +void burst_trigger(void) +{ + burst_request = BURST_TRIGGER; +} + +static void burst_start(void) // continuous, or COUNT bursts after a trigger +{ + uint8_t r = burst_request; + burst_request = 0; + if (setting.burst_count == 0 || r == BURST_TRIGGER) + SI4463_burst_start(setting.burst_period, setting.burst_on_time, setting.burst_count); + else + SI4463_burst_stop(); // COUNT bursts wait for a trigger +} +#endif + #ifdef TINYSA4 void set_deviation(int d) { @@ -4015,6 +4080,12 @@ pureRSSI_t perform(bool break_on_operation, int i, freq_t f, int tracking) / int *current_fm_modulation = 0; if (MODE_OUTPUT(setting.mode)) { +#ifdef __BURST__ + if (setting.modulation == MO_BURST) { + if (i == 1) // LO's are set: TIM8 keys the Si4468 from here, the PLL's are not touched + burst_start(); + } else +#endif if (setting.modulation != MO_NONE && setting.modulation != MO_EXTERNAL && setting.modulation_frequency != 0) { modulation_counter = 0; cycle_counter = 0; @@ -4038,6 +4109,13 @@ pureRSSI_t perform(bool break_on_operation, int i, freq_t f, int tracking) / } } modulation_again: +#ifdef __BURST__ + if (MODE_OUTPUT(setting.mode) && setting.modulation == MO_BURST) { + if (i == 1 && burst_request) // new burst settings or a trigger: reload TIM8 only + burst_start(); + chThdSleepMilliseconds(1); + } else +#endif // ----------------------------------------------------- apply modulation for output modes --------------------------------------- if (MODE_OUTPUT(setting.mode) && setting.modulation != MO_NONE && setting.modulation != MO_EXTERNAL){ if (setting.modulation == MO_AM) { // AM modulation @@ -4755,8 +4833,12 @@ again: // Spur redu start_of_sweep_timestamp = chVTGetSystemTimeX(); // initialize start sweep time if (MODE_OUTPUT(setting.mode)) { // No substepping and no RSSI in output mode - if (break_on_operation && operation_requested) // break subscanning if requested + if (break_on_operation && operation_requested) { // break subscanning if requested +#ifdef __BURST__ + SI4463_burst_stop(); // carrier off while the input is handled +#endif return(0); // abort + } if ( i==1 && MODE_OUTPUT(setting.mode) && setting.modulation != MO_NONE && setting.modulation != MO_EXTERNAL) { // if in output mode with modulation and LO setup done // i = 1; // Everything set so skip LO setting diff --git a/si4468.c b/si4468.c index fe71385..8a8335c 100644 --- a/si4468.c +++ b/si4468.c @@ -1202,6 +1202,23 @@ static uint8_t SI4463_channel = 0; static uint8_t SI4463_in_tx_mode = false; //int SI4463_R = 5; static int SI4463_output_level = 0x20; +#ifdef __BURST__ +static uint8_t SI4463_burst = false; // GPIO1 is the OOK input keyed by TIM8 on PB9, so CTS is polled over SPI + +static uint8_t SI4463_read_CTS_SPI(void) +{ + set_SPI_mode(SPI_MODE_SI); + while (SPI_RX_IS_NOT_EMPTY(SI4432_SPI)) + (void)SPI_READ_8BIT(SI4432_SPI); // a lingering byte would be taken for the CTS + SI_CS_LOW; + SPI_WRITE_8BIT(SI4432_SPI, SI446X_CMD_READ_CMD_BUFF); + SPI_WRITE_8BIT(SI4432_SPI, 0xFF); + while (SPI_IS_BUSY(SI4432_SPI)); + uint8_t cts = SPI_READ_16BIT(SI4432_SPI) >> 8; // drop SI446X_CMD_READ_CMD_BUFF + SI_CS_HIGH; + return cts; +} +#endif static si446x_state_t SI4463_get_state(void); static int SI4463_set_state(si446x_state_t); @@ -1222,6 +1239,15 @@ extern volatile int sleep; int SI4463_wait_CTS(void) { int t=0; +#ifdef __BURST__ + if (SI4463_burst) { // CTS over SPI: GPIO1 is the burst input + while (SI4463_read_CTS_SPI() != 0xFF) { + if (++t >= SI4463_CTS_TIMEOUT/16) + return -1; + } + return 0; + } +#endif while (!SI4463_READ_CTS) { t++; if (t >=SI4463_CTS_TIMEOUT) @@ -1370,14 +1396,30 @@ int SI4463_do_api(void* data, uint8_t len, void* out, uint8_t outLen) while (SPI_RX_IS_NOT_EMPTY(SI4432_SPI)) (void)SPI_READ_8BIT(SI4432_SPI); // Remove lingering bytes from SPI RX buffer +#ifdef __BURST__ + int t = 0; + if (!SI4463_burst) // While bursting CTS is checked in the response read itself +#endif if (SI4463_WAIT_CTS) return -1; // Wait for CTS +#ifdef __BURST__ +read_again: +#endif SI_CS_LOW; #if 1 SPI_WRITE_8BIT(SI4432_SPI, SI446X_CMD_READ_CMD_BUFF); SPI_WRITE_8BIT(SI4432_SPI, 0xFF); while (SPI_IS_BUSY(SI4432_SPI)); +#ifdef __BURST__ + if ((SPI_READ_16BIT(SI4432_SPI) >> 8) != 0xFF) { // drop SI446X_CMD_READ_CMD_BUFF, the response follows only a CTS of 0xFF + SI_CS_HIGH; + if (++t >= SI4463_CTS_TIMEOUT/16) + return -1; + goto read_again; + } +#else SPI_READ_16BIT(SI4432_SPI); // drop SI446X_CMD_READ_CMD_BUFF and CTS 0xFF +#endif #else shiftOut( SI446X_CMD_READ_CMD_BUFF ); shiftIn(); // Should always be 0xFF @@ -1508,6 +1550,73 @@ void SI4463_set_gpio(int i, int s) SI4463_refresh_gpio(); } +#ifdef __BURST__ +// +// Burst: TIM8_CH3 on PB9 (AF10) drives GPIO1, the TX direct mode OOK input, while the Si4468 stays in TX. +// GPIO1 is the CTS output to PB9 otherwise, so PB9 only drives while GPIO1 is an input. +// +void SI4463_burst_stop(void) +{ + TIM8->CCMR2 = TIM_CCMR2_OC3M_2; // force inactive: carrier off + TIM8->CR1 = 0; +} + +static void SI4463_burst_release_pin(void) +{ + SI4463_burst_stop(); + palSetLineMode(LINE_RX_CTS, PAL_MODE_INPUT_PULLUP); // stop driving before GPIO1 is the CTS output again + gpio_state[1] = SI446X_GPIO_MODE_CTS; +} + +void SI4463_burst_mode(int on) +{ + if (on == SI4463_burst) + return; + if (on) { + palSetLineMode(LINE_RX_CTS, PAL_MODE_INPUT_PULLDOWN); // keeps the carrier off once GPIO1 is an input + gpio_state[1] = SI446X_GPIO_MODE_INPUT; + SI4463_refresh_gpio(); + SI4463_burst = true; // from here CTS is polled over SPI + SI4463_wait_CTS(); + rccEnableTIM8(false); + SI4463_burst_stop(); + palSetLineMode(LINE_RX_CTS, PAL_MODE_ALTERNATE(10) | PAL_STM32_OSPEED_HIGHEST); + } else { + SI4463_burst_release_pin(); + SI4463_refresh_gpio(); // still polls CTS over SPI + SI4463_burst = false; + } +} + +// count 0: continuous, else count bursts. Each period is off first, then on. +void SI4463_burst_start(float period, float on_time, uint16_t count) +{ + if (!SI4463_burst) + return; + uint32_t ticks = period * STM32_TIM8CLK; + uint32_t psc = ticks >> 16; // 16 bit period + uint32_t arr = ticks / (psc + 1); + uint32_t on = on_time * STM32_TIM8CLK / (psc + 1) + 0.5; + if (arr < 2) + arr = 2; + if (on < 1) + on = 1; + if (on >= arr) + on = arr - 1; + TIM8->CR1 = 0; + TIM8->PSC = psc; + TIM8->ARR = arr - 1; + TIM8->CCR3 = arr - on; + TIM8->RCR = count ? count - 1 : 0; + TIM8->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_0 | TIM_CCMR2_OC3PE; // PWM mode 2: on from CCR3, off when stopped at 0 + TIM8->CCER = TIM_CCER_CC3E; + TIM8->BDTR = TIM_BDTR_MOE; + TIM8->EGR = TIM_EGR_UG; // load prescaler, period and repetition counter + TIM8->SR = 0; + TIM8->CR1 = TIM_CR1_ARPE | TIM_CR1_CEN | (count ? TIM_CR1_OPM : 0); // one pulse mode stops after count periods +} +#endif + #if 0 static void SI4463_clear_FIFO(void) { @@ -1558,10 +1667,20 @@ void SI4463_start_tx(uint8_t CHANNEL) { uint8_t data[] = { SI446X_CMD_SET_PROPERTY, 0x20, 0x01, 0x00, +#ifdef __BURST__ + SI4463_burst ? 0xA9 : 0x00, // OOK in asynchronous direct mode from GPIO1 (burst) or CW mode +#else 0x00, // CW mode +#endif }; SI4463_do_api(data, sizeof(data), NULL, 0); } +#ifdef __BURST__ + if (SI4463_burst) { // OOK also shifts by MODEM_FREQ_DEV (300kHz from the RX config): keep the carrier frequency + static const uint8_t no_deviation[] = { SI446X_CMD_SET_PROPERTY, 0x20, 0x03, 0x0a, 0x00, 0x00, 0x00 }; + SI4463_do_api((void *)no_deviation, sizeof(no_deviation), NULL, 0); + } +#endif #endif #if 1 { @@ -2612,6 +2731,12 @@ freq_t SI4463_set_freq(freq_t freq) void SI4463_init_rx(void) { +#ifdef __BURST__ + if (SI4463_burst) { // the reset makes GPIO1 the CTS output again + SI4463_burst_release_pin(); + SI4463_burst = false; + } +#endif reset: SI_SDN_LOW; my_microsecond_delay(10000); diff --git a/ui.c b/ui.c index bd1c030..bf60a74 100644 --- a/ui.c +++ b/ui.c @@ -1485,6 +1485,9 @@ enum { KM_FILENAME, #endif KM_TRIGGER_GRID, +#ifdef __BURST__ + KM_BURST_PERIOD, KM_BURST_ON, KM_BURST_COUNT, +#endif KM_NONE // always at enum end }; @@ -1577,6 +1580,11 @@ static const struct { [KM_FILENAME] = {keypads_text , "NAME"}, // filename #endif [KM_TRIGGER_GRID] = {keypads_time , "INTERVAL"}, // KM_CODE +#ifdef __BURST__ +[KM_BURST_PERIOD] = {keypads_plusmin_unit, "BURST\nPERIOD"}, +[KM_BURST_ON] = {keypads_plusmin_unit, "BURST\nON TIME"}, +[KM_BURST_COUNT] = {keypads_positive , "BURST\nCOUNT"}, +#endif }; #if 0 // Not used @@ -1714,6 +1722,11 @@ static UI_FUNCTION_ADV_CALLBACK(menu_restart_acb){ (void)item; (void)data; if(b){ +#ifdef __BURST__ + if (setting.modulation == MO_BURST) + plot_printf(b->text, sizeof b->text, "START BURST"); + else +#endif if (setting.sweep) { if (current_index >= 0) { float current_level = setting.level + ((float)current_index)* setting.level_sweep / (float)sweep_points; @@ -1725,6 +1738,12 @@ static UI_FUNCTION_ADV_CALLBACK(menu_restart_acb){ plot_printf(b->text, sizeof b->text, "START SWEEP"); return; } +#ifdef __BURST__ + if (setting.modulation == MO_BURST) { + burst_trigger(); // COUNT bursts, or restart the continuous bursts + return; + } +#endif setting.sweep = !setting.sweep; dirty = true; } @@ -2551,7 +2570,11 @@ const char * const menu_modulation_text[MO_MAX]= #else "FM 3kHz", #endif - "FM 75kHz", "External"}; + "FM 75kHz", "External", +#ifdef __BURST__ + "Burst", +#endif +}; static UI_FUNCTION_ADV_CALLBACK(menu_modulation_acb) { @@ -2588,9 +2611,20 @@ static UI_FUNCTION_ADV_CALLBACK(menu_audible_harmonic) (void)item; (void)data; if (b){ +#ifdef __BURST__ + if (setting.modulation == MO_BURST) { // blank button, CTCSS has no meaning for a burst + b->text[0] = 0; + return; + } +#endif + plot_printf(b->text, sizeof b->text, "CTCSS + 1 kHz"); b->icon = (config.wfm_1khz_harmonic && setting.modulation_frequency < MAX_CTCSS_FREQ) ? BUTTON_ICON_CHECK : BUTTON_ICON_NOCHECK; return; } +#ifdef __BURST__ + if (setting.modulation == MO_BURST) + return; +#endif if (setting.modulation_frequency >= MAX_CTCSS_FREQ) return; config.wfm_1khz_harmonic = config.wfm_1khz_harmonic ? 0 : 100; // Toggle between off and 100% @@ -2607,6 +2641,15 @@ static UI_FUNCTION_ADV_CALLBACK(menu_smodulation_acb){ plot_printf(b->text, sizeof b->text, "MOD: %s", menu_modulation_text[setting.modulation]); else { #ifdef TINYSA4 +#ifdef __BURST__ + if (setting.modulation == MO_BURST) { + if (!setting.mixer_output && get_sweep_frequency(ST_STOP) >= MAX_LOW_OUTPUT_FREQ) // output straight from the ADF, the Si4468 can not key it + plot_printf(b->text, sizeof b->text, "MOD: BURST N/A"); + else + plot_printf(b->text, sizeof b->text, setting.burst_count ? "MOD: BURST %.3Fs/%.3Fs x%d" : "MOD: BURST %.3Fs/%.3Fs", + setting.burst_on_time, setting.burst_period, setting.burst_count); + } else +#endif if (setting.modulation == MO_AM) plot_printf(b->text, sizeof b->text, "MOD: %8.4FHz AM %d%%", setting.modulation_frequency, setting.modulation_depth_x100); else @@ -4715,10 +4758,13 @@ static const menuitem_t menu_modulation[] = { { MT_FORM | MT_ADV_CALLBACK | MT_LOW, MO_AM, "AM", menu_modulation_acb}, #ifdef TINYSA4 { MT_FORM | MT_ADV_CALLBACK, MO_WFM, "FM", menu_modulation_acb}, +#ifdef __BURST__ + { MT_FORM | MT_ADV_CALLBACK, MO_BURST, "BURST", menu_modulation_acb}, +#endif { MT_FORM | MT_KEYPAD, KM_MODULATION, "FREQ: %s", "1Hz..3.5kHz"}, { MT_FORM | MT_KEYPAD, KM_DEPTH, "AM DEPTH: %s%%", "0..100"}, { MT_FORM | MT_KEYPAD, KM_DEVIATION, "FM DEVIATION: %s", "1kHz..300kHz"}, - { MT_FORM | MT_ADV_CALLBACK, 0, "CTCSS + 1 kHz", menu_audible_harmonic}, + { MT_FORM | MT_ADV_CALLBACK, 0, MT_CUSTOM_LABEL, menu_audible_harmonic}, // { MT_FORM | MT_ADV_CALLBACK, MO_NFM2, MT_CUSTOM_LABEL, menu_modulation_acb}, // { MT_FORM | MT_ADV_CALLBACK, MO_NFM3, MT_CUSTOM_LABEL, menu_modulation_acb}, #else @@ -4731,6 +4777,25 @@ static const menuitem_t menu_modulation[] = { { MT_FORM | MT_NONE, 0, NULL, menu_back} // next-> menu_back }; +#ifdef __BURST__ +// In burst mode the FREQ, AM DEPTH and FM DEVIATION buttons of menu_modulation set the burst +static const menuitem_t menu_burst_keypads[] = { + { MT_FORM | MT_KEYPAD, KM_BURST_PERIOD, "PERIOD: %s", "2us..89s"}, + { MT_FORM | MT_KEYPAD, KM_BURST_ON, "ON TIME: %s", "1us..PERIOD"}, + { MT_FORM | MT_KEYPAD, KM_BURST_COUNT, "COUNT: %s", "0=continuous..65535"}, +}; + +static const menuitem_t *burst_item(const menuitem_t *m) // m is a keypad item +{ + if (setting.modulation == MO_BURST) { + if (m->data == KM_MODULATION) return &menu_burst_keypads[0]; + if (m->data == KM_DEPTH) return &menu_burst_keypads[1]; + if (m->data == KM_DEVIATION) return &menu_burst_keypads[2]; + } + return m; +} +#endif + static const menuitem_t menu_sweep[] = { { MT_FORM | MT_KEYPAD, KM_SPAN, "SPAN: %s", VARIANT("0..350MHz", range_text)}, { MT_FORM | MT_KEYPAD | MT_LOW, KM_LEVELSWEEP,"LEVEL CHANGE: %s", VARIANT("-70..70","-90..90")}, @@ -6035,6 +6100,23 @@ static void fetch_numeric_target(uint8_t mode) uistat.value = setting.modulation_depth_x100; plot_printf(uistat.text, sizeof uistat.text, "%3d", (int)uistat.value); break; +#endif +#ifdef __BURST__ + case KM_BURST_PERIOD: + uistat.value = setting.burst_period; + plot_printf(uistat.text, sizeof uistat.text, "%.3Fs", uistat.value); + break; + case KM_BURST_ON: + uistat.value = setting.burst_on_time; + plot_printf(uistat.text, sizeof uistat.text, "%.3Fs", uistat.value); + break; + case KM_BURST_COUNT: + uistat.value = setting.burst_count; + if (setting.burst_count) + plot_printf(uistat.text, sizeof uistat.text, "%d", setting.burst_count); + else + plot_printf(uistat.text, sizeof uistat.text, "CONTINUOUS"); + break; #endif case KM_VAR: uistat.freq_value = setting.frequency_var; @@ -6331,6 +6413,17 @@ set_numeric_value(void) case KM_DEPTH: set_depth((int)uistat.value); break; +#ifdef __BURST__ + case KM_BURST_PERIOD: + set_burst_period(uistat.value); + break; + case KM_BURST_ON: + set_burst_on_time(uistat.value); + break; + case KM_BURST_COUNT: + set_burst_count((int)uistat.value); + break; +#endif case KM_PRESET_NAME: plot_printf(setting.preset_name, sizeof setting.preset_name, "%s", kp_buf); break; @@ -7012,6 +7105,9 @@ menu_invoke(int item) break; case MT_KEYPAD: +#ifdef __BURST__ + menu = burst_item(menu); +#endif uistat.auto_center_marker = false; if (current_menu_is_form()) { redraw_frame(); // Remove form numbers @@ -7368,8 +7464,12 @@ draw_menu_buttons(const menuitem_t *menu, uint32_t mask) text = (char *)m->label; // Only keypad retrieves value if (MT_MASK(m->type) == MT_KEYPAD) { - fetch_numeric_target(m->data); - plot_printf(button.text, sizeof button.text, m->label, uistat.text); + const menuitem_t *k = m; +#ifdef __BURST__ + k = burst_item(k); +#endif + fetch_numeric_target(k->data); + plot_printf(button.text, sizeof button.text, k->label, uistat.text); text = button.text; }