Refactor local oscillator cal
This commit is contained in:
parent
ec409d8805
commit
d0900aa392
@ -23,7 +23,6 @@ void formatFreq(uint32_t freq, char* buff, uint16_t buff_size);
|
|||||||
/* touch functions */
|
/* touch functions */
|
||||||
boolean readTouch();
|
boolean readTouch();
|
||||||
|
|
||||||
void setupTouch();
|
|
||||||
void scaleTouch(struct Point *p);
|
void scaleTouch(struct Point *p);
|
||||||
|
|
||||||
// Color definitions
|
// Color definitions
|
||||||
|
165
setup.cpp
165
setup.cpp
@ -1,9 +1,10 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <EEPROM.h>
|
#include "freeRam.h"
|
||||||
#include "morse.h"
|
#include "morse.h"
|
||||||
|
#include "nano_gui.h"
|
||||||
|
#include "setup.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "ubitx.h"
|
#include "ubitx.h"
|
||||||
#include "nano_gui.h"
|
|
||||||
|
|
||||||
/** Menus
|
/** Menus
|
||||||
* The Radio menus are accessed by tapping on the function button.
|
* The Radio menus are accessed by tapping on the function button.
|
||||||
@ -85,43 +86,6 @@ struct SettingScreen_t {
|
|||||||
void (*Finalize)(const long int final_value);
|
void (*Finalize)(const long int final_value);
|
||||||
};
|
};
|
||||||
|
|
||||||
#define LIMIT(val,min,max) ((val) < (min)) ? (min) : (((max) < (val)) ? (max) : (val))
|
|
||||||
ssCwToneInitialize(long int* start_value_out)
|
|
||||||
{
|
|
||||||
*start_value_out = globalSettings.cwSideToneFreq;
|
|
||||||
}
|
|
||||||
ssCwToneValidate(const long int candidate_value_in, long int* validated_value_out)
|
|
||||||
{
|
|
||||||
*validated_value_out = LIMIT(candidate_value_in,100,2000);
|
|
||||||
}
|
|
||||||
ssCwToneChange(const long int new_value, char* buff_out, const size_t buff_out_size)
|
|
||||||
{
|
|
||||||
globalSettings.cwSideToneFreq = new_value;
|
|
||||||
tone(CW_TONE, globalSettings.cwSideToneFreq);
|
|
||||||
ltoa(globalSettings.cwSideToneFreq,buff_out,10);
|
|
||||||
}
|
|
||||||
ssCwToneFinalize(const long int final_value)
|
|
||||||
{
|
|
||||||
noTone(CW_TONE);
|
|
||||||
globalSettings.cwSideToneFreq = final_value;
|
|
||||||
SaveSettingsToEeprom();
|
|
||||||
}
|
|
||||||
const char SS_EMPTY [] PROGMEM = "";
|
|
||||||
const char SS_CW_TONE [] PROGMEM = "Set CW Tone (Hz)";
|
|
||||||
const char SS_LONG_TEXT [] PROGMEM = "This is a long string of text that should be wrapped at least once, possibly more.";
|
|
||||||
const SettingScreen_t settingScreens [] PROGMEM = {
|
|
||||||
SS_CW_TONE,
|
|
||||||
SS_LONG_TEXT,
|
|
||||||
1,
|
|
||||||
10,
|
|
||||||
ssCwToneInitialize,
|
|
||||||
ssCwToneValidate,
|
|
||||||
ssCwToneChange,
|
|
||||||
ssCwToneFinalize
|
|
||||||
};
|
|
||||||
void runSetting(const SettingScreen_t* const screen);
|
|
||||||
void runToneSetting(){runSetting(&settingScreens[0]);}
|
|
||||||
|
|
||||||
void runSetting(const SettingScreen_t* const p_screen)
|
void runSetting(const SettingScreen_t* const p_screen)
|
||||||
{
|
{
|
||||||
SettingScreen_t screen = {0};
|
SettingScreen_t screen = {0};
|
||||||
@ -142,7 +106,7 @@ void runSetting(const SettingScreen_t* const p_screen)
|
|||||||
screen.OnValueChange(last_value,b,sizeof(b));
|
screen.OnValueChange(last_value,b,sizeof(b));
|
||||||
displayText(b, LAYOUT_SETTING_VALUE_X, LAYOUT_SETTING_VALUE_Y, LAYOUT_SETTING_VALUE_WIDTH, LAYOUT_SETTING_VALUE_HEIGHT, COLOR_TEXT, COLOR_TITLE_BACKGROUND, COLOR_BACKGROUND);
|
displayText(b, LAYOUT_SETTING_VALUE_X, LAYOUT_SETTING_VALUE_Y, LAYOUT_SETTING_VALUE_WIDTH, LAYOUT_SETTING_VALUE_HEIGHT, COLOR_TEXT, COLOR_TITLE_BACKGROUND, COLOR_BACKGROUND);
|
||||||
|
|
||||||
raw_value = last_value * screen.KnobDivider;
|
raw_value = last_value * (int32_t)screen.KnobDivider;
|
||||||
|
|
||||||
while (!btnDown())
|
while (!btnDown())
|
||||||
{
|
{
|
||||||
@ -176,73 +140,96 @@ void runSetting(const SettingScreen_t* const p_screen)
|
|||||||
screen.Finalize(last_value);
|
screen.Finalize(last_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void printCarrierFreq(unsigned long freq)
|
#define LIMIT(val,min,max) ((val) < (min)) ? (min) : (((max) < (val)) ? (max) : (val))
|
||||||
|
|
||||||
|
ssCwToneInitialize(long int* start_value_out)
|
||||||
{
|
{
|
||||||
formatFreq(freq,c,sizeof(c));
|
*start_value_out = globalSettings.cwSideToneFreq;
|
||||||
displayText(c, LAYOUT_SETTING_VALUE_X, LAYOUT_SETTING_VALUE_Y, LAYOUT_SETTING_VALUE_WIDTH, LAYOUT_SETTING_VALUE_HEIGHT, COLOR_TEXT, COLOR_TITLE_BACKGROUND, COLOR_BACKGROUND);
|
|
||||||
}
|
}
|
||||||
|
ssCwToneValidate(const long int candidate_value_in, long int* validated_value_out)
|
||||||
|
{
|
||||||
|
*validated_value_out = LIMIT(candidate_value_in,100,2000);
|
||||||
|
}
|
||||||
|
ssCwToneChange(const long int new_value, char* buff_out, const size_t buff_out_size)
|
||||||
|
{
|
||||||
|
globalSettings.cwSideToneFreq = new_value;
|
||||||
|
tone(CW_TONE, globalSettings.cwSideToneFreq);
|
||||||
|
ltoa(globalSettings.cwSideToneFreq,buff_out,10);
|
||||||
|
strcat_P(buff_out,(const char*)F("Hz"));
|
||||||
|
}
|
||||||
|
ssCwToneFinalize(const long int final_value)
|
||||||
|
{
|
||||||
|
noTone(CW_TONE);
|
||||||
|
globalSettings.cwSideToneFreq = final_value;
|
||||||
|
SaveSettingsToEeprom();
|
||||||
|
}
|
||||||
|
const char SS_CW_TONE_T [] PROGMEM = "Set CW Tone";
|
||||||
|
const char SS_CW_TONE_A [] PROGMEM = "Select a frequency that\nCW mode to tune for";
|
||||||
|
const SettingScreen_t ssTone PROGMEM = {
|
||||||
|
SS_CW_TONE_T,
|
||||||
|
SS_CW_TONE_A,
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
ssCwToneInitialize,
|
||||||
|
ssCwToneValidate,
|
||||||
|
ssCwToneChange,
|
||||||
|
ssCwToneFinalize
|
||||||
|
};
|
||||||
|
void runToneSetting(){runSetting(&ssTone);}
|
||||||
|
|
||||||
void setupFreq(){
|
void ssLocalOscInitialize(long int* start_value_out){
|
||||||
//displayDialog(F("Set Frequency"),F("Push TUNE to Save"));
|
//round off the current frequency the nearest kHz
|
||||||
|
|
||||||
//round off the the nearest khz
|
|
||||||
{
|
{
|
||||||
uint32_t freq = GetActiveVfoFreq();
|
uint32_t freq = GetActiveVfoFreq();
|
||||||
freq = (freq/1000l)* 1000l;
|
freq = (freq/1000L) * 1000L;
|
||||||
setFrequency(freq);
|
setFrequency(freq);
|
||||||
|
si5351bx_setfreq(0, globalSettings.usbCarrierFreq); //set back the carrier oscillator, cw tx switches it off
|
||||||
}
|
}
|
||||||
|
*start_value_out = globalSettings.oscillatorCal;
|
||||||
strcpy_P(c,(const char*)F("You should have a"));
|
Serial.println(freeRam());
|
||||||
displayText(c, LAYOUT_SETTING_VALUE_X, LAYOUT_ITEM_Y, LAYOUT_ITEM_WIDTH, LAYOUT_ITEM_HEIGHT, COLOR_TEXT, COLOR_BACKGROUND, COLOR_BACKGROUND);
|
}
|
||||||
strcpy_P(c,(const char*)F("signal exactly at"));
|
void ssLocalOscValidate(const long int candidate_value_in, long int* validated_value_out)
|
||||||
displayText(c, LAYOUT_SETTING_VALUE_X, LAYOUT_ITEM_Y + 1*LAYOUT_ITEM_PITCH_Y, LAYOUT_ITEM_WIDTH, LAYOUT_ITEM_HEIGHT, COLOR_TEXT, COLOR_BACKGROUND, COLOR_BACKGROUND);
|
|
||||||
ltoa(GetActiveVfoFreq()/1000L, c, 10);
|
|
||||||
strcat_P(c,(const char*)F(" KHz"));
|
|
||||||
displayText(c, LAYOUT_SETTING_VALUE_X, LAYOUT_ITEM_Y + 2*LAYOUT_ITEM_PITCH_Y, LAYOUT_ITEM_WIDTH, LAYOUT_ITEM_HEIGHT, COLOR_TEXT, COLOR_BACKGROUND, COLOR_BACKGROUND);
|
|
||||||
strcpy_P(c,(const char*)F("Rotate to zerobeat"));
|
|
||||||
displayText(c, LAYOUT_SETTING_VALUE_X, LAYOUT_ITEM_Y + 4*LAYOUT_ITEM_PITCH_Y, LAYOUT_ITEM_WIDTH, LAYOUT_ITEM_HEIGHT, COLOR_TEXT, COLOR_BACKGROUND, COLOR_BACKGROUND);
|
|
||||||
|
|
||||||
ltoa(globalSettings.oscillatorCal, b, 10);
|
|
||||||
displayText(b, LAYOUT_SETTING_VALUE_X, LAYOUT_SETTING_VALUE_Y, LAYOUT_SETTING_VALUE_WIDTH, LAYOUT_SETTING_VALUE_HEIGHT, COLOR_TEXT, COLOR_TITLE_BACKGROUND, COLOR_BACKGROUND);
|
|
||||||
//keep clear of any previous button press
|
|
||||||
while (btnDown())
|
|
||||||
active_delay(100);
|
|
||||||
active_delay(100);
|
|
||||||
|
|
||||||
while (!btnDown())
|
|
||||||
{
|
{
|
||||||
int knob = enc_read();
|
*validated_value_out = candidate_value_in;//No check - allow anything
|
||||||
if(knob != 0){
|
|
||||||
globalSettings.oscillatorCal += knob * 875;
|
|
||||||
}
|
}
|
||||||
else{
|
void ssLocalOscChange(const long int new_value, char* buff_out, const size_t buff_out_size)
|
||||||
continue; //don't update the frequency or the display
|
{
|
||||||
}
|
si5351_set_calibration(new_value);
|
||||||
|
|
||||||
si5351bx_setfreq(0, globalSettings.usbCarrierFreq); //set back the carrier oscillator anyway, cw tx switches it off
|
|
||||||
si5351_set_calibration(globalSettings.oscillatorCal);
|
|
||||||
setFrequency(GetActiveVfoFreq());
|
setFrequency(GetActiveVfoFreq());
|
||||||
|
const long int u = abs(new_value);
|
||||||
ltoa(globalSettings.oscillatorCal, b, 10);
|
if(new_value != u){
|
||||||
displayText(b, LAYOUT_SETTING_VALUE_X, LAYOUT_SETTING_VALUE_Y, LAYOUT_SETTING_VALUE_WIDTH, LAYOUT_SETTING_VALUE_HEIGHT, COLOR_TEXT, COLOR_TITLE_BACKGROUND, COLOR_BACKGROUND);
|
strcpy_P(buff_out,(const char*)F("-"));
|
||||||
|
++buff_out;
|
||||||
}
|
}
|
||||||
|
formatFreq(u,buff_out,buff_out_size);
|
||||||
|
strcat_P(buff_out,(const char*)F("Hz"));
|
||||||
|
}
|
||||||
|
void ssLocalOscFinalize(const long int final_value)
|
||||||
|
{
|
||||||
|
globalSettings.oscillatorCal = final_value;
|
||||||
SaveSettingsToEeprom();
|
SaveSettingsToEeprom();
|
||||||
initOscillators();
|
|
||||||
si5351_set_calibration(globalSettings.oscillatorCal);
|
si5351_set_calibration(globalSettings.oscillatorCal);
|
||||||
setFrequency(GetActiveVfoFreq());
|
setFrequency(GetActiveVfoFreq());
|
||||||
|
|
||||||
//debounce and delay
|
|
||||||
while(btnDown())
|
|
||||||
active_delay(50);
|
|
||||||
active_delay(100);
|
|
||||||
}
|
}
|
||||||
|
const char SS_LOCAL_OSC_T [] PROGMEM = "Set Local Osc Calibration";
|
||||||
|
const char SS_LOCAL_OSC_A [] PROGMEM = "Exit menu, tune so that the\ndial displays the desired freq,\nthen tune here until the\nsignal is zerobeat";
|
||||||
|
const SettingScreen_t ssLocalOsc PROGMEM = {
|
||||||
|
SS_LOCAL_OSC_T,
|
||||||
|
SS_LOCAL_OSC_A,
|
||||||
|
1,
|
||||||
|
875,
|
||||||
|
ssLocalOscInitialize,
|
||||||
|
ssLocalOscValidate,
|
||||||
|
ssLocalOscChange,
|
||||||
|
ssLocalOscFinalize
|
||||||
|
};
|
||||||
|
void runLocalOscSetting(){runSetting(&ssLocalOsc);}
|
||||||
|
|
||||||
void setupBFO(){
|
void setupBFO(){
|
||||||
//displayDialog(F("Set BFO"),F("Press TUNE to Save"));
|
//displayDialog(F("Set BFO"),F("Press TUNE to Save"));
|
||||||
|
|
||||||
si5351bx_setfreq(0, globalSettings.usbCarrierFreq);
|
si5351bx_setfreq(0, globalSettings.usbCarrierFreq);
|
||||||
printCarrierFreq(globalSettings.usbCarrierFreq);
|
//printCarrierFreq(globalSettings.usbCarrierFreq);
|
||||||
|
|
||||||
while (!btnDown()){
|
while (!btnDown()){
|
||||||
int knob = enc_read();
|
int knob = enc_read();
|
||||||
@ -255,7 +242,7 @@ void setupBFO(){
|
|||||||
|
|
||||||
si5351bx_setfreq(0, globalSettings.usbCarrierFreq);
|
si5351bx_setfreq(0, globalSettings.usbCarrierFreq);
|
||||||
setFrequency(GetActiveVfoFreq());
|
setFrequency(GetActiveVfoFreq());
|
||||||
printCarrierFreq(globalSettings.usbCarrierFreq);
|
//printCarrierFreq(globalSettings.usbCarrierFreq);
|
||||||
|
|
||||||
active_delay(100);
|
active_delay(100);
|
||||||
}
|
}
|
||||||
@ -450,7 +437,7 @@ const char MI_SET_BFO [] PROGMEM = "Beat Frequency Osc (BFO)";
|
|||||||
const char MI_TOUCH [] PROGMEM = "Touch Screen";
|
const char MI_TOUCH [] PROGMEM = "Touch Screen";
|
||||||
const MenuItem_t calibrationMenu [] PROGMEM {
|
const MenuItem_t calibrationMenu [] PROGMEM {
|
||||||
{MT_CAL,nullptr},//Title
|
{MT_CAL,nullptr},//Title
|
||||||
{MI_SET_FREQ,setupFreq},
|
{MI_SET_FREQ,runLocalOscSetting},
|
||||||
{MI_SET_BFO,setupBFO},
|
{MI_SET_BFO,setupBFO},
|
||||||
{MI_TOUCH,setupTouch},
|
{MI_TOUCH,setupTouch},
|
||||||
};
|
};
|
||||||
|
9
ubitx.h
9
ubitx.h
@ -92,10 +92,6 @@ extern char c[30], b[30];
|
|||||||
#define HIGHEST_FREQ (30000000l)
|
#define HIGHEST_FREQ (30000000l)
|
||||||
static const uint32_t THRESHOLD_USB_LSB = 10000000L;
|
static const uint32_t THRESHOLD_USB_LSB = 10000000L;
|
||||||
|
|
||||||
extern unsigned long firstIF;
|
|
||||||
|
|
||||||
extern uint8_t menuOn;
|
|
||||||
|
|
||||||
/* these are functions implemented in the main file named as ubitx_xxx.ino */
|
/* these are functions implemented in the main file named as ubitx_xxx.ino */
|
||||||
void active_delay(int delay_by);
|
void active_delay(int delay_by);
|
||||||
void saveVFOs();
|
void saveVFOs();
|
||||||
@ -122,11 +118,6 @@ void drawTx();
|
|||||||
//are useful to concatanate the values with text like "Set Freq to " x " KHz"
|
//are useful to concatanate the values with text like "Set Freq to " x " KHz"
|
||||||
int getValueByKnob(int minimum, int maximum, int step_size, int initial, char* prefix, char *postfix);
|
int getValueByKnob(int minimum, int maximum, int step_size, int initial, char* prefix, char *postfix);
|
||||||
|
|
||||||
//functions of the setup menu. implemented in seteup.cpp
|
|
||||||
void doSetup2(); //main setup function, displays the setup menu, calls various dialog boxes
|
|
||||||
void setupBFO();
|
|
||||||
void setupFreq();
|
|
||||||
|
|
||||||
//main functions to check if any button is pressed and other user interface events
|
//main functions to check if any button is pressed and other user interface events
|
||||||
void doCommands(); //does the commands with encoder to jump from button to button
|
void doCommands(); //does the commands with encoder to jump from button to button
|
||||||
void checkTouch(); //does the commands with a touch on the buttons
|
void checkTouch(); //does the commands with a touch on the buttons
|
||||||
|
35
ubitx_ui.cpp
35
ubitx_ui.cpp
@ -1,7 +1,8 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <EEPROM.h>
|
#include "freeRam.h"
|
||||||
#include "morse.h"
|
#include "morse.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
#include "setup.h"
|
||||||
#include "ubitx.h"
|
#include "ubitx.h"
|
||||||
#include "nano_gui.h"
|
#include "nano_gui.h"
|
||||||
|
|
||||||
@ -900,6 +901,7 @@ void doCommand(Button* button){
|
|||||||
}
|
}
|
||||||
case BUTTON_MNU:
|
case BUTTON_MNU:
|
||||||
{
|
{
|
||||||
|
Serial.println(freeRam());
|
||||||
doSetup2();
|
doSetup2();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -953,20 +955,17 @@ void drawFocus(int ibtn, int color){
|
|||||||
}
|
}
|
||||||
|
|
||||||
void doCommands(){
|
void doCommands(){
|
||||||
int select=0, i, prevButton, btnState;
|
int select = 0;
|
||||||
|
int prev_button = 0;
|
||||||
|
|
||||||
//wait for the button to be raised up
|
//wait for the button to be raised up
|
||||||
while(btnDown())
|
while(btnDown())
|
||||||
active_delay(50);
|
active_delay(50);
|
||||||
active_delay(50); //debounce
|
active_delay(50); //debounce
|
||||||
|
|
||||||
menuOn = 2;
|
while (true){
|
||||||
|
|
||||||
while (menuOn){
|
|
||||||
|
|
||||||
//check if the knob's button was pressed
|
//check if the knob's button was pressed
|
||||||
btnState = btnDown();
|
if (btnDown()){
|
||||||
if (btnState){
|
|
||||||
Button button;
|
Button button;
|
||||||
memcpy_P(&button, &(btn_set[select/10]), sizeof(Button));
|
memcpy_P(&button, &(btn_set[select/10]), sizeof(Button));
|
||||||
|
|
||||||
@ -986,27 +985,27 @@ void doCommands(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
i = enc_read();
|
int knob = enc_read();
|
||||||
|
|
||||||
if (i == 0){
|
if (knob == 0){
|
||||||
active_delay(50);
|
active_delay(50);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i > 0){
|
if (knob > 0){
|
||||||
if (select + i < BUTTON_TOTAL * 10)
|
if (select + knob < BUTTON_TOTAL * 10)
|
||||||
select += i;
|
select += knob;
|
||||||
}
|
}
|
||||||
if (i < 0 && select + i >= 0)
|
if (knob < 0 && select + knob >= 0)
|
||||||
select += i; //caught ya, i is already -ve here, so you add it
|
select += knob; //caught ya, i is already -ve here, so you add it
|
||||||
|
|
||||||
if (prevButton == select / 10)
|
if (prev_button == select / 10)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
//we are on a new button
|
//we are on a new button
|
||||||
drawFocus(prevButton, COLOR_INACTIVE_BORDER);
|
drawFocus(prev_button, COLOR_INACTIVE_BORDER);
|
||||||
drawFocus(select/10, COLOR_ACTIVE_BORDER);
|
drawFocus(select/10, COLOR_ACTIVE_BORDER);
|
||||||
prevButton = select/10;
|
prev_button = select/10;
|
||||||
}
|
}
|
||||||
// guiUpdate();
|
// guiUpdate();
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
*/
|
*/
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
#include "setup.h"
|
||||||
#include "ubitx.h"
|
#include "ubitx.h"
|
||||||
#include "nano_gui.h"
|
#include "nano_gui.h"
|
||||||
|
|
||||||
@ -45,11 +46,11 @@
|
|||||||
* the input and output from the USB port. We must keep a count of the bytes used while reading
|
* the input and output from the USB port. We must keep a count of the bytes used while reading
|
||||||
* the serial port as we can easily run out of buffer space. This is done in the serial_in_count variable.
|
* the serial port as we can easily run out of buffer space. This is done in the serial_in_count variable.
|
||||||
*/
|
*/
|
||||||
char c[30], b[30];
|
char b[30];
|
||||||
|
char c[30];
|
||||||
|
|
||||||
//during CAT commands, we will freeeze the display until CAT is disengaged
|
//during CAT commands, we will freeeze the display until CAT is disengaged
|
||||||
unsigned char doingCAT = 0;
|
unsigned char doingCAT = 0;
|
||||||
byte menuOn = 0; //set to 1 when the menu is being displayed, if a menu item sets it to zero, the menu is exited
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -477,7 +478,7 @@ void setup()
|
|||||||
setupTouch();
|
setupTouch();
|
||||||
SetActiveVfoMode(VfoMode_e::VFO_MODE_USB);
|
SetActiveVfoMode(VfoMode_e::VFO_MODE_USB);
|
||||||
setFrequency(10000000L);
|
setFrequency(10000000L);
|
||||||
setupFreq();
|
runLocalOscSetting();
|
||||||
SetActiveVfoMode(VfoMode_e::VFO_MODE_LSB);
|
SetActiveVfoMode(VfoMode_e::VFO_MODE_LSB);
|
||||||
setFrequency(7100000L);
|
setFrequency(7100000L);
|
||||||
setupBFO();
|
setupBFO();
|
||||||
|
Loading…
Reference in New Issue
Block a user