kopia lustrzana https://github.com/ArjanteMarvelde/uSDR-pico
rodzic
493756488a
commit
82d6dd841b
|
@ -19,7 +19,7 @@ pico_sdk_init()
|
|||
|
||||
# Add executable. Default name is the project name, version 0.1
|
||||
|
||||
add_executable(uSDR uSDR.c lcd.c si5351.c dsp.c hmi.c monitor.c)
|
||||
add_executable(uSDR uSDR.c lcd.c si5351.c dsp.c hmi.c monitor.c relay.c)
|
||||
|
||||
pico_set_program_name(uSDR "uSDR")
|
||||
pico_set_program_version(uSDR "0.1")
|
||||
|
|
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
185
dsp.c
185
dsp.c
|
@ -37,6 +37,7 @@
|
|||
#define ADC0_IRQ_FIFO 22
|
||||
|
||||
#include "dsp.h"
|
||||
#include "hmi.h"
|
||||
|
||||
|
||||
/*
|
||||
|
@ -64,10 +65,10 @@
|
|||
|
||||
|
||||
/*
|
||||
* AGC reference level is log2(64) = 6, where 64 is the MSB of half DAC_RANGE
|
||||
* AGC reference level is log2(0x40) = 6, where 0x40 is the MSB of half DAC_RANGE
|
||||
* 1/AGC_DECAY and 1/AGC_ATTACK are multipliers before agc_gain value integrator
|
||||
* These values should ultimately be set by the HMI.
|
||||
* The time it takes to effect in a gain change is the ( (Set time)/(signal delta) ) / samplerate
|
||||
* The time it takes to a gain change is the ( (Set time)/(signal delta) ) / samplerate
|
||||
* So when delta is 1, and attack is 64, the time is 64/15625 = 4msec (fast attack)
|
||||
* The decay time is about 100x this value
|
||||
* Slow attack would be about 4096
|
||||
|
@ -76,7 +77,7 @@
|
|||
#define AGC_DECAY 8192
|
||||
#define AGC_FAST 64
|
||||
#define AGC_SLOW 4096
|
||||
#define AGC_OFF 65534
|
||||
#define AGC_OFF 32766
|
||||
volatile uint16_t agc_decay = AGC_OFF;
|
||||
volatile uint16_t agc_attack = AGC_OFF;
|
||||
void dsp_setagc(int agc)
|
||||
|
@ -102,7 +103,7 @@ void dsp_setagc(int agc)
|
|||
* MODE is modulation/demodulation
|
||||
* This setting steers the signal processing branch chosen
|
||||
*/
|
||||
volatile uint16_t dsp_mode; // For values see hmi.c
|
||||
volatile uint16_t dsp_mode; // For values see hmi.c, assume {USB,LSB,AM,CW}
|
||||
void dsp_setmode(int mode)
|
||||
{
|
||||
dsp_mode = (uint16_t)mode;
|
||||
|
@ -110,6 +111,39 @@ void dsp_setmode(int mode)
|
|||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VOX LINGER is the number of 16us cycles to wait before releasing TX mode
|
||||
* The level of detection is related to the maximum ADC range.
|
||||
*/
|
||||
#define VOX_LINGER 500000/16
|
||||
#define VOX_HIGH ADC_BIAS/2
|
||||
#define VOX_MEDIUM ADC_BIAS/4
|
||||
#define VOX_LOW ADC_BIAS/16
|
||||
#define VOX_OFF 0
|
||||
volatile uint16_t vox_count;
|
||||
volatile uint16_t vox_level = VOX_OFF;
|
||||
void dsp_setvox(int vox)
|
||||
{
|
||||
switch(vox)
|
||||
{
|
||||
case 1:
|
||||
vox_level = VOX_LOW;
|
||||
break;
|
||||
case 2:
|
||||
vox_level = VOX_MEDIUM;
|
||||
break;
|
||||
case 3:
|
||||
vox_level = VOX_HIGH;
|
||||
break;
|
||||
default:
|
||||
vox_level = VOX_OFF;
|
||||
vox_count = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Low pass filters Fc=3, 7 and 15 kHz (see http://t-filter.engineerjs.com/)
|
||||
* Settings: sample rates 62500, 31250 or 15625 Hz, stopband -40dB, passband ripple 5dB
|
||||
|
@ -161,10 +195,12 @@ void adcfifo_handler(void)
|
|||
*/
|
||||
volatile int16_t i_s_raw[15], q_s_raw[15]; // Raw I/Q samples minus DC bias
|
||||
volatile uint16_t peak=0; // Peak detector running value
|
||||
volatile int16_t agc_gain=0, agc_accu=0; // AGC gain (shift value), log peak level integrator
|
||||
volatile int16_t agc_gain=0; // AGC gain (left-shift value)
|
||||
volatile int16_t agc_accu=0; // Log peak level integrator
|
||||
volatile int16_t i_s[15], q_s[15]; // Filtered I/Q samples
|
||||
volatile int16_t i_dc, q_dc; // DC bias for I/Q channel
|
||||
volatile int rx_cnt=0; // Decimation counter
|
||||
|
||||
bool rx(void)
|
||||
{
|
||||
int16_t q_sample, i_sample, a_sample;
|
||||
|
@ -175,8 +211,8 @@ bool rx(void)
|
|||
|
||||
/*** SAMPLING ***/
|
||||
|
||||
i_sample = adc_result[0]; // Take last ADC 0 result
|
||||
q_sample = adc_result[1]; // Take last ADC 1 result
|
||||
q_sample = adc_result[0]; // Take last ADC 0 result, connected to Q input
|
||||
i_sample = adc_result[1]; // Take last ADC 1 result, connected to I input
|
||||
|
||||
/*
|
||||
* Remove DC and store new sample
|
||||
|
@ -184,25 +220,26 @@ bool rx(void)
|
|||
* Amplitude of samples should fit inside [-2048, 2047]
|
||||
*/
|
||||
q_sample = (q_sample&0x0fff) - ADC_BIAS; // Clip to 12 bits and subtract mid-range
|
||||
q_dc += (q_sample>>7) - (q_dc>>7); // then IIR running average
|
||||
q_dc += q_sample/128 - q_dc/128; // then IIR running average
|
||||
q_sample -= q_dc; // and subtract DC
|
||||
i_sample = (i_sample&0x0fff) - ADC_BIAS; // Same for I sample
|
||||
i_dc += (i_sample>>7) - (i_dc>>7);
|
||||
i_dc += i_sample/128 - i_dc/128;
|
||||
i_sample -= i_dc;
|
||||
|
||||
/*
|
||||
* Shift with AGC feedback from AUDIO GENERATION stage
|
||||
* Note: bitshift does not work with negative numbers, so need to MPY/DIV
|
||||
* This behavior in essence is exponential, complementing the logarithmic peak detector
|
||||
*/
|
||||
if (agc_gain > 0)
|
||||
{
|
||||
q_sample <<= agc_gain;
|
||||
i_sample <<= agc_gain;
|
||||
q_sample = q_sample * (1<<agc_gain);
|
||||
i_sample = i_sample * (1<<agc_gain);
|
||||
}
|
||||
else if (agc_gain < 0)
|
||||
{
|
||||
q_sample >>= -agc_gain;
|
||||
i_sample >>= -agc_gain;
|
||||
q_sample = q_sample / (1<<(-agc_gain));
|
||||
i_sample = i_sample / (1<<(-agc_gain));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -235,8 +272,8 @@ bool rx(void)
|
|||
q_accu += (int32_t)q_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz raw sampling
|
||||
i_accu += (int32_t)i_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz raw sampling
|
||||
}
|
||||
q_accu >>= 8;
|
||||
i_accu >>= 8;
|
||||
q_accu = q_accu/256;
|
||||
i_accu = i_accu/256;
|
||||
|
||||
q_s[14] = q_accu;
|
||||
i_s[14] = i_accu;
|
||||
|
@ -247,20 +284,20 @@ bool rx(void)
|
|||
{
|
||||
case 0: //USB
|
||||
/*
|
||||
* USB demodulate: I[7] - Qh,
|
||||
* USB demodulate: I[7] - Qh,
|
||||
* Qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (q_s[0]-q_s[14])*315L + (q_s[2]-q_s[12])*440L + (q_s[4]-q_s[10])*734L + (q_s[6]-q_s[ 8])*2202L;
|
||||
qh = q_accu >> 12;
|
||||
qh = q_accu / 4096L;
|
||||
a_sample = i_s[7] - qh;
|
||||
break;
|
||||
case 1: //LSB
|
||||
/*
|
||||
* USB demodulate: I[7] - Qh,
|
||||
* LSB demodulate: I[7] + Qh,
|
||||
* Qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (q_s[0]-q_s[14])*315L + (q_s[2]-q_s[12])*440L + (q_s[4]-q_s[10])*734L + (q_s[6]-q_s[ 8])*2202L;
|
||||
qh = q_accu >> 12;
|
||||
qh = q_accu / 4096L;
|
||||
a_sample = i_s[7] + qh;
|
||||
break;
|
||||
case 2: //AM
|
||||
|
@ -277,9 +314,9 @@ bool rx(void)
|
|||
/*** AUDIO GENERATION ***/
|
||||
/*
|
||||
* AGC, peak detector
|
||||
* Sample speed is still 15625 per second
|
||||
* Sample speed is 15625 per second
|
||||
*/
|
||||
peak = (127*peak + (ABS(a_sample)))>>7; // Running average level detect, a=1/128
|
||||
peak += (ABS(a_sample))/128 - peak/128; // Running average level detect, a=1/128
|
||||
k=0; i=peak; // Logarithmic peak detection
|
||||
if (i&0xff00) {k+=8; i>>=8;} // k=log2(peak), find highest bit set
|
||||
if (i&0x00f0) {k+=4; i>>=4;}
|
||||
|
@ -295,7 +332,6 @@ bool rx(void)
|
|||
agc_gain++; // Increase gain
|
||||
agc_accu += agc_decay; // Reset integrator
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Scale and clip output,
|
||||
|
@ -315,49 +351,83 @@ bool rx(void)
|
|||
|
||||
/*
|
||||
* CORE1:
|
||||
* Execute TX branch signal processing
|
||||
* The VOX function is called separately every cycle, to check audio level.
|
||||
* Execute TX branch signal processing when tx enabled
|
||||
*/
|
||||
volatile int16_t a_s_raw[15]; // Raw samples, minus DC bias
|
||||
volatile int16_t a_level=0; // Average level of raw sample stream
|
||||
volatile int16_t a_s[15]; // Filtered and decimated samples
|
||||
volatile int16_t a_dc; // DC level
|
||||
volatile int tx_cnt=0; // Decimation counter
|
||||
|
||||
bool vox(void)
|
||||
{
|
||||
int16_t a_sample;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Get sample and shift into delay line
|
||||
*/
|
||||
a_sample = adc_result[2]; // Get latest ADC 2 result
|
||||
|
||||
|
||||
/*
|
||||
* Remove DC and store new raw sample
|
||||
* IIR filter: dc = a*sample + (1-a)*dc where a = 1/128
|
||||
*/
|
||||
a_sample = (a_sample&0x0fff) - ADC_BIAS; // Clip and subtract mid-range
|
||||
a_dc += (a_sample - a_dc)/128; // then IIR running average
|
||||
a_sample -= a_dc; // subtract DC
|
||||
for (i=0; i<14; i++) // and store in shift register
|
||||
a_s_raw[i] = a_s_raw[i+1];
|
||||
a_s_raw[14] = a_sample;
|
||||
|
||||
/*
|
||||
* Detect level of audio signal
|
||||
* Return true if VOX enabled and:
|
||||
* - Audio level higher than threshold
|
||||
* - Linger time sill active
|
||||
*/
|
||||
if (a_sample<0) a_sample = -a_sample; // Absolute value
|
||||
a_level += (a_sample - a_level)/128; // running average, 16usec * 128 = 2msec
|
||||
|
||||
if (vox_level != VOX_OFF)
|
||||
{
|
||||
if (a_level > vox_level)
|
||||
{
|
||||
vox_count = VOX_LINGER;
|
||||
return(true);
|
||||
}
|
||||
if (vox_count>0)
|
||||
{
|
||||
vox_count--;
|
||||
return(true);
|
||||
}
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
|
||||
bool tx(void)
|
||||
{
|
||||
static int tx_phase = 0;
|
||||
int16_t a_sample;
|
||||
int32_t a_accu;
|
||||
int16_t qh;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Get sample and shift into delay line
|
||||
*/
|
||||
a_sample = adc_result[2]; // Take last ADC 2 result
|
||||
|
||||
for (i=0; i<14; i++)
|
||||
a_s_raw[i] = a_s_raw[i+1]; // Audio raw samples shift register
|
||||
|
||||
/*
|
||||
* Remove DC and store new sample
|
||||
* IIR filter: dc = a*sample + (1-a)*dc where a = 1/128
|
||||
*/
|
||||
a_sample = (a_sample&0x0fff) - ADC_BIAS; // Clip and subtract mid-range
|
||||
a_dc = (a_sample>>7) + a_dc - (a_dc>>7); // then IIR running average
|
||||
a_s_raw[14] = a_sample - a_dc; // and subtract DC, store in shift register
|
||||
/*** RAW Audio SAMPLES from VOX function ***/
|
||||
|
||||
/*
|
||||
* Low pass filter + decimation
|
||||
*/
|
||||
tx_cnt = (tx_cnt+1)&3; // Calculate only every fourth sample
|
||||
if (tx_cnt>0) return true;
|
||||
if (tx_cnt>0) return true; // So effective sample rate will be 15625Hz
|
||||
|
||||
for (i=0; i<14; i++) // Shift decimated samples
|
||||
a_s[i] = a_s[i+1];
|
||||
|
||||
a_accu = 0; // Initialize accumulator
|
||||
for (i=0; i<15; i++) // Low pass FIR filter
|
||||
a_accu += (int32_t)a_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz sampling
|
||||
a_s[14] = a_accu >> 8;
|
||||
for (i=0; i<15; i++) // Low pass FIR filter, using raw samples
|
||||
a_accu += (int32_t)a_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz sampling
|
||||
a_s[14] = a_accu / 256;
|
||||
|
||||
/*
|
||||
* From here things get dependent on transmit mode.
|
||||
|
@ -368,14 +438,14 @@ bool tx(void)
|
|||
* Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills):
|
||||
*/
|
||||
a_accu = (a_s[0]-a_s[14])*315L + (a_s[2]-a_s[12])*440L + (a_s[4]-a_s[10])*734L + (a_s[6]-a_s[ 8])*2202L;
|
||||
qh = (int16_t)(a_accu >> 12);
|
||||
qh = (int16_t)(a_accu / 4096);
|
||||
|
||||
/*
|
||||
* Write I and Q to QSE DACs, phase is 7 back.
|
||||
* Need to multiply AC with DAC_RANGE/ADC_RANGE (appr 1/16, but compensate for losses)
|
||||
*/
|
||||
pwm_set_chan_level(dac_iq, PWM_CHAN_A, DAC_BIAS + (a_s[7]/4));
|
||||
pwm_set_chan_level(dac_iq, PWM_CHAN_B, DAC_BIAS + (qh/4));
|
||||
pwm_set_chan_level(dac_iq, PWM_CHAN_A, DAC_BIAS + (qh/4));
|
||||
pwm_set_chan_level(dac_iq, PWM_CHAN_B, DAC_BIAS + (a_s[7]/4));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -398,8 +468,8 @@ void dsp_loop()
|
|||
fifo_incnt++;
|
||||
|
||||
/* Initialize DACs */
|
||||
gpio_set_function(20, GPIO_FUNC_PWM); // GP20 is PWM for I DAC (Slice 2, Channel A)
|
||||
gpio_set_function(21, GPIO_FUNC_PWM); // GP21 is PWM for Q DAC (Slice 2, Channel B)
|
||||
gpio_set_function(20, GPIO_FUNC_PWM); // GP20 is PWM for Q DAC (Slice 2, Channel A)
|
||||
gpio_set_function(21, GPIO_FUNC_PWM); // GP21 is PWM for I DAC (Slice 2, Channel B)
|
||||
dac_iq = pwm_gpio_to_slice_num(20); // Get PWM slice for GP20 (Same for GP21)
|
||||
pwm_set_clkdiv_int_frac (dac_iq, 1, 0); // clock divide by 1
|
||||
pwm_set_wrap(dac_iq, DAC_RANGE-1); // Set cycle length
|
||||
|
@ -414,8 +484,8 @@ void dsp_loop()
|
|||
/* Initialize ADCs */
|
||||
adc_init(); // Initialize ADC to known state
|
||||
adc_set_clkdiv(0); // Fastest clock (500 kSps)
|
||||
adc_gpio_init(26); // GP26 is ADC 0 for I channel
|
||||
adc_gpio_init(27); // GP27 is ADC 1 for Q channel
|
||||
adc_gpio_init(26); // GP26 is ADC 0 for Q channel
|
||||
adc_gpio_init(27); // GP27 is ADC 1 for I channel
|
||||
adc_gpio_init(28); // GP28 is ADC 2 for Audio channel
|
||||
adc_select_input(0); // Start with ADC0
|
||||
adc_next = 0;
|
||||
|
@ -434,18 +504,11 @@ void dsp_loop()
|
|||
while(1)
|
||||
{
|
||||
cmd = multicore_fifo_pop_blocking(); // Wait for fifo output
|
||||
if (cmd == DSP_RX)
|
||||
{
|
||||
fifo_rx++;
|
||||
rx();
|
||||
}
|
||||
else if (cmd == DSP_TX)
|
||||
{
|
||||
fifo_tx++;
|
||||
tx_enabled = ptt_active || vox(); // Sample audio and check level
|
||||
if (tx_enabled)
|
||||
tx();
|
||||
}
|
||||
else
|
||||
fifo_xx++;
|
||||
rx();
|
||||
if (multicore_fifo_rvalid())
|
||||
fifo_overrun++; // Check for missed events
|
||||
}
|
||||
|
|
1
dsp.h
1
dsp.h
|
@ -16,6 +16,7 @@
|
|||
|
||||
void dsp_setagc(int agc);
|
||||
void dsp_setmode(int mode);
|
||||
void dsp_setvox(int vox);
|
||||
|
||||
extern volatile bool tx_enabled;
|
||||
#define DSP_SETPTT(on) tx_enabled = (on)
|
||||
|
|
127
hmi.c
127
hmi.c
|
@ -37,6 +37,7 @@
|
|||
#include "hmi.h"
|
||||
#include "dsp.h"
|
||||
#include "si5351.h"
|
||||
#include "relay.h"
|
||||
|
||||
/*
|
||||
* GPIO assignments
|
||||
|
@ -66,11 +67,12 @@
|
|||
* using Left/Right for digit and ENC for value, Enter to commit change.
|
||||
* Press ESC to enter the submenu states (there is only one sub menu level):
|
||||
*
|
||||
* Submenu Values ENC Enter Escape Left Right
|
||||
* -------------------------------------------------------------------------------------
|
||||
* Mode USB, LSB, AM, CW change commit exit prev next
|
||||
* AGC Fast, Slow, Off change commit exit prev next
|
||||
* Pre +10dB, 0, -10dB, -20dB change commit exit prev next
|
||||
* Submenu Values ENC Enter Escape Left Right
|
||||
* -----------------------------------------------------------------------------------------------
|
||||
* Mode USB, LSB, AM, CW change commit exit prev next
|
||||
* AGC Fast, Slow, Off change commit exit prev next
|
||||
* Pre +10dB, 0, -10dB, -20dB, -30dB change commit exit prev next
|
||||
* Vox NoVOX, Low, Medium, High change commit exit prev next
|
||||
*
|
||||
* --will be extended--
|
||||
*/
|
||||
|
@ -80,7 +82,9 @@
|
|||
#define HMI_S_MODE 1
|
||||
#define HMI_S_AGC 2
|
||||
#define HMI_S_PRE 3
|
||||
#define HMI_NSTATES 4
|
||||
#define HMI_S_VOX 4
|
||||
#define HMI_S_BPF 5
|
||||
#define HMI_NSTATES 6
|
||||
|
||||
/* Event definitions */
|
||||
#define HMI_E_NOEVENT 0
|
||||
|
@ -97,14 +101,18 @@
|
|||
/* Sub menu option string sets */
|
||||
#define HMI_NMODE 4
|
||||
#define HMI_NAGC 3
|
||||
#define HMI_NPRE 4
|
||||
char hmi_o_menu[HMI_NSTATES][8] = {"Tune","Mode","AGC ","Pre "}; // Indexed by hmi_state
|
||||
char hmi_o_mode[HMI_NMODE][8] = {"USB", "LSB", "AM ", "CW "}; // Indexed by hmi_sub[HMI_S_MODE]
|
||||
char hmi_o_agc [HMI_NAGC][8] = {"NoGC", "Slow", "Fast"}; // Indexed by hmi_sub[HMI_S_AGC]
|
||||
char hmi_o_pre [HMI_NPRE][8] = {"-20dB", "-10dB", "0dB", "+10dB"}; // Indexed by hmi_sub[HMI_S_PRE]
|
||||
#define HMI_NPRE 5
|
||||
#define HMI_NVOX 4
|
||||
#define HMI_NBPF 5
|
||||
char hmi_o_menu[HMI_NSTATES][8] = {"Tune","Mode","AGC","Pre","VOX"}; // Indexed by hmi_state
|
||||
char hmi_o_mode[HMI_NMODE][8] = {"USB","LSB","AM","CW"}; // Indexed by hmi_sub[HMI_S_MODE]
|
||||
char hmi_o_agc [HMI_NAGC][8] = {"NoGC","Slow","Fast"}; // Indexed by hmi_sub[HMI_S_AGC]
|
||||
char hmi_o_pre [HMI_NPRE][8] = {"-30dB","-20dB","-10dB","0dB","+10dB"}; // Indexed by hmi_sub[HMI_S_PRE]
|
||||
char hmi_o_vox [HMI_NVOX][8] = {"NoVOX","VOX-L","VOX-M","VOX-H"}; // Indexed by hmi_sub[HMI_S_VOX]
|
||||
char hmi_o_test[HMI_NBPF][8] = {"<2.5","2-6","5-12","10-24","20-40"};
|
||||
|
||||
uint8_t hmi_state, hmi_option; // Current state and option selection
|
||||
uint8_t hmi_sub[HMI_NSTATES] = {4,0,0,0}; // Stored option selection per state
|
||||
uint8_t hmi_sub[HMI_NSTATES] = {4,0,0,3,0,0}; // Stored option selection per state
|
||||
|
||||
uint32_t hmi_freq; // Frequency from Tune state
|
||||
uint32_t hmi_step[6] = {10000000, 1000000, 100000, 10000, 1000, 100}; // Frequency digit increments
|
||||
|
@ -113,6 +121,10 @@ uint32_t hmi_step[6] = {10000000, 1000000, 100000, 10000, 1000, 100}; // Frequen
|
|||
#define HMI_MULFREQ 1 // Factor between HMI and actual frequency
|
||||
// Set to 2 for certain types of mixer
|
||||
|
||||
#define PTT_DEBOUNCE 3 // Nr of cycles for debounce
|
||||
int ptt_state; // Debounce counter
|
||||
bool ptt_active; // Resulting state
|
||||
|
||||
/*
|
||||
* Some macros
|
||||
*/
|
||||
|
@ -201,7 +213,11 @@ void hmi_handler(uint8_t event)
|
|||
case HMI_S_PRE:
|
||||
if (event==HMI_E_ENTER)
|
||||
{
|
||||
// Set PRE
|
||||
if (hmi_option == 0) relay_setattn(0x03); // {"-30dB","-20dB","-10dB","0dB","+10dB"}
|
||||
if (hmi_option == 1) relay_setattn(0x01);
|
||||
if (hmi_option == 2) relay_setattn(0x02);
|
||||
if (hmi_option == 3) relay_setattn(0x00);
|
||||
if (hmi_option == 4) relay_setattn(0x04);
|
||||
hmi_sub[hmi_state] = hmi_option; // Store selected option
|
||||
}
|
||||
if (event==HMI_E_INCREMENT)
|
||||
|
@ -213,6 +229,40 @@ void hmi_handler(uint8_t event)
|
|||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
}
|
||||
break;
|
||||
case HMI_S_VOX:
|
||||
if (event==HMI_E_ENTER)
|
||||
{
|
||||
dsp_setvox(hmi_option);
|
||||
hmi_sub[hmi_state] = hmi_option; // Store selected option
|
||||
}
|
||||
if (event==HMI_E_INCREMENT)
|
||||
{
|
||||
hmi_option = (hmi_option<HMI_NVOX-1)?hmi_option+1:HMI_NVOX-1;
|
||||
}
|
||||
if (event==HMI_E_DECREMENT)
|
||||
{
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
}
|
||||
break;
|
||||
case HMI_S_BPF:
|
||||
if (event==HMI_E_ENTER)
|
||||
{
|
||||
if (hmi_option == 0) relay_setattn(0x01); // {"<2.5","2-6","5-12","10-24","20-40"}
|
||||
if (hmi_option == 1) relay_setattn(0x02);
|
||||
if (hmi_option == 2) relay_setattn(0x04);
|
||||
if (hmi_option == 3) relay_setattn(0x08);
|
||||
if (hmi_option == 4) relay_setattn(0x10);
|
||||
hmi_sub[hmi_state] = hmi_option; // Store selected option
|
||||
}
|
||||
if (event==HMI_E_INCREMENT)
|
||||
{
|
||||
hmi_option = (hmi_option<HMI_NBPF-1)?hmi_option+1:HMI_NBPF-1;
|
||||
}
|
||||
if (event==HMI_E_DECREMENT)
|
||||
{
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* General actions for submenus */
|
||||
|
@ -263,12 +313,16 @@ void hmi_callback(uint gpio, uint32_t events)
|
|||
if (events&GPIO_IRQ_EDGE_FALL)
|
||||
evt = HMI_E_RIGHT;
|
||||
break;
|
||||
/*
|
||||
case GP_PTT: // PTT
|
||||
if (events&GPIO_IRQ_EDGE_FALL)
|
||||
DSP_SETPTT(true);
|
||||
ptt_active = true;
|
||||
else
|
||||
DSP_SETPTT(false);
|
||||
// This event needs to be detected better, to prevent hanging in TX state
|
||||
// 10nF also helps suppressing the ripple...
|
||||
ptt_active = false;
|
||||
return;
|
||||
*/
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
@ -286,7 +340,9 @@ void hmi_init(void)
|
|||
* The callback handles interrupts for all GPIOs with IRQ enabled.
|
||||
* Level interrupts don't seem to work properly.
|
||||
* For debouncing, the GPIO pins should be pulled-up and connected to gnd with 100nF.
|
||||
* PTT has separate debouncing logic
|
||||
*/
|
||||
|
||||
// Init input GPIOs
|
||||
gpio_init_mask(GP_MASK_IN);
|
||||
|
||||
|
@ -305,7 +361,7 @@ void hmi_init(void)
|
|||
gpio_set_irq_enabled(GP_AUX_1, GPIO_IRQ_EDGE_ALL, true);
|
||||
gpio_set_irq_enabled(GP_AUX_2, GPIO_IRQ_EDGE_ALL, true);
|
||||
gpio_set_irq_enabled(GP_AUX_3, GPIO_IRQ_EDGE_ALL, true);
|
||||
gpio_set_irq_enabled(GP_PTT, GPIO_IRQ_EDGE_ALL, true);
|
||||
//gpio_set_irq_enabled(GP_PTT, GPIO_IRQ_EDGE_ALL, true);
|
||||
|
||||
// Set callback, one for all GPIO, not sure about correctness!
|
||||
gpio_set_irq_enabled_with_callback(GP_ENC_A, GPIO_IRQ_EDGE_ALL, true, hmi_callback);
|
||||
|
@ -315,8 +371,11 @@ void hmi_init(void)
|
|||
hmi_option = 4; // Active kHz digit
|
||||
hmi_freq = 7074000UL; // Initial frequency
|
||||
|
||||
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Set freq to 7074 kHz
|
||||
SI_SETPHASE(0, 1); // Set phase to 90deg
|
||||
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Set freq to 7074 kHz (depends on mixer type)
|
||||
SI_SETPHASE(0, 1); // Set phase to 90deg (depends on mixer type)
|
||||
|
||||
ptt_state = 0;
|
||||
ptt_active = false;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -328,14 +387,14 @@ void hmi_evaluate(void)
|
|||
char s[32];
|
||||
|
||||
// Print top line of display
|
||||
sprintf(s, "%s %7.1f %c%3d", hmi_o_mode[hmi_sub[HMI_S_MODE]], (double)hmi_freq/1000.0, (tx_enabled?'T':'R'),920);
|
||||
sprintf(s, "%s %7.1f %c%3d", hmi_o_mode[hmi_sub[HMI_S_MODE]], (double)hmi_freq/1000.0, (tx_enabled?0x07:0x06), (tx_enabled?0:920));
|
||||
lcd_writexy(0,0,s);
|
||||
|
||||
// Print bottom line of dsiplay, depending on state
|
||||
switch (hmi_state)
|
||||
{
|
||||
case HMI_S_TUNE:
|
||||
sprintf(s, " %s %s", hmi_o_agc[hmi_sub[HMI_S_AGC]], hmi_o_pre[hmi_sub[HMI_S_PRE]]);
|
||||
sprintf(s, "%s %s %s", hmi_o_vox[hmi_sub[HMI_S_VOX]], hmi_o_agc[hmi_sub[HMI_S_AGC]], hmi_o_pre[hmi_sub[HMI_S_PRE]]);
|
||||
lcd_writexy(0,1,s);
|
||||
lcd_curxy(4+(hmi_option>4?6:hmi_option), 0, true);
|
||||
break;
|
||||
|
@ -354,10 +413,36 @@ void hmi_evaluate(void)
|
|||
lcd_writexy(0,1,s);
|
||||
lcd_curxy(8, 1, false);
|
||||
break;
|
||||
case HMI_S_VOX:
|
||||
sprintf(s, "Set VOX: %s ", hmi_o_vox[hmi_option]);
|
||||
lcd_writexy(0,1,s);
|
||||
lcd_curxy(8, 1, false);
|
||||
break;
|
||||
case HMI_S_BPF:
|
||||
sprintf(s, "Band: %d %s ", hmi_option, hmi_o_test[hmi_option]);
|
||||
lcd_writexy(0,1,s);
|
||||
lcd_curxy(8, 1, false);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SI_SETFREQ(0, hmi_freq); // Set freq to latest
|
||||
/* PTT debouncing */
|
||||
if (gpio_get(GP_PTT)) // Get PTT level
|
||||
{
|
||||
if (ptt_state<PTT_DEBOUNCE) // Increment debounce counter when high
|
||||
ptt_state++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ptt_state>0) // Decrement debounce counter when low
|
||||
ptt_state--;
|
||||
}
|
||||
if (ptt_state == PTT_DEBOUNCE) // Reset PTT when debonced level high
|
||||
ptt_active = false;
|
||||
if (ptt_state == 0) // Set PTT when debounced level low
|
||||
ptt_active = true;
|
||||
|
||||
/* Set freq to latest entered value */
|
||||
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq);
|
||||
}
|
||||
|
||||
|
|
2
hmi.h
2
hmi.h
|
@ -9,6 +9,8 @@
|
|||
* See hmi.c for more information
|
||||
*/
|
||||
|
||||
extern bool ptt_active;
|
||||
|
||||
void hmi_init(void);
|
||||
void hmi_evaluate(void);
|
||||
|
||||
|
|
93
lcd.c
93
lcd.c
|
@ -83,80 +83,85 @@
|
|||
#define LCD_DATA 0x40
|
||||
|
||||
/* I2C address and pins */
|
||||
#define I2C_LCD 0x3E
|
||||
#define I2C0_SDA 16
|
||||
#define I2C0_SCL 17
|
||||
#define I2C_LCD 0x3E
|
||||
|
||||
|
||||
uint8_t cgram[65] = // Write CGRAM
|
||||
{ // 8x8 bytes
|
||||
0x80,
|
||||
0x08, 0x10, 0x08, 0x10, 0x08, 0x10, 0x08, 0x00,
|
||||
0x08, 0x10, 0x08, 0x10, 0x08, 0x10, 0x0b, 0x00,
|
||||
0x08, 0x10, 0x08, 0x10, 0x08, 0x13, 0x0b, 0x00,
|
||||
0x08, 0x10, 0x08, 0x10, 0x0b, 0x13, 0x0b, 0x00,
|
||||
0x08, 0x10, 0x08, 0x13, 0x0b, 0x13, 0x0b, 0x00,
|
||||
0x08, 0x10, 0x0b, 0x13, 0x0b, 0x13, 0x0b, 0x00,
|
||||
0x08, 0x13, 0x0b, 0x13, 0x0b, 0x13, 0x0b, 0x00,
|
||||
0x0b, 0x13, 0x0b, 0x13, 0x0b, 0x13, 0x0b, 0x00
|
||||
/*
|
||||
* User defined characters
|
||||
*/
|
||||
uint8_t cgram[8][8] = // Write CGRAM
|
||||
{
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 0x00: blank
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00}, // 0x01: Level 1
|
||||
{0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00}, // 0x02: Level 2
|
||||
{0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00}, // 0x03: Level 3
|
||||
{0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00}, // 0x04: Level 4
|
||||
{0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00}, // 0x05: Level 5
|
||||
{0x00, 0x04, 0x04, 0x04, 0x1f, 0x0e, 0x04, 0x00}, // 0x06: Receive arrow down
|
||||
{0x04, 0x0e, 0x1f, 0x04, 0x04, 0x04, 0x00, 0x00} // 0x07: Transmit arrow up
|
||||
};
|
||||
|
||||
|
||||
void lcd_init(void)
|
||||
{
|
||||
uint8_t txdata[8];
|
||||
|
||||
/* I2C0 initialisation at 400Khz. */
|
||||
i2c_init(i2c0, 400*1000);
|
||||
gpio_set_function(I2C0_SDA, GPIO_FUNC_I2C);
|
||||
gpio_set_function(I2C0_SCL, GPIO_FUNC_I2C);
|
||||
gpio_pull_up(I2C0_SDA);
|
||||
gpio_pull_up(I2C0_SCL);
|
||||
uint8_t txdata[10];
|
||||
uint8_t i;
|
||||
|
||||
sleep_ms(50);
|
||||
txdata[0] = LCD_COMMAND;
|
||||
|
||||
/* Initialize function set (see datasheet fig 23)*/
|
||||
txdata[0] = LCD_COMMAND;
|
||||
txdata[1] = LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE | LCD_5x8DOTS;
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(4500);
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(100);
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
|
||||
/* Initialize display control */
|
||||
txdata[0] = LCD_COMMAND;
|
||||
txdata[1] = LCD_DISPLAYCONTROL | LCD_DISPLAYOFF | LCD_CURSOROFF | LCD_BLINKOFF;
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
|
||||
/* Display clear */
|
||||
txdata[0] = LCD_COMMAND;
|
||||
txdata[1] = LCD_CLEARDISPLAY;
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(1530);
|
||||
|
||||
/* Initialize entry mode set */
|
||||
txdata[0] = LCD_COMMAND;
|
||||
txdata[1] = LCD_ENTRYMODESET | LCD_ENTRYINC | LCD_ENTRYNOSHIFT;
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
|
||||
/* Load CGRAM */
|
||||
txdata[1] = 0x40; //Set CGRAM address 0
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
i2c_write_blocking(i2c0, I2C_LCD, cgram, 65, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
for (i=0; i<8; i++)
|
||||
{
|
||||
txdata[0] = LCD_COMMAND;
|
||||
txdata[1] = LCD_SETCGRAMADDR | (i<<3); //Set CGRAM address
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
txdata[0] = LCD_DATA;
|
||||
for (int j=0; j<8; j++) txdata[1+j] = cgram[i][j];
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 9, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
}
|
||||
|
||||
/* Initialize display control */
|
||||
txdata[0] = LCD_COMMAND;
|
||||
txdata[1] = LCD_DISPLAYCONTROL | LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
|
||||
/* Display clear once more */
|
||||
txdata[0] = LCD_COMMAND;
|
||||
txdata[1] = LCD_CLEARDISPLAY;
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(1530);
|
||||
}
|
||||
|
||||
|
@ -166,7 +171,7 @@ void lcd_clear(void)
|
|||
|
||||
txdata[0] = LCD_COMMAND;
|
||||
txdata[1] = LCD_CLEARDISPLAY;
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(1530);
|
||||
}
|
||||
|
||||
|
@ -178,12 +183,12 @@ void lcd_curxy(uint8_t x, uint8_t y, bool on)
|
|||
y &= 0x01;
|
||||
txdata[0] = LCD_COMMAND;
|
||||
txdata[1] = x | 0x80 | (y==1?0x40:0x00);
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
|
||||
txdata[0] = LCD_COMMAND;
|
||||
txdata[1] = LCD_DISPLAYCONTROL | LCD_DISPLAYON | (on?LCD_CURSORON:LCD_CURSOROFF) | LCD_BLINKOFF;
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
}
|
||||
|
||||
|
@ -195,12 +200,12 @@ void lcd_putxy(uint8_t x, uint8_t y, uint8_t c)
|
|||
y &= 0x01;
|
||||
txdata[0] = LCD_COMMAND;
|
||||
txdata[1] = x | 0x80 | (y==1?0x40:0x00);
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
|
||||
txdata[0] = LCD_DATA;
|
||||
txdata[1] = c;
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
}
|
||||
|
||||
|
@ -213,14 +218,14 @@ void lcd_writexy(uint8_t x, uint8_t y, uint8_t *s)
|
|||
y &= 0x01;
|
||||
txdata[0] = LCD_COMMAND;
|
||||
txdata[1] = x | 0x80 | ((y==1)?0x40:0x00);
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, 2, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
|
||||
len = strlen(s);
|
||||
len = (len>(16-x))?(16-x):len;
|
||||
txdata[0] = LCD_DATA;
|
||||
for(i=0; i<len; i++) txdata[i+1]=s[i];
|
||||
i2c_write_blocking(i2c0, I2C_LCD, txdata, len+1, false);
|
||||
i2c_write_blocking(i2c1, I2C_LCD, txdata, len+1, false);
|
||||
sleep_us(LCD_DELAY);
|
||||
}
|
||||
|
||||
|
|
3
lcd.h
3
lcd.h
|
@ -9,9 +9,6 @@
|
|||
* See lcd.c for more information
|
||||
*/
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/i2c.h"
|
||||
|
||||
void lcd_init(void);
|
||||
void lcd_clear(void);
|
||||
void lcd_curxy(uint8_t x, uint8_t y, bool on);
|
||||
|
|
154
monitor.c
154
monitor.c
|
@ -10,46 +10,67 @@
|
|||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "lcd.h"
|
||||
#include "si5351.h"
|
||||
#include "dsp.h"
|
||||
#include "relay.h"
|
||||
#include "monitor.h"
|
||||
|
||||
/* Monitor definitions */
|
||||
|
||||
#define CR 13
|
||||
#define LF 10
|
||||
#define CMD_LEN 32
|
||||
|
||||
char mon_cmd[CMD_LEN+1];
|
||||
|
||||
#define SP 32
|
||||
#define CMD_LEN 80
|
||||
#define CMD_ARGS 16
|
||||
|
||||
char mon_cmd[CMD_LEN+1]; // Command string buffer
|
||||
char *argv[CMD_ARGS]; // Argument pointers
|
||||
int nargs; // Nr of arguments
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *cmdstr; // Command string
|
||||
int cmdlen; // Command string length
|
||||
void (*cmd)(char* par); // Command executive
|
||||
void (*cmd)(void); // Command executive
|
||||
char *cmdsyn; // Command syntax
|
||||
char *help; // Command help text
|
||||
} shell_t;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------- */
|
||||
/* Below the definitions of the shell commands, add where needed */
|
||||
/* ------------------------------------------------------------- */
|
||||
|
||||
|
||||
/*** Initialisation, called at startup ***/
|
||||
void mon_init()
|
||||
{
|
||||
stdio_init_all(); // Initialize Standard IO
|
||||
mon_cmd[CMD_LEN] = '\0'; // Termination to be sure
|
||||
printf("\n");
|
||||
printf("=============\n");
|
||||
printf(" uSDR-Pico \n");
|
||||
printf(" PE1ATM \n");
|
||||
printf(" 2021, Udjat \n");
|
||||
printf("=============\n");
|
||||
printf("Pico> "); // prompt
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*** ------------------------------------------------------------- ***/
|
||||
/*** Below the definitions of the shell commands, add where needed ***/
|
||||
/*** ------------------------------------------------------------- ***/
|
||||
|
||||
/*
|
||||
* Dumps a defined range of Si5351 registers
|
||||
*/
|
||||
uint8_t si5351_reg[200];
|
||||
void mon_si(char *par)
|
||||
void mon_si(void)
|
||||
{
|
||||
int base=0, nreg=200, i;
|
||||
|
||||
// Next: p = strtok(NULL, delim); (returns NULL if none left)
|
||||
for (i=0; i<nreg; i++) si5351_reg[i] = 0xaa;
|
||||
si_getreg(si5351_reg, (uint8_t)base, (uint8_t)nreg);
|
||||
for (i=0; i<nreg; i++) printf("%02x ",(int)(si5351_reg[i]));
|
||||
|
@ -58,9 +79,9 @@ void mon_si(char *par)
|
|||
|
||||
|
||||
/*
|
||||
* Dumps the complete built-in and programmed characterset on the LCD
|
||||
* Dumps the entire built-in and programmed characterset on the LCD
|
||||
*/
|
||||
void mon_lt(char *par)
|
||||
void mon_lt(void)
|
||||
{
|
||||
printf("Check LCD...");
|
||||
lcd_test();
|
||||
|
@ -72,7 +93,7 @@ void mon_lt(char *par)
|
|||
* Checks for inter-core fifo overruns
|
||||
*/
|
||||
extern volatile uint32_t fifo_overrun, fifo_rx, fifo_tx, fifo_xx, fifo_incnt;
|
||||
void mon_fo(char *par)
|
||||
void mon_fo(void)
|
||||
{
|
||||
printf("Fifo input: %lu\n", fifo_incnt);
|
||||
printf("Fifo rx: %lu\n", fifo_rx);
|
||||
|
@ -86,7 +107,7 @@ void mon_fo(char *par)
|
|||
* Toggles the PTT status, overriding the HW signal
|
||||
*/
|
||||
bool ptt = false;
|
||||
void mon_pt(char *par)
|
||||
void mon_pt(void)
|
||||
{
|
||||
if (ptt)
|
||||
{
|
||||
|
@ -101,60 +122,109 @@ void mon_pt(char *par)
|
|||
tx_enabled = ptt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Relay read or write
|
||||
*/
|
||||
void mon_bp(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (*argv[1]=='w')
|
||||
{
|
||||
if (nargs>=2)
|
||||
{
|
||||
ret = atoi(argv[2]);
|
||||
relay_setband((uint8_t)ret);
|
||||
}
|
||||
}
|
||||
ret = relay_getband();
|
||||
if (ret<0)
|
||||
printf ("I2C read error\n");
|
||||
else
|
||||
printf("%02x\n", ret);
|
||||
}
|
||||
|
||||
#define NCMD 4
|
||||
/*
|
||||
* Relay read or write
|
||||
*/
|
||||
void mon_rx(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (*argv[1]=='w')
|
||||
{
|
||||
if (nargs>=2)
|
||||
{
|
||||
ret = atoi(argv[2]);
|
||||
relay_setattn((uint8_t)ret);
|
||||
}
|
||||
}
|
||||
ret = relay_getattn();
|
||||
if (ret<0)
|
||||
printf ("I2C read error\n");
|
||||
else
|
||||
printf("%02x\n", ret);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Command shell table, organize the command functions above
|
||||
*/
|
||||
#define NCMD 6
|
||||
shell_t shell[NCMD]=
|
||||
{
|
||||
{"si", 2, &mon_si, "si <start> <nr of reg>", "Dumps Si5351 registers"},
|
||||
{"lt", 2, &mon_lt, "lt (no parameters)", "LCD test, dumps characterset on LCD"},
|
||||
{"fo", 2, &mon_fo, "fo (no parameters)", "Returns inter core fifo overruns"},
|
||||
{"pt", 2, &mon_pt, "pt (no parameters)", "Toggles PTT status"}
|
||||
{"pt", 2, &mon_pt, "pt (no parameters)", "Toggles PTT status"},
|
||||
{"bp", 2, &mon_bp, "bp {r|w} <value>", "Read or Write BPF relays"},
|
||||
{"rx", 2, &mon_rx, "rx {r|w} <value>", "Read or Write RX relays"}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*** ---------------------------------------- ***/
|
||||
/*** Commandstring parser and monitor process ***/
|
||||
/*** ---------------------------------------- ***/
|
||||
|
||||
|
||||
/* Commandstring parser, checks commandstring and invokes shellcommand */
|
||||
char delim[] = " ";
|
||||
/*
|
||||
* Command line parser
|
||||
*/
|
||||
void mon_parse(char* s)
|
||||
{
|
||||
char *p;
|
||||
int i;
|
||||
|
||||
p = s; // Get command part of string
|
||||
for (i=0; i<NCMD; i++)
|
||||
if (strncmp(p, shell[i].cmdstr, shell[i].cmdlen) == 0) break;
|
||||
if (i<NCMD)
|
||||
(*shell[i].cmd)(p);
|
||||
else
|
||||
p = s; // Set to start of string
|
||||
nargs = 0;
|
||||
while (*p!='\0') // Assume stringlength >0
|
||||
{
|
||||
for (i=0; i<NCMD; i++)
|
||||
while (*p==' ') p++; // Skip whitespace
|
||||
if (*p=='\0') break; // String might end in spaces
|
||||
argv[nargs++] = p; // Store first valid char loc after whitespace
|
||||
while ((*p!=' ')&&(*p!='\0')) p++; // Skip non-whitespace
|
||||
}
|
||||
if (nargs==0) return; // No command or parameter
|
||||
for (i=0; i<NCMD; i++) // Lookup shell command
|
||||
if (strncmp(argv[0], shell[i].cmdstr, shell[i].cmdlen) == 0) break;
|
||||
if (i<NCMD)
|
||||
(*shell[i].cmd)();
|
||||
else // Unknown command
|
||||
{
|
||||
for (i=0; i<NCMD; i++) // Print help if no match
|
||||
printf("%s\n %s\n", shell[i].cmdsyn, shell[i].help);
|
||||
}
|
||||
}
|
||||
|
||||
void mon_init()
|
||||
{
|
||||
stdio_init_all(); // Initialize Standard IO
|
||||
mon_cmd[CMD_LEN] = '\0'; // Termination to be sure
|
||||
printf("\n");
|
||||
printf("=============\n");
|
||||
printf(" uSDR-Pico \n");
|
||||
printf(" PE1ATM \n");
|
||||
printf(" 2021, Udjat \n");
|
||||
printf("=============\n");
|
||||
printf("Pico> "); // prompt
|
||||
}
|
||||
|
||||
/*
|
||||
* Monitor process
|
||||
* This function collects characters from stdin until CR
|
||||
* Then the command is send to a parser and executed.
|
||||
*/
|
||||
void mon_evaluate(uint32_t timeout)
|
||||
void mon_evaluate(void)
|
||||
{
|
||||
static int i = 0;
|
||||
int c = getchar_timeout_us(timeout); // NOTE: this is the only SDK way to read from stdin
|
||||
int c = getchar_timeout_us(10L); // NOTE: this is the only SDK way to read from stdin
|
||||
if (c==PICO_ERROR_TIMEOUT) return; // Early bail out
|
||||
|
||||
switch (c)
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
*/
|
||||
|
||||
void mon_init();
|
||||
void mon_evaluate(uint32_t timeout);
|
||||
void mon_evaluate(void);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* relay.c
|
||||
*
|
||||
* Created: Nov 2021
|
||||
* Author: Arjan te Marvelde
|
||||
*
|
||||
* Two PCF8574 expanders are on the I2C bus, one on the RX and one on the BPF board.
|
||||
* The RX (0x42) bit assignments:
|
||||
* 0: Enable -20dB attenuator
|
||||
* 1: Enable -10dB attenuator
|
||||
* 2: Enable +10dB pre-amplifier
|
||||
* The BPF (0x40) bit assignments:
|
||||
* 0: Enable LPF 2.5 MHz
|
||||
* 1: Enable BPF 2.0 - 6.0 MHz
|
||||
* 2: Enable BPF 5.0 -12.0 MHz
|
||||
* 3: Enable BPF 10.0 -24.0 MHz
|
||||
* 4: Enable BPF 20.0 -40.0 MHz
|
||||
*
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/i2c.h"
|
||||
|
||||
#include "relay.h"
|
||||
|
||||
|
||||
/* I2C address and pins */
|
||||
#define I2C_RX 0x21
|
||||
#define I2C_BPF 0x20
|
||||
|
||||
|
||||
void relay_setband(uint8_t val)
|
||||
{
|
||||
uint8_t data[2];
|
||||
|
||||
data[0] = val&0x1f;
|
||||
i2c_write_blocking(i2c1, I2C_BPF, data, 1, false);
|
||||
}
|
||||
|
||||
int relay_getband(void)
|
||||
{
|
||||
uint8_t data[2];
|
||||
int ret;
|
||||
|
||||
ret = i2c_read_blocking(i2c1, I2C_BPF, data, 1, false);
|
||||
if (ret>=0)
|
||||
ret=data[0];
|
||||
return(ret);
|
||||
}
|
||||
|
||||
void relay_setattn(uint8_t val)
|
||||
{
|
||||
uint8_t data[2];
|
||||
|
||||
data[0] = val&0x07;
|
||||
i2c_write_blocking(i2c1, I2C_RX, data, 1, false);
|
||||
}
|
||||
|
||||
int relay_getattn(void)
|
||||
{
|
||||
uint8_t data[2];
|
||||
int ret;
|
||||
|
||||
ret = i2c_read_blocking(i2c1, I2C_RX, data, 1, false);
|
||||
if (ret>=0)
|
||||
ret=data[0];
|
||||
return(ret);
|
||||
}
|
||||
|
||||
void relay_init(void)
|
||||
{
|
||||
relay_setattn(0);
|
||||
relay_setband(0);
|
||||
relay_setattn(REL_PRE_10);
|
||||
relay_setband(REL_BPF12);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef __RELAY_H__
|
||||
#define __RELAY_H__
|
||||
/*
|
||||
* relay.h
|
||||
*
|
||||
* Created: Nov 2021
|
||||
* Author: Arjan te Marvelde
|
||||
*
|
||||
* See relay.c for more information
|
||||
*/
|
||||
|
||||
#define REL_LPF2 0x01
|
||||
#define REL_BPF6 0x02
|
||||
#define REL_BPF12 0x04
|
||||
#define REL_BPF24 0x08
|
||||
#define REL_BPF40 0x10
|
||||
|
||||
#define REL_ATT_30 0x03
|
||||
#define REL_ATT_20 0x01
|
||||
#define REL_ATT_10 0x02
|
||||
#define REL_PRE_10 0x04
|
||||
|
||||
extern void relay_setband(uint8_t val);
|
||||
extern void relay_setattn(uint8_t val);
|
||||
extern int relay_getband(void);
|
||||
extern int relay_getattn(void);
|
||||
extern void relay_init(void);
|
||||
|
||||
#endif
|
61
si5351.c
61
si5351.c
|
@ -61,8 +61,8 @@ NOTE: Phase offsets only work when Ri = 1, this means minimum Fout is 4.762MHz a
|
|||
|
||||
|
||||
|
||||
Control Si5351:
|
||||
================
|
||||
Control Si5351 (see AN619):
|
||||
===========================
|
||||
----+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
@ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||
----+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
|
@ -166,15 +166,12 @@ Control Si5351:
|
|||
|
||||
|
||||
|
||||
#define SI_XTAL_FREQ 24998851UL // Replace with measured crystal frequency of XTAL for CL = 10pF (default)
|
||||
#define SI_XTAL_FREQ 25001414UL // Replace with measured crystal frequency of XTAL for CL = 10pF (default)
|
||||
#define SI_MSN_LO ((0.6e9)/SI_XTAL_FREQ)
|
||||
#define SI_MSN_HI ((0.9e9)/SI_XTAL_FREQ)
|
||||
#define SI_PLL_C 1000000UL // Parameter c for PLL-A and -B setting
|
||||
|
||||
|
||||
/* I2C1 pins */
|
||||
#define I2C1_SDA 18
|
||||
#define I2C1_SCL 19
|
||||
|
||||
vfo_t vfo[2]; // 0: clk0 and clk1 1: clk2
|
||||
|
||||
|
@ -183,9 +180,9 @@ int si_getreg(uint8_t *data, uint8_t reg, uint8_t len)
|
|||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_write_blocking(i2c1, I2C_VFO, ®, 1, true);
|
||||
ret = i2c_write_blocking(i2c0, I2C_VFO, ®, 1, true);
|
||||
if (ret<0) printf ("I2C write error\n");
|
||||
ret = i2c_read_blocking(i2c1, I2C_VFO, data, len, false);
|
||||
ret = i2c_read_blocking(i2c0, I2C_VFO, data, len, false);
|
||||
if (ret<0) printf ("I2C read error\n");
|
||||
return(len);
|
||||
}
|
||||
|
@ -223,7 +220,7 @@ void si_setmsn(uint8_t i)
|
|||
data[6] = ((SI_PLL_C & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16);
|
||||
data[7] = (P2 & 0x0000FF00) >> 8;
|
||||
data[8] = (P2 & 0x000000FF);
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
}
|
||||
|
||||
// Set up registers with MS and R divider for vfo[i], assuming values have been set in vfo[i]
|
||||
|
@ -254,38 +251,38 @@ void si_setmsi(uint8_t i)
|
|||
data[6] = 0x00;
|
||||
data[7] = 0x00;
|
||||
data[8] = 0x00;
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
// If vfo[0] also set clk 1
|
||||
if (i==0)
|
||||
{
|
||||
data[0] = SI_SYNTH_MS1; // Same data in synthesizer
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
if (vfo[0].phase&1) // Phase is either 90 or 270 deg?
|
||||
{
|
||||
data[0] = SI_CLK1_PHOFF;
|
||||
data[1] = vfo[0].msi;
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
|
||||
}
|
||||
else // Phase is 0 or 180 deg
|
||||
{
|
||||
data[0] = SI_CLK1_PHOFF;
|
||||
data[1] = 0;
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
|
||||
}
|
||||
if (vfo[0].phase&2) // Phase is 180 or 270 deg?
|
||||
{
|
||||
data[0] = SI_CLK1_CTL;
|
||||
data[1] = 0x5f; // CLK1: INT, PLLA, INV, MS, 8mA
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||
data[1] = 0x5d; // CLK1: INT, PLLA, INV, MS, 8mA
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset associated PLL
|
||||
data[0] = SI_PLL_RESET;
|
||||
data[1] = (i==1)?0x80:0x20;
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
|
||||
}
|
||||
|
||||
|
||||
|
@ -335,13 +332,7 @@ void si_init(void)
|
|||
{
|
||||
uint8_t data[16]; // I2C trx buffer
|
||||
|
||||
i2c_init(i2c1, 400*1000);
|
||||
gpio_set_function(I2C1_SDA, GPIO_FUNC_I2C);
|
||||
gpio_set_function(I2C1_SCL, GPIO_FUNC_I2C);
|
||||
gpio_pull_up(I2C1_SDA);
|
||||
gpio_pull_up(I2C1_SCL);
|
||||
|
||||
// Hard initialize Synth registers: all 10MHz, CLK1 90 deg ahead, PLLA for CLK 0&1, PLLB for CLK2
|
||||
// Hard initialize Synth registers: 7.074MHz, CLK1 90 deg ahead, PLLA for CLK 0&1, PLLB for CLK2
|
||||
// Ri=1,
|
||||
// MSi=68, P1=8192, P2=0, P3=1
|
||||
// MSN=27.2 P1=2969, P2=600000, P3=1000000
|
||||
|
@ -368,12 +359,12 @@ void si_init(void)
|
|||
data[6] = 0xf9; // MSNA_P3[19:16] , MSNA_P2[19:16]
|
||||
data[7] = 0x27; // MSNA_P2[15:8]
|
||||
data[8] = 0xc0; // MSNA_P2[7:0]
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
|
||||
// PLLB: MSN P1=0x00000b99, P2=0x000927c0, P3=0x000f4240
|
||||
data[0] = SI_SYNTH_PLLB; // Same content
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
// MS0 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1
|
||||
data[0] = SI_SYNTH_MS0;
|
||||
|
@ -385,43 +376,43 @@ void si_init(void)
|
|||
data[6] = 0x00; // MS0_P3[19:16] , MS0_P2[19:16]
|
||||
data[7] = 0x00; // MS0_P2[15:8]
|
||||
data[8] = 0x00; // MS0_P2[7:0]
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
// MS1 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1
|
||||
data[0] = SI_SYNTH_MS1; // Same content
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
// MS2 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1
|
||||
data[0] = SI_SYNTH_MS2; // Same content
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
// Phase offsets for 3 clocks
|
||||
data[0] = SI_CLK0_PHOFF;
|
||||
data[1] = 0x00; // CLK0: phase 0 deg
|
||||
data[2] = 0x44; // CLK1: phase 90 deg (=MSi)
|
||||
data[3] = 0x00; // CLK2: phase 0 deg
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 4, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 4, false);
|
||||
|
||||
// Output port settings for 3 clocks
|
||||
data[0] = SI_CLK0_CTL;
|
||||
data[1] = 0x4f; // CLK0: INT, PLLA, nonINV, MS, 8mA
|
||||
data[2] = 0x4f; // CLK1: INT, PLLA, nonINV, MS, 8mA
|
||||
data[1] = 0x4d; // CLK0: INT, PLLA, nonINV, MS, 4mA
|
||||
data[2] = 0x4d; // CLK1: INT, PLLA, nonINV, MS, 4mA
|
||||
data[3] = 0x6f; // CLK2: INT, PLLB, nonINV, MS, 8mA
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 4, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 4, false);
|
||||
|
||||
// Disable spread spectrum (startup state is undefined)
|
||||
data[0] = SI_SS_EN;
|
||||
data[1] = 0x00;
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
|
||||
|
||||
// Reset both PLL
|
||||
data[0] = SI_PLL_RESET;
|
||||
data[1] = 0xa0;
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
|
||||
|
||||
// Enable all outputs
|
||||
data[0] = SI_CLK_OE;
|
||||
data[1] = 0x00;
|
||||
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
|
||||
}
|
||||
|
||||
|
|
53
uSDR.c
53
uSDR.c
|
@ -16,6 +16,8 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/sem.h"
|
||||
#include "hardware/i2c.h"
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/timer.h"
|
||||
#include "hardware/clocks.h"
|
||||
|
@ -25,7 +27,15 @@
|
|||
#include "dsp.h"
|
||||
#include "si5351.h"
|
||||
#include "monitor.h"
|
||||
#include "relay.h"
|
||||
|
||||
#define LED_MS 1000
|
||||
#define LOOP_MS 10
|
||||
|
||||
#define I2C0_SDA 16
|
||||
#define I2C0_SCL 17
|
||||
#define I2C1_SDA 18
|
||||
#define I2C1_SCL 19
|
||||
|
||||
/*
|
||||
* LED TIMER definition and callback routine
|
||||
|
@ -40,6 +50,18 @@ bool led_callback(struct repeating_timer *t)
|
|||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scheduler callback function.
|
||||
* This executes every LOOP_MS.
|
||||
*/
|
||||
semaphore_t loop_sem;
|
||||
struct repeating_timer loop_timer;
|
||||
bool loop_callback(struct repeating_timer *t)
|
||||
{
|
||||
sem_release(&loop_sem);
|
||||
return(true);
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
|
@ -47,7 +69,27 @@ int main()
|
|||
gpio_init(PICO_DEFAULT_LED_PIN);
|
||||
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
|
||||
gpio_put(PICO_DEFAULT_LED_PIN, true); // Set LED on
|
||||
add_repeating_timer_ms(-1000, led_callback, NULL, &led_timer);
|
||||
add_repeating_timer_ms(-LED_MS, led_callback, NULL, &led_timer);
|
||||
|
||||
/*
|
||||
* i2c0 is used for the si5351 interface
|
||||
* i2c1 is used for the LCD and all other interfaces
|
||||
*/
|
||||
|
||||
/* i2c0 initialisation at 400Khz. */
|
||||
i2c_init(i2c0, 400*1000);
|
||||
gpio_set_function(I2C0_SDA, GPIO_FUNC_I2C);
|
||||
gpio_set_function(I2C0_SCL, GPIO_FUNC_I2C);
|
||||
gpio_pull_up(I2C0_SDA);
|
||||
gpio_pull_up(I2C0_SCL);
|
||||
|
||||
/* i2c1 initialisation at 400Khz. */
|
||||
i2c_init(i2c1, 400*1000);
|
||||
gpio_set_function(I2C1_SDA, GPIO_FUNC_I2C);
|
||||
gpio_set_function(I2C1_SCL, GPIO_FUNC_I2C);
|
||||
gpio_pull_up(I2C1_SDA);
|
||||
gpio_pull_up(I2C1_SCL);
|
||||
|
||||
|
||||
/* Initialize units */
|
||||
mon_init(); // Monitor shell on stdio
|
||||
|
@ -55,10 +97,15 @@ int main()
|
|||
dsp_init(); // Signal processing unit
|
||||
lcd_init(); // LCD output unit
|
||||
hmi_init(); // HMI user inputs
|
||||
relay_init();
|
||||
|
||||
while (1)
|
||||
/* A simple round-robin scheduler */
|
||||
sem_init(&loop_sem, 1, 1) ;
|
||||
add_repeating_timer_ms(-LOOP_MS, loop_callback, NULL, &loop_timer);
|
||||
while (1)
|
||||
{
|
||||
mon_evaluate(10000L); // Check monitor input, wait max 10000 usec
|
||||
sem_acquire_blocking(&loop_sem); // Wait until timer callback releases sem
|
||||
mon_evaluate(); // Check monitor input
|
||||
si_evaluate(); // Refresh VFO settings
|
||||
hmi_evaluate(); // Refresh HMI
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue