Features: - VFO mode with direct frequency input - POCSAG decoder (512/1200 baud) with BCH(31,21) correction - Full-screen Spectrum analyzer - FM Radio receiver - RSSI signal indicator overlay - Custom boot splash (UA1ZBE / POCSAG pager / build date) Architecture: - app/mode.c — mode dispatcher (VFO/POCSAG/Spectrum/FM) - app/boot_splash.c — 2-second boot splash - app/pocsag/ — POCSAG decoder + BCH correction - app/display_rssi.c — RSSI indicator - main.c — entry point with custom init - syscalls.c — bare-metal _sbrk stub Build: arm-none-eabi-gcc -Os -flto -Wall -Werror -Wextra Size: 57.9KB Flash / 3.6KB RAM Controls: - 0-9: Direct frequency input (VFO) - SK2: POCSAG mode - SK1: Spectrum analyzer - 0: FM Radio - EXIT: Return to VFO - F/*: Toggle 512/1200 baud (in POCSAG)
334 lines
7.6 KiB
C
334 lines
7.6 KiB
C
/* UA1ZBE Custom Firmware - Mode Dispatcher Implementation
|
|
*
|
|
* Central dispatcher for VFO, POCSAG, Spectrum, and FM modes.
|
|
*
|
|
* Architecture:
|
|
* - VFO mode: use full APP_TimeSlice infrastructure
|
|
* - POCSAG mode: use APP_TimeSlice for interrupts but intercept keys
|
|
* - Spectrum: blocking loop, returns to VFO
|
|
* - FM: uses FM radio subsystem
|
|
*/
|
|
|
|
#include "mode.h"
|
|
#include "app/app.h"
|
|
#include "app/common.h"
|
|
#include "app/display_rssi.h"
|
|
#include "driver/bk4819.h"
|
|
#include "driver/keyboard.h"
|
|
#include "driver/st7565.h"
|
|
#include "driver/system.h"
|
|
#include "ui/helper.h"
|
|
#include "ui/main.h"
|
|
#include "misc.h"
|
|
#include "radio.h"
|
|
#include "settings.h"
|
|
#include "functions.h"
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#ifdef ENABLE_POCSAG
|
|
#include "pocsag/pocsag.h"
|
|
#endif
|
|
|
|
#ifdef ENABLE_SPECTRUM
|
|
#include "app/spectrum.h"
|
|
#endif
|
|
|
|
#ifdef ENABLE_FMRADIO
|
|
#include "app/fm.h"
|
|
#endif
|
|
|
|
/* === State === */
|
|
|
|
static op_mode_t s_current_mode = MODE_VFO;
|
|
static GUI_DisplayType_t s_saved_display_type;
|
|
|
|
/* === Mode entry/exit === */
|
|
|
|
static void mode_enter_vfo(void)
|
|
{
|
|
RADIO_SetupRegisters(true);
|
|
gScreenToDisplay = s_saved_display_type;
|
|
gUpdateDisplay = true;
|
|
}
|
|
|
|
static void mode_enter_pocsag(void)
|
|
{
|
|
#ifdef ENABLE_POCSAG
|
|
POCSAG_ConfigureRadio(gRxVfo->freq_config_RX.Frequency);
|
|
POCSAG_Init(POCSAG_BAUD_1200);
|
|
|
|
/* Save current display type and set to INVALID to skip standard key handling */
|
|
s_saved_display_type = gScreenToDisplay;
|
|
gScreenToDisplay = DISPLAY_INVALID;
|
|
|
|
ST7565_FillScreen(0x00);
|
|
for (int line = 0; line < FRAME_LINES; line++)
|
|
memset(gFrameBuffer[line], 0, LCD_WIDTH);
|
|
memset(gStatusLine, 0, sizeof(gStatusLine));
|
|
#endif
|
|
}
|
|
|
|
static void mode_enter_fm(void)
|
|
{
|
|
#ifdef ENABLE_FMRADIO
|
|
s_saved_display_type = gScreenToDisplay;
|
|
gFmRadioMode = false;
|
|
#endif
|
|
}
|
|
|
|
static void mode_exit_current(void)
|
|
{
|
|
switch (s_current_mode) {
|
|
case MODE_POCSAG:
|
|
#ifdef ENABLE_POCSAG
|
|
POCSAG_Stop();
|
|
gScreenToDisplay = s_saved_display_type;
|
|
#endif
|
|
break;
|
|
case MODE_FM:
|
|
#ifdef ENABLE_FMRADIO
|
|
FM_TurnOff();
|
|
gFmRadioMode = false;
|
|
gScreenToDisplay = s_saved_display_type;
|
|
#endif
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* === Mode switching === */
|
|
|
|
void MODE_Switch(op_mode_t mode)
|
|
{
|
|
if (mode == s_current_mode || mode >= MODE_COUNT)
|
|
return;
|
|
|
|
mode_exit_current();
|
|
s_current_mode = mode;
|
|
|
|
switch (mode) {
|
|
case MODE_VFO: mode_enter_vfo(); break;
|
|
case MODE_POCSAG: mode_enter_pocsag(); break;
|
|
case MODE_SPECTRUM: /* Spectrum is blocking */ break;
|
|
case MODE_FM: mode_enter_fm(); break;
|
|
default: break;
|
|
}
|
|
|
|
gUpdateDisplay = true;
|
|
}
|
|
|
|
op_mode_t MODE_GetCurrent(void)
|
|
{
|
|
return s_current_mode;
|
|
}
|
|
|
|
void MODE_Init(void)
|
|
{
|
|
s_current_mode = MODE_VFO;
|
|
s_saved_display_type = DISPLAY_MAIN;
|
|
mode_enter_vfo();
|
|
}
|
|
|
|
/* === POCSAG display === */
|
|
|
|
static void pocsag_draw(void)
|
|
{
|
|
#ifdef ENABLE_POCSAG
|
|
ST7565_FillScreen(0x00);
|
|
for (int line = 0; line < FRAME_LINES; line++)
|
|
memset(gFrameBuffer[line], 0, LCD_WIDTH);
|
|
memset(gStatusLine, 0, sizeof(gStatusLine));
|
|
|
|
char buf[32];
|
|
|
|
/* Header */
|
|
uint32_t baud = POCSAG_GetBaud();
|
|
sprintf(buf, "POCSAG %lu", baud);
|
|
UI_PrintString(buf, 0, LCD_WIDTH, 0, 10);
|
|
|
|
/* Status */
|
|
const char *state_str = POCSAG_StateString(POCSAG_GetState());
|
|
UI_PrintStringSmallNormal(state_str, 0, LCD_WIDTH, 16);
|
|
|
|
if (POCSAG_SignalDetected())
|
|
UI_PrintStringSmallNormal("SIG", 100, LCD_WIDTH, 16);
|
|
|
|
/* Message count and stats */
|
|
sprintf(buf, "Msgs:%d W:%lu", gPocsag.msg_count, gPocsag.total_words);
|
|
UI_PrintStringSmallNormal(buf, 0, LCD_WIDTH, 28);
|
|
|
|
sprintf(buf, "Corr:%lu", gPocsag.corrected_errors);
|
|
UI_PrintStringSmallNormal(buf, 0, LCD_WIDTH, 38);
|
|
|
|
/* RSSI */
|
|
sprintf(buf, "%ddBm", RSSI_GetdBm());
|
|
UI_PrintStringSmallNormal(buf, 0, LCD_WIDTH, 48);
|
|
|
|
/* Help */
|
|
UI_PrintStringSmallNormal("F=512/1200 X=VFO", 0, LCD_WIDTH, 56);
|
|
|
|
ST7565_BlitStatusLine();
|
|
ST7565_BlitFullScreen();
|
|
#endif
|
|
}
|
|
|
|
/* === Key debounce for POCSAG === */
|
|
|
|
static KEY_Code_t s_last_key = KEY_INVALID;
|
|
static uint16_t s_key_debounce = 0;
|
|
|
|
static KEY_Code_t read_key_debounced(void)
|
|
{
|
|
KEY_Code_t key = KEYBOARD_Poll();
|
|
|
|
if (key == s_last_key) {
|
|
if (s_key_debounce < 3) {
|
|
s_key_debounce++;
|
|
return KEY_INVALID;
|
|
}
|
|
} else {
|
|
s_last_key = key;
|
|
s_key_debounce = 0;
|
|
return KEY_INVALID;
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
/* === Timeslice handlers === */
|
|
|
|
void MODE_TimeSlice10ms(void)
|
|
{
|
|
/* Always call standard app processing for interrupts, etc. */
|
|
APP_TimeSlice10ms();
|
|
|
|
/* Mode-specific processing */
|
|
switch (s_current_mode) {
|
|
case MODE_VFO:
|
|
/* VFO display already handled by APP_TimeSlice10ms.
|
|
* Add RSSI overlay on top. */
|
|
if (gScreenToDisplay == DISPLAY_MAIN) {
|
|
RSSI_Draw(95, 0, false);
|
|
ST7565_BlitFullScreen();
|
|
}
|
|
break;
|
|
|
|
case MODE_POCSAG:
|
|
#ifdef ENABLE_POCSAG
|
|
/* Feed audio samples to POCSAG decoder */
|
|
{
|
|
uint16_t audio = BK4819_GetVoiceAmplitudeOut();
|
|
POCSAG_FeedSample(audio);
|
|
}
|
|
POCSAG_Process();
|
|
|
|
/* Check for messages */
|
|
if (POCSAG_MessageAvailable()) {
|
|
pocsag_msg_t msg;
|
|
if (POCSAG_GetMessage(&msg)) {
|
|
/* New message received */
|
|
}
|
|
}
|
|
|
|
/* Process POCSAG-specific keys (gScreenToDisplay is INVALID) */
|
|
{
|
|
KEY_Code_t Key = read_key_debounced();
|
|
if (Key != KEY_INVALID) {
|
|
if (Key == KEY_EXIT) {
|
|
MODE_Switch(MODE_VFO);
|
|
} else if (Key == KEY_F || Key == KEY_STAR) {
|
|
uint32_t cur = POCSAG_GetBaud();
|
|
POCSAG_SwitchBaud(cur == POCSAG_BAUD_1200
|
|
? POCSAG_BAUD_512
|
|
: POCSAG_BAUD_1200);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Redraw POCSAG display periodically */
|
|
{
|
|
static uint16_t rc = 0;
|
|
if (++rc >= 5) {
|
|
rc = 0;
|
|
pocsag_draw();
|
|
}
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case MODE_SPECTRUM:
|
|
/* Spectrum has returned — go back to VFO */
|
|
MODE_Switch(MODE_VFO);
|
|
break;
|
|
|
|
case MODE_FM:
|
|
#ifdef ENABLE_FMRADIO
|
|
if (!gFmRadioMode)
|
|
MODE_Switch(MODE_VFO);
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MODE_TimeSlice500ms(void)
|
|
{
|
|
switch (s_current_mode) {
|
|
case MODE_VFO:
|
|
APP_TimeSlice500ms();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* === External key processing hook === */
|
|
|
|
bool MODE_ProcessKey(int key, bool pressed, bool held)
|
|
{
|
|
KEY_Code_t k = (KEY_Code_t)key;
|
|
|
|
if (k == KEY_EXIT && pressed && !held) {
|
|
if (s_current_mode != MODE_VFO) {
|
|
MODE_Switch(MODE_VFO);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (pressed && !held) {
|
|
switch (s_current_mode) {
|
|
case MODE_VFO:
|
|
if (k == KEY_SIDE2) {
|
|
#ifdef ENABLE_POCSAG
|
|
MODE_Switch(MODE_POCSAG);
|
|
return true;
|
|
#endif
|
|
}
|
|
if (k == KEY_SIDE1) {
|
|
#ifdef ENABLE_SPECTRUM
|
|
MODE_Switch(MODE_SPECTRUM);
|
|
return true;
|
|
#endif
|
|
}
|
|
if (k == KEY_0) {
|
|
#ifdef ENABLE_FMRADIO
|
|
MODE_Switch(MODE_FM);
|
|
FM_Start();
|
|
return true;
|
|
#endif
|
|
}
|
|
return false;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|