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.

820
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)
*
* 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
* Signal processing of RX and TX branch, to be run on the second processor core (CORE1).
*
* 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)
/*
* 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)
volatile uint16_t dac_iq, dac_audio;
/*
* 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))));
}
/*
* 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.
/* 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 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)
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)
{
int16_t q_sample, i_sample, a_sample;
int32_t q_accu, i_accu;
int16_t qh;
uint16_t i;
int16_t k;
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++;
}
/*** SAMPLING ***/
/** CORE1: Timer callback routine **/
/*
* 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.
*/
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)
{
int32_t temp;
q_sample = adc_result[0]; // Take last ADC 0 result, connected to Q input
i_sample = adc_result[1]; // Take last ADC 1 result, connected to I input
/*
* Remove DC and store new sample
* IIR filter: dc = a*sample + (1-a)*dc where a = 1/128
* Amplitude of samples should fit inside [-2048, 2047]
*/
q_sample = (q_sample&0x0fff) - ADC_BIAS; // Clip to 12 bits and subtract mid-range
q_dc += q_sample/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)
{
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));
}
/*
* Shift-in I and Q raw samples
* Here the rate is 15625Hz
*/
for (i=0; i<14; i++)
{
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
}
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
{
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)
// 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)
{
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;
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
}
#if DSP_FFT == 1
if (tx_enabled)
{
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
}
else
{
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
}
#else
/*
* 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);
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++;
tx_enabled = false;
vox_active = false;
/*
* 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: 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: 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 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_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_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 */
adc_init(); // Initialize ADC to known state
adc_set_clkdiv(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);
/*
* 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.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_set_round_robin(0x01+0x02+0x04); // Sequence ADC 0-1-2 (GP 26, 27, 28) free running
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

224
hmi.c
Wyświetl plik

@ -100,36 +100,41 @@
#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_o_menu[HMI_NSTATES][8] = {"Tune","Mode","AGC","Pre","VOX"}; // Indexed by hmi_state
char hmi_o_mode[HMI_NMODE][8] = {"USB","LSB","AM","CW"}; // Indexed by hmi_sub[HMI_S_MODE]
char hmi_o_agc [HMI_NAGC][8] = {"NoGC","Slow","Fast"}; // Indexed by hmi_sub[HMI_S_AGC]
char hmi_o_pre [HMI_NPRE][8] = {"-30dB","-20dB","-10dB","0dB","+10dB"}; // Indexed by hmi_sub[HMI_S_PRE]
char hmi_o_vox [HMI_NVOX][8] = {"NoVOX","VOX-L","VOX-M","VOX-H"}; // Indexed by hmi_sub[HMI_S_VOX]
char hmi_o_bpf [HMI_NBPF][8] = {"<2.5","2-6","5-12","10-24","20-40"};
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_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"}; // 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
uint32_t hmi_freq; // Frequency from Tune state
uint32_t hmi_step[6] = {10000000, 1000000, 100000, 10000, 1000, 100}; // Frequency digit increments
#define HMI_MAXFREQ 30000000
#define HMI_MINFREQ 100
#define HMI_MULFREQ 1 // Factor between HMI and actual frequency
// Set to 2 for certain types of mixer
#define HMI_MULFREQ 1 // Factor between HMI and actual frequency
// Set to 2 for certain types of mixer
#define PTT_DEBOUNCE 3 // Nr of cycles for debounce
int ptt_state; // Debounce counter
bool ptt_active; // Resulting state
#define PTT_DEBOUNCE 3 // Nr of cycles for debounce
int ptt_state; // Debounce counter
bool ptt_active; // Resulting state
/*
* Some macros
@ -151,93 +156,60 @@ 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
switch (event)
{
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Commit frequency
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
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
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
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;
}
if (event==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)
{
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
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)
{
hmi_option = (hmi_option>0)?hmi_option-1:0; // Digit to the left
}
return; // Early bail-out
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;
case HMI_E_ESCAPE:
hmi_state = HMI_S_TUNE; // Leave submenus
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
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;
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
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;
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_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;
case HMI_E_INCREMENT:
hmi_option = (hmi_option<hmi_noption[hmi_state]-1)?hmi_option+1:hmi_noption[hmi_state]-1;
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)
{
hmi_state = HMI_S_TUNE; // Leave submenus
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
}
if (event==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)
{
hmi_state = (hmi_state>1)?(hmi_state-1):HMI_NSTATES-1; // Change submenu
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
}
case HMI_E_DECREMENT:
hmi_option = (hmi_option>0)?hmi_option-1:0;
break;
}
}
/*
@ -250,23 +222,23 @@ void hmi_callback(uint gpio, uint32_t events)
switch (gpio)
{
case GP_ENC_A: // Encoder
case GP_ENC_A: // Encoder
if (events&GPIO_IRQ_EDGE_FALL)
evt = gpio_get(GP_ENC_B)?HMI_E_INCREMENT:HMI_E_DECREMENT;
break;
case GP_AUX_0: // Enter
case GP_AUX_0: // Enter
if (events&GPIO_IRQ_EDGE_FALL)
evt = HMI_E_ENTER;
break;
case GP_AUX_1: // Escape
case GP_AUX_1: // Escape
if (events&GPIO_IRQ_EDGE_FALL)
evt = HMI_E_ESCAPE;
break;
case GP_AUX_2: // Previous
case GP_AUX_2: // Previous
if (events&GPIO_IRQ_EDGE_FALL)
evt = HMI_E_LEFT;
break;
case GP_AUX_3: // Next
case GP_AUX_3: // Next
if (events&GPIO_IRQ_EDGE_FALL)
evt = HMI_E_RIGHT;
break;
@ -274,7 +246,7 @@ void hmi_callback(uint gpio, uint32_t events)
return;
}
hmi_handler(evt); // Invoke state machine
hmi_handler(evt); // Invoke state machine
}
/*
@ -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);
@ -315,13 +288,13 @@ void hmi_init(void)
// Initialize LCD and set VFO
hmi_state = HMI_S_TUNE;
hmi_option = 4; // Active kHz digit
hmi_freq = 7074000UL; // Initial frequency
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_SETPHASE(0, 1); // Set phase to 90deg (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,42 +354,31 @@ void hmi_evaluate(void)
}
/* PTT debouncing */
if (hmi_sub[HMI_S_VOX] == 0) // No VOX active
if (gpio_get(GP_PTT)) // Get PTT level
{
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
ptt_state++;
}
else
{
if (ptt_state>0) // Decrement debounce counter when low
ptt_state--;
}
if (ptt_state == PTT_DEBOUNCE) // Reset PTT when debounced level high
ptt_active = false;
if (ptt_state == 0) // Set PTT when debounced level low
ptt_active = true;
if (ptt_state<PTT_DEBOUNCE) // Increment debounce counter when high
ptt_state++;
}
else
else
{
if (ptt_state>0) // Decrement debounce counter when low
ptt_state--;
}
if (ptt_state == PTT_DEBOUNCE) // Reset PTT when debounced level high
ptt_active = false;
gpio_set_dir(GP_PTT, true); // PTT output
}
if (ptt_state == 0) // Set PTT when debounced level low
ptt_active = true;
/* 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]);
dsp_setagc(hmi_sub[HMI_S_AGC]);
relay_setband(hmi_bpf[hmi_sub[HMI_S_BPF]]);
sleep_ms(1); // I2C doesn't work without...
sleep_ms(1); // I2C doesn't work without...
relay_setattn(hmi_pre[hmi_sub[HMI_S_PRE]]);
hmi_update = false;
}

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);

54
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,20 +49,20 @@
#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
#define LCD_1804 0 // Seeed / Grove
#define LCD_8574_ADA 1 // Adafruit I2C backpack
#define LCD_8574_GEN 2 // Generic I2C backpack
// 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_CLEARDISPLAY 0x01 // Note: LCD_ENTRYINC is set
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
@ -68,8 +74,8 @@
// flags for display entry mode: LCD_ENTRYMODESET
#define LCD_ENTRYSHIFT 0x01
#define LCD_ENTRYNOSHIFT 0x00
#define LCD_ENTRYINC 0x02 // Also applies to CGRAM writes
#define LCD_ENTRYDEC 0x00 // Also applies to CGRAM writes
#define LCD_ENTRYINC 0x02 // Also applies to CGRAM writes
#define LCD_ENTRYDEC 0x00 // Also applies to CGRAM writes
// flags for display on/off control: LCD_DISPLAYCONTROL
#define LCD_DISPLAYON 0x04
@ -125,10 +131,8 @@
#endif
/** Other definitions **/
#define LCD_DELAY 100 // Delay for regular write
#define LCD_DELAY 100 // Delay for regular write
/*
@ -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)
{
@ -236,9 +240,9 @@ void lcd_init(void)
/* Load CGRAM */
for (i=0; i<8; i++)
{
lcd_sendbyte(true, LCD_SETCGRAMADDR | (i<<3)); //Set CGRAM address
lcd_sendbyte(true, LCD_SETCGRAMADDR | (i<<3)); //Set CGRAM address
for (int j=0; j<8; j++)
lcd_sendbyte(false, cgram[i][j]); // One byte at a time
lcd_sendbyte(false, cgram[i][j]); // One byte at a time
}
}
@ -253,7 +257,7 @@ void lcd_curxy(uint8_t x, uint8_t y, bool on)
{
uint8_t txdata[3];
x &= 0x0f; // Clip range
x &= 0x0f; // Clip range
y &= 0x01;
lcd_sendbyte(true, (x | 0x80 | (y==1?0x40:0x00)));
lcd_sendbyte(true, LCD_DISPLAYCONTROL | LCD_DISPLAYON | (on?LCD_CURSORON:LCD_CURSOROFF) | LCD_BLINKOFF);
@ -261,7 +265,7 @@ void lcd_curxy(uint8_t x, uint8_t y, bool on)
void lcd_putxy(uint8_t x, uint8_t y, uint8_t c)
{
x &= 0x0f; // Clip range
x &= 0x0f; // Clip range
y &= 0x01;
lcd_sendbyte(true, (x | 0x80 | (y==1?0x40:0x00)));
lcd_sendbyte(false, c);
@ -271,12 +275,12 @@ void lcd_writexy(uint8_t x, uint8_t y, uint8_t *s)
{
uint8_t i, len;
x &= 0x0f; // Clip range
x &= 0x0f; // Clip range
y &= 0x01;
lcd_sendbyte(true, (x | 0x80 | (y==1?0x40:0x00)));
len = strlen(s);
len = (len>(16-x))?(16-x):len; // Clip range
len = (len>(16-x))?(16-x):len; // Clip range
for(i=0; i<len; i++)
lcd_sendbyte(false, s[i]);
}

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"},
{"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"}
{"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"},
{"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"},
{"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

178
si5351.c
Wyświetl plik

@ -3,13 +3,26 @@
*
* Created: Jan 2020
* Author: Arjan
*
* Driver for the SI5351A VCO
*
Si5351 principle:
=================
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.
+-------+ +-------+ +------+
- 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)
@ -59,7 +72,6 @@ Quadrature Phase offsets (i.e. delay):
- Set INV bit (reg 17) to add 180 deg.
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>
@ -128,7 +140,7 @@ Control Si5351 (see AN619):
#include "hardware/clocks.h"
#include "si5351.h"
#define I2C_VFO 0x60 // I2C address
#define I2C_VFO 0x60 // I2C address
// SI5351 register address definitions
#define SI_CLK_OE 3
@ -148,34 +160,36 @@ Control Si5351 (see AN619):
#define SI_XTAL_LOAD 183
// CLK_OE register 3 values
#define SI_CLK0_ENABLE 0b00000001 // Enable clock 0 output
#define SI_CLK1_ENABLE 0b00000010 // Enable clock 1 output
#define SI_CLK2_ENABLE 0b00000100 // Enable clock 2 output
#define SI_CLK0_ENABLE 0b00000001 // Enable clock 0 output
#define SI_CLK1_ENABLE 0b00000010 // Enable clock 1 output
#define SI_CLK2_ENABLE 0b00000100 // Enable clock 2 output
// CLKi_CTL register 16, 17, 18 values
// Normally 0x4f for clk 0 and 1, 0x6f for clk 2
#define SI_CLK_INT 0b01000000 // Set integer mode
#define SI_CLK_PLL 0b00100000 // Select PLL B as MS source (default 0 = PLL A)
#define SI_CLK_INV 0b00010000 // Invert output (i.e. phase + 180deg)
#define SI_CLK_SRC 0b00001100 // Select output source: 11=MS, 00=XTAL direct
#define SI_CLK_DRV 0b00000011 // Select output drive, increasingly: 2-4-6-8 mA (best risetime, use max = 11)
#define SI_CLK_INT 0b01000000 // Set integer mode
#define SI_CLK_PLL 0b00100000 // Select PLL B as MS source (default 0 = PLL A)
#define SI_CLK_INV 0b00010000 // Invert output (i.e. phase + 180deg)
#define SI_CLK_SRC 0b00001100 // Select output source: 11=MS, 00=XTAL direct
#define SI_CLK_DRV 0b00000011 // Select output drive, increasingly: 2-4-6-8 mA (best risetime, use max = 11)
// PLL_RESET register 177 values
#define SI_PLLB_RST 0b10000000 // Reset PLL B
#define SI_PLLA_RST 0b00100000 // Reset PLL A
#define SI_PLLB_RST 0b10000000 // Reset PLL B
#define SI_PLLA_RST 0b00100000 // Reset PLL A
#define SI_XTAL_FREQ 25001414UL // Replace with measured crystal frequency of XTAL for CL = 10pF (default)
#define SI_XTAL_FREQ 25001414UL // Replace with measured crystal frequency of XTAL for CL = 10pF (default)
#define SI_MSN_LO ((0.6e9)/SI_XTAL_FREQ)
#define SI_MSN_HI ((0.9e9)/SI_XTAL_FREQ)
#define SI_PLL_C 1000000UL // Parameter c for PLL-A and -B setting
#define SI_PLL_C 1000000UL // Parameter c for PLL-A and -B setting
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,24 +202,22 @@ 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
uint32_t P1, P2; // MSN parameters
uint8_t data[16]; // I2C trx buffer
uint32_t P1, P2; // MSN parameters
uint32_t A;
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)
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));
P1 = (uint32_t)(128 * A + P2 - 512);
P2 = (uint32_t)(128 * B - SI_PLL_C * P2);
@ -223,21 +235,19 @@ 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
uint8_t data[16]; // I2C trx buffer
uint32_t P1;
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)
@ -256,25 +266,25 @@ void si_setmsi(uint8_t i)
// If vfo[0] also set clk 1
if (i==0)
{
data[0] = SI_SYNTH_MS1; // Same data in synthesizer
data[0] = SI_SYNTH_MS1; // Same data in synthesizer
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
if (vfo[0].phase&1) // Phase is either 90 or 270 deg?
if (vfo[0].phase&1) // Phase is either 90 or 270 deg?
{
data[0] = SI_CLK1_PHOFF;
data[1] = vfo[0].msi;
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
}
else // Phase is 0 or 180 deg
else // Phase is 0 or 180 deg
{
data[0] = SI_CLK1_PHOFF;
data[1] = 0;
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
}
if (vfo[0].phase&2) // Phase is 180 or 270 deg?
if (vfo[0].phase&2) // Phase is 180 or 270 deg?
{
data[0] = SI_CLK1_CTL;
data[1] = 0x5d; // CLK1: INT, PLLA, INV, MS, 8mA
data[1] = 0x5d; // CLK1: INT, PLLA, INV, MS, 8mA
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
}
}
@ -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
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;
@ -351,53 +365,53 @@ void si_init(void)
// PLLA: MSN P1=0x00000b99, P2=0x000927c0, P3=0x000f4240
data[0] = SI_SYNTH_PLLA;
data[1] = 0x42; // MSNA_P3[15:8]
data[2] = 0x40; // MSNA_P3[7:0]
data[3] = 0x00; // 0b000000 , MSNA_P1[17:16]
data[4] = 0x0b; // MSNA_P1[15:8]
data[5] = 0x99; // MSNA_P1[7:0]
data[6] = 0xf9; // MSNA_P3[19:16] , MSNA_P2[19:16]
data[7] = 0x27; // MSNA_P2[15:8]
data[8] = 0xc0; // MSNA_P2[7:0]
data[1] = 0x42; // MSNA_P3[15:8]
data[2] = 0x40; // MSNA_P3[7:0]
data[3] = 0x00; // 0b000000 , MSNA_P1[17:16]
data[4] = 0x0b; // MSNA_P1[15:8]
data[5] = 0x99; // MSNA_P1[7:0]
data[6] = 0xf9; // MSNA_P3[19:16] , MSNA_P2[19:16]
data[7] = 0x27; // MSNA_P2[15:8]
data[8] = 0xc0; // MSNA_P2[7:0]
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
// PLLB: MSN P1=0x00000b99, P2=0x000927c0, P3=0x000f4240
data[0] = SI_SYNTH_PLLB; // Same content
data[0] = SI_SYNTH_PLLB; // Same content
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
// MS0 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1
data[0] = SI_SYNTH_MS0;
data[1] = 0x00; // MS0_P3[15:8]
data[2] = 0x01; // MS0_P3[7:0]
data[3] = 0x00; // 0b0, R0_DIV[2:0] , MS0_DIVBY4[1:0] , MS0_P1[17:16]
data[4] = 0x20; // MS0_P1[15:8]
data[5] = 0x00; // MS0_P1[7:0]
data[6] = 0x00; // MS0_P3[19:16] , MS0_P2[19:16]
data[7] = 0x00; // MS0_P2[15:8]
data[8] = 0x00; // MS0_P2[7:0]
data[1] = 0x00; // MS0_P3[15:8]
data[2] = 0x01; // MS0_P3[7:0]
data[3] = 0x00; // 0b0, R0_DIV[2:0] , MS0_DIVBY4[1:0] , MS0_P1[17:16]
data[4] = 0x20; // MS0_P1[15:8]
data[5] = 0x00; // MS0_P1[7:0]
data[6] = 0x00; // MS0_P3[19:16] , MS0_P2[19:16]
data[7] = 0x00; // MS0_P2[15:8]
data[8] = 0x00; // MS0_P2[7:0]
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
// MS1 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1
data[0] = SI_SYNTH_MS1; // Same content
data[0] = SI_SYNTH_MS1; // Same content
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
// MS2 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1
data[0] = SI_SYNTH_MS2; // Same content
data[0] = SI_SYNTH_MS2; // Same content
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
// Phase offsets for 3 clocks
data[0] = SI_CLK0_PHOFF;
data[1] = 0x00; // CLK0: phase 0 deg
data[2] = 0x44; // CLK1: phase 90 deg (=MSi)
data[3] = 0x00; // CLK2: phase 0 deg
data[1] = 0x00; // CLK0: phase 0 deg
data[2] = 0x44; // CLK1: phase 90 deg (=MSi)
data[3] = 0x00; // CLK2: phase 0 deg
i2c_write_blocking(i2c0, I2C_VFO, data, 4, false);
// Output port settings for 3 clocks
data[0] = SI_CLK0_CTL;
data[1] = 0x4d; // CLK0: INT, PLLA, nonINV, MS, 4mA
data[2] = 0x4d; // CLK1: INT, PLLA, nonINV, MS, 4mA
data[3] = 0x6f; // CLK2: INT, PLLB, nonINV, MS, 8mA
data[1] = 0x4d; // CLK0: INT, PLLA, nonINV, MS, 4mA
data[2] = 0x4d; // CLK1: INT, PLLA, nonINV, MS, 4mA
data[3] = 0x6f; // CLK2: INT, PLLB, nonINV, MS, 8mA
i2c_write_blocking(i2c0, I2C_VFO, data, 4, false);
// Disable spread spectrum (startup state is undefined)

