pull/66/head
Mikhail Yudin 2023-08-07 22:19:00 +07:00
rodzic 8d6d99de50
commit 3d9171dc9e
5 zmienionych plików z 267 dodań i 222 usunięć

Wyświetl plik

@ -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**

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 10 MiB

Wyświetl plik

@ -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 <string.h>
Hardware::THardware Hw;
Radio::CBK4819 RadioDriver;
CSpectrum<RadioDriver> Spectrum;

Wyświetl plik

@ -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 <Radio::CBK4819 &RadioDriver>
class CSpectrum {
template <Radio::CBK4819 &RadioDriver> 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<const TUV_K5Display> 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;
};

Wyświetl plik

@ -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;