Files
uv-k5-ua1zbe-firmware/app/mode.c
ua1zbe a1206d6fd4 UA1ZBE Custom Firmware v1.0
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)
2026-04-12 00:44:05 +03:00

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;
}