Added frequency domain processing
pull/13/head
ArjanteMarvelde 2022-06-28 22:00:47 +02:00
rodzic f31ae4a79c
commit 4a76b70b22
17 zmienionych plików z 1641 dodań i 795 usunięć

Wyświetl plik

@ -16,28 +16,34 @@ set(PICO_SDK_PATH "C:/Users/Arjan/Documents/Pico/pico-sdk")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(uSDR C CXX ASM)
project(uSDR-FFT C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
# uSDR.c main loop and initialisation of the software
# lcd.c LCD driver stuff; pay attention, X different HW implementations exist
# si5351.c The drivers for setting output frequency and phase in the SI5351 chip
# dsp.c The signal processing stuff, either timedomain or frequency domain
# fix_fft.c The FFT transformations in fixed point format
# hmi.c All user interaction, controlling freq, modulation, levels, etc
# monitor.c A tty shell on a serial interface
# relay.c Switching for the band filter and attenuator relays
add_executable(uSDR-FFT uSDR.c lcd.c si5351.c dsp.c fix_fft.c hmi.c monitor.c relay.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")
pico_set_program_name(uSDR-FFT "uSDR-FFT")
pico_set_program_version(uSDR-FFT "0.1")
# Pull in our pico_stdlib which aggregates commonly used features
target_link_libraries(uSDR pico_stdlib)
target_link_libraries(uSDR-FFT pico_stdlib)
# Disable uart output, enable usb output
pico_enable_stdio_uart(uSDR 1)
pico_enable_stdio_usb(uSDR 0)
pico_enable_stdio_uart(uSDR-FFT 1)
pico_enable_stdio_usb(uSDR-FFT 0)
# Add any user requested libraries
target_link_libraries(uSDR
target_link_libraries(uSDR-FFT
pico_stdlib
pico_multicore
hardware_i2c
@ -49,5 +55,5 @@ target_link_libraries(uSDR
hardware_adc
)
pico_add_extra_outputs(uSDR)
pico_add_extra_outputs(uSDR-FFT)

BIN
CODEv3.zip 100644

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

768
dsp.c
Wyświetl plik

@ -4,42 +4,33 @@
* Created: Mar 2021
* Author: Arjan te Marvelde
*
* Signal processing of RX and TX branch, to be run on the second processor core.
* Each branch has a dedicated routine that must run on set times.
* The period is determined by reads from the inter-core fifo, by the dsp_loop() routine.
* This fifo is written from core0 from a 16us timer callback routine (i.e. 62.5kHz)
* Signal processing of RX and TX branch, to be run on the second processor core (CORE1).
*
* The RX branch:
* - Sample I and Q QSD channels, and shift into I and Q delay line (62.5 kHz per channel)
* - Low pass filter: Fc=4kHz
* - Quarter rate (15.625 kHz) to improve low freq behavior of Hilbert transform
* - Calculate 15 tap Hilbert transform on Q
* - Demodulate, taking proper delays into account
* - Push to Audio output DAC
*
* Always perform audio sampling (62.5kHz) and level detections, in case of VOX active
*
* The TX branch (if VOX or PTT):
* - Low pass filter: Fc=3kHz
* - Eight rate (7.8125 kHz) to improve low F behavior of Hilbert transform
* - Generate Q samples by doing a Hilbert transform
* - Push I and Q to QSE output DACs
* The actual DSP engine can be either FFT based in the frequency domain, or in the time domain.
* In dsp.h this can be selected compile-time, by defining the environment variable DSP_FFT.
*
*/
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "pico/platform.h"
#include "pico/time.h"
#include "pico/sem.h"
#include "hardware/structs/bus_ctrl.h"
#include "hardware/pwm.h"
#include "hardware/adc.h"
#include "hardware/irq.h"
#include "hardware/timer.h"
#include "hardware/clocks.h"
#define ADC0_IRQ_FIFO 22 // FIFO IRQ number
#define GP_PTT 15 // PTT pin 20 (GPIO 15)
#include "dsp.h"
#include "hmi.h"
#include "fix_fft.h"
#define GP_PTT 15 // PTT pin 20 (GPIO 15)
volatile bool tx_enabled; // TX branch active
volatile uint32_t dsp_overrun; // Overrun counter
/*
@ -48,22 +39,25 @@
* PWM frequency = Fsys / DAC_RANGE
* A value of 250 means 125MHz/250=500kHz
* ADC is 12 bit, so resolution is by definition 4096
* To eliminate undefined behavior we clip off the upper 4 sample bits.
*/
#define DAC_RANGE 256
#define DAC_BIAS (DAC_RANGE/2)
#define ADC_RANGE 4096
#define ADC_BIAS (ADC_RANGE/2)
volatile uint16_t dac_iq, dac_audio;
/*
* Callback timeout and inter-core FIFO commands.
* The timer value in usec determines frequency of TX and RX loops
* Exact time is obtained by passing the value as negative
* Here we use 16us (62.5 kHz == PWM freq/4)
* MODE is modulation/demodulation
* This setting steers the signal processing branch chosen
*/
#define DSP_US 16
#define DSP_TX 1
#define DSP_RX 2
volatile int dsp_mode;
void dsp_setmode(int mode)
{
dsp_mode = mode;
}
/*
@ -77,526 +71,404 @@
*/
#define AGC_REF 6
#define AGC_DECAY 8192
#define AGC_FAST 64
#define AGC_SLOW 4096
#define AGC_OFF 32766
volatile uint16_t agc_decay = AGC_OFF;
volatile uint16_t agc_attack = AGC_OFF;
#define AGC_SHORT 64
#define AGC_LONG 4096
#define AGC_DIS 32766
volatile uint16_t agc_decay = AGC_DIS;
volatile uint16_t agc_attack = AGC_DIS;
void dsp_setagc(int agc)
{
switch(agc)
{
case 1: //SLOW, for values see hmi.c
agc_attack = AGC_SLOW;
case AGC_SLOW:
agc_attack = AGC_LONG;
agc_decay = AGC_DECAY;
break;
case 2: //FAST
agc_attack = AGC_FAST;
case AGC_FAST:
agc_attack = AGC_SHORT;
agc_decay = AGC_DECAY;
break;
default: //OFF
agc_attack = AGC_OFF;
agc_decay = AGC_OFF;
default:
agc_attack = AGC_DIS;
agc_decay = AGC_DIS;
break;
}
}
/*
* MODE is modulation/demodulation
* This setting steers the signal processing branch chosen
*/
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;
}
/*
* 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.
* VOX LINGER is the number msec to wait before releasing TX mode
* The level of detection is derived from 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;
#define VOX_LINGER 500 // 500msec
volatile uint16_t vox_count = 0;
volatile uint16_t vox_level = 0;
volatile bool vox_active; // Is set when audio energy > vox level (and not OFF)
void dsp_setvox(int vox)
{
switch(vox)
{
case 1:
vox_level = VOX_LOW;
case VOX_HIGH:
vox_level = ADC_BIAS/2;
break;
case 2:
vox_level = VOX_MEDIUM;
case VOX_MEDIUM:
vox_level = ADC_BIAS/4;
break;
case 3:
vox_level = VOX_HIGH;
case VOX_LOW:
vox_level = ADC_BIAS/16;
break;
default:
vox_level = VOX_OFF;
vox_level = 0;
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
* Note: 8 bit precision, so divide sum by 256 (this could be improved when 32bit accumulator)
*/
int16_t lpf3_62[15] = { 3, 3, 5, 7, 9, 10, 11, 11, 11, 10, 9, 7, 5, 3, 3}; // Pass: 0-3000, Stop: 6000-31250
int16_t lpf3_31[15] = { -2, -3, -3, 1, 10, 21, 31, 35, 31, 21, 10, 1, -3, -3, -2}; // Pass: 0-3000, Stop: 6000-15625
int16_t lpf3_15[15] = { 3, 4, -3,-14,-15, 6, 38, 53, 38, 6,-15,-14, -3, 4, 3}; // Pass: 0-3000, Stop: 4500-7812
int16_t lpf7_62[15] = { -2, -1, 1, 7, 16, 26, 33, 36, 33, 26, 16, 7, 1, -1, -2}; // Pass: 0-7000, Stop: 10000-31250
int16_t lpf7_31[15] = { -1, 4, 9, 2,-12, -2, 40, 66, 40, -2,-12, 2, 9, 4, -1}; // Pass: 0-7000, Stop: 10000-15625
int16_t lpf15_62[15] = { -1, 3, 12, 6,-12, -4, 40, 69, 40, -4,-12, 6, 12, 3, -1}; // Pass: 0-15000, Stop: 20000-31250
volatile uint16_t dac_iq, dac_audio;
volatile uint32_t fifo_overrun, fifo_rx, fifo_tx, fifo_xx, fifo_incnt;
volatile bool tx_enabled, vox_active;
#define ABS(x) ( (x)<0 ? -(x) : (x) )
/*
* Some macro's
* See Alpha Max plus Beta Min algorithm for MAG (vector length)
* Z = alpha*max(i,q) + beta*min(i,q);
* alpha = 1/1, beta = 3/8 (error<6.8%)
* alpha = 15/16, beta = 15/32 (error<6.25%)
* Better algorithm:
* Z = max( max(i,q), alpha*max(i,q)+beta*min(i,q) )
* alpha = 29/32, beta = 61/128 (error<2.4%)
*/
#define ABS(x) ((x)<0?-(x):(x))
#define MAG(i,q) (ABS(i)>ABS(q) ? ABS(i)+((3*ABS(q))>>3) : ABS(q)+((3*ABS(i))>>3))
/*
* CORE1:
* ADC IRQ handler.
* Fills the results array in RR fashion, for 3 channels (~2usec per channel).
*/
volatile uint16_t adc_result[3];
volatile int adc_next; // Remember which ADC the result is from
uint32_t adc_count=0; // Debugging
void adcfifo_handler(void)
inline int16_t mag(int16_t i, int16_t q)
{
adc_result[adc_next] = adc_fifo_get(); // Get result from fifo
adc_next = adc_hw->cs; // Update adc_next with HW CS register
adc_next = (adc_next >> ADC_CS_AINSEL_LSB) & 3; // Shift and Mask: Only 0, 1 and 2 are valid numbers
adc_count++;
i = ABS(i); q = ABS(q);
if (i>q)
return (MAX(i,((29*i/32) + (61*q/128))));
else
return (MAX(q,((29*q/32) + (61*i/128))));
}
/* Note:
* A simple IIR single pole low pass filter could be made for anti-aliasing.
* Do this at full speed 16usec sampling,
* then for dsp_tim decimate with 1/8 (128usec)
* and for dsp_fft decimate with 1/4 (64usec)
* to obtain proper spectrum.
* y(n) = (1-a)*y(n-1) + a*x(n) = y(n-1) + a*(x(n) - y(n-1))
* in this a = T / (T + R*C)
* T is sample period (e.g. 16usec)
* RC the desired RC time: T*(1-a)/a.
* example: a=1/256 : RC = 255*16usec = 4.08msec ( 245Hz)
*/
volatile int32_t q_sample, i_sample, a_sample; // Latest sample values
/*** DSP engine ***/
#if DSP_FFT == 1
#define AGC_TOP 16383L
#include "dsp_fft.c"
#else
#define AGC_TOP 1500L
#include "dsp_tim.c"
#endif
/** CORE1: ADC IRQ handler **/
#define LSH 8 // Shift for higher level accuracy
#define ADC_INT 8 // Nr of samples for integration (max 10)
volatile int32_t adc_sample[3] = {0,0,0}; // Buffer for ADC samples
volatile int32_t adc_result[3] = {0,0,0}; // Buffer for ADC results
volatile uint32_t adc_level[3] = {10<<LSH,10<<LSH,10<<LSH}; // Levels for ADC channels
volatile int adccnt = 0;
void __not_in_flash_func(adcfifo_handler)(void)
{
if (++adccnt >= ADC_INT) // Nr of integration samples reached?
{
adc_irq_set_enabled(false); // Disable interrupts
adc_run(false); // Stop freerunning
}
adc_sample[0] = adc_sample[0] + adc_fifo_get() - ADC_BIAS; // Read first three samples from FIFO
adc_sample[1] = adc_sample[1] + adc_fifo_get() - ADC_BIAS;
adc_sample[2] = adc_sample[2] + adc_fifo_get() - ADC_BIAS;
adccnt++;
}
/** CORE1: Timer callback routine **/
/*
* CORE1:
* Execute RX branch signal processing, max time to spend is <16us, i.e. rate is 62.5 kHz
* No ADC sample interleaving, read both I and Q channels.
* The delay is only 2us per conversion, which causes less distortion than interpolation of samples.
* This runs every TIM_US , i.e. 16usec
* First the decimation filter is applied on latest ADC results
* The filtered samples are set aside, so a new ADC cycle can be started.
* The ADC cycle takes 8usec to complete (3x ADC0..2 + 1x stray ADC0 conversion).
* The timing is critical, it assumes that the ADC is finished.
* Once every 4 TIM_US intervals signal preprocessing is done, and DSP may be invoked.
* Do not put any other stuff in this callback routine.
*/
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 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)
semaphore_t dsp_sem;
repeating_timer_t dsp_timer;
volatile int cnt = 4;
volatile int32_t rx_agc = 1, tx_agc = 1; // Factor as AGC
bool __not_in_flash_func(dsp_callback)(repeating_timer_t *t)
{
int16_t q_sample, i_sample, a_sample;
int32_t q_accu, i_accu;
int16_t qh;
uint16_t i;
int16_t k;
/*** SAMPLING ***/
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
int32_t temp;
/*
* Remove DC and store new sample
* IIR filter: dc = a*sample + (1-a)*dc where a = 1/128
* Amplitude of samples should fit inside [-2048, 2047]
* Here the rate is 15625Hz
*/
q_sample = (q_sample&0x0fff) - ADC_BIAS; // Clip to 12 bits and subtract mid-range
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/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)
// Get DC bias corrected samples
adc_result[0] = adc_sample[0];
adc_result[1] = adc_sample[1];
adc_result[2] = adc_sample[2];
// Re-start ADCs
while (!adc_fifo_is_empty()) adc_fifo_get(); // Empty leftovers from fifo
adc_sample[0] = 0;
adc_sample[1] = 0;
adc_sample[2] = 0;
adc_set_round_robin(0x01+0x02+0x04); // Sequence ADC 0-1-2 (GP 26, 27, 28) free running
adc_select_input(0); // Start with ADC0
adccnt=0; // Check for ADC FIFO interrupt overruns
adc_run(true); // Start ADC
adc_irq_set_enabled(true); // Enable ADC interrupts
// Calculate and save level, left shifted by LSH
// a=1/1024 : RC = 1023*64usec = 65msec (15Hz)
adc_level[0] = (1023*adc_level[0] + (ABS(adc_result[0])<<LSH))/1024;
adc_level[1] = (1023*adc_level[1] + (ABS(adc_result[1])<<LSH))/1024;
adc_level[2] = (1023*adc_level[2] + (ABS(adc_result[2])<<LSH))/1024;
// Crude AGC mechanism
if (!tx_enabled)
{
q_sample = q_sample * (1<<agc_gain);
i_sample = i_sample * (1<<agc_gain);
}
else if (agc_gain < 0)
{
q_sample = q_sample / (1<<(-agc_gain));
i_sample = i_sample / (1<<(-agc_gain));
temp = (MAX(adc_level[1], adc_level[0]))>>LSH; // Max I or Q
rx_agc = (temp==0) ? AGC_TOP : AGC_TOP/temp; // calculate required AGC factor
}
/*
* Shift-in I and Q raw samples
*/
for (i=0; i<14; i++)
#if DSP_FFT == 1
if (tx_enabled)
{
q_s_raw[i] = q_s_raw[i+1]; // Q raw samples shift register
i_s_raw[i] = i_s_raw[i+1]; // I raw samples shift register
A_buf[dsp_active][dsp_tick] = (int16_t)(tx_agc*adc_result[2]);
pwm_set_gpio_level(21, I_buf[dsp_active][dsp_tick] + DAC_BIAS); // Output I to DAC
pwm_set_gpio_level(20, Q_buf[dsp_active][dsp_tick] + DAC_BIAS); // Output Q to DAC
}
q_s_raw[14] = q_sample; // Store in shift registers
i_s_raw[14] = i_sample;
/*
* Low pass filter + decimation
*/
rx_cnt = (rx_cnt+1)&3; // Calculate only every fourth sample
if (rx_cnt>0) return (true); // So net sample time is 64us or 15.625 kHz
for (i=0; i<14; i++) // Shift decimated samples
else
{
q_s[i] = q_s[i+1];
i_s[i] = i_s[i+1];
}
q_accu = 0; // Initialize accumulators
i_accu = 0;
for (i=0; i<15; i++) // Low pass FIR filter
{
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 = q_accu/256;
i_accu = i_accu/256;
q_s[14] = q_accu;
i_s[14] = i_accu;
/*** DEMODULATION ***/
switch (dsp_mode)
{
case 0: //USB
/*
* 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 / 4096L;
a_sample = i_s[7] - qh;
break;
case 1: //LSB
/*
* 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 / 4096L;
a_sample = i_s[7] + qh;
break;
case 2: //AM
/*
* AM demodulate: sqrt(sqr(i)+sqr(q))
* Approximated with MAG(i,q)
*/
a_sample = MAG(i_s[14], q_s[14]);
break;
default:
break;
I_buf[dsp_active][dsp_tick] = (int16_t)(rx_agc*adc_result[1]);
Q_buf[dsp_active][dsp_tick] = (int16_t)(rx_agc*adc_result[0]);
pwm_set_gpio_level(22, A_buf[dsp_active][dsp_tick] + DAC_BIAS); // Output A to DAC
}
/*** AUDIO GENERATION ***/
/*
* AGC, peak detector
* Sample speed is 15625 per second
*/
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;}
if (i&0x000c) {k+=2; i>>=2;}
if (i&0x0002) {k+=1;}
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
// When sample buffer is full, move pointer and signal DSP loop
if (++dsp_tick >= BUFSIZE) // Increment tick and check range
{
agc_gain--; // Decrease gain
agc_accu -= agc_attack; // Reset integrator
} else if (agc_accu < -(agc_decay)) // Decay time, gain correction in case of low level
{
agc_gain++; // Increase gain
agc_accu += agc_decay; // Reset integrator
dsp_tick = 0; // Reset counter
if (++dsp_active > 2) dsp_active = 0; // Rotate offset
dsp_overrun++; // Increment overrun counter
sem_release(&dsp_sem); // Signal DSP loop semaphore
}
/*
* Scale and clip output,
* Send to audio DAC output
*/
a_sample += DAC_BIAS; // Add bias level
if (a_sample > DAC_RANGE) // Clip to DAC range
a_sample = DAC_RANGE;
else if (a_sample<0)
a_sample = 0;
pwm_set_chan_level(dac_audio, PWM_CHAN_A, a_sample);
#else
if (tx_enabled)
{
a_sample = tx_agc * adc_result[2]; // Store A for DSP use
pwm_set_gpio_level(21, i_sample); // Output I to DAC
pwm_set_gpio_level(20, q_sample); // Output Q to DAC
}
else
{
pwm_set_gpio_level(22, a_sample); // Output Q to DAC
q_sample = rx_agc * adc_result[0]; // Store Q for DSP use
i_sample = rx_agc * adc_result[1]; // Store I for DSP use
}
dsp_overrun++; // Increment overrun counter
sem_release(&dsp_sem); // Signal DSP loop semaphore
#endif
return true;
}
/*
* CORE1:
* 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) // Only when VOX is enabled
{
if (a_level > vox_level)
{
vox_count = VOX_LINGER; // While audio present, reset linger timer
return(true); // and keep TX active
}
if (vox_count>0)
{
vox_count--; // No audio; decrement linger timer
return(true); // but keep TX active
}
}
return(false); // All other cases: no TX
}
bool tx(void)
{
int32_t a_accu, q_accu;
int16_t qh;
int i;
uint16_t i_dac, q_dac;
/*** 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; // So effective sample rate will be 15625Hz
a_accu = 0; // Initialize accumulator
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
for (i=0; i<14; i++) // Shift decimated samples
a_s[i] = a_s[i+1];
a_s[14] = a_accu / 256; // Store rescaled accumulator
/*** MODULATION ***/
switch (dsp_mode)
{
case 0: // USB
/*
* qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
*/
q_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 = -(q_accu / 4096L); // USB: sign is negative
break;
case 1: // LSB
/*
* qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
*/
q_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 = q_accu / 4096L; // LSB: sign is positive
break;
case 2: // AM
/*
* I and Q values are identical
*/
qh = a_s[7];
break;
default:
break;
}
/*
* Write I and Q to QSE DACs, phase is 7 samples back.
* Need to multiply AC with DAC_RANGE/ADC_RANGE (appr 1/8)
* Any case: clip to range
*/
a_accu = DAC_BIAS - (qh/8);
if (a_accu<0)
q_dac = 0;
else if (a_accu>(DAC_RANGE-1))
q_dac = DAC_RANGE-1;
else
q_dac = a_accu;
a_accu = DAC_BIAS + (a_s[7]/8);
if (a_accu<0)
i_dac = 0;
else if (a_accu>(DAC_RANGE-1))
i_dac = DAC_RANGE-1;
else
i_dac = a_accu;
// pwm_set_both_levels(dac_iq, q_dac, i_dac); // Set both channels of the IQ slice simultaneously
// pwm_set_chan_level(dac_iq, PWM_CHAN_A, q_dac);
// pwm_set_chan_level(dac_iq, PWM_CHAN_B, i_dac);
pwm_set_gpio_level(21, i_dac);
pwm_set_gpio_level(20, q_dac);
return true;
}
/*
* CORE1:
* Timing loop, triggered through inter-core fifo
*/
void dsp_loop()
/** CORE1: DSP loop, triggered through repeating timer/semaphore **/
void __not_in_flash_func(dsp_loop)()
{
uint32_t cmd;
uint16_t slice_num;
alarm_pool_t *ap;
tx_enabled = false;
fifo_overrun = 0;
fifo_rx = 0;
fifo_tx = 0;
fifo_xx = 0;
fifo_incnt++;
vox_active = false;
/* Initialize DACs, default mode is free running, A and B pins are output */
/*
* Initialize DACs,
* default mode is free running,
* A and B pins are output
*/
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: 125MHz
pwm_set_clkdiv_int_frac (dac_iq, 1, 0); // clock divide by 1: full system clock
pwm_set_wrap(dac_iq, DAC_RANGE-1); // Set cycle length; nr of counts until wrap, i.e. 125/DAC_RANGE MHz
pwm_set_enabled(dac_iq, true); // Set the PWM running
gpio_set_function(22, GPIO_FUNC_PWM); // GP22 is PWM for Audio DAC (Slice 3, Channel A)
dac_audio = pwm_gpio_to_slice_num(22); // Find PWM slice for GP22
pwm_set_clkdiv_int_frac (dac_audio, 1, 0); // clock divide by 1: 125MHz
pwm_set_clkdiv_int_frac (dac_audio, 1, 0); // clock divide by 1: full system clock
pwm_set_wrap(dac_audio, DAC_RANGE-1); // Set cycle length; nr of counts until wrap, i.e. 125/DAC_RANGE MHz
pwm_set_enabled(dac_audio, true); // Set the PWM running
/* Initialize ADCs */
/*
* Initialize ADCs, use in round robin mode (3 channels)
* samples are stored in array through IRQ callback
*/
adc_init(); // Initialize ADC to known state
adc_set_clkdiv(0); // Fastest clock (500 kSps)
adc_set_clkdiv(0.0); // Fastest clock (500 kSps)
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;
adc_set_round_robin(0x01+0x02+0x04); // Sequence ADC 0-1-2 (GP 26, 27, 28) free running
adc_fifo_setup(true,false,1,false,false); // IRQ for every result (fifo threshold = 1)
irq_set_exclusive_handler(ADC0_IRQ_FIFO, adcfifo_handler);
adc_irq_set_enabled(true);
irq_set_enabled(ADC0_IRQ_FIFO, true);
adc_select_input(0); // Start with ADC0
adc_fifo_setup(true,false,3,false,false); // IRQ result, fifo threshold = 1, so IRQ after each ADC0..2
irq_set_exclusive_handler(ADC_IRQ_FIFO, adcfifo_handler); // Install ISR at ADC_IRQ_FIFO vector (22)
irq_set_priority (ADC_IRQ_FIFO, PICO_HIGHEST_IRQ_PRIORITY); // Prevent race condition with timer
irq_set_enabled(ADC_IRQ_FIFO, true); // Enable interrupt vector in NVIC
adc_irq_set_enabled(true); // Enable the ADC FIFO interrupt
adc_run(true);
adc_level[0] = ADC_BIAS/2;
adc_level[1] = ADC_BIAS/2;
adc_level[2] = ADC_BIAS/2;
// Consider using alarm_pool_add_repeating_timer_us() for a core1 associated timer
// First create an alarm pool on core1: alarm_pool_create(HWalarm, Ntimers)
// For the core1 alarm pool don't use default HWalarm (usually 3) but e.g. 1
// Timer callback signals semaphore, while loop blocks on getting it
// Use alarm_pool_add_repeating_timer_us() for a core1 associated timer
// First create an alarm pool on core1:
// alarm_pool_t *alarm_pool_create( uint hardware_alarm_num,
// uint max_timers);
// For the core1 alarm pool don't use the default alarm_num (usually 3) but e.g. 1
// Timer callback signals semaphore, while loop blocks on getting it.
// Initialize repeating timer on core1:
// bool alarm_pool_add_repeating_timer_us( alarm_pool_t *pool,
// int64_t delay_us,
// repeating_timer_callback_t callback,
// void *user_data,
// repeating_timer_t *out);
sem_init(&dsp_sem, 0, 1);
ap = alarm_pool_create(1, 4);
alarm_pool_add_repeating_timer_us( ap, -TIM_US, dsp_callback, NULL, &dsp_timer);
dsp_overrun = 0;
while(1)
{
cmd = multicore_fifo_pop_blocking(); // Wait for fifo output
sem_acquire_blocking(&dsp_sem); // Wait until timer callback releases sem
dsp_overrun--; // Decrement overrun counter
tx_enabled = vox() || ptt_active; // Sample audio and check level
if (tx_enabled)
// Use adc_level[2] for VOX
vox_active = false; // Normally false
if (vox_level != 0) // Only when VOX is enabled
{
if (vox_level != VOX_OFF) // Only when vox is enabled
gpio_put(GP_PTT, false); // drive PTT low (active)
tx();
if ((adc_level[2]>>LSH) > vox_level) // AND level > limit
{
vox_count = S_RATE * VOX_LINGER / 1000; // While audio present, reset linger timer
vox_active = true; // and keep TX active
}
else if (--vox_count>0) // else decrement linger counter
vox_active = true; // and keep TX active until 0
}
if (tx_enabled) // Use previous setting
{
gpio_put(GP_PTT, false); // Drive PTT low (active)
tx(); // Do TX signal processing
}
else
{
if (vox_level != VOX_OFF) // Only when vox is enabled
gpio_put(GP_PTT, true); // drive PTT high (inactive)
rx();
gpio_put(GP_PTT, true); // Drive PTT high (inactive)
rx(); // Do RX signal processing
}
if (multicore_fifo_rvalid())
fifo_overrun++; // Check for missed events
tx_enabled = vox_active || ptt_active; // Check RX or TX
#if DSP_FFT == 1
dsp_tickx = dsp_tick;
#endif
}
}
/*
* CORE0:
* Timer callback, triggers core1 through inter-core fifo.
* Either TX or RX, but could do both when testing in loopback on I+Q channels.
*/
struct repeating_timer dsp_timer;
bool dsp_callback(struct repeating_timer *t)
{
if (tx_enabled)
multicore_fifo_push_blocking(DSP_TX); // Send TX to core 1 through fifo
else
multicore_fifo_push_blocking(DSP_RX); // Send RX to core 1 through fifo
fifo_incnt++;
return true;
}
/*
* CORE0:
* Initialize dsp context and spawn CORE1 process
*
* Some CORE1 code parts should not run from Flash, but be loaded in SRAM at boot time
* See platform.h for function qualifier macro's
* for example:
* void __not_in_flash_func(funcname)(int arg1, float arg2)
* {
* }
*
* Need to set BUS_PRIORITY of Core 1 to high
* #include bus_ctrl.h
* bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_PROC1_BITS; // Set Core 1 prio high
*/
/** CORE0: Initialize dsp context and spawn CORE1 process **/
void dsp_init()
{
multicore_launch_core1(dsp_loop); // Start processing on core1
add_repeating_timer_us(-DSP_US, dsp_callback, NULL, &dsp_timer);
bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_PROC1_BITS; // Set Core 1 prio on bus to high
multicore_launch_core1(dsp_loop); // Start processing on Core 1
}
/* DMA EXAMPLE, should convert to chained DMA to reload after 3 words
// Init GPIO for analogue use: hi-Z, no pulls, disable digital input buffer.
adc_gpio_init(26 + CAPTURE_CHANNEL);
adc_init();
adc_select_input(CAPTURE_CHANNEL);
adc_fifo_setup(
true, // Write each completed conversion to the sample FIFO
true, // Enable DMA data request (DREQ)
1, // DREQ (and IRQ) asserted when at least 1 sample present
false, // We won't see the ERR bit because of 8 bit reads; disable.
true // Shift each sample to 8 bits when pushing to FIFO
);
// Divisor of 0 -> full speed. Free-running capture with the divider is
// equivalent to pressing the ADC_CS_START_ONCE button once per `div + 1`
// cycles (div not necessarily an integer). Each conversion takes 96
// cycles, so in general you want a divider of 0 (hold down the button
// continuously) or > 95 (take samples less frequently than 96 cycle
// intervals). This is all timed by the 48 MHz ADC clock.
adc_set_clkdiv(0);
printf("Arming DMA\n");
sleep_ms(1000);
// Set up the DMA to start transferring data as soon as it appears in FIFO
uint dma_chan = dma_claim_unused_channel(true);
dma_channel_config cfg = dma_channel_get_default_config(dma_chan);
// Reading from constant address, writing to incrementing byte addresses
channel_config_set_transfer_data_size(&cfg, DMA_SIZE_8);
channel_config_set_read_increment(&cfg, false);
channel_config_set_write_increment(&cfg, true);
// Pace transfers based on availability of ADC samples
channel_config_set_dreq(&cfg, DREQ_ADC);
dma_channel_configure(dma_chan, &cfg,
capture_buf, // dst
&adc_hw->fifo, // src
CAPTURE_DEPTH, // transfer count
true // start immediately
);
printf("Starting capture\n");
adc_run(true);
// Once DMA finishes, stop any new conversions from starting, and clean up
// the FIFO in case the ADC was still mid-conversion.
dma_channel_wait_for_finish_blocking(dma_chan);
printf("Capture finished\n");
adc_run(false);
adc_fifo_drain();
// Print samples to stdout so you can display them in pyplot, excel, matlab
for (int i = 0; i < CAPTURE_DEPTH; ++i) {
printf("%-3d, ", capture_buf[i]);
if (i % 10 == 9)
printf("\n");
}
*/

