mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 03:15:37 +00:00
Add Fox Hunt mode
This commit is contained in:
1 parent
c0e3909853
commit
73f44911ed
9 files changed
+559
No files matched your search
@@ -211,6 +211,9 @@ enable_feature(ENABLE_FEAT_F4HWN_BEAM
|
||||
enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
app/rxtx_log.c
|
||||
)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
app/foxhunt.c
|
||||
)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER)
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
#include "app/rxtx_log.h"
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
#include "app/foxhunt.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_FMRADIO)
|
||||
static void ACTION_Scan_FM(bool bRestart);
|
||||
@@ -137,6 +140,9 @@ void (*const action_opt_table[])(void) = {
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
[ACTION_OPT_RXTX_LOG] = &ACTION_RxTxLog,
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
[ACTION_OPT_FOXHUNT] = &ACTION_FoxHunt,
|
||||
#endif
|
||||
};
|
||||
|
||||
static_assert(ARRAY_SIZE(action_opt_table) == ACTION_OPT_LEN);
|
||||
@@ -382,6 +388,9 @@ void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||
case ACTION_OPT_BEAM:
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
case ACTION_OPT_FOXHUNT:
|
||||
#endif
|
||||
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
/* Copyright 2026 Armel F4HWN
|
||||
* https://github.com/armel
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "app/foxhunt.h"
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
#include "k5viewer.h"
|
||||
#endif
|
||||
|
||||
// Signal window mapped onto the RSSI bar, in dBm.
|
||||
// Roughly S0 (empty) to S9 + 40 dB (full), IARU VHF/UHF scale.
|
||||
#define FOXHUNT_DBM_FLOOR (-141)
|
||||
#define FOXHUNT_DBM_CEIL (-53)
|
||||
|
||||
// Gauge geometry inside the main frame buffer (y in 0..55, status line excluded).
|
||||
// 13 segments on the IARU S-meter scale: S1..S9 are 6 dB apart, the four marks
|
||||
// past S9 (+10..+40) are 10 dB apart, so the axis compresses at the top.
|
||||
#define FOXHUNT_BAR_X0 6 // leftmost segment x (centres the 13 segments)
|
||||
#define FOXHUNT_SEG_COUNT 13 // S1..S9 + (+10..+40)
|
||||
#define FOXHUNT_SEG_PITCH 9 // px between segment starts
|
||||
#define FOXHUNT_SEG_W 8 // segment width
|
||||
#define FOXHUNT_SEG_BOTTOM 37 // base the staircase grows up from
|
||||
|
||||
// Refresh period and trend sampling window.
|
||||
#define FOXHUNT_TICK_MS 50
|
||||
#define FOXHUNT_TREND_TICKS 20 // compare the level roughly once per second
|
||||
|
||||
// Geiger audio: the blip RATE tracks the signal level (faster = closer), the
|
||||
// pitch rises too as a secondary cue. Below the silence floor, no blips at all.
|
||||
#define FOXHUNT_AUDIO_SETTLE_MS 60 // audio amplifier warm-up after enabling the speaker
|
||||
#define FOXHUNT_BLIP_MS 50 // tone duration per blip
|
||||
#define FOXHUNT_RATE_SLOW_TICKS 20 // ticks between blips at the floor (~1/s)
|
||||
#define FOXHUNT_RATE_FAST_TICKS 2 // ticks between blips at the ceiling (fast)
|
||||
#define FOXHUNT_SILENCE_DBM (-120) // stay silent below this level (noise floor)
|
||||
#define FOXHUNT_TONE_MIN 400 // Hz at the floor of the window
|
||||
#define FOXHUNT_TONE_MAX 2400 // Hz at the ceiling of the window
|
||||
|
||||
// Audio mode cycled by the 1 key: silent -> Geiger beep -> received station.
|
||||
#define FOXHUNT_AUDIO_OFF 0
|
||||
#define FOXHUNT_AUDIO_BEEP 1
|
||||
#define FOXHUNT_AUDIO_STATION 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).
|
||||
// pgaTab (bk4829.c) = {-33,-27,-21,-15,-9,-6,-3,0}; index 7 (= 0 dB) matches the
|
||||
// RX default (0x03DF), so step 0 leaves the gain untouched.
|
||||
static const uint8_t FOXHUNT_ATT_PGA[4] = {7, 5, 3, 1}; // 0, -6, -15, -27 dB
|
||||
static const uint8_t FOXHUNT_ATT_DB[4] = {0, 6, 15, 27};
|
||||
|
||||
static KeyboardState kbd = {KEY_INVALID, KEY_INVALID, 0};
|
||||
|
||||
static bool foxRunning;
|
||||
static uint8_t foxAudioMode;
|
||||
static uint8_t attStep;
|
||||
static int16_t curDbm;
|
||||
static int16_t peakDbm;
|
||||
static int16_t trendRef;
|
||||
static int16_t trendDelta;
|
||||
static uint8_t trendTick;
|
||||
static uint8_t audioTick;
|
||||
|
||||
static char str[16];
|
||||
|
||||
// Read the calibrated signal level of the current RX VFO, in dBm.
|
||||
static int16_t FOXHUNT_ReadDbm(void)
|
||||
{
|
||||
return BK4819_GetRSSI_dBm() + dBmCorrTable[gRxVfo->Band];
|
||||
}
|
||||
|
||||
// Apply the selected attenuator step to the BK4829 PGA gain field.
|
||||
static void FOXHUNT_ApplyAtt(void)
|
||||
{
|
||||
uint16_t reg = BK4819_ReadRegister(BK4819_REG_13);
|
||||
reg = (reg & ~0x7u) | (uint16_t)FOXHUNT_ATT_PGA[attStep]; // PGA = REG_13 bits 0..2
|
||||
BK4819_WriteRegister(BK4819_REG_13, reg);
|
||||
}
|
||||
|
||||
// Set the speaker/AF state for the current audio mode.
|
||||
static void FOXHUNT_SetAudio(void)
|
||||
{
|
||||
if (foxAudioMode == FOXHUNT_AUDIO_OFF) {
|
||||
AUDIO_AudioPathOff();
|
||||
return;
|
||||
}
|
||||
// Speaker on, let the amplifier warm up, RX audio muted for now. BEEP keeps it
|
||||
// muted (only the blips are heard); STATION un-mutes per level in the loop.
|
||||
AUDIO_AudioPathOn();
|
||||
SYSTEM_DelayMs(FOXHUNT_AUDIO_SETTLE_MS);
|
||||
BK4819_SetAF(BK4819_AF_MUTE);
|
||||
}
|
||||
|
||||
// Map a dBm value to a Geiger tone frequency (higher signal, higher pitch).
|
||||
static uint16_t FOXHUNT_DbmToTone(int16_t dbm)
|
||||
{
|
||||
if (dbm <= FOXHUNT_DBM_FLOOR) return FOXHUNT_TONE_MIN;
|
||||
if (dbm >= FOXHUNT_DBM_CEIL) return FOXHUNT_TONE_MAX;
|
||||
return FOXHUNT_TONE_MIN +
|
||||
(uint16_t)(((int32_t)(dbm - FOXHUNT_DBM_FLOOR) * (FOXHUNT_TONE_MAX - FOXHUNT_TONE_MIN)) /
|
||||
(FOXHUNT_DBM_CEIL - FOXHUNT_DBM_FLOOR));
|
||||
}
|
||||
|
||||
// Ticks between two blips: fewer ticks = faster clicking as the signal rises.
|
||||
static uint8_t FOXHUNT_BlipPeriod(int16_t dbm)
|
||||
{
|
||||
if (dbm <= FOXHUNT_DBM_FLOOR) return FOXHUNT_RATE_SLOW_TICKS;
|
||||
if (dbm >= FOXHUNT_DBM_CEIL) return FOXHUNT_RATE_FAST_TICKS;
|
||||
return FOXHUNT_RATE_SLOW_TICKS -
|
||||
(uint8_t)(((int32_t)(dbm - FOXHUNT_DBM_FLOOR) * (FOXHUNT_RATE_SLOW_TICKS - FOXHUNT_RATE_FAST_TICKS)) /
|
||||
(FOXHUNT_DBM_CEIL - FOXHUNT_DBM_FLOOR));
|
||||
}
|
||||
|
||||
// Emit one short blip at the given pitch, then hand the chip back to RX.
|
||||
// The speaker path is already on and settled (see the key-1 handler); between
|
||||
// blips TurnsOffTones_TurnsOnRX mutes the AF, so the RX stays silent.
|
||||
static void FOXHUNT_Blip(uint16_t freq)
|
||||
{
|
||||
BK4819_PrepareToPlayTone(true);
|
||||
BK4819_PlayToneRaw(freq, FOXHUNT_BLIP_MS);
|
||||
BK4819_TurnsOffTones_TurnsOnRX();
|
||||
|
||||
// TurnsOffTones_TurnsOnRX restores a normal RX front-end, so re-assert the
|
||||
// fixed gain and attenuator the level reading relies on.
|
||||
BK4819_SetAGC(false);
|
||||
FOXHUNT_ApplyAtt();
|
||||
}
|
||||
|
||||
// Number of lit segments for a level, on the IARU S-meter scale:
|
||||
// S1..S9 are 6 dB apart, the four marks past S9 (+10..+40) are 10 dB apart.
|
||||
static uint8_t FOXHUNT_FillCount(int16_t dbm)
|
||||
{
|
||||
int16_t n;
|
||||
if (dbm < -141) return 0;
|
||||
if (dbm <= -93)
|
||||
n = 1 + (dbm + 141) / 6; // S1 (-141) .. S9 (-93) -> 1..9
|
||||
else
|
||||
n = 9 + (dbm + 93) / 10; // +10 (-83) .. +40 (-53) -> 10..13
|
||||
if (n > FOXHUNT_SEG_COUNT)
|
||||
n = FOXHUNT_SEG_COUNT;
|
||||
return (uint8_t)n;
|
||||
}
|
||||
|
||||
// Build the IARU S-meter reading string (e.g. "S7" or "S9+30").
|
||||
static void FOXHUNT_BuildS(char *out, int16_t dbm)
|
||||
{
|
||||
if (dbm >= -93) {
|
||||
int16_t over = dbm - (-93);
|
||||
if (over > 40) over = 40;
|
||||
sprintf(out, "S9+%02d", over);
|
||||
} else if (dbm < -141) {
|
||||
sprintf(out, "S0");
|
||||
} else {
|
||||
sprintf(out, "S%d", (dbm + 147) / 6);
|
||||
}
|
||||
}
|
||||
|
||||
// Fill a solid rectangle, column by column.
|
||||
static void FOXHUNT_FillRect(int16_t x0, int16_t y0, int16_t x1, int16_t y1, bool black)
|
||||
{
|
||||
for (int16_t x = x0; x <= x1; x++)
|
||||
UI_DrawLineBuffer(gFrameBuffer, x, y0, x, y1, black);
|
||||
}
|
||||
|
||||
static void FOXHUNT_DrawBar(void)
|
||||
{
|
||||
uint8_t nCur = FOXHUNT_FillCount(curDbm);
|
||||
uint8_t i;
|
||||
|
||||
// Continuous S-meter staircase (no frame): each segment is 1 px taller than
|
||||
// the previous, from S1 (1 px) to S9+40 (13 px), growing up from the base.
|
||||
for (i = 0; i < nCur; i++) {
|
||||
int16_t sx = FOXHUNT_BAR_X0 + i * FOXHUNT_SEG_PITCH;
|
||||
FOXHUNT_FillRect(sx, FOXHUNT_SEG_BOTTOM - i, sx + FOXHUNT_SEG_W - 1, FOXHUNT_SEG_BOTTOM, true);
|
||||
}
|
||||
|
||||
// Solid separator line between the staircase and the scale labels below,
|
||||
// with a 1 px gap above (y38) and below (y40) it.
|
||||
UI_DrawLineBuffer(gFrameBuffer, FOXHUNT_BAR_X0, FOXHUNT_SEG_BOTTOM + 2,
|
||||
FOXHUNT_BAR_X0 + (FOXHUNT_SEG_COUNT - 1) * FOXHUNT_SEG_PITCH + FOXHUNT_SEG_W - 1,
|
||||
FOXHUNT_SEG_BOTTOM + 2, true);
|
||||
}
|
||||
|
||||
static void FOXHUNT_Draw(void)
|
||||
{
|
||||
const uint8_t *trendIcon;
|
||||
char sMeter[8];
|
||||
char big[8];
|
||||
|
||||
UI_DisplayClear();
|
||||
UI_StatusClear();
|
||||
|
||||
// Title as an inverse label, in the scan-list tag style.
|
||||
GUI_DisplaySmallestInverse("FOX HUNT", 2, 0, true, true, 34);
|
||||
|
||||
// Battery (icon + optional percentage/voltage) top-right, as on the main screens.
|
||||
unsigned int bx = LCD_WIDTH - sizeof(BITMAP_BatteryLevel1);
|
||||
UI_DrawBattery(gStatusLine + bx, gBatteryDisplayLevel, gLowBatteryBlink);
|
||||
if (gSetting_battery_text != 0) {
|
||||
if (gSetting_battery_text == 1) { // voltage
|
||||
const uint16_t v = MIN(gBatteryVoltageAverage, 999);
|
||||
sprintf(str, "%u.%02u", v / 100, v % 100);
|
||||
} else { // percentage
|
||||
sprintf(str, "%02u%%", BATTERY_VoltsToPercent(gBatteryVoltageAverage));
|
||||
}
|
||||
bx -= 7 * strlen(str);
|
||||
UI_PrintStringSmallBufferNormal(str, gStatusLine + bx);
|
||||
}
|
||||
|
||||
// Audio state icon between the label and the battery: speaker for beep,
|
||||
// headphones for station audio, nothing when silent.
|
||||
if (foxAudioMode == FOXHUNT_AUDIO_BEEP)
|
||||
memcpy(gStatusLine + 55, BITMAP_FoxHuntSignal, sizeof(BITMAP_FoxHuntSignal));
|
||||
else if (foxAudioMode == FOXHUNT_AUDIO_STATION)
|
||||
memcpy(gStatusLine + 55, BITMAP_FoxHuntSpeaker, sizeof(BITMAP_FoxHuntSpeaker));
|
||||
|
||||
// Hero level, floating: the big number renders naturally (left-anchored) and
|
||||
// the "dBm" unit follows right after it, so the block flows with the width.
|
||||
sprintf(big, "%d", curDbm);
|
||||
UI_DisplayFrequency(big, 2, 0, false);
|
||||
UI_PrintStringSmallNormal("dBm", (uint8_t)(strlen(big) * 13 + 4), 0, 1);
|
||||
|
||||
// Trend as an arrow on line 0 (up = nearer, down = farther, = stable),
|
||||
// right-anchored; the signed delta since ~1 s ago sits on line 1.
|
||||
trendIcon = (trendDelta > 0) ? BITMAP_FoxHuntUp
|
||||
: (trendDelta < 0) ? BITMAP_FoxHuntDown
|
||||
: BITMAP_FoxHuntFlat;
|
||||
memcpy(gFrameBuffer[0] + (126 - 11), trendIcon, 11);
|
||||
// Signed delta on line 1 — only when it actually moved (no "00 dBm" when
|
||||
// stable). "dBm" right-anchored to x126, the value 2 px to its left (a real
|
||||
// space would be 7 px), so they are drawn as two separate strings.
|
||||
if (trendDelta != 0) {
|
||||
sprintf(str, "%+03d", trendDelta); // sign + 2 digits, e.g. "+05" / "-12"
|
||||
UI_PrintStringSmallNormal("dBm", 127 - 3 * 7, 0, 1);
|
||||
UI_PrintStringSmallNormal(str, 127 - 3 * 7 - 2 - strlen(str) * 7, 0, 1);
|
||||
}
|
||||
|
||||
// Context line: peak hold and S-meter as small inverse labels (scan-list tag
|
||||
// style, 4 px/char), PK anchored left and S anchored right.
|
||||
sprintf(str, "PK %d", peakDbm);
|
||||
GUI_DisplaySmallestInverse(str, 4, 2, false, true, 4 + strlen(str) * 4);
|
||||
FOXHUNT_BuildS(sMeter, curDbm);
|
||||
GUI_DisplaySmallestInverse(sMeter, 126 - strlen(sMeter) * 4, 2, false, true, 125);
|
||||
|
||||
// Segmented S-meter staircase.
|
||||
FOXHUNT_DrawBar();
|
||||
|
||||
// Scale under the gauge, aligned with segments 1 / 5 / 9 / 13.
|
||||
GUI_DisplaySmallest("S1", 6, 41, false, true);
|
||||
GUI_DisplaySmallest("S5", 42, 41, false, true);
|
||||
GUI_DisplaySmallest("S9", 78, 41, false, true);
|
||||
GUI_DisplaySmallest("+40", 110, 41, false, true);
|
||||
|
||||
// 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);
|
||||
sprintf(str, "%u.%05u", gRxVfo->pRX->Frequency / 100000, gRxVfo->pRX->Frequency % 100000);
|
||||
UI_PrintStringSmallNormal(str, 126 - strlen(str) * 7, 0, 6);
|
||||
}
|
||||
|
||||
static void FOXHUNT_HandleKeys(void)
|
||||
{
|
||||
kbd.prev = kbd.current;
|
||||
kbd.current = KEYBOARD_GetKey();
|
||||
|
||||
// Act only on the rising edge of a new key.
|
||||
if (kbd.current == KEY_INVALID || kbd.current == kbd.prev)
|
||||
return;
|
||||
|
||||
switch (kbd.current) {
|
||||
case KEY_EXIT:
|
||||
foxRunning = false;
|
||||
break;
|
||||
case KEY_UP:
|
||||
case KEY_4:
|
||||
if (attStep < 3) { attStep++; FOXHUNT_ApplyAtt(); }
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
case KEY_0:
|
||||
if (attStep > 0) { attStep--; FOXHUNT_ApplyAtt(); }
|
||||
break;
|
||||
case KEY_MENU:
|
||||
// Reset the peak hold and the trend reference.
|
||||
peakDbm = curDbm;
|
||||
trendRef = curDbm;
|
||||
break;
|
||||
case KEY_1:
|
||||
// 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;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void APP_RunFoxHunt(void)
|
||||
{
|
||||
// Finish any pending backlight fade before taking over the screen.
|
||||
BACKLIGHT_UpdateTickless();
|
||||
|
||||
// Hunt on the user-selected VFO: dual-watch / cross-band may have left the
|
||||
// radio listening on the other VFO, so force RX on the selected one and retune.
|
||||
gEeprom.RX_VFO = gEeprom.TX_VFO;
|
||||
gRxVfo = gTxVfo;
|
||||
RADIO_SetupRegisters(true);
|
||||
|
||||
// Start silent; the audio (beep / station) is opt-in from inside the mode.
|
||||
AUDIO_AudioPathOff();
|
||||
foxAudioMode = FOXHUNT_AUDIO_OFF;
|
||||
audioTick = 0;
|
||||
|
||||
// Fixed front-end gain so the attenuator steps and the reading stay stable.
|
||||
BK4819_SetAGC(false);
|
||||
attStep = 0;
|
||||
FOXHUNT_ApplyAtt();
|
||||
|
||||
curDbm = FOXHUNT_ReadDbm();
|
||||
peakDbm = curDbm;
|
||||
trendRef = curDbm;
|
||||
trendDelta = 0;
|
||||
trendTick = 0;
|
||||
|
||||
kbd.current = KEY_INVALID;
|
||||
kbd.prev = KEY_INVALID;
|
||||
|
||||
foxRunning = true;
|
||||
while (foxRunning) {
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
// Keep the K5Viewer link alive and pick up any remote key.
|
||||
K5VIEWER_ParseInput();
|
||||
#endif
|
||||
FOXHUNT_HandleKeys();
|
||||
|
||||
curDbm = FOXHUNT_ReadDbm();
|
||||
if (curDbm > peakDbm)
|
||||
peakDbm = curDbm;
|
||||
|
||||
if (++trendTick >= FOXHUNT_TREND_TICKS) {
|
||||
trendDelta = curDbm - trendRef;
|
||||
trendRef = curDbm;
|
||||
trendTick = 0;
|
||||
}
|
||||
|
||||
FOXHUNT_Draw();
|
||||
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
// Mirror the screen to K5Viewer (sends only changed chunks).
|
||||
K5VIEWER_Update(false);
|
||||
#endif
|
||||
|
||||
// Beep mode: Geiger blip whose rate/pitch track the level (silent below floor).
|
||||
if (foxAudioMode == FOXHUNT_AUDIO_BEEP && curDbm >= FOXHUNT_SILENCE_DBM) {
|
||||
if (++audioTick >= FOXHUNT_BlipPeriod(curDbm)) {
|
||||
audioTick = 0;
|
||||
FOXHUNT_Blip(FOXHUNT_DbmToTone(curDbm));
|
||||
}
|
||||
}
|
||||
// Station mode: route the received audio, gated by the silence floor.
|
||||
else if (foxAudioMode == FOXHUNT_AUDIO_STATION) {
|
||||
BK4819_SetAF(curDbm >= FOXHUNT_SILENCE_DBM
|
||||
? (gRxVfo->Modulation == MODULATION_AM ? BK4819_AF_AM : BK4819_AF_FM)
|
||||
: BK4819_AF_MUTE);
|
||||
}
|
||||
|
||||
SYSTEM_DelayMs(FOXHUNT_TICK_MS);
|
||||
}
|
||||
|
||||
// Mute any pending tone and restore the normal VFO selection and RX config.
|
||||
AUDIO_AudioPathOff();
|
||||
RADIO_SelectVfos();
|
||||
RADIO_SetupRegisters(true);
|
||||
}
|
||||
|
||||
void ACTION_FoxHunt(void)
|
||||
{
|
||||
APP_RunFoxHunt();
|
||||
GUI_SelectNextDisplay(DISPLAY_MAIN);
|
||||
}
|
||||
|
||||
#endif // ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
@@ -0,0 +1,56 @@
|
||||
/* Copyright 2026 Armel F4HWN
|
||||
* https://github.com/armel
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef APP_FOXHUNT_H
|
||||
#define APP_FOXHUNT_H
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
|
||||
#include "keyboard_state.h"
|
||||
|
||||
#include "../bitmaps.h"
|
||||
#include "../board.h"
|
||||
#include "py32f0xx.h"
|
||||
#include "../driver/backlight.h"
|
||||
#include "../driver/bk4819-regs.h"
|
||||
#include "../driver/bk4819.h"
|
||||
#include "../driver/gpio.h"
|
||||
#include "../driver/keyboard.h"
|
||||
#include "../driver/st7565.h"
|
||||
#include "../driver/system.h"
|
||||
#include "../driver/systick.h"
|
||||
#include "../external/printf/printf.h"
|
||||
#include "../font.h"
|
||||
#include "../helper/battery.h"
|
||||
#include "../misc.h"
|
||||
#include "../radio.h"
|
||||
#include "../settings.h"
|
||||
#include "../ui/battery.h"
|
||||
#include "../ui/helper.h"
|
||||
#include "../ui/ui.h"
|
||||
#include "../audio.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
// Entry point wired as an assignable side-key action (see action.c).
|
||||
void ACTION_FoxHunt(void);
|
||||
|
||||
// Self-contained modal loop that owns the screen and keypad until EXIT.
|
||||
void APP_RunFoxHunt(void);
|
||||
|
||||
#endif // ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
#endif // APP_FOXHUNT_H
|
||||
@@ -301,6 +301,81 @@ const uint8_t BITMAP_NOAA[12] =
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
const uint8_t BITMAP_FoxHuntSignal[10] =
|
||||
{ // point source + the speaker bitmap's two sound waves, oriented horizontally
|
||||
0b00001000,
|
||||
0b00011100,
|
||||
0b00011100,
|
||||
0b00001000,
|
||||
0b00000000,
|
||||
0b00100010,
|
||||
0b00011100,
|
||||
0b01000001,
|
||||
0b00100010,
|
||||
0b00011100
|
||||
};
|
||||
|
||||
const uint8_t BITMAP_FoxHuntSpeaker[10] =
|
||||
{ // speaker cone + two sound waves (bit0 = top row)
|
||||
0b00011100,
|
||||
0b00011100,
|
||||
0b00111110,
|
||||
0b01111111,
|
||||
0b00000000,
|
||||
0b00100010,
|
||||
0b00011100,
|
||||
0b01000001,
|
||||
0b00100010,
|
||||
0b00011100
|
||||
};
|
||||
|
||||
const uint8_t BITMAP_FoxHuntUp[11] =
|
||||
{ // filled up triangle (getting nearer)
|
||||
0b00100000,
|
||||
0b00110000,
|
||||
0b00111000,
|
||||
0b00111100,
|
||||
0b00111110,
|
||||
0b00111111,
|
||||
0b00111110,
|
||||
0b00111100,
|
||||
0b00111000,
|
||||
0b00110000,
|
||||
0b00100000
|
||||
};
|
||||
|
||||
const uint8_t BITMAP_FoxHuntDown[11] =
|
||||
{ // filled down triangle (getting farther)
|
||||
0b00000001,
|
||||
0b00000011,
|
||||
0b00000111,
|
||||
0b00001111,
|
||||
0b00011111,
|
||||
0b00111111,
|
||||
0b00011111,
|
||||
0b00001111,
|
||||
0b00000111,
|
||||
0b00000011,
|
||||
0b00000001
|
||||
};
|
||||
|
||||
const uint8_t BITMAP_FoxHuntFlat[11] =
|
||||
{ // equals sign (stable)
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_CUSTOM_MENU_LAYOUT
|
||||
const uint8_t BITMAP_CurrentIndicator[8] = {
|
||||
0xFF,
|
||||
|
||||
@@ -45,6 +45,14 @@ extern const uint8_t BITMAP_compand[6];
|
||||
|
||||
extern const uint8_t BITMAP_NOAA[12];
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
extern const uint8_t BITMAP_FoxHuntSignal[10];
|
||||
extern const uint8_t BITMAP_FoxHuntSpeaker[10];
|
||||
extern const uint8_t BITMAP_FoxHuntUp[11];
|
||||
extern const uint8_t BITMAP_FoxHuntDown[11];
|
||||
extern const uint8_t BITMAP_FoxHuntFlat[11];
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_CUSTOM_MENU_LAYOUT
|
||||
extern const uint8_t BITMAP_CurrentIndicator[8];
|
||||
#endif
|
||||
|
||||
@@ -132,6 +132,9 @@ enum ACTION_OPT_t {
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
ACTION_OPT_RXTX_LOG,
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
ACTION_OPT_FOXHUNT,
|
||||
#endif
|
||||
ACTION_OPT_LEN
|
||||
};
|
||||
|
||||
@@ -525,6 +525,9 @@ const t_sidefunction gSubMenu_SIDEFUNCTIONS[] =
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
{"RF LOG", ACTION_OPT_RXTX_LOG},
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
{"FOX HUNT", ACTION_OPT_FOXHUNT},
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"ENABLE_FEAT_F4HWN_SCAN_RSSI": false,
|
||||
"ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": false,
|
||||
"ENABLE_FEAT_F4HWN_BEAM": false,
|
||||
"ENABLE_FEAT_F4HWN_FOXHUNT": false,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG": false,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": false,
|
||||
@@ -207,6 +208,7 @@
|
||||
"ENABLE_FEAT_F4HWN_SCAN_RSSI": true,
|
||||
"ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": true,
|
||||
"ENABLE_FEAT_F4HWN_BEAM": true,
|
||||
"ENABLE_FEAT_F4HWN_FOXHUNT": true,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG": true,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": true,
|
||||
|
||||
Reference in new issue
Block a user