2023-07-15 08:49:35 +00:00
|
|
|
#pragma once
|
|
|
|
#include "system.hpp"
|
|
|
|
#include "uv_k5_display.hpp"
|
2023-07-21 10:50:18 +00:00
|
|
|
#include "radio.hpp"
|
2023-07-15 08:49:35 +00:00
|
|
|
|
|
|
|
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-16 08:05:47 +00:00
|
|
|
static constexpr auto operator""_Hz(u64 Hz) { return Hz / 10; }
|
|
|
|
static constexpr auto operator""_KHz(u64 KHz) { return KHz * 1000_Hz; }
|
|
|
|
static constexpr auto operator""_MHz(u64 KHz) { return KHz * 1000_KHz; }
|
2023-07-18 07:30:15 +00:00
|
|
|
static constexpr auto operator""_ms(u64 us) { return us * 1000; }
|
|
|
|
static constexpr auto operator""_s(u64 us) { return us * 1000_ms; }
|
2023-07-15 08:49:35 +00:00
|
|
|
|
2023-07-21 10:50:18 +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 DrawingSizeY = 16 + 6 * 8;
|
|
|
|
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-15 10:55:12 +00:00
|
|
|
u8 rssiMin = 255, rssiMax = 0;
|
2023-07-15 08:49:35 +00:00
|
|
|
u8 highestPeakX = 0;
|
|
|
|
u8 highestPeakT = 0;
|
|
|
|
u8 highestPeakRssi = 0;
|
|
|
|
u32 highestPeakF = 0;
|
2023-07-21 10:50:18 +00:00
|
|
|
u32 FStart, FEnd, fMeasure;
|
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),
|
|
|
|
rssiTriggerLevel(65) {
|
2023-07-15 08:49:35 +00:00
|
|
|
Display.SetFont(&FontSmallNr);
|
|
|
|
};
|
|
|
|
|
2023-07-15 10:55:12 +00:00
|
|
|
inline void Measure() {
|
2023-07-18 07:30:15 +00:00
|
|
|
if (highestPeakRssi >= rssiTriggerLevel) {
|
|
|
|
// listen
|
|
|
|
if (fMeasure != highestPeakF) {
|
|
|
|
fMeasure = highestPeakF;
|
|
|
|
SetFrequency(fMeasure);
|
|
|
|
}
|
2023-07-19 18:31:33 +00:00
|
|
|
Fw.BK4819Write(0x47, u16OldAfSettings);
|
2023-07-18 07:30:15 +00:00
|
|
|
Fw.DelayUs(1_s);
|
2023-07-15 10:55:12 +00:00
|
|
|
|
2023-07-18 07:30:15 +00:00
|
|
|
// check signal level
|
|
|
|
Fw.BK4819Write(0x47, 0); // AF
|
2023-07-15 10:55:12 +00:00
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
highestPeakRssi = GetRssi(fMeasure, scanDelay);
|
2023-07-18 07:30:15 +00:00
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
rssiHistory[highestPeakX >> sampleZoom] = highestPeakRssi;
|
2023-07-18 07:30:15 +00:00
|
|
|
return;
|
|
|
|
}
|
2023-07-16 08:05:47 +00:00
|
|
|
|
2023-07-18 07:30:15 +00:00
|
|
|
u8 rssi = 0;
|
|
|
|
u8 xPeak = 64;
|
|
|
|
u32 fPeak = currentFreq;
|
|
|
|
|
|
|
|
Fw.BK4819Write(0x47, 0);
|
|
|
|
|
|
|
|
rssiMin = 255;
|
|
|
|
rssiMax = 0;
|
|
|
|
fMeasure = FStart;
|
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
for (u8 i = 0; i < measurementsCount; ++i, fMeasure += scanStep) {
|
2023-07-18 07:30:15 +00:00
|
|
|
rssi = rssiHistory[i] = GetRssi(fMeasure, scanDelay);
|
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-19 18:31:33 +00:00
|
|
|
xPeak = i << sampleZoom;
|
2023-07-15 08:49:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
++highestPeakT;
|
2023-07-15 20:15:28 +00:00
|
|
|
if (highestPeakT >= 8 || rssiMax > highestPeakRssi) {
|
2023-07-15 08:49:35 +00:00
|
|
|
highestPeakT = 0;
|
|
|
|
highestPeakRssi = rssiMax;
|
|
|
|
highestPeakX = xPeak;
|
|
|
|
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-15 08:49:35 +00:00
|
|
|
Display.SetCoursor(0, 0);
|
|
|
|
Display.PrintFixedDigitsNumber2(scanDelay, 0);
|
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
Display.SetCoursor(0, 8 * 2 + 5 * 7);
|
|
|
|
Display.PrintFixedDigitsNumber2(scanStep << (7 - sampleZoom));
|
2023-07-15 08:49:35 +00:00
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
Display.SetCoursor(1, 8 * 2 + 6 * 7);
|
|
|
|
Display.PrintFixedDigitsNumber2(scanStep);
|
2023-07-15 20:15:28 +00:00
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
Display.SetCoursor(1, 8 * 2 + 13 * 7);
|
2023-07-15 20:15:28 +00:00
|
|
|
Display.PrintFixedDigitsNumber2(highestPeakRssi, 0);
|
2023-07-16 08:05:47 +00:00
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
Display.SetCoursor(0, 8 * 2 + 10 * 7);
|
|
|
|
Display.PrintFixedDigitsNumber2(highestPeakF);
|
|
|
|
|
|
|
|
Display.SetCoursor(6, 8 * 2 + 4 * 7);
|
|
|
|
Display.PrintFixedDigitsNumber2(currentFreq);
|
|
|
|
|
|
|
|
Display.SetCoursor(1, 0);
|
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-15 08:49:35 +00:00
|
|
|
switch (u8LastBtnPressed) {
|
|
|
|
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);
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
UpdateScanStep(1);
|
2023-07-15 20:15:28 +00:00
|
|
|
break;
|
2023-07-15 08:49:35 +00:00
|
|
|
case 11: // up
|
2023-07-15 10:55:12 +00:00
|
|
|
UpdateCurrentFreq(100_KHz);
|
2023-07-15 08:49:35 +00:00
|
|
|
break;
|
|
|
|
case 12: // down
|
2023-07-15 10:55:12 +00:00
|
|
|
UpdateCurrentFreq(-100_KHz);
|
2023-07-15 08:49:35 +00:00
|
|
|
break;
|
|
|
|
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) {
|
|
|
|
currentFreq = GetFrequency();
|
|
|
|
OnUserInput();
|
|
|
|
u16OldAfSettings = Fw.BK4819Read(0x47);
|
|
|
|
Fw.BK4819Write(0x47, 0); // mute AF during scan
|
|
|
|
}
|
|
|
|
bDisplayCleared = false;
|
|
|
|
|
|
|
|
HandleUserInput();
|
|
|
|
|
|
|
|
Measure();
|
|
|
|
}
|
|
|
|
|
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-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;
|
|
|
|
FEnd = 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
|
|
|
|
|
|
|
Fw.DelayUs(90_ms);
|
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-21 10:50:18 +00:00
|
|
|
u8LastBtnPressed = Fw.PollKeyboard();
|
|
|
|
if (u8LastBtnPressed == ExitKey) {
|
|
|
|
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();
|
|
|
|
SetFrequency(currentFreq);
|
|
|
|
Fw.BK4819Write(0x47, u16OldAfSettings); // set previous AF settings
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-19 18:31:33 +00:00
|
|
|
void SetFrequency(u32 f) {
|
|
|
|
Fw.BK4819Write(0x39, (f >> 16) & 0xFFFF);
|
|
|
|
Fw.BK4819Write(0x38, f & 0xFFFF);
|
2023-07-15 08:49:35 +00:00
|
|
|
Fw.BK4819Write(0x30, 0);
|
|
|
|
Fw.BK4819Write(0x30, 0xbff1);
|
|
|
|
}
|
|
|
|
|
2023-07-15 20:15:28 +00:00
|
|
|
u8 GetRssi(u32 f, u32 delay = 800) {
|
2023-07-15 08:49:35 +00:00
|
|
|
SetFrequency(f);
|
|
|
|
Fw.DelayUs(delay);
|
|
|
|
return Fw.BK4819Read(0x67);
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 GetFrequency() {
|
2023-07-16 08:05:47 +00:00
|
|
|
return (Fw.BK4819Read(0x39) << 16) | Fw.BK4819Read(0x38);
|
2023-07-15 08:49:35 +00:00
|
|
|
}
|
|
|
|
|
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-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-21 10:50:18 +00:00
|
|
|
u8 u8LastBtnPressed;
|
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;
|
|
|
|
u8 rssiTriggerLevel;
|
|
|
|
|
|
|
|
bool working = false;
|
|
|
|
bool isUserInput = false;
|
|
|
|
bool bDisplayCleared = true;
|
2023-07-15 08:49:35 +00:00
|
|
|
};
|