51
dsp.h
Wyświetl plik

@ -1,5 +1,5 @@
#ifndef __DSP_H__
#define __DSP_H__
#ifndef __DSP_FFT_H__
#define __DSP_FFT_H__
/*
* dsp.h
*
@ -9,21 +9,50 @@
* See dsp.c for more information
*/
#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "hardware/pwm.h"
#define DSP_FFT 1 // 1 for FFT, 0 for time domain processing
void dsp_setagc(int agc);
void dsp_setmode(int mode);
/*
* Callback timeout is TIM_US, value in usec
* The carrier offset is !=0 only in FFT case.
*/
#if DSP_FFT == 1
#define TIM_US 64
#define S_RATE 15625 // 1e6/TIM_US
#define FC_OFFSET 3906 // in bin FFT_SIZE/4 ==> S_RATE/4
#else
#define TIM_US 64
#define S_RATE 15625 // 1e6/TIM_US
#define FC_OFFSET 0 // Must be 0 for time-domain DSP
#endif
/** DSP module interface **/
extern volatile bool tx_enabled; // Determined by (vox_active || ptt_active)
#define VOX_OFF 0
#define VOX_LOW 1
#define VOX_MEDIUM 2
#define VOX_HIGH 3
void dsp_setvox(int vox);
extern volatile bool tx_enabled;
#define DSP_SETPTT(on) tx_enabled = (on)
#define MODE_USB 0
#define MODE_LSB 1
#define MODE_AM 2
#define MODE_CW 3
void dsp_setmode(int mode);
#define AGC_NONE 0
#define AGC_SLOW 1
#define AGC_FAST 2
void dsp_setagc(int agc);
void dsp_init();
#endif

