2023-07-15 08:49:35 +00:00
|
|
|
#pragma once
|
2023-07-23 14:23:53 +00:00
|
|
|
#include "radio.hpp"
|
2023-07-15 08:49:35 +00:00
|
|
|
#include "system.hpp"
|
|
|
|
#include "uv_k5_display.hpp"
|
|
|
|
|
|
|
|
typedef unsigned char u8;
|
2023-07-16 08:05:47 +00:00
|
|
|
typedef signed short i16;
|
2023-07-15 08:49:35 +00:00
|
|
|
typedef unsigned short u16;
|
2023-07-19 18:31:33 +00:00
|
|
|
typedef signed int i32;
|
2023-07-15 08:49:35 +00:00
|
|
|
typedef unsigned int u32;
|
2023-07-16 08:05:47 +00:00
|
|
|
typedef signed long long i64;
|
|
|
|
typedef unsigned long long u64;
|
2023-07-15 08:49:35 +00:00
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
template <const System::TOrgFunctions &Fw, const System::TOrgData &FwData,
|
|
|
|
Radio::CBK4819<Fw> &RadioDriver>
|
2023-07-15 08:49:35 +00:00
|
|
|
class CSpectrum {
|
|
|
|
public:
|
2023-07-21 10:50:18 +00:00
|
|
|
static constexpr auto ExitKey = 13;
|
2023-07-15 08:49:35 +00:00
|
|
|
static constexpr auto DrawingEndY = 42;
|
2023-07-19 18:31:33 +00:00
|
|
|
static constexpr auto BarPos = 5 * 128;
|
|
|
|
|
|
|
|
u8 rssiHistory[128] = {};
|
2023-07-20 20:13:29 +00:00
|
|
|
u8 measurementsCount = 32;
|
2023-07-23 14:23:53 +00:00
|
|
|
u8 rssiMin = 255;
|
2023-07-15 08:49:35 +00:00
|
|
|
u8 highestPeakX = 0;
|
|
|
|
u8 highestPeakT = 0;
|
|
|
|
u8 highestPeakRssi = 0;
|
|
|
|
u32 highestPeakF = 0;
|
2023-07-23 14:23:53 +00:00
|
|
|
u32 FStart;
|
2023-07-15 20:15:28 +00:00
|
|
|
|
2023-07-15 08:49:35 +00:00
|
|
|
CSpectrum()
|
|
|
|
: DisplayBuff(FwData.pDisplayBuffer), FontSmallNr(FwData.pSmallDigs),
|
2023-07-21 10:50:18 +00:00
|
|
|
Display(DisplayBuff), scanDelay(800), sampleZoom(2), scanStep(25_KHz),
|
2023-07-23 14:23:53 +00:00
|
|
|
frequencyChangeStep(100_KHz), rssiTriggerLevel(65) {
|
2023-07-15 08:49:35 +00:00
|
|
|
Display.SetFont(&FontSmallNr);
|
|
|
|
};
|
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
inline bool ListenPeak() {
|
|
|
|
if (highestPeakRssi < rssiTriggerLevel) {
|
|
|
|
return false;
|
|
|
|
}
|
2023-07-15 10:55:12 +00:00
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
// measure peak for this moment
|
|
|
|
highestPeakRssi = GetRssi(highestPeakF); // also sets freq for us
|
|
|
|
rssiHistory[highestPeakX >> sampleZoom] = highestPeakRssi;
|
2023-07-18 07:30:15 +00:00
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
if (highestPeakRssi >= rssiTriggerLevel) {
|
2023-07-23 15:33:06 +00:00
|
|
|
RadioDriver.ToggleAFDAC(true);
|
2023-07-23 14:23:53 +00:00
|
|
|
Listen(1000000);
|
|
|
|
return true;
|
2023-07-18 07:30:15 +00:00
|
|
|
}
|
2023-07-16 08:05:47 +00:00
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
return false;
|
|
|
|
}
|
2023-07-18 07:30:15 +00:00
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
inline void Scan() {
|
|
|
|
u8 rssi = 0, rssiMax = 0;
|
|
|
|
u8 iPeak = 0;
|
|
|
|
u32 fPeak = currentFreq, fMeasure = FStart;
|
2023-07-18 07:30:15 +00:00
|
|
|
|
|
|
|
rssiMin = 255;
|
2023-07-23 15:33:06 +00:00
|
|
|
RadioDriver.ToggleAFDAC(false);
|
2023-07-18 07:30:15 +00:00
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
for (u8 i = 0; i < measurementsCount; ++i, fMeasure += scanStep) {
|
2023-07-23 14:23:53 +00:00
|
|
|
rssi = rssiHistory[i] = GetRssi(fMeasure);
|
2023-07-19 18:31:33 +00:00
|
|
|
if (rssi < rssiMin) {
|
|
|
|
rssiMin = rssi;
|
|
|
|
}
|
2023-07-18 07:30:15 +00:00
|
|
|
if (rssi > rssiMax) {
|
|
|
|
rssiMax = rssi;
|
|
|
|
fPeak = fMeasure;
|
2023-07-23 14:23:53 +00:00
|
|
|
iPeak = i;
|
2023-07-15 08:49:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
++highestPeakT;
|
2023-07-23 14:23:53 +00:00
|
|
|
if (rssiMax > highestPeakRssi || highestPeakT >= (8 << sampleZoom)) {
|
2023-07-15 08:49:35 +00:00
|
|
|
highestPeakT = 0;
|
|
|
|
highestPeakRssi = rssiMax;
|
2023-07-23 14:23:53 +00:00
|
|
|
highestPeakX = iPeak << sampleZoom;
|
2023-07-15 08:49:35 +00:00
|
|
|
highestPeakF = fPeak;
|
|
|
|
}
|
2023-07-15 10:55:12 +00:00
|
|
|
}
|
2023-07-15 08:49:35 +00:00
|
|
|
|
2023-07-15 10:55:12 +00:00
|
|
|
inline void DrawSpectrum() {
|
2023-07-19 18:31:33 +00:00
|
|
|
for (u8 x = 0; x < 128; ++x) {
|
|
|
|
Display.DrawHLine(Rssi2Y(rssiHistory[x >> sampleZoom]), DrawingEndY, x);
|
2023-07-15 08:49:35 +00:00
|
|
|
}
|
2023-07-18 07:30:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
inline void DrawNums() {
|
2023-07-23 14:23:53 +00:00
|
|
|
Display.SetCoursorXY(0, 0);
|
2023-07-15 08:49:35 +00:00
|
|
|
Display.PrintFixedDigitsNumber2(scanDelay, 0);
|
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
Display.SetCoursorXY(51, 0);
|
2023-07-19 18:31:33 +00:00
|
|
|
Display.PrintFixedDigitsNumber2(scanStep << (7 - sampleZoom));
|
2023-07-15 08:49:35 +00:00
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
Display.SetCoursorXY(58, 8);
|
2023-07-19 18:31:33 +00:00
|
|
|
Display.PrintFixedDigitsNumber2(scanStep);
|
2023-07-15 20:15:28 +00:00
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
Display.SetCoursorXY(107, 8);
|
2023-07-15 20:15:28 +00:00
|
|
|
Display.PrintFixedDigitsNumber2(highestPeakRssi, 0);
|
2023-07-16 08:05:47 +00:00
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
Display.SetCoursorXY(86, 0);
|
2023-07-19 18:31:33 +00:00
|
|
|
Display.PrintFixedDigitsNumber2(highestPeakF);
|
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
Display.SetCoursorXY(44, 48);
|
2023-07-19 18:31:33 +00:00
|
|
|
Display.PrintFixedDigitsNumber2(currentFreq);
|
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
Display.SetCoursorXY(100, 48);
|
|
|
|
Display.PrintFixedDigitsNumber2(frequencyChangeStep);
|
|
|
|
|
|
|
|
Display.SetCoursorXY(0, 8);
|
2023-07-16 08:05:47 +00:00
|
|
|
Display.PrintFixedDigitsNumber2(rssiTriggerLevel, 0);
|
2023-07-15 20:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
inline void DrawRssiTriggerLevel() {
|
|
|
|
Display.DrawLine(0, 127, Rssi2Y(rssiTriggerLevel));
|
2023-07-15 08:49:35 +00:00
|
|
|
}
|
|
|
|
|
2023-07-15 10:55:12 +00:00
|
|
|
inline void DrawTicks() {
|
|
|
|
u32 f = modulo(FStart, 1_MHz);
|
2023-07-19 18:31:33 +00:00
|
|
|
u32 step = scanStep >> sampleZoom;
|
|
|
|
for (u8 i = 0; i < 128; ++i, f += step) {
|
2023-07-15 10:55:12 +00:00
|
|
|
u8 barValue = 0b00001000;
|
2023-07-19 18:31:33 +00:00
|
|
|
modulo(f, 100_KHz) < step && (barValue |= 0b00010000);
|
|
|
|
modulo(f, 500_KHz) < step && (barValue |= 0b00100000);
|
|
|
|
modulo(f, 1_MHz) < step && (barValue |= 0b11000000);
|
2023-07-15 10:55:12 +00:00
|
|
|
|
|
|
|
*(FwData.pDisplayBuffer + BarPos + i) |= barValue;
|
2023-07-15 08:49:35 +00:00
|
|
|
}
|
|
|
|
|
2023-07-15 10:55:12 +00:00
|
|
|
// center
|
|
|
|
*(FwData.pDisplayBuffer + BarPos + 64) |= 0b10101010;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void DrawArrow(u8 x) {
|
|
|
|
u8 *peakPos = FwData.pDisplayBuffer + BarPos + x;
|
|
|
|
x > 1 && (*(peakPos - 2) |= 0b01000000);
|
|
|
|
x > 0 && (*(peakPos - 1) |= 0b01100000);
|
|
|
|
(*(peakPos) |= 0b01110000);
|
|
|
|
x < 127 && (*(peakPos + 1) |= 0b01100000);
|
|
|
|
x < 128 && (*(peakPos + 2) |= 0b01000000);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HandleUserInput() {
|
2023-07-23 14:23:53 +00:00
|
|
|
switch (lastButtonPressed) {
|
2023-07-15 08:49:35 +00:00
|
|
|
case 1:
|
2023-07-20 20:13:29 +00:00
|
|
|
UpdateScanDelay(200);
|
2023-07-15 08:49:35 +00:00
|
|
|
break;
|
|
|
|
case 7:
|
2023-07-20 20:13:29 +00:00
|
|
|
UpdateScanDelay(-200);
|
2023-07-15 08:49:35 +00:00
|
|
|
break;
|
2023-07-15 20:15:28 +00:00
|
|
|
case 2:
|
2023-07-19 18:31:33 +00:00
|
|
|
UpdateSampleZoom(1);
|
2023-07-15 20:15:28 +00:00
|
|
|
break;
|
|
|
|
case 8:
|
2023-07-19 18:31:33 +00:00
|
|
|
UpdateSampleZoom(-1);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
UpdateRssiTriggerLevel(5);
|
|
|
|
break;
|
|
|
|
case 9:
|
2023-07-15 20:15:28 +00:00
|
|
|
UpdateRssiTriggerLevel(-5);
|
|
|
|
break;
|
2023-07-19 18:31:33 +00:00
|
|
|
case 4:
|
|
|
|
UpdateScanStep(-1);
|
2023-07-23 15:33:06 +00:00
|
|
|
UpdateSampleZoom(1);
|
2023-07-19 18:31:33 +00:00
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
UpdateScanStep(1);
|
2023-07-23 15:33:06 +00:00
|
|
|
UpdateSampleZoom(-1);
|
2023-07-15 20:15:28 +00:00
|
|
|
break;
|
2023-07-15 08:49:35 +00:00
|
|
|
case 11: // up
|
2023-07-23 14:23:53 +00:00
|
|
|
UpdateCurrentFreq(frequencyChangeStep);
|
2023-07-15 08:49:35 +00:00
|
|
|
break;
|
|
|
|
case 12: // down
|
2023-07-23 14:23:53 +00:00
|
|
|
UpdateCurrentFreq(-frequencyChangeStep);
|
|
|
|
break;
|
|
|
|
case 14:
|
|
|
|
UpdateFreqChangeStep(100_KHz);
|
|
|
|
break;
|
|
|
|
case 15:
|
|
|
|
UpdateFreqChangeStep(-100_KHz);
|
2023-07-15 08:49:35 +00:00
|
|
|
break;
|
2023-07-23 16:59:12 +00:00
|
|
|
case 5:
|
|
|
|
ToggleBacklight();
|
2023-07-15 08:49:35 +00:00
|
|
|
default:
|
|
|
|
isUserInput = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-15 10:55:12 +00:00
|
|
|
void Render() {
|
2023-07-21 10:50:18 +00:00
|
|
|
DisplayBuff.ClearAll();
|
2023-07-15 10:55:12 +00:00
|
|
|
DrawTicks();
|
|
|
|
DrawArrow(highestPeakX);
|
|
|
|
DrawSpectrum();
|
2023-07-15 20:15:28 +00:00
|
|
|
DrawRssiTriggerLevel();
|
2023-07-18 07:30:15 +00:00
|
|
|
DrawNums();
|
2023-07-21 10:50:18 +00:00
|
|
|
Fw.FlushFramebufferToScreen();
|
2023-07-15 10:55:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Update() {
|
|
|
|
if (bDisplayCleared) {
|
2023-07-23 14:23:53 +00:00
|
|
|
currentFreq = RadioDriver.GetFrequency();
|
2023-07-15 10:55:12 +00:00
|
|
|
OnUserInput();
|
|
|
|
u16OldAfSettings = Fw.BK4819Read(0x47);
|
|
|
|
Fw.BK4819Write(0x47, 0); // mute AF during scan
|
|
|
|
}
|
|
|
|
bDisplayCleared = false;
|
|
|
|
|
|
|
|
HandleUserInput();
|
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
if (!ListenPeak())
|
|
|
|
Scan();
|
2023-07-15 10:55:12 +00:00
|
|
|
}
|
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
void UpdateRssiTriggerLevel(i32 diff) {
|
|
|
|
rssiTriggerLevel = clamp(rssiTriggerLevel + diff, 10, 255);
|
2023-07-15 20:15:28 +00:00
|
|
|
OnUserInput();
|
|
|
|
}
|
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
void UpdateScanDelay(i32 diff) {
|
2023-07-20 20:13:29 +00:00
|
|
|
scanDelay = clamp(scanDelay + diff, 800, 3200);
|
2023-07-19 18:31:33 +00:00
|
|
|
OnUserInput();
|
|
|
|
}
|
|
|
|
|
|
|
|
void UpdateSampleZoom(i32 diff) {
|
2023-07-20 20:13:29 +00:00
|
|
|
sampleZoom = clamp(sampleZoom - diff, 0, 5);
|
2023-07-19 18:31:33 +00:00
|
|
|
measurementsCount = 1 << (7 - sampleZoom);
|
2023-07-15 10:55:12 +00:00
|
|
|
OnUserInput();
|
|
|
|
}
|
|
|
|
|
2023-07-16 08:05:47 +00:00
|
|
|
void UpdateCurrentFreq(i64 diff) {
|
2023-07-19 18:31:33 +00:00
|
|
|
currentFreq = clamp(currentFreq + diff, 18_MHz, 1300_MHz);
|
2023-07-15 10:55:12 +00:00
|
|
|
OnUserInput();
|
|
|
|
}
|
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
void UpdateScanStep(i32 diff) {
|
2023-07-20 20:13:29 +00:00
|
|
|
if (diff > 0 && scanStep < 25_KHz) {
|
|
|
|
scanStep <<= 1;
|
|
|
|
}
|
|
|
|
if (diff < 0 && scanStep > 6250_Hz) {
|
|
|
|
scanStep >>= 1;
|
|
|
|
}
|
|
|
|
OnUserInput();
|
2023-07-19 18:31:33 +00:00
|
|
|
}
|
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
void UpdateFreqChangeStep(i64 diff) {
|
|
|
|
frequencyChangeStep = clamp(frequencyChangeStep + diff, 100_KHz, 2_MHz);
|
|
|
|
OnUserInput();
|
|
|
|
}
|
|
|
|
|
2023-07-15 10:55:12 +00:00
|
|
|
inline void OnUserInput() {
|
2023-07-15 08:49:35 +00:00
|
|
|
isUserInput = true;
|
2023-07-19 18:31:33 +00:00
|
|
|
u32 halfOfScanRange = scanStep << (6 - sampleZoom);
|
|
|
|
FStart = currentFreq - halfOfScanRange;
|
2023-07-15 08:49:35 +00:00
|
|
|
|
|
|
|
// reset peak
|
2023-07-18 07:30:15 +00:00
|
|
|
highestPeakT = 0;
|
2023-07-18 04:41:27 +00:00
|
|
|
highestPeakRssi = 0;
|
|
|
|
highestPeakX = 64;
|
2023-07-18 07:30:15 +00:00
|
|
|
highestPeakF = currentFreq;
|
2023-07-19 18:31:33 +00:00
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
Fw.DelayUs(90000);
|
2023-07-15 08:49:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Handle() {
|
2023-07-21 10:50:18 +00:00
|
|
|
if (RadioDriver.IsLockedByOrgFw()) {
|
2023-07-15 08:49:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-07-21 10:50:18 +00:00
|
|
|
if (!working) {
|
|
|
|
if (IsFlashLightOn()) {
|
|
|
|
working = true;
|
|
|
|
TurnOffFlashLight();
|
|
|
|
}
|
2023-07-15 08:49:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
lastButtonPressed = Fw.PollKeyboard();
|
|
|
|
if (lastButtonPressed == ExitKey) {
|
2023-07-21 10:50:18 +00:00
|
|
|
working = false;
|
|
|
|
RestoreParams();
|
|
|
|
return;
|
|
|
|
}
|
2023-07-15 08:49:35 +00:00
|
|
|
Update();
|
|
|
|
Render();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void RestoreParams() {
|
|
|
|
if (!bDisplayCleared) {
|
|
|
|
bDisplayCleared = true;
|
|
|
|
DisplayBuff.ClearAll();
|
|
|
|
Fw.FlushFramebufferToScreen();
|
2023-07-23 14:23:53 +00:00
|
|
|
RadioDriver.SetFrequency(currentFreq);
|
2023-07-15 08:49:35 +00:00
|
|
|
Fw.BK4819Write(0x47, u16OldAfSettings); // set previous AF settings
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
inline void Listen(u32 duration) {
|
|
|
|
Fw.BK4819Write(0x47, u16OldAfSettings);
|
|
|
|
for (u8 i = 0; i < 16 && lastButtonPressed == 255; ++i) {
|
|
|
|
lastButtonPressed = Fw.PollKeyboard();
|
|
|
|
Fw.DelayUs(duration >> 4);
|
|
|
|
}
|
|
|
|
Fw.BK4819Write(0x47, 0);
|
2023-07-15 08:49:35 +00:00
|
|
|
}
|
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
u8 GetRssi(u32 f) {
|
|
|
|
RadioDriver.SetFrequency(f);
|
|
|
|
Fw.DelayUs(scanDelay);
|
2023-07-15 08:49:35 +00:00
|
|
|
return Fw.BK4819Read(0x67);
|
|
|
|
}
|
|
|
|
|
2023-07-21 10:50:18 +00:00
|
|
|
inline bool IsFlashLightOn() { return GPIOC->DATA & GPIO_PIN_3; }
|
|
|
|
inline void TurnOffFlashLight() {
|
|
|
|
GPIOC->DATA &= ~GPIO_PIN_3;
|
|
|
|
*FwData.p8FlashLightStatus = 3;
|
2023-07-15 08:49:35 +00:00
|
|
|
}
|
|
|
|
|
2023-07-23 16:59:12 +00:00
|
|
|
inline void ToggleBacklight() {
|
|
|
|
GPIOB->DATA ^= GPIO_PIN_6;
|
|
|
|
}
|
|
|
|
|
2023-07-15 20:15:28 +00:00
|
|
|
inline u8 Rssi2Y(u8 rssi) {
|
2023-07-20 20:13:29 +00:00
|
|
|
return clamp(DrawingEndY - (rssi - rssiMin), 1, DrawingEndY);
|
2023-07-15 20:15:28 +00:00
|
|
|
}
|
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
inline i32 clamp(i32 v, i32 min, i32 max) {
|
2023-07-15 08:49:35 +00:00
|
|
|
if (v < min)
|
|
|
|
return min;
|
|
|
|
if (v > max)
|
|
|
|
return max;
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
inline u32 modulo(u32 num, u32 div) {
|
2023-07-15 08:49:35 +00:00
|
|
|
while (num >= div)
|
|
|
|
num -= div;
|
|
|
|
return num;
|
|
|
|
}
|
|
|
|
|
|
|
|
TUV_K5Display DisplayBuff;
|
|
|
|
const TUV_K5SmallNumbers FontSmallNr;
|
|
|
|
CDisplay<const TUV_K5Display> Display;
|
|
|
|
|
2023-07-23 14:23:53 +00:00
|
|
|
u8 lastButtonPressed;
|
2023-07-15 08:49:35 +00:00
|
|
|
u32 currentFreq;
|
|
|
|
u16 u16OldAfSettings;
|
2023-07-21 10:50:18 +00:00
|
|
|
|
2023-07-15 08:49:35 +00:00
|
|
|
u16 scanDelay;
|
2023-07-21 10:50:18 +00:00
|
|
|
u8 sampleZoom;
|
|
|
|
u32 scanStep;
|
2023-07-23 14:23:53 +00:00
|
|
|
u32 frequencyChangeStep;
|
2023-07-21 10:50:18 +00:00
|
|
|
u8 rssiTriggerLevel;
|
|
|
|
|
|
|
|
bool working = false;
|
|
|
|
bool isUserInput = false;
|
|
|
|
bool bDisplayCleared = true;
|
2023-07-15 08:49:35 +00:00
|
|
|
};
|