kopia lustrzana https://github.com/ArjanteMarvelde/uSDR-pico
rodzic
f31ae4a79c
commit
4a76b70b22
|
@ -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)
|
||||
|
||||
|
|
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
768
dsp.c
768
dsp.c
|
@ -4,42 +4,33 @@
|
|||
* Created: Mar 2021
|
||||
* Author: Arjan te Marvelde
|
||||
*
|
||||
* Signal processing of RX and TX branch, to be run on the second processor core.
|
||||
* Each branch has a dedicated routine that must run on set times.
|
||||
* The period is determined by reads from the inter-core fifo, by the dsp_loop() routine.
|
||||
* This fifo is written from core0 from a 16us timer callback routine (i.e. 62.5kHz)
|
||||
* Signal processing of RX and TX branch, to be run on the second processor core (CORE1).
|
||||
*
|
||||
* The RX branch:
|
||||
* - Sample I and Q QSD channels, and shift into I and Q delay line (62.5 kHz per channel)
|
||||
* - Low pass filter: Fc=4kHz
|
||||
* - Quarter rate (15.625 kHz) to improve low freq behavior of Hilbert transform
|
||||
* - Calculate 15 tap Hilbert transform on Q
|
||||
* - Demodulate, taking proper delays into account
|
||||
* - Push to Audio output DAC
|
||||
*
|
||||
* Always perform audio sampling (62.5kHz) and level detections, in case of VOX active
|
||||
*
|
||||
* The TX branch (if VOX or PTT):
|
||||
* - Low pass filter: Fc=3kHz
|
||||
* - Eight rate (7.8125 kHz) to improve low F behavior of Hilbert transform
|
||||
* - Generate Q samples by doing a Hilbert transform
|
||||
* - Push I and Q to QSE output DACs
|
||||
* The actual DSP engine can be either FFT based in the frequency domain, or in the time domain.
|
||||
* In dsp.h this can be selected compile-time, by defining the environment variable DSP_FFT.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/multicore.h"
|
||||
#include "pico/platform.h"
|
||||
#include "pico/time.h"
|
||||
#include "pico/sem.h"
|
||||
#include "hardware/structs/bus_ctrl.h"
|
||||
#include "hardware/pwm.h"
|
||||
#include "hardware/adc.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/timer.h"
|
||||
#include "hardware/clocks.h"
|
||||
|
||||
#define ADC0_IRQ_FIFO 22 // FIFO IRQ number
|
||||
#define GP_PTT 15 // PTT pin 20 (GPIO 15)
|
||||
|
||||
#include "dsp.h"
|
||||
#include "hmi.h"
|
||||
#include "fix_fft.h"
|
||||
|
||||
#define GP_PTT 15 // PTT pin 20 (GPIO 15)
|
||||
|
||||
volatile bool tx_enabled; // TX branch active
|
||||
volatile uint32_t dsp_overrun; // Overrun counter
|
||||
|
||||
|
||||
/*
|
||||
|
@ -48,22 +39,25 @@
|
|||
* PWM frequency = Fsys / DAC_RANGE
|
||||
* A value of 250 means 125MHz/250=500kHz
|
||||
* ADC is 12 bit, so resolution is by definition 4096
|
||||
* To eliminate undefined behavior we clip off the upper 4 sample bits.
|
||||
*/
|
||||
#define DAC_RANGE 256
|
||||
#define DAC_BIAS (DAC_RANGE/2)
|
||||
#define ADC_RANGE 4096
|
||||
#define ADC_BIAS (ADC_RANGE/2)
|
||||
|
||||
|
||||
volatile uint16_t dac_iq, dac_audio;
|
||||
|
||||
|
||||
/*
|
||||
* Callback timeout and inter-core FIFO commands.
|
||||
* The timer value in usec determines frequency of TX and RX loops
|
||||
* Exact time is obtained by passing the value as negative
|
||||
* Here we use 16us (62.5 kHz == PWM freq/4)
|
||||
* MODE is modulation/demodulation
|
||||
* This setting steers the signal processing branch chosen
|
||||
*/
|
||||
#define DSP_US 16
|
||||
#define DSP_TX 1
|
||||
#define DSP_RX 2
|
||||
volatile int dsp_mode;
|
||||
void dsp_setmode(int mode)
|
||||
{
|
||||
dsp_mode = mode;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
@ -77,526 +71,404 @@
|
|||
*/
|
||||
#define AGC_REF 6
|
||||
#define AGC_DECAY 8192
|
||||
#define AGC_FAST 64
|
||||
#define AGC_SLOW 4096
|
||||
#define AGC_OFF 32766
|
||||
volatile uint16_t agc_decay = AGC_OFF;
|
||||
volatile uint16_t agc_attack = AGC_OFF;
|
||||
#define AGC_SHORT 64
|
||||
#define AGC_LONG 4096
|
||||
#define AGC_DIS 32766
|
||||
volatile uint16_t agc_decay = AGC_DIS;
|
||||
volatile uint16_t agc_attack = AGC_DIS;
|
||||
void dsp_setagc(int agc)
|
||||
{
|
||||
switch(agc)
|
||||
{
|
||||
case 1: //SLOW, for values see hmi.c
|
||||
agc_attack = AGC_SLOW;
|
||||
case AGC_SLOW:
|
||||
agc_attack = AGC_LONG;
|
||||
agc_decay = AGC_DECAY;
|
||||
break;
|
||||
case 2: //FAST
|
||||
agc_attack = AGC_FAST;
|
||||
case AGC_FAST:
|
||||
agc_attack = AGC_SHORT;
|
||||
agc_decay = AGC_DECAY;
|
||||
break;
|
||||
default: //OFF
|
||||
agc_attack = AGC_OFF;
|
||||
agc_decay = AGC_OFF;
|
||||
default:
|
||||
agc_attack = AGC_DIS;
|
||||
agc_decay = AGC_DIS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* MODE is modulation/demodulation
|
||||
* This setting steers the signal processing branch chosen
|
||||
*/
|
||||
volatile uint16_t dsp_mode; // For values see hmi.c, assume {USB,LSB,AM,CW}
|
||||
void dsp_setmode(int mode)
|
||||
{
|
||||
dsp_mode = (uint16_t)mode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VOX LINGER is the number of 16us cycles to wait before releasing TX mode
|
||||
* The level of detection is related to the maximum ADC range.
|
||||
* VOX LINGER is the number msec to wait before releasing TX mode
|
||||
* The level of detection is derived from the maximum ADC range.
|
||||
*/
|
||||
#define VOX_LINGER 500000/16
|
||||
#define VOX_HIGH ADC_BIAS/2
|
||||
#define VOX_MEDIUM ADC_BIAS/4
|
||||
#define VOX_LOW ADC_BIAS/16
|
||||
#define VOX_OFF 0
|
||||
volatile uint16_t vox_count;
|
||||
volatile uint16_t vox_level = VOX_OFF;
|
||||
#define VOX_LINGER 500 // 500msec
|
||||
|
||||
volatile uint16_t vox_count = 0;
|
||||
volatile uint16_t vox_level = 0;
|
||||
volatile bool vox_active; // Is set when audio energy > vox level (and not OFF)
|
||||
void dsp_setvox(int vox)
|
||||
{
|
||||
switch(vox)
|
||||
{
|
||||
case 1:
|
||||
vox_level = VOX_LOW;
|
||||
case VOX_HIGH:
|
||||
vox_level = ADC_BIAS/2;
|
||||
break;
|
||||
case 2:
|
||||
vox_level = VOX_MEDIUM;
|
||||
case VOX_MEDIUM:
|
||||
vox_level = ADC_BIAS/4;
|
||||
break;
|
||||
case 3:
|
||||
vox_level = VOX_HIGH;
|
||||
case VOX_LOW:
|
||||
vox_level = ADC_BIAS/16;
|
||||
break;
|
||||
default:
|
||||
vox_level = VOX_OFF;
|
||||
vox_level = 0;
|
||||
vox_count = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Low pass filters Fc=3, 7 and 15 kHz (see http://t-filter.engineerjs.com/)
|
||||
* Settings: sample rates 62500, 31250 or 15625 Hz, stopband -40dB, passband ripple 5dB
|
||||
* Note: 8 bit precision, so divide sum by 256 (this could be improved when 32bit accumulator)
|
||||
*/
|
||||
int16_t lpf3_62[15] = { 3, 3, 5, 7, 9, 10, 11, 11, 11, 10, 9, 7, 5, 3, 3}; // Pass: 0-3000, Stop: 6000-31250
|
||||
int16_t lpf3_31[15] = { -2, -3, -3, 1, 10, 21, 31, 35, 31, 21, 10, 1, -3, -3, -2}; // Pass: 0-3000, Stop: 6000-15625
|
||||
int16_t lpf3_15[15] = { 3, 4, -3,-14,-15, 6, 38, 53, 38, 6,-15,-14, -3, 4, 3}; // Pass: 0-3000, Stop: 4500-7812
|
||||
int16_t lpf7_62[15] = { -2, -1, 1, 7, 16, 26, 33, 36, 33, 26, 16, 7, 1, -1, -2}; // Pass: 0-7000, Stop: 10000-31250
|
||||
int16_t lpf7_31[15] = { -1, 4, 9, 2,-12, -2, 40, 66, 40, -2,-12, 2, 9, 4, -1}; // Pass: 0-7000, Stop: 10000-15625
|
||||
int16_t lpf15_62[15] = { -1, 3, 12, 6,-12, -4, 40, 69, 40, -4,-12, 6, 12, 3, -1}; // Pass: 0-15000, Stop: 20000-31250
|
||||
|
||||
volatile uint16_t dac_iq, dac_audio;
|
||||
volatile uint32_t fifo_overrun, fifo_rx, fifo_tx, fifo_xx, fifo_incnt;
|
||||
volatile bool tx_enabled, vox_active;
|
||||
|
||||
|
||||
#define ABS(x) ( (x)<0 ? -(x) : (x) )
|
||||
|
||||
/*
|
||||
* Some macro's
|
||||
* See Alpha Max plus Beta Min algorithm for MAG (vector length)
|
||||
* Z = alpha*max(i,q) + beta*min(i,q);
|
||||
* alpha = 1/1, beta = 3/8 (error<6.8%)
|
||||
* alpha = 15/16, beta = 15/32 (error<6.25%)
|
||||
* Better algorithm:
|
||||
* Z = max( max(i,q), alpha*max(i,q)+beta*min(i,q) )
|
||||
* alpha = 29/32, beta = 61/128 (error<2.4%)
|
||||
*/
|
||||
#define ABS(x) ((x)<0?-(x):(x))
|
||||
#define MAG(i,q) (ABS(i)>ABS(q) ? ABS(i)+((3*ABS(q))>>3) : ABS(q)+((3*ABS(i))>>3))
|
||||
|
||||
|
||||
/*
|
||||
* CORE1:
|
||||
* ADC IRQ handler.
|
||||
* Fills the results array in RR fashion, for 3 channels (~2usec per channel).
|
||||
*/
|
||||
volatile uint16_t adc_result[3];
|
||||
volatile int adc_next; // Remember which ADC the result is from
|
||||
uint32_t adc_count=0; // Debugging
|
||||
void adcfifo_handler(void)
|
||||
inline int16_t mag(int16_t i, int16_t q)
|
||||
{
|
||||
adc_result[adc_next] = adc_fifo_get(); // Get result from fifo
|
||||
adc_next = adc_hw->cs; // Update adc_next with HW CS register
|
||||
adc_next = (adc_next >> ADC_CS_AINSEL_LSB) & 3; // Shift and Mask: Only 0, 1 and 2 are valid numbers
|
||||
adc_count++;
|
||||
i = ABS(i); q = ABS(q);
|
||||
if (i>q)
|
||||
return (MAX(i,((29*i/32) + (61*q/128))));
|
||||
else
|
||||
return (MAX(q,((29*q/32) + (61*i/128))));
|
||||
}
|
||||
|
||||
/* Note:
|
||||
* A simple IIR single pole low pass filter could be made for anti-aliasing.
|
||||
* Do this at full speed 16usec sampling,
|
||||
* then for dsp_tim decimate with 1/8 (128usec)
|
||||
* and for dsp_fft decimate with 1/4 (64usec)
|
||||
* to obtain proper spectrum.
|
||||
* y(n) = (1-a)*y(n-1) + a*x(n) = y(n-1) + a*(x(n) - y(n-1))
|
||||
* in this a = T / (T + R*C)
|
||||
* T is sample period (e.g. 16usec)
|
||||
* RC the desired RC time: T*(1-a)/a.
|
||||
* example: a=1/256 : RC = 255*16usec = 4.08msec ( 245Hz)
|
||||
*/
|
||||
|
||||
volatile int32_t q_sample, i_sample, a_sample; // Latest sample values
|
||||
|
||||
/*** DSP engine ***/
|
||||
#if DSP_FFT == 1
|
||||
#define AGC_TOP 16383L
|
||||
#include "dsp_fft.c"
|
||||
#else
|
||||
#define AGC_TOP 1500L
|
||||
#include "dsp_tim.c"
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/** CORE1: ADC IRQ handler **/
|
||||
#define LSH 8 // Shift for higher level accuracy
|
||||
#define ADC_INT 8 // Nr of samples for integration (max 10)
|
||||
volatile int32_t adc_sample[3] = {0,0,0}; // Buffer for ADC samples
|
||||
volatile int32_t adc_result[3] = {0,0,0}; // Buffer for ADC results
|
||||
volatile uint32_t adc_level[3] = {10<<LSH,10<<LSH,10<<LSH}; // Levels for ADC channels
|
||||
volatile int adccnt = 0;
|
||||
void __not_in_flash_func(adcfifo_handler)(void)
|
||||
{
|
||||
if (++adccnt >= ADC_INT) // Nr of integration samples reached?
|
||||
{
|
||||
adc_irq_set_enabled(false); // Disable interrupts
|
||||
adc_run(false); // Stop freerunning
|
||||
}
|
||||
adc_sample[0] = adc_sample[0] + adc_fifo_get() - ADC_BIAS; // Read first three samples from FIFO
|
||||
adc_sample[1] = adc_sample[1] + adc_fifo_get() - ADC_BIAS;
|
||||
adc_sample[2] = adc_sample[2] + adc_fifo_get() - ADC_BIAS;
|
||||
adccnt++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** CORE1: Timer callback routine **/
|
||||
/*
|
||||
* CORE1:
|
||||
* Execute RX branch signal processing, max time to spend is <16us, i.e. rate is 62.5 kHz
|
||||
* No ADC sample interleaving, read both I and Q channels.
|
||||
* The delay is only 2us per conversion, which causes less distortion than interpolation of samples.
|
||||
* This runs every TIM_US , i.e. 16usec
|
||||
* First the decimation filter is applied on latest ADC results
|
||||
* The filtered samples are set aside, so a new ADC cycle can be started.
|
||||
* The ADC cycle takes 8usec to complete (3x ADC0..2 + 1x stray ADC0 conversion).
|
||||
* The timing is critical, it assumes that the ADC is finished.
|
||||
* Once every 4 TIM_US intervals signal preprocessing is done, and DSP may be invoked.
|
||||
* Do not put any other stuff in this callback routine.
|
||||
*/
|
||||
volatile int16_t i_s_raw[15], q_s_raw[15]; // Raw I/Q samples minus DC bias
|
||||
volatile uint16_t peak=0; // Peak detector running value
|
||||
volatile int16_t agc_gain=0; // AGC gain (left-shift value)
|
||||
volatile int16_t agc_accu=0; // Log peak level integrator
|
||||
volatile int16_t i_s[15], q_s[15]; // Filtered I/Q samples
|
||||
volatile int16_t i_dc, q_dc; // DC bias for I/Q channel
|
||||
volatile int rx_cnt=0; // Decimation counter
|
||||
|
||||
bool rx(void)
|
||||
semaphore_t dsp_sem;
|
||||
repeating_timer_t dsp_timer;
|
||||
volatile int cnt = 4;
|
||||
volatile int32_t rx_agc = 1, tx_agc = 1; // Factor as AGC
|
||||
bool __not_in_flash_func(dsp_callback)(repeating_timer_t *t)
|
||||
{
|
||||
int16_t q_sample, i_sample, a_sample;
|
||||
int32_t q_accu, i_accu;
|
||||
int16_t qh;
|
||||
uint16_t i;
|
||||
int16_t k;
|
||||
|
||||
/*** SAMPLING ***/
|
||||
|
||||
q_sample = adc_result[0]; // Take last ADC 0 result, connected to Q input
|
||||
i_sample = adc_result[1]; // Take last ADC 1 result, connected to I input
|
||||
int32_t temp;
|
||||
|
||||
/*
|
||||
* Remove DC and store new sample
|
||||
* IIR filter: dc = a*sample + (1-a)*dc where a = 1/128
|
||||
* Amplitude of samples should fit inside [-2048, 2047]
|
||||
* Here the rate is 15625Hz
|
||||
*/
|
||||
q_sample = (q_sample&0x0fff) - ADC_BIAS; // Clip to 12 bits and subtract mid-range
|
||||
q_dc += q_sample/128 - q_dc/128; // then IIR running average
|
||||
q_sample -= q_dc; // and subtract DC
|
||||
i_sample = (i_sample&0x0fff) - ADC_BIAS; // Same for I sample
|
||||
i_dc += i_sample/128 - i_dc/128;
|
||||
i_sample -= i_dc;
|
||||
|
||||
/*
|
||||
* Shift with AGC feedback from AUDIO GENERATION stage
|
||||
* Note: bitshift does not work with negative numbers, so need to MPY/DIV
|
||||
* This behavior in essence is exponential, complementing the logarithmic peak detector
|
||||
*/
|
||||
if (agc_gain > 0)
|
||||
// Get DC bias corrected samples
|
||||
adc_result[0] = adc_sample[0];
|
||||
adc_result[1] = adc_sample[1];
|
||||
adc_result[2] = adc_sample[2];
|
||||
|
||||
// Re-start ADCs
|
||||
while (!adc_fifo_is_empty()) adc_fifo_get(); // Empty leftovers from fifo
|
||||
adc_sample[0] = 0;
|
||||
adc_sample[1] = 0;
|
||||
adc_sample[2] = 0;
|
||||
adc_set_round_robin(0x01+0x02+0x04); // Sequence ADC 0-1-2 (GP 26, 27, 28) free running
|
||||
adc_select_input(0); // Start with ADC0
|
||||
adccnt=0; // Check for ADC FIFO interrupt overruns
|
||||
adc_run(true); // Start ADC
|
||||
adc_irq_set_enabled(true); // Enable ADC interrupts
|
||||
|
||||
// Calculate and save level, left shifted by LSH
|
||||
// a=1/1024 : RC = 1023*64usec = 65msec (15Hz)
|
||||
adc_level[0] = (1023*adc_level[0] + (ABS(adc_result[0])<<LSH))/1024;
|
||||
adc_level[1] = (1023*adc_level[1] + (ABS(adc_result[1])<<LSH))/1024;
|
||||
adc_level[2] = (1023*adc_level[2] + (ABS(adc_result[2])<<LSH))/1024;
|
||||
|
||||
// Crude AGC mechanism
|
||||
if (!tx_enabled)
|
||||
{
|
||||
q_sample = q_sample * (1<<agc_gain);
|
||||
i_sample = i_sample * (1<<agc_gain);
|
||||
}
|
||||
else if (agc_gain < 0)
|
||||
{
|
||||
q_sample = q_sample / (1<<(-agc_gain));
|
||||
i_sample = i_sample / (1<<(-agc_gain));
|
||||
temp = (MAX(adc_level[1], adc_level[0]))>>LSH; // Max I or Q
|
||||
rx_agc = (temp==0) ? AGC_TOP : AGC_TOP/temp; // calculate required AGC factor
|
||||
}
|
||||
|
||||
/*
|
||||
* Shift-in I and Q raw samples
|
||||
*/
|
||||
for (i=0; i<14; i++)
|
||||
#if DSP_FFT == 1
|
||||
|
||||
if (tx_enabled)
|
||||
{
|
||||
q_s_raw[i] = q_s_raw[i+1]; // Q raw samples shift register
|
||||
i_s_raw[i] = i_s_raw[i+1]; // I raw samples shift register
|
||||
A_buf[dsp_active][dsp_tick] = (int16_t)(tx_agc*adc_result[2]);
|
||||
pwm_set_gpio_level(21, I_buf[dsp_active][dsp_tick] + DAC_BIAS); // Output I to DAC
|
||||
pwm_set_gpio_level(20, Q_buf[dsp_active][dsp_tick] + DAC_BIAS); // Output Q to DAC
|
||||
}
|
||||
q_s_raw[14] = q_sample; // Store in shift registers
|
||||
i_s_raw[14] = i_sample;
|
||||
|
||||
|
||||
/*
|
||||
* Low pass filter + decimation
|
||||
*/
|
||||
rx_cnt = (rx_cnt+1)&3; // Calculate only every fourth sample
|
||||
if (rx_cnt>0) return (true); // So net sample time is 64us or 15.625 kHz
|
||||
|
||||
for (i=0; i<14; i++) // Shift decimated samples
|
||||
else
|
||||
{
|
||||
q_s[i] = q_s[i+1];
|
||||
i_s[i] = i_s[i+1];
|
||||
}
|
||||
q_accu = 0; // Initialize accumulators
|
||||
i_accu = 0;
|
||||
for (i=0; i<15; i++) // Low pass FIR filter
|
||||
{
|
||||
q_accu += (int32_t)q_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz raw sampling
|
||||
i_accu += (int32_t)i_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz raw sampling
|
||||
}
|
||||
q_accu = q_accu/256;
|
||||
i_accu = i_accu/256;
|
||||
|
||||
q_s[14] = q_accu;
|
||||
i_s[14] = i_accu;
|
||||
|
||||
|
||||
/*** DEMODULATION ***/
|
||||
switch (dsp_mode)
|
||||
{
|
||||
case 0: //USB
|
||||
/*
|
||||
* USB demodulate: I[7] - Qh,
|
||||
* Qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (q_s[0]-q_s[14])*315L + (q_s[2]-q_s[12])*440L + (q_s[4]-q_s[10])*734L + (q_s[6]-q_s[ 8])*2202L;
|
||||
qh = q_accu / 4096L;
|
||||
a_sample = i_s[7] - qh;
|
||||
break;
|
||||
case 1: //LSB
|
||||
/*
|
||||
* LSB demodulate: I[7] + Qh,
|
||||
* Qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (q_s[0]-q_s[14])*315L + (q_s[2]-q_s[12])*440L + (q_s[4]-q_s[10])*734L + (q_s[6]-q_s[ 8])*2202L;
|
||||
qh = q_accu / 4096L;
|
||||
a_sample = i_s[7] + qh;
|
||||
break;
|
||||
case 2: //AM
|
||||
/*
|
||||
* AM demodulate: sqrt(sqr(i)+sqr(q))
|
||||
* Approximated with MAG(i,q)
|
||||
*/
|
||||
a_sample = MAG(i_s[14], q_s[14]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
I_buf[dsp_active][dsp_tick] = (int16_t)(rx_agc*adc_result[1]);
|
||||
Q_buf[dsp_active][dsp_tick] = (int16_t)(rx_agc*adc_result[0]);
|
||||
pwm_set_gpio_level(22, A_buf[dsp_active][dsp_tick] + DAC_BIAS); // Output A to DAC
|
||||
}
|
||||
|
||||
/*** AUDIO GENERATION ***/
|
||||
/*
|
||||
* AGC, peak detector
|
||||
* Sample speed is 15625 per second
|
||||
*/
|
||||
peak += (ABS(a_sample))/128 - peak/128; // Running average level detect, a=1/128
|
||||
k=0; i=peak; // Logarithmic peak detection
|
||||
if (i&0xff00) {k+=8; i>>=8;} // k=log2(peak), find highest bit set
|
||||
if (i&0x00f0) {k+=4; i>>=4;}
|
||||
if (i&0x000c) {k+=2; i>>=2;}
|
||||
if (i&0x0002) {k+=1;}
|
||||
agc_accu += (k - AGC_REF); // Add difference with target to integrator (Acc += Xn - R)
|
||||
if (agc_accu > agc_attack) // Attack time, gain correction in case of high level
|
||||
// When sample buffer is full, move pointer and signal DSP loop
|
||||
if (++dsp_tick >= BUFSIZE) // Increment tick and check range
|
||||
{
|
||||
agc_gain--; // Decrease gain
|
||||
agc_accu -= agc_attack; // Reset integrator
|
||||
} else if (agc_accu < -(agc_decay)) // Decay time, gain correction in case of low level
|
||||
{
|
||||
agc_gain++; // Increase gain
|
||||
agc_accu += agc_decay; // Reset integrator
|
||||
dsp_tick = 0; // Reset counter
|
||||
if (++dsp_active > 2) dsp_active = 0; // Rotate offset
|
||||
dsp_overrun++; // Increment overrun counter
|
||||
sem_release(&dsp_sem); // Signal DSP loop semaphore
|
||||
}
|
||||
|
||||
/*
|
||||
* Scale and clip output,
|
||||
* Send to audio DAC output
|
||||
*/
|
||||
a_sample += DAC_BIAS; // Add bias level
|
||||
if (a_sample > DAC_RANGE) // Clip to DAC range
|
||||
a_sample = DAC_RANGE;
|
||||
else if (a_sample<0)
|
||||
a_sample = 0;
|
||||
pwm_set_chan_level(dac_audio, PWM_CHAN_A, a_sample);
|
||||
#else
|
||||
|
||||
if (tx_enabled)
|
||||
{
|
||||
a_sample = tx_agc * adc_result[2]; // Store A for DSP use
|
||||
pwm_set_gpio_level(21, i_sample); // Output I to DAC
|
||||
pwm_set_gpio_level(20, q_sample); // Output Q to DAC
|
||||
}
|
||||
else
|
||||
{
|
||||
pwm_set_gpio_level(22, a_sample); // Output Q to DAC
|
||||
q_sample = rx_agc * adc_result[0]; // Store Q for DSP use
|
||||
i_sample = rx_agc * adc_result[1]; // Store I for DSP use
|
||||
}
|
||||
dsp_overrun++; // Increment overrun counter
|
||||
sem_release(&dsp_sem); // Signal DSP loop semaphore
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CORE1:
|
||||
* The VOX function is called separately every cycle, to check audio level.
|
||||
* Execute TX branch signal processing when tx enabled
|
||||
*/
|
||||
volatile int16_t a_s_raw[15]; // Raw samples, minus DC bias
|
||||
volatile int16_t a_level=0; // Average level of raw sample stream
|
||||
volatile int16_t a_s[15]; // Filtered and decimated samples
|
||||
volatile int16_t a_dc; // DC level
|
||||
volatile int tx_cnt=0; // Decimation counter
|
||||
|
||||
bool vox(void)
|
||||
{
|
||||
int16_t a_sample;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Get sample and shift into delay line
|
||||
*/
|
||||
a_sample = adc_result[2]; // Get latest ADC 2 result
|
||||
|
||||
|
||||
/*
|
||||
* Remove DC and store new raw sample
|
||||
* IIR filter: dc = a*sample + (1-a)*dc where a = 1/128
|
||||
*/
|
||||
a_sample = (a_sample&0x0fff) - ADC_BIAS; // Clip and subtract mid-range
|
||||
a_dc += (a_sample - a_dc)/128; // then IIR running average
|
||||
a_sample -= a_dc; // subtract DC
|
||||
for (i=0; i<14; i++) // and store in shift register
|
||||
a_s_raw[i] = a_s_raw[i+1];
|
||||
a_s_raw[14] = a_sample;
|
||||
|
||||
/*
|
||||
* Detect level of audio signal
|
||||
* Return true if VOX enabled and:
|
||||
* - Audio level higher than threshold
|
||||
* - Linger time sill active
|
||||
*/
|
||||
if (a_sample<0) a_sample = -a_sample; // Absolute value
|
||||
a_level += (a_sample - a_level)/128; // running average, 16usec * 128 = 2msec
|
||||
|
||||
if (vox_level != VOX_OFF) // Only when VOX is enabled
|
||||
{
|
||||
if (a_level > vox_level)
|
||||
{
|
||||
vox_count = VOX_LINGER; // While audio present, reset linger timer
|
||||
return(true); // and keep TX active
|
||||
}
|
||||
if (vox_count>0)
|
||||
{
|
||||
vox_count--; // No audio; decrement linger timer
|
||||
return(true); // but keep TX active
|
||||
}
|
||||
}
|
||||
return(false); // All other cases: no TX
|
||||
}
|
||||
|
||||
bool tx(void)
|
||||
{
|
||||
int32_t a_accu, q_accu;
|
||||
int16_t qh;
|
||||
int i;
|
||||
uint16_t i_dac, q_dac;
|
||||
|
||||
/*** RAW Audio SAMPLES from VOX function ***/
|
||||
/*** Low pass filter + decimation ***/
|
||||
tx_cnt = (tx_cnt+1)&3; // Calculate only every fourth sample
|
||||
if (tx_cnt>0) return true; // So effective sample rate will be 15625Hz
|
||||
|
||||
a_accu = 0; // Initialize accumulator
|
||||
for (i=0; i<15; i++) // Low pass FIR filter, using raw samples
|
||||
a_accu += (int32_t)a_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz sampling
|
||||
|
||||
for (i=0; i<14; i++) // Shift decimated samples
|
||||
a_s[i] = a_s[i+1];
|
||||
a_s[14] = a_accu / 256; // Store rescaled accumulator
|
||||
|
||||
/*** MODULATION ***/
|
||||
switch (dsp_mode)
|
||||
{
|
||||
case 0: // USB
|
||||
/*
|
||||
* qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (a_s[0]-a_s[14])*315L + (a_s[2]-a_s[12])*440L + (a_s[4]-a_s[10])*734L + (a_s[6]-a_s[ 8])*2202L;
|
||||
qh = -(q_accu / 4096L); // USB: sign is negative
|
||||
break;
|
||||
case 1: // LSB
|
||||
/*
|
||||
* qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (a_s[0]-a_s[14])*315L + (a_s[2]-a_s[12])*440L + (a_s[4]-a_s[10])*734L + (a_s[6]-a_s[ 8])*2202L;
|
||||
qh = q_accu / 4096L; // LSB: sign is positive
|
||||
break;
|
||||
case 2: // AM
|
||||
/*
|
||||
* I and Q values are identical
|
||||
*/
|
||||
qh = a_s[7];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write I and Q to QSE DACs, phase is 7 samples back.
|
||||
* Need to multiply AC with DAC_RANGE/ADC_RANGE (appr 1/8)
|
||||
* Any case: clip to range
|
||||
*/
|
||||
a_accu = DAC_BIAS - (qh/8);
|
||||
if (a_accu<0)
|
||||
q_dac = 0;
|
||||
else if (a_accu>(DAC_RANGE-1))
|
||||
q_dac = DAC_RANGE-1;
|
||||
else
|
||||
q_dac = a_accu;
|
||||
|
||||
a_accu = DAC_BIAS + (a_s[7]/8);
|
||||
if (a_accu<0)
|
||||
i_dac = 0;
|
||||
else if (a_accu>(DAC_RANGE-1))
|
||||
i_dac = DAC_RANGE-1;
|
||||
else
|
||||
i_dac = a_accu;
|
||||
|
||||
// pwm_set_both_levels(dac_iq, q_dac, i_dac); // Set both channels of the IQ slice simultaneously
|
||||
// pwm_set_chan_level(dac_iq, PWM_CHAN_A, q_dac);
|
||||
// pwm_set_chan_level(dac_iq, PWM_CHAN_B, i_dac);
|
||||
pwm_set_gpio_level(21, i_dac);
|
||||
pwm_set_gpio_level(20, q_dac);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CORE1:
|
||||
* Timing loop, triggered through inter-core fifo
|
||||
*/
|
||||
void dsp_loop()
|
||||
/** CORE1: DSP loop, triggered through repeating timer/semaphore **/
|
||||
void __not_in_flash_func(dsp_loop)()
|
||||
{
|
||||
uint32_t cmd;
|
||||
uint16_t slice_num;
|
||||
alarm_pool_t *ap;
|
||||
|
||||
tx_enabled = false;
|
||||
fifo_overrun = 0;
|
||||
fifo_rx = 0;
|
||||
fifo_tx = 0;
|
||||
fifo_xx = 0;
|
||||
fifo_incnt++;
|
||||
vox_active = false;
|
||||
|
||||
/* Initialize DACs, default mode is free running, A and B pins are output */
|
||||
/*
|
||||
* Initialize DACs,
|
||||
* default mode is free running,
|
||||
* A and B pins are output
|
||||
*/
|
||||
gpio_set_function(20, GPIO_FUNC_PWM); // GP20 is PWM for Q DAC (Slice 2, Channel A)
|
||||
gpio_set_function(21, GPIO_FUNC_PWM); // GP21 is PWM for I DAC (Slice 2, Channel B)
|
||||
dac_iq = pwm_gpio_to_slice_num(20); // Get PWM slice for GP20 (Same for GP21)
|
||||
pwm_set_clkdiv_int_frac (dac_iq, 1, 0); // clock divide by 1: 125MHz
|
||||
pwm_set_clkdiv_int_frac (dac_iq, 1, 0); // clock divide by 1: full system clock
|
||||
pwm_set_wrap(dac_iq, DAC_RANGE-1); // Set cycle length; nr of counts until wrap, i.e. 125/DAC_RANGE MHz
|
||||
pwm_set_enabled(dac_iq, true); // Set the PWM running
|
||||
|
||||
gpio_set_function(22, GPIO_FUNC_PWM); // GP22 is PWM for Audio DAC (Slice 3, Channel A)
|
||||
dac_audio = pwm_gpio_to_slice_num(22); // Find PWM slice for GP22
|
||||
pwm_set_clkdiv_int_frac (dac_audio, 1, 0); // clock divide by 1: 125MHz
|
||||
pwm_set_clkdiv_int_frac (dac_audio, 1, 0); // clock divide by 1: full system clock
|
||||
pwm_set_wrap(dac_audio, DAC_RANGE-1); // Set cycle length; nr of counts until wrap, i.e. 125/DAC_RANGE MHz
|
||||
pwm_set_enabled(dac_audio, true); // Set the PWM running
|
||||
|
||||
/* Initialize ADCs */
|
||||
/*
|
||||
* Initialize ADCs, use in round robin mode (3 channels)
|
||||
* samples are stored in array through IRQ callback
|
||||
*/
|
||||
adc_init(); // Initialize ADC to known state
|
||||
adc_set_clkdiv(0); // Fastest clock (500 kSps)
|
||||
adc_set_clkdiv(0.0); // Fastest clock (500 kSps)
|
||||
adc_gpio_init(26); // GP26 is ADC 0 for Q channel
|
||||
adc_gpio_init(27); // GP27 is ADC 1 for I channel
|
||||
adc_gpio_init(28); // GP28 is ADC 2 for Audio channel
|
||||
adc_select_input(0); // Start with ADC0
|
||||
adc_next = 0;
|
||||
|
||||
adc_set_round_robin(0x01+0x02+0x04); // Sequence ADC 0-1-2 (GP 26, 27, 28) free running
|
||||
adc_fifo_setup(true,false,1,false,false); // IRQ for every result (fifo threshold = 1)
|
||||
irq_set_exclusive_handler(ADC0_IRQ_FIFO, adcfifo_handler);
|
||||
adc_irq_set_enabled(true);
|
||||
irq_set_enabled(ADC0_IRQ_FIFO, true);
|
||||
adc_select_input(0); // Start with ADC0
|
||||
adc_fifo_setup(true,false,3,false,false); // IRQ result, fifo threshold = 1, so IRQ after each ADC0..2
|
||||
irq_set_exclusive_handler(ADC_IRQ_FIFO, adcfifo_handler); // Install ISR at ADC_IRQ_FIFO vector (22)
|
||||
irq_set_priority (ADC_IRQ_FIFO, PICO_HIGHEST_IRQ_PRIORITY); // Prevent race condition with timer
|
||||
irq_set_enabled(ADC_IRQ_FIFO, true); // Enable interrupt vector in NVIC
|
||||
adc_irq_set_enabled(true); // Enable the ADC FIFO interrupt
|
||||
adc_run(true);
|
||||
adc_level[0] = ADC_BIAS/2;
|
||||
adc_level[1] = ADC_BIAS/2;
|
||||
adc_level[2] = ADC_BIAS/2;
|
||||
|
||||
// Consider using alarm_pool_add_repeating_timer_us() for a core1 associated timer
|
||||
// First create an alarm pool on core1: alarm_pool_create(HWalarm, Ntimers)
|
||||
// For the core1 alarm pool don't use default HWalarm (usually 3) but e.g. 1
|
||||
// Timer callback signals semaphore, while loop blocks on getting it
|
||||
// Use alarm_pool_add_repeating_timer_us() for a core1 associated timer
|
||||
// First create an alarm pool on core1:
|
||||
// alarm_pool_t *alarm_pool_create( uint hardware_alarm_num,
|
||||
// uint max_timers);
|
||||
// For the core1 alarm pool don't use the default alarm_num (usually 3) but e.g. 1
|
||||
// Timer callback signals semaphore, while loop blocks on getting it.
|
||||
// Initialize repeating timer on core1:
|
||||
// bool alarm_pool_add_repeating_timer_us( alarm_pool_t *pool,
|
||||
// int64_t delay_us,
|
||||
// repeating_timer_callback_t callback,
|
||||
// void *user_data,
|
||||
// repeating_timer_t *out);
|
||||
|
||||
sem_init(&dsp_sem, 0, 1);
|
||||
ap = alarm_pool_create(1, 4);
|
||||
alarm_pool_add_repeating_timer_us( ap, -TIM_US, dsp_callback, NULL, &dsp_timer);
|
||||
|
||||
dsp_overrun = 0;
|
||||
|
||||
while(1)
|
||||
{
|
||||
cmd = multicore_fifo_pop_blocking(); // Wait for fifo output
|
||||
sem_acquire_blocking(&dsp_sem); // Wait until timer callback releases sem
|
||||
dsp_overrun--; // Decrement overrun counter
|
||||
|
||||
tx_enabled = vox() || ptt_active; // Sample audio and check level
|
||||
if (tx_enabled)
|
||||
// Use adc_level[2] for VOX
|
||||
vox_active = false; // Normally false
|
||||
if (vox_level != 0) // Only when VOX is enabled
|
||||
{
|
||||
if (vox_level != VOX_OFF) // Only when vox is enabled
|
||||
gpio_put(GP_PTT, false); // drive PTT low (active)
|
||||
tx();
|
||||
if ((adc_level[2]>>LSH) > vox_level) // AND level > limit
|
||||
{
|
||||
vox_count = S_RATE * VOX_LINGER / 1000; // While audio present, reset linger timer
|
||||
vox_active = true; // and keep TX active
|
||||
}
|
||||
else if (--vox_count>0) // else decrement linger counter
|
||||
vox_active = true; // and keep TX active until 0
|
||||
|
||||
}
|
||||
|
||||
if (tx_enabled) // Use previous setting
|
||||
{
|
||||
gpio_put(GP_PTT, false); // Drive PTT low (active)
|
||||
tx(); // Do TX signal processing
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vox_level != VOX_OFF) // Only when vox is enabled
|
||||
gpio_put(GP_PTT, true); // drive PTT high (inactive)
|
||||
rx();
|
||||
gpio_put(GP_PTT, true); // Drive PTT high (inactive)
|
||||
rx(); // Do RX signal processing
|
||||
}
|
||||
if (multicore_fifo_rvalid())
|
||||
fifo_overrun++; // Check for missed events
|
||||
|
||||
tx_enabled = vox_active || ptt_active; // Check RX or TX
|
||||
|
||||
#if DSP_FFT == 1
|
||||
dsp_tickx = dsp_tick;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CORE0:
|
||||
* Timer callback, triggers core1 through inter-core fifo.
|
||||
* Either TX or RX, but could do both when testing in loopback on I+Q channels.
|
||||
*/
|
||||
struct repeating_timer dsp_timer;
|
||||
bool dsp_callback(struct repeating_timer *t)
|
||||
{
|
||||
if (tx_enabled)
|
||||
multicore_fifo_push_blocking(DSP_TX); // Send TX to core 1 through fifo
|
||||
else
|
||||
multicore_fifo_push_blocking(DSP_RX); // Send RX to core 1 through fifo
|
||||
fifo_incnt++;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CORE0:
|
||||
* Initialize dsp context and spawn CORE1 process
|
||||
*
|
||||
* Some CORE1 code parts should not run from Flash, but be loaded in SRAM at boot time
|
||||
* See platform.h for function qualifier macro's
|
||||
* for example:
|
||||
* void __not_in_flash_func(funcname)(int arg1, float arg2)
|
||||
* {
|
||||
* }
|
||||
*
|
||||
* Need to set BUS_PRIORITY of Core 1 to high
|
||||
* #include bus_ctrl.h
|
||||
* bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_PROC1_BITS; // Set Core 1 prio high
|
||||
*/
|
||||
/** CORE0: Initialize dsp context and spawn CORE1 process **/
|
||||
void dsp_init()
|
||||
{
|
||||
multicore_launch_core1(dsp_loop); // Start processing on core1
|
||||
add_repeating_timer_us(-DSP_US, dsp_callback, NULL, &dsp_timer);
|
||||
bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_PROC1_BITS; // Set Core 1 prio on bus to high
|
||||
multicore_launch_core1(dsp_loop); // Start processing on Core 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* DMA EXAMPLE, should convert to chained DMA to reload after 3 words
|
||||
// Init GPIO for analogue use: hi-Z, no pulls, disable digital input buffer.
|
||||
adc_gpio_init(26 + CAPTURE_CHANNEL);
|
||||
|
||||
adc_init();
|
||||
adc_select_input(CAPTURE_CHANNEL);
|
||||
adc_fifo_setup(
|
||||
true, // Write each completed conversion to the sample FIFO
|
||||
true, // Enable DMA data request (DREQ)
|
||||
1, // DREQ (and IRQ) asserted when at least 1 sample present
|
||||
false, // We won't see the ERR bit because of 8 bit reads; disable.
|
||||
true // Shift each sample to 8 bits when pushing to FIFO
|
||||
);
|
||||
|
||||
// Divisor of 0 -> full speed. Free-running capture with the divider is
|
||||
// equivalent to pressing the ADC_CS_START_ONCE button once per `div + 1`
|
||||
// cycles (div not necessarily an integer). Each conversion takes 96
|
||||
// cycles, so in general you want a divider of 0 (hold down the button
|
||||
// continuously) or > 95 (take samples less frequently than 96 cycle
|
||||
// intervals). This is all timed by the 48 MHz ADC clock.
|
||||
adc_set_clkdiv(0);
|
||||
|
||||
printf("Arming DMA\n");
|
||||
sleep_ms(1000);
|
||||
// Set up the DMA to start transferring data as soon as it appears in FIFO
|
||||
uint dma_chan = dma_claim_unused_channel(true);
|
||||
dma_channel_config cfg = dma_channel_get_default_config(dma_chan);
|
||||
|
||||
// Reading from constant address, writing to incrementing byte addresses
|
||||
channel_config_set_transfer_data_size(&cfg, DMA_SIZE_8);
|
||||
channel_config_set_read_increment(&cfg, false);
|
||||
channel_config_set_write_increment(&cfg, true);
|
||||
|
||||
// Pace transfers based on availability of ADC samples
|
||||
channel_config_set_dreq(&cfg, DREQ_ADC);
|
||||
|
||||
dma_channel_configure(dma_chan, &cfg,
|
||||
capture_buf, // dst
|
||||
&adc_hw->fifo, // src
|
||||
CAPTURE_DEPTH, // transfer count
|
||||
true // start immediately
|
||||
);
|
||||
|
||||
printf("Starting capture\n");
|
||||
adc_run(true);
|
||||
|
||||
// Once DMA finishes, stop any new conversions from starting, and clean up
|
||||
// the FIFO in case the ADC was still mid-conversion.
|
||||
dma_channel_wait_for_finish_blocking(dma_chan);
|
||||
printf("Capture finished\n");
|
||||
adc_run(false);
|
||||
adc_fifo_drain();
|
||||
|
||||
// Print samples to stdout so you can display them in pyplot, excel, matlab
|
||||
for (int i = 0; i < CAPTURE_DEPTH; ++i) {
|
||||
printf("%-3d, ", capture_buf[i]);
|
||||
if (i % 10 == 9)
|
||||
printf("\n");
|
||||
}
|
||||
*/
|
||||
|
|
51
dsp.h
51
dsp.h
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef __FIX_FFT_H__
|
||||
#define __FIX_FFT_H__
|
||||
/*
|
||||
* fix_fft.h
|
||||
*
|
||||
* Created: Apr 2022
|
||||
* Author: Arjan te Marvelde
|
||||
*
|
||||
* See fix_fft.c for more information
|
||||
*/
|
||||
|
||||
#define FFT_SIZE 1024 // Use this for buffer allocations
|
||||
#define FFT_ORDER 10 // FFT_SIZE = 1 << FFT_ORDER
|
||||
|
||||
int fix_fft(int16_t *fr, int16_t *fi, bool inverse);
|
||||
|
||||
#endif
|
140
hmi.c
140
hmi.c
|
@ -100,25 +100,30 @@
|
|||
#define HMI_NEVENTS 9
|
||||
|
||||
/* Sub menu option string sets */
|
||||
#define HMI_NTUNE 6
|
||||
#define HMI_NMODE 4
|
||||
#define HMI_NAGC 3
|
||||
#define HMI_NPRE 5
|
||||
#define HMI_NVOX 4
|
||||
#define HMI_NBPF 5
|
||||
char hmi_noption[HMI_NSTATES] = {HMI_NTUNE, HMI_NMODE, HMI_NAGC, HMI_NPRE, HMI_NVOX, HMI_NBPF};
|
||||
char hmi_o_menu[HMI_NSTATES][8] = {"Tune","Mode","AGC","Pre","VOX"}; // Indexed by hmi_state
|
||||
char hmi_o_mode[HMI_NMODE][8] = {"USB","LSB","AM","CW"}; // Indexed by hmi_sub[HMI_S_MODE]
|
||||
char hmi_o_mode[HMI_NMODE][8] = {"USB","LSB","AM ","CW "}; // Indexed by hmi_sub[HMI_S_MODE]
|
||||
char hmi_o_agc [HMI_NAGC][8] = {"NoGC","Slow","Fast"}; // Indexed by hmi_sub[HMI_S_AGC]
|
||||
char hmi_o_pre [HMI_NPRE][8] = {"-30dB","-20dB","-10dB","0dB","+10dB"}; // Indexed by hmi_sub[HMI_S_PRE]
|
||||
char hmi_o_vox [HMI_NVOX][8] = {"NoVOX","VOX-L","VOX-M","VOX-H"}; // Indexed by hmi_sub[HMI_S_VOX]
|
||||
char hmi_o_bpf [HMI_NBPF][8] = {"<2.5","2-6","5-12","10-24","20-40"};
|
||||
char hmi_o_bpf [HMI_NBPF][8] = {"<2.5","2-6","5-12","10-24","20-40"}; // Indexed by
|
||||
|
||||
// Map option to setting
|
||||
uint8_t hmi_pre[5] = {REL_ATT_30, REL_ATT_20, REL_ATT_10, REL_ATT_00, REL_PRE_10};
|
||||
uint8_t hmi_bpf[5] = {REL_LPF2, REL_BPF6, REL_BPF12, REL_BPF24, REL_BPF40};
|
||||
int hmi_mode[4] = {MODE_USB, MODE_LSB, MODE_AM, MODE_CW};
|
||||
int hmi_agc[3] = {AGC_NONE, AGC_SLOW, AGC_FAST};
|
||||
int hmi_pre[5] = {REL_ATT_30, REL_ATT_20, REL_ATT_10, REL_ATT_00, REL_PRE_10};
|
||||
int hmi_vox[4] = {VOX_OFF, VOX_LOW, VOX_MEDIUM, VOX_HIGH};
|
||||
int hmi_bpf[5] = {REL_LPF2, REL_BPF6, REL_BPF12, REL_BPF24, REL_BPF40};
|
||||
|
||||
uint8_t hmi_state, hmi_option; // Current state and option selection
|
||||
uint8_t hmi_sub[HMI_NSTATES] = {4,0,0,3,0,2}; // Stored option selection per state
|
||||
bool hmi_update;
|
||||
int hmi_state, hmi_option; // Current state and menu option selection
|
||||
int hmi_sub[HMI_NSTATES] = {4,0,0,3,0,2}; // Stored option selection per state
|
||||
bool hmi_update; // LCD needs update
|
||||
|
||||
uint32_t hmi_freq; // Frequency from Tune state
|
||||
uint32_t hmi_step[6] = {10000000, 1000000, 100000, 10000, 1000, 100}; // Frequency digit increments
|
||||
|
@ -151,92 +156,59 @@ void hmi_handler(uint8_t event)
|
|||
/* Special case for TUNE state */
|
||||
if (hmi_state == HMI_S_TUNE)
|
||||
{
|
||||
if (event==HMI_E_ENTER) // Commit current value
|
||||
{
|
||||
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Commit frequency
|
||||
}
|
||||
if (event==HMI_E_ESCAPE) // Enter submenus
|
||||
switch (event)
|
||||
{
|
||||
case HMI_E_ENTER: // Commit current value
|
||||
SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Commit frequency
|
||||
break;
|
||||
case HMI_E_ESCAPE: // Enter submenus
|
||||
hmi_sub[hmi_state] = hmi_option; // Store selection (i.e. digit)
|
||||
hmi_state = HMI_S_MODE; // Should remember last one
|
||||
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
|
||||
}
|
||||
if (event==HMI_E_INCREMENT)
|
||||
{
|
||||
break;
|
||||
case HMI_E_INCREMENT:
|
||||
if (hmi_freq < (HMI_MAXFREQ - hmi_step[hmi_option])) // Boundary check
|
||||
hmi_freq += hmi_step[hmi_option]; // Increment selected digit
|
||||
}
|
||||
if (event==HMI_E_DECREMENT)
|
||||
{
|
||||
if (hmi_freq > (hmi_step[hmi_option] + HMI_MINFREQ)) // Boundary check
|
||||
break;
|
||||
case HMI_E_DECREMENT:
|
||||
if (hmi_freq > (HMI_MINFREQ + hmi_step[hmi_option])) // Boundary check
|
||||
hmi_freq -= hmi_step[hmi_option]; // Decrement selected digit
|
||||
}
|
||||
if (event==HMI_E_RIGHT)
|
||||
{
|
||||
hmi_option = (hmi_option<6)?hmi_option+1:6; // Digit to the right
|
||||
}
|
||||
if (event==HMI_E_LEFT)
|
||||
{
|
||||
break;
|
||||
case HMI_E_RIGHT:
|
||||
hmi_option = (hmi_option<5)?hmi_option+1:5; // Digit to the right
|
||||
break;
|
||||
case HMI_E_LEFT:
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0; // Digit to the left
|
||||
break;
|
||||
}
|
||||
return; // Early bail-out
|
||||
}
|
||||
|
||||
/* Submenu states */
|
||||
switch(hmi_state)
|
||||
/* Actions for other states */
|
||||
switch (event)
|
||||
{
|
||||
case HMI_S_MODE:
|
||||
if (event==HMI_E_INCREMENT)
|
||||
hmi_option = (hmi_option<HMI_NMODE-1)?hmi_option+1:HMI_NMODE-1;
|
||||
if (event==HMI_E_DECREMENT)
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
case HMI_E_ENTER:
|
||||
hmi_sub[hmi_state] = hmi_option; // Store value for selected option
|
||||
hmi_update = true; // Mark HMI updated: activate value
|
||||
break;
|
||||
case HMI_S_AGC:
|
||||
if (event==HMI_E_INCREMENT)
|
||||
hmi_option = (hmi_option<HMI_NAGC-1)?hmi_option+1:HMI_NAGC-1;
|
||||
if (event==HMI_E_DECREMENT)
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
break;
|
||||
case HMI_S_PRE:
|
||||
if (event==HMI_E_INCREMENT)
|
||||
hmi_option = (hmi_option<HMI_NPRE-1)?hmi_option+1:HMI_NPRE-1;
|
||||
if (event==HMI_E_DECREMENT)
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
break;
|
||||
case HMI_S_VOX:
|
||||
if (event==HMI_E_INCREMENT)
|
||||
hmi_option = (hmi_option<HMI_NVOX-1)?hmi_option+1:HMI_NVOX-1;
|
||||
if (event==HMI_E_DECREMENT)
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
break;
|
||||
case HMI_S_BPF:
|
||||
if (event==HMI_E_INCREMENT)
|
||||
hmi_option = (hmi_option<HMI_NBPF-1)?hmi_option+1:HMI_NBPF-1;
|
||||
if (event==HMI_E_DECREMENT)
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* General actions for submenus */
|
||||
if (event==HMI_E_ENTER)
|
||||
{
|
||||
hmi_sub[hmi_state] = hmi_option; // Store selected option
|
||||
hmi_update = true; // Mark HMI updated
|
||||
}
|
||||
if (event==HMI_E_ESCAPE)
|
||||
{
|
||||
case HMI_E_ESCAPE:
|
||||
hmi_state = HMI_S_TUNE; // Leave submenus
|
||||
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
|
||||
}
|
||||
if (event==HMI_E_RIGHT)
|
||||
{
|
||||
break;
|
||||
case HMI_E_RIGHT:
|
||||
hmi_state = (hmi_state<HMI_NSTATES-1)?(hmi_state+1):1; // Change submenu
|
||||
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
|
||||
}
|
||||
if (event==HMI_E_LEFT)
|
||||
{
|
||||
break;
|
||||
case HMI_E_LEFT:
|
||||
hmi_state = (hmi_state>1)?(hmi_state-1):HMI_NSTATES-1; // Change submenu
|
||||
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
|
||||
break;
|
||||
case HMI_E_INCREMENT:
|
||||
hmi_option = (hmi_option<hmi_noption[hmi_state]-1)?hmi_option+1:hmi_noption[hmi_state]-1;
|
||||
break;
|
||||
case HMI_E_DECREMENT:
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,6 +273,7 @@ void hmi_init(void)
|
|||
gpio_pull_up(GP_AUX_2);
|
||||
gpio_pull_up(GP_AUX_3);
|
||||
gpio_pull_up(GP_PTT);
|
||||
gpio_set_oeover(GP_PTT, GPIO_OVERRIDE_HIGH); // Enable output on PTT GPIO; bidirectional
|
||||
|
||||
// Enable interrupt on level low
|
||||
gpio_set_irq_enabled(GP_ENC_A, GPIO_IRQ_EDGE_ALL, true);
|
||||
|
@ -318,10 +291,10 @@ void hmi_init(void)
|
|||
hmi_option = 4; // Active kHz digit
|
||||
hmi_freq = 7074000UL; // Initial frequency
|
||||
|
||||
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Set freq to 7074 kHz (depends on mixer type)
|
||||
SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Set freq to 7074 kHz (depends on mixer type)
|
||||
SI_SETPHASE(0, 1); // Set phase to 90deg (depends on mixer type)
|
||||
|
||||
ptt_state = 0;
|
||||
ptt_state = PTT_DEBOUNCE;
|
||||
ptt_active = false;
|
||||
|
||||
dsp_setmode(hmi_sub[HMI_S_MODE]);
|
||||
|
@ -381,9 +354,6 @@ void hmi_evaluate(void)
|
|||
}
|
||||
|
||||
/* PTT debouncing */
|
||||
if (hmi_sub[HMI_S_VOX] == 0) // No VOX active
|
||||
{
|
||||
gpio_set_dir(GP_PTT, false); // PTT input
|
||||
if (gpio_get(GP_PTT)) // Get PTT level
|
||||
{
|
||||
if (ptt_state<PTT_DEBOUNCE) // Increment debounce counter when high
|
||||
|
@ -398,19 +368,11 @@ void hmi_evaluate(void)
|
|||
ptt_active = false;
|
||||
if (ptt_state == 0) // Set PTT when debounced level low
|
||||
ptt_active = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptt_active = false;
|
||||
gpio_set_dir(GP_PTT, true); // PTT output
|
||||
}
|
||||
|
||||
|
||||
/* Set parameters corresponding to latest entered option value */
|
||||
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq);
|
||||
dsp_setmode(hmi_sub[HMI_S_MODE]);
|
||||
dsp_setvox(hmi_sub[HMI_S_VOX]);
|
||||
dsp_setagc(hmi_sub[HMI_S_AGC]);
|
||||
if (hmi_update)
|
||||
SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Always set frequency
|
||||
if (hmi_update) // Others only when indicated
|
||||
{
|
||||
dsp_setmode(hmi_sub[HMI_S_MODE]);
|
||||
dsp_setvox(hmi_sub[HMI_S_VOX]);
|
||||
|
|
2
hmi.h
2
hmi.h
|
@ -9,6 +9,8 @@
|
|||
* See hmi.c for more information
|
||||
*/
|
||||
|
||||
#define CARRIER_OFFSET 3500
|
||||
|
||||
extern bool ptt_active;
|
||||
|
||||
void hmi_init(void);
|
||||
|
|
28
lcd.c
28
lcd.c
|
@ -7,13 +7,20 @@
|
|||
* --> Set I2C address below!
|
||||
* --> Select LCD_TYPE below!
|
||||
*
|
||||
* Driver for 16x2 HD44780 based LCD displays.
|
||||
* There exist many different types, so you may need to adapt some of the code.
|
||||
* Most notably the startup sequence and the way bytes are sent over the I2C interface.
|
||||
* But also the register mappings as described below.
|
||||
*
|
||||
* LCD_1804:
|
||||
* ---------
|
||||
* Grove 16x2 LCD HD44780, with integrated JHD1804 I2C bridge (@ 0x3E)
|
||||
* 2 byte interface,
|
||||
* byte0 contains coomand/data,
|
||||
* byte1 contains 8-bit command or data word
|
||||
*
|
||||
* LCD_8574_ADA:
|
||||
* -------------
|
||||
* Standard 16x2 LCD HD44780, with PCF8574 based Adafruit backpack I2C bridge (@ 0x27)
|
||||
* Same registers, but interface uses 4-bit transfers for data/comand, in bits 3..6
|
||||
* bit 0 is unused
|
||||
|
@ -23,6 +30,7 @@
|
|||
* bit 7 is backlight (1 for on)
|
||||
*
|
||||
* LCD_8574_GEN:
|
||||
* -------------
|
||||
* Standard 16x2 LCD HD44780, with PCF8574 based Generic backpack I2C bridge (@ 0x27)
|
||||
* Same registers, but interface uses 4-bit transfers for data/comand, in bits 4..7
|
||||
* bit 0 is Register Select (0 for command, 1 for data)
|
||||
|
@ -31,8 +39,6 @@
|
|||
* bit 3 is backlight (1 for on)
|
||||
* bit 4..7 data or command nibble (write high nibble first)
|
||||
*
|
||||
* Note: There may be other bit-mappings, code needs to be adjusted to that...
|
||||
*
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
@ -43,18 +49,18 @@
|
|||
#include "lcd.h"
|
||||
|
||||
/** User selectable definitions **/
|
||||
// Set I2C address
|
||||
//#define I2C_LCD 0x27
|
||||
#define I2C_LCD 0x3E
|
||||
// Set I2C address for your device
|
||||
#define I2C_LCD 0x3E // Grove: 0x3E, 8574 backpack: 0x20..0x27
|
||||
|
||||
// Select LCD type matching your HW
|
||||
// Select LCD type to match your device,
|
||||
// or define a new one when code changes are needed.
|
||||
#define LCD_1804 0 // Seeed / Grove
|
||||
#define LCD_8574_ADA 1 // Adafruit I2C backpack
|
||||
#define LCD_8574_GEN 2 // Generic I2C backpack
|
||||
#define LCD_TYPE LCD_1804
|
||||
|
||||
|
||||
/** HD44780 interface **/
|
||||
/** Generic HD44780 interface **/
|
||||
// commands
|
||||
#define LCD_CLEARDISPLAY 0x01 // Note: LCD_ENTRYINC is set
|
||||
#define LCD_RETURNHOME 0x02
|
||||
|
@ -125,12 +131,10 @@
|
|||
#endif
|
||||
|
||||
|
||||
|
||||
/** Other definitions **/
|
||||
#define LCD_DELAY 100 // Delay for regular write
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* User defined (CGRAM) characters
|
||||
* Display RAM addresses 0x00-0x1f for top row and 0x40-0x5f for bottom row
|
||||
|
@ -144,8 +148,8 @@ uint8_t cgram[8][8] =
|
|||
{0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00}, // 0x03: Level 3
|
||||
{0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00}, // 0x04: Level 4
|
||||
{0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00}, // 0x05: Level 5
|
||||
{0x00, 0x04, 0x04, 0x04, 0x1f, 0x0e, 0x04, 0x00}, // 0x06: Receive arrow down
|
||||
{0x04, 0x0e, 0x1f, 0x04, 0x04, 0x04, 0x00, 0x00} // 0x07: Transmit arrow up
|
||||
{0x00, 0x04, 0x04, 0x04, 0x15, 0x0e, 0x04, 0x00}, // 0x06: Receive arrow down
|
||||
{0x04, 0x0e, 0x15, 0x04, 0x04, 0x04, 0x00, 0x00} // 0x07: Transmit arrow up
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -202,7 +206,7 @@ void lcd_sendbyte(uint8_t command, uint8_t data)
|
|||
}
|
||||
|
||||
/*
|
||||
* It seems that there is too much in here, but it doesn't harm either.
|
||||
* It seems that there is too much init here, but it doesn't harm either.
|
||||
*/
|
||||
void lcd_init(void)
|
||||
{
|
||||
|
|
84
monitor.c
84
monitor.c
|
@ -89,20 +89,6 @@ void mon_lt(void)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* Checks for inter-core fifo overruns
|
||||
*/
|
||||
extern volatile uint32_t fifo_overrun, fifo_rx, fifo_tx, fifo_xx, fifo_incnt;
|
||||
void mon_fo(void)
|
||||
{
|
||||
printf("Fifo input: %lu\n", fifo_incnt);
|
||||
printf("Fifo rx: %lu\n", fifo_rx);
|
||||
printf("Fifo tx: %lu\n", fifo_tx);
|
||||
printf("Fifo unknown: %lu\n", fifo_xx);
|
||||
printf("Fifo overruns: %lu\n", fifo_overrun);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Toggles the PTT status, overriding the HW signal
|
||||
*/
|
||||
|
@ -131,12 +117,13 @@ void mon_bp(void)
|
|||
|
||||
if (*argv[1]=='w')
|
||||
{
|
||||
if (nargs>=2)
|
||||
if (nargs>2)
|
||||
{
|
||||
ret = atoi(argv[2]);
|
||||
relay_setband((uint8_t)ret);
|
||||
relay_setband(ret);
|
||||
}
|
||||
}
|
||||
sleep_ms(1);
|
||||
ret = relay_getband();
|
||||
if (ret<0)
|
||||
printf ("I2C read error\n");
|
||||
|
@ -153,12 +140,13 @@ void mon_rx(void)
|
|||
|
||||
if (*argv[1]=='w')
|
||||
{
|
||||
if (nargs>=2)
|
||||
if (nargs>2)
|
||||
{
|
||||
ret = atoi(argv[2]);
|
||||
relay_setattn((uint8_t)ret);
|
||||
relay_setattn(ret);
|
||||
}
|
||||
}
|
||||
sleep_ms(1);
|
||||
ret = relay_getattn();
|
||||
if (ret<0)
|
||||
printf ("I2C read error\n");
|
||||
|
@ -167,18 +155,56 @@ void mon_rx(void)
|
|||
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks for overruns
|
||||
*/
|
||||
extern volatile uint32_t dsp_overrun;
|
||||
#if DSP_FFT == 1
|
||||
extern volatile uint32_t dsp_tickx;
|
||||
extern volatile int scale0;
|
||||
extern volatile int scale1;
|
||||
#endif
|
||||
void mon_or(void)
|
||||
{
|
||||
printf("DSP overruns : %d\n", dsp_overrun);
|
||||
#if DSP_FFT == 1
|
||||
printf("DSP loop load : %lu%%\n", (100*dsp_tickx)/512);
|
||||
printf("FFT scale = %d, iFFT scale = %d\n", scale0, scale1);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ADC and AGC levels
|
||||
*/
|
||||
extern volatile uint32_t adc_level[3];
|
||||
extern volatile int32_t rx_agc;
|
||||
extern volatile int adccnt;
|
||||
void mon_adc(void)
|
||||
{
|
||||
// Print results
|
||||
printf("ADC0: %5u/2048\n", adc_level[0]>>8);
|
||||
printf("ADC1: %5u/2048\n", adc_level[1]>>8);
|
||||
printf("ADC2: %5u/2048\n", adc_level[2]>>8);
|
||||
printf("AGC : %7d\n", rx_agc);
|
||||
printf("ADCc: %5d\n", adccnt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Command shell table, organize the command functions above
|
||||
*/
|
||||
#define NCMD 6
|
||||
#define NCMD 7
|
||||
shell_t shell[NCMD]=
|
||||
{
|
||||
{"si", 2, &mon_si, "si <start> <nr of reg>", "Dumps Si5351 registers"},
|
||||
{"lt", 2, &mon_lt, "lt (no parameters)", "LCD test, dumps characterset on LCD"},
|
||||
{"fo", 2, &mon_fo, "fo (no parameters)", "Returns inter core fifo overruns"},
|
||||
{"or", 2, &mon_or, "or (no parameters)", "Returns overrun information"},
|
||||
{"pt", 2, &mon_pt, "pt (no parameters)", "Toggles PTT status"},
|
||||
{"bp", 2, &mon_bp, "bp {r|w} <value>", "Read or Write BPF relays"},
|
||||
{"rx", 2, &mon_rx, "rx {r|w} <value>", "Read or Write RX relays"}
|
||||
{"rx", 2, &mon_rx, "rx {r|w} <value>", "Read or Write RX relays"},
|
||||
{"adc", 3, &mon_adc, "adc (no parameters)", "Dump latest ADC readouts"}
|
||||
};
|
||||
|
||||
|
||||
|
@ -187,6 +213,9 @@ shell_t shell[NCMD]=
|
|||
/*** Commandstring parser and monitor process ***/
|
||||
/*** ---------------------------------------- ***/
|
||||
|
||||
#define ISALPHANUM(c) (((c)>' ') && ((c)<127))
|
||||
#define ISWHITESP(c) (((c)!='\0') && ((c)<=' '))
|
||||
#define ISEOL(c) ((c)=='\0')
|
||||
/*
|
||||
* Command line parser
|
||||
*/
|
||||
|
@ -197,18 +226,19 @@ void mon_parse(char* s)
|
|||
|
||||
p = s; // Set to start of string
|
||||
nargs = 0;
|
||||
while (*p!='\0') // Assume stringlength >0
|
||||
while (ISWHITESP(*p)) p++; // Skip leading whitespace
|
||||
while (!ISEOL(*p)) // Check remaining stringlength >0
|
||||
{
|
||||
while (*p==' ') p++; // Skip whitespace
|
||||
if (*p=='\0') break; // String might end in spaces
|
||||
argv[nargs++] = p; // Store first valid char loc after whitespace
|
||||
while ((*p!=' ')&&(*p!='\0')) p++; // Skip non-whitespace
|
||||
while (ISALPHANUM(*p)) p++; // Skip non-whitespace
|
||||
while (ISWHITESP(*p)) p++; // Skip separating whitespace
|
||||
}
|
||||
if (nargs==0) return; // No command or parameter
|
||||
if (nargs==0) return; // Nothing to do
|
||||
|
||||
for (i=0; i<NCMD; i++) // Lookup shell command
|
||||
if (strncmp(argv[0], shell[i].cmdstr, shell[i].cmdlen) == 0) break;
|
||||
if (i<NCMD)
|
||||
(*shell[i].cmd)();
|
||||
(*shell[i].cmd)(); // Execute if found
|
||||
else // Unknown command
|
||||
{
|
||||
for (i=0; i<NCMD; i++) // Print help if no match
|
||||
|
|
10
relay.c
10
relay.c
|
@ -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
12
relay.h
|
@ -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
|
||||
|
|
80
si5351.c
80
si5351.c
|
@ -3,13 +3,26 @@
|
|||
*
|
||||
* Created: Jan 2020
|
||||
* Author: Arjan
|
||||
*
|
||||
* Driver for the SI5351A VCO
|
||||
*
|
||||
|
||||
Si5351 principle of operation:
|
||||
==============================
|
||||
Crystal frequency Fxtal (usually 25MHz) is multiplied in a PLL by MSN to obtain Fvco.
|
||||
PLL A and B have independent MSN values, the Fout channel i can be derived from either.
|
||||
Fvco is between 600MHz and 900MHz, but the spec in reality is more relaxed.
|
||||
Fvco is divided by MSi and Ri to obtain the output frequency Fout.
|
||||
MSi and Ri are selected to be in the ballpark of desired frequency range.
|
||||
MSN is then used for tuning.
|
||||
Only certain values of MSi and Ri are allowed when quadrature output is needed.
|
||||
|
||||
Si5351 principle:
|
||||
=================
|
||||
+-------+ +-------+ +------+
|
||||
- Fxtal --> | * MSN | -- Fvco --> | / MSi | --> | / Ri | -- Fout -->
|
||||
+-------+ +-------+ +------+
|
||||
|
||||
Details:
|
||||
========
|
||||
---Derivation of Fout---
|
||||
MSN determines: Fvco = Fxtal * (MSN) , where MSN = a + b/c
|
||||
MSi and Ri determine: Fout = Fvco / (Ri*MSi) , where MSi = a + b/c (different a, b and c)
|
||||
|
@ -60,7 +73,6 @@ Quadrature Phase offsets (i.e. delay):
|
|||
NOTE: Phase offsets only work when Ri = 1, this means minimum Fout is 4.762MHz at Fvco = 600MHz. Additional flip/flop dividers are needed to get 80m band frequencies, or Fvco must be tuned below spec.
|
||||
|
||||
|
||||
|
||||
Control Si5351 (see AN619):
|
||||
===========================
|
||||
----+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||
|
@ -117,7 +129,7 @@ Control Si5351 (see AN619):
|
|||
====
|
||||
183 | XTAL_CL | Reserved |
|
||||
====
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
@ -173,9 +185,11 @@ Control Si5351 (see AN619):
|
|||
|
||||
|
||||
|
||||
vfo_t vfo[2]; // 0: clk0 and clk1 1: clk2
|
||||
vfo_t vfo[2]; // 0: clk0 & clk1 1: clk2
|
||||
|
||||
/* read contents of SI5351 registers, from reg to reg+len-1, output in data array */
|
||||
/*
|
||||
* read contents of SI5351 registers, from reg to reg+len-1, output in data array
|
||||
*/
|
||||
int si_getreg(uint8_t *data, uint8_t reg, uint8_t len)
|
||||
{
|
||||
int ret;
|
||||
|
@ -188,9 +202,11 @@ int si_getreg(uint8_t *data, uint8_t reg, uint8_t len)
|
|||
}
|
||||
|
||||
|
||||
// Set up MSN PLL divider for vfo[i], assuming MSN has been set in vfo[i]
|
||||
// Optimize for speed, this may be called with short intervals
|
||||
// See also SiLabs AN619 section 3.2
|
||||
/*
|
||||
* Set up MSN PLL divider for vfo[i], assuming MSN has been set in vfo[i]
|
||||
* Optimize for speed, this may be called with short intervals
|
||||
* See also SiLabs AN619 section 3.2
|
||||
*/
|
||||
void si_setmsn(uint8_t i)
|
||||
{
|
||||
uint8_t data[16]; // I2C trx buffer
|
||||
|
@ -199,11 +215,7 @@ void si_setmsn(uint8_t i)
|
|||
uint32_t B;
|
||||
|
||||
i=(i>0?1:0);
|
||||
/*
|
||||
P1 = 128*a + Floor(128*b/c) - 512
|
||||
P2 = 128*b - c*Floor(128*b/c)
|
||||
P3 = c (P3 = 1000000 for MSN tuning)
|
||||
*/
|
||||
|
||||
A = (uint32_t)(floor(vfo[i].msn)); // A is integer part of MSN
|
||||
B = (uint32_t)((vfo[i].msn - (float)A) * SI_PLL_C); // B is C * fraction part of MSN (C is a constant)
|
||||
P2 = (uint32_t)(floor((float)(128 * B) / (float)SI_PLL_C));
|
||||
|
@ -223,9 +235,11 @@ void si_setmsn(uint8_t i)
|
|||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
}
|
||||
|
||||
// Set up registers with MS and R divider for vfo[i], assuming values have been set in vfo[i]
|
||||
// In this implementation we only use integer mode, i.e. b=0 and P3=1
|
||||
// See also SiLabs AN619 section 4.1
|
||||
/*
|
||||
* Set up registers with MS and R divider for vfo[i], assuming values have been set in vfo[i]
|
||||
* In this implementation we only use integer mode, i.e. b=0 and P3=1
|
||||
* See also SiLabs AN619 section 4.1
|
||||
*/
|
||||
void si_setmsi(uint8_t i)
|
||||
{
|
||||
uint8_t data[16]; // I2C trx buffer
|
||||
|
@ -233,11 +247,7 @@ void si_setmsi(uint8_t i)
|
|||
uint8_t R;
|
||||
|
||||
i=(i>0?1:0);
|
||||
/*
|
||||
P1 = 128*a + Floor(128*b/c) - 512
|
||||
P2 = 128*b - c*Floor(128*b/c) (P2 = 0 for MSi integer mode)
|
||||
P3 = c (P3 = 1 for MSi integer mode)
|
||||
*/
|
||||
|
||||
P1 = (uint32_t)(128*(uint32_t)floor(vfo[i].msi) - 512);
|
||||
R = vfo[i].ri;
|
||||
R = (R&0xf0) ? ((R&0xc0)?((R&0x80)?7:6):(R&0x20)?5:4) : ((R&0x0c)?((R&0x08)?3:2):(R&0x02)?1:0); // quick log2(r)
|
||||
|
@ -286,10 +296,12 @@ void si_setmsi(uint8_t i)
|
|||
}
|
||||
|
||||
|
||||
// For each vfo, calculate required MSN setting, MSN = MSi*Ri*Fout/Fxtal
|
||||
// If in range, just set MSN registers
|
||||
// If not in range, recalculate MSi and Ri and also MSN
|
||||
// Set MSN, MSi and Ri registers (implicitly resets PLL)
|
||||
/*
|
||||
* For each vfo, calculate required MSN setting, MSN = MSi*Ri*Fout/Fxtal
|
||||
* If in range, just set MSN registers
|
||||
* If not in range, recalculate MSi and Ri and also MSN
|
||||
* Set MSN, MSi and Ri registers (implicitly resets PLL)
|
||||
*/
|
||||
void si_evaluate(void)
|
||||
{
|
||||
float msn;
|
||||
|
@ -327,22 +339,24 @@ void si_evaluate(void)
|
|||
}
|
||||
|
||||
|
||||
// Initialize the Si5351 VFO registers
|
||||
/*
|
||||
* Initialize the Si5351 VFO registers
|
||||
* Hard initialize Synth registers to: 7.074MHz, CLK1 90 deg ahead, PLLA for CLK 0&1, PLLB for CLK2
|
||||
| Ri=1,
|
||||
| MSi=68, P1=8192, P2=0, P3=1
|
||||
| MSN=27.2 P1=2969, P2=600000, P3=1000000
|
||||
*/
|
||||
void si_init(void)
|
||||
{
|
||||
uint8_t data[16]; // I2C trx buffer
|
||||
|
||||
// Hard initialize Synth registers: 7.074MHz, CLK1 90 deg ahead, PLLA for CLK 0&1, PLLB for CLK2
|
||||
// Ri=1,
|
||||
// MSi=68, P1=8192, P2=0, P3=1
|
||||
// MSN=27.2 P1=2969, P2=600000, P3=1000000
|
||||
vfo[0].freq = 10000000;
|
||||
vfo[0].freq = 10000000; // Check this, should be 7074000?
|
||||
vfo[0].flag = 0;
|
||||
vfo[0].phase = 1;
|
||||
vfo[0].ri = 1;
|
||||
vfo[0].msi = 68;
|
||||
vfo[0].msn = 27.2;
|
||||
vfo[1].freq = 10000000;
|
||||
vfo[1].freq = 10000000; // Check this, should be 7074000?
|
||||
vfo[1].flag = 0;
|
||||
vfo[1].phase = 0;
|
||||
vfo[1].ri = 1;
|
||||
|
|
29
uSDR.c
29
uSDR.c
|
@ -29,8 +29,6 @@
|
|||
#include "monitor.h"
|
||||
#include "relay.h"
|
||||
|
||||
#define LED_MS 1000
|
||||
#define LOOP_MS 100
|
||||
|
||||
#define I2C0_SDA 16
|
||||
#define I2C0_SCL 17
|
||||
|
@ -40,6 +38,7 @@
|
|||
/*
|
||||
* LED TIMER definition and callback routine
|
||||
*/
|
||||
#define LED_MS 1000
|
||||
struct repeating_timer led_timer;
|
||||
bool led_callback(struct repeating_timer *t)
|
||||
{
|
||||
|
@ -54,6 +53,7 @@ bool led_callback(struct repeating_timer *t)
|
|||
* Scheduler callback function.
|
||||
* This executes every LOOP_MS.
|
||||
*/
|
||||
#define LOOP_MS 100
|
||||
semaphore_t loop_sem;
|
||||
struct repeating_timer loop_timer;
|
||||
bool loop_callback(struct repeating_timer *t)
|
||||
|
@ -65,7 +65,18 @@ bool loop_callback(struct repeating_timer *t)
|
|||
|
||||
int main()
|
||||
{
|
||||
/* Initialize LED pin output */
|
||||
/*
|
||||
* Optional: increase core voltage (normally 1.1V)
|
||||
* Optional: overclock the CPU to 250MHz (normally 125MHz)
|
||||
* Note that clk_peri (e.g. I2C) is derived from the SYS PLL
|
||||
* Note that clk_adc sampling clock is derived from the 48MHz USB PLL.
|
||||
*/
|
||||
//vreg_set_voltage(VREG_VOLTAGE_1_25); sleep_ms(10);
|
||||
//set_sys_clock_khz(250000, false); sleep_ms(10);
|
||||
|
||||
/*
|
||||
* Initialize LED pin output
|
||||
*/
|
||||
gpio_init(PICO_DEFAULT_LED_PIN);
|
||||
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
|
||||
gpio_put(PICO_DEFAULT_LED_PIN, true); // Set LED on
|
||||
|
@ -74,24 +85,20 @@ int main()
|
|||
/*
|
||||
* i2c0 is used for the si5351 interface
|
||||
* i2c1 is used for the LCD and all other interfaces
|
||||
* if the display cannot keep up, try lowering the i2c1 frequency
|
||||
*/
|
||||
|
||||
/* i2c0 initialisation at 400Khz. */
|
||||
i2c_init(i2c0, 400*1000);
|
||||
i2c_init(i2c0, 400000); // i2c0 initialisation at 400Khz
|
||||
gpio_set_function(I2C0_SDA, GPIO_FUNC_I2C);
|
||||
gpio_set_function(I2C0_SCL, GPIO_FUNC_I2C);
|
||||
gpio_pull_up(I2C0_SDA);
|
||||
gpio_pull_up(I2C0_SCL);
|
||||
|
||||
/* i2c1 initialisation at 400Khz. */
|
||||
i2c_init(i2c1, 100*1000);
|
||||
i2c_init(i2c1, 100000); // i2c1 initialisation at 100Khz
|
||||
gpio_set_function(I2C1_SDA, GPIO_FUNC_I2C);
|
||||
gpio_set_function(I2C1_SCL, GPIO_FUNC_I2C);
|
||||
gpio_pull_up(I2C1_SDA);
|
||||
gpio_pull_up(I2C1_SCL);
|
||||
|
||||
|
||||
/* Initialize units */
|
||||
/* Initialize the SW units */
|
||||
mon_init(); // Monitor shell on stdio
|
||||
si_init(); // VFO control unit
|
||||
dsp_init(); // Signal processing unit
|
||||
|
|
Ładowanie…
Reference in New Issue