pull/13/head
ArjanteMarvelde 2021-11-05 13:00:21 +01:00
rodzic ee39455791
commit 493756488a
4 zmienionych plików z 195 dodań i 118 usunięć

BIN
doc/uSDR - v1.5.pdf 100644

Plik binarny nie jest wyświetlany.

75
dsp.c
Wyświetl plik

@ -38,6 +38,7 @@
#include "dsp.h" #include "dsp.h"
/* /*
* DAC_RANGE defines PWM cycle, determining DAC resolution and PWM frequency. * DAC_RANGE defines PWM cycle, determining DAC resolution and PWM frequency.
* DAC resolution = Vcc / DAC_RANGE * DAC resolution = Vcc / DAC_RANGE
@ -66,10 +67,48 @@
* AGC reference level is log2(64) = 6, where 64 is the MSB of half DAC_RANGE * AGC reference level is log2(64) = 6, where 64 is the MSB of half DAC_RANGE
* 1/AGC_DECAY and 1/AGC_ATTACK are multipliers before agc_gain value integrator * 1/AGC_DECAY and 1/AGC_ATTACK are multipliers before agc_gain value integrator
* These values should ultimately be set by the HMI. * 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
* 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
*/ */
#define AGC_REF 6 #define AGC_REF 6
#define AGC_DECAY 1024 #define AGC_DECAY 8192
#define AGC_ATTACK 128 #define AGC_FAST 64
#define AGC_SLOW 4096
#define AGC_OFF 65534
volatile uint16_t agc_decay = AGC_OFF;
volatile uint16_t agc_attack = AGC_OFF;
void dsp_setagc(int agc)
{
switch(agc)
{
case 1: //SLOW, for values see hmi.c
agc_attack = AGC_SLOW;
agc_decay = AGC_DECAY;
break;
case 2: //FAST
agc_attack = AGC_FAST;
agc_decay = AGC_DECAY;
break;
default: //OFF
agc_attack = AGC_OFF;
agc_decay = AGC_OFF;
break;
}
}
/*
* MODE is modulation/demodulation
* This setting steers the signal processing branch chosen
*/
volatile uint16_t dsp_mode; // For values see hmi.c
void dsp_setmode(int mode)
{
dsp_mode = (uint16_t)mode;
}
/* /*
* Low pass filters Fc=3, 7 and 15 kHz (see http://t-filter.engineerjs.com/) * Low pass filters Fc=3, 7 and 15 kHz (see http://t-filter.engineerjs.com/)
@ -88,6 +127,7 @@ volatile uint32_t fifo_overrun, fifo_rx, fifo_tx, fifo_xx, fifo_incnt;
volatile bool tx_enabled; volatile bool tx_enabled;
/* /*
* Some macro's * Some macro's
* See Alpha Max plus Beta Min algorithm for MAG (vector length) * See Alpha Max plus Beta Min algorithm for MAG (vector length)
@ -203,7 +243,9 @@ bool rx(void)
/*** DEMODULATION ***/ /*** DEMODULATION ***/
switch(dsp_mode)
{
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) * Qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
@ -211,13 +253,26 @@ bool rx(void)
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; 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 >> 12;
a_sample = i_s[7] - qh; a_sample = i_s[7] - qh;
break;
case 1: //LSB
/*
* 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;
a_sample = i_s[7] + qh;
break;
case 2: //AM
/* /*
* AM demodulate: sqrt(sqr(i)+sqr(q)) * AM demodulate: sqrt(sqr(i)+sqr(q))
* Approximated with MAG(i,q) * Approximated with MAG(i,q)
*/ */
// a_sample = MAG(i_s[14], q_s[14]); a_sample = MAG(i_s[14], q_s[14]);
break;
default:
break;
}
/*** AUDIO GENERATION ***/ /*** AUDIO GENERATION ***/
/* /*
@ -231,14 +286,14 @@ bool rx(void)
if (i&0x000c) {k+=2; i>>=2;} if (i&0x000c) {k+=2; i>>=2;}
if (i&0x0002) {k+=1;} if (i&0x0002) {k+=1;}
agc_accu += (k - AGC_REF); // Add difference with target to integrator (Acc += Xn - R) agc_accu += (k - AGC_REF); // Add difference with target to integrator (Acc += Xn - R)
if (agc_accu > AGC_ATTACK) // Attack time, gain correction in case of high level if (agc_accu > agc_attack) // Attack time, gain correction in case of high level
{ {
agc_gain--; // Decrease gain agc_gain--; // Decrease gain
agc_accu -= AGC_ATTACK; // Reset integrator agc_accu -= agc_attack; // Reset integrator
} else if (agc_accu < -(AGC_DECAY)) // Decay time, gain correction in case of low level } else if (agc_accu < -(agc_decay)) // Decay time, gain correction in case of low level
{ {
agc_gain++; // Increase gain agc_gain++; // Increase gain
agc_accu += AGC_DECAY; // Reset integrator agc_accu += agc_decay; // Reset integrator
} }

7
dsp.h
Wyświetl plik

@ -13,7 +13,14 @@
#include "hardware/adc.h" #include "hardware/adc.h"
#include "hardware/pwm.h" #include "hardware/pwm.h"
void dsp_setagc(int agc);
void dsp_setmode(int mode);
extern volatile bool tx_enabled; extern volatile bool tx_enabled;
#define DSP_SETPTT(on) tx_enabled = (on)
void dsp_init(); void dsp_init();

209
hmi.c
Wyświetl plik

@ -51,36 +51,36 @@
#define GP_MASK_IN ((1<<GP_ENC_A)|(1<<GP_ENC_B)|(1<<GP_AUX_0)|(1<<GP_AUX_1)|(1<<GP_AUX_2)|(1<<GP_AUX_3)|(1<<GP_PTT)) #define GP_MASK_IN ((1<<GP_ENC_A)|(1<<GP_ENC_B)|(1<<GP_AUX_0)|(1<<GP_AUX_1)|(1<<GP_AUX_2)|(1<<GP_AUX_3)|(1<<GP_PTT))
/* /*
* Events: GPIO_IRQ_LEVEL_LOW, GPIO_IRQ_LEVEL_HIGH, GPIO_IRQ_EDGE_FALL, GPIO_IRQ_EDGE_RISE * Event flags
*/ */
#define GPIO_IRQ_ALL (GPIO_IRQ_LEVEL_LOW|GPIO_IRQ_LEVEL_HIGH|GPIO_IRQ_EDGE_FALL|GPIO_IRQ_EDGE_RISE) #define GPIO_IRQ_ALL (GPIO_IRQ_LEVEL_LOW|GPIO_IRQ_LEVEL_HIGH|GPIO_IRQ_EDGE_FALL|GPIO_IRQ_EDGE_RISE)
#define GPIO_IRQ_EDGE_ALL (GPIO_IRQ_EDGE_FALL|GPIO_IRQ_EDGE_RISE) #define GPIO_IRQ_EDGE_ALL (GPIO_IRQ_EDGE_FALL|GPIO_IRQ_EDGE_RISE)
/* /*
* Display layout: +----------------+ * Display layout:
* |USB 14074.0 920| --> USB mode, 14074.0 kHz, S9+20dB
* |Tune Att Fast| --> Menu:Tune, Attenuator, Fast AGC
* +----------------+ * +----------------+
* LEFT and RIGHT buttons (or encoder) are used to navigate sub-menus such as {tune,mode,agc,pre}. * |USB 14074.0 R920| --> mode=USB, freq=14074.0kHz, state=Rx,S9+20dB
* ENTER is used to get into the sub-menu. * | Fast -10dB| --> ..., AGC=Fast, Pre=-10dB
* ENTER is used again to exit and accept changes or ESCAPE to exit without changes. * +----------------+
* In this HMI state only tuning is possible,
* 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):
* *
* When entered in a submenu: * Submenu Values ENC Enter Escape Left Right
* Menu Values Encoder Enter Escape Left Right
* ------------------------------------------------------------------------------------- * -------------------------------------------------------------------------------------
* Mode USB, LSB, AM, CW <value> Accept Exit <value> <value> * Mode USB, LSB, AM, CW change commit exit prev next
* Tune Frequency (digit) <value> Accept Exit <=dig dig=> * AGC Fast, Slow, Off change commit exit prev next
* AGC Fast, Slow, Off <value> Accept Exit <value> <value> * Pre +10dB, 0, -10dB, -20dB change commit exit prev next
* Pre +20dB, 0, -20dB <value> Accept Exit <value> <value> *
* --will be extended--
*/ */
/* State definitions */ /* State definitions */
#define HMI_S_MENU 0 #define HMI_S_TUNE 0
#define HMI_S_TUNE 1 #define HMI_S_MODE 1
#define HMI_S_MODE 2 #define HMI_S_AGC 2
#define HMI_S_AGC 3 #define HMI_S_PRE 3
#define HMI_S_PRE 4 #define HMI_NSTATES 4
#define HMI_NSTATES 5
/* Event definitions */ /* Event definitions */
#define HMI_E_NOEVENT 0 #define HMI_E_NOEVENT 0
@ -97,20 +97,25 @@
/* Sub menu option string sets */ /* Sub menu option string sets */
#define HMI_NMODE 4 #define HMI_NMODE 4
#define HMI_NAGC 3 #define HMI_NAGC 3
#define HMI_NPRE 3 #define HMI_NPRE 4
char hmi_o_menu[HMI_NSTATES][8] = {"Menu","Tune","Mode","AGC ","Pre "}; // Selected by hmi_state 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 "}; // Selected by hmi_option/hmi_mode 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"}; // Selected by hmi_option/hmi_agc char hmi_o_agc [HMI_NAGC][8] = {"NoGC", "Slow", "Fast"}; // Indexed by hmi_sub[HMI_S_AGC]
char hmi_o_pre [HMI_NPRE][8] = {"Off", "Amp", "Att"}; // Selected by hmi_option/hmi_pre char hmi_o_pre [HMI_NPRE][8] = {"-20dB", "-10dB", "0dB", "+10dB"}; // Indexed by hmi_sub[HMI_S_PRE]
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_state, hmi_option; // Current state and option
uint8_t hmi_sub[HMI_NSTATES] = {0,4,0,0,0}; // Stored option per state
uint32_t hmi_freq; // Frequency from Tune state uint32_t hmi_freq; // Frequency from Tune state
uint32_t hmi_step[6] = {10000000, 1000000, 100000, 10000, 1000, 100}; // Frequency digit increments uint32_t hmi_step[6] = {10000000, 1000000, 100000, 10000, 1000, 100}; // Frequency digit increments
#define HMI_MAXFREQ 30000000 #define HMI_MAXFREQ 30000000
#define HMI_MINFREQ 100 #define HMI_MINFREQ 100
#define HMI_MULFREQ 1 // Factor between HMI and actual frequency #define HMI_MULFREQ 1 // Factor between HMI and actual frequency
// Set to 2 for certain types of mixer
/*
* Some macros
*/
#ifndef MIN #ifndef MIN
#define MIN(x, y) ((x)<(y)?(x):(y)) // Get min value #define MIN(x, y) ((x)<(y)?(x):(y)) // Get min value
#endif #endif
@ -119,107 +124,118 @@ uint32_t hmi_step[6] = {10000000, 1000000, 100000, 10000, 1000, 100}; // Frequen
#endif #endif
/* /*
* Finite State Machine, * HMI State Machine,
* Handle event according to current state * Handle event according to current state
* Code needs to be optimized
*/ */
void hmi_handler(uint8_t event) void hmi_handler(uint8_t event)
{ {
switch(hmi_state) /* Special case for TUNE state */
if (hmi_state == HMI_S_TUNE)
{ {
case HMI_S_MENU: if (event==HMI_E_ENTER) // Commit current value
if ((event==HMI_E_INCREMENT)||(event==HMI_E_RIGHT))
hmi_option = (hmi_option<HMI_NSTATES-1)?hmi_option+1:HMI_NSTATES-1;
if ((event==HMI_E_DECREMENT)||(event==HMI_E_LEFT))
hmi_option = (hmi_option>1)?hmi_option-1:0;
if (event==HMI_E_ENTER)
{ {
hmi_state = hmi_option; // Enter new submenu
hmi_option = hmi_sub[hmi_state]; // Restore option
}
break;
case HMI_S_TUNE:
if (event==HMI_E_ENTER)
{
hmi_sub[hmi_state] = hmi_option; // Store option
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Commit frequency SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Commit frequency
} }
if (event==HMI_E_ESCAPE) if (event==HMI_E_ESCAPE) // Enter submenus
{ {
hmi_sub[hmi_state] = hmi_option; // Store option hmi_sub[hmi_state] = hmi_option; // Store selection (i.e. digit)
hmi_option = hmi_state; hmi_state = HMI_S_MODE; // Should remember last one
hmi_state = HMI_S_MENU; // Leave submenu hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
} }
if (event==HMI_E_INCREMENT) if (event==HMI_E_INCREMENT)
{ {
hmi_freq+= hmi_step[hmi_option]; if (hmi_freq < (HMI_MAXFREQ - hmi_step[hmi_option])) // Boundary check
hmi_freq = MIN(hmi_freq , HMI_MAXFREQ); hmi_freq += hmi_step[hmi_option]; // Increment selected digit
} }
if (event==HMI_E_DECREMENT) if (event==HMI_E_DECREMENT)
hmi_freq = (hmi_freq>hmi_step[hmi_option]+HMI_MINFREQ)?hmi_freq-hmi_step[hmi_option]:HMI_MINFREQ; {
if (hmi_freq > (hmi_step[hmi_option] + HMI_MINFREQ)) // Boundary check
hmi_freq -= hmi_step[hmi_option]; // Decrement selected digit
}
if (event==HMI_E_RIGHT) if (event==HMI_E_RIGHT)
hmi_option = (hmi_option<6)?hmi_option+1:6; {
hmi_option = (hmi_option<6)?hmi_option+1:6; // Digit to the right
}
if (event==HMI_E_LEFT) if (event==HMI_E_LEFT)
hmi_option = (hmi_option>0)?hmi_option-1:0; {
break; hmi_option = (hmi_option>0)?hmi_option-1:0; // Digit to the left
}
return;
}
/* Submenu states */
switch(hmi_state)
{
case HMI_S_MODE: case HMI_S_MODE:
if (event==HMI_E_ENTER) if (event==HMI_E_ENTER)
{ {
// Set Mode dsp_setmode(hmi_option); // Commit Mode
hmi_sub[hmi_state] = hmi_option; // Store option hmi_sub[hmi_state] = hmi_option; // Store selected option
hmi_option = hmi_state;
hmi_state = HMI_S_MENU; // Leave submenu
} }
if (event==HMI_E_ESCAPE) if (event==HMI_E_INCREMENT)
{ {
hmi_option = hmi_state;
hmi_state = HMI_S_MENU; // Leave submenu
}
if ((event==HMI_E_INCREMENT)||(event==HMI_E_RIGHT))
hmi_option = (hmi_option<HMI_NMODE-1)?hmi_option+1:HMI_NMODE-1; hmi_option = (hmi_option<HMI_NMODE-1)?hmi_option+1:HMI_NMODE-1;
if ((event==HMI_E_DECREMENT)||(event==HMI_E_LEFT)) }
if (event==HMI_E_DECREMENT)
{
hmi_option = (hmi_option>0)?hmi_option-1:0; hmi_option = (hmi_option>0)?hmi_option-1:0;
}
break; break;
case HMI_S_AGC: case HMI_S_AGC:
if (event==HMI_E_ENTER) if (event==HMI_E_ENTER)
{ {
// Set AGC dsp_setagc(hmi_option); // Commit AGC
hmi_sub[hmi_state] = hmi_option; // Store option hmi_sub[hmi_state] = hmi_option; // Store selected option
hmi_option = hmi_state;
hmi_state = HMI_S_MENU; // Leave submenu
} }
if (event==HMI_E_ESCAPE) if (event==HMI_E_INCREMENT)
{ {
hmi_option = hmi_state;
hmi_state = HMI_S_MENU; // Leave submenu
}
if ((event==HMI_E_INCREMENT)||(event==HMI_E_RIGHT))
hmi_option = (hmi_option<HMI_NAGC-1)?hmi_option+1:HMI_NAGC-1; hmi_option = (hmi_option<HMI_NAGC-1)?hmi_option+1:HMI_NAGC-1;
if ((event==HMI_E_DECREMENT)||(event==HMI_E_LEFT)) }
if (event==HMI_E_DECREMENT)
{
hmi_option = (hmi_option>0)?hmi_option-1:0; hmi_option = (hmi_option>0)?hmi_option-1:0;
}
break; break;
case HMI_S_PRE: case HMI_S_PRE:
if (event==HMI_E_ENTER) if (event==HMI_E_ENTER)
{ {
// Set Preamp // Set PRE
hmi_sub[hmi_state] = hmi_option; // Store option hmi_sub[hmi_state] = hmi_option; // Store selected option
hmi_option = hmi_state;
hmi_state = HMI_S_MENU; // Leave submenu
} }
if (event==HMI_E_INCREMENT)
{
hmi_option = (hmi_option<HMI_NPRE-1)?hmi_option+1:HMI_NPRE-1;
}
if (event==HMI_E_DECREMENT)
{
hmi_option = (hmi_option>0)?hmi_option-1:0;
}
break;
}
/* General actions for submenus */
if (event==HMI_E_ESCAPE) if (event==HMI_E_ESCAPE)
{ {
hmi_option = hmi_state; hmi_state = HMI_S_TUNE; // Leave submenus
hmi_state = HMI_S_MENU; // Leave submenu hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
} }
if ((event==HMI_E_INCREMENT)||(event==HMI_E_RIGHT)) if (event==HMI_E_RIGHT)
hmi_option = (hmi_option<HMI_NPRE-1)?hmi_option+1:HMI_NPRE-1; {
if ((event==HMI_E_DECREMENT)||(event==HMI_E_LEFT)) hmi_state = (hmi_state<HMI_NSTATES-1)?(hmi_state+1):1; // Change submenu
hmi_option = (hmi_option>0)?hmi_option-1:0; hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
break; }
if (event==HMI_E_LEFT)
{
hmi_state = (hmi_state>1)?(hmi_state-1):HMI_NSTATES-1; // Change submenu
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
} }
} }
/* /*
* GPIO IRQ callback routine * GPIO IRQ callback routine
* Sets the detected event and invokes the HMI state machine
*/ */
void hmi_callback(uint gpio, uint32_t events) void hmi_callback(uint gpio, uint32_t events)
{ {
@ -249,15 +265,15 @@ void hmi_callback(uint gpio, uint32_t events)
break; break;
case GP_PTT: // PTT case GP_PTT: // PTT
if (events&GPIO_IRQ_EDGE_FALL) if (events&GPIO_IRQ_EDGE_FALL)
tx_enabled = true; DSP_SETPTT(true);
else else
tx_enabled = false; DSP_SETPTT(false);
return; return;
default: default:
return; return;
} }
hmi_handler(evt); hmi_handler(evt); // Invoke state machine
} }
/* /*
@ -304,38 +320,37 @@ void hmi_init(void)
} }
/* /*
* Redraw the LCD, representing current state * Redraw the display, representing current state
* This function is called regularly from the main loop.
*/ */
void hmi_evaluate(void) void hmi_evaluate(void)
{ {
char s[20]; 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?'T':'R'),920);
lcd_writexy(0,0,s); lcd_writexy(0,0,s);
// Print bottom line of dsiplay, depending on state
switch (hmi_state) switch (hmi_state)
{ {
case HMI_S_MENU:
sprintf(s, "=> %s ", hmi_o_menu[hmi_option]);
lcd_writexy(0,1,s);
lcd_curxy(2, 1, false);
break;
case HMI_S_TUNE: case HMI_S_TUNE:
sprintf(s, "%s %s %s", hmi_o_menu[HMI_S_TUNE], hmi_o_pre[hmi_sub[HMI_S_PRE]], hmi_o_agc[hmi_sub[HMI_S_AGC]]); sprintf(s, " %s %s", hmi_o_agc[hmi_sub[HMI_S_AGC]], hmi_o_pre[hmi_sub[HMI_S_PRE]]);
lcd_writexy(0,1,s); lcd_writexy(0,1,s);
lcd_curxy(4+(hmi_option>4?6:hmi_option), 0, true); lcd_curxy(4+(hmi_option>4?6:hmi_option), 0, true);
break; break;
case HMI_S_MODE: case HMI_S_MODE:
sprintf(s, "=> Mode: %s ", hmi_o_mode[hmi_option]); sprintf(s, "Set Mode: %s ", hmi_o_mode[hmi_option]);
lcd_writexy(0,1,s); lcd_writexy(0,1,s);
lcd_curxy(9, 1, false); lcd_curxy(9, 1, false);
break; break;
case HMI_S_AGC: case HMI_S_AGC:
sprintf(s, "=> AGC: %s ", hmi_o_agc[hmi_option]); sprintf(s, "Set AGC: %s ", hmi_o_agc[hmi_option]);
lcd_writexy(0,1,s); lcd_writexy(0,1,s);
lcd_curxy(8, 1, false); lcd_curxy(8, 1, false);
break; break;
case HMI_S_PRE: case HMI_S_PRE:
sprintf(s, "=> Pre: %s ", hmi_o_pre[hmi_option]); sprintf(s, "Set Pre: %s ", hmi_o_pre[hmi_option]);
lcd_writexy(0,1,s); lcd_writexy(0,1,s);
lcd_curxy(8, 1, false); lcd_curxy(8, 1, false);
break; break;