2023-09-29 15:01:26 +02:00
/* Copyright 2023 fagci
* https : //github.com/fagci
*
* Licensed under the Apache License , Version 2.0 ( the " License " ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
*/
2023-10-25 20:47:14 +02:00
# include "app/spectrum.h"
# include "driver/backlight.h"
2023-10-28 21:53:22 +02:00
# include "audio.h"
2023-11-22 23:43:39 +01:00
# include "ui/helper.h"
2023-12-02 17:45:47 +00:00
# include "am_fix.h"
# include "ui/main.h"
2023-09-29 15:01:26 +02:00
struct FrequencyBandInfo {
uint32_t lower ;
uint32_t upper ;
uint32_t middle ;
} ;
2023-10-20 16:49:53 +02:00
# define F_MIN frequencyBandTable[0].lower
# define F_MAX frequencyBandTable[ARRAY_SIZE(frequencyBandTable) - 1].upper
2023-09-29 15:01:26 +02:00
const uint16_t RSSI_MAX_VALUE = 65535 ;
2023-12-02 17:45:47 +00:00
2023-09-29 15:01:26 +02:00
static uint16_t R30 , R37 , R3D , R43 , R47 , R48 , R7E ;
static uint32_t initialFreq ;
static char String [ 32 ] ;
bool isInitialized = false ;
bool isListening = true ;
bool monitorMode = false ;
bool redrawStatus = true ;
bool redrawScreen = false ;
bool newScanStart = true ;
bool preventKeypress = true ;
bool audioState = true ;
State currentState = SPECTRUM , previousState = SPECTRUM ;
PeakInfo peak ;
ScanInfo scanInfo ;
KeyboardState kbd = { KEY_INVALID , KEY_INVALID , 0 } ;
2023-10-01 12:38:55 +02:00
const char * bwOptions [ ] = { " 25k " , " 12.5k " , " 6.25k " } ;
2023-09-29 15:01:26 +02:00
const uint8_t modulationTypeTuneSteps [ ] = { 100 , 50 , 10 } ;
const uint8_t modTypeReg47Values [ ] = { 1 , 7 , 5 } ;
2023-10-04 22:29:26 +02:00
SpectrumSettings settings = { stepsCount : STEPS_64 ,
scanStepIndex : S_STEP_25_0kHz ,
frequencyChangeStep : 80000 ,
scanDelay : 3200 ,
rssiTriggerLevel : 150 ,
backlightState : true ,
bw : BK4819_FILTER_BW_WIDE ,
listenBw : BK4819_FILTER_BW_WIDE ,
modulationType : false ,
dbMin : - 130 ,
dbMax : - 50 } ;
2023-09-29 15:01:26 +02:00
uint32_t fMeasure = 0 ;
uint32_t currentFreq , tempFreq ;
2023-10-20 16:49:53 +02:00
uint16_t rssiHistory [ 128 ] ;
2023-12-02 17:45:47 +00:00
int vfo ;
2023-09-29 15:01:26 +02:00
uint8_t freqInputIndex = 0 ;
uint8_t freqInputDotIndex = 0 ;
KEY_Code_t freqInputArr [ 10 ] ;
2023-10-20 16:49:53 +02:00
char freqInputString [ 11 ] ;
2023-09-29 15:01:26 +02:00
uint8_t menuState = 0 ;
uint16_t listenT = 0 ;
RegisterSpec registerSpecs [ ] = {
{ } ,
2023-10-30 16:39:18 +01:00
{ " LNAs " , BK4819_REG_13 , 8 , 0 b11 , 1 } ,
{ " LNA " , BK4819_REG_13 , 5 , 0 b111 , 1 } ,
{ " PGA " , BK4819_REG_13 , 0 , 0 b111 , 1 } ,
{ " IF " , BK4819_REG_3D , 0 , 0xFFFF , 0x2aaa } ,
2023-09-29 15:01:26 +02:00
// {"MIX", 0x13, 3, 0b11, 1}, // TODO: hidden
} ;
uint16_t statuslineUpdateTimer = 0 ;
static uint8_t DBm2S ( int dbm ) {
uint8_t i = 0 ;
dbm * = - 1 ;
for ( i = 0 ; i < ARRAY_SIZE ( U8RssiMap ) ; i + + ) {
if ( dbm > = U8RssiMap [ i ] ) {
return i ;
}
}
return i ;
}
2023-12-02 17:45:47 +00:00
static int Rssi2DBm ( uint16_t rssi )
{
return ( rssi / 2 ) - 160 + dBmCorrTable [ gRxVfo - > Band ] ;
}
2023-09-29 15:01:26 +02:00
static uint16_t GetRegMenuValue ( uint8_t st ) {
RegisterSpec s = registerSpecs [ st ] ;
2023-10-31 17:23:10 +01:00
return ( BK4819_ReadRegister ( s . num ) > > s . offset ) & s . mask ;
2023-09-29 15:01:26 +02:00
}
static void SetRegMenuValue ( uint8_t st , bool add ) {
uint16_t v = GetRegMenuValue ( st ) ;
RegisterSpec s = registerSpecs [ st ] ;
uint16_t reg = BK4819_ReadRegister ( s . num ) ;
2023-10-31 17:23:10 +01:00
if ( add & & v < = s . mask - s . inc ) {
2023-09-29 15:01:26 +02:00
v + = s . inc ;
} else if ( ! add & & v > = 0 + s . inc ) {
v - = s . inc ;
}
// TODO: use max value for bits count in max value, or reset by additional
// mask in spec
2023-10-31 17:23:10 +01:00
reg & = ~ ( s . mask < < s . offset ) ;
2023-09-29 15:01:26 +02:00
BK4819_WriteRegister ( s . num , reg | ( v < < s . offset ) ) ;
redrawScreen = true ;
}
// GUI functions
static void PutPixel ( uint8_t x , uint8_t y , bool fill ) {
2023-11-22 23:43:39 +01:00
UI_DrawPixelBuffer ( gFrameBuffer , x , y , fill ) ;
2023-09-29 15:01:26 +02:00
}
static void PutPixelStatus ( uint8_t x , uint8_t y , bool fill ) {
2023-11-22 23:43:39 +01:00
UI_DrawPixelBuffer ( & gStatusLine , x , y , fill ) ;
2023-09-29 15:01:26 +02:00
}
2023-11-22 23:43:39 +01:00
static void DrawVLine ( int sy , int ey , int nx , bool fill ) {
2023-09-29 15:01:26 +02:00
for ( int i = sy ; i < = ey ; i + + ) {
if ( i < 56 & & nx < 128 ) {
PutPixel ( nx , i , fill ) ;
}
}
}
static void GUI_DisplaySmallest ( const char * pString , uint8_t x , uint8_t y ,
bool statusbar , bool fill ) {
uint8_t c ;
uint8_t pixels ;
const uint8_t * p = ( const uint8_t * ) pString ;
while ( ( c = * p + + ) & & c ! = ' \0 ' ) {
c - = 0x20 ;
for ( int i = 0 ; i < 3 ; + + i ) {
pixels = gFont3x5 [ c ] [ i ] ;
for ( int j = 0 ; j < 6 ; + + j ) {
if ( pixels & 1 ) {
if ( statusbar )
PutPixelStatus ( x + i , y + j , fill ) ;
else
PutPixel ( x + i , y + j , fill ) ;
}
pixels > > = 1 ;
}
}
x + = 4 ;
}
}
// Utility functions
KEY_Code_t GetKey ( ) {
KEY_Code_t btn = KEYBOARD_Poll ( ) ;
if ( btn = = KEY_INVALID & & ! GPIO_CheckBit ( & GPIOC - > DATA , GPIOC_PIN_PTT ) ) {
btn = KEY_PTT ;
}
return btn ;
}
static int clamp ( int v , int min , int max ) {
return v < = min ? min : ( v > = max ? max : v ) ;
}
static uint8_t my_abs ( signed v ) { return v > 0 ? v : - v ; }
void SetState ( State state ) {
previousState = currentState ;
currentState = state ;
redrawScreen = true ;
redrawStatus = true ;
}
// Radio functions
static void ToggleAFBit ( bool on ) {
uint16_t reg = BK4819_ReadRegister ( BK4819_REG_47 ) ;
reg & = ~ ( 1 < < 8 ) ;
if ( on )
reg | = on < < 8 ;
BK4819_WriteRegister ( BK4819_REG_47 , reg ) ;
}
static void BackupRegisters ( ) {
2023-10-30 13:26:16 +01:00
R30 = BK4819_ReadRegister ( BK4819_REG_30 ) ;
R37 = BK4819_ReadRegister ( BK4819_REG_37 ) ;
2023-10-30 16:39:18 +01:00
R3D = BK4819_ReadRegister ( BK4819_REG_3D ) ;
2023-10-30 13:26:16 +01:00
R43 = BK4819_ReadRegister ( BK4819_REG_43 ) ;
R47 = BK4819_ReadRegister ( BK4819_REG_47 ) ;
R48 = BK4819_ReadRegister ( BK4819_REG_48 ) ;
R7E = BK4819_ReadRegister ( BK4819_REG_7E ) ;
2023-09-29 15:01:26 +02:00
}
static void RestoreRegisters ( ) {
2023-10-30 13:26:16 +01:00
BK4819_WriteRegister ( BK4819_REG_30 , R30 ) ;
BK4819_WriteRegister ( BK4819_REG_37 , R37 ) ;
2023-10-30 16:39:18 +01:00
BK4819_WriteRegister ( BK4819_REG_3D , R3D ) ;
2023-10-30 13:26:16 +01:00
BK4819_WriteRegister ( BK4819_REG_43 , R43 ) ;
BK4819_WriteRegister ( BK4819_REG_47 , R47 ) ;
BK4819_WriteRegister ( BK4819_REG_48 , R48 ) ;
BK4819_WriteRegister ( BK4819_REG_7E , R7E ) ;
2023-09-29 15:01:26 +02:00
}
static void ToggleAFDAC ( bool on ) {
uint32_t Reg = BK4819_ReadRegister ( BK4819_REG_30 ) ;
Reg & = ~ ( 1 < < 9 ) ;
if ( on )
Reg | = ( 1 < < 9 ) ;
BK4819_WriteRegister ( BK4819_REG_30 , Reg ) ;
}
static void SetF ( uint32_t f ) {
fMeasure = f ;
BK4819_SetFrequency ( fMeasure ) ;
BK4819_PickRXFilterPathBasedOnFrequency ( fMeasure ) ;
2023-10-01 12:38:55 +02:00
uint16_t reg = BK4819_ReadRegister ( BK4819_REG_30 ) ;
2023-09-29 15:01:26 +02:00
BK4819_WriteRegister ( BK4819_REG_30 , 0 ) ;
2023-10-01 12:38:55 +02:00
BK4819_WriteRegister ( BK4819_REG_30 , reg ) ;
2023-09-29 15:01:26 +02:00
}
// Spectrum related
bool IsPeakOverLevel ( ) { return peak . rssi > = settings . rssiTriggerLevel ; }
static void ResetPeak ( ) {
peak . t = 0 ;
peak . rssi = 0 ;
}
bool IsCenterMode ( ) { return settings . scanStepIndex < S_STEP_2_5kHz ; }
uint8_t GetStepsCount ( ) { return 128 > > settings . stepsCount ; }
uint16_t GetScanStep ( ) { return scanStepValues [ settings . scanStepIndex ] ; }
uint32_t GetBW ( ) { return GetStepsCount ( ) * GetScanStep ( ) ; }
uint32_t GetFStart ( ) {
return IsCenterMode ( ) ? currentFreq - ( GetBW ( ) > > 1 ) : currentFreq ;
}
uint32_t GetFEnd ( ) { return currentFreq + GetBW ( ) ; }
static void TuneToPeak ( ) {
scanInfo . f = peak . f ;
scanInfo . rssi = peak . rssi ;
scanInfo . i = peak . i ;
SetF ( scanInfo . f ) ;
}
static void DeInitSpectrum ( ) {
SetF ( initialFreq ) ;
RestoreRegisters ( ) ;
isInitialized = false ;
}
2023-10-01 12:38:55 +02:00
uint8_t GetBWRegValueForScan ( ) {
return scanStepBWRegValues [ settings . scanStepIndex ] ;
}
2023-09-29 15:01:26 +02:00
uint16_t GetRssi ( ) {
2023-10-01 12:38:55 +02:00
// SYSTICK_DelayUs(800);
// testing autodelay based on Glitch value
while ( ( BK4819_ReadRegister ( 0x63 ) & 0 b11111111 ) > = 255 ) {
SYSTICK_DelayUs ( 100 ) ;
}
2023-12-02 17:45:47 +00:00
if ( settings . modulationType = = MODULATION_AM )
{
return BK4819_GetRSSI ( ) - rssi_gain_diff [ vfo ] ; //return the corrected RSSI to allow for AM_Fixs AGC action.
}
else
{
return BK4819_GetRSSI ( ) ;
}
2023-09-29 15:01:26 +02:00
}
static void ToggleAudio ( bool on ) {
if ( on = = audioState ) {
return ;
}
audioState = on ;
if ( on ) {
2023-10-28 21:53:22 +02:00
AUDIO_AudioPathOn ( ) ;
2023-09-29 15:01:26 +02:00
} else {
2023-10-28 21:53:22 +02:00
AUDIO_AudioPathOff ( ) ;
2023-09-29 15:01:26 +02:00
}
}
static void ToggleRX ( bool on ) {
isListening = on ;
2023-10-18 17:24:56 +02:00
BK4819_ToggleGpioOut ( BK4819_GPIO6_PIN2_GREEN , on ) ;
2023-09-29 15:01:26 +02:00
ToggleAudio ( on ) ;
ToggleAFDAC ( on ) ;
ToggleAFBit ( on ) ;
if ( on ) {
listenT = 1000 ;
BK4819_WriteRegister ( 0x43 , listenBWRegValues [ settings . listenBw ] ) ;
} else {
BK4819_WriteRegister ( 0x43 , GetBWRegValueForScan ( ) ) ;
}
}
// Scan info
static void ResetScanStats ( ) {
scanInfo . rssi = 0 ;
scanInfo . rssiMax = 0 ;
scanInfo . iPeak = 0 ;
scanInfo . fPeak = 0 ;
}
static void InitScan ( ) {
ResetScanStats ( ) ;
scanInfo . i = 0 ;
scanInfo . f = GetFStart ( ) ;
scanInfo . scanStep = GetScanStep ( ) ;
scanInfo . measurementsCount = GetStepsCount ( ) ;
}
static void ResetBlacklist ( ) {
for ( int i = 0 ; i < 128 ; + + i ) {
if ( rssiHistory [ i ] = = RSSI_MAX_VALUE )
rssiHistory [ i ] = 0 ;
}
}
static void RelaunchScan ( ) {
InitScan ( ) ;
ResetPeak ( ) ;
ToggleRX ( false ) ;
2023-10-01 12:38:55 +02:00
# ifdef SPECTRUM_AUTOMATIC_SQUELCH
2023-09-29 15:01:26 +02:00
settings . rssiTriggerLevel = RSSI_MAX_VALUE ;
2023-10-01 12:38:55 +02:00
# endif
2023-09-29 15:01:26 +02:00
preventKeypress = true ;
scanInfo . rssiMin = RSSI_MAX_VALUE ;
}
static void UpdateScanInfo ( ) {
if ( scanInfo . rssi > scanInfo . rssiMax ) {
scanInfo . rssiMax = scanInfo . rssi ;
scanInfo . fPeak = scanInfo . f ;
scanInfo . iPeak = scanInfo . i ;
}
if ( scanInfo . rssi < scanInfo . rssiMin ) {
scanInfo . rssiMin = scanInfo . rssi ;
settings . dbMin = Rssi2DBm ( scanInfo . rssiMin ) ;
redrawStatus = true ;
}
}
static void AutoTriggerLevel ( ) {
if ( settings . rssiTriggerLevel = = RSSI_MAX_VALUE ) {
settings . rssiTriggerLevel = clamp ( scanInfo . rssiMax + 8 , 0 , RSSI_MAX_VALUE ) ;
}
}
static void UpdatePeakInfoForce ( ) {
peak . t = 0 ;
peak . rssi = scanInfo . rssiMax ;
peak . f = scanInfo . fPeak ;
peak . i = scanInfo . iPeak ;
AutoTriggerLevel ( ) ;
}
static void UpdatePeakInfo ( ) {
if ( peak . f = = 0 | | peak . t > = 1024 | | peak . rssi < scanInfo . rssiMax )
UpdatePeakInfoForce ( ) ;
}
static void Measure ( ) { rssiHistory [ scanInfo . i ] = scanInfo . rssi = GetRssi ( ) ; }
// Update things by keypress
2023-11-02 01:46:07 +01:00
static uint16_t dbm2rssi ( int dBm )
{
2023-12-02 17:45:47 +00:00
return ( dBm + 160 - dBmCorrTable [ gRxVfo - > Band ] ) * 2 ;
2023-11-02 01:46:07 +01:00
}
static void ClampRssiTriggerLevel ( )
{
settings . rssiTriggerLevel = clamp ( settings . rssiTriggerLevel , dbm2rssi ( settings . dbMin ) , dbm2rssi ( settings . dbMax ) ) ;
}
2023-09-29 15:01:26 +02:00
static void UpdateRssiTriggerLevel ( bool inc ) {
if ( inc )
2023-11-02 01:46:07 +01:00
settings . rssiTriggerLevel + = 2 ;
2023-09-29 15:01:26 +02:00
else
2023-11-02 01:46:07 +01:00
settings . rssiTriggerLevel - = 2 ;
ClampRssiTriggerLevel ( ) ;
2023-09-29 15:01:26 +02:00
redrawScreen = true ;
2023-10-01 12:38:55 +02:00
redrawStatus = true ;
2023-09-29 15:01:26 +02:00
}
static void UpdateDBMax ( bool inc ) {
if ( inc & & settings . dbMax < 10 ) {
settings . dbMax + = 1 ;
} else if ( ! inc & & settings . dbMax > settings . dbMin ) {
settings . dbMax - = 1 ;
} else {
return ;
}
2023-11-02 01:46:07 +01:00
ClampRssiTriggerLevel ( ) ;
2023-09-29 15:01:26 +02:00
redrawStatus = true ;
2023-10-01 12:38:55 +02:00
redrawScreen = true ;
2023-09-29 15:01:26 +02:00
SYSTEM_DelayMs ( 20 ) ;
}
static void UpdateScanStep ( bool inc ) {
if ( inc & & settings . scanStepIndex < S_STEP_100_0kHz ) {
settings . scanStepIndex + + ;
} else if ( ! inc & & settings . scanStepIndex > 0 ) {
settings . scanStepIndex - - ;
} else {
return ;
}
settings . frequencyChangeStep = GetBW ( ) > > 1 ;
RelaunchScan ( ) ;
ResetBlacklist ( ) ;
2023-10-01 12:38:55 +02:00
redrawScreen = true ;
2023-09-29 15:01:26 +02:00
}
static void UpdateCurrentFreq ( bool inc ) {
if ( inc & & currentFreq < F_MAX ) {
currentFreq + = settings . frequencyChangeStep ;
} else if ( ! inc & & currentFreq > F_MIN ) {
currentFreq - = settings . frequencyChangeStep ;
} else {
return ;
}
RelaunchScan ( ) ;
ResetBlacklist ( ) ;
redrawScreen = true ;
}
static void UpdateCurrentFreqStill ( bool inc ) {
uint8_t offset = modulationTypeTuneSteps [ settings . modulationType ] ;
uint32_t f = fMeasure ;
if ( inc & & f < F_MAX ) {
f + = offset ;
} else if ( ! inc & & f > F_MIN ) {
f - = offset ;
}
SetF ( f ) ;
redrawScreen = true ;
}
static void UpdateFreqChangeStep ( bool inc ) {
uint16_t diff = GetScanStep ( ) * 4 ;
if ( inc & & settings . frequencyChangeStep < 200000 ) {
settings . frequencyChangeStep + = diff ;
} else if ( ! inc & & settings . frequencyChangeStep > 10000 ) {
settings . frequencyChangeStep - = diff ;
}
SYSTEM_DelayMs ( 100 ) ;
redrawScreen = true ;
}
static void ToggleModulation ( ) {
2023-10-31 17:23:10 +01:00
if ( settings . modulationType < MODULATION_UKNOWN - 1 ) {
2023-09-29 15:01:26 +02:00
settings . modulationType + + ;
} else {
2023-10-31 17:23:10 +01:00
settings . modulationType = MODULATION_FM ;
2023-09-29 15:01:26 +02:00
}
2023-10-31 17:23:10 +01:00
RADIO_SetModulation ( settings . modulationType ) ;
2023-12-02 17:45:47 +00:00
BK4819_SetAGC ( settings . modulationType ! = MODULATION_AM | | ! gSetting_AM_fix ) ;
BK4819_InitAGC ( ) ;
RelaunchScan ( ) ;
2023-09-29 15:01:26 +02:00
redrawScreen = true ;
}
static void ToggleListeningBW ( ) {
if ( settings . listenBw = = BK4819_FILTER_BW_NARROWER ) {
settings . listenBw = BK4819_FILTER_BW_WIDE ;
} else {
settings . listenBw + + ;
}
redrawScreen = true ;
}
static void ToggleBacklight ( ) {
settings . backlightState = ! settings . backlightState ;
if ( settings . backlightState ) {
2023-10-25 20:47:14 +02:00
BACKLIGHT_TurnOn ( ) ;
2023-09-29 15:01:26 +02:00
} else {
2023-10-25 20:47:14 +02:00
BACKLIGHT_TurnOff ( ) ;
2023-09-29 15:01:26 +02:00
}
}
static void ToggleStepsCount ( ) {
if ( settings . stepsCount = = STEPS_128 ) {
settings . stepsCount = STEPS_16 ;
} else {
settings . stepsCount - - ;
}
settings . frequencyChangeStep = GetBW ( ) > > 1 ;
RelaunchScan ( ) ;
ResetBlacklist ( ) ;
2023-10-01 12:38:55 +02:00
redrawScreen = true ;
2023-09-29 15:01:26 +02:00
}
static void ResetFreqInput ( ) {
tempFreq = 0 ;
for ( int i = 0 ; i < 10 ; + + i ) {
freqInputString [ i ] = ' - ' ;
}
}
static void FreqInput ( ) {
freqInputIndex = 0 ;
freqInputDotIndex = 0 ;
ResetFreqInput ( ) ;
SetState ( FREQ_INPUT ) ;
}
static void UpdateFreqInput ( KEY_Code_t key ) {
if ( key ! = KEY_EXIT & & freqInputIndex > = 10 ) {
return ;
}
if ( key = = KEY_STAR ) {
if ( freqInputIndex = = 0 | | freqInputDotIndex ) {
return ;
}
freqInputDotIndex = freqInputIndex ;
}
if ( key = = KEY_EXIT ) {
freqInputIndex - - ;
2023-11-26 19:05:44 +01:00
if ( freqInputDotIndex = = freqInputIndex )
freqInputDotIndex = 0 ;
2023-09-29 15:01:26 +02:00
} else {
freqInputArr [ freqInputIndex + + ] = key ;
}
ResetFreqInput ( ) ;
uint8_t dotIndex =
freqInputDotIndex = = 0 ? freqInputIndex : freqInputDotIndex ;
KEY_Code_t digitKey ;
for ( int i = 0 ; i < 10 ; + + i ) {
if ( i < freqInputIndex ) {
digitKey = freqInputArr [ i ] ;
2023-10-06 19:57:08 +02:00
freqInputString [ i ] = digitKey < = KEY_9 ? ' 0 ' + digitKey - KEY_0 : ' . ' ;
2023-09-29 15:01:26 +02:00
} else {
freqInputString [ i ] = ' - ' ;
}
}
uint32_t base = 100000 ; // 1MHz in BK units
for ( int i = dotIndex - 1 ; i > = 0 ; - - i ) {
2023-10-06 19:57:08 +02:00
tempFreq + = ( freqInputArr [ i ] - KEY_0 ) * base ;
2023-09-29 15:01:26 +02:00
base * = 10 ;
}
base = 10000 ; // 0.1MHz in BK units
if ( dotIndex < freqInputIndex ) {
for ( int i = dotIndex + 1 ; i < freqInputIndex ; + + i ) {
2023-10-06 19:57:08 +02:00
tempFreq + = ( freqInputArr [ i ] - KEY_0 ) * base ;
2023-09-29 15:01:26 +02:00
base / = 10 ;
}
}
redrawScreen = true ;
}
static void Blacklist ( ) {
rssiHistory [ peak . i ] = RSSI_MAX_VALUE ;
ResetPeak ( ) ;
ToggleRX ( false ) ;
newScanStart = true ;
}
// Draw things
2023-10-01 12:38:55 +02:00
// applied x2 to prevent initial rounding
2023-09-29 15:01:26 +02:00
uint8_t Rssi2PX ( uint16_t rssi , uint8_t pxMin , uint8_t pxMax ) {
2023-10-01 12:38:55 +02:00
const int DB_MIN = settings . dbMin < < 1 ;
const int DB_MAX = settings . dbMax < < 1 ;
2023-09-29 15:01:26 +02:00
const int DB_RANGE = DB_MAX - DB_MIN ;
const uint8_t PX_RANGE = pxMax - pxMin ;
2023-12-02 17:45:47 +00:00
int dbm = clamp ( Rssi2DBm ( rssi ) < < 1 , DB_MIN , DB_MAX ) ;
2023-09-29 15:01:26 +02:00
return ( ( dbm - DB_MIN ) * PX_RANGE + DB_RANGE / 2 ) / DB_RANGE + pxMin ;
}
uint8_t Rssi2Y ( uint16_t rssi ) {
return DrawingEndY - Rssi2PX ( rssi , 0 , DrawingEndY ) ;
}
static void DrawSpectrum ( ) {
for ( uint8_t x = 0 ; x < 128 ; + + x ) {
uint16_t rssi = rssiHistory [ x > > settings . stepsCount ] ;
if ( rssi ! = RSSI_MAX_VALUE ) {
2023-11-22 23:43:39 +01:00
DrawVLine ( Rssi2Y ( rssi ) , DrawingEndY , x , true ) ;
2023-09-29 15:01:26 +02:00
}
}
}
static void DrawStatus ( ) {
2023-10-01 12:38:55 +02:00
# ifdef SPECTRUM_EXTRA_VALUES
sprintf ( String , " %d/%d P:%d T:%d " , settings . dbMin , settings . dbMax ,
Rssi2DBm ( peak . rssi ) , Rssi2DBm ( settings . rssiTriggerLevel ) ) ;
# else
2023-09-29 15:01:26 +02:00
sprintf ( String , " %d/%d " , settings . dbMin , settings . dbMax ) ;
2023-10-01 12:38:55 +02:00
# endif
GUI_DisplaySmallest ( String , 0 , 1 , true , true ) ;
2023-11-22 23:43:39 +01:00
BOARD_ADC_GetBatteryInfo ( & gBatteryVoltages [ gBatteryCheckCounter + + % 4 ] , & gBatteryCurrent ) ;
2023-09-29 15:01:26 +02:00
2023-11-22 23:43:39 +01:00
uint16_t voltage = ( gBatteryVoltages [ 0 ] + gBatteryVoltages [ 1 ] + gBatteryVoltages [ 2 ] +
2023-09-29 15:01:26 +02:00
gBatteryVoltages [ 3 ] ) /
2023-11-22 23:43:39 +01:00
4 * 760 / gBatteryCalibration [ 3 ] ;
2023-09-29 15:01:26 +02:00
2023-11-22 23:43:39 +01:00
unsigned perc = BATTERY_VoltsToPercent ( voltage ) ;
2023-09-29 15:01:26 +02:00
2023-11-22 23:43:39 +01:00
// sprintf(String, "%d %d", voltage, perc);
// GUI_DisplaySmallest(String, 48, 1, true, true);
gStatusLine [ 116 ] = 0 b00011100 ;
gStatusLine [ 117 ] = 0 b00111110 ;
for ( int i = 118 ; i < = 126 ; i + + ) {
gStatusLine [ i ] = 0 b00100010 ;
2023-09-29 15:01:26 +02:00
}
2023-11-22 23:43:39 +01:00
for ( unsigned i = 127 ; i > = 118 ; i - - ) {
if ( 127 - i < = ( perc + 5 ) * 9 / 100 ) {
gStatusLine [ i ] = 0 b00111110 ;
2023-09-29 15:01:26 +02:00
}
}
}
static void DrawF ( uint32_t f ) {
sprintf ( String , " %u.%05u " , f / 100000 , f % 100000 ) ;
2023-10-01 12:38:55 +02:00
UI_PrintStringSmall ( String , 8 , 127 , 0 ) ;
2023-09-29 15:01:26 +02:00
2023-10-31 17:23:10 +01:00
sprintf ( String , " %3s " , gModulationStr [ settings . modulationType ] ) ;
2023-10-01 12:38:55 +02:00
GUI_DisplaySmallest ( String , 116 , 1 , false , true ) ;
2023-09-29 15:01:26 +02:00
sprintf ( String , " %s " , bwOptions [ settings . listenBw ] ) ;
2023-10-01 12:38:55 +02:00
GUI_DisplaySmallest ( String , 108 , 7 , false , true ) ;
2023-09-29 15:01:26 +02:00
}
static void DrawNums ( ) {
if ( currentState = = SPECTRUM ) {
sprintf ( String , " %ux " , GetStepsCount ( ) ) ;
GUI_DisplaySmallest ( String , 0 , 1 , false , true ) ;
sprintf ( String , " %u.%02uk " , GetScanStep ( ) / 100 , GetScanStep ( ) % 100 ) ;
GUI_DisplaySmallest ( String , 0 , 7 , false , true ) ;
}
if ( IsCenterMode ( ) ) {
2023-10-20 16:52:15 +02:00
sprintf ( String , " %u.%05u \x7F %u.%02uk " , currentFreq / 100000 ,
2023-09-29 15:01:26 +02:00
currentFreq % 100000 , settings . frequencyChangeStep / 100 ,
settings . frequencyChangeStep % 100 ) ;
GUI_DisplaySmallest ( String , 36 , 49 , false , true ) ;
} else {
sprintf ( String , " %u.%05u " , GetFStart ( ) / 100000 , GetFStart ( ) % 100000 ) ;
GUI_DisplaySmallest ( String , 0 , 49 , false , true ) ;
2023-10-20 16:52:15 +02:00
sprintf ( String , " \x7F %u.%02uk " , settings . frequencyChangeStep / 100 ,
2023-09-29 15:01:26 +02:00
settings . frequencyChangeStep % 100 ) ;
GUI_DisplaySmallest ( String , 48 , 49 , false , true ) ;
sprintf ( String , " %u.%05u " , GetFEnd ( ) / 100000 , GetFEnd ( ) % 100000 ) ;
GUI_DisplaySmallest ( String , 93 , 49 , false , true ) ;
}
}
static void DrawRssiTriggerLevel ( ) {
if ( settings . rssiTriggerLevel = = RSSI_MAX_VALUE | | monitorMode )
return ;
uint8_t y = Rssi2Y ( settings . rssiTriggerLevel ) ;
for ( uint8_t x = 0 ; x < 128 ; x + = 2 ) {
PutPixel ( x , y , true ) ;
}
}
static void DrawTicks ( ) {
uint32_t f = GetFStart ( ) % 100000 ;
uint32_t step = GetScanStep ( ) ;
for ( uint8_t i = 0 ; i < 128 ; i + = ( 1 < < settings . stepsCount ) , f + = step ) {
uint8_t barValue = 0 b00000001 ;
( f % 10000 ) < step & & ( barValue | = 0 b00000010 ) ;
( f % 50000 ) < step & & ( barValue | = 0 b00000100 ) ;
( f % 100000 ) < step & & ( barValue | = 0 b00011000 ) ;
gFrameBuffer [ 5 ] [ i ] | = barValue ;
}
// center
if ( IsCenterMode ( ) ) {
2023-11-22 23:43:39 +01:00
memset ( gFrameBuffer [ 5 ] + 62 , 0x80 , 5 ) ;
2023-09-29 15:01:26 +02:00
gFrameBuffer [ 5 ] [ 64 ] = 0xff ;
} else {
2023-11-22 23:43:39 +01:00
memset ( gFrameBuffer [ 5 ] + 1 , 0x80 , 3 ) ;
memset ( gFrameBuffer [ 5 ] + 124 , 0x80 , 3 ) ;
2023-09-29 15:01:26 +02:00
gFrameBuffer [ 5 ] [ 0 ] = 0xff ;
gFrameBuffer [ 5 ] [ 127 ] = 0xff ;
}
}
static void DrawArrow ( uint8_t x ) {
for ( signed i = - 2 ; i < = 2 ; + + i ) {
signed v = x + i ;
if ( ! ( v & 128 ) ) {
gFrameBuffer [ 5 ] [ v ] | = ( 0 b01111000 < < my_abs ( i ) ) & 0 b01111000 ;
}
}
}
static void OnKeyDown ( uint8_t key ) {
switch ( key ) {
case KEY_3 :
UpdateDBMax ( true ) ;
break ;
case KEY_9 :
UpdateDBMax ( false ) ;
break ;
case KEY_1 :
UpdateScanStep ( true ) ;
break ;
case KEY_7 :
UpdateScanStep ( false ) ;
break ;
case KEY_2 :
UpdateFreqChangeStep ( true ) ;
break ;
case KEY_8 :
UpdateFreqChangeStep ( false ) ;
break ;
case KEY_UP :
UpdateCurrentFreq ( true ) ;
break ;
case KEY_DOWN :
UpdateCurrentFreq ( false ) ;
break ;
case KEY_SIDE1 :
Blacklist ( ) ;
break ;
case KEY_STAR :
UpdateRssiTriggerLevel ( true ) ;
break ;
case KEY_F :
UpdateRssiTriggerLevel ( false ) ;
break ;
case KEY_5 :
FreqInput ( ) ;
break ;
case KEY_0 :
ToggleModulation ( ) ;
break ;
case KEY_6 :
ToggleListeningBW ( ) ;
break ;
case KEY_4 :
ToggleStepsCount ( ) ;
break ;
case KEY_SIDE2 :
ToggleBacklight ( ) ;
break ;
case KEY_PTT :
SetState ( STILL ) ;
TuneToPeak ( ) ;
break ;
case KEY_MENU :
break ;
case KEY_EXIT :
if ( menuState ) {
menuState = 0 ;
break ;
}
DeInitSpectrum ( ) ;
break ;
default :
break ;
}
}
static void OnKeyDownFreqInput ( uint8_t key ) {
switch ( key ) {
case KEY_0 :
case KEY_1 :
case KEY_2 :
case KEY_3 :
case KEY_4 :
case KEY_5 :
case KEY_6 :
case KEY_7 :
case KEY_8 :
case KEY_9 :
case KEY_STAR :
UpdateFreqInput ( key ) ;
break ;
case KEY_EXIT :
if ( freqInputIndex = = 0 ) {
SetState ( previousState ) ;
break ;
}
UpdateFreqInput ( key ) ;
break ;
case KEY_MENU :
if ( tempFreq < F_MIN | | tempFreq > F_MAX ) {
break ;
}
SetState ( previousState ) ;
currentFreq = tempFreq ;
if ( currentState = = SPECTRUM ) {
ResetBlacklist ( ) ;
RelaunchScan ( ) ;
} else {
SetF ( currentFreq ) ;
}
break ;
default :
break ;
}
}
void OnKeyDownStill ( KEY_Code_t key ) {
switch ( key ) {
case KEY_3 :
UpdateDBMax ( true ) ;
break ;
case KEY_9 :
UpdateDBMax ( false ) ;
break ;
case KEY_UP :
if ( menuState ) {
SetRegMenuValue ( menuState , true ) ;
break ;
}
UpdateCurrentFreqStill ( true ) ;
break ;
case KEY_DOWN :
if ( menuState ) {
SetRegMenuValue ( menuState , false ) ;
break ;
}
UpdateCurrentFreqStill ( false ) ;
break ;
case KEY_STAR :
UpdateRssiTriggerLevel ( true ) ;
break ;
case KEY_F :
UpdateRssiTriggerLevel ( false ) ;
break ;
case KEY_5 :
FreqInput ( ) ;
break ;
case KEY_0 :
ToggleModulation ( ) ;
break ;
case KEY_6 :
ToggleListeningBW ( ) ;
break ;
case KEY_SIDE1 :
monitorMode = ! monitorMode ;
break ;
case KEY_SIDE2 :
ToggleBacklight ( ) ;
break ;
case KEY_PTT :
// TODO: start transmit
2023-10-18 17:24:56 +02:00
/* BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
BK4819_ToggleGpioOut ( BK4819_GPIO5_PIN1_RED , true ) ; */
2023-09-29 15:01:26 +02:00
break ;
case KEY_MENU :
if ( menuState = = ARRAY_SIZE ( registerSpecs ) - 1 ) {
menuState = 1 ;
} else {
menuState + + ;
}
redrawScreen = true ;
break ;
case KEY_EXIT :
if ( ! menuState ) {
SetState ( SPECTRUM ) ;
monitorMode = false ;
RelaunchScan ( ) ;
break ;
}
menuState = 0 ;
break ;
default :
break ;
}
}
static void RenderFreqInput ( ) {
UI_PrintString ( freqInputString , 2 , 127 , 0 , 8 ) ;
}
static void RenderStatus ( ) {
memset ( gStatusLine , 0 , sizeof ( gStatusLine ) ) ;
DrawStatus ( ) ;
ST7565_BlitStatusLine ( ) ;
}
static void RenderSpectrum ( ) {
DrawTicks ( ) ;
DrawArrow ( peak . i < < settings . stepsCount ) ;
DrawSpectrum ( ) ;
DrawRssiTriggerLevel ( ) ;
DrawF ( peak . f ) ;
DrawNums ( ) ;
}
static void RenderStill ( ) {
DrawF ( fMeasure ) ;
const uint8_t METER_PAD_LEFT = 3 ;
for ( int i = 0 ; i < 121 ; i + + ) {
if ( i % 10 = = 0 ) {
gFrameBuffer [ 2 ] [ i + METER_PAD_LEFT ] = 0 b01110000 ;
} else if ( i % 5 = = 0 ) {
gFrameBuffer [ 2 ] [ i + METER_PAD_LEFT ] = 0 b00110000 ;
} else {
gFrameBuffer [ 2 ] [ i + METER_PAD_LEFT ] = 0 b00010000 ;
}
}
uint8_t x = Rssi2PX ( scanInfo . rssi , 0 , 121 ) ;
for ( int i = 0 ; i < x ; + + i ) {
if ( i % 5 ) {
gFrameBuffer [ 2 ] [ i + METER_PAD_LEFT ] | = 0 b00000111 ;
}
}
int dbm = Rssi2DBm ( scanInfo . rssi ) ;
uint8_t s = DBm2S ( dbm ) ;
sprintf ( String , " S: %u " , s ) ;
GUI_DisplaySmallest ( String , 4 , 25 , false , true ) ;
sprintf ( String , " %d dBm " , dbm ) ;
GUI_DisplaySmallest ( String , 28 , 25 , false , true ) ;
if ( ! monitorMode ) {
uint8_t x = Rssi2PX ( settings . rssiTriggerLevel , 0 , 121 ) ;
gFrameBuffer [ 2 ] [ METER_PAD_LEFT + x ] = 0 b11111111 ;
}
const uint8_t PAD_LEFT = 4 ;
const uint8_t CELL_WIDTH = 30 ;
uint8_t offset = PAD_LEFT ;
uint8_t row = 4 ;
for ( int i = 0 , idx = 1 ; idx < = 4 ; + + i , + + idx ) {
if ( idx = = 5 ) {
row + = 2 ;
i = 0 ;
}
offset = PAD_LEFT + i * CELL_WIDTH ;
if ( menuState = = idx ) {
for ( int j = 0 ; j < CELL_WIDTH ; + + j ) {
gFrameBuffer [ row ] [ j + offset ] = 0xFF ;
gFrameBuffer [ row + 1 ] [ j + offset ] = 0xFF ;
}
}
sprintf ( String , " %s " , registerSpecs [ idx ] . name ) ;
GUI_DisplaySmallest ( String , offset + 2 , row * 8 + 2 , false ,
menuState ! = idx ) ;
sprintf ( String , " %u " , GetRegMenuValue ( idx ) ) ;
GUI_DisplaySmallest ( String , offset + 2 , ( row + 1 ) * 8 + 1 , false ,
menuState ! = idx ) ;
}
}
static void Render ( ) {
memset ( gFrameBuffer , 0 , sizeof ( gFrameBuffer ) ) ;
switch ( currentState ) {
case SPECTRUM :
RenderSpectrum ( ) ;
break ;
case FREQ_INPUT :
RenderFreqInput ( ) ;
break ;
case STILL :
RenderStill ( ) ;
break ;
}
ST7565_BlitFullScreen ( ) ;
}
bool HandleUserInput ( ) {
kbd . prev = kbd . current ;
kbd . current = GetKey ( ) ;
2023-11-26 19:05:44 +01:00
if ( kbd . current ! = KEY_INVALID & & kbd . current = = kbd . prev ) {
if ( kbd . counter < 16 )
kbd . counter + + ;
else
kbd . counter - = 3 ;
2023-09-29 15:01:26 +02:00
SYSTEM_DelayMs ( 20 ) ;
}
2023-11-26 19:05:44 +01:00
else {
kbd . counter = 0 ;
}
2023-09-29 15:01:26 +02:00
2023-11-26 19:05:44 +01:00
if ( kbd . counter = = 3 | | kbd . counter = = 16 ) {
2023-09-29 15:01:26 +02:00
switch ( currentState ) {
case SPECTRUM :
OnKeyDown ( kbd . current ) ;
break ;
case FREQ_INPUT :
OnKeyDownFreqInput ( kbd . current ) ;
break ;
case STILL :
OnKeyDownStill ( kbd . current ) ;
break ;
}
}
return true ;
}
static void Scan ( ) {
if ( rssiHistory [ scanInfo . i ] ! = RSSI_MAX_VALUE ) {
SetF ( scanInfo . f ) ;
Measure ( ) ;
UpdateScanInfo ( ) ;
}
}
static void NextScanStep ( ) {
+ + peak . t ;
+ + scanInfo . i ;
scanInfo . f + = scanInfo . scanStep ;
}
static void UpdateScan ( ) {
Scan ( ) ;
if ( scanInfo . i < scanInfo . measurementsCount ) {
NextScanStep ( ) ;
return ;
}
redrawScreen = true ;
preventKeypress = false ;
UpdatePeakInfo ( ) ;
if ( IsPeakOverLevel ( ) ) {
ToggleRX ( true ) ;
TuneToPeak ( ) ;
return ;
}
newScanStart = true ;
}
static void UpdateStill ( ) {
Measure ( ) ;
redrawScreen = true ;
preventKeypress = false ;
peak . rssi = scanInfo . rssi ;
AutoTriggerLevel ( ) ;
ToggleRX ( IsPeakOverLevel ( ) | | monitorMode ) ;
}
static void UpdateListening ( ) {
preventKeypress = false ;
if ( currentState = = STILL ) {
listenT = 0 ;
}
if ( listenT ) {
listenT - - ;
SYSTEM_DelayMs ( 1 ) ;
return ;
}
if ( currentState = = SPECTRUM ) {
BK4819_WriteRegister ( 0x43 , GetBWRegValueForScan ( ) ) ;
Measure ( ) ;
BK4819_WriteRegister ( 0x43 , listenBWRegValues [ settings . listenBw ] ) ;
} else {
Measure ( ) ;
}
peak . rssi = scanInfo . rssi ;
redrawScreen = true ;
if ( IsPeakOverLevel ( ) | | monitorMode ) {
listenT = 1000 ;
return ;
}
ToggleRX ( false ) ;
newScanStart = true ;
}
static void Tick ( ) {
2023-12-02 17:45:47 +00:00
if ( settings . modulationType = = MODULATION_AM )
{
AM_fix_10ms ( vfo ) ; //allow AM_Fix to apply its AGC action
}
2023-09-29 15:01:26 +02:00
if ( ! preventKeypress ) {
HandleUserInput ( ) ;
}
if ( newScanStart ) {
InitScan ( ) ;
newScanStart = false ;
}
if ( isListening & & currentState ! = FREQ_INPUT ) {
UpdateListening ( ) ;
} else {
if ( currentState = = SPECTRUM ) {
UpdateScan ( ) ;
} else if ( currentState = = STILL ) {
UpdateStill ( ) ;
}
}
if ( redrawStatus | | + + statuslineUpdateTimer > 4096 ) {
RenderStatus ( ) ;
redrawStatus = false ;
statuslineUpdateTimer = 0 ;
}
if ( redrawScreen ) {
Render ( ) ;
redrawScreen = false ;
}
}
void APP_RunSpectrum ( ) {
// TX here coz it always? set to active VFO
2023-12-02 17:45:47 +00:00
vfo = gEeprom . TX_VFO ;
currentFreq = initialFreq = gEeprom . VfoInfo [ vfo ] . pRX - > Frequency - ( ( GetStepsCount ( ) / 2 ) * GetScanStep ( ) ) ; //set the current frequency in the middle of the display
2023-09-29 15:01:26 +02:00
BackupRegisters ( ) ;
isListening = true ; // to turn off RX later
redrawStatus = true ;
redrawScreen = false ; // we will wait until scan done
newScanStart = true ;
2023-10-01 12:38:55 +02:00
ToggleRX ( true ) , ToggleRX ( false ) ; // hack to prevent noise when squelch off
2023-12-02 17:45:47 +00:00
RADIO_SetModulation ( settings . modulationType = gRxVfo - > Modulation ) ;
BK4819_SetAGC ( settings . modulationType ! = MODULATION_AM | | ! gSetting_AM_fix ) ;
2023-09-29 15:01:26 +02:00
BK4819_SetFilterBandwidth ( settings . listenBw = BK4819_FILTER_BW_WIDE , false ) ;
RelaunchScan ( ) ;
for ( int i = 0 ; i < 128 ; + + i ) {
rssiHistory [ i ] = 0 ;
}
isInitialized = true ;
while ( isInitialized ) {
Tick ( ) ;
}
}