UV_K5_playground/src/spectrum_fagci/spectrum.hpp

351 wiersze
8.2 KiB
C++
Czysty Zwykły widok Historia

#pragma once
#include "system.hpp"
#include "uv_k5_display.hpp"
2023-07-21 10:50:18 +00:00
#include "radio.hpp"
typedef unsigned char u8;
2023-07-16 08:05:47 +00:00
typedef signed short i16;
typedef unsigned short u16;
2023-07-19 18:31:33 +00:00
typedef signed int i32;
typedef unsigned int u32;
2023-07-16 08:05:47 +00:00
typedef signed long long i64;
typedef unsigned long long u64;
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-21 10:50:18 +00:00
template <const System::TOrgFunctions &Fw, const System::TOrgData &FwData, Radio::CBK4819<Fw> &RadioDriver>
class CSpectrum {
public:
2023-07-21 10:50:18 +00:00
static constexpr auto ExitKey = 13;
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;
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
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) {
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;
}
}
++highestPeakT;
2023-07-15 20:15:28 +00:00
if (highestPeakT >= 8 || rssiMax > highestPeakRssi) {
highestPeakT = 0;
highestPeakRssi = rssiMax;
highestPeakX = xPeak;
highestPeakF = fPeak;
}
2023-07-15 10:55:12 +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-18 07:30:15 +00:00
}
inline void DrawNums() {
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-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 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 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() {
switch (u8LastBtnPressed) {
case 1:
2023-07-20 20:13:29 +00:00
UpdateScanDelay(200);
break;
case 7:
2023-07-20 20:13:29 +00:00
UpdateScanDelay(-200);
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;
case 11: // up
2023-07-15 10:55:12 +00:00
UpdateCurrentFreq(100_KHz);
break;
case 12: // down
2023-07-15 10:55:12 +00:00
UpdateCurrentFreq(-100_KHz);
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() {
isUserInput = true;
2023-07-19 18:31:33 +00:00
u32 halfOfScanRange = scanStep << (6 - sampleZoom);
FStart = currentFreq - halfOfScanRange;
FEnd = currentFreq + halfOfScanRange;
// reset peak
2023-07-18 07:30:15 +00:00
highestPeakT = 0;
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);
}
void Handle() {
2023-07-21 10:50:18 +00:00
if (RadioDriver.IsLockedByOrgFw()) {
return;
}
2023-07-21 10:50:18 +00:00
if (!working) {
if (IsFlashLightOn()) {
working = true;
TurnOffFlashLight();
}
return;
}
2023-07-21 10:50:18 +00:00
u8LastBtnPressed = Fw.PollKeyboard();
if (u8LastBtnPressed == ExitKey) {
working = false;
RestoreParams();
return;
}
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);
Fw.BK4819Write(0x30, 0);
Fw.BK4819Write(0x30, 0xbff1);
}
2023-07-15 20:15:28 +00:00
u8 GetRssi(u32 f, u32 delay = 800) {
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-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 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) {
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) {
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;
u32 currentFreq;
u16 u16OldAfSettings;
2023-07-21 10:50:18 +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;
};