mirror of
https://github.com/phdlee/uvk5cec
synced 2024-11-21 23:51:47 +00:00
1311 lines
30 KiB
C
1311 lines
30 KiB
C
/* Copyright 2023 fagci
|
|
* https://github.com/fagci
|
|
*
|
|
* 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/spectrum.h"
|
|
#include "am_fix.h"
|
|
#include "audio.h"
|
|
#include "misc.h"
|
|
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
#include "chFrScanner.h"
|
|
#endif
|
|
|
|
#include "driver/backlight.h"
|
|
#include "frequencies.h"
|
|
#include "ui/helper.h"
|
|
#include "ui/main.h"
|
|
|
|
struct FrequencyBandInfo {
|
|
uint32_t lower;
|
|
uint32_t upper;
|
|
uint32_t middle;
|
|
};
|
|
|
|
#define F_MIN frequencyBandTable[0].lower
|
|
#define F_MAX frequencyBandTable[BAND_N_ELEM - 1].upper
|
|
|
|
const uint16_t RSSI_MAX_VALUE = 65535;
|
|
|
|
static uint32_t initialFreq;
|
|
static char String[32];
|
|
|
|
bool isInitialized = false;
|
|
bool isListening = true;
|
|
bool monitorMode = false;
|
|
bool redrawStatus = true;
|
|
bool redrawScreen = false;
|
|
bool newScanStart = true;
|
|
bool preventKeypress = true;
|
|
bool audioState = true;
|
|
bool lockAGC = false;
|
|
|
|
State currentState = SPECTRUM, previousState = SPECTRUM;
|
|
|
|
PeakInfo peak;
|
|
ScanInfo scanInfo;
|
|
KeyboardState kbd = {KEY_INVALID, KEY_INVALID, 0};
|
|
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
static uint16_t blacklistFreqs[15];
|
|
static uint8_t blacklistFreqsIdx;
|
|
#endif
|
|
|
|
const char *bwOptions[] = {" 25k", "12.5k", "6.25k"};
|
|
const uint8_t modulationTypeTuneSteps[] = {100, 50, 10};
|
|
const uint8_t modTypeReg47Values[] = {1, 7, 5};
|
|
|
|
SpectrumSettings settings = {.stepsCount = STEPS_64,
|
|
.scanStepIndex = S_STEP_25_0kHz,
|
|
.frequencyChangeStep = 80000,
|
|
.scanDelay = 3200,
|
|
.rssiTriggerLevel = 150,
|
|
.backlightState = true,
|
|
.bw = BK4819_FILTER_BW_WIDE,
|
|
.listenBw = BK4819_FILTER_BW_WIDE,
|
|
.modulationType = false,
|
|
.dbMin = -130,
|
|
.dbMax = -50};
|
|
|
|
uint32_t fMeasure = 0;
|
|
uint32_t currentFreq, tempFreq;
|
|
uint16_t rssiHistory[128];
|
|
int vfo;
|
|
uint8_t freqInputIndex = 0;
|
|
uint8_t freqInputDotIndex = 0;
|
|
KEY_Code_t freqInputArr[10];
|
|
char freqInputString[11];
|
|
|
|
uint8_t menuState = 0;
|
|
uint16_t listenT = 0;
|
|
|
|
RegisterSpec registerSpecs[] = {
|
|
{},
|
|
{"LNAs", BK4819_REG_13, 8, 0b11, 1},
|
|
{"LNA", BK4819_REG_13, 5, 0b111, 1},
|
|
{"PGA", BK4819_REG_13, 0, 0b111, 1},
|
|
{"IF", BK4819_REG_3D, 0, 0xFFFF, 0x2aaa},
|
|
// {"MIX", 0x13, 3, 0b11, 1}, // TODO: hidden
|
|
};
|
|
|
|
uint16_t statuslineUpdateTimer = 0;
|
|
|
|
static uint8_t DBm2S(int dbm) {
|
|
uint8_t i = 0;
|
|
dbm *= -1;
|
|
for (i = 0; i < ARRAY_SIZE(U8RssiMap); i++) {
|
|
if (dbm >= U8RssiMap[i]) {
|
|
return i;
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static int Rssi2DBm(uint16_t rssi) {
|
|
return (rssi / 2) - 160 + dBmCorrTable[gRxVfo->Band];
|
|
}
|
|
|
|
static uint16_t GetRegMenuValue(uint8_t st) {
|
|
RegisterSpec s = registerSpecs[st];
|
|
return (BK4819_ReadRegister(s.num) >> s.offset) & s.mask;
|
|
}
|
|
|
|
void LockAGC()
|
|
{
|
|
RADIO_SetupAGC(settings.modulationType==MODULATION_AM, lockAGC);
|
|
lockAGC = true;
|
|
}
|
|
|
|
static void SetRegMenuValue(uint8_t st, bool add) {
|
|
uint16_t v = GetRegMenuValue(st);
|
|
RegisterSpec s = registerSpecs[st];
|
|
|
|
if(s.num == BK4819_REG_13)
|
|
LockAGC();
|
|
|
|
uint16_t reg = BK4819_ReadRegister(s.num);
|
|
if (add && v <= s.mask - s.inc) {
|
|
v += s.inc;
|
|
} else if (!add && v >= 0 + s.inc) {
|
|
v -= s.inc;
|
|
}
|
|
// TODO: use max value for bits count in max value, or reset by additional
|
|
// mask in spec
|
|
reg &= ~(s.mask << s.offset);
|
|
BK4819_WriteRegister(s.num, reg | (v << s.offset));
|
|
redrawScreen = true;
|
|
}
|
|
|
|
// GUI functions
|
|
|
|
static void PutPixel(uint8_t x, uint8_t y, bool fill) {
|
|
UI_DrawPixelBuffer(gFrameBuffer, x, y, fill);
|
|
}
|
|
static void PutPixelStatus(uint8_t x, uint8_t y, bool fill) {
|
|
UI_DrawPixelBuffer(&gStatusLine, x, y, fill);
|
|
}
|
|
|
|
static void DrawVLine(int sy, int ey, int nx, bool fill) {
|
|
for (int i = sy; i <= ey; i++) {
|
|
if (i < 56 && nx < 128) {
|
|
PutPixel(nx, i, fill);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void GUI_DisplaySmallest(const char *pString, uint8_t x, uint8_t y,
|
|
bool statusbar, bool fill) {
|
|
uint8_t c;
|
|
uint8_t pixels;
|
|
const uint8_t *p = (const uint8_t *)pString;
|
|
|
|
while ((c = *p++) && c != '\0') {
|
|
c -= 0x20;
|
|
for (int i = 0; i < 3; ++i) {
|
|
pixels = gFont3x5[c][i];
|
|
for (int j = 0; j < 6; ++j) {
|
|
if (pixels & 1) {
|
|
if (statusbar)
|
|
PutPixelStatus(x + i, y + j, fill);
|
|
else
|
|
PutPixel(x + i, y + j, fill);
|
|
}
|
|
pixels >>= 1;
|
|
}
|
|
}
|
|
x += 4;
|
|
}
|
|
}
|
|
|
|
// Utility functions
|
|
|
|
KEY_Code_t GetKey() {
|
|
KEY_Code_t btn = KEYBOARD_Poll();
|
|
if (btn == KEY_INVALID && !GPIO_CheckBit(&GPIOC->DATA, GPIOC_PIN_PTT)) {
|
|
btn = KEY_PTT;
|
|
}
|
|
return btn;
|
|
}
|
|
|
|
static int clamp(int v, int min, int max) {
|
|
return v <= min ? min : (v >= max ? max : v);
|
|
}
|
|
|
|
static uint8_t my_abs(signed v) { return v > 0 ? v : -v; }
|
|
|
|
void SetState(State state) {
|
|
previousState = currentState;
|
|
currentState = state;
|
|
redrawScreen = true;
|
|
redrawStatus = true;
|
|
}
|
|
|
|
// Radio functions
|
|
|
|
static void ToggleAFBit(bool on) {
|
|
uint16_t reg = BK4819_ReadRegister(BK4819_REG_47);
|
|
reg &= ~(1 << 8);
|
|
if (on)
|
|
reg |= on << 8;
|
|
BK4819_WriteRegister(BK4819_REG_47, reg);
|
|
}
|
|
|
|
static const BK4819_REGISTER_t registers_to_save[] ={
|
|
BK4819_REG_30,
|
|
BK4819_REG_37,
|
|
BK4819_REG_3D,
|
|
BK4819_REG_43,
|
|
BK4819_REG_47,
|
|
BK4819_REG_48,
|
|
BK4819_REG_7E,
|
|
};
|
|
|
|
static uint16_t registers_stack [sizeof(registers_to_save)];
|
|
|
|
static void BackupRegisters() {
|
|
for (uint32_t i = 0; i < ARRAY_SIZE(registers_to_save); i++){
|
|
registers_stack[i] = BK4819_ReadRegister(registers_to_save[i]);
|
|
}
|
|
}
|
|
|
|
static void RestoreRegisters() {
|
|
|
|
for (uint32_t i = 0; i < ARRAY_SIZE(registers_to_save); i++){
|
|
BK4819_WriteRegister(registers_to_save[i], registers_stack[i]);
|
|
}
|
|
}
|
|
|
|
static void ToggleAFDAC(bool on) {
|
|
uint32_t Reg = BK4819_ReadRegister(BK4819_REG_30);
|
|
Reg &= ~(1 << 9);
|
|
if (on)
|
|
Reg |= (1 << 9);
|
|
BK4819_WriteRegister(BK4819_REG_30, Reg);
|
|
}
|
|
|
|
static void SetF(uint32_t f) {
|
|
fMeasure = f;
|
|
|
|
BK4819_SetFrequency(fMeasure);
|
|
BK4819_PickRXFilterPathBasedOnFrequency(fMeasure);
|
|
uint16_t reg = BK4819_ReadRegister(BK4819_REG_30);
|
|
BK4819_WriteRegister(BK4819_REG_30, 0);
|
|
BK4819_WriteRegister(BK4819_REG_30, reg);
|
|
}
|
|
|
|
// Spectrum related
|
|
|
|
bool IsPeakOverLevel() { return peak.rssi >= settings.rssiTriggerLevel; }
|
|
|
|
static void ResetPeak() {
|
|
peak.t = 0;
|
|
peak.rssi = 0;
|
|
}
|
|
|
|
bool IsCenterMode() { return settings.scanStepIndex < S_STEP_2_5kHz; }
|
|
// scan step in 0.01khz
|
|
uint16_t GetScanStep() { return scanStepValues[settings.scanStepIndex]; }
|
|
|
|
uint16_t GetStepsCount()
|
|
{
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
if(gScanRangeStart) {
|
|
return (gScanRangeStop - gScanRangeStart) / GetScanStep();
|
|
}
|
|
#endif
|
|
return 128 >> settings.stepsCount;
|
|
}
|
|
|
|
uint32_t GetBW() { return GetStepsCount() * GetScanStep(); }
|
|
uint32_t GetFStart() {
|
|
return IsCenterMode() ? currentFreq - (GetBW() >> 1) : currentFreq;
|
|
}
|
|
|
|
uint32_t GetFEnd() { return currentFreq + GetBW(); }
|
|
|
|
static void TuneToPeak() {
|
|
scanInfo.f = peak.f;
|
|
scanInfo.rssi = peak.rssi;
|
|
scanInfo.i = peak.i;
|
|
SetF(scanInfo.f);
|
|
}
|
|
|
|
static void DeInitSpectrum() {
|
|
SetF(initialFreq);
|
|
RestoreRegisters();
|
|
isInitialized = false;
|
|
}
|
|
|
|
uint8_t GetBWRegValueForScan() {
|
|
return scanStepBWRegValues[settings.scanStepIndex];
|
|
}
|
|
|
|
uint16_t GetRssi() {
|
|
// SYSTICK_DelayUs(800);
|
|
// testing autodelay based on Glitch value
|
|
while ((BK4819_ReadRegister(0x63) & 0b11111111) >= 255) {
|
|
SYSTICK_DelayUs(100);
|
|
}
|
|
uint16_t rssi = BK4819_GetRSSI();
|
|
#ifdef ENABLE_AM_FIX
|
|
if(settings.modulationType==MODULATION_AM && gSetting_AM_fix)
|
|
rssi += AM_fix_get_gain_diff()*2;
|
|
#endif
|
|
return rssi;
|
|
}
|
|
|
|
static void ToggleAudio(bool on) {
|
|
if (on == audioState) {
|
|
return;
|
|
}
|
|
audioState = on;
|
|
if (on) {
|
|
AUDIO_AudioPathOn();
|
|
} else {
|
|
AUDIO_AudioPathOff();
|
|
}
|
|
}
|
|
|
|
static void ToggleRX(bool on) {
|
|
isListening = on;
|
|
|
|
RADIO_SetupAGC(on, lockAGC);
|
|
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, on);
|
|
|
|
ToggleAudio(on);
|
|
ToggleAFDAC(on);
|
|
ToggleAFBit(on);
|
|
|
|
if (on) {
|
|
listenT = 1000;
|
|
BK4819_WriteRegister(0x43, listenBWRegValues[settings.listenBw]);
|
|
} else {
|
|
BK4819_WriteRegister(0x43, GetBWRegValueForScan());
|
|
}
|
|
}
|
|
|
|
// Scan info
|
|
|
|
static void ResetScanStats() {
|
|
scanInfo.rssi = 0;
|
|
scanInfo.rssiMax = 0;
|
|
scanInfo.iPeak = 0;
|
|
scanInfo.fPeak = 0;
|
|
}
|
|
|
|
static void InitScan() {
|
|
ResetScanStats();
|
|
scanInfo.i = 0;
|
|
scanInfo.f = GetFStart();
|
|
|
|
scanInfo.scanStep = GetScanStep();
|
|
scanInfo.measurementsCount = GetStepsCount();
|
|
}
|
|
|
|
static void ResetBlacklist() {
|
|
for (int i = 0; i < 128; ++i) {
|
|
if (rssiHistory[i] == RSSI_MAX_VALUE)
|
|
rssiHistory[i] = 0;
|
|
}
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
memset(blacklistFreqs, 0, sizeof(blacklistFreqs));
|
|
blacklistFreqsIdx = 0;
|
|
#endif
|
|
}
|
|
|
|
static void RelaunchScan() {
|
|
InitScan();
|
|
ResetPeak();
|
|
ToggleRX(false);
|
|
#ifdef SPECTRUM_AUTOMATIC_SQUELCH
|
|
settings.rssiTriggerLevel = RSSI_MAX_VALUE;
|
|
#endif
|
|
preventKeypress = true;
|
|
scanInfo.rssiMin = RSSI_MAX_VALUE;
|
|
}
|
|
|
|
static void UpdateScanInfo() {
|
|
if (scanInfo.rssi > scanInfo.rssiMax) {
|
|
scanInfo.rssiMax = scanInfo.rssi;
|
|
scanInfo.fPeak = scanInfo.f;
|
|
scanInfo.iPeak = scanInfo.i;
|
|
}
|
|
|
|
if (scanInfo.rssi < scanInfo.rssiMin) {
|
|
scanInfo.rssiMin = scanInfo.rssi;
|
|
settings.dbMin = Rssi2DBm(scanInfo.rssiMin);
|
|
redrawStatus = true;
|
|
}
|
|
}
|
|
|
|
static void AutoTriggerLevel() {
|
|
if (settings.rssiTriggerLevel == RSSI_MAX_VALUE) {
|
|
settings.rssiTriggerLevel = clamp(scanInfo.rssiMax + 8, 0, RSSI_MAX_VALUE);
|
|
}
|
|
}
|
|
|
|
static void UpdatePeakInfoForce() {
|
|
peak.t = 0;
|
|
peak.rssi = scanInfo.rssiMax;
|
|
peak.f = scanInfo.fPeak;
|
|
peak.i = scanInfo.iPeak;
|
|
AutoTriggerLevel();
|
|
}
|
|
|
|
static void UpdatePeakInfo() {
|
|
if (peak.f == 0 || peak.t >= 1024 || peak.rssi < scanInfo.rssiMax)
|
|
UpdatePeakInfoForce();
|
|
}
|
|
|
|
static void SetRssiHistory(uint16_t idx, uint16_t rssi)
|
|
{
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
if(scanInfo.measurementsCount > 128) {
|
|
uint8_t i = (uint32_t)ARRAY_SIZE(rssiHistory) * 1000 / scanInfo.measurementsCount * idx / 1000;
|
|
if(rssiHistory[i] < rssi || isListening)
|
|
rssiHistory[i] = rssi;
|
|
rssiHistory[(i+1)%128] = 0;
|
|
return;
|
|
}
|
|
#endif
|
|
rssiHistory[idx] = rssi;
|
|
}
|
|
|
|
static void Measure()
|
|
{
|
|
uint16_t rssi = scanInfo.rssi = GetRssi();
|
|
SetRssiHistory(scanInfo.i, rssi);
|
|
}
|
|
|
|
// Update things by keypress
|
|
|
|
static uint16_t dbm2rssi(int dBm) {
|
|
return (dBm + 160 - dBmCorrTable[gRxVfo->Band]) * 2;
|
|
}
|
|
|
|
static void ClampRssiTriggerLevel() {
|
|
settings.rssiTriggerLevel =
|
|
clamp(settings.rssiTriggerLevel, dbm2rssi(settings.dbMin),
|
|
dbm2rssi(settings.dbMax));
|
|
}
|
|
|
|
static void UpdateRssiTriggerLevel(bool inc) {
|
|
if (inc)
|
|
settings.rssiTriggerLevel += 2;
|
|
else
|
|
settings.rssiTriggerLevel -= 2;
|
|
|
|
ClampRssiTriggerLevel();
|
|
|
|
redrawScreen = true;
|
|
redrawStatus = true;
|
|
}
|
|
|
|
static void UpdateDBMax(bool inc) {
|
|
if (inc && settings.dbMax < 10) {
|
|
settings.dbMax += 1;
|
|
} else if (!inc && settings.dbMax > settings.dbMin) {
|
|
settings.dbMax -= 1;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
ClampRssiTriggerLevel();
|
|
redrawStatus = true;
|
|
redrawScreen = true;
|
|
SYSTEM_DelayMs(20);
|
|
}
|
|
|
|
static void UpdateScanStep(bool inc) {
|
|
if (inc) {
|
|
settings.scanStepIndex = settings.scanStepIndex != S_STEP_100_0kHz ? settings.scanStepIndex + 1 : 0;
|
|
} else {
|
|
settings.scanStepIndex = settings.scanStepIndex != 0 ? settings.scanStepIndex - 1 : S_STEP_100_0kHz;
|
|
}
|
|
|
|
settings.frequencyChangeStep = GetBW() >> 1;
|
|
RelaunchScan();
|
|
ResetBlacklist();
|
|
redrawScreen = true;
|
|
}
|
|
|
|
static void UpdateCurrentFreq(bool inc) {
|
|
if (inc && currentFreq < F_MAX) {
|
|
currentFreq += settings.frequencyChangeStep;
|
|
} else if (!inc && currentFreq > F_MIN) {
|
|
currentFreq -= settings.frequencyChangeStep;
|
|
} else {
|
|
return;
|
|
}
|
|
RelaunchScan();
|
|
ResetBlacklist();
|
|
redrawScreen = true;
|
|
}
|
|
|
|
static void UpdateCurrentFreqStill(bool inc) {
|
|
uint8_t offset = modulationTypeTuneSteps[settings.modulationType];
|
|
uint32_t f = fMeasure;
|
|
if (inc && f < F_MAX) {
|
|
f += offset;
|
|
} else if (!inc && f > F_MIN) {
|
|
f -= offset;
|
|
}
|
|
SetF(f);
|
|
redrawScreen = true;
|
|
}
|
|
|
|
static void UpdateFreqChangeStep(bool inc) {
|
|
uint16_t diff = GetScanStep() * 4;
|
|
if (inc && settings.frequencyChangeStep < 200000) {
|
|
settings.frequencyChangeStep += diff;
|
|
} else if (!inc && settings.frequencyChangeStep > 10000) {
|
|
settings.frequencyChangeStep -= diff;
|
|
}
|
|
SYSTEM_DelayMs(100);
|
|
redrawScreen = true;
|
|
}
|
|
|
|
static void ToggleModulation() {
|
|
if (settings.modulationType < MODULATION_UKNOWN - 1) {
|
|
settings.modulationType++;
|
|
} else {
|
|
settings.modulationType = MODULATION_FM;
|
|
}
|
|
RADIO_SetModulation(settings.modulationType);
|
|
|
|
RelaunchScan();
|
|
redrawScreen = true;
|
|
}
|
|
|
|
static void ToggleListeningBW() {
|
|
if (settings.listenBw == BK4819_FILTER_BW_NARROWER) {
|
|
settings.listenBw = BK4819_FILTER_BW_WIDE;
|
|
} else {
|
|
settings.listenBw++;
|
|
}
|
|
redrawScreen = true;
|
|
}
|
|
|
|
static void ToggleBacklight() {
|
|
settings.backlightState = !settings.backlightState;
|
|
if (settings.backlightState) {
|
|
BACKLIGHT_TurnOn();
|
|
} else {
|
|
BACKLIGHT_TurnOff();
|
|
}
|
|
}
|
|
|
|
static void ToggleStepsCount() {
|
|
if (settings.stepsCount == STEPS_128) {
|
|
settings.stepsCount = STEPS_16;
|
|
} else {
|
|
settings.stepsCount--;
|
|
}
|
|
settings.frequencyChangeStep = GetBW() >> 1;
|
|
RelaunchScan();
|
|
ResetBlacklist();
|
|
redrawScreen = true;
|
|
}
|
|
|
|
static void ResetFreqInput() {
|
|
tempFreq = 0;
|
|
for (int i = 0; i < 10; ++i) {
|
|
freqInputString[i] = '-';
|
|
}
|
|
}
|
|
|
|
static void FreqInput() {
|
|
freqInputIndex = 0;
|
|
freqInputDotIndex = 0;
|
|
ResetFreqInput();
|
|
SetState(FREQ_INPUT);
|
|
}
|
|
|
|
static void UpdateFreqInput(KEY_Code_t key) {
|
|
if (key != KEY_EXIT && freqInputIndex >= 10) {
|
|
return;
|
|
}
|
|
if (key == KEY_STAR) {
|
|
if (freqInputIndex == 0 || freqInputDotIndex) {
|
|
return;
|
|
}
|
|
freqInputDotIndex = freqInputIndex;
|
|
}
|
|
if (key == KEY_EXIT) {
|
|
freqInputIndex--;
|
|
if (freqInputDotIndex == freqInputIndex)
|
|
freqInputDotIndex = 0;
|
|
} else {
|
|
freqInputArr[freqInputIndex++] = key;
|
|
}
|
|
|
|
ResetFreqInput();
|
|
|
|
uint8_t dotIndex =
|
|
freqInputDotIndex == 0 ? freqInputIndex : freqInputDotIndex;
|
|
|
|
KEY_Code_t digitKey;
|
|
for (int i = 0; i < 10; ++i) {
|
|
if (i < freqInputIndex) {
|
|
digitKey = freqInputArr[i];
|
|
freqInputString[i] = digitKey <= KEY_9 ? '0' + digitKey - KEY_0 : '.';
|
|
} else {
|
|
freqInputString[i] = '-';
|
|
}
|
|
}
|
|
|
|
uint32_t base = 100000; // 1MHz in BK units
|
|
for (int i = dotIndex - 1; i >= 0; --i) {
|
|
tempFreq += (freqInputArr[i] - KEY_0) * base;
|
|
base *= 10;
|
|
}
|
|
|
|
base = 10000; // 0.1MHz in BK units
|
|
if (dotIndex < freqInputIndex) {
|
|
for (int i = dotIndex + 1; i < freqInputIndex; ++i) {
|
|
tempFreq += (freqInputArr[i] - KEY_0) * base;
|
|
base /= 10;
|
|
}
|
|
}
|
|
redrawScreen = true;
|
|
}
|
|
|
|
static void Blacklist() {
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
blacklistFreqs[blacklistFreqsIdx++ % ARRAY_SIZE(blacklistFreqs)] = peak.i;
|
|
#endif
|
|
|
|
SetRssiHistory(peak.i, RSSI_MAX_VALUE);
|
|
ResetPeak();
|
|
ToggleRX(false);
|
|
ResetScanStats();
|
|
}
|
|
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
static bool IsBlacklisted(uint16_t idx)
|
|
{
|
|
for(uint8_t i = 0; i < ARRAY_SIZE(blacklistFreqs); i++)
|
|
if(blacklistFreqs[i] == idx)
|
|
return true;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Draw things
|
|
|
|
// applied x2 to prevent initial rounding
|
|
uint8_t Rssi2PX(uint16_t rssi, uint8_t pxMin, uint8_t pxMax) {
|
|
const int DB_MIN = settings.dbMin << 1;
|
|
const int DB_MAX = settings.dbMax << 1;
|
|
const int DB_RANGE = DB_MAX - DB_MIN;
|
|
|
|
const uint8_t PX_RANGE = pxMax - pxMin;
|
|
|
|
int dbm = clamp(Rssi2DBm(rssi) << 1, DB_MIN, DB_MAX);
|
|
|
|
return ((dbm - DB_MIN) * PX_RANGE + DB_RANGE / 2) / DB_RANGE + pxMin;
|
|
}
|
|
|
|
uint8_t Rssi2Y(uint16_t rssi) {
|
|
return DrawingEndY - Rssi2PX(rssi, 0, DrawingEndY);
|
|
}
|
|
|
|
static void DrawSpectrum() {
|
|
for (uint8_t x = 0; x < 128; ++x) {
|
|
uint16_t rssi = rssiHistory[x >> settings.stepsCount];
|
|
if (rssi != RSSI_MAX_VALUE) {
|
|
DrawVLine(Rssi2Y(rssi), DrawingEndY, x, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DrawStatus() {
|
|
#ifdef SPECTRUM_EXTRA_VALUES
|
|
sprintf(String, "%d/%d P:%d T:%d", settings.dbMin, settings.dbMax,
|
|
Rssi2DBm(peak.rssi), Rssi2DBm(settings.rssiTriggerLevel));
|
|
#else
|
|
sprintf(String, "%d/%d", settings.dbMin, settings.dbMax);
|
|
#endif
|
|
GUI_DisplaySmallest(String, 0, 1, true, true);
|
|
|
|
BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[gBatteryCheckCounter++ % 4],
|
|
&gBatteryCurrent);
|
|
|
|
uint16_t voltage = (gBatteryVoltages[0] + gBatteryVoltages[1] +
|
|
gBatteryVoltages[2] + gBatteryVoltages[3]) /
|
|
4 * 760 / gBatteryCalibration[3];
|
|
|
|
unsigned perc = BATTERY_VoltsToPercent(voltage);
|
|
|
|
// sprintf(String, "%d %d", voltage, perc);
|
|
// GUI_DisplaySmallest(String, 48, 1, true, true);
|
|
|
|
gStatusLine[116] = 0b00011100;
|
|
gStatusLine[117] = 0b00111110;
|
|
for (int i = 118; i <= 126; i++) {
|
|
gStatusLine[i] = 0b00100010;
|
|
}
|
|
|
|
for (unsigned i = 127; i >= 118; i--) {
|
|
if (127 - i <= (perc + 5) * 9 / 100) {
|
|
gStatusLine[i] = 0b00111110;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DrawF(uint32_t f) {
|
|
sprintf(String, "%u.%05u", f / 100000, f % 100000);
|
|
UI_PrintStringSmallNormal(String, 8, 127, 0);
|
|
|
|
sprintf(String, "%3s", gModulationStr[settings.modulationType]);
|
|
GUI_DisplaySmallest(String, 116, 1, false, true);
|
|
sprintf(String, "%s", bwOptions[settings.listenBw]);
|
|
GUI_DisplaySmallest(String, 108, 7, false, true);
|
|
}
|
|
|
|
static void DrawNums() {
|
|
|
|
if (currentState == SPECTRUM) {
|
|
sprintf(String, "%ux", GetStepsCount());
|
|
GUI_DisplaySmallest(String, 0, 1, false, true);
|
|
sprintf(String, "%u.%02uk", GetScanStep() / 100, GetScanStep() % 100);
|
|
GUI_DisplaySmallest(String, 0, 7, false, true);
|
|
}
|
|
|
|
if (IsCenterMode()) {
|
|
sprintf(String, "%u.%05u \x7F%u.%02uk", currentFreq / 100000,
|
|
currentFreq % 100000, settings.frequencyChangeStep / 100,
|
|
settings.frequencyChangeStep % 100);
|
|
GUI_DisplaySmallest(String, 36, 49, false, true);
|
|
} else {
|
|
sprintf(String, "%u.%05u", GetFStart() / 100000, GetFStart() % 100000);
|
|
GUI_DisplaySmallest(String, 0, 49, false, true);
|
|
|
|
sprintf(String, "\x7F%u.%02uk", settings.frequencyChangeStep / 100,
|
|
settings.frequencyChangeStep % 100);
|
|
GUI_DisplaySmallest(String, 48, 49, false, true);
|
|
|
|
sprintf(String, "%u.%05u", GetFEnd() / 100000, GetFEnd() % 100000);
|
|
GUI_DisplaySmallest(String, 93, 49, false, true);
|
|
}
|
|
}
|
|
|
|
static void DrawRssiTriggerLevel() {
|
|
if (settings.rssiTriggerLevel == RSSI_MAX_VALUE || monitorMode)
|
|
return;
|
|
uint8_t y = Rssi2Y(settings.rssiTriggerLevel);
|
|
for (uint8_t x = 0; x < 128; x += 2) {
|
|
PutPixel(x, y, true);
|
|
}
|
|
}
|
|
|
|
static void DrawTicks() {
|
|
uint32_t f = GetFStart();
|
|
uint32_t span = GetFEnd() - GetFStart();
|
|
uint32_t step = span / 128;
|
|
for (uint8_t i = 0; i < 128; i += (1 << settings.stepsCount)) {
|
|
f = GetFStart() + span * i / 128;
|
|
uint8_t barValue = 0b00000001;
|
|
(f % 10000) < step && (barValue |= 0b00000010);
|
|
(f % 50000) < step && (barValue |= 0b00000100);
|
|
(f % 100000) < step && (barValue |= 0b00011000);
|
|
|
|
gFrameBuffer[5][i] |= barValue;
|
|
}
|
|
|
|
// center
|
|
if (IsCenterMode()) {
|
|
memset(gFrameBuffer[5] + 62, 0x80, 5);
|
|
gFrameBuffer[5][64] = 0xff;
|
|
} else {
|
|
memset(gFrameBuffer[5] + 1, 0x80, 3);
|
|
memset(gFrameBuffer[5] + 124, 0x80, 3);
|
|
|
|
gFrameBuffer[5][0] = 0xff;
|
|
gFrameBuffer[5][127] = 0xff;
|
|
}
|
|
}
|
|
|
|
static void DrawArrow(uint8_t x) {
|
|
for (signed i = -2; i <= 2; ++i) {
|
|
signed v = x + i;
|
|
if (!(v & 128)) {
|
|
gFrameBuffer[5][v] |= (0b01111000 << my_abs(i)) & 0b01111000;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void OnKeyDown(uint8_t key) {
|
|
switch (key) {
|
|
case KEY_3:
|
|
UpdateDBMax(true);
|
|
break;
|
|
case KEY_9:
|
|
UpdateDBMax(false);
|
|
break;
|
|
case KEY_1:
|
|
UpdateScanStep(true);
|
|
break;
|
|
case KEY_7:
|
|
UpdateScanStep(false);
|
|
break;
|
|
case KEY_2:
|
|
UpdateFreqChangeStep(true);
|
|
break;
|
|
case KEY_8:
|
|
UpdateFreqChangeStep(false);
|
|
break;
|
|
case KEY_UP:
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
if(!gScanRangeStart)
|
|
#endif
|
|
UpdateCurrentFreq(true);
|
|
break;
|
|
case KEY_DOWN:
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
if(!gScanRangeStart)
|
|
#endif
|
|
UpdateCurrentFreq(false);
|
|
break;
|
|
case KEY_SIDE1:
|
|
Blacklist();
|
|
break;
|
|
case KEY_STAR:
|
|
UpdateRssiTriggerLevel(true);
|
|
break;
|
|
case KEY_F:
|
|
UpdateRssiTriggerLevel(false);
|
|
break;
|
|
case KEY_5:
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
if(!gScanRangeStart)
|
|
#endif
|
|
FreqInput();
|
|
break;
|
|
case KEY_0:
|
|
ToggleModulation();
|
|
break;
|
|
case KEY_6:
|
|
ToggleListeningBW();
|
|
break;
|
|
case KEY_4:
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
if(!gScanRangeStart)
|
|
#endif
|
|
ToggleStepsCount();
|
|
break;
|
|
case KEY_SIDE2:
|
|
ToggleBacklight();
|
|
break;
|
|
case KEY_PTT:
|
|
SetState(STILL);
|
|
TuneToPeak();
|
|
break;
|
|
case KEY_MENU:
|
|
break;
|
|
case KEY_EXIT:
|
|
if (menuState) {
|
|
menuState = 0;
|
|
break;
|
|
}
|
|
DeInitSpectrum();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void OnKeyDownFreqInput(uint8_t key) {
|
|
switch (key) {
|
|
case KEY_0:
|
|
case KEY_1:
|
|
case KEY_2:
|
|
case KEY_3:
|
|
case KEY_4:
|
|
case KEY_5:
|
|
case KEY_6:
|
|
case KEY_7:
|
|
case KEY_8:
|
|
case KEY_9:
|
|
case KEY_STAR:
|
|
UpdateFreqInput(key);
|
|
break;
|
|
case KEY_EXIT:
|
|
if (freqInputIndex == 0) {
|
|
SetState(previousState);
|
|
break;
|
|
}
|
|
UpdateFreqInput(key);
|
|
break;
|
|
case KEY_MENU:
|
|
if (tempFreq < F_MIN || tempFreq > F_MAX) {
|
|
break;
|
|
}
|
|
SetState(previousState);
|
|
currentFreq = tempFreq;
|
|
if (currentState == SPECTRUM) {
|
|
ResetBlacklist();
|
|
RelaunchScan();
|
|
} else {
|
|
SetF(currentFreq);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void OnKeyDownStill(KEY_Code_t key) {
|
|
switch (key) {
|
|
case KEY_3:
|
|
UpdateDBMax(true);
|
|
break;
|
|
case KEY_9:
|
|
UpdateDBMax(false);
|
|
break;
|
|
case KEY_UP:
|
|
if (menuState) {
|
|
SetRegMenuValue(menuState, true);
|
|
break;
|
|
}
|
|
UpdateCurrentFreqStill(true);
|
|
break;
|
|
case KEY_DOWN:
|
|
if (menuState) {
|
|
SetRegMenuValue(menuState, false);
|
|
break;
|
|
}
|
|
UpdateCurrentFreqStill(false);
|
|
break;
|
|
case KEY_STAR:
|
|
UpdateRssiTriggerLevel(true);
|
|
break;
|
|
case KEY_F:
|
|
UpdateRssiTriggerLevel(false);
|
|
break;
|
|
case KEY_5:
|
|
FreqInput();
|
|
break;
|
|
case KEY_0:
|
|
ToggleModulation();
|
|
break;
|
|
case KEY_6:
|
|
ToggleListeningBW();
|
|
break;
|
|
case KEY_SIDE1:
|
|
monitorMode = !monitorMode;
|
|
break;
|
|
case KEY_SIDE2:
|
|
ToggleBacklight();
|
|
break;
|
|
case KEY_PTT:
|
|
// TODO: start transmit
|
|
/* BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
|
|
BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, true); */
|
|
break;
|
|
case KEY_MENU:
|
|
if (menuState == ARRAY_SIZE(registerSpecs) - 1) {
|
|
menuState = 1;
|
|
} else {
|
|
menuState++;
|
|
}
|
|
redrawScreen = true;
|
|
break;
|
|
case KEY_EXIT:
|
|
if (!menuState) {
|
|
SetState(SPECTRUM);
|
|
lockAGC = false;
|
|
monitorMode = false;
|
|
RelaunchScan();
|
|
break;
|
|
}
|
|
menuState = 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void RenderFreqInput() { UI_PrintString(freqInputString, 2, 127, 0, 8); }
|
|
|
|
static void RenderStatus() {
|
|
memset(gStatusLine, 0, sizeof(gStatusLine));
|
|
DrawStatus();
|
|
ST7565_BlitStatusLine();
|
|
}
|
|
|
|
static void RenderSpectrum() {
|
|
DrawTicks();
|
|
DrawArrow(128u * peak.i / GetStepsCount());
|
|
DrawSpectrum();
|
|
DrawRssiTriggerLevel();
|
|
DrawF(peak.f);
|
|
DrawNums();
|
|
}
|
|
|
|
static void RenderStill() {
|
|
DrawF(fMeasure);
|
|
|
|
const uint8_t METER_PAD_LEFT = 3;
|
|
|
|
memset(&gFrameBuffer[2][METER_PAD_LEFT], 0b00010000, 121);
|
|
|
|
for (int i = 0; i < 121; i+=5) {
|
|
gFrameBuffer[2][i + METER_PAD_LEFT] = 0b00110000;
|
|
}
|
|
|
|
for (int i = 0; i < 121; i+=10) {
|
|
gFrameBuffer[2][i + METER_PAD_LEFT] = 0b01110000;
|
|
}
|
|
|
|
uint8_t x = Rssi2PX(scanInfo.rssi, 0, 121);
|
|
for (int i = 0; i < x; ++i) {
|
|
if (i % 5) {
|
|
gFrameBuffer[2][i + METER_PAD_LEFT] |= 0b00000111;
|
|
}
|
|
}
|
|
|
|
int dbm = Rssi2DBm(scanInfo.rssi);
|
|
uint8_t s = DBm2S(dbm);
|
|
sprintf(String, "S: %u", s);
|
|
GUI_DisplaySmallest(String, 4, 25, false, true);
|
|
sprintf(String, "%d dBm", dbm);
|
|
GUI_DisplaySmallest(String, 28, 25, false, true);
|
|
|
|
if (!monitorMode) {
|
|
uint8_t x = Rssi2PX(settings.rssiTriggerLevel, 0, 121);
|
|
gFrameBuffer[2][METER_PAD_LEFT + x] = 0b11111111;
|
|
}
|
|
|
|
const uint8_t PAD_LEFT = 4;
|
|
const uint8_t CELL_WIDTH = 30;
|
|
uint8_t offset = PAD_LEFT;
|
|
uint8_t row = 4;
|
|
|
|
for (int i = 0, idx = 1; idx <= 4; ++i, ++idx) {
|
|
if (idx == 5) {
|
|
row += 2;
|
|
i = 0;
|
|
}
|
|
offset = PAD_LEFT + i * CELL_WIDTH;
|
|
if (menuState == idx) {
|
|
for (int j = 0; j < CELL_WIDTH; ++j) {
|
|
gFrameBuffer[row][j + offset] = 0xFF;
|
|
gFrameBuffer[row + 1][j + offset] = 0xFF;
|
|
}
|
|
}
|
|
sprintf(String, "%s", registerSpecs[idx].name);
|
|
GUI_DisplaySmallest(String, offset + 2, row * 8 + 2, false,
|
|
menuState != idx);
|
|
sprintf(String, "%u", GetRegMenuValue(idx));
|
|
GUI_DisplaySmallest(String, offset + 2, (row + 1) * 8 + 1, false,
|
|
menuState != idx);
|
|
}
|
|
}
|
|
|
|
static void Render() {
|
|
UI_DisplayClear();
|
|
|
|
switch (currentState) {
|
|
case SPECTRUM:
|
|
RenderSpectrum();
|
|
break;
|
|
case FREQ_INPUT:
|
|
RenderFreqInput();
|
|
break;
|
|
case STILL:
|
|
RenderStill();
|
|
break;
|
|
}
|
|
|
|
ST7565_BlitFullScreen();
|
|
}
|
|
|
|
bool HandleUserInput() {
|
|
kbd.prev = kbd.current;
|
|
kbd.current = GetKey();
|
|
|
|
if (kbd.current != KEY_INVALID && kbd.current == kbd.prev) {
|
|
if (kbd.counter < 16)
|
|
kbd.counter++;
|
|
else
|
|
kbd.counter -= 3;
|
|
SYSTEM_DelayMs(20);
|
|
} else {
|
|
kbd.counter = 0;
|
|
}
|
|
|
|
if (kbd.counter == 3 || kbd.counter == 16) {
|
|
switch (currentState) {
|
|
case SPECTRUM:
|
|
OnKeyDown(kbd.current);
|
|
break;
|
|
case FREQ_INPUT:
|
|
OnKeyDownFreqInput(kbd.current);
|
|
break;
|
|
case STILL:
|
|
OnKeyDownStill(kbd.current);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void Scan() {
|
|
if (rssiHistory[scanInfo.i] != RSSI_MAX_VALUE
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
&& !IsBlacklisted(scanInfo.i)
|
|
#endif
|
|
) {
|
|
SetF(scanInfo.f);
|
|
Measure();
|
|
UpdateScanInfo();
|
|
}
|
|
}
|
|
|
|
static void NextScanStep() {
|
|
++peak.t;
|
|
++scanInfo.i;
|
|
scanInfo.f += scanInfo.scanStep;
|
|
}
|
|
|
|
static void UpdateScan() {
|
|
Scan();
|
|
|
|
if (scanInfo.i < scanInfo.measurementsCount) {
|
|
NextScanStep();
|
|
return;
|
|
}
|
|
|
|
if(scanInfo.measurementsCount < 128)
|
|
memset(&rssiHistory[scanInfo.measurementsCount], 0,
|
|
sizeof(rssiHistory) - scanInfo.measurementsCount*sizeof(rssiHistory[0]));
|
|
|
|
redrawScreen = true;
|
|
preventKeypress = false;
|
|
|
|
UpdatePeakInfo();
|
|
if (IsPeakOverLevel()) {
|
|
ToggleRX(true);
|
|
TuneToPeak();
|
|
return;
|
|
}
|
|
|
|
newScanStart = true;
|
|
}
|
|
|
|
static void UpdateStill() {
|
|
Measure();
|
|
redrawScreen = true;
|
|
preventKeypress = false;
|
|
|
|
peak.rssi = scanInfo.rssi;
|
|
AutoTriggerLevel();
|
|
|
|
ToggleRX(IsPeakOverLevel() || monitorMode);
|
|
}
|
|
|
|
static void UpdateListening() {
|
|
preventKeypress = false;
|
|
if (currentState == STILL) {
|
|
listenT = 0;
|
|
}
|
|
if (listenT) {
|
|
listenT--;
|
|
SYSTEM_DelayMs(1);
|
|
return;
|
|
}
|
|
|
|
if (currentState == SPECTRUM) {
|
|
BK4819_WriteRegister(0x43, GetBWRegValueForScan());
|
|
Measure();
|
|
BK4819_WriteRegister(0x43, listenBWRegValues[settings.listenBw]);
|
|
} else {
|
|
Measure();
|
|
}
|
|
|
|
peak.rssi = scanInfo.rssi;
|
|
redrawScreen = true;
|
|
|
|
if (IsPeakOverLevel() || monitorMode) {
|
|
listenT = 1000;
|
|
return;
|
|
}
|
|
|
|
ToggleRX(false);
|
|
ResetScanStats();
|
|
}
|
|
|
|
static void Tick() {
|
|
#ifdef ENABLE_AM_FIX
|
|
if (gNextTimeslice) {
|
|
gNextTimeslice = false;
|
|
if(settings.modulationType == MODULATION_AM && !lockAGC) {
|
|
AM_fix_10ms(vfo); //allow AM_Fix to apply its AGC action
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
if (gNextTimeslice_500ms) {
|
|
gNextTimeslice_500ms = false;
|
|
|
|
// if a lot of steps then it takes long time
|
|
// we don't want to wait for whole scan
|
|
// listening has it's own timer
|
|
if(GetStepsCount()>128 && !isListening) {
|
|
UpdatePeakInfo();
|
|
if (IsPeakOverLevel()) {
|
|
ToggleRX(true);
|
|
TuneToPeak();
|
|
return;
|
|
}
|
|
redrawScreen = true;
|
|
preventKeypress = false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!preventKeypress) {
|
|
HandleUserInput();
|
|
}
|
|
if (newScanStart) {
|
|
InitScan();
|
|
newScanStart = false;
|
|
}
|
|
if (isListening && currentState != FREQ_INPUT) {
|
|
UpdateListening();
|
|
} else {
|
|
if (currentState == SPECTRUM) {
|
|
UpdateScan();
|
|
} else if (currentState == STILL) {
|
|
UpdateStill();
|
|
}
|
|
}
|
|
if (redrawStatus || ++statuslineUpdateTimer > 4096) {
|
|
RenderStatus();
|
|
redrawStatus = false;
|
|
statuslineUpdateTimer = 0;
|
|
}
|
|
if (redrawScreen) {
|
|
Render();
|
|
redrawScreen = false;
|
|
}
|
|
}
|
|
|
|
void APP_RunSpectrum() {
|
|
// TX here coz it always? set to active VFO
|
|
vfo = gEeprom.TX_VFO;
|
|
// set the current frequency in the middle of the display
|
|
#ifdef ENABLE_SCAN_RANGES
|
|
if(gScanRangeStart) {
|
|
currentFreq = initialFreq = gScanRangeStart;
|
|
for(uint8_t i = 0; i < ARRAY_SIZE(scanStepValues); i++) {
|
|
if(scanStepValues[i] >= gTxVfo->StepFrequency) {
|
|
settings.scanStepIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
settings.stepsCount = STEPS_128;
|
|
}
|
|
else
|
|
#endif
|
|
currentFreq = initialFreq = gTxVfo->pRX->Frequency -
|
|
((GetStepsCount() / 2) * GetScanStep());
|
|
|
|
BackupRegisters();
|
|
|
|
isListening = true; // to turn off RX later
|
|
redrawStatus = true;
|
|
redrawScreen = true;
|
|
newScanStart = true;
|
|
|
|
|
|
ToggleRX(true), ToggleRX(false); // hack to prevent noise when squelch off
|
|
RADIO_SetModulation(settings.modulationType = gTxVfo->Modulation);
|
|
|
|
BK4819_SetFilterBandwidth(settings.listenBw = BK4819_FILTER_BW_WIDE, false);
|
|
|
|
RelaunchScan();
|
|
|
|
memset(rssiHistory, 0, sizeof(rssiHistory));
|
|
|
|
isInitialized = true;
|
|
|
|
while (isInitialized) {
|
|
Tick();
|
|
}
|
|
}
|