diff --git a/README.md b/README.md index b9d057d..cf23dd5 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,41 @@ AFSK messenger with T9 typing support * if message is cleared use **EXIT** to exit messenger view * There is no timeout for the button. If you want to type letters located on the same button in a row, use an asterisk (*) to confirm the selected character +## src/spectrum_fagci ![auto release build](https://github.com/piotr022/UV_K5_playground/actions/workflows/c-cpp.yml/badge.svg) + +Spectrum scanner by [@fagci](https://github.com/fagci) + +![spectrum_fagci](./docs/spectrum_fagci.gif) + +[more videos](https://youtube.com/playlist?list=PL67vBSofOrHMyXOEhonOzQz5gJzsZFiDq) + +[download mod](https://github.com/piotr022/UV_K5_playground/releases/latest) + +Features: + +* no scan sound +* squelch by user input level +* 0.2 .. 3.2MHz frequency ranges +* ticks by frequency (100,500,1000KHz) +* catch signal peak frequency +* automatic frequency change step +* automatic scan step +* frequency blacklist to remove unwanted signals +* backlight control + +How to start: + +* set squelch level to 0 or go into monitor mode before enabling mod +* to enable spectum view press **flash light button** + +How to operate: + +* press **8** / **2** for zoom in / zoom out +* press and hold **3** / **9** to set squelch level +* press **5** to toggle backlight +* press **0** to remove frequency from sspectrum to scan +* press **EXIT** to disable spectrum view + ## src/spectrum ![auto release build](https://github.com/piotr022/UV_K5_playground/actions/workflows/c-cpp.yml/badge.svg) ![spectrum](./docs/spectrum.gif) **update** diff --git a/docs/spectrum_fagci.gif b/docs/spectrum_fagci.gif new file mode 100644 index 0000000..f98dbd4 Binary files /dev/null and b/docs/spectrum_fagci.gif differ diff --git a/src/spectrum_fagci/main.cpp b/src/spectrum_fagci/main.cpp index 1f9f326..314c4f4 100644 --- a/src/spectrum_fagci/main.cpp +++ b/src/spectrum_fagci/main.cpp @@ -1,13 +1,12 @@ #include "hardware/hardware.hpp" +#include "radio.hpp" #include "registers.hpp" #include "spectrum.hpp" #include "system.hpp" -#include "radio.hpp" #include "uv_k5_display.hpp" #include Hardware::THardware Hw; - Radio::CBK4819 RadioDriver; CSpectrum Spectrum; diff --git a/src/spectrum_fagci/spectrum.hpp b/src/spectrum_fagci/spectrum.hpp index ab7869b..25a85da 100644 --- a/src/spectrum_fagci/spectrum.hpp +++ b/src/spectrum_fagci/spectrum.hpp @@ -1,112 +1,97 @@ #pragma once #include "radio.hpp" #include "system.hpp" +#include "types.hpp" #include "uv_k5_display.hpp" -typedef unsigned char u8; -typedef signed short i16; -typedef unsigned short u16; -typedef signed int i32; -typedef unsigned int u32; -typedef signed long long i64; -typedef unsigned long long u64; - -template -class CSpectrum { +template class CSpectrum { public: static constexpr auto ExitKey = 13; static constexpr auto DrawingEndY = 42; static constexpr auto BarPos = 5 * 128; - u8 rssiHistory[64] = {}; - u8 measurementsCount = 32; + u8 rssiHistory[128] = {}; + u32 fMeasure; + + u8 peakT = 0; + u8 peakRssi = 0; + u8 peakI = 0; + u32 peakF = 0; u8 rssiMin = 255; - u8 highestPeakX = 0; - u8 highestPeakT = 0; - u8 highestPeakRssi = 0; - u32 highestPeakF = 0; - u32 FStart, fMeasure; + + u16 scanDelay = 1200; + + bool resetBlacklist = false; CSpectrum() - : DisplayBuff(gDisplayBuffer), FontSmallNr(gSmallDigs), - Display(DisplayBuff), scanDelay(800), sampleZoom(2), scanStep(25_KHz), - frequencyChangeStep(100_KHz), rssiTriggerLevel(65), stickyPeakTrigger(false) { + : DisplayBuff(gDisplayBuffer), Display(DisplayBuff), + FontSmallNr(gSmallDigs), frequencyChangeStep(400_KHz), bwMul(2), + rssiTriggerLevel(60) { Display.SetFont(&FontSmallNr); }; - inline bool ListenPeak() { - if (highestPeakRssi < rssiTriggerLevel) { - return false; - } - - if (fMeasure != highestPeakF) { - fMeasure = highestPeakF; - RadioDriver.SetFrequency(fMeasure); - BK4819Write(0x47, u16OldAfSettings); - RadioDriver.ToggleAFDAC(true); - } - - Listen(1000000); - - highestPeakRssi = GetRssi(); - rssiHistory[highestPeakX >> sampleZoom] = highestPeakRssi; - - return true; - } - - inline void Scan() { + void Scan() { u8 rssi = 0, rssiMax = 0; u8 iPeak = 0; u32 fPeak = currentFreq; - rssiMin = 255; - fMeasure = FStart; + fMeasure = GetFStart(); RadioDriver.ToggleAFDAC(false); - BK4819Write(0x47, 0); + MuteAF(); - for (u8 i = 0; i < measurementsCount; ++i, fMeasure += scanStep) { - rssi = rssiHistory[i] = GetRssi(fMeasure); - if (rssi < rssiMin) { - rssiMin = rssi; + u16 scanStep = GetScanStep(); + u8 measurementsCount = GetMeasurementsCount(); + + for (u8 i = 0; + i < measurementsCount && (PollKeyboard() == 255 || resetBlacklist); + ++i, fMeasure += scanStep) { + if (!resetBlacklist && rssiHistory[i] == 255) { + continue; } + RadioDriver.SetFrequency(fMeasure); + rssi = rssiHistory[i] = GetRssi(); if (rssi > rssiMax) { rssiMax = rssi; fPeak = fMeasure; iPeak = i; } + if (rssi < rssiMin) { + rssiMin = rssi; + } } + resetBlacklist = false; + ++peakT; - ++highestPeakT; - if (rssiMax > highestPeakRssi || highestPeakT >= (8 << sampleZoom)) { - highestPeakT = 0; - highestPeakRssi = rssiMax; - highestPeakX = iPeak << sampleZoom; - highestPeakF = fPeak; + if (rssiMax > peakRssi || peakT >= 16) { + peakT = 0; + peakRssi = rssiMax; + peakF = fPeak; + peakI = iPeak; } } - inline void DrawSpectrum() { + void DrawSpectrum() { for (u8 x = 0; x < 128; ++x) { - Display.DrawHLine(Rssi2Y(rssiHistory[x >> sampleZoom]), DrawingEndY, x); + auto v = rssiHistory[x >> BWMul2XDiv()]; + if (v != 255) { + Display.DrawHLine(Rssi2Y(v), DrawingEndY, x); + } } } - inline void DrawNums() { + void DrawNums() { Display.SetCoursorXY(0, 0); Display.PrintFixedDigitsNumber2(scanDelay, 0); Display.SetCoursorXY(51, 0); - Display.PrintFixedDigitsNumber2(scanStep << (7 - sampleZoom)); + Display.PrintFixedDigitsNumber2(GetBW()); - Display.SetCoursorXY(58, 8); - Display.PrintFixedDigitsNumber2(scanStep); - - Display.SetCoursorXY(107, 8); - Display.PrintFixedDigitsNumber2(highestPeakRssi, 0); + /* Display.SetCoursorXY(0, 0); + Display.PrintFixedDigitsNumber2(rssiMinV, 0); */ Display.SetCoursorXY(86, 0); - Display.PrintFixedDigitsNumber2(highestPeakF); + Display.PrintFixedDigitsNumber2(peakF); Display.SetCoursorXY(44, 48); Display.PrintFixedDigitsNumber2(currentFreq); @@ -114,74 +99,65 @@ public: Display.SetCoursorXY(100, 48); Display.PrintFixedDigitsNumber2(frequencyChangeStep); - Display.SetCoursorXY(0, 8); - Display.PrintFixedDigitsNumber2(rssiTriggerLevel, 0); + /* Display.SetCoursorXY(0, 8); + Display.PrintFixedDigitsNumber2(rssiMaxV, 0); */ } - inline void DrawRssiTriggerLevel() { - for (u8 x = 0; x < 128; x += stickyPeakTrigger ? 2 : 4) { - Display.DrawLine(x, x + 2, Rssi2Y(rssiTriggerLevel)); + void DrawRssiTriggerLevel() { + u8 y = Rssi2Y(rssiTriggerLevel); + for (u8 x = 0; x < 126; x += 4) { + Display.DrawLine(x, x + 2, y); } } - inline void DrawTicks() { - u32 f = modulo(FStart, 1_MHz); - u32 step = scanStep >> sampleZoom; - for (u8 i = 0; i < 128; ++i, f += step) { - u8 barValue = 0b00001000; - modulo(f, 100_KHz) < step && (barValue |= 0b00010000); - modulo(f, 500_KHz) < step && (barValue |= 0b00100000); - modulo(f, 1_MHz) < step && (barValue |= 0b11000000); - - gDisplayBuffer[BarPos + i] |= barValue; - } - + void DrawTicks() { // center - gDisplayBuffer[BarPos + 64] |= 0b10101010; + gDisplayBuffer[BarPos + 64] = 0b10101000; } - inline void DrawArrow(u8 x) { - u8 *peakPos = gDisplayBuffer + 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 DrawArrow(u8 x) { + for (signed i = -2; i <= 2; ++i) { + signed v = x + i; + if (!(v & 128)) { + gDisplayBuffer[BarPos + v] |= (0b01111000 << abs(i)) & 0b01111000; + } + } } - void HandleUserInput() { - switch (lastButtonPressed) { - case 1: - UpdateScanDelay(200); - break; - case 7: - UpdateScanDelay(-200); - break; - case 2: - UpdateSampleZoom(1); - break; - case 8: - UpdateSampleZoom(-1); - break; + void OnKey(u8 key) { + switch (key) { case 3: - UpdateRssiTriggerLevel(5); + UpdateRssiTriggerLevel(1); + DelayMs(90); break; case 9: - UpdateRssiTriggerLevel(-5); + UpdateRssiTriggerLevel(-1); + DelayMs(90); break; - case 4: - UpdateScanStep(-1); - UpdateSampleZoom(1); + } + } + + void OnKeyDown(u8 key) { + switch (key) { + case 1: + if (scanDelay < 8000) { + scanDelay += 200; + rssiMin = 255; + } break; - case 6: - UpdateScanStep(1); - UpdateSampleZoom(-1); + case 7: + if (scanDelay > 800) { + scanDelay -= 200; + rssiMin = 255; + } break; - case 11: // up - UpdateCurrentFreq(frequencyChangeStep); + case 2: + UpdateBWMul(1); + resetBlacklist = true; break; - case 12: // down - UpdateCurrentFreq(-frequencyChangeStep); + case 8: + UpdateBWMul(-1); + resetBlacklist = true; break; case 14: UpdateFreqChangeStep(100_KHz); @@ -189,18 +165,42 @@ public: case 15: UpdateFreqChangeStep(-100_KHz); break; + case 11: // up + UpdateCurrentFreq(frequencyChangeStep); + resetBlacklist = true; + break; + case 12: // down + UpdateCurrentFreq(-frequencyChangeStep); + resetBlacklist = true; + break; case 5: ToggleBacklight(); + break; case 0: - stickyPeakTrigger = !stickyPeakTrigger; - OnUserInput(); + Blacklist(); + break; } + ResetPeak(); + } + + bool HandleUserInput() { + btnPrev = btn; + btn = PollKeyboard(); + if (btn == ExitKey) { + DeInit(); + return false; + } + OnKey(btn); + if (btn != 255 && btnPrev == 255) { + OnKeyDown(btn); + } + return true; } void Render() { DisplayBuff.ClearAll(); DrawTicks(); - DrawArrow(highestPeakX); + DrawArrow(peakI << BWMul2XDiv()); DrawSpectrum(); DrawRssiTriggerLevel(); DrawNums(); @@ -208,167 +208,168 @@ public: } void Update() { - if (bDisplayCleared) { - currentFreq = RadioDriver.GetFrequency(); - OnUserInput(); - u16OldAfSettings = BK4819Read(0x47); - BK4819Write(0x47, 0); // mute AF during scan + if (peakRssi >= rssiTriggerLevel) { + Listen(1600); + return; } - bDisplayCleared = false; - - HandleUserInput(); - - if (!ListenPeak()) - Scan(); + Scan(); } void UpdateRssiTriggerLevel(i32 diff) { - rssiTriggerLevel = clamp(rssiTriggerLevel + diff, 10, 255); - OnUserInput(); + if ((diff > 0 && rssiTriggerLevel < 255) || + (diff < 0 && rssiTriggerLevel > 0)) { + rssiTriggerLevel += diff; + } } - void UpdateScanDelay(i32 diff) { - scanDelay = clamp(scanDelay + diff, 800, 3200); - OnUserInput(); - } - - void UpdateSampleZoom(i32 diff) { - sampleZoom = clamp(sampleZoom - diff, 1, 5); - measurementsCount = 1 << (7 - sampleZoom); - OnUserInput(); + void UpdateBWMul(i32 diff) { + if ((diff > 0 && bwMul < 4) || (diff < 0 && bwMul > 0)) { + bwMul += diff; + } + frequencyChangeStep = 100_KHz << bwMul; } void UpdateCurrentFreq(i64 diff) { - currentFreq = clamp(currentFreq + diff, 18_MHz, 1300_MHz); - OnUserInput(); - } - - void UpdateScanStep(i32 diff) { - if (diff > 0 && scanStep < 25_KHz) { - scanStep <<= 1; + if ((diff > 0 && currentFreq < 1300_MHz) || + (diff < 0 && currentFreq > 18_MHz)) { + currentFreq += diff; } - if (diff < 0 && scanStep > 6250_Hz) { - scanStep >>= 1; - } - OnUserInput(); } void UpdateFreqChangeStep(i64 diff) { frequencyChangeStep = clamp(frequencyChangeStep + diff, 100_KHz, 2_MHz); - OnUserInput(); } - inline void OnUserInput() { - u32 halfOfScanRange = scanStep << (6 - sampleZoom); - FStart = currentFreq - halfOfScanRange; - - // reset peak - highestPeakT = 0; - highestPeakRssi = 0; - highestPeakX = 64; - highestPeakF = currentFreq; - - DelayUs(90000); - } + void Blacklist() { rssiHistory[peakI] = 255; } void Handle() { if (RadioDriver.IsLockedByOrgFw()) { return; } - if (!working) { - if (IsFlashLightOn()) { - working = true; - TurnOffFlashLight(); - } - return; + if (!isInitialized && IsFlashLightOn()) { + TurnOffFlashLight(); + Init(); } - lastButtonPressed = PollKeyboard(); - if (lastButtonPressed == ExitKey) { - working = false; - RestoreParams(); - return; + if (isInitialized && HandleUserInput()) { + Update(); + Render(); } - Update(); - Render(); } private: - void RestoreParams() { - if (!bDisplayCleared) { - bDisplayCleared = true; - DisplayBuff.ClearAll(); - FlushFramebufferToScreen(); - RadioDriver.SetFrequency(currentFreq); - BK4819Write(0x47, u16OldAfSettings); // set previous AF settings - } + void Init() { + currentFreq = RadioDriver.GetFrequency(); + oldAFSettings = BK4819Read(0x47); + oldBWSettings = BK4819Read(0x43); + MuteAF(); + SetWideBW(); + isInitialized = true; } - inline void Listen(u32 duration) { - for (u8 i = 0; i < 16 && lastButtonPressed == 255; ++i) { - lastButtonPressed = PollKeyboard(); - DelayUs(duration >> 4); + void DeInit() { + DisplayBuff.ClearAll(); + FlushFramebufferToScreen(); + RadioDriver.SetFrequency(currentFreq); + RestoreOldAFSettings(); + BK4819Write(0x43, oldBWSettings); + isInitialized = false; + } + + void ResetPeak() { + peakRssi = 0; + peakF = currentFreq; + peakT = 0; + } + + void SetWideBW() { + auto Reg = BK4819Read(0x43); + Reg &= ~(0b11 << 4); + BK4819Write(0x43, Reg | (0b11 << 4)); + } + void MuteAF() { BK4819Write(0x47, 0); } + void RestoreOldAFSettings() { BK4819Write(0x47, oldAFSettings); } + + void Listen(u16 durationMs) { + if (fMeasure != peakF) { + fMeasure = peakF; + RadioDriver.SetFrequency(fMeasure); + RestoreOldAFSettings(); + RadioDriver.ToggleAFDAC(true); } + for (u8 i = 0; i < 16 && PollKeyboard() == 255; ++i) { + DelayMs(durationMs >> 4); + } + peakRssi = rssiHistory[peakI] = GetRssi(); + } + + u16 GetScanStep() { return 25_KHz >> (2 >> bwMul); } + u32 GetBW() { return 200_KHz << bwMul; } + u32 GetFStart() { return currentFreq - (100_KHz << bwMul); } + + u8 BWMul2XDiv() { return clamp(4 - bwMul, 0, 2); } + u8 GetMeasurementsCount() { + if (bwMul == 3) { + return 64; + } + if (bwMul > 3) { + return 128; + } + return 32; + } + + void ResetRSSI() { + RadioDriver.ToggleRXDSP(false); + RadioDriver.ToggleRXDSP(true); } u8 GetRssi() { - if (!stickyPeakTrigger) { - // reset RSSI register - RadioDriver.ToggleRXDSP(false); - RadioDriver.ToggleRXDSP(true); - } + ResetRSSI(); DelayUs(scanDelay); - return BK4819Read(0x67); + return (BK4819Read(0x67) & 0x1FF) >> 1; } - u8 GetRssi(u32 f) { - RadioDriver.SetFrequency(f); - return GetRssi(); - } - - inline bool IsFlashLightOn() { return GPIOC->DATA & GPIO_PIN_3; } - inline void TurnOffFlashLight() { + bool IsFlashLightOn() { return GPIOC->DATA & GPIO_PIN_3; } + void TurnOffFlashLight() { GPIOC->DATA &= ~GPIO_PIN_3; gFlashLightStatus = 3; } - inline void ToggleBacklight() { GPIOB->DATA ^= GPIO_PIN_6; } + void ToggleBacklight() { GPIOB->DATA ^= GPIO_PIN_6; } - inline u8 Rssi2Y(u8 rssi) { - return clamp(DrawingEndY - (rssi - rssiMin), 1, DrawingEndY); + u8 Rssi2Y(u8 rssi) { + return DrawingEndY - clamp(rssi - rssiMin, 0, DrawingEndY); } - inline i32 clamp(i32 v, i32 min, i32 max) { - if (v < min) + i32 clamp(i32 v, i32 min, i32 max) { + if (v <= min) return min; - if (v > max) + if (v >= max) return max; return v; } - inline u32 modulo(u32 num, u32 div) { + u32 modulo(u32 num, u32 div) { while (num >= div) num -= div; return num; } TUV_K5Display DisplayBuff; - const TUV_K5SmallNumbers FontSmallNr; CDisplay Display; + const TUV_K5SmallNumbers FontSmallNr; - u8 lastButtonPressed; - u32 currentFreq; - u16 u16OldAfSettings; - - u16 scanDelay; - u8 sampleZoom; - u32 scanStep; u32 frequencyChangeStep; + u8 bwMul; u8 rssiTriggerLevel; - bool stickyPeakTrigger; - bool working = false; - bool bDisplayCleared = true; + u8 btn = 255; + u8 btnPrev = 255; + u32 currentFreq; + u16 oldAFSettings; + u16 oldBWSettings; + + bool isInitialized = false; }; diff --git a/src/spectrum_fagci/types.hpp b/src/spectrum_fagci/types.hpp new file mode 100644 index 0000000..7b6f88c --- /dev/null +++ b/src/spectrum_fagci/types.hpp @@ -0,0 +1,10 @@ +#pragma once + +typedef unsigned char u8; +typedef signed short i16; +typedef unsigned short u16; +typedef signed int i32; +typedef unsigned int u32; +typedef signed long long i64; +typedef unsigned long long u64; +