49
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,49 +65,56 @@ 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
gpio_put(PICO_DEFAULT_LED_PIN, true); // Set LED on
add_repeating_timer_ms(-LED_MS, led_callback, NULL, &led_timer);
/*
* i2c0 is used for the si5351 interface
* i2c1 is used for the LCD and all other interfaces
* 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 */
mon_init(); // Monitor shell on stdio
si_init(); // VFO control unit
dsp_init(); // Signal processing unit
/* Initialize the SW units */
mon_init(); // Monitor shell on stdio
si_init(); // VFO control unit
dsp_init(); // Signal processing unit
relay_init();
lcd_init(); // LCD output unit
hmi_init(); // HMI user inputs
lcd_init(); // LCD output unit
hmi_init(); // HMI user inputs
/* A simple round-robin scheduler */
sem_init(&loop_sem, 1, 1) ;
add_repeating_timer_ms(-LOOP_MS, loop_callback, NULL, &loop_timer);
while (1)
{
sem_acquire_blocking(&loop_sem); // Wait until timer callback releases sem
mon_evaluate(); // Check monitor input
si_evaluate(); // Refresh VFO settings
hmi_evaluate(); // Refresh HMI
sem_acquire_blocking(&loop_sem); // Wait until timer callback releases sem
mon_evaluate(); // Check monitor input
si_evaluate(); // Refresh VFO settings
hmi_evaluate(); // Refresh HMI
}
return 0;