mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 03:15:37 +00:00
Rework Fox Hunt/Beacon for ARDF: fox IDs, TX window, unified keys
This commit is contained in:
1 parent
d5cde97e5b
commit
3bdddce67b
2 files changed
+305
-144
No files matched your search
+303
-142
@@ -77,19 +77,47 @@
|
||||
#define FOXHUNT_AUDIO_STATION 2
|
||||
|
||||
// --- Beacon (fox) sub-mode ---------------------------------------------------
|
||||
// Turns the radio into the hidden transmitter: each cycle keys up on the TX VFO
|
||||
// and sends the CW message ("<call> BEACON") in Morse, then stays silent for the
|
||||
// (adjustable) idle gap. The carrier stays up during the message; only the tone
|
||||
// modulation is keyed on/off (EnterTxMute/ExitTxMute) = MCW on FM.
|
||||
// Turns the radio into the hidden transmitter: each cycle keys up on the TX VFO and
|
||||
// repeats the CW fox identifier (MOE..MO5, or "<call> 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.
|
||||
#define FOXHUNT_BEACON_TONE_HZ 1000 // CW tone pitch (Hz)
|
||||
#define FOXHUNT_MORSE_UNIT_MS 80 // one Morse time unit (~15 WPM)
|
||||
#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)
|
||||
#define FOXHUNT_BEACON_IDLE_MIN 5 // shortest idle gap (s)
|
||||
#define FOXHUNT_BEACON_IDLE_MAX 60 // longest idle gap (s)
|
||||
#define FOXHUNT_BEACON_IDLE_MAX 240 // longest idle gap (s) — 4 min = the ARDF
|
||||
// silence of a 5-fox cycle (60 s TX x4)
|
||||
#define FOXHUNT_BEACON_IDLE_STEP 5 // idle adjust step (s)
|
||||
// TX window: the ID is repeated for this many seconds each cycle (a real ARDF fox keys
|
||||
// up for a fixed slot, not a single one-shot). 60 s = the classic ARDF slot.
|
||||
#define FOXHUNT_BEACON_TX_DEF 30 // default TX window (s)
|
||||
#define FOXHUNT_BEACON_TX_MIN 5 // shortest TX window (s)
|
||||
#define FOXHUNT_BEACON_TX_MAX 60 // longest TX window (s)
|
||||
#define FOXHUNT_BEACON_TX_STEP 5 // TX adjust step (s)
|
||||
#define FOXHUNT_CALLSIGN_ADDR 0x00A0C8u // boot message line 1 in SPI flash
|
||||
#define FOXHUNT_CALLSIGN_MAX 12 // maximum boot-message characters used by the beacon
|
||||
|
||||
// Fox identifier (3 key). MOE..MO5 are the five standard IARU ARDF foxes ("MO" + 1..5
|
||||
// dits: E I S H 5); MO is the ARDF finish/home beacon (transmitted continuously on a real
|
||||
// course — here it rides the normal TX/IDLE cycle, so set a short IDLE for a near-constant
|
||||
// MO); CALL prefixes the operator's callsign (legal ID on the ham bands): "<call> MOE".
|
||||
// Pure MOE..MO5 / MO (no callsign) suit casual / PMR fox games.
|
||||
#define FOXHUNT_FOX_MOE 0
|
||||
#define FOXHUNT_FOX_MOI 1
|
||||
#define FOXHUNT_FOX_MOS 2
|
||||
#define FOXHUNT_FOX_MOH 3
|
||||
#define FOXHUNT_FOX_MO5 4
|
||||
#define FOXHUNT_FOX_MO 5
|
||||
#define FOXHUNT_FOX_CALL 6
|
||||
#define FOXHUNT_FOX_COUNT 7
|
||||
|
||||
// Framebuffer lines for the transmitted message (gFontBig spans two lines each):
|
||||
// CALL id shows the callsign on _CALL and the "MOx" below on _ID; a pure single-word
|
||||
// fox (MOE..MO5) is centred on _ONE.
|
||||
#define FOXHUNT_MSG_LINE_CALL 1
|
||||
#define FOXHUNT_MSG_LINE_ID 3
|
||||
#define FOXHUNT_MSG_LINE_ONE 2
|
||||
|
||||
// Attenuator via the BK4829 PGA (REG_13 bits 0..2) — the last gain stage before
|
||||
// the RSSI detector, so it scales the reading linearly. Reducing the early LNA
|
||||
// alone barely moved the reading on strong signals (mixer + PGA re-amplify).
|
||||
@@ -106,6 +134,7 @@ static uint8_t foxGraphMode; // FOXHUNT_GRAPH_BAR / _HIST, cycled by the 2 key
|
||||
static uint8_t attStep;
|
||||
static int16_t curDbm;
|
||||
static int16_t peakDbm;
|
||||
static int16_t minDbm; // lowest level held since the last reset (body-scan null)
|
||||
static int16_t trendRef;
|
||||
static int16_t trendDelta;
|
||||
static uint8_t trendTick;
|
||||
@@ -130,19 +159,28 @@ static const uint8_t FOXHUNT_MORSE_DIGIT[10] = {
|
||||
0x3F, 0x2F, 0x27, 0x23, 0x21, 0x20, 0x30, 0x38, 0x3C, 0x3E, // 0..9
|
||||
};
|
||||
|
||||
// Trailing character of each fox id: MOE / MOI / MOS / MOH / MO5 (index = FOXHUNT_FOX_*).
|
||||
static const char FOXHUNT_FOX_TAIL[5] = {'E', 'I', 'S', 'H', '5'};
|
||||
|
||||
// Beacon sub-mode state.
|
||||
static bool foxBeacon; // false = hunt (RX), true = beacon (TX)
|
||||
static bool beaconPhaseTx; // true = transmit this tick, false = idle gap
|
||||
static uint8_t beaconIdle; // configured silence between IDs (s)
|
||||
static uint8_t beaconIdleLeft; // seconds left in the current silence
|
||||
static uint8_t beaconIdleTick; // tick counter that clocks the idle countdown
|
||||
static char beaconMsg[24]; // CW message, e.g. "F4HWN BEACON"
|
||||
static char beaconMsg[24]; // CW message, e.g. "F4HWN MOE" or "MOS"
|
||||
static uint8_t beaconCharsSent; // characters completed in the current burst
|
||||
static uint8_t beaconTx; // TX window length (s): the ID repeats for this long
|
||||
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 char foxCall[FOXHUNT_CALLSIGN_MAX + 1]; // sanitised callsign for the CALL id
|
||||
|
||||
static void FOXHUNT_EnterHunt(void);
|
||||
static void FOXHUNT_EnterBeacon(void);
|
||||
static void FOXHUNT_BeaconDraw(bool txNow, uint8_t idleLeft);
|
||||
static void FOXHUNT_UpdateBeaconProgress(uint8_t visibleChars);
|
||||
static void FOXHUNT_BuildBeaconMsg(void);
|
||||
static void FOXHUNT_SaveConfig(void);
|
||||
|
||||
// Read the calibrated signal level of the current RX VFO, in dBm.
|
||||
@@ -320,6 +358,25 @@ static void FOXHUNT_DrawHist(void)
|
||||
}
|
||||
}
|
||||
|
||||
// Push the status line and the full frame buffer to the LCD, then mirror to K5Viewer.
|
||||
// Factored out because the same argument-less sequence runs after every full redraw
|
||||
// (hunt loop, beacon idle, beacon burst, TX-denied notice).
|
||||
static void FOXHUNT_BlitScreen(void)
|
||||
{
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
K5VIEWER_Update(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Draw a left-anchored inverse tag in the smallest font (the PK / MN / ATT / beacon
|
||||
// setting style). The box spans the text width, so the caller only gives x and the line.
|
||||
static void FOXHUNT_Tag(const char *s, uint8_t x, uint8_t line)
|
||||
{
|
||||
GUI_DisplaySmallestInverse(s, x, line, false, true, (uint8_t)(x + strlen(s) * 4));
|
||||
}
|
||||
|
||||
// Battery icon plus the optional voltage/percentage text, top-right of the status
|
||||
// line — shared by the hunt and beacon screens.
|
||||
static void FOXHUNT_DrawStatusBattery(void)
|
||||
@@ -408,10 +465,13 @@ static void FOXHUNT_Draw(void)
|
||||
UI_PrintStringSmallNormal(str, 127 - 3 * 7 - 2 - strlen(str) * 7, 0, 1);
|
||||
}
|
||||
|
||||
// Context line: peak hold (left) and S-meter (right) as small inverse labels
|
||||
// (scan-list tag style, 4 px/char).
|
||||
// Context line: peak hold (left), min hold (centre) and S-meter (right) as small
|
||||
// inverse labels (scan-list tag style, 4 px/char). PK and MN bracket a body-scan
|
||||
// rotation — the deeper MN sits below PK, the sharper the null.
|
||||
sprintf(str, "PK %d", peakDbm);
|
||||
GUI_DisplaySmallestInverse(str, 4, 2, false, true, 4 + strlen(str) * 4);
|
||||
FOXHUNT_Tag(str, 4, 2);
|
||||
sprintf(str, "MN %d", minDbm);
|
||||
FOXHUNT_Tag(str, (uint8_t)((LCD_WIDTH - strlen(str) * 4) / 2), 2);
|
||||
FOXHUNT_BuildS(sMeter, curDbm);
|
||||
GUI_DisplaySmallestInverse(sMeter, 126 - strlen(sMeter) * 4, 2, false, true, 125);
|
||||
|
||||
@@ -432,37 +492,53 @@ static void FOXHUNT_Draw(void)
|
||||
|
||||
// Attenuator as an inverse label (left), tuned frequency (right).
|
||||
sprintf(str, "ATT %ddB", FOXHUNT_ATT_DB[attStep]);
|
||||
GUI_DisplaySmallestInverse(str, 4, 6, false, true, 4 + strlen(str) * 4);
|
||||
FOXHUNT_Tag(str, 4, 6);
|
||||
FOXHUNT_DrawFreqBR(gRxVfo->pRX->Frequency);
|
||||
}
|
||||
|
||||
// Map a navigation key to a logical step (+1 = up/increase, -1 = down/decrease),
|
||||
// honouring the SET_NAV up/down orientation (UV-K1 vs UV-K5), like the rest of
|
||||
// the firmware (see MAIN_Key_UP_DOWN / spectrum GetDirection).
|
||||
static int8_t FOXHUNT_NavDir(KEY_Code_t key)
|
||||
// One key = one setting, each press cycling to the next value and wrapping — the same
|
||||
// scheme for the hunt gauges and the beacon settings, so there are no navigation arrows
|
||||
// to learn. FOXHUNT_ApplyAtt is re-asserted for the attenuator; the beacon values are
|
||||
// read where they take effect (TX window at the burst start, idle at the burst end, fox
|
||||
// at the next repeat), so cycling any of them is safe at any time, TX included.
|
||||
|
||||
// Attenuator: 0 -> -6 -> -15 -> -27 -> 0 dB.
|
||||
static void FOXHUNT_AttCycle(void)
|
||||
{
|
||||
int8_t dir = (key == KEY_UP) ? 1 : -1;
|
||||
return gEeprom.SET_NAV ? dir : -dir;
|
||||
attStep = (uint8_t)((attStep + 1) & 3);
|
||||
FOXHUNT_ApplyAtt();
|
||||
}
|
||||
|
||||
// Change the attenuator by one step in the given direction.
|
||||
static void FOXHUNT_AttStep(int8_t dir)
|
||||
// Fox identifier: MOE -> MOI -> MOS -> MOH -> MO5 -> CALL -> MOE.
|
||||
static void FOXHUNT_FoxCycle(void)
|
||||
{
|
||||
if (dir > 0) {
|
||||
if (attStep < 3) { attStep++; FOXHUNT_ApplyAtt(); }
|
||||
} else {
|
||||
if (attStep > 0) { attStep--; FOXHUNT_ApplyAtt(); }
|
||||
}
|
||||
foxFox = (uint8_t)((foxFox + 1) % FOXHUNT_FOX_COUNT);
|
||||
}
|
||||
|
||||
// Change the beacon idle gap by one step in the given direction (shared by the
|
||||
// idle-phase keys and the mid-transmission adjust).
|
||||
static void FOXHUNT_IdleStep(int8_t dir)
|
||||
// TX window: 5 s up to 60 s, then back to 5 s.
|
||||
static void FOXHUNT_TxCycle(void)
|
||||
{
|
||||
if (dir > 0) {
|
||||
if (beaconIdle < FOXHUNT_BEACON_IDLE_MAX) beaconIdle += FOXHUNT_BEACON_IDLE_STEP;
|
||||
} else {
|
||||
if (beaconIdle > FOXHUNT_BEACON_IDLE_MIN) beaconIdle -= FOXHUNT_BEACON_IDLE_STEP;
|
||||
beaconTx = (beaconTx >= FOXHUNT_BEACON_TX_MAX)
|
||||
? FOXHUNT_BEACON_TX_MIN : (uint8_t)(beaconTx + FOXHUNT_BEACON_TX_STEP);
|
||||
}
|
||||
|
||||
// Idle gap: 5 s up to 240 s, then back to 5 s.
|
||||
static void FOXHUNT_IdleCycle(void)
|
||||
{
|
||||
beaconIdle = (beaconIdle >= FOXHUNT_BEACON_IDLE_MAX)
|
||||
? FOXHUNT_BEACON_IDLE_MIN : (uint8_t)(beaconIdle + FOXHUNT_BEACON_IDLE_STEP);
|
||||
}
|
||||
|
||||
// Apply a beacon number key; returns true when it changed a setting so the caller can
|
||||
// refresh. The keys follow the on-screen layout: 1 = TX (top-left), 2 = IDLE (below it),
|
||||
// 3 = FOX (right). Shared by both beacon phases.
|
||||
static bool FOXHUNT_BeaconKey(KEY_Code_t key)
|
||||
{
|
||||
switch (key) {
|
||||
case KEY_1: FOXHUNT_TxCycle(); return true;
|
||||
case KEY_2: FOXHUNT_IdleCycle(); return true;
|
||||
case KEY_3: FOXHUNT_FoxCycle(); return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,32 +610,26 @@ static void FOXHUNT_HandleKeys(void)
|
||||
case KEY_EXIT:
|
||||
foxRunning = false;
|
||||
break;
|
||||
case KEY_UP:
|
||||
case KEY_DOWN:
|
||||
// Arrows follow SET_NAV; the 4/0 alternates below stay fixed.
|
||||
FOXHUNT_AttStep(FOXHUNT_NavDir(kbd.current));
|
||||
break;
|
||||
case KEY_4:
|
||||
FOXHUNT_AttStep(1);
|
||||
break;
|
||||
case KEY_0:
|
||||
FOXHUNT_AttStep(-1);
|
||||
break;
|
||||
case KEY_MENU:
|
||||
// Reset the peak hold and the trend reference.
|
||||
peakDbm = curDbm;
|
||||
trendRef = curDbm;
|
||||
break;
|
||||
case KEY_1:
|
||||
// Toggle the main gauge: S-meter staircase <-> full-width history.
|
||||
foxGraphMode ^= 1;
|
||||
break;
|
||||
case KEY_2:
|
||||
// Cycle the audio: silent -> Geiger beep -> station audio -> silent.
|
||||
foxAudioMode = (uint8_t)((foxAudioMode + 1) % 3);
|
||||
FOXHUNT_SetAudio();
|
||||
if (foxAudioMode == FOXHUNT_AUDIO_BEEP)
|
||||
audioTick = FOXHUNT_RATE_SLOW_TICKS; // blip promptly
|
||||
break;
|
||||
case KEY_2:
|
||||
// Toggle the main gauge: S-meter staircase <-> full-width history.
|
||||
foxGraphMode ^= 1;
|
||||
case KEY_3:
|
||||
// Cycle the attenuator (0 -> -6 -> -15 -> -27 dB, then back to 0).
|
||||
FOXHUNT_AttCycle();
|
||||
break;
|
||||
case KEY_MENU:
|
||||
// Reset the peak / min hold and the trend reference (before each body scan).
|
||||
peakDbm = curDbm;
|
||||
minDbm = curDbm;
|
||||
trendRef = curDbm;
|
||||
break;
|
||||
case KEY_SIDE1:
|
||||
case KEY_SIDE2:
|
||||
@@ -585,6 +655,7 @@ static void FOXHUNT_EnterHunt(void)
|
||||
|
||||
curDbm = FOXHUNT_ReadDbm();
|
||||
peakDbm = curDbm;
|
||||
minDbm = curDbm;
|
||||
trendRef = curDbm;
|
||||
trendDelta = 0;
|
||||
trendTick = 0;
|
||||
@@ -636,8 +707,7 @@ static void FOXHUNT_TxDeniedNotice(VfoState_t state)
|
||||
// on line 1 so it sits exactly where the beacon's "TX" / "IDLE" text appears.
|
||||
UI_PrintString(VfoStateStr[state], 0, 127, 1, 8);
|
||||
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
FOXHUNT_BlitScreen();
|
||||
|
||||
// Hold the notice ~1 s while keeping the backlight fade alive.
|
||||
for (uint8_t i = 0; i < 20; i++)
|
||||
@@ -654,16 +724,42 @@ static void FOXHUNT_EnterBeacon(void)
|
||||
beaconPhaseTx = true;
|
||||
}
|
||||
|
||||
// Sleep in short slices while watching the keypad, so a burst stays interactive:
|
||||
// EXIT cuts it mid-air (returns true), and UP/DOWN retune the idle gap live.
|
||||
// Paint the TX line (line 0): "TX" left, remaining-window seconds right. Kept separate
|
||||
// so FOXHUNT_TxDelay can refresh just this line each second, live, without redrawing the
|
||||
// whole screen mid-Morse. Records the value shown so the caller only repaints on change.
|
||||
static void FOXHUNT_DrawTxSeconds(void)
|
||||
{
|
||||
const uint8_t sec = (uint8_t)((beaconTxMsLeft + 999u) / 1000u);
|
||||
memset(gFrameBuffer[0], 0, sizeof(gFrameBuffer[0]));
|
||||
UI_PrintStringSmallNormal("TX", 1, 0, 0);
|
||||
sprintf(str, "%02us", sec);
|
||||
UI_PrintStringSmallNormal(str, (uint8_t)(127 - strlen(str) * 7), 0, 0);
|
||||
beaconTxSecShown = sec;
|
||||
}
|
||||
|
||||
// Sleep in short slices while watching the keypad, so a burst stays interactive and the
|
||||
// TX-window countdown (beaconTxMsLeft) drains in real time. EXIT leaves Fox Hunt and a
|
||||
// side key aborts back to the hunt (both return true). The 1/2/3 keys edit the beacon
|
||||
// settings live, exactly as in the idle phase: the TX window (1) and idle gap (2) take
|
||||
// effect at the upcoming window / gap, and a FOX change (3) at the next repeat (the
|
||||
// message is rebuilt there, never under the running loop).
|
||||
static bool FOXHUNT_TxDelay(uint16_t ms)
|
||||
{
|
||||
while (ms) {
|
||||
const uint16_t slice = (ms > 10) ? 10 : ms; // 10 ms: same fade rate as idle
|
||||
SYSTEM_DelayMs(slice);
|
||||
ms -= slice;
|
||||
beaconTxMsLeft = (beaconTxMsLeft > slice) ? (beaconTxMsLeft - slice) : 0;
|
||||
BACKLIGHT_Update(); // keep any brightness fade moving in TX
|
||||
|
||||
// Tick the remaining-window seconds live: repaint only line 0 when the whole
|
||||
// second changes, so the countdown drains smoothly through a long ID instead of
|
||||
// jumping once per repeat (a one-line blit barely dents the Morse timing).
|
||||
if ((uint8_t)((beaconTxMsLeft + 999u) / 1000u) != beaconTxSecShown) {
|
||||
FOXHUNT_DrawTxSeconds();
|
||||
ST7565_BlitLine(0);
|
||||
}
|
||||
|
||||
kbd.prev = kbd.current;
|
||||
kbd.current = KEYBOARD_GetKey();
|
||||
if (kbd.current == KEY_INVALID || kbd.current == kbd.prev)
|
||||
@@ -679,11 +775,14 @@ static bool FOXHUNT_TxDelay(uint16_t ms)
|
||||
foxBeacon = false; // abort the burst and switch back to hunt
|
||||
return true;
|
||||
}
|
||||
if (kbd.current == KEY_UP || kbd.current == KEY_DOWN) {
|
||||
// The gap is only read once the burst ends, so it is safe to change
|
||||
// now; refresh the "IDLE nn" label so the change is visible at once.
|
||||
FOXHUNT_IdleStep(FOXHUNT_NavDir(kbd.current));
|
||||
FOXHUNT_BeaconDraw(true, 0);
|
||||
if (kbd.current == KEY_MENU) {
|
||||
// Cut the transmission short but stay in Beacon: aborting the burst without
|
||||
// clearing foxBeacon/foxRunning drops the loop straight into a fresh idle
|
||||
// gap of the configured IDLE length.
|
||||
return true;
|
||||
}
|
||||
if (FOXHUNT_BeaconKey(kbd.current)) {
|
||||
FOXHUNT_BeaconDraw(true, 0); // show the new value immediately
|
||||
ST7565_BlitFullScreen();
|
||||
}
|
||||
}
|
||||
@@ -733,8 +832,10 @@ static bool FOXHUNT_MorseChar(char c, uint8_t visibleChars)
|
||||
return FOXHUNT_TxDelay(FOXHUNT_MORSE_UNIT_MS * 2);
|
||||
}
|
||||
|
||||
// One beacon burst: transmit the CW identification message once, at the TX VFO's
|
||||
// configured power. Carrier stays up; the tone is keyed per Morse element (MCW).
|
||||
// One beacon cycle: repeat the CW identification for the whole TX window, at the TX
|
||||
// VFO's configured power. Carrier stays up across the window; the tone is keyed per
|
||||
// Morse element (MCW), and the ID is re-sent until the window drains — a real ARDF fox
|
||||
// keys up for a fixed slot rather than sending a single one-shot.
|
||||
static void FOXHUNT_BeaconTransmit(void)
|
||||
{
|
||||
RADIO_SetTxParameters(); // key up: carrier + PA
|
||||
@@ -743,9 +844,28 @@ static void FOXHUNT_BeaconTransmit(void)
|
||||
BK4819_TransmitTone(false, FOXHUNT_BEACON_TONE_HZ);
|
||||
BK4819_EnterTxMute();
|
||||
|
||||
beaconTxMsLeft = (uint16_t)beaconTx * 1000u; // drained inside FOXHUNT_TxDelay
|
||||
|
||||
bool stop = false;
|
||||
for (uint8_t i = 0; !stop && beaconMsg[i]; i++)
|
||||
stop = FOXHUNT_MorseChar(beaconMsg[i], i + 1u);
|
||||
while (!stop && beaconTxMsLeft > 0) {
|
||||
// Rebuild from the current fox at the repeat boundary — this is where a FOX
|
||||
// change made live (during the previous repeat or the idle gap) takes effect,
|
||||
// never mid-message.
|
||||
FOXHUNT_BuildBeaconMsg();
|
||||
|
||||
// Redraw once per repeat (remaining-window countdown + reset progress), in the
|
||||
// muted gap so it never stretches a Morse element.
|
||||
beaconCharsSent = 0;
|
||||
FOXHUNT_BeaconDraw(true, 0);
|
||||
FOXHUNT_BlitScreen();
|
||||
|
||||
for (uint8_t i = 0; !stop && beaconMsg[i]; i++)
|
||||
stop = FOXHUNT_MorseChar(beaconMsg[i], i + 1u);
|
||||
|
||||
// Word gap before the next repeat, only while the window still has room.
|
||||
if (!stop && beaconTxMsLeft > 0)
|
||||
stop = FOXHUNT_TxDelay(FOXHUNT_MORSE_UNIT_MS * 7);
|
||||
}
|
||||
|
||||
// Key down: mute, drop the PA and restore RX for the silent gap. If the burst
|
||||
// was interrupted, FOXHUNT_TxDelay already set the target mode (quit or hunt).
|
||||
@@ -754,14 +874,49 @@ static void FOXHUNT_BeaconTransmit(void)
|
||||
RADIO_SetupRegisters(true);
|
||||
}
|
||||
|
||||
// Draw the transmitted portion of "<call> BEACON" on two normal-font lines at
|
||||
// their final positions: the boot message first, then BEACON below it.
|
||||
// Build the CW message from the current fox id: "<call> MOE" for CALL (falls back to a
|
||||
// bare MOE when no callsign is set), otherwise the pure "MOx" of the chosen fox.
|
||||
static void FOXHUNT_BuildBeaconMsg(void)
|
||||
{
|
||||
if (foxFox == FOXHUNT_FOX_CALL) {
|
||||
if (foxCall[0] != '\0')
|
||||
sprintf(beaconMsg, "%s MOE", foxCall);
|
||||
else
|
||||
strcpy(beaconMsg, "MOE");
|
||||
} else if (foxFox == FOXHUNT_FOX_MO) {
|
||||
strcpy(beaconMsg, "MO"); // ARDF finish/home beacon
|
||||
} else {
|
||||
sprintf(beaconMsg, "MO%c", FOXHUNT_FOX_TAIL[foxFox]);
|
||||
}
|
||||
}
|
||||
|
||||
// Short label for the FOX setting: "FOX CALL" or "FOX MOx".
|
||||
static void FOXHUNT_FoxLabel(char *out)
|
||||
{
|
||||
if (foxFox == FOXHUNT_FOX_CALL)
|
||||
strcpy(out, "FOX CALL");
|
||||
else if (foxFox == FOXHUNT_FOX_MO)
|
||||
strcpy(out, "FOX MO");
|
||||
else
|
||||
sprintf(out, "FOX MO%c", FOXHUNT_FOX_TAIL[foxFox]);
|
||||
}
|
||||
|
||||
// Draw the transmitted portion of the message. A CALL id splits on the space: callsign
|
||||
// on the upper line, "MOx" below. A pure fox (no space) is centred on one line. Each
|
||||
// revealed prefix keeps the anchor of the full string, so characters appear in place
|
||||
// instead of re-centring as they arrive.
|
||||
static void FOXHUNT_DrawBeaconMessage(uint8_t visibleChars)
|
||||
{
|
||||
static const char suffix[] = "BEACON";
|
||||
const char *separator = strchr(beaconMsg, ' ');
|
||||
|
||||
if (separator == NULL) {
|
||||
if (separator == NULL) { // pure fox id (MOE..MO5)
|
||||
const size_t idLen = strlen(beaconMsg);
|
||||
const size_t visLen = MIN((size_t)visibleChars, idLen);
|
||||
char id[sizeof(beaconMsg)];
|
||||
memcpy(id, beaconMsg, visLen);
|
||||
id[visLen] = '\0';
|
||||
UI_PrintString(id, (uint8_t)((LCD_WIDTH - idLen * 8u) / 2u), 0,
|
||||
FOXHUNT_MSG_LINE_ONE, 8);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -770,65 +925,74 @@ static void FOXHUNT_DrawBeaconMessage(uint8_t visibleChars)
|
||||
char call[FOXHUNT_CALLSIGN_MAX + 1];
|
||||
memcpy(call, beaconMsg, visibleCallLen);
|
||||
call[visibleCallLen] = '\0';
|
||||
|
||||
const uint8_t callX = (uint8_t)((LCD_WIDTH - callLen * 8u) / 2u);
|
||||
UI_PrintString(call, callX, 0, 2, 8);
|
||||
UI_PrintString(call, (uint8_t)((LCD_WIDTH - callLen * 8u) / 2u), 0,
|
||||
FOXHUNT_MSG_LINE_CALL, 8);
|
||||
|
||||
if ((size_t)visibleChars > callLen + 1u) {
|
||||
const size_t visibleSuffixLen = MIN((size_t)visibleChars - callLen - 1u,
|
||||
sizeof(suffix) - 1u);
|
||||
char visibleSuffix[sizeof(suffix)];
|
||||
memcpy(visibleSuffix, suffix, visibleSuffixLen);
|
||||
visibleSuffix[visibleSuffixLen] = '\0';
|
||||
UI_PrintString(visibleSuffix,
|
||||
(uint8_t)((LCD_WIDTH - (sizeof(suffix) - 1u) * 8u) / 2u),
|
||||
0, 4, 8);
|
||||
const char *id = separator + 1;
|
||||
const size_t idLen = strlen(id);
|
||||
const size_t visibleIdLen = MIN((size_t)visibleChars - callLen - 1u, idLen);
|
||||
char idbuf[8];
|
||||
memcpy(idbuf, id, visibleIdLen);
|
||||
idbuf[visibleIdLen] = '\0';
|
||||
UI_PrintString(idbuf, (uint8_t)((LCD_WIDTH - idLen * 8u) / 2u), 0,
|
||||
FOXHUNT_MSG_LINE_ID, 8);
|
||||
}
|
||||
}
|
||||
|
||||
// Update only the line pair whose latest character changed so screen refreshes
|
||||
// do not lengthen the Morse timing more than necessary.
|
||||
// Update only the line pair whose latest character changed so screen refreshes do not
|
||||
// lengthen the Morse timing more than necessary.
|
||||
static void FOXHUNT_UpdateBeaconProgress(uint8_t visibleChars)
|
||||
{
|
||||
const char *separator = strchr(beaconMsg, ' ');
|
||||
const size_t callLen = separator != NULL ? (size_t)(separator - beaconMsg) : 0u;
|
||||
uint8_t topLine;
|
||||
|
||||
beaconCharsSent = visibleChars;
|
||||
|
||||
if ((size_t)visibleChars <= callLen) {
|
||||
memset(gFrameBuffer[2], 0, sizeof(gFrameBuffer[2]));
|
||||
memset(gFrameBuffer[3], 0, sizeof(gFrameBuffer[3]));
|
||||
FOXHUNT_DrawBeaconMessage(beaconCharsSent);
|
||||
ST7565_BlitLine(2);
|
||||
ST7565_BlitLine(3);
|
||||
} else if ((size_t)visibleChars > callLen + 1u) {
|
||||
memset(gFrameBuffer[4], 0, sizeof(gFrameBuffer[4]));
|
||||
memset(gFrameBuffer[5], 0, sizeof(gFrameBuffer[5]));
|
||||
FOXHUNT_DrawBeaconMessage(beaconCharsSent);
|
||||
ST7565_BlitLine(4);
|
||||
ST7565_BlitLine(5);
|
||||
if (separator == NULL) { // pure fox id
|
||||
topLine = FOXHUNT_MSG_LINE_ONE;
|
||||
} else {
|
||||
const size_t callLen = (size_t)(separator - beaconMsg);
|
||||
if ((size_t)visibleChars == callLen + 1u) // the space: nothing new to show
|
||||
return;
|
||||
topLine = ((size_t)visibleChars <= callLen)
|
||||
? FOXHUNT_MSG_LINE_CALL
|
||||
: FOXHUNT_MSG_LINE_ID;
|
||||
}
|
||||
|
||||
memset(gFrameBuffer[topLine], 0, sizeof(gFrameBuffer[topLine]));
|
||||
memset(gFrameBuffer[topLine + 1], 0, sizeof(gFrameBuffer[topLine + 1]));
|
||||
FOXHUNT_DrawBeaconMessage(beaconCharsSent);
|
||||
ST7565_BlitLine(topLine);
|
||||
ST7565_BlitLine(topLine + 1);
|
||||
}
|
||||
|
||||
static void FOXHUNT_BeaconDraw(bool txNow, uint8_t idleLeft)
|
||||
{
|
||||
// Frame (clear + BEACON tag + battery + TX frequency).
|
||||
// Frame (clear + BEACON tag + battery + TX frequency on the bottom-right).
|
||||
FOXHUNT_BeaconChrome();
|
||||
|
||||
// The TX layout starts one line higher so the bottom information remains
|
||||
// visible below the two progressively completed message lines.
|
||||
if (txNow) {
|
||||
UI_PrintString("TX", 0, 127, 0, 10);
|
||||
// Top line: "TX" + remaining window seconds (refreshed live in FOXHUNT_TxDelay).
|
||||
FOXHUNT_DrawTxSeconds();
|
||||
FOXHUNT_DrawBeaconMessage(beaconCharsSent);
|
||||
} else {
|
||||
// Silence phase: big "IDLE" with the countdown to the next burst below it.
|
||||
UI_PrintString("IDLE", 0, 127, 1, 10);
|
||||
sprintf(str, "%02us", idleLeft);
|
||||
UI_PrintString(str, 0, 127, 3, 10);
|
||||
}
|
||||
|
||||
// Bottom-left: the idle-duration setting as an inverse label (ATT style).
|
||||
sprintf(str, "IDLE %02u", beaconIdle);
|
||||
GUI_DisplaySmallestInverse(str, 4, 6, false, true, 4 + strlen(str) * 4);
|
||||
// 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.
|
||||
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);
|
||||
}
|
||||
|
||||
static void FOXHUNT_BeaconKeys(void)
|
||||
@@ -849,12 +1013,14 @@ static void FOXHUNT_BeaconKeys(void)
|
||||
case KEY_SIDE2:
|
||||
foxBeacon = false; // toggle back to the hunt
|
||||
break;
|
||||
case KEY_UP:
|
||||
case KEY_DOWN:
|
||||
// Idle duration, arrows following the SET_NAV orientation.
|
||||
FOXHUNT_IdleStep(FOXHUNT_NavDir(kbd.current));
|
||||
case KEY_MENU:
|
||||
// Restart a full idle interval now (the TX-phase M drops here too).
|
||||
beaconIdleLeft = beaconIdle;
|
||||
beaconIdleTick = 0;
|
||||
break;
|
||||
default:
|
||||
// 1 = TX window, 2 = idle gap, 3 = fox (same keys as during TX).
|
||||
FOXHUNT_BeaconKey(kbd.current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -878,14 +1044,10 @@ static void FOXHUNT_BeaconTick(void)
|
||||
return;
|
||||
}
|
||||
|
||||
beaconCharsSent = 0;
|
||||
FOXHUNT_BeaconDraw(true, 0);
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
K5VIEWER_Update(false);
|
||||
#endif
|
||||
FOXHUNT_BeaconTransmit(); // may clear foxBeacon (abort)
|
||||
// The burst repeats the ID for the whole TX window, redrawing the screen
|
||||
// itself each repeat (remaining-window countdown); it may clear foxBeacon
|
||||
// (side-key abort) or foxRunning (EXIT).
|
||||
FOXHUNT_BeaconTransmit();
|
||||
|
||||
beaconPhaseTx = false;
|
||||
beaconIdleLeft = beaconIdle;
|
||||
@@ -910,11 +1072,7 @@ static void FOXHUNT_BeaconTick(void)
|
||||
}
|
||||
|
||||
FOXHUNT_BeaconDraw(false, beaconIdleLeft);
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
K5VIEWER_Update(false);
|
||||
#endif
|
||||
FOXHUNT_BlitScreen();
|
||||
|
||||
if (++beaconIdleTick >= (1000 / FOXHUNT_TICK_MS)) { // one second elapsed
|
||||
beaconIdleTick = 0;
|
||||
@@ -933,29 +1091,32 @@ static void FOXHUNT_BeaconTick(void)
|
||||
// span 0x9000..0x90E0 (224 B); the old 0x90D6 mapping bound was 10 B short and
|
||||
// overlapped the last VFO (470 MHz VFO1 @ 0x90D0..0x90DF), so the config sits at
|
||||
// 0x90E0, just past the real end. Normal saves are read-modify-write (Append=false)
|
||||
// so they preserve these bytes, and the VFO mapping is extended to 0x90E5
|
||||
// so they preserve these bytes, and the VFO mapping is extended to 0x90E7
|
||||
// (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
|
||||
|
||||
// RAM mirror of the bytes last written to flash, so FOXHUNT_SaveConfig only touches
|
||||
// the flash when a value actually changed.
|
||||
static uint8_t foxCfgSaved[5];
|
||||
static uint8_t foxCfgSaved[FOXHUNT_CFG_LEN];
|
||||
|
||||
// Pack the live settings into the 5-byte on-flash layout.
|
||||
static void FOXHUNT_ConfigPack(uint8_t out[5])
|
||||
// Pack the live settings into the on-flash layout.
|
||||
static void FOXHUNT_ConfigPack(uint8_t out[FOXHUNT_CFG_LEN])
|
||||
{
|
||||
out[0] = FOXHUNT_CFG_MAGIC;
|
||||
out[1] = attStep;
|
||||
out[2] = foxGraphMode;
|
||||
out[3] = foxAudioMode;
|
||||
out[4] = beaconIdle;
|
||||
out[5] = foxFox;
|
||||
out[6] = beaconTx;
|
||||
}
|
||||
|
||||
// Restore persisted settings; erased/invalid flash leaves the defaults in place.
|
||||
static void FOXHUNT_LoadConfig(void)
|
||||
{
|
||||
uint8_t cfg[5];
|
||||
uint8_t cfg[FOXHUNT_CFG_LEN];
|
||||
PY25Q16_ReadBuffer(FOXHUNT_CFG_ADDR, cfg, sizeof(cfg));
|
||||
|
||||
if (cfg[0] == FOXHUNT_CFG_MAGIC) { // valid, saved config
|
||||
@@ -965,6 +1126,10 @@ static void FOXHUNT_LoadConfig(void)
|
||||
if (cfg[4] >= FOXHUNT_BEACON_IDLE_MIN && cfg[4] <= FOXHUNT_BEACON_IDLE_MAX
|
||||
&& (cfg[4] % FOXHUNT_BEACON_IDLE_STEP) == 0)
|
||||
beaconIdle = cfg[4];
|
||||
if (cfg[5] < FOXHUNT_FOX_COUNT) foxFox = cfg[5];
|
||||
if (cfg[6] >= FOXHUNT_BEACON_TX_MIN && cfg[6] <= FOXHUNT_BEACON_TX_MAX
|
||||
&& (cfg[6] % FOXHUNT_BEACON_TX_STEP) == 0)
|
||||
beaconTx = cfg[6];
|
||||
}
|
||||
|
||||
FOXHUNT_ConfigPack(foxCfgSaved); // mirror the loaded (or default) state
|
||||
@@ -976,7 +1141,7 @@ static void FOXHUNT_LoadConfig(void)
|
||||
// compare avoids re-reading the 4 KB sector on every quiet tick.
|
||||
static void FOXHUNT_SaveConfig(void)
|
||||
{
|
||||
uint8_t cfg[5];
|
||||
uint8_t cfg[FOXHUNT_CFG_LEN];
|
||||
FOXHUNT_ConfigPack(cfg);
|
||||
|
||||
if (memcmp(cfg, foxCfgSaved, sizeof(cfg)) == 0)
|
||||
@@ -1000,12 +1165,14 @@ void APP_RunFoxHunt(void)
|
||||
gCurrentVfo = gTxVfo;
|
||||
RADIO_SetupRegisters(true);
|
||||
|
||||
// Beacon defaults.
|
||||
// Beacon defaults (LoadConfig may override fox / tx / idle below).
|
||||
beaconIdle = FOXHUNT_BEACON_IDLE_DEF;
|
||||
beaconTx = FOXHUNT_BEACON_TX_DEF;
|
||||
foxFox = FOXHUNT_FOX_CALL; // identified beacon by default (legal on ham)
|
||||
beaconPhaseTx = false;
|
||||
|
||||
// Build the CW message "<call> BEACON": the callsign is the boot message
|
||||
// (line 1), sanitised to what Morse can send; empty/erased falls back to FOX.
|
||||
// Read the callsign from the boot message (line 1), sanitised to what Morse can
|
||||
// send; empty/erased leaves foxCall empty (the CALL id then falls back to bare MOE).
|
||||
{
|
||||
char call[FOXHUNT_CALLSIGN_MAX + 1];
|
||||
uint8_t n = 0;
|
||||
@@ -1018,21 +1185,19 @@ void APP_RunFoxHunt(void)
|
||||
if (c >= 'a' && c <= 'z')
|
||||
c -= 32;
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '/')
|
||||
call[n++] = c;
|
||||
foxCall[n++] = c;
|
||||
}
|
||||
call[n] = '\0';
|
||||
if (n == 0)
|
||||
strcpy(call, "FOX");
|
||||
sprintf(beaconMsg, "%s BEACON", call);
|
||||
foxCall[n] = '\0';
|
||||
}
|
||||
|
||||
// Start in the hunt (RX) sub-mode, S-meter staircase gauge by default, then
|
||||
// restore any persisted settings (attenuator, gauge, audio, beacon idle).
|
||||
// Start in the hunt (RX) sub-mode, S-meter staircase gauge by default, then restore
|
||||
// any persisted settings (attenuator, gauge, audio, beacon idle / fox / tx).
|
||||
attStep = 0;
|
||||
foxGraphMode = FOXHUNT_GRAPH_BAR;
|
||||
foxAudioMode = FOXHUNT_AUDIO_OFF;
|
||||
FOXHUNT_LoadConfig(); // may override att / gauge / audio / idle
|
||||
FOXHUNT_EnterHunt(); // applies the restored attStep and audio mode
|
||||
FOXHUNT_LoadConfig(); // may override att / gauge / audio / idle / fox / tx
|
||||
FOXHUNT_BuildBeaconMsg(); // assemble the CW message from the (restored) fox id
|
||||
FOXHUNT_EnterHunt(); // applies the restored attStep and audio mode
|
||||
|
||||
// Prime the key state with whatever is held right now, so the side key that
|
||||
// launched Fox Hunt (still down on a long-press assignment) is not taken for
|
||||
@@ -1059,6 +1224,8 @@ void APP_RunFoxHunt(void)
|
||||
curDbm = FOXHUNT_ReadDbm();
|
||||
if (curDbm > peakDbm)
|
||||
peakDbm = curDbm;
|
||||
if (curDbm < minDbm)
|
||||
minDbm = curDbm;
|
||||
|
||||
if (++trendTick >= FOXHUNT_TREND_TICKS) {
|
||||
trendDelta = curDbm - trendRef;
|
||||
@@ -1070,13 +1237,7 @@ void APP_RunFoxHunt(void)
|
||||
|
||||
FOXHUNT_Draw();
|
||||
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
// Mirror the screen to K5Viewer (sends only changed chunks).
|
||||
K5VIEWER_Update(false);
|
||||
#endif
|
||||
FOXHUNT_BlitScreen(); // status + full frame + K5Viewer mirror
|
||||
|
||||
// Beep mode: Geiger blip whose rate/pitch track the level (silent below floor).
|
||||
if (foxAudioMode == FOXHUNT_AUDIO_BEEP && curDbm >= FOXHUNT_SILENCE_DBM) {
|
||||
|
||||
@@ -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, 0x0090E5), // 14 VFO * 16 Bytes = 0x9000 -> 0x90E0 (the old
|
||||
_MK_MAPPING(0x009000, 0x009000, 0x0090E7), // 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 settings * 5 Bytes 0x90E0 -> 0x90E5
|
||||
// Fox Hunt settings * 7 Bytes 0x90E0 -> 0x90E7
|
||||
// (ENABLE_FEAT_F4HWN_FOXHUNT, written directly by
|
||||
// app/foxhunt.c; concatenated here so aircopy clones
|
||||
// them together with the VFOs)
|
||||
|
||||
Reference in new issue
Block a user