diff --git a/App/app/foxhunt.c b/App/app/foxhunt.c index 2682d865..f80dec65 100644 --- a/App/app/foxhunt.c +++ b/App/app/foxhunt.c @@ -90,8 +90,12 @@ // --- Beacon (fox) app -------------------------------------------------------- // Turns the radio into the hidden transmitter: each cycle keys up on the TX VFO and // repeats the CW fox identifier (MOE..MO5, or " MOE") in Morse for the TX window, -// then stays silent for the (adjustable) idle gap. The carrier stays up during the -// window; only the tone modulation is keyed on/off (EnterTxMute/ExitTxMute) = MCW on FM. +// then stays silent for the (adjustable) idle gap. Two keying modes (KEY_4): +// TONE (default): carrier stays up the whole window, only the tone is keyed on/off +// (EnterTxMute/ExitTxMute) = MCW on FM (F2A). +// CARR: the carrier (PA_ENABLE) is keyed together with the tone, so between +// elements the carrier itself is gone (carrier interruption, the ARDF +// field pattern) — harder to home in on, at the cost of some key clicks. #define FOXHUNT_BEACON_TONE_HZ 1000 // CW tone pitch (Hz) #define FOXHUNT_MORSE_UNIT_MS 100 // one Morse time unit (~12 WPM, ARDF pace) #define FOXHUNT_BEACON_IDLE_DEF 30 // default silence between IDs (s) @@ -212,6 +216,7 @@ static uint8_t beaconTx; // TX window length (s): the ID repeats for th static uint16_t beaconTxMsLeft; // ms left in the current TX window (drained as it plays) static uint8_t beaconTxSecShown; // whole-second value last painted on the TX line static uint8_t foxFox; // selected fox identifier (FOXHUNT_FOX_*) +static bool beaconCarrierKeyed; // false = TONE (F2A, keyed tone); true = CARR (keyed carrier) static char foxCall[FOXHUNT_CALLSIGN_MAX + 1]; // sanitised callsign for the CALL id static void FOXHUNT_EnterHunt(void); @@ -638,14 +643,15 @@ static void FOXHUNT_IdleCycle(int8_t dir) } // Apply a beacon number key in the given direction; returns true when it changed a -// setting so the caller can refresh. Keys follow the on-screen layout: 1 = TX (top-left), -// 2 = IDLE (below it), 3 = FOX (right). Shared by both beacon phases. +// setting so the caller can refresh. Keys: 1 = TX, 2 = IDLE, 3 = FOX, 4 = keying mode +// (TONE/CARR, a plain toggle so dir is moot). Shared by both beacon phases. static bool FOXHUNT_BeaconKey(KEY_Code_t key, int8_t dir) { switch (key) { case KEY_1: FOXHUNT_TxCycle(dir); return true; case KEY_2: FOXHUNT_IdleCycle(dir); return true; case KEY_3: FOXHUNT_FoxCycle(dir); return true; + case KEY_4: beaconCarrierKeyed = !beaconCarrierKeyed; return true; default: return false; } } @@ -946,8 +952,27 @@ static uint8_t FOXHUNT_MorseByte(char c) return 0; } -// Send one character as modulated CW: key the running TX tone on per element, -// muting the modulation (carrier stays up) between them. Returns true if aborted. +// Beacon keying primitive. Both modes key the tone (Enter/ExitTxMute); CARR additionally +// gates the PA in lockstep, so between elements the carrier itself is gone — not just the +// tone. Muting the tone in CARR too is deliberate: gating PA_ENABLE does not perfectly +// kill the carrier, and a tone left riding would bleed through that residual carrier as a +// near-continuous note. The PLL stays locked, so the carrier gate is only a GPIO toggle. +static void FOXHUNT_KeyOn(void) +{ + BK4819_ExitTxMute(); + if (beaconCarrierKeyed) + BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, true); +} +static void FOXHUNT_KeyOff(void) +{ + BK4819_EnterTxMute(); + if (beaconCarrierKeyed) + BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false); +} + +// Send one character as Morse: key each element on (FOXHUNT_KeyOn) for its dit/dah +// duration, off (FOXHUNT_KeyOff) in the gaps. What "key" means depends on the beacon +// mode (tone vs carrier), see above. Returns true if aborted. static bool FOXHUNT_MorseChar(char c, uint8_t visibleChars) { uint8_t code = FOXHUNT_MorseByte(c); @@ -964,9 +989,9 @@ static bool FOXHUNT_MorseChar(char c, uint8_t visibleChars) for (bit >>= 1; bit; bit >>= 1) { // then walk the elements, MSB first const uint16_t on = (code & bit) ? (FOXHUNT_MORSE_UNIT_MS * 3) // dah : FOXHUNT_MORSE_UNIT_MS; // dit - BK4819_ExitTxMute(); - if (FOXHUNT_TxDelay(on)) { BK4819_EnterTxMute(); return true; } - BK4819_EnterTxMute(); + FOXHUNT_KeyOn(); + if (FOXHUNT_TxDelay(on)) { FOXHUNT_KeyOff(); return true; } + FOXHUNT_KeyOff(); if (FOXHUNT_TxDelay(FOXHUNT_MORSE_UNIT_MS)) return true; // intra gap } @@ -985,11 +1010,14 @@ static bool FOXHUNT_MorseChar(char c, uint8_t visibleChars) // keys up for a fixed slot rather than sending a single one-shot. static void FOXHUNT_BeaconTransmit(void) { - RADIO_SetTxParameters(); // key up: carrier + PA + RADIO_SetTxParameters(); // key up: carrier + PA + PLL lock - // Prime the tone generator, then start silent before the first element. + // Prime the tone generator, then open the window silent before the first element: + // tone muted in both modes, and in CARR the carrier dropped too (PLL still locked). BK4819_TransmitTone(false, FOXHUNT_BEACON_TONE_HZ); BK4819_EnterTxMute(); + if (beaconCarrierKeyed) + BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false); beaconTxMsLeft = (uint16_t)beaconTx * 1000u; // drained inside FOXHUNT_TxDelay @@ -1151,16 +1179,19 @@ static void FOXHUNT_BeaconDraw(bool txNow, uint8_t idleLeft) UI_PrintString(str, 0, 127, 3, 10); } - // Bottom-left column: 1 = TX window over 2 = IDLE gap. Bottom-right: 3 = FOX id over - // the freq (drawn by FOXHUNT_BeaconChrome). All three are inverse tags; each has its - // own number key, editable in either phase, so there is no cursor to show. + // Settings tags: TX (line 5, left) over IDLE (line 6, left). On line 5, right of TX: + // FOX shifted left with the keying mode (TONE/CARR) right-aligned beside it, so the pair + // spans roughly the width of the TX frequency drawn just below on line 6 + // (FOXHUNT_BeaconChrome). Keys: 1 TX, 2 IDLE, 3 FOX, 4 mode; editable in either phase. sprintf(str, "TX %us", beaconTx); FOXHUNT_Tag(str, 4, 5); sprintf(str, "IDLE %us", beaconIdle); FOXHUNT_Tag(str, 4, 6); - FOXHUNT_FoxLabel(str); - FOXHUNT_Tag(str, (uint8_t)(125 - strlen(str) * 4), 5); + FOXHUNT_Tag(str, 66, 5); + + const char *mode = beaconCarrierKeyed ? "CARR" : "TONE"; + FOXHUNT_Tag(mode, (uint8_t)(125 - strlen(mode) * 4), 5); } static void FOXHUNT_BeaconKeys(void) @@ -1270,7 +1301,7 @@ static void FOXHUNT_BeaconTick(void) // (eeprom_compat.c) so aircopy clones them with the VFOs. A factory reset clears it. #define FOXHUNT_CFG_ADDR 0x0090E0u #define FOXHUNT_CFG_MAGIC 0xF4u // tells a written config from erased flash (0xFF) -#define FOXHUNT_CFG_LEN 7 // magic + att + graph + audio + idle + fox + tx +#define FOXHUNT_CFG_LEN 8 // magic + att + graph + audio + idle + fox + tx + carrier // RAM mirror of the bytes last written to flash, so FOXHUNT_SaveConfig only touches // the flash when a value actually changed. @@ -1286,6 +1317,7 @@ static __attribute__((noinline)) void FOXHUNT_ConfigPack(uint8_t out[FOXHUNT_CFG out[4] = beaconIdle; out[5] = foxFox; out[6] = beaconTx; + out[7] = beaconCarrierKeyed; } // Restore persisted settings; erased/invalid flash leaves the defaults in place. @@ -1305,6 +1337,7 @@ static void FOXHUNT_LoadConfig(void) if (cfg[6] >= FOXHUNT_BEACON_TX_MIN && cfg[6] <= FOXHUNT_BEACON_TX_MAX && (cfg[6] % FOXHUNT_BEACON_TX_STEP) == 0) beaconTx = cfg[6]; + if (cfg[7] <= 1) beaconCarrierKeyed = cfg[7]; // erased (0xFF) legacy config -> keep default } FOXHUNT_ConfigPack(foxCfgSaved); // mirror the loaded (or default) state @@ -1338,15 +1371,16 @@ static void FOXHUNT_Begin(void) gCurrentVfo = gTxVfo; RADIO_SetupRegisters(true); - // Initialise the complete legacy seven-byte configuration before loading it. - // Keeping the layout unchanged preserves existing FieldOps settings and lets - // either independently compiled app update its fields without a migration. + // Initialise the full configuration before loading it. The carrier-keying byte was + // appended (index 7): an older seven-byte config reads 0xFF there and LoadConfig's + // `<= 1` guard rejects it, so it stays at the default below — no migration needed. attStep = 0; foxGraphMode = FOXHUNT_GRAPH_BAR; foxAudioMode = FOXHUNT_AUDIO_OFF; beaconIdle = FOXHUNT_BEACON_IDLE_DEF; beaconTx = FOXHUNT_BEACON_TX_DEF; foxFox = FOXHUNT_FOX_CALL; + beaconCarrierKeyed = false; // TONE (F2A) by default beaconPhaseTx = false; FOXHUNT_LoadConfig(); diff --git a/App/apps/app_api.h b/App/apps/app_api.h index 6a286fe2..58a0de21 100644 --- a/App/apps/app_api.h +++ b/App/apps/app_api.h @@ -231,6 +231,7 @@ typedef struct app_api { void (*tx_tone)(uint16_t hz);/* BK4819_TransmitTone prime (MCW tone) */ void (*tx_mute)(bool on); /* key the tone on(false)/off(true) via TxMute */ void (*tx_end)(void); /* PA off + RADIO_SetupRegisters (back to RX) */ + void (*tx_carrier)(bool on); /* gate the PA on/off (beacon CARR carrier keying) */ uint32_t (*tx_freq)(void); /* current TX VFO frequency (x10 Hz) */ void (*boot_callsign)(char *buf, uint8_t len); /* sanitised callsign from the boot message */ void (*print_string)(const char *s, uint8_t start, uint8_t end, diff --git a/App/apps/app_overlay.c b/App/apps/app_overlay.c index e52980d9..444aa1ad 100644 --- a/App/apps/app_overlay.c +++ b/App/apps/app_overlay.c @@ -850,6 +850,7 @@ static void app_tx_set_params(void) { RADIO_SetTxParameters(); } static void app_tx_tone(uint16_t hz) { BK4819_TransmitTone(false, hz); } static void app_tx_mute(bool on) { if (on) BK4819_EnterTxMute(); else BK4819_ExitTxMute(); } static void app_tx_end(void) { BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false); RADIO_SetupRegisters(true); } +static void app_tx_carrier(bool on) { BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, on); } static uint32_t app_tx_freq(void) { return gTxVfo->pTX->Frequency; } static void app_boot_callsign(char *buf, uint8_t len) { @@ -1051,6 +1052,7 @@ static const app_api_t app_api = { .tx_tone = app_tx_tone, .tx_mute = app_tx_mute, .tx_end = app_tx_end, + .tx_carrier = app_tx_carrier, .tx_freq = app_tx_freq, .boot_callsign = app_boot_callsign, .print_string = UI_PrintString, diff --git a/App/apps/beacon/beacon_app.c b/App/apps/beacon/beacon_app.c index 0228c980..b1567aac 100644 --- a/App/apps/beacon/beacon_app.c +++ b/App/apps/beacon/beacon_app.c @@ -17,12 +17,13 @@ /* * Beacon (fox) — overlay app. Ported from App/app/foxhunt.c, beacon sub-mode only. * Turns the radio into a hidden ARDF transmitter: keys up on the TX VFO and repeats - * a CW fox identifier (MOE..MO5 / MO / " MOE") as MCW for the TX window, then - * stays silent for the idle gap. The carrier stays up during the window; only the - * tone is keyed per Morse element. + * a CW fox identifier (MOE..MO5 / MO / " MOE") for the TX window, then stays + * silent for the idle gap. Two keying modes (key 4): TONE (default) keeps the carrier + * up and keys only the tone (MCW / F2A); CARR keys the PA together with the tone, so + * between elements the carrier itself is gone (carrier interruption, ARDF field pattern). * - * Keys: 1 TX window · 2 idle gap · 3 fox id (F reverses) · MENU restart idle - * long F keypad lock · EXIT quit. + * Keys: 1 TX window · 2 idle gap · 3 fox id · 4 keying mode TONE/CARR (F reverses) + * MENU restart idle · long F keypad lock · EXIT quit. */ #include @@ -64,7 +65,7 @@ static const uint8_t FONT_LOCK[9] = {0x7c,0x46,0x45,0x45,0x45,0x45,0x45,0x46,0x7 static const app_api_t *A; -static bool foxLocked, fArm, fLongDone, running, phaseTx; +static bool foxLocked, fArm, fLongDone, running, phaseTx, carrier; static uint16_t fHoldMs; static uint8_t beaconIdle, idleLeft, idleTick, beaconTx, secShown, charsSent, foxFox; static uint16_t txMsLeft; @@ -95,6 +96,7 @@ static bool settingKey(uint8_t key,int8_t dir){ if(dir>0){ if(++foxFox>=FOX_COUNT) foxFox=0; } else foxFox=foxFox?(uint8_t)(foxFox-1u):(uint8_t)(FOX_COUNT-1u); return true; + case APP_KEY_4: carrier=!carrier; return true; /* TONE <-> CARR (dir moot) */ default: return false; } } @@ -184,7 +186,8 @@ static void beaconDraw(bool txNow,uint8_t il){ } char *o=put(str,"TX "); o=putu(o,beaconTx); o=put(o,"s"); *o='\0'; tag(str,4,5); o=put(str,"IDLE "); o=putu(o,beaconIdle); o=put(o,"s"); *o='\0'; tag(str,4,6); - foxLabel(str); tag(str,(uint8_t)(125-slen(str)*4),5); + foxLabel(str); tag(str,66,5); + { const char *m=carrier?"CARR":"TONE"; tag(m,(uint8_t)(125-slen(m)*4),5); } } static void blit(void){ A->blit_status(); A->blit_full(); } @@ -218,15 +221,19 @@ static bool txDelay(uint16_t ms){ } return false; } +/* Keying: both modes key the tone; CARR gates the PA in lockstep so the carrier is truly + * gone between elements (tone muted too, else it bleeds through residual PA leakage). */ +static void keyOn(void){ A->tx_mute(false); if(carrier) A->tx_carrier(true); } +static void keyOff(void){ A->tx_mute(true); if(carrier) A->tx_carrier(false); } static bool morseChar(char c,uint8_t vis){ uint8_t code=morseByte(c); if(code==0){ if(txDelay(MORSE_UNIT*4)) return true; updateProgress(vis); return false; } uint8_t bit=0x80; while(!(code&bit))bit>>=1; for(bit>>=1;bit;bit>>=1){ uint16_t on=(code&bit)?(MORSE_UNIT*3):MORSE_UNIT; - A->tx_mute(false); - if(txDelay(on)){ A->tx_mute(true); return true; } - A->tx_mute(true); + keyOn(); + if(txDelay(on)){ keyOff(); return true; } + keyOff(); if(txDelay(MORSE_UNIT)) return true; } updateProgress(vis); @@ -235,7 +242,8 @@ static bool morseChar(char c,uint8_t vis){ static void transmit(void){ A->tx_set_params(); A->tx_tone(TONE_HZ); - A->tx_mute(true); + A->tx_mute(true); /* open muted (both modes) */ + if(carrier) A->tx_carrier(false); /* CARR: carrier off until first element */ txMsLeft=(uint16_t)beaconTx*1000u; bool stop=false; while(!stop && txMsLeft>0){ @@ -270,14 +278,15 @@ static void tickDelay(void){ for(uint8_t i=0;idelay_ms(10); /* ---- config (deferred) ---- */ static void loadConfig(void){ - uint8_t c[4]; A->cfg_load(c,4); + uint8_t c[5]; A->cfg_load(c,5); if(c[0]==CFG_MAGIC){ if(c[1]>=IDLE_MIN&&c[1]<=IDLE_MAX&&(c[1]%IDLE_STEP)==0) beaconIdle=c[1]; if(c[2]=TX_MIN&&c[3]<=TX_MAX&&(c[3]%TX_STEP)==0) beaconTx=c[3]; + if(c[4]<=1) carrier=c[4]; /* erased (0xFF) legacy config -> keep default */ } } -static void saveConfig(void){ uint8_t c[4]={CFG_MAGIC,beaconIdle,foxFox,beaconTx}; A->cfg_save(c,4); } +static void saveConfig(void){ uint8_t c[5]={CFG_MAGIC,beaconIdle,foxFox,beaconTx,(uint8_t)carrier}; A->cfg_save(c,5); } static void txDenied(void){ chrome(); @@ -289,7 +298,7 @@ static void txDenied(void){ __attribute__((section(".text.entry"),used)) void app_main(const app_api_t *api){ A=api; - foxLocked=fArm=fLongDone=false; fHoldMs=0; + foxLocked=fArm=fLongDone=carrier=false; fHoldMs=0; beaconIdle=IDLE_DEF; beaconTx=TX_DEF; foxFox=FOX_CALL; prevKey=APP_KEY_INVALID; A->boot_callsign(foxCall,sizeof(foxCall)); diff --git a/App/driver/eeprom_compat.c b/App/driver/eeprom_compat.c index fd36bb58..a7b751a1 100644 --- a/App/driver/eeprom_compat.c +++ b/App/driver/eeprom_compat.c @@ -51,10 +51,10 @@ static const AddrMapping_t ADDR_MAPPINGS[] = { _MK_MAPPING(0x008000, 0x008000, 0x00886E), // 1024 MR + 7 VFO Attributes * 2 Bytes (ex 0x002000) 0x008000 -> 0x00880E // List name * 4 Bytes 0x00880E -> 0x00886E - _MK_MAPPING(0x009000, 0x009000, 0x0090E7), // 14 VFO * 16 Bytes = 0x9000 -> 0x90E0 (the old + _MK_MAPPING(0x009000, 0x009000, 0x0090E8), // 14 VFO * 16 Bytes = 0x9000 -> 0x90E0 (the old // 0x90D6 bound was 10 B short: it truncated the // 470 MHz VFO1 record at 0x90D0 -> 0x90DF) - // Fox Hunt / Beacon settings * 7 Bytes 0x90E0 -> 0x90E7 + // Fox Hunt / Beacon settings * 8 Bytes 0x90E0 -> 0x90E8 // (written directly by app/foxhunt.c; concatenated // here so aircopy clones them with the VFOs)