0
mirror of https://github.com/OneOfEleven/uv-k5-firmware-custom.git synced 2025-04-27 22:01:26 +03:00
2023-10-29 22:33:38 +00:00

1363 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"
#define F_MIN FrequencyBandTable[0].lower
#define F_MAX FrequencyBandTable[ARRAY_SIZE(FrequencyBandTable) - 1].upper
const uint16_t RSSI_MAX_VALUE = 65535;
static uint32_t initialFreq;
static char String[32];
bool isInitialized = false;
bool monitorMode = false;
bool redrawStatus = true;
bool redrawScreen = false;
bool newScanStart = true;
bool preventKeypress = true;
bool isListening = false;
bool isTransmitting = false;
State currentState = SPECTRUM, previousState = SPECTRUM;
PeakInfo peak;
ScanInfo scanInfo;
KeyboardState kbd = {KEY_INVALID, KEY_INVALID, 0};
const char *bwOptions[] = {" 25k", "12.5k", "6.25k"};
const char *modulationTypeOptions[] = {" FM", " AM", "USB"};
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,
};
uint32_t fMeasure = 0;
uint32_t fTx = 0;
uint32_t currentFreq;
uint32_t tempFreq;
uint16_t rssiHistory[128] = {0};
bool blacklist[128] = {false};
static const RegisterSpec afOutRegSpec = {"AF OUT", 0x47, 8, 0xF, 1};
static const RegisterSpec afDacGainRegSpec = {"AF DAC G", 0x48, 0, 0xF, 1};
static const RegisterSpec registerSpecs[] = {
{},
{"LNAs", 0x13, 8, 0b11, 1},
{"LNA", 0x13, 5, 0b111, 1},
{"PGA", 0x13, 0, 0b111, 1},
{"MIX", 0x13, 3, 0b11, 1},
{"DEV", 0x40, 0, 4095, 1},
{"CMP", 0x31, 3, 1, 1},
{"MIC", 0x7D, 0, 0x1F, 1},
};
static uint16_t registersBackup[128];
static const uint8_t registersToBackup[] = {
0x13, 0x30, 0x31, 0x37, 0x3D, 0x40, 0x43, 0x47, 0x48, 0x7D, 0x7E,
};
static MovingAverage mov = {{128}, {}, 255, 128, 0, 0};
static const uint8_t MOV_N = ARRAY_SIZE(mov.buf);
const uint8_t FREQ_INPUT_LENGTH = 10;
uint8_t freqInputIndex = 0;
uint8_t freqInputDotIndex = 0;
key_code_t freqInputArr[10];
char freqInputString[] = "----------"; // XXXX.XXXXX
uint8_t menuState = 0;
uint16_t listenT = 0;
uint16_t batteryUpdateTimer = 0;
bool isMovingInitialized = false;
uint8_t lastStepsCount = 0;
uint8_t CountBits(uint16_t n)
{
uint8_t count = 0;
while (n)
{
count++;
n >>= 1;
}
return count;
}
static uint16_t GetRegMask(RegisterSpec s)
{
return (1 << CountBits(s.maxValue)) - 1;
}
static uint16_t GetRegValue(RegisterSpec s)
{
return (BK4819_ReadRegister(s.num) >> s.offset) & s.maxValue;
}
static void SetRegValue(RegisterSpec s, uint16_t v)
{
uint16_t reg = BK4819_ReadRegister(s.num);
reg &= ~(GetRegMask(s) << s.offset);
BK4819_WriteRegister(s.num, reg | (v << s.offset));
}
static void UpdateRegMenuValue(RegisterSpec s, bool add)
{
uint16_t v = GetRegValue(s);
if (add && v <= s.maxValue - s.inc)
v += s.inc;
else
if (!add && v >= 0 + s.inc)
v -= s.inc;
SetRegValue(s, v);
redrawScreen = true;
}
// 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;
}
void SetState(State state)
{
previousState = currentState;
currentState = state;
redrawScreen = true;
redrawStatus = true;
}
// Radio functions
static void BackupRegisters()
{
for (int i = 0; i < ARRAY_SIZE(registersToBackup); ++i)
{
uint8_t regNum = registersToBackup[i];
registersBackup[regNum] = BK4819_ReadRegister(regNum);
}
}
static void RestoreRegisters()
{
for (int i = 0; i < ARRAY_SIZE(registersToBackup); ++i)
{
uint8_t regNum = registersToBackup[i];
BK4819_WriteRegister(regNum, registersBackup[regNum]);
}
}
static void SetModulation(ModulationType type)
{
// restore only registers, which we affect here fully
BK4819_WriteRegister(0x37, registersBackup[0x37]);
BK4819_WriteRegister(0x3D, registersBackup[0x3D]);
BK4819_WriteRegister(0x48, registersBackup[0x48]);
SetRegValue(afOutRegSpec, modTypeReg47Values[type]);
if (type == MOD_USB)
{
BK4819_WriteRegister(0x37, 0b0001011000001111);
BK4819_WriteRegister(0x3D, 0b0010101101000101);
BK4819_WriteRegister(0x48, 0b0000001110101000);
}
if (type == MOD_AM)
SetRegValue(afDacGainRegSpec, 0xE);
}
static void SetF(uint32_t f) {
fMeasure = f;
BK4819_set_rf_frequency(fMeasure);
BK4819_set_rf_filter_path(fMeasure);
uint16_t reg = BK4819_ReadRegister(BK4819_REG_30);
BK4819_WriteRegister(BK4819_REG_30, 0);
BK4819_WriteRegister(BK4819_REG_30, reg);
}
static void SetTxF(uint32_t f) {
fTx = f;
BK4819_set_rf_frequency(f);
BK4819_set_rf_filter_path(f);
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_1_0kHz; }
uint8_t GetStepsCount() { return 128 >> settings.stepsCount; }
uint16_t GetScanStep() { return scanStepValues[settings.scanStepIndex]; }
uint32_t GetBW() { return GetStepsCount() * GetScanStep(); }
uint32_t GetFStart() {
return IsCenterMode() ? currentFreq - (GetBW() >> 1) : currentFreq;
}
uint32_t GetFEnd() { return currentFreq + GetBW(); }
static void MovingCp(uint16_t *dst, uint16_t *src) {
memcpy(dst, src, GetStepsCount() * sizeof(uint16_t));
}
static void ResetMoving() {
for (int i = 0; i < MOV_N; ++i) {
MovingCp(mov.buf[i], rssiHistory);
}
}
static void MoveHistory() {
const uint8_t XN = GetStepsCount();
uint32_t midSum = 0;
mov.min = RSSI_MAX_VALUE;
mov.max = 0;
if (lastStepsCount != XN) {
ResetMoving();
lastStepsCount = XN;
}
for (int i = MOV_N - 1; i > 0; --i) {
MovingCp(mov.buf[i], mov.buf[i - 1]);
}
MovingCp(mov.buf[0], rssiHistory);
uint8_t skipped = 0;
for (int x = 0; x < XN; ++x) {
if (blacklist[x]) {
skipped++;
continue;
}
uint32_t sum = 0;
for (int i = 0; i < MOV_N; ++i) {
sum += mov.buf[i][x];
}
uint16_t pointV = mov.mean[x] = sum / MOV_N;
midSum += pointV;
if (pointV > mov.max) {
mov.max = pointV;
}
if (pointV < mov.min) {
mov.min = pointV;
}
}
if (skipped == XN) {
return;
}
mov.mid = midSum / (XN - skipped);
}
static void TuneToPeak() {
scanInfo.f = peak.f;
scanInfo.rssi = peak.rssi;
scanInfo.i = peak.i;
SetF(scanInfo.f);
}
uint8_t GetBWRegValueForScan() {
return scanStepBWRegValues[settings.scanStepIndex == S_STEP_100_0kHz ? 11
: 0];
}
uint8_t GetBWRegValueForListen() {
return listenBWRegValues[settings.listenBw];
}
static void ResetRSSI() {
uint32_t Reg = BK4819_ReadRegister(BK4819_REG_30);
Reg &= ~1;
BK4819_WriteRegister(BK4819_REG_30, Reg);
Reg |= 1;
BK4819_WriteRegister(BK4819_REG_30, Reg);
}
uint16_t GetRssi() {
if (currentState == SPECTRUM) {
ResetRSSI();
SYSTICK_DelayUs(3200);
}
return BK4819_GetRSSI();
}
uint32_t GetOffsetedF(uint32_t f) {
switch (gCurrentVfo->FREQUENCY_DEVIATION_SETTING) {
case FREQUENCY_DEVIATION_OFF:
break;
case FREQUENCY_DEVIATION_ADD:
f += gCurrentVfo->FREQUENCY_OF_DEVIATION;
break;
case FREQUENCY_DEVIATION_SUB:
f -= gCurrentVfo->FREQUENCY_OF_DEVIATION;
break;
}
return Clamp(f, FrequencyBandTable[0].lower,
FrequencyBandTable[ARRAY_SIZE(FrequencyBandTable) - 1].upper);
}
bool IsTXAllowed() { return g_setting_ALL_TX != 2; }
static void ToggleAudio(bool on) {
if (on)
GPIO_SetBit(&GPIOC->DATA, GPIOC_PIN_SPEAKER);
else {
GPIO_ClearBit(&GPIOC->DATA, GPIOC_PIN_SPEAKER);
}
}
static void ToggleTX(bool);
static void ToggleRX(bool);
static void ToggleRX(bool on) {
/* if (isListening == on) {
return;
} */
isListening = on;
if (on) {
ToggleTX(false);
}
BK4819_set_GPIO_pin(BK4819_GPIO6_PIN2_GREEN, on);
BK4819_RX_TurnOn();
ToggleAudio(on);
BK4819_ToggleAFDAC(on);
BK4819_ToggleAFBit(on);
if (on) {
listenT = 1000;
BK4819_WriteRegister(0x43, GetBWRegValueForListen());
} else {
BK4819_WriteRegister(0x43, GetBWRegValueForScan());
}
}
uint16_t registersVault[128] = {0};
static void RegBackupSet(uint8_t num, uint16_t value) {
registersVault[num] = BK4819_ReadRegister(num);
BK4819_WriteRegister(num, value);
}
static void RegRestore(uint8_t num) {
BK4819_WriteRegister(num, registersVault[num]);
}
static void ToggleTX(bool on) {
if (isTransmitting == on) {
return;
}
isTransmitting = on;
if (on) {
ToggleRX(false);
}
BK4819_set_GPIO_pin(BK4819_GPIO5_PIN1_RED, on);
if (on) {
ToggleAudio(false);
SetTxF(GetOffsetedF(fMeasure));
RegBackupSet(BK4819_REG_47, 0x6040);
RegBackupSet(BK4819_REG_7E, 0x302E);
RegBackupSet(BK4819_REG_50, 0x3B20);
RegBackupSet(BK4819_REG_37, 0x1D0F);
RegBackupSet(BK4819_REG_52, 0x028F);
RegBackupSet(BK4819_REG_30, 0x0000);
BK4819_WriteRegister(BK4819_REG_30, 0xC1FE);
RegBackupSet(BK4819_REG_51, 0x0000);
BK4819_SetupPowerAmplifier(gCurrentVfo->TXP_CalculatedSetting,
gCurrentVfo->p_tx->Frequency);
}
else
{
RADIO_tx_eot();
RADIO_EnableCxCSS();
BK4819_SetupPowerAmplifier(0, 0);
RegRestore(BK4819_REG_51);
BK4819_WriteRegister(BK4819_REG_30, 0);
RegRestore(BK4819_REG_30);
RegRestore(BK4819_REG_52);
RegRestore(BK4819_REG_37);
RegRestore(BK4819_REG_50);
RegRestore(BK4819_REG_7E);
RegRestore(BK4819_REG_47);
SetF(fMeasure);
}
BK4819_set_GPIO_pin(BK4819_GPIO0_PIN28_RX_ENABLE, !on);
BK4819_set_GPIO_pin(BK4819_GPIO1_PIN29_PA_ENABLE, on);
}
// 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 (blacklist[i])
blacklist[i] = false;
}
}
static void RelaunchScan() {
InitScan();
ResetPeak();
lastStepsCount = 0;
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;
}
}
static void AutoTriggerLevel() {
if (settings.rssiTriggerLevel == RSSI_MAX_VALUE) {
settings.rssiTriggerLevel = Clamp(scanInfo.rssiMax + 4, 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 Measure() { rssiHistory[scanInfo.i] = scanInfo.rssi = GetRssi(); }
// Update things by keypress
static void UpdateRssiTriggerLevel(bool inc) {
if (inc)
settings.rssiTriggerLevel += 2;
else
settings.rssiTriggerLevel -= 2;
redrawScreen = true;
SYSTEM_DelayMs(10);
}
static void ApplyPreset(FreqPreset p) {
currentFreq = p.fStart;
settings.scanStepIndex = p.stepSizeIndex;
settings.listenBw = p.listenBW;
settings.modulationType = p.modulationType;
settings.stepsCount = p.stepsCountIndex;
SetModulation(settings.modulationType);
RelaunchScan();
ResetBlacklist();
redrawScreen = true;
}
static void SelectNearestPreset(bool inc) {
FreqPreset p;
const uint8_t SZ = ARRAY_SIZE(freqPresets);
if (inc) {
for (int i = 0; i < SZ; ++i) {
p = freqPresets[i];
if (currentFreq < p.fStart) {
ApplyPreset(p);
return;
}
}
} else {
for (int i = SZ - 1; i >= 0; --i) {
p = freqPresets[i];
if (currentFreq > p.fEnd) {
ApplyPreset(p);
return;
}
}
}
ApplyPreset(p);
}
static void UpdateScanStep(bool inc) {
if (inc && settings.scanStepIndex < S_STEP_100_0kHz) {
settings.scanStepIndex++;
} else if (!inc && settings.scanStepIndex > 0) {
settings.scanStepIndex--;
} else {
return;
}
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 < MOD_USB) {
settings.modulationType++;
} else {
settings.modulationType = MOD_FM;
}
SetModulation(settings.modulationType);
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) {
GPIO_SetBit(&GPIOB->DATA, GPIOB_PIN_BACKLIGHT);
} else {
GPIO_ClearBit(&GPIOB->DATA, GPIOB_PIN_BACKLIGHT);
}
}
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--;
} 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 : '.';
} else {
freqInputString[i] = '-';
}
}
uint32_t base = 100000; // 1MHz in BK units
for (int i = dotIndex - 1; i >= 0; --i) {
tempFreq += freqInputArr[i] * base;
base *= 10;
}
base = 10000; // 0.1MHz in BK units
if (dotIndex < freqInputIndex) {
for (int i = dotIndex + 1; i < freqInputIndex; ++i) {
tempFreq += freqInputArr[i] * base;
base /= 10;
}
}
redrawScreen = true;
}
static void Blacklist() {
blacklist[peak.i] = true;
ResetPeak();
ToggleRX(false);
newScanStart = true;
}
// Draw things
static uint8_t Rssi2Y(uint16_t rssi) {
return DrawingEndY - ConvertDomain(rssi, mov.min - 2,
mov.max + 30 + (mov.max - mov.min) / 3, 0,
DrawingEndY);
}
static void DrawSpectrum() {
for (uint8_t x = 0; x < 128; ++x) {
uint8_t i = x >> settings.stepsCount;
if (blacklist[i]) {
continue;
}
uint16_t rssi = rssiHistory[i];
DrawHLine(Rssi2Y(rssi), DrawingEndY, x, true);
}
}
static void UpdateBatteryInfo() {
for (int i = 0; i < 4; i++) {
BOARD_ADC_GetBatteryInfo(&g_battery_voltages[i], &g_usb_current);
}
uint16_t voltage = Mid(g_battery_voltages, ARRAY_SIZE(g_battery_voltages));
g_battery_display_level = 0;
for (int i = ARRAY_SIZE(g_battery_calibration) - 1; i >= 0; --i) {
if (g_battery_calibration[i] < voltage) {
g_battery_display_level = i + 1;
break;
}
}
}
static void DrawStatus() {
g_status_line[127] = 0b01111110;
for (int i = 126; i >= 116; i--) {
g_status_line[i] = 0b01000010;
}
uint8_t v = g_battery_display_level;
v <<= 1;
for (int i = 125; i >= 116; i--) {
if (126 - i <= v) {
g_status_line[i + 2] = 0b01111110;
}
}
g_status_line[117] = 0b01111110;
g_status_line[116] = 0b00011000;
}
static void DrawF(uint32_t f) {
sprintf(String, "%u.%05u", f / 100000, f % 100000);
if (currentState == STILL && kbd.current == KEY_PTT) {
if (g_battery_display_level == 6) {
sprintf(String, "VOLTAGE HIGH");
} else if (!IsTXAllowed()) {
sprintf(String, "DISABLED");
} else {
f = GetOffsetedF(f);
sprintf(String, "TX %u.%05u", f / 100000, f % 100000);
}
}
UI_PrintStringSmall(String, 8, 127, 0);
sprintf(String, "%s", modulationTypeOptions[settings.modulationType]);
UI_PrintStringSmallest(String, 116, 1, false, true);
sprintf(String, "%s", bwOptions[settings.listenBw]);
UI_PrintStringSmallest(String, 108, 7, false, true);
}
static void DrawNums() {
if (currentState == SPECTRUM) {
sprintf(String, "%ux", GetStepsCount());
UI_PrintStringSmallest(String, 0, 1, false, true);
sprintf(String, "%u.%02uk", GetScanStep() / 100, GetScanStep() % 100);
UI_PrintStringSmallest(String, 0, 7, false, true);
}
if (IsCenterMode()) {
sprintf(String, "%u.%05u \xB1%u.%02uk", currentFreq / 100000,
currentFreq % 100000, settings.frequencyChangeStep / 100,
settings.frequencyChangeStep % 100);
UI_PrintStringSmallest(String, 36, 49, false, true);
} else {
sprintf(String, "%u.%05u", GetFStart() / 100000, GetFStart() % 100000);
UI_PrintStringSmallest(String, 0, 49, false, true);
sprintf(String, "\xB1%u.%02uk", settings.frequencyChangeStep / 100,
settings.frequencyChangeStep % 100);
UI_PrintStringSmallest(String, 48, 49, false, true);
sprintf(String, "%u.%05u", GetFEnd() / 100000, GetFEnd() % 100000);
UI_PrintStringSmallest(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() % 100000;
uint32_t step = GetScanStep();
for (uint8_t i = 0; i < 128; i += (1 << settings.stepsCount), f += step) {
uint8_t barValue = 0b00000001;
(f % 10000) < step && (barValue |= 0b00000010);
(f % 50000) < step && (barValue |= 0b00000100);
(f % 100000) < step && (barValue |= 0b00011000);
g_frame_buffer[5][i] |= barValue;
}
// center
/* if (IsCenterMode()) {
g_frame_buffer[5][62] = 0x80;
g_frame_buffer[5][63] = 0x80;
g_frame_buffer[5][64] = 0xff;
g_frame_buffer[5][65] = 0x80;
g_frame_buffer[5][66] = 0x80;
} else {
g_frame_buffer[5][0] = 0xff;
g_frame_buffer[5][1] = 0x80;
g_frame_buffer[5][2] = 0x80;
g_frame_buffer[5][3] = 0x80;
g_frame_buffer[5][124] = 0x80;
g_frame_buffer[5][125] = 0x80;
g_frame_buffer[5][126] = 0x80;
g_frame_buffer[5][127] = 0xff;
} */
}
static void DrawArrow(uint8_t x) {
for (signed i = -2; i <= 2; ++i) {
signed v = x + i;
uint8_t a = i > 0 ? i : -i;
if (!(v & 128)) {
g_frame_buffer[5][v] |= (0b01111000 << a) & 0b01111000;
}
}
}
static void DeInitSpectrum() {
SetF(initialFreq);
ToggleRX(false);
RestoreRegisters();
isInitialized = false;
}
static void OnKeyDown(uint8_t key) {
switch (key) {
case KEY_3:
SelectNearestPreset(true);
break;
case KEY_9:
SelectNearestPreset(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:
UpdateCurrentFreq(true);
break;
case KEY_DOWN:
UpdateCurrentFreq(false);
break;
case KEY_SIDE1:
Blacklist();
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_4:
ToggleStepsCount();
break;
case KEY_SIDE2:
ToggleBacklight();
break;
case KEY_PTT:
SetState(STILL);
TuneToPeak();
settings.rssiTriggerLevel = 120;
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:
break;
case KEY_9:
break;
case KEY_UP:
if (menuState) {
UpdateRegMenuValue(registerSpecs[menuState], true);
break;
}
UpdateCurrentFreqStill(true);
break;
case KEY_DOWN:
if (menuState) {
UpdateRegMenuValue(registerSpecs[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:
// start transmit
UpdateBatteryInfo();
if (g_battery_display_level != 6 && IsTXAllowed()) {
ToggleTX(true);
}
redrawScreen = true;
break;
case KEY_MENU:
if (menuState == ARRAY_SIZE(registerSpecs) - 1) {
menuState = 1;
} else {
menuState++;
}
redrawScreen = true;
break;
case KEY_EXIT:
if (menuState) {
menuState = 0;
break;
}
SetState(SPECTRUM);
monitorMode = false;
RelaunchScan();
break;
default:
break;
}
}
static void OnKeysReleased() {
if (isTransmitting) {
ToggleTX(false);
}
}
static void RenderFreqInput() {
UI_PrintString(freqInputString, 2, 127, 0, 8, true);
}
static void RenderStatus() {
memset(g_status_line, 0, sizeof(g_status_line));
DrawStatus();
ST7565_BlitStatusLine();
}
static void RenderSpectrum() {
DrawTicks();
DrawArrow(peak.i << settings.stepsCount);
DrawSpectrum();
DrawRssiTriggerLevel();
DrawF(peak.f);
DrawNums();
}
static void RenderStill() {
DrawF(fMeasure);
const uint8_t METER_PAD_LEFT = 3;
for (int i = 0; i < 121; i++) {
if (i % 10 == 0) {
g_frame_buffer[2][i + METER_PAD_LEFT] = 0b11000000;
} else {
g_frame_buffer[2][i + METER_PAD_LEFT] = 0b01000000;
}
}
uint8_t x = Rssi2PX(scanInfo.rssi, 0, 121);
for (int i = 0; i < x; ++i) {
if (i % 5 && i / 5 < x / 5) {
g_frame_buffer[2][i + METER_PAD_LEFT] |= 0b00011100;
}
}
int dbm = Rssi2DBm(scanInfo.rssi);
uint8_t s = DBm2S(dbm);
if (s < 10) {
sprintf(String, "S%u", s);
} else {
sprintf(String, "S9+%u0", s - 9);
}
UI_PrintStringSmallest(String, 4, 10, false, true);
sprintf(String, "%d dBm", dbm);
UI_PrintStringSmallest(String, 32, 10, false, true);
if (isTransmitting) {
uint8_t afDB = BK4819_ReadRegister(0x6F) & 0b1111111;
uint8_t afPX = ConvertDomain(afDB, 26, 194, 0, 121);
for (int i = 0; i < afPX; ++i) {
g_frame_buffer[3][i + METER_PAD_LEFT] |= 0b00000011;
}
}
if (!monitorMode) {
uint8_t x = Rssi2PX(settings.rssiTriggerLevel, 0, 121);
g_frame_buffer[2][METER_PAD_LEFT + x - 1] |= 0b01000001;
g_frame_buffer[2][METER_PAD_LEFT + x] = 0b01111111;
g_frame_buffer[2][METER_PAD_LEFT + x + 1] |= 0b01000001;
}
const uint8_t PAD_LEFT = 4;
const uint8_t CELL_WIDTH = 30;
uint8_t offset = PAD_LEFT;
uint8_t row = 3;
for (int i = 0, idx = 1; idx <= 7; ++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) {
g_frame_buffer[row][j + offset] = 0xFF;
g_frame_buffer[row + 1][j + offset] = 0xFF;
}
}
RegisterSpec s = registerSpecs[idx];
sprintf(String, "%s", s.name);
UI_PrintStringSmallest(String, offset + 2, row * 8 + 2, false,
menuState != idx);
sprintf(String, "%u", GetRegValue(s));
UI_PrintStringSmallest(String, offset + 2, (row + 1) * 8 + 1, false,
menuState != idx);
}
}
static void Render() {
memset(g_frame_buffer, 0, sizeof(g_frame_buffer));
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.counter = 0;
OnKeysReleased();
return true;
}
if (kbd.current == kbd.prev && kbd.counter <= 20) {
kbd.counter++;
SYSTEM_DelayMs(10);
}
if (kbd.counter == 4 || kbd.counter > 20) {
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 (blacklist[scanInfo.i]) {
return;
}
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;
}
MoveHistory();
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 (!isListening) {
ToggleRX(true);
}
if (currentState == STILL) {
listenT = 0;
}
if (listenT) {
listenT--;
SYSTEM_DelayMs(1);
return;
}
if (currentState == SPECTRUM) {
BK4819_WriteRegister(0x43, GetBWRegValueForScan());
Measure();
BK4819_WriteRegister(0x43, GetBWRegValueForListen());
} else {
Measure();
}
peak.rssi = scanInfo.rssi;
redrawScreen = true;
if (IsPeakOverLevel() || monitorMode) {
listenT = 1000;
return;
}
ToggleRX(false);
newScanStart = true;
}
static void UpdateTransmitting() {}
static void Tick() {
if (!preventKeypress) {
HandleUserInput();
}
if (newScanStart) {
InitScan();
newScanStart = false;
}
if (isTransmitting) {
UpdateTransmitting();
} else if (isListening && currentState != FREQ_INPUT) {
UpdateListening();
} else {
if (currentState == SPECTRUM) {
UpdateScan();
} else if (currentState == STILL) {
UpdateStill();
}
}
if (++batteryUpdateTimer > 4096) {
batteryUpdateTimer = 0;
UpdateBatteryInfo();
redrawStatus = true;
}
if (redrawStatus) {
RenderStatus();
redrawStatus = false;
}
if (redrawScreen) {
Render();
redrawScreen = false;
}
}
static void AutomaticPresetChoose(uint32_t f) {
for (int i = 0; i < ARRAY_SIZE(freqPresets); ++i) {
FreqPreset p = freqPresets[i];
if (f >= p.fStart && f <= freqPresets[i].fEnd) {
ApplyPreset(p);
}
}
}
void APP_RunSpectrum() {
BackupRegisters();
// TX here coz it always? set to active VFO
VFO_Info_t vfo = g_eeprom.vfo_info[g_eeprom.TX_CHANNEL];
initialFreq = vfo.p_rx->Frequency;
currentFreq = initialFreq;
settings.scanStepIndex = gStepSettingToIndex[vfo.STEP_SETTING];
settings.listenBw = vfo.CHANNEL_BANDWIDTH == BANDWIDTH_WIDE
? BANDWIDTH_WIDE
: BANDWIDTH_NARROW;
settings.modulationType = vfo.IsAM ? MOD_AM : MOD_FM;
AutomaticPresetChoose(currentFreq);
redrawStatus = true;
redrawScreen = false; // we will wait until scan done
newScanStart = true;
ToggleRX(true), ToggleRX(false); // hack to prevent noise when squelch off
SetModulation(settings.modulationType);
RelaunchScan();
for (int i = 0; i < 128; ++i) {
rssiHistory[i] = 0;
}
isInitialized = true;
while (isInitialized) {
Tick();
}
}