288
dsp_fft.c 100644
Wyświetl plik

@ -0,0 +1,288 @@
/*
* dsp_fft.c
* ==>TO BE INCLUDED IN dsp.c
*
* Created: May 2022
* Author: Arjan te Marvelde
*
* Signal processing of RX and TX branch, to be run on the second processor core (CORE1).
* A branch has a dedicated routine that must run on set times.
* In this case it runs when half FFT_SIZE of samples is ready to be processed.
*
*
* The pace for sampling is set by a timer at 64usec (15.625 kHz)
* The associated timer callback routine:
* - handles data transfer to/from physical interfaces
* - starts a new ADC conversion sequence
* - maintains dsp_tick counter
* - when dsp_tick == FFT_SIZE/2 (one buffer), the dsp-loop is triggered.
*
* The ADC functions in round-robin and fifo mode, triggering IRQ after 3 conversions (ADC[0..2])
* The ADC FIFO IRQ handler reads the 3 samples from the fifo after stopping the ADC
*
* Buffer structure, built from half FFT_SIZE buffers.
* The I, Q and A external interfaces communicate each through 3x buffers.
* One buffer is being filled or emptied, depending on data direction.
* The other two are swapped with the FFT signal processing buffers.
* Since we use complex FFT, the algorithm uses 4x buffers.
*
* I, Q and A buffers used as queues. RX case looks like:
*
* +--+--+--+
* i --> | | | |
* +--+--+--+
* \ \ \ +--+--+
* --------> | | | +--+--+--+
* +--+--+ FFT-DSP-iFFT --> | | | | --> a
* --------> | | | +--+--+--+
* / / / +--+--+
* +--+--+--+
* q --> | | | |
* +--+--+--+
*
* RX, when triggered by timer callback:
* - The oldest real FFT buffer is moved to the output queue (check this)
* - The oldest two I and Q buffers are copied into the FFT buffers
* - FFT is executed
* - Signal processing is done
* - iFFT is executed
*
* The bin step is the sampling frequency divided by the FFT_SIZE.
* So for S_RATE=15625 and FFT_SIZE=1024 this step is 15625/1024=15.259 Hz
* The Carrier offset (Fc) is at about half the Nyquist frequency: bin 256 or 3906 Hz
*
*/
/*
* FFT buffer allocation
* Buffer size is FFT_SIZE/2 (see fix_fft.h).
* In case FFT_SIZE of 1024, a buffer is 1kB
* RX: 3 buffers for I samples, 3 buffers for Q samples, 3 buffers for Audio
* DSP: 4 buffers for FFT, complex samples and these have to be consecutive!
* TX: re-use RX buffers in reverse order
* Total of 13kByte RAM is required.
* Samples are 16 bit signed integer, but align buffers on 32bit boundaries
* dsp_tick points into I, Q and A buffers, so wrap once per two FFTs
* When tick==FFT_SIZE/2: do buffer copy
*/
#define BUFSIZE FFT_SIZE/2
int16_t I_buf[3][BUFSIZE] __attribute__((aligned(4))); // I sample queue, 3x buffer of FFT_SIZE/2
int16_t Q_buf[3][BUFSIZE] __attribute__((aligned(4))); // Q sample queue, 3x buffer of FFT_SIZE/2
int16_t A_buf[3][BUFSIZE] __attribute__((aligned(4))); // A sample queue, 3x buffer of FFT_SIZE/2
int16_t XI_buf[FFT_SIZE] __attribute__((aligned(4))); // Re FFT buffer, 1x buffer of FFT_SIZE
int16_t XQ_buf[FFT_SIZE] __attribute__((aligned(4))); // Im FFT buffer, 1x buffer of FFT_SIZE
// Sample buffer indexes, updated by timer callback
volatile int dsp_active = 0; // I, Q, A active buffer number (0..2)
volatile uint32_t dsp_tick = 0; // Index in active buffer
volatile uint32_t dsp_tickx = 0; // Load indicator DSP loop
// Spectrum bins for a frequency
#define BIN(f) (int)(((f)*FFT_SIZE+S_RATE/2)/S_RATE)
#define BIN_FC 256
#define BIN_100 7
#define BIN_300 20
#define BIN_900 59
#define BIN_3000 197
/*
* This applies a bandpass filter to XI and XQ buffers
* lowbin and highbin edges must be between 3 and FFT_SIZE/2 - 3
* Edge is a 7 bin raised cosine flank, i.e. 100Hz wide
* Coefficients are: 0, 0.067, 0.25, 0.5, 0.75, 0.933, 1
* where the edge bin is in the center of this flank
* Note: maybe make slope less steep, e.g. 9 or 11 bins
*/
inline void dsp_bandpass(int lowbin, int highbin)
{
int i;
if ((lowbin<3)||(highbin>(FFT_SIZE/2-3))||(highbin-lowbin<6)) return;
XI_buf[0] = 0; XQ_buf[0] = 0;
for (i=1; i<lowbin-2; i++)
{
XI_buf[i] = 0; XI_buf[FFT_SIZE-i] = 0;
XQ_buf[i] = 0; XQ_buf[FFT_SIZE-i] = 0;
}
for (i=highbin+3; i<FFT_SIZE-highbin-2; i++)
{
XI_buf[i] = 0;
XQ_buf[i] = 0;
}
// Note: There is not much difference between using or discarding Q bins
i=lowbin-2;
XI_buf[i] = XI_buf[i]*0.067; XQ_buf[i] = XQ_buf[i]*0.067; i++;
XI_buf[i] = XI_buf[i]*0.250; XQ_buf[i] = XQ_buf[i]*0.250; i++;
XI_buf[i] = XI_buf[i]*0.500; XQ_buf[i] = XQ_buf[i]*0.500; i++;
XI_buf[i] = XI_buf[i]*0.750; XQ_buf[i] = XQ_buf[i]*0.750; i++;
XI_buf[i] = XI_buf[i]*0.933; XQ_buf[i] = XQ_buf[i]*0.933;
i=highbin-2;
XI_buf[i] = XI_buf[i]*0.933; XQ_buf[i] = XQ_buf[i]*0.933; i++;
XI_buf[i] = XI_buf[i]*0.250; XQ_buf[i] = XQ_buf[i]*0.250; i++;
XI_buf[i] = XI_buf[i]*0.500; XQ_buf[i] = XQ_buf[i]*0.500; i++;
XI_buf[i] = XI_buf[i]*0.750; XQ_buf[i] = XQ_buf[i]*0.750; i++;
XI_buf[i] = XI_buf[i]*0.067; XQ_buf[i] = XQ_buf[i]*0.067;
i=FFT_SIZE-highbin-2;
XI_buf[i] = XI_buf[i]*0.067; XQ_buf[i] = XQ_buf[i]*0.067; i++;
XI_buf[i] = XI_buf[i]*0.250; XQ_buf[i] = XQ_buf[i]*0.250; i++;
XI_buf[i] = XI_buf[i]*0.500; XQ_buf[i] = XQ_buf[i]*0.500; i++;
XI_buf[i] = XI_buf[i]*0.750; XQ_buf[i] = XQ_buf[i]*0.750; i++;
XI_buf[i] = XI_buf[i]*0.933; XQ_buf[i] = XQ_buf[i]*0.933;
i=FFT_SIZE-lowbin-2;
XI_buf[i] = XI_buf[i]*0.933; XQ_buf[i] = XQ_buf[i]*0.933; i++;
XI_buf[i] = XI_buf[i]*0.250; XQ_buf[i] = XQ_buf[i]*0.250; i++;
XI_buf[i] = XI_buf[i]*0.500; XQ_buf[i] = XQ_buf[i]*0.500; i++;
XI_buf[i] = XI_buf[i]*0.750; XQ_buf[i] = XQ_buf[i]*0.750; i++;
XI_buf[i] = XI_buf[i]*0.067; XQ_buf[i] = XQ_buf[i]*0.067;
}
/** CORE1: RX branch **/
/*
* Execute RX branch signal processing
*/
volatile int scale0;
volatile int scale1;
bool __not_in_flash_func(rx)(void)
{
int b;
int i;
int16_t *ip, *qp, *ap, *xip, *xqp;
int16_t peak;
b = dsp_active; // Point to Active buffer
/*** Copy saved I/Q buffers to FFT buffer ***/
if (++b > 2) b = 0; // Point to Old Saved buffer
ip = &I_buf[b][0]; xip = &XI_buf[0];
qp = &Q_buf[b][0]; xqp = &XQ_buf[0];
for (i=0; i<BUFSIZE; i++)
{
*xip++ = *ip++;
*xqp++ = *qp++;
}
if (++b > 2) b = 0; // Point to New Saved buffer
ip = &I_buf[b][0]; xip = &XI_buf[BUFSIZE];
qp = &Q_buf[b][0]; xqp = &XQ_buf[BUFSIZE];
for (i=0; i<BUFSIZE; i++)
{
*xip++ = *ip++;
*xqp++ = *qp++;
}
/*** Execute FFT ***/
scale0 = fix_fft(&XI_buf[0], &XQ_buf[0], false);
/*** Shift and filter sidebands ***/
XI_buf[0] = 0;
XQ_buf[0] = 0;
switch (dsp_mode)
{
case MODE_USB:
// Shift Fc to 0Hz
for (i=1; i<BIN_3000; i++)
{
XI_buf[i] = XI_buf[i+BIN_FC];
XI_buf[FFT_SIZE-i] = XI_buf[FFT_SIZE-BIN_FC-i];
XQ_buf[i] = XQ_buf[i+BIN_FC];
XQ_buf[FFT_SIZE-i] = XQ_buf[FFT_SIZE-BIN_FC-i];
}
// Bandpass DSB (2x USB)
dsp_bandpass(BIN_100, BIN_3000);
break;
case MODE_LSB:
// Shift Fc to 0Hz, i.e. swap buffers
for (i=1; i<BIN_3000; i++)
{
XI_buf[BUFSIZE-i] = XI_buf[BIN_FC-i];
XI_buf[i] = XI_buf[FFT_SIZE-BIN_FC+i];
XI_buf[FFT_SIZE-i] = XI_buf[BUFSIZE-i];
XQ_buf[BUFSIZE-i] = XQ_buf[BIN_FC-i];
XQ_buf[i] = XQ_buf[FFT_SIZE-BIN_FC+i];
XQ_buf[FFT_SIZE-i] = XQ_buf[BUFSIZE-i];
}
// Bandpass DSB (2x LSB)
dsp_bandpass(BIN_100, BIN_3000);
break;
case MODE_AM:
// Shift the rest to the right place
for (i=1; i<BIN_3000; i++)
{
XI_buf[FFT_SIZE-i] = XI_buf[BIN_FC-i];
XI_buf[i] = XI_buf[BIN_FC+i];
XQ_buf[FFT_SIZE-i] = XQ_buf[BIN_FC-i];
XQ_buf[i] = XQ_buf[BIN_FC+i];
}
// Bandpass DSB (LSB + USB)
dsp_bandpass(BIN_100, BIN_3000);
break;
case MODE_CW:
// Shift carrier from Fc to 900Hz
for (i=-BIN_900+1; i<BIN_900-1; i++)
{
XI_buf[i+BIN_900] = XI_buf[BIN_FC+i];
XI_buf[FFT_SIZE-i-BIN_900] = XI_buf[FFT_SIZE-BIN_FC-i];
XQ_buf[i+BIN_900] = XQ_buf[BIN_FC+i];
XQ_buf[FFT_SIZE-i-BIN_900] = XQ_buf[FFT_SIZE-BIN_FC-i];
}
// Bandpass CW
dsp_bandpass(BIN_900-BIN_300, BIN_900+BIN_300);
break;
}
/*** Execute inverse FFT ***/
scale1 = fix_fft(&XI_buf[0], &XQ_buf[0], true);
/*** Export FFT buffer to A ***/
b = dsp_active; // Assume active buffer not changed, i.e. no overruns
if (++b > 2) b = 0; // Point to oldest
ap = &A_buf[b][0]; xip = &XI_buf[0];
for (i=0; i<BUFSIZE; i++)
{
*ap++ = *xip++; // Copy oldest results
}
/*** Scale down into DAC_RANGE! ***/
peak = 128;
for (i=0; i<BUFSIZE; i++)
{
A_buf[b][i] /= peak;
}
return true;
}
/** CORE1: TX branch **/
/*
* Execute TX branch signal processing
*/
bool __not_in_flash_func(tx)(void)
{
// Export FFT buffers to I/Q
// Import A buffers
// FFT
// Filter
// iFFT
return true;
}

238
dsp_tim.c 100644
Wyświetl plik

@ -0,0 +1,238 @@
/*
* dsp_tim.c
* ==>TO BE INCLUDED IN dsp.c
*
* Created: May 2022
* Author: Arjan te Marvelde
*
* Signal processing of RX and TX branch, to be run on the second processor core.
* Each branch has a dedicated routine that must run on set times.
* The period is determined by reads from the inter-core fifo, by the dsp_loop() routine.
* This fifo is written from core0 from a 16us timer callback routine (i.e. 62.5kHz)
*
* The RX branch:
* - Sample I and Q QSD channels, and shift into I and Q delay line (62.5 kHz per channel)
* - Low pass filter: Fc=4kHz
* - Quarter rate (15.625 kHz) to improve low freq behavior of Hilbert transform
* - Calculate 15 tap Hilbert transform on Q
* - Demodulate, taking proper delays into account
* - Push to Audio output DAC
*
* Always perform audio sampling (62.5kHz) and level detections, in case of VOX active
*
* The TX branch (if VOX or PTT):
* - Low pass filter: Fc=3kHz
* - Eight rate (7.8125 kHz) to improve low F behavior of Hilbert transform
* - Generate Q samples by doing a Hilbert transform
* - Push I and Q to QSE output DACs
*
*/
/*
* Low pass FIR 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
* Note: 8 bit precision, so divide sum by 256 (this could be improved when 32bit accumulator)
*/
int16_t lpf3_62[15] = { 3, 3, 5, 7, 9, 10, 11, 11, 11, 10, 9, 7, 5, 3, 3}; // Pass: 0-3000, Stop: 6000-31250
int16_t lpf3_31[15] = { -2, -3, -3, 1, 10, 21, 31, 35, 31, 21, 10, 1, -3, -3, -2}; // Pass: 0-3000, Stop: 6000-15625
int16_t lpf3_15[15] = { 3, 4, -3,-14,-15, 6, 38, 53, 38, 6,-15,-14, -3, 4, 3}; // Pass: 0-3000, Stop: 4500-7812
int16_t lpf7_62[15] = { -2, -1, 1, 7, 16, 26, 33, 36, 33, 26, 16, 7, 1, -1, -2}; // Pass: 0-7000, Stop: 10000-31250
int16_t lpf7_31[15] = { -1, 4, 9, 2,-12, -2, 40, 66, 40, -2,-12, 2, 9, 4, -1}; // Pass: 0-7000, Stop: 10000-15625
int16_t lpf15_62[15] = { -1, 3, 12, 6,-12, -4, 40, 69, 40, -4,-12, 6, 12, 3, -1}; // Pass: 0-15000, Stop: 20000-31250
/** CORE1: RX Branch **/
/*
* Execute RX branch signal processing
*/
volatile int32_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 gain (left-shift value)
volatile int16_t agc_accu=0; // Log peak level integrator
volatile int32_t i_s[15], q_s[15]; // Filtered I/Q samples
volatile int32_t i2, q2; // Squared samples
bool __not_in_flash_func(rx)(void)
{
int32_t q_accu, i_accu;
int32_t qh;
uint16_t i;
int16_t k;
/*** SAMPLING ***/
/*
* Shift-in I and Q raw samples
*/
for (i=0; i<14; i++)
{
q_s_raw[i] = q_s_raw[i+1];
i_s_raw[i] = i_s_raw[i+1];
}
q_s_raw[14] = q_sample; // Store decimated samples in shift registers
i_s_raw[14] = i_sample;
/*
* Low pass FIR filter
*/
q_accu = 0; // Initialize accumulators
i_accu = 0;
for (i=0; i<15; i++) // Low pass FIR filter
{
q_accu += (int32_t)q_s_raw[i]*lpf3_15[i]; // Fc=3kHz, at 15625 Hz sampling
i_accu += (int32_t)i_s_raw[i]*lpf3_15[i]; // Fc=3kHz, at 15625 Hz sampling
}
q_accu = q_accu/256;
i_accu = i_accu/256;
for (i=0; i<14; i++) // Shift filtered samples
{
q_s[i] = q_s[i+1];
i_s[i] = i_s[i+1];
}
q_s[14] = q_accu;
i_s[14] = i_accu;
/*** DEMODULATION ***/
switch (dsp_mode)
{
case MODE_USB:
/*
* 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 / 4096L;
a_sample = i_s[7] - qh;
break;
case MODE_LSB:
/*
* 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 / 4096L;
a_sample = i_s[7] + qh;
break;
case MODE_AM:
/*
* AM demodulate: sqrt(sqr(i)+sqr(q))
* Approximated with mag(i,q)
*/
a_sample = mag(i_s[7], q_s[7]);
break;
default:
break;
}
/*** AUDIO GENERATION ***/
/*
* Scale and clip output,
* Send to audio DAC output
*/
a_sample = (a_sample/32) + DAC_BIAS; // -15dB and add bias level
if (a_sample > DAC_RANGE) // Clip to DAC range
a_sample = DAC_RANGE;
else if (a_sample<0)
a_sample = 0;
return true;
}
/** CORE1: TX branch **/
/*
* Execute TX branch signal processing,
* max time to spend is <16us, i.e. rate is 62.5 kHz
* The audio sampling has already been done in vox()
*/
volatile int16_t a_s_raw[15]; // Raw samples, minus DC bias
volatile int16_t a_s[15]; // Filtered and decimated samplesvolatile int16_t a_dc; // DC level
bool __not_in_flash_func(tx)(void)
{
int32_t a_accu, q_accu;
int16_t qh;
int i;
uint16_t i_dac, q_dac;
/*** RAW Audio ***/
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;
/*** Low pass filter ***/
a_accu = 0; // Initialize accumulator
for (i=0; i<15; i++) // Low pass FIR filter, using raw samples
a_accu += (int32_t)a_s_raw[i]*lpf3_15[i]; // Fc=3kHz, at 15.625 kHz sampling
for (i=0; i<14; i++) // Shift decimated samples
a_s[i] = a_s[i+1];
a_s[14] = a_accu / 256; // Store rescaled accumulator
/*** MODULATION ***/
switch (dsp_mode)
{
case 0: // USB
/*
* qh is Classic Hilbert transform 15 taps, 12 bits
* (see Iowa Hills calculator)
*/
q_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 = -(q_accu / 4096L); // USB: sign is negative
break;
case 1: // LSB
/*
* qh is Classic Hilbert transform 15 taps, 12 bits
* (see Iowa Hills calculator)
*/
q_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 = q_accu / 4096L; // LSB: sign is positive
break;
case 2: // AM
/*
* I and Q values are identical
*/
qh = a_s[7];
break;
default:
break;
}
/*
* Write I and Q to QSE DACs, phase is 7 samples back.
* Need to multiply AC with DAC_RANGE/ADC_RANGE (appr 1/8)
* Any case: clip to range
*/
a_accu = DAC_BIAS - (qh/8);
if (a_accu<0)
q_sample = 0;
else if (a_accu>(DAC_RANGE-1))
q_sample = DAC_RANGE-1;
else
q_sample = a_accu;
a_accu = DAC_BIAS + (a_s[7]/8);
if (a_accu<0)
i_sample = 0;
else if (a_accu>(DAC_RANGE-1))
i_sample = DAC_RANGE-1;
else
i_sample = a_accu;
return true;
}

373
fix_fft.c 100644
Wyświetl plik

@ -0,0 +1,373 @@
/* fix_fft.c - Fixed-point in-place DIT Fast Fourier Transform */
/*
All data are fixed-point uint16_t integers, in which -32768
to +32768 represent -1.0 to +1.0 respectively. Integer
arithmetic is used for speed, instead of the more natural
floating-point.
For the forward FFT (time -> freq), fixed scaling is
performed to prevent arithmetic overflow, and to map a 0dB
sine/cosine wave (i.e. amplitude = 32767) to two -6dB freq
coefficients. The return value is always 0.
For the inverse FFT (freq -> time), fixed scaling cannot be
done, as two 0dB coefficients would sum to a peak amplitude
of 64K, overflowing the 32k range of the fixed-point integers.
Thus, the fix_fft() routine performs variable scaling, and
returns a value which is the number of bits LEFT by which
the output must be shifted to get the actual amplitude
(i.e. if fix_fft() returns 3, each value of fr[] and fi[]
must be multiplied by 8 (2**3) for proper scaling.
Clearly, this cannot be done within fixed-point uint16_t
integers. In practice, if the result is to be used as a
filter, the scale_shift can usually be ignored, as the
result will be approximately correctly normalized as is.
Written by: Tom Roberts 11/8/89
Made portable: Malcolm Slaney 12/15/94 malcolm@interval.com
Enhanced: Dimitrios P. Bouras 14 Jun 2006 dbouras@ieee.org
*/
/*
This implementation uses a lookup table for bit reverse sorting,
which adds 2kbyte to the memory footprint.
The iFFT range detector has been optimized.
The bitshifting of signed integers is undefined, so these have been
replaced by divisions. The compiler will optimize it.
The size is fixed at 1024.
*/
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "pico/platform.h"
#include "fix_fft.h"
/** Fixed point Sine lookup table, [-1, 1] == [-32766, 32767] **/
int16_t Sine[3*FFT_SIZE/4] =
{
0, 201, 402, 603, 804, 1005, 1206, 1406,
1607, 1808, 2009, 2209, 2410, 2610, 2811, 3011,
3211, 3411, 3611, 3811, 4011, 4210, 4409, 4608,
4807, 5006, 5205, 5403, 5601, 5799, 5997, 6195,
6392, 6589, 6786, 6982, 7179, 7375, 7571, 7766,
7961, 8156, 8351, 8545, 8739, 8932, 9126, 9319,
9511, 9703, 9895, 10087, 10278, 10469, 10659, 10849,
11038, 11227, 11416, 11604, 11792, 11980, 12166, 12353,
12539, 12724, 12909, 13094, 13278, 13462, 13645, 13827,
14009, 14191, 14372, 14552, 14732, 14911, 15090, 15268,
15446, 15623, 15799, 15975, 16150, 16325, 16499, 16672,
16845, 17017, 17189, 17360, 17530, 17699, 17868, 18036,
18204, 18371, 18537, 18702, 18867, 19031, 19194, 19357,
19519, 19680, 19840, 20000, 20159, 20317, 20474, 20631,
20787, 20942, 21096, 21249, 21402, 21554, 21705, 21855,
22004, 22153, 22301, 22448, 22594, 22739, 22883, 23027,
23169, 23311, 23452, 23592, 23731, 23869, 24006, 24143,
24278, 24413, 24546, 24679, 24811, 24942, 25072, 25201,
25329, 25456, 25582, 25707, 25831, 25954, 26077, 26198,
26318, 26437, 26556, 26673, 26789, 26905, 27019, 27132,
27244, 27355, 27466, 27575, 27683, 27790, 27896, 28001,
28105, 28208, 28309, 28410, 28510, 28608, 28706, 28802,
28897, 28992, 29085, 29177, 29268, 29358, 29446, 29534,
29621, 29706, 29790, 29873, 29955, 30036, 30116, 30195,
30272, 30349, 30424, 30498, 30571, 30643, 30713, 30783,
30851, 30918, 30984, 31049, 31113, 31175, 31236, 31297,
31356, 31413, 31470, 31525, 31580, 31633, 31684, 31735,
31785, 31833, 31880, 31926, 31970, 32014, 32056, 32097,
32137, 32176, 32213, 32249, 32284, 32318, 32350, 32382,
32412, 32441, 32468, 32495, 32520, 32544, 32567, 32588,
32609, 32628, 32646, 32662, 32678, 32692, 32705, 32717,
32727, 32736, 32744, 32751, 32757, 32761, 32764, 32766,
32767, 32766, 32764, 32761, 32757, 32751, 32744, 32736,
32727, 32717, 32705, 32692, 32678, 32662, 32646, 32628,
32609, 32588, 32567, 32544, 32520, 32495, 32468, 32441,
32412, 32382, 32350, 32318, 32284, 32249, 32213, 32176,
32137, 32097, 32056, 32014, 31970, 31926, 31880, 31833,
31785, 31735, 31684, 31633, 31580, 31525, 31470, 31413,
31356, 31297, 31236, 31175, 31113, 31049, 30984, 30918,
30851, 30783, 30713, 30643, 30571, 30498, 30424, 30349,
30272, 30195, 30116, 30036, 29955, 29873, 29790, 29706,
29621, 29534, 29446, 29358, 29268, 29177, 29085, 28992,
28897, 28802, 28706, 28608, 28510, 28410, 28309, 28208,
28105, 28001, 27896, 27790, 27683, 27575, 27466, 27355,
27244, 27132, 27019, 26905, 26789, 26673, 26556, 26437,
26318, 26198, 26077, 25954, 25831, 25707, 25582, 25456,
25329, 25201, 25072, 24942, 24811, 24679, 24546, 24413,
24278, 24143, 24006, 23869, 23731, 23592, 23452, 23311,
23169, 23027, 22883, 22739, 22594, 22448, 22301, 22153,
22004, 21855, 21705, 21554, 21402, 21249, 21096, 20942,
20787, 20631, 20474, 20317, 20159, 20000, 19840, 19680,
19519, 19357, 19194, 19031, 18867, 18702, 18537, 18371,
18204, 18036, 17868, 17699, 17530, 17360, 17189, 17017,
16845, 16672, 16499, 16325, 16150, 15975, 15799, 15623,
15446, 15268, 15090, 14911, 14732, 14552, 14372, 14191,
14009, 13827, 13645, 13462, 13278, 13094, 12909, 12724,
12539, 12353, 12166, 11980, 11792, 11604, 11416, 11227,
11038, 10849, 10659, 10469, 10278, 10087, 9895, 9703,
9511, 9319, 9126, 8932, 8739, 8545, 8351, 8156,
7961, 7766, 7571, 7375, 7179, 6982, 6786, 6589,
6392, 6195, 5997, 5799, 5601, 5403, 5205, 5006,
4807, 4608, 4409, 4210, 4011, 3811, 3611, 3411,
3211, 3011, 2811, 2610, 2410, 2209, 2009, 1808,
1607, 1406, 1206, 1005, 804, 603, 402, 201,
0, -201, -402, -603, -804, -1005, -1206, -1406,
-1607, -1808, -2009, -2209, -2410, -2610, -2811, -3011,
-3211, -3411, -3611, -3811, -4011, -4210, -4409, -4608,
-4807, -5006, -5205, -5403, -5601, -5799, -5997, -6195,
-6392, -6589, -6786, -6982, -7179, -7375, -7571, -7766,
-7961, -8156, -8351, -8545, -8739, -8932, -9126, -9319,
-9511, -9703, -9895, -10087, -10278, -10469, -10659, -10849,
-11038, -11227, -11416, -11604, -11792, -11980, -12166, -12353,
-12539, -12724, -12909, -13094, -13278, -13462, -13645, -13827,
-14009, -14191, -14372, -14552, -14732, -14911, -15090, -15268,
-15446, -15623, -15799, -15975, -16150, -16325, -16499, -16672,
-16845, -17017, -17189, -17360, -17530, -17699, -17868, -18036,
-18204, -18371, -18537, -18702, -18867, -19031, -19194, -19357,
-19519, -19680, -19840, -20000, -20159, -20317, -20474, -20631,
-20787, -20942, -21096, -21249, -21402, -21554, -21705, -21855,
-22004, -22153, -22301, -22448, -22594, -22739, -22883, -23027,
-23169, -23311, -23452, -23592, -23731, -23869, -24006, -24143,
-24278, -24413, -24546, -24679, -24811, -24942, -25072, -25201,
-25329, -25456, -25582, -25707, -25831, -25954, -26077, -26198,
-26318, -26437, -26556, -26673, -26789, -26905, -27019, -27132,
-27244, -27355, -27466, -27575, -27683, -27790, -27896, -28001,
-28105, -28208, -28309, -28410, -28510, -28608, -28706, -28802,
-28897, -28992, -29085, -29177, -29268, -29358, -29446, -29534,
-29621, -29706, -29790, -29873, -29955, -30036, -30116, -30195,
-30272, -30349, -30424, -30498, -30571, -30643, -30713, -30783,
-30851, -30918, -30984, -31049, -31113, -31175, -31236, -31297,
-31356, -31413, -31470, -31525, -31580, -31633, -31684, -31735,
-31785, -31833, -31880, -31926, -31970, -32014, -32056, -32097,
-32137, -32176, -32213, -32249, -32284, -32318, -32350, -32382,
-32412, -32441, -32468, -32495, -32520, -32544, -32567, -32588,
-32609, -32628, -32646, -32662, -32678, -32692, -32705, -32717,
-32727, -32736, -32744, -32751, -32757, -32761, -32764, -32766
};
static int16_t bitrev[FFT_SIZE] =
{
0x000, 0x200, 0x100, 0x300, 0x080, 0x280, 0x180, 0x380, 0x040, 0x240, 0x140, 0x340, 0x0c0, 0x2c0, 0x1c0, 0x3c0,
0x020, 0x220, 0x120, 0x320, 0x0a0, 0x2a0, 0x1a0, 0x3a0, 0x060, 0x260, 0x160, 0x360, 0x0e0, 0x2e0, 0x1e0, 0x3e0,
0x010, 0x210, 0x110, 0x310, 0x090, 0x290, 0x190, 0x390, 0x050, 0x250, 0x150, 0x350, 0x0d0, 0x2d0, 0x1d0, 0x3d0,
0x030, 0x230, 0x130, 0x330, 0x0b0, 0x2b0, 0x1b0, 0x3b0, 0x070, 0x270, 0x170, 0x370, 0x0f0, 0x2f0, 0x1f0, 0x3f0,
0x008, 0x208, 0x108, 0x308, 0x088, 0x288, 0x188, 0x388, 0x048, 0x248, 0x148, 0x348, 0x0c8, 0x2c8, 0x1c8, 0x3c8,
0x028, 0x228, 0x128, 0x328, 0x0a8, 0x2a8, 0x1a8, 0x3a8, 0x068, 0x268, 0x168, 0x368, 0x0e8, 0x2e8, 0x1e8, 0x3e8,
0x018, 0x218, 0x118, 0x318, 0x098, 0x298, 0x198, 0x398, 0x058, 0x258, 0x158, 0x358, 0x0d8, 0x2d8, 0x1d8, 0x3d8,
0x038, 0x238, 0x138, 0x338, 0x0b8, 0x2b8, 0x1b8, 0x3b8, 0x078, 0x278, 0x178, 0x378, 0x0f8, 0x2f8, 0x1f8, 0x3f8,
0x004, 0x204, 0x104, 0x304, 0x084, 0x284, 0x184, 0x384, 0x044, 0x244, 0x144, 0x344, 0x0c4, 0x2c4, 0x1c4, 0x3c4,
0x024, 0x224, 0x124, 0x324, 0x0a4, 0x2a4, 0x1a4, 0x3a4, 0x064, 0x264, 0x164, 0x364, 0x0e4, 0x2e4, 0x1e4, 0x3e4,
0x014, 0x214, 0x114, 0x314, 0x094, 0x294, 0x194, 0x394, 0x054, 0x254, 0x154, 0x354, 0x0d4, 0x2d4, 0x1d4, 0x3d4,
0x034, 0x234, 0x134, 0x334, 0x0b4, 0x2b4, 0x1b4, 0x3b4, 0x074, 0x274, 0x174, 0x374, 0x0f4, 0x2f4, 0x1f4, 0x3f4,
0x00c, 0x20c, 0x10c, 0x30c, 0x08c, 0x28c, 0x18c, 0x38c, 0x04c, 0x24c, 0x14c, 0x34c, 0x0cc, 0x2cc, 0x1cc, 0x3cc,
0x02c, 0x22c, 0x12c, 0x32c, 0x0ac, 0x2ac, 0x1ac, 0x3ac, 0x06c, 0x26c, 0x16c, 0x36c, 0x0ec, 0x2ec, 0x1ec, 0x3ec,
0x01c, 0x21c, 0x11c, 0x31c, 0x09c, 0x29c, 0x19c, 0x39c, 0x05c, 0x25c, 0x15c, 0x35c, 0x0dc, 0x2dc, 0x1dc, 0x3dc,
0x03c, 0x23c, 0x13c, 0x33c, 0x0bc, 0x2bc, 0x1bc, 0x3bc, 0x07c, 0x27c, 0x17c, 0x37c, 0x0fc, 0x2fc, 0x1fc, 0x3fc,
0x002, 0x202, 0x102, 0x302, 0x082, 0x282, 0x182, 0x382, 0x042, 0x242, 0x142, 0x342, 0x0c2, 0x2c2, 0x1c2, 0x3c2,
0x022, 0x222, 0x122, 0x322, 0x0a2, 0x2a2, 0x1a2, 0x3a2, 0x062, 0x262, 0x162, 0x362, 0x0e2, 0x2e2, 0x1e2, 0x3e2,
0x012, 0x212, 0x112, 0x312, 0x092, 0x292, 0x192, 0x392, 0x052, 0x252, 0x152, 0x352, 0x0d2, 0x2d2, 0x1d2, 0x3d2,
0x032, 0x232, 0x132, 0x332, 0x0b2, 0x2b2, 0x1b2, 0x3b2, 0x072, 0x272, 0x172, 0x372, 0x0f2, 0x2f2, 0x1f2, 0x3f2,
0x00a, 0x20a, 0x10a, 0x30a, 0x08a, 0x28a, 0x18a, 0x38a, 0x04a, 0x24a, 0x14a, 0x34a, 0x0ca, 0x2ca, 0x1ca, 0x3ca,
0x02a, 0x22a, 0x12a, 0x32a, 0x0aa, 0x2aa, 0x1aa, 0x3aa, 0x06a, 0x26a, 0x16a, 0x36a, 0x0ea, 0x2ea, 0x1ea, 0x3ea,
0x01a, 0x21a, 0x11a, 0x31a, 0x09a, 0x29a, 0x19a, 0x39a, 0x05a, 0x25a, 0x15a, 0x35a, 0x0da, 0x2da, 0x1da, 0x3da,
0x03a, 0x23a, 0x13a, 0x33a, 0x0ba, 0x2ba, 0x1ba, 0x3ba, 0x07a, 0x27a, 0x17a, 0x37a, 0x0fa, 0x2fa, 0x1fa, 0x3fa,
0x006, 0x206, 0x106, 0x306, 0x086, 0x286, 0x186, 0x386, 0x046, 0x246, 0x146, 0x346, 0x0c6, 0x2c6, 0x1c6, 0x3c6,
0x026, 0x226, 0x126, 0x326, 0x0a6, 0x2a6, 0x1a6, 0x3a6, 0x066, 0x266, 0x166, 0x366, 0x0e6, 0x2e6, 0x1e6, 0x3e6,
0x016, 0x216, 0x116, 0x316, 0x096, 0x296, 0x196, 0x396, 0x056, 0x256, 0x156, 0x356, 0x0d6, 0x2d6, 0x1d6, 0x3d6,
0x036, 0x236, 0x136, 0x336, 0x0b6, 0x2b6, 0x1b6, 0x3b6, 0x076, 0x276, 0x176, 0x376, 0x0f6, 0x2f6, 0x1f6, 0x3f6,
0x00e, 0x20e, 0x10e, 0x30e, 0x08e, 0x28e, 0x18e, 0x38e, 0x04e, 0x24e, 0x14e, 0x34e, 0x0ce, 0x2ce, 0x1ce, 0x3ce,
0x02e, 0x22e, 0x12e, 0x32e, 0x0ae, 0x2ae, 0x1ae, 0x3ae, 0x06e, 0x26e, 0x16e, 0x36e, 0x0ee, 0x2ee, 0x1ee, 0x3ee,
0x01e, 0x21e, 0x11e, 0x31e, 0x09e, 0x29e, 0x19e, 0x39e, 0x05e, 0x25e, 0x15e, 0x35e, 0x0de, 0x2de, 0x1de, 0x3de,
0x03e, 0x23e, 0x13e, 0x33e, 0x0be, 0x2be, 0x1be, 0x3be, 0x07e, 0x27e, 0x17e, 0x37e, 0x0fe, 0x2fe, 0x1fe, 0x3fe,
0x001, 0x201, 0x101, 0x301, 0x081, 0x281, 0x181, 0x381, 0x041, 0x241, 0x141, 0x341, 0x0c1, 0x2c1, 0x1c1, 0x3c1,
0x021, 0x221, 0x121, 0x321, 0x0a1, 0x2a1, 0x1a1, 0x3a1, 0x061, 0x261, 0x161, 0x361, 0x0e1, 0x2e1, 0x1e1, 0x3e1,
0x011, 0x211, 0x111, 0x311, 0x091, 0x291, 0x191, 0x391, 0x051, 0x251, 0x151, 0x351, 0x0d1, 0x2d1, 0x1d1, 0x3d1,
0x031, 0x231, 0x131, 0x331, 0x0b1, 0x2b1, 0x1b1, 0x3b1, 0x071, 0x271, 0x171, 0x371, 0x0f1, 0x2f1, 0x1f1, 0x3f1,
0x009, 0x209, 0x109, 0x309, 0x089, 0x289, 0x189, 0x389, 0x049, 0x249, 0x149, 0x349, 0x0c9, 0x2c9, 0x1c9, 0x3c9,
0x029, 0x229, 0x129, 0x329, 0x0a9, 0x2a9, 0x1a9, 0x3a9, 0x069, 0x269, 0x169, 0x369, 0x0e9, 0x2e9, 0x1e9, 0x3e9,
0x019, 0x219, 0x119, 0x319, 0x099, 0x299, 0x199, 0x399, 0x059, 0x259, 0x159, 0x359, 0x0d9, 0x2d9, 0x1d9, 0x3d9,
0x039, 0x239, 0x139, 0x339, 0x0b9, 0x2b9, 0x1b9, 0x3b9, 0x079, 0x279, 0x179, 0x379, 0x0f9, 0x2f9, 0x1f9, 0x3f9,
0x005, 0x205, 0x105, 0x305, 0x085, 0x285, 0x185, 0x385, 0x045, 0x245, 0x145, 0x345, 0x0c5, 0x2c5, 0x1c5, 0x3c5,
0x025, 0x225, 0x125, 0x325, 0x0a5, 0x2a5, 0x1a5, 0x3a5, 0x065, 0x265, 0x165, 0x365, 0x0e5, 0x2e5, 0x1e5, 0x3e5,
0x015, 0x215, 0x115, 0x315, 0x095, 0x295, 0x195, 0x395, 0x055, 0x255, 0x155, 0x355, 0x0d5, 0x2d5, 0x1d5, 0x3d5,
0x035, 0x235, 0x135, 0x335, 0x0b5, 0x2b5, 0x1b5, 0x3b5, 0x075, 0x275, 0x175, 0x375, 0x0f5, 0x2f5, 0x1f5, 0x3f5,
0x00d, 0x20d, 0x10d, 0x30d, 0x08d, 0x28d, 0x18d, 0x38d, 0x04d, 0x24d, 0x14d, 0x34d, 0x0cd, 0x2cd, 0x1cd, 0x3cd,
0x02d, 0x22d, 0x12d, 0x32d, 0x0ad, 0x2ad, 0x1ad, 0x3ad, 0x06d, 0x26d, 0x16d, 0x36d, 0x0ed, 0x2ed, 0x1ed, 0x3ed,
0x01d, 0x21d, 0x11d, 0x31d, 0x09d, 0x29d, 0x19d, 0x39d, 0x05d, 0x25d, 0x15d, 0x35d, 0x0dd, 0x2dd, 0x1dd, 0x3dd,
0x03d, 0x23d, 0x13d, 0x33d, 0x0bd, 0x2bd, 0x1bd, 0x3bd, 0x07d, 0x27d, 0x17d, 0x37d, 0x0fd, 0x2fd, 0x1fd, 0x3fd,
0x003, 0x203, 0x103, 0x303, 0x083, 0x283, 0x183, 0x383, 0x043, 0x243, 0x143, 0x343, 0x0c3, 0x2c3, 0x1c3, 0x3c3,
0x023, 0x223, 0x123, 0x323, 0x0a3, 0x2a3, 0x1a3, 0x3a3, 0x063, 0x263, 0x163, 0x363, 0x0e3, 0x2e3, 0x1e3, 0x3e3,
0x013, 0x213, 0x113, 0x313, 0x093, 0x293, 0x193, 0x393, 0x053, 0x253, 0x153, 0x353, 0x0d3, 0x2d3, 0x1d3, 0x3d3,
0x033, 0x233, 0x133, 0x333, 0x0b3, 0x2b3, 0x1b3, 0x3b3, 0x073, 0x273, 0x173, 0x373, 0x0f3, 0x2f3, 0x1f3, 0x3f3,
0x00b, 0x20b, 0x10b, 0x30b, 0x08b, 0x28b, 0x18b, 0x38b, 0x04b, 0x24b, 0x14b, 0x34b, 0x0cb, 0x2cb, 0x1cb, 0x3cb,
0x02b, 0x22b, 0x12b, 0x32b, 0x0ab, 0x2ab, 0x1ab, 0x3ab, 0x06b, 0x26b, 0x16b, 0x36b, 0x0eb, 0x2eb, 0x1eb, 0x3eb,
0x01b, 0x21b, 0x11b, 0x31b, 0x09b, 0x29b, 0x19b, 0x39b, 0x05b, 0x25b, 0x15b, 0x35b, 0x0db, 0x2db, 0x1db, 0x3db,
0x03b, 0x23b, 0x13b, 0x33b, 0x0bb, 0x2bb, 0x1bb, 0x3bb, 0x07b, 0x27b, 0x17b, 0x37b, 0x0fb, 0x2fb, 0x1fb, 0x3fb,
0x007, 0x207, 0x107, 0x307, 0x087, 0x287, 0x187, 0x387, 0x047, 0x247, 0x147, 0x347, 0x0c7, 0x2c7, 0x1c7, 0x3c7,
0x027, 0x227, 0x127, 0x327, 0x0a7, 0x2a7, 0x1a7, 0x3a7, 0x067, 0x267, 0x167, 0x367, 0x0e7, 0x2e7, 0x1e7, 0x3e7,
0x017, 0x217, 0x117, 0x317, 0x097, 0x297, 0x197, 0x397, 0x057, 0x257, 0x157, 0x357, 0x0d7, 0x2d7, 0x1d7, 0x3d7,
0x037, 0x237, 0x137, 0x337, 0x0b7, 0x2b7, 0x1b7, 0x3b7, 0x077, 0x277, 0x177, 0x377, 0x0f7, 0x2f7, 0x1f7, 0x3f7,
0x00f, 0x20f, 0x10f, 0x30f, 0x08f, 0x28f, 0x18f, 0x38f, 0x04f, 0x24f, 0x14f, 0x34f, 0x0cf, 0x2cf, 0x1cf, 0x3cf,
0x02f, 0x22f, 0x12f, 0x32f, 0x0af, 0x2af, 0x1af, 0x3af, 0x06f, 0x26f, 0x16f, 0x36f, 0x0ef, 0x2ef, 0x1ef, 0x3ef,
0x01f, 0x21f, 0x11f, 0x31f, 0x09f, 0x29f, 0x19f, 0x39f, 0x05f, 0x25f, 0x15f, 0x35f, 0x0df, 0x2df, 0x1df, 0x3df,
0x03f, 0x23f, 0x13f, 0x33f, 0x0bf, 0x2bf, 0x1bf, 0x3bf, 0x07f, 0x27f, 0x17f, 0x37f, 0x0ff, 0x2ff, 0x1ff, 0x3ff
};
/** FIX_MPY() **/
/*
* Assume Q(0,15) notation, 1 sign, 0 int, 15 frac bits
*/
int16_t __not_in_flash_func(FIX_MPY)(int16_t a, int16_t b) // Fixed-point mpy and scaling
{
int32_t c;
c = (int32_t)a * (int32_t)b; // multiply
c = c + 0x4000; // and round up
c = c >>15; // Shift right fractional bits
return((int16_t)c); // Return scaled product
}
/** FIX_FFT() **/
/*
* fr[] i samples [1024]
* fi[] q samples [1024]
* inverse true: iFFT
* Note: i-FFT could also be calculated by exchanging the arrays for FFT (fxtbook.pdf 21.7)
*/
int __not_in_flash_func(fix_fft)(int16_t *fr, int16_t *fi, bool inverse)
{
uint16_t i, j, m, k, step, scale;
bool shift;
int16_t qr, qi, tr, ti, wr, wi;
int16_t *bp;
/* Decimation in time: re-order samples */
bp=&bitrev[0];
for (i=0; i<FFT_SIZE; i++)
{
if (*bp > i)
{
tr = fr[i]; fr[i] = fr[*bp]; fr[*bp] = tr;
ti = fi[i]; fi[i] = fi[*bp]; fi[*bp] = ti;
}
bp++;
}
scale = 0;
step = 1; // Counting up: 1, 2, 4, 8, ...
/* FFT Stages */
for (k=FFT_ORDER; k>0; k--) // #cycles: FFT_ORDER
{
/* Scaling
* Variable scaling, depends on current data
* --> it seems quite CPU intensive to go through complete array
* FFT_ORDER times, could this be optimized?
* If always scaling:
* --> the main loop has log_2(FFT_SIZE) cycles,
* resulting in an overall factor of 1/FFT_SIZE,
* distributed over cycles to maximize accuracy.
*/
shift = false; // No shift, unless...
for (i=0; i<FFT_SIZE; ++i) // Range test all samples
{
if ((fr[i] > 0x3fff) || (fr[i] < -0x4000) || (fi[i] > 0x3fff) || (fi[i] < -0x4000))
{
shift = true;
scale++;
break; // Bail out at first detect
}
}
/* Inner loops resolving the butterflies for each stage*/
for (m=0; m<step; m++) // #cycles: step
{
// Determine wiggle factors
j = m << (k-1); // 0 <= j < FFT_SIZE/2
wr = Sine[j+FFT_SIZE/4]; // Real part, i.e. Cosine
wi = inverse ? Sine[j] : -Sine[j]; // Imaginary part
if (shift) { wr = wr/2; wi = wi/2; } // Scale factors by 1/2
for (i=m; i<FFT_SIZE; i+=(step*2)) // #cycles: FFT_SIZE/step
{
j = i + step; // re-assign j !
tr = FIX_MPY(wr,fr[j]) - FIX_MPY(wi,fi[j]); // Complex multiply
ti = FIX_MPY(wr,fi[j]) + FIX_MPY(wi,fr[j]);
if (shift)
{ qr = fr[i]/2; qi = fi[i]/2; }
else
{ qr = fr[i]; qi = fi[i]; }
fr[i] = qr + tr;
fi[i] = qi + ti;
fr[j] = qr - tr;
fi[j] = qi - ti;
} // #total: FFT_ORDER * step * FFT_SIZE/step
}
step = step<<1;
}
return scale;
}
#ifdef BLAH
// int16_t contains signed fixed point representation Q(1,14)
// 1 sign bit, 1 int bit and 14 frac bits
// precomputed value K represents 0.5
#define Q 14
#define K (1 << (Q - 1))
// a + b
int16_t q_add(int16_t a, int16_t b)
{
int32_t tmp;
tmp = (int32_t)a + (int32_t)b;
if (tmp > 0x7FFF) // Clip result
tmp = 0x7FFF;
else if (tmp < -0x8000)
tmp = -0x8000;
return (int16_t)tmp;
}
// a - b
int16_t q_sub(int16_t a, int16_t b)
{
return a - b;
}
// a * b
int16_t q_mul(int16_t a, int16_t b)
{
int32_t tmp;
tmp = (int32_t)a * (int32_t)b;
tmp += K; // Rounding; mid values are rounded up
tmp = tmp >> Q; // Correct by dividing by base
if (tmp > 0x7FFF) // Clip result
tmp = 0x7FFF;
else if (tmp < -0x8000)
tmp = -0x8000;
return (int16_t)tmp;
}
// a / b
int16_t q_div(int16_t a, int16_t b)
{
int32_t tmp;
tmp = (int32_t)a << Q; // Pre multiply with base
if ((tmp >= 0 && b >= 0) || (tmp < 0 && b < 0)) // Rounding; mid values are rounded up
tmp += (b >> 2);
else // or down...
tmp -= (b >> 2);
return (int16_t)(tmp / b);
}
#endif

17
fix_fft.h 100644
Wyświetl plik

@ -0,0 +1,17 @@
#ifndef __FIX_FFT_H__
#define __FIX_FFT_H__
/*
* fix_fft.h
*
* Created: Apr 2022
* Author: Arjan te Marvelde
*
* See fix_fft.c for more information
*/
#define FFT_SIZE 1024 // Use this for buffer allocations
#define FFT_ORDER 10 // FFT_SIZE = 1 << FFT_ORDER
int fix_fft(int16_t *fr, int16_t *fi, bool inverse);
#endif

140
hmi.c
Wyświetl plik

@ -100,25 +100,30 @@
#define HMI_NEVENTS 9
/* Sub menu option string sets */
#define HMI_NTUNE 6
#define HMI_NMODE 4
#define HMI_NAGC 3
#define HMI_NPRE 5
#define HMI_NVOX 4
#define HMI_NBPF 5
char hmi_noption[HMI_NSTATES] = {HMI_NTUNE, HMI_NMODE, HMI_NAGC, HMI_NPRE, HMI_NVOX, HMI_NBPF};
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_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_bpf [HMI_NBPF][8] = {"<2.5","2-6","5-12","10-24","20-40"};
char hmi_o_bpf [HMI_NBPF][8] = {"<2.5","2-6","5-12","10-24","20-40"}; // Indexed by
// Map option to setting
uint8_t hmi_pre[5] = {REL_ATT_30, REL_ATT_20, REL_ATT_10, REL_ATT_00, REL_PRE_10};
uint8_t hmi_bpf[5] = {REL_LPF2, REL_BPF6, REL_BPF12, REL_BPF24, REL_BPF40};
int hmi_mode[4] = {MODE_USB, MODE_LSB, MODE_AM, MODE_CW};
int hmi_agc[3] = {AGC_NONE, AGC_SLOW, AGC_FAST};
int hmi_pre[5] = {REL_ATT_30, REL_ATT_20, REL_ATT_10, REL_ATT_00, REL_PRE_10};
int hmi_vox[4] = {VOX_OFF, VOX_LOW, VOX_MEDIUM, VOX_HIGH};
int hmi_bpf[5] = {REL_LPF2, REL_BPF6, REL_BPF12, REL_BPF24, REL_BPF40};
uint8_t hmi_state, hmi_option; // Current state and option selection
uint8_t hmi_sub[HMI_NSTATES] = {4,0,0,3,0,2}; // Stored option selection per state
bool hmi_update;
int hmi_state, hmi_option; // Current state and menu option selection
int hmi_sub[HMI_NSTATES] = {4,0,0,3,0,2}; // Stored option selection per state
bool hmi_update; // LCD needs update
uint32_t hmi_freq; // Frequency from Tune state
uint32_t hmi_step[6] = {10000000, 1000000, 100000, 10000, 1000, 100}; // Frequency digit increments
@ -151,92 +156,59 @@ void hmi_handler(uint8_t event)
/* Special case for TUNE state */
if (hmi_state == HMI_S_TUNE)
{
if (event==HMI_E_ENTER) // Commit current value
{
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Commit frequency
}
if (event==HMI_E_ESCAPE) // Enter submenus
switch (event)
{
case HMI_E_ENTER: // Commit current value
SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Commit frequency
break;
case HMI_E_ESCAPE: // Enter submenus
hmi_sub[hmi_state] = hmi_option; // Store selection (i.e. digit)
hmi_state = HMI_S_MODE; // Should remember last one
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
}
if (event==HMI_E_INCREMENT)
{
break;
case HMI_E_INCREMENT:
if (hmi_freq < (HMI_MAXFREQ - hmi_step[hmi_option])) // Boundary check
hmi_freq += hmi_step[hmi_option]; // Increment selected digit
}
if (event==HMI_E_DECREMENT)
{
if (hmi_freq > (hmi_step[hmi_option] + HMI_MINFREQ)) // Boundary check
break;
case HMI_E_DECREMENT:
if (hmi_freq > (HMI_MINFREQ + hmi_step[hmi_option])) // Boundary check
hmi_freq -= hmi_step[hmi_option]; // Decrement selected digit
}
if (event==HMI_E_RIGHT)
{
hmi_option = (hmi_option<6)?hmi_option+1:6; // Digit to the right
}
if (event==HMI_E_LEFT)
{
break;
case HMI_E_RIGHT:
hmi_option = (hmi_option<5)?hmi_option+1:5; // Digit to the right
break;
case HMI_E_LEFT:
hmi_option = (hmi_option>0)?hmi_option-1:0; // Digit to the left
break;
}
return; // Early bail-out
}
/* Submenu states */
switch(hmi_state)
/* Actions for other states */
switch (event)
{
case HMI_S_MODE:
if (event==HMI_E_INCREMENT)
hmi_option = (hmi_option<HMI_NMODE-1)?hmi_option+1:HMI_NMODE-1;
if (event==HMI_E_DECREMENT)
hmi_option = (hmi_option>0)?hmi_option-1:0;
case HMI_E_ENTER:
hmi_sub[hmi_state] = hmi_option; // Store value for selected option
hmi_update = true; // Mark HMI updated: activate value
break;
case HMI_S_AGC:
if (event==HMI_E_INCREMENT)
hmi_option = (hmi_option<HMI_NAGC-1)?hmi_option+1:HMI_NAGC-1;
if (event==HMI_E_DECREMENT)
hmi_option = (hmi_option>0)?hmi_option-1:0;
break;
case HMI_S_PRE:
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;
case HMI_S_VOX:
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_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 */
if (event==HMI_E_ENTER)
{
hmi_sub[hmi_state] = hmi_option; // Store selected option
hmi_update = true; // Mark HMI updated
}
if (event==HMI_E_ESCAPE)
{
case HMI_E_ESCAPE:
hmi_state = HMI_S_TUNE; // Leave submenus
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
}
if (event==HMI_E_RIGHT)
{
break;
case HMI_E_RIGHT:
hmi_state = (hmi_state<HMI_NSTATES-1)?(hmi_state+1):1; // Change submenu
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
}
if (event==HMI_E_LEFT)
{
break;
case 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
break;
case HMI_E_INCREMENT:
hmi_option = (hmi_option<hmi_noption[hmi_state]-1)?hmi_option+1:hmi_noption[hmi_state]-1;
break;
case HMI_E_DECREMENT:
hmi_option = (hmi_option>0)?hmi_option-1:0;
break;
}
}
@ -301,6 +273,7 @@ void hmi_init(void)
gpio_pull_up(GP_AUX_2);
gpio_pull_up(GP_AUX_3);
gpio_pull_up(GP_PTT);
gpio_set_oeover(GP_PTT, GPIO_OVERRIDE_HIGH); // Enable output on PTT GPIO; bidirectional
// Enable interrupt on level low
gpio_set_irq_enabled(GP_ENC_A, GPIO_IRQ_EDGE_ALL, true);
@ -318,10 +291,10 @@ 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 (depends on mixer type)
SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // 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_state = PTT_DEBOUNCE;
ptt_active = false;
dsp_setmode(hmi_sub[HMI_S_MODE]);
@ -381,9 +354,6 @@ void hmi_evaluate(void)
}
/* PTT debouncing */
if (hmi_sub[HMI_S_VOX] == 0) // No VOX active
{
gpio_set_dir(GP_PTT, false); // PTT input
if (gpio_get(GP_PTT)) // Get PTT level
{
if (ptt_state<PTT_DEBOUNCE) // Increment debounce counter when high
@ -398,19 +368,11 @@ void hmi_evaluate(void)
ptt_active = false;
if (ptt_state == 0) // Set PTT when debounced level low
ptt_active = true;
}
else
{
ptt_active = false;
gpio_set_dir(GP_PTT, true); // PTT output
}
/* Set parameters corresponding to latest entered option value */
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq);
dsp_setmode(hmi_sub[HMI_S_MODE]);
dsp_setvox(hmi_sub[HMI_S_VOX]);
dsp_setagc(hmi_sub[HMI_S_AGC]);
if (hmi_update)
SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Always set frequency
if (hmi_update) // Others only when indicated
{
dsp_setmode(hmi_sub[HMI_S_MODE]);
dsp_setvox(hmi_sub[HMI_S_VOX]);

2
hmi.h
Wyświetl plik

@ -9,6 +9,8 @@
* See hmi.c for more information
*/
#define CARRIER_OFFSET 3500
extern bool ptt_active;
void hmi_init(void);

28
lcd.c
Wyświetl plik

@ -7,13 +7,20 @@
* --> Set I2C address below!
* --> Select LCD_TYPE below!
*
* Driver for 16x2 HD44780 based LCD displays.
* There exist many different types, so you may need to adapt some of the code.
* Most notably the startup sequence and the way bytes are sent over the I2C interface.
* But also the register mappings as described below.
*
* LCD_1804:
* ---------
* Grove 16x2 LCD HD44780, with integrated JHD1804 I2C bridge (@ 0x3E)
* 2 byte interface,
* byte0 contains coomand/data,
* byte1 contains 8-bit command or data word
*
* LCD_8574_ADA:
* -------------
* Standard 16x2 LCD HD44780, with PCF8574 based Adafruit backpack I2C bridge (@ 0x27)
* Same registers, but interface uses 4-bit transfers for data/comand, in bits 3..6
* bit 0 is unused
@ -23,6 +30,7 @@
* bit 7 is backlight (1 for on)
*
* LCD_8574_GEN:
* -------------
* Standard 16x2 LCD HD44780, with PCF8574 based Generic backpack I2C bridge (@ 0x27)
* Same registers, but interface uses 4-bit transfers for data/comand, in bits 4..7
* bit 0 is Register Select (0 for command, 1 for data)
@ -31,8 +39,6 @@
* bit 3 is backlight (1 for on)
* bit 4..7 data or command nibble (write high nibble first)
*
* Note: There may be other bit-mappings, code needs to be adjusted to that...
*
*/
#include <stdio.h>
#include <string.h>
@ -43,18 +49,18 @@
#include "lcd.h"
/** User selectable definitions **/
// Set I2C address
//#define I2C_LCD 0x27
#define I2C_LCD 0x3E
// Set I2C address for your device
#define I2C_LCD 0x3E // Grove: 0x3E, 8574 backpack: 0x20..0x27
// Select LCD type matching your HW
// Select LCD type to match your device,
// or define a new one when code changes are needed.
#define LCD_1804 0 // Seeed / Grove
#define LCD_8574_ADA 1 // Adafruit I2C backpack
#define LCD_8574_GEN 2 // Generic I2C backpack
#define LCD_TYPE LCD_1804
/** HD44780 interface **/
/** Generic HD44780 interface **/
// commands
#define LCD_CLEARDISPLAY 0x01 // Note: LCD_ENTRYINC is set
#define LCD_RETURNHOME 0x02
@ -125,12 +131,10 @@
#endif
/** Other definitions **/
#define LCD_DELAY 100 // Delay for regular write
/*
* User defined (CGRAM) characters
* Display RAM addresses 0x00-0x1f for top row and 0x40-0x5f for bottom row
@ -144,8 +148,8 @@ uint8_t cgram[8][8] =
{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
{0x00, 0x04, 0x04, 0x04, 0x15, 0x0e, 0x04, 0x00}, // 0x06: Receive arrow down
{0x04, 0x0e, 0x15, 0x04, 0x04, 0x04, 0x00, 0x00} // 0x07: Transmit arrow up
};
/*
@ -202,7 +206,7 @@ void lcd_sendbyte(uint8_t command, uint8_t data)
}
/*
* It seems that there is too much in here, but it doesn't harm either.
* It seems that there is too much init here, but it doesn't harm either.
*/
void lcd_init(void)
{

Wyświetl plik

@ -89,20 +89,6 @@ void mon_lt(void)
}
/*
* Checks for inter-core fifo overruns
*/
extern volatile uint32_t fifo_overrun, fifo_rx, fifo_tx, fifo_xx, fifo_incnt;
void mon_fo(void)
{
printf("Fifo input: %lu\n", fifo_incnt);
printf("Fifo rx: %lu\n", fifo_rx);
printf("Fifo tx: %lu\n", fifo_tx);
printf("Fifo unknown: %lu\n", fifo_xx);
printf("Fifo overruns: %lu\n", fifo_overrun);
}
/*
* Toggles the PTT status, overriding the HW signal
*/
@ -131,12 +117,13 @@ void mon_bp(void)
if (*argv[1]=='w')
{
if (nargs>=2)
if (nargs>2)
{
ret = atoi(argv[2]);
relay_setband((uint8_t)ret);
relay_setband(ret);
}
}
sleep_ms(1);
ret = relay_getband();
if (ret<0)
printf ("I2C read error\n");
@ -153,12 +140,13 @@ void mon_rx(void)
if (*argv[1]=='w')
{
if (nargs>=2)
if (nargs>2)
{
ret = atoi(argv[2]);
relay_setattn((uint8_t)ret);
relay_setattn(ret);
}
}
sleep_ms(1);
ret = relay_getattn();
if (ret<0)
printf ("I2C read error\n");
@ -167,18 +155,56 @@ void mon_rx(void)
}
/*
* Checks for overruns
*/
extern volatile uint32_t dsp_overrun;
#if DSP_FFT == 1
extern volatile uint32_t dsp_tickx;
extern volatile int scale0;
extern volatile int scale1;
#endif
void mon_or(void)
{
printf("DSP overruns : %d\n", dsp_overrun);
#if DSP_FFT == 1
printf("DSP loop load : %lu%%\n", (100*dsp_tickx)/512);
printf("FFT scale = %d, iFFT scale = %d\n", scale0, scale1);
#endif
}
/*
* ADC and AGC levels
*/
extern volatile uint32_t adc_level[3];
extern volatile int32_t rx_agc;
extern volatile int adccnt;
void mon_adc(void)
{
// Print results
printf("ADC0: %5u/2048\n", adc_level[0]>>8);
printf("ADC1: %5u/2048\n", adc_level[1]>>8);
printf("ADC2: %5u/2048\n", adc_level[2]>>8);
printf("AGC : %7d\n", rx_agc);
printf("ADCc: %5d\n", adccnt);
}
/*
* Command shell table, organize the command functions above
*/
#define NCMD 6
#define NCMD 7
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"},
{"or", 2, &mon_or, "or (no parameters)", "Returns overrun information"},
{"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"}
{"rx", 2, &mon_rx, "rx {r|w} <value>", "Read or Write RX relays"},
{"adc", 3, &mon_adc, "adc (no parameters)", "Dump latest ADC readouts"}
};
@ -187,6 +213,9 @@ shell_t shell[NCMD]=
/*** Commandstring parser and monitor process ***/
/*** ---------------------------------------- ***/
#define ISALPHANUM(c) (((c)>' ') && ((c)<127))
#define ISWHITESP(c) (((c)!='\0') && ((c)<=' '))
#define ISEOL(c) ((c)=='\0')
/*
* Command line parser
*/
@ -197,18 +226,19 @@ void mon_parse(char* s)
p = s; // Set to start of string
nargs = 0;
while (*p!='\0') // Assume stringlength >0
while (ISWHITESP(*p)) p++; // Skip leading whitespace
while (!ISEOL(*p)) // Check remaining stringlength >0
{
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
while (ISALPHANUM(*p)) p++; // Skip non-whitespace
while (ISWHITESP(*p)) p++; // Skip separating whitespace
}
if (nargs==0) return; // No command or parameter
if (nargs==0) return; // Nothing to do
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)();
(*shell[i].cmd)(); // Execute if found
else // Unknown command
{
for (i=0; i<NCMD; i++) // Print help if no match

10
relay.c
Wyświetl plik

@ -30,14 +30,15 @@
#define I2C_BPF 0x20
void relay_setband(uint8_t val)
void relay_setband(int val)
{
uint8_t data[2];
int ret;
data[0] = val&0x1f;
data[0] = ((uint8_t)val)&0x1f;
if (i2c_write_blocking(i2c1, I2C_BPF, data, 1, false) < 0)
i2c_write_blocking(i2c1, I2C_BPF, data, 1, false);
sleep_ms(1);
}
int relay_getband(void)
@ -51,13 +52,14 @@ int relay_getband(void)
return(ret);
}
void relay_setattn(uint8_t val)
void relay_setattn(int val)
{
uint8_t data[2];
data[0] = val&0x07;
data[0] = ((uint8_t)val)&0x07;
if (i2c_write_blocking(i2c1, I2C_RX, data, 1, false) < 0)
i2c_write_blocking(i2c1, I2C_RX, data, 1, false);
sleep_ms(1);
}
int relay_getattn(void)

12
relay.h
Wyświetl plik

@ -9,22 +9,24 @@
* See relay.c for more information
*/
/* relay_setband() values */
#define REL_LPF2 0x01
#define REL_BPF6 0x02
#define REL_BPF12 0x04
#define REL_BPF24 0x08
#define REL_BPF40 0x10
/* relay_setattn() values */
#define REL_ATT_30 0x03
#define REL_ATT_20 0x01
#define REL_ATT_10 0x02
#define REL_ATT_00 0x00
#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);
void relay_setband(int val);
void relay_setattn(int val);
int relay_getband(void);
int relay_getattn(void);
void relay_init(void);
#endif

Wyświetl plik

@ -3,13 +3,26 @@
*
* Created: Jan 2020
* Author: Arjan
*
* Driver for the SI5351A VCO
*
Si5351 principle of operation:
==============================
Crystal frequency Fxtal (usually 25MHz) is multiplied in a PLL by MSN to obtain Fvco.
PLL A and B have independent MSN values, the Fout channel i can be derived from either.
Fvco is between 600MHz and 900MHz, but the spec in reality is more relaxed.
Fvco is divided by MSi and Ri to obtain the output frequency Fout.
MSi and Ri are selected to be in the ballpark of desired frequency range.
MSN is then used for tuning.
Only certain values of MSi and Ri are allowed when quadrature output is needed.
Si5351 principle:
=================
+-------+ +-------+ +------+
- Fxtal --> | * MSN | -- Fvco --> | / MSi | --> | / Ri | -- Fout -->
+-------+ +-------+ +------+
Details:
========
---Derivation of Fout---
MSN determines: Fvco = Fxtal * (MSN) , where MSN = a + b/c
MSi and Ri determine: Fout = Fvco / (Ri*MSi) , where MSi = a + b/c (different a, b and c)
@ -60,7 +73,6 @@ Quadrature Phase offsets (i.e. delay):
NOTE: Phase offsets only work when Ri = 1, this means minimum Fout is 4.762MHz at Fvco = 600MHz. Additional flip/flop dividers are needed to get 80m band frequencies, or Fvco must be tuned below spec.
Control Si5351 (see AN619):
===========================
----+---------+---------+---------+---------+---------+---------+---------+---------+
@ -117,7 +129,7 @@ Control Si5351 (see AN619):
====
183 | XTAL_CL | Reserved |
====
*
*/
#include <stdio.h>
@ -173,9 +185,11 @@ Control Si5351 (see AN619):
vfo_t vfo[2]; // 0: clk0 and clk1 1: clk2
vfo_t vfo[2]; // 0: clk0 & clk1 1: clk2
/* read contents of SI5351 registers, from reg to reg+len-1, output in data array */
/*
* read contents of SI5351 registers, from reg to reg+len-1, output in data array
*/
int si_getreg(uint8_t *data, uint8_t reg, uint8_t len)
{
int ret;
@ -188,9 +202,11 @@ int si_getreg(uint8_t *data, uint8_t reg, uint8_t len)
}
// Set up MSN PLL divider for vfo[i], assuming MSN has been set in vfo[i]
// Optimize for speed, this may be called with short intervals
// See also SiLabs AN619 section 3.2
/*
* Set up MSN PLL divider for vfo[i], assuming MSN has been set in vfo[i]
* Optimize for speed, this may be called with short intervals
* See also SiLabs AN619 section 3.2
*/
void si_setmsn(uint8_t i)
{
uint8_t data[16]; // I2C trx buffer
@ -199,11 +215,7 @@ void si_setmsn(uint8_t i)
uint32_t B;
i=(i>0?1:0);
/*
P1 = 128*a + Floor(128*b/c) - 512
P2 = 128*b - c*Floor(128*b/c)
P3 = c (P3 = 1000000 for MSN tuning)
*/
A = (uint32_t)(floor(vfo[i].msn)); // A is integer part of MSN
B = (uint32_t)((vfo[i].msn - (float)A) * SI_PLL_C); // B is C * fraction part of MSN (C is a constant)
P2 = (uint32_t)(floor((float)(128 * B) / (float)SI_PLL_C));
@ -223,9 +235,11 @@ void si_setmsn(uint8_t i)
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]
// In this implementation we only use integer mode, i.e. b=0 and P3=1
// See also SiLabs AN619 section 4.1
/*
* Set up registers with MS and R divider for vfo[i], assuming values have been set in vfo[i]
* In this implementation we only use integer mode, i.e. b=0 and P3=1
* See also SiLabs AN619 section 4.1
*/
void si_setmsi(uint8_t i)
{
uint8_t data[16]; // I2C trx buffer
@ -233,11 +247,7 @@ void si_setmsi(uint8_t i)
uint8_t R;
i=(i>0?1:0);
/*
P1 = 128*a + Floor(128*b/c) - 512
P2 = 128*b - c*Floor(128*b/c) (P2 = 0 for MSi integer mode)
P3 = c (P3 = 1 for MSi integer mode)
*/
P1 = (uint32_t)(128*(uint32_t)floor(vfo[i].msi) - 512);
R = vfo[i].ri;
R = (R&0xf0) ? ((R&0xc0)?((R&0x80)?7:6):(R&0x20)?5:4) : ((R&0x0c)?((R&0x08)?3:2):(R&0x02)?1:0); // quick log2(r)
@ -286,10 +296,12 @@ void si_setmsi(uint8_t i)
}
// For each vfo, calculate required MSN setting, MSN = MSi*Ri*Fout/Fxtal
// If in range, just set MSN registers
// If not in range, recalculate MSi and Ri and also MSN
// Set MSN, MSi and Ri registers (implicitly resets PLL)
/*
* For each vfo, calculate required MSN setting, MSN = MSi*Ri*Fout/Fxtal
* If in range, just set MSN registers
* If not in range, recalculate MSi and Ri and also MSN
* Set MSN, MSi and Ri registers (implicitly resets PLL)
*/
void si_evaluate(void)
{
float msn;
@ -327,22 +339,24 @@ void si_evaluate(void)
}
// Initialize the Si5351 VFO registers
/*
* Initialize the Si5351 VFO registers
* Hard initialize Synth registers to: 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
*/
void si_init(void)
{
uint8_t data[16]; // I2C trx buffer
// 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
vfo[0].freq = 10000000;
vfo[0].freq = 10000000; // Check this, should be 7074000?
vfo[0].flag = 0;
vfo[0].phase = 1;
vfo[0].ri = 1;
vfo[0].msi = 68;
vfo[0].msn = 27.2;
vfo[1].freq = 10000000;
vfo[1].freq = 10000000; // Check this, should be 7074000?
vfo[1].flag = 0;
vfo[1].phase = 0;
vfo[1].ri = 1;

29
uSDR.c
Wyświetl plik

@ -29,8 +29,6 @@
#include "monitor.h"
#include "relay.h"
#define LED_MS 1000
#define LOOP_MS 100
#define I2C0_SDA 16
#define I2C0_SCL 17
@ -40,6 +38,7 @@
/*
* LED TIMER definition and callback routine
*/
#define LED_MS 1000
struct repeating_timer led_timer;
bool led_callback(struct repeating_timer *t)
{
@ -54,6 +53,7 @@ bool led_callback(struct repeating_timer *t)
* Scheduler callback function.
* This executes every LOOP_MS.
*/
#define LOOP_MS 100
semaphore_t loop_sem;
struct repeating_timer loop_timer;
bool loop_callback(struct repeating_timer *t)
@ -65,7 +65,18 @@ bool loop_callback(struct repeating_timer *t)
int main()
{
/* Initialize LED pin output */
/*
* Optional: increase core voltage (normally 1.1V)
* Optional: overclock the CPU to 250MHz (normally 125MHz)
* Note that clk_peri (e.g. I2C) is derived from the SYS PLL
* Note that clk_adc sampling clock is derived from the 48MHz USB PLL.
*/
//vreg_set_voltage(VREG_VOLTAGE_1_25); sleep_ms(10);
//set_sys_clock_khz(250000, false); sleep_ms(10);
/*
* Initialize LED pin output
*/
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
@ -74,24 +85,20 @@ int main()
/*
* i2c0 is used for the si5351 interface
* i2c1 is used for the LCD and all other interfaces
* if the display cannot keep up, try lowering the i2c1 frequency
*/
/* i2c0 initialisation at 400Khz. */
i2c_init(i2c0, 400*1000);
i2c_init(i2c0, 400000); // i2c0 initialisation at 400Khz
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, 100*1000);
i2c_init(i2c1, 100000); // i2c1 initialisation at 100Khz
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 */
/* Initialize the SW units */
mon_init(); // Monitor shell on stdio
si_init(); // VFO control unit
dsp_init(); // Signal processing unit