diff --git a/CMakeLists.txt b/CMakeLists.txt index 5661b70..c658bee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/CODEv3.zip b/CODEv3.zip new file mode 100644 index 0000000..4b2511e Binary files /dev/null and b/CODEv3.zip differ diff --git a/doc/uSDR - v3.00.pdf b/doc/uSDR - v3.00.pdf new file mode 100644 index 0000000..ceaf3bd Binary files /dev/null and b/doc/uSDR - v3.00.pdf differ diff --git a/dsp.c b/dsp.c index dd73c57..b7aa40a 100644 --- a/dsp.c +++ b/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) - * - * The RX branch: - * - Sample I and Q QSD channels, and shift into I and Q delay line (62.5 kHz per channel) - * - Low pass filter: Fc=4kHz - * - Quarter rate (15.625 kHz) to improve low freq behavior of Hilbert transform - * - Calculate 15 tap Hilbert transform on Q - * - Demodulate, taking proper delays into account - * - Push to Audio output DAC - * - * Always perform audio sampling (62.5kHz) and level detections, in case of VOX active - * - * The TX branch (if VOX or PTT): - * - Low pass filter: Fc=3kHz - * - Eight rate (7.8125 kHz) to improve low F behavior of Hilbert transform - * - Generate Q samples by doing a Hilbert transform - * - Push I and Q to QSE output DACs + * Signal processing of RX and TX branch, to be run on the second processor core (CORE1). + * + * The actual DSP engine can be either FFT based in the frequency domain, or in the time domain. + * In dsp.h this can be selected compile-time, by defining the environment variable DSP_FFT. * */ #include "pico/stdlib.h" #include "pico/multicore.h" +#include "pico/platform.h" +#include "pico/time.h" +#include "pico/sem.h" +#include "hardware/structs/bus_ctrl.h" #include "hardware/pwm.h" #include "hardware/adc.h" #include "hardware/irq.h" #include "hardware/timer.h" #include "hardware/clocks.h" -#define ADC0_IRQ_FIFO 22 // FIFO IRQ number -#define GP_PTT 15 // PTT pin 20 (GPIO 15) - #include "dsp.h" #include "hmi.h" +#include "fix_fft.h" + +#define GP_PTT 15 // PTT pin 20 (GPIO 15) + +volatile bool tx_enabled; // TX branch active +volatile uint32_t dsp_overrun; // Overrun counter /* @@ -48,22 +39,25 @@ * PWM frequency = Fsys / DAC_RANGE * A value of 250 means 125MHz/250=500kHz * ADC is 12 bit, so resolution is by definition 4096 - * To eliminate undefined behavior we clip off the upper 4 sample bits. */ #define DAC_RANGE 256 #define DAC_BIAS (DAC_RANGE/2) #define ADC_RANGE 4096 #define ADC_BIAS (ADC_RANGE/2) -/* - * Callback timeout and inter-core FIFO commands. - * The timer value in usec determines frequency of TX and RX loops - * Exact time is obtained by passing the value as negative - * Here we use 16us (62.5 kHz == PWM freq/4) + +volatile uint16_t dac_iq, dac_audio; + + +/* + * MODE is modulation/demodulation + * This setting steers the signal processing branch chosen */ -#define DSP_US 16 -#define DSP_TX 1 -#define DSP_RX 2 +volatile int dsp_mode; +void dsp_setmode(int mode) +{ + dsp_mode = mode; +} /* @@ -77,526 +71,404 @@ */ #define AGC_REF 6 #define AGC_DECAY 8192 -#define AGC_FAST 64 -#define AGC_SLOW 4096 -#define AGC_OFF 32766 -volatile uint16_t agc_decay = AGC_OFF; -volatile uint16_t agc_attack = AGC_OFF; +#define AGC_SHORT 64 +#define AGC_LONG 4096 +#define AGC_DIS 32766 +volatile uint16_t agc_decay = AGC_DIS; +volatile uint16_t agc_attack = AGC_DIS; void dsp_setagc(int agc) { switch(agc) { - case 1: //SLOW, for values see hmi.c - agc_attack = AGC_SLOW; + case AGC_SLOW: + agc_attack = AGC_LONG; agc_decay = AGC_DECAY; break; - case 2: //FAST - agc_attack = AGC_FAST; + case AGC_FAST: + agc_attack = AGC_SHORT; agc_decay = AGC_DECAY; break; - default: //OFF - agc_attack = AGC_OFF; - agc_decay = AGC_OFF; + default: + agc_attack = AGC_DIS; + agc_decay = AGC_DIS; break; } } -/* - * MODE is modulation/demodulation - * This setting steers the signal processing branch chosen - */ -volatile uint16_t dsp_mode; // For values see hmi.c, assume {USB,LSB,AM,CW} -void dsp_setmode(int mode) -{ - dsp_mode = (uint16_t)mode; -} - - /* - * VOX LINGER is the number of 16us cycles to wait before releasing TX mode - * The level of detection is related to the maximum ADC range. + * VOX LINGER is the number msec to wait before releasing TX mode + * The level of detection is derived from the maximum ADC range. */ -#define VOX_LINGER 500000/16 -#define VOX_HIGH ADC_BIAS/2 -#define VOX_MEDIUM ADC_BIAS/4 -#define VOX_LOW ADC_BIAS/16 -#define VOX_OFF 0 -volatile uint16_t vox_count; -volatile uint16_t vox_level = VOX_OFF; +#define VOX_LINGER 500 // 500msec + +volatile uint16_t vox_count = 0; +volatile uint16_t vox_level = 0; +volatile bool vox_active; // Is set when audio energy > vox level (and not OFF) void dsp_setvox(int vox) { switch(vox) { - case 1: - vox_level = VOX_LOW; + case VOX_HIGH: + vox_level = ADC_BIAS/2; break; - case 2: - vox_level = VOX_MEDIUM; + case VOX_MEDIUM: + vox_level = ADC_BIAS/4; break; - case 3: - vox_level = VOX_HIGH; + case VOX_LOW: + vox_level = ADC_BIAS/16; break; default: - vox_level = VOX_OFF; + vox_level = 0; vox_count = 0; break; } } -/* - * Low pass filters Fc=3, 7 and 15 kHz (see http://t-filter.engineerjs.com/) - * Settings: sample rates 62500, 31250 or 15625 Hz, stopband -40dB, passband ripple 5dB - * Note: 8 bit precision, so divide sum by 256 (this could be improved when 32bit accumulator) - */ -int16_t lpf3_62[15] = { 3, 3, 5, 7, 9, 10, 11, 11, 11, 10, 9, 7, 5, 3, 3}; // Pass: 0-3000, Stop: 6000-31250 -int16_t lpf3_31[15] = { -2, -3, -3, 1, 10, 21, 31, 35, 31, 21, 10, 1, -3, -3, -2}; // Pass: 0-3000, Stop: 6000-15625 -int16_t lpf3_15[15] = { 3, 4, -3,-14,-15, 6, 38, 53, 38, 6,-15,-14, -3, 4, 3}; // Pass: 0-3000, Stop: 4500-7812 -int16_t lpf7_62[15] = { -2, -1, 1, 7, 16, 26, 33, 36, 33, 26, 16, 7, 1, -1, -2}; // Pass: 0-7000, Stop: 10000-31250 -int16_t lpf7_31[15] = { -1, 4, 9, 2,-12, -2, 40, 66, 40, -2,-12, 2, 9, 4, -1}; // Pass: 0-7000, Stop: 10000-15625 -int16_t lpf15_62[15] = { -1, 3, 12, 6,-12, -4, 40, 69, 40, -4,-12, 6, 12, 3, -1}; // Pass: 0-15000, Stop: 20000-31250 - -volatile uint16_t dac_iq, dac_audio; -volatile uint32_t fifo_overrun, fifo_rx, fifo_tx, fifo_xx, fifo_incnt; -volatile bool tx_enabled, vox_active; - - - +#define ABS(x) ( (x)<0 ? -(x) : (x) ) + /* - * Some macro's - * See Alpha Max plus Beta Min algorithm for MAG (vector length) + * Z = alpha*max(i,q) + beta*min(i,q); + * alpha = 1/1, beta = 3/8 (error<6.8%) + * alpha = 15/16, beta = 15/32 (error<6.25%) + * Better algorithm: + * Z = max( max(i,q), alpha*max(i,q)+beta*min(i,q) ) + * alpha = 29/32, beta = 61/128 (error<2.4%) */ -#define ABS(x) ((x)<0?-(x):(x)) -#define MAG(i,q) (ABS(i)>ABS(q) ? ABS(i)+((3*ABS(q))>>3) : ABS(q)+((3*ABS(i))>>3)) - - -/* - * CORE1: - * ADC IRQ handler. - * Fills the results array in RR fashion, for 3 channels (~2usec per channel). - */ -volatile uint16_t adc_result[3]; -volatile int adc_next; // Remember which ADC the result is from -uint32_t adc_count=0; // Debugging -void adcfifo_handler(void) +inline int16_t mag(int16_t i, int16_t q) { - adc_result[adc_next] = adc_fifo_get(); // Get result from fifo - adc_next = adc_hw->cs; // Update adc_next with HW CS register - adc_next = (adc_next >> ADC_CS_AINSEL_LSB) & 3; // Shift and Mask: Only 0, 1 and 2 are valid numbers - adc_count++; + i = ABS(i); q = ABS(q); + if (i>q) + return (MAX(i,((29*i/32) + (61*q/128)))); + else + return (MAX(q,((29*q/32) + (61*i/128)))); } - -/* - * CORE1: - * Execute RX branch signal processing, max time to spend is <16us, i.e. rate is 62.5 kHz - * No ADC sample interleaving, read both I and Q channels. - * The delay is only 2us per conversion, which causes less distortion than interpolation of samples. +/* Note: + * A simple IIR single pole low pass filter could be made for anti-aliasing. + * Do this at full speed 16usec sampling, + * then for dsp_tim decimate with 1/8 (128usec) + * and for dsp_fft decimate with 1/4 (64usec) + * to obtain proper spectrum. + * y(n) = (1-a)*y(n-1) + a*x(n) = y(n-1) + a*(x(n) - y(n-1)) + * in this a = T / (T + R*C) + * T is sample period (e.g. 16usec) + * RC the desired RC time: T*(1-a)/a. + * example: a=1/256 : RC = 255*16usec = 4.08msec ( 245Hz) */ -volatile int16_t i_s_raw[15], q_s_raw[15]; // Raw I/Q samples minus DC bias -volatile uint16_t peak=0; // Peak detector running value -volatile int16_t agc_gain=0; // AGC gain (left-shift value) -volatile int16_t agc_accu=0; // Log peak level integrator -volatile int16_t i_s[15], q_s[15]; // Filtered I/Q samples -volatile int16_t i_dc, q_dc; // DC bias for I/Q channel -volatile int rx_cnt=0; // Decimation counter -bool rx(void) +volatile int32_t q_sample, i_sample, a_sample; // Latest sample values + +/*** DSP engine ***/ +#if DSP_FFT == 1 +#define AGC_TOP 16383L +#include "dsp_fft.c" +#else +#define AGC_TOP 1500L +#include "dsp_tim.c" +#endif + + + +/** CORE1: ADC IRQ handler **/ +#define LSH 8 // Shift for higher level accuracy +#define ADC_INT 8 // Nr of samples for integration (max 10) +volatile int32_t adc_sample[3] = {0,0,0}; // Buffer for ADC samples +volatile int32_t adc_result[3] = {0,0,0}; // Buffer for ADC results +volatile uint32_t adc_level[3] = {10<= ADC_INT) // Nr of integration samples reached? + { + adc_irq_set_enabled(false); // Disable interrupts + adc_run(false); // Stop freerunning + } + adc_sample[0] = adc_sample[0] + adc_fifo_get() - ADC_BIAS; // Read first three samples from FIFO + adc_sample[1] = adc_sample[1] + adc_fifo_get() - ADC_BIAS; + adc_sample[2] = adc_sample[2] + adc_fifo_get() - ADC_BIAS; + adccnt++; +} - /*** SAMPLING ***/ + + +/** CORE1: Timer callback routine **/ +/* + * This runs every TIM_US , i.e. 16usec + * First the decimation filter is applied on latest ADC results + * The filtered samples are set aside, so a new ADC cycle can be started. + * The ADC cycle takes 8usec to complete (3x ADC0..2 + 1x stray ADC0 conversion). + * The timing is critical, it assumes that the ADC is finished. + * Once every 4 TIM_US intervals signal preprocessing is done, and DSP may be invoked. + * Do not put any other stuff in this callback routine. + */ +semaphore_t dsp_sem; +repeating_timer_t dsp_timer; +volatile int cnt = 4; +volatile int32_t rx_agc = 1, tx_agc = 1; // Factor as AGC +bool __not_in_flash_func(dsp_callback)(repeating_timer_t *t) +{ + int32_t temp; - q_sample = adc_result[0]; // Take last ADC 0 result, connected to Q input - i_sample = adc_result[1]; // Take last ADC 1 result, connected to I input - - /* - * Remove DC and store new sample - * IIR filter: dc = a*sample + (1-a)*dc where a = 1/128 - * Amplitude of samples should fit inside [-2048, 2047] - */ - q_sample = (q_sample&0x0fff) - ADC_BIAS; // Clip to 12 bits and subtract mid-range - q_dc += q_sample/128 - q_dc/128; // then IIR running average - q_sample -= q_dc; // and subtract DC - i_sample = (i_sample&0x0fff) - ADC_BIAS; // Same for I sample - i_dc += i_sample/128 - i_dc/128; - i_sample -= i_dc; - - /* - * Shift with AGC feedback from AUDIO GENERATION stage - * Note: bitshift does not work with negative numbers, so need to MPY/DIV - * This behavior in essence is exponential, complementing the logarithmic peak detector - */ - if (agc_gain > 0) - { - q_sample = q_sample * (1<0) return (true); // So net sample time is 64us or 15.625 kHz - - for (i=0; i<14; i++) // Shift decimated samples - { - q_s[i] = q_s[i+1]; - i_s[i] = i_s[i+1]; - } - q_accu = 0; // Initialize accumulators - i_accu = 0; - for (i=0; i<15; i++) // Low pass FIR filter - { - q_accu += (int32_t)q_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz raw sampling - i_accu += (int32_t)i_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz raw sampling - } - q_accu = q_accu/256; - i_accu = i_accu/256; - - q_s[14] = q_accu; - i_s[14] = i_accu; - - /*** DEMODULATION ***/ - switch (dsp_mode) + // Get DC bias corrected samples + adc_result[0] = adc_sample[0]; + adc_result[1] = adc_sample[1]; + adc_result[2] = adc_sample[2]; + + // Re-start ADCs + while (!adc_fifo_is_empty()) adc_fifo_get(); // Empty leftovers from fifo + adc_sample[0] = 0; + adc_sample[1] = 0; + adc_sample[2] = 0; + adc_set_round_robin(0x01+0x02+0x04); // Sequence ADC 0-1-2 (GP 26, 27, 28) free running + adc_select_input(0); // Start with ADC0 + adccnt=0; // Check for ADC FIFO interrupt overruns + adc_run(true); // Start ADC + adc_irq_set_enabled(true); // Enable ADC interrupts + + // Calculate and save level, left shifted by LSH + // a=1/1024 : RC = 1023*64usec = 65msec (15Hz) + adc_level[0] = (1023*adc_level[0] + (ABS(adc_result[0])<>LSH; // Max I or Q + rx_agc = (temp==0) ? AGC_TOP : AGC_TOP/temp; // calculate required AGC factor + } + +#if DSP_FFT == 1 + + if (tx_enabled) + { + A_buf[dsp_active][dsp_tick] = (int16_t)(tx_agc*adc_result[2]); + pwm_set_gpio_level(21, I_buf[dsp_active][dsp_tick] + DAC_BIAS); // Output I to DAC + pwm_set_gpio_level(20, Q_buf[dsp_active][dsp_tick] + DAC_BIAS); // Output Q to DAC + } + else + { + I_buf[dsp_active][dsp_tick] = (int16_t)(rx_agc*adc_result[1]); + Q_buf[dsp_active][dsp_tick] = (int16_t)(rx_agc*adc_result[0]); + pwm_set_gpio_level(22, A_buf[dsp_active][dsp_tick] + DAC_BIAS); // Output A to DAC } - /*** AUDIO GENERATION ***/ - /* - * AGC, peak detector - * Sample speed is 15625 per second - */ - peak += (ABS(a_sample))/128 - peak/128; // Running average level detect, a=1/128 - k=0; i=peak; // Logarithmic peak detection - if (i&0xff00) {k+=8; i>>=8;} // k=log2(peak), find highest bit set - if (i&0x00f0) {k+=4; i>>=4;} - if (i&0x000c) {k+=2; i>>=2;} - if (i&0x0002) {k+=1;} - agc_accu += (k - AGC_REF); // Add difference with target to integrator (Acc += Xn - R) - if (agc_accu > agc_attack) // Attack time, gain correction in case of high level + // When sample buffer is full, move pointer and signal DSP loop + if (++dsp_tick >= BUFSIZE) // Increment tick and check range { - agc_gain--; // Decrease gain - agc_accu -= agc_attack; // Reset integrator - } else if (agc_accu < -(agc_decay)) // Decay time, gain correction in case of low level - { - agc_gain++; // Increase gain - agc_accu += agc_decay; // Reset integrator + dsp_tick = 0; // Reset counter + if (++dsp_active > 2) dsp_active = 0; // Rotate offset + dsp_overrun++; // Increment overrun counter + sem_release(&dsp_sem); // Signal DSP loop semaphore } + +#else - /* - * Scale and clip output, - * Send to audio DAC output - */ - a_sample += DAC_BIAS; // Add bias level - if (a_sample > DAC_RANGE) // Clip to DAC range - a_sample = DAC_RANGE; - else if (a_sample<0) - a_sample = 0; - pwm_set_chan_level(dac_audio, PWM_CHAN_A, a_sample); + if (tx_enabled) + { + a_sample = tx_agc * adc_result[2]; // Store A for DSP use + pwm_set_gpio_level(21, i_sample); // Output I to DAC + pwm_set_gpio_level(20, q_sample); // Output Q to DAC + } + else + { + pwm_set_gpio_level(22, a_sample); // Output Q to DAC + q_sample = rx_agc * adc_result[0]; // Store Q for DSP use + i_sample = rx_agc * adc_result[1]; // Store I for DSP use + } + dsp_overrun++; // Increment overrun counter + sem_release(&dsp_sem); // Signal DSP loop semaphore +#endif + return true; } - - -/* - * CORE1: - * The VOX function is called separately every cycle, to check audio level. - * Execute TX branch signal processing when tx enabled - */ -volatile int16_t a_s_raw[15]; // Raw samples, minus DC bias -volatile int16_t a_level=0; // Average level of raw sample stream -volatile int16_t a_s[15]; // Filtered and decimated samples -volatile int16_t a_dc; // DC level -volatile int tx_cnt=0; // Decimation counter - -bool vox(void) -{ - int16_t a_sample; - int i; - - /* - * Get sample and shift into delay line - */ - a_sample = adc_result[2]; // Get latest ADC 2 result - /* - * Remove DC and store new raw sample - * IIR filter: dc = a*sample + (1-a)*dc where a = 1/128 - */ - a_sample = (a_sample&0x0fff) - ADC_BIAS; // Clip and subtract mid-range - a_dc += (a_sample - a_dc)/128; // then IIR running average - a_sample -= a_dc; // subtract DC - for (i=0; i<14; i++) // and store in shift register - a_s_raw[i] = a_s_raw[i+1]; - a_s_raw[14] = a_sample; - - /* - * Detect level of audio signal - * Return true if VOX enabled and: - * - Audio level higher than threshold - * - Linger time sill active - */ - if (a_sample<0) a_sample = -a_sample; // Absolute value - a_level += (a_sample - a_level)/128; // running average, 16usec * 128 = 2msec - if (vox_level != VOX_OFF) // Only when VOX is enabled - { - if (a_level > vox_level) - { - vox_count = VOX_LINGER; // While audio present, reset linger timer - return(true); // and keep TX active - } - if (vox_count>0) - { - vox_count--; // No audio; decrement linger timer - return(true); // but keep TX active - } - } - return(false); // All other cases: no TX -} - -bool tx(void) -{ - int32_t a_accu, q_accu; - int16_t qh; - int i; - uint16_t i_dac, q_dac; - - /*** RAW Audio SAMPLES from VOX function ***/ - /*** Low pass filter + decimation ***/ - tx_cnt = (tx_cnt+1)&3; // Calculate only every fourth sample - if (tx_cnt>0) return true; // So effective sample rate will be 15625Hz - - a_accu = 0; // Initialize accumulator - for (i=0; i<15; i++) // Low pass FIR filter, using raw samples - a_accu += (int32_t)a_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz sampling - - for (i=0; i<14; i++) // Shift decimated samples - a_s[i] = a_s[i+1]; - a_s[14] = a_accu / 256; // Store rescaled accumulator - - /*** MODULATION ***/ - switch (dsp_mode) - { - case 0: // USB - /* - * qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator) - */ - q_accu = (a_s[0]-a_s[14])*315L + (a_s[2]-a_s[12])*440L + (a_s[4]-a_s[10])*734L + (a_s[6]-a_s[ 8])*2202L; - qh = -(q_accu / 4096L); // USB: sign is negative - break; - case 1: // LSB - /* - * qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator) - */ - q_accu = (a_s[0]-a_s[14])*315L + (a_s[2]-a_s[12])*440L + (a_s[4]-a_s[10])*734L + (a_s[6]-a_s[ 8])*2202L; - qh = q_accu / 4096L; // LSB: sign is positive - break; - case 2: // AM - /* - * I and Q values are identical - */ - qh = a_s[7]; - break; - default: - break; - } - - /* - * Write I and Q to QSE DACs, phase is 7 samples back. - * Need to multiply AC with DAC_RANGE/ADC_RANGE (appr 1/8) - * Any case: clip to range - */ - a_accu = DAC_BIAS - (qh/8); - if (a_accu<0) - q_dac = 0; - else if (a_accu>(DAC_RANGE-1)) - q_dac = DAC_RANGE-1; - else - q_dac = a_accu; - - a_accu = DAC_BIAS + (a_s[7]/8); - if (a_accu<0) - i_dac = 0; - else if (a_accu>(DAC_RANGE-1)) - i_dac = DAC_RANGE-1; - else - i_dac = a_accu; - - // pwm_set_both_levels(dac_iq, q_dac, i_dac); // Set both channels of the IQ slice simultaneously - // pwm_set_chan_level(dac_iq, PWM_CHAN_A, q_dac); - // pwm_set_chan_level(dac_iq, PWM_CHAN_B, i_dac); - pwm_set_gpio_level(21, i_dac); - pwm_set_gpio_level(20, q_dac); - - - return true; -} - - -/* - * CORE1: - * Timing loop, triggered through inter-core fifo - */ -void dsp_loop() +/** CORE1: DSP loop, triggered through repeating timer/semaphore **/ +void __not_in_flash_func(dsp_loop)() { uint32_t cmd; uint16_t slice_num; + alarm_pool_t *ap; - tx_enabled = false; - fifo_overrun = 0; - fifo_rx = 0; - fifo_tx = 0; - fifo_xx = 0; - fifo_incnt++; + tx_enabled = false; + vox_active = false; + + /* + * Initialize DACs, + * default mode is free running, + * A and B pins are output + */ + gpio_set_function(20, GPIO_FUNC_PWM); // GP20 is PWM for Q DAC (Slice 2, Channel A) + gpio_set_function(21, GPIO_FUNC_PWM); // GP21 is PWM for I DAC (Slice 2, Channel B) + dac_iq = pwm_gpio_to_slice_num(20); // Get PWM slice for GP20 (Same for GP21) + pwm_set_clkdiv_int_frac (dac_iq, 1, 0); // clock divide by 1: full system clock + pwm_set_wrap(dac_iq, DAC_RANGE-1); // Set cycle length; nr of counts until wrap, i.e. 125/DAC_RANGE MHz + pwm_set_enabled(dac_iq, true); // Set the PWM running + + gpio_set_function(22, GPIO_FUNC_PWM); // GP22 is PWM for Audio DAC (Slice 3, Channel A) + dac_audio = pwm_gpio_to_slice_num(22); // Find PWM slice for GP22 + pwm_set_clkdiv_int_frac (dac_audio, 1, 0); // clock divide by 1: full system clock + pwm_set_wrap(dac_audio, DAC_RANGE-1); // Set cycle length; nr of counts until wrap, i.e. 125/DAC_RANGE MHz + pwm_set_enabled(dac_audio, true); // Set the PWM running - /* Initialize DACs, default mode is free running, A and B pins are output */ - gpio_set_function(20, GPIO_FUNC_PWM); // GP20 is PWM for Q DAC (Slice 2, Channel A) - gpio_set_function(21, GPIO_FUNC_PWM); // GP21 is PWM for I DAC (Slice 2, Channel B) - dac_iq = pwm_gpio_to_slice_num(20); // Get PWM slice for GP20 (Same for GP21) - pwm_set_clkdiv_int_frac (dac_iq, 1, 0); // clock divide by 1: 125MHz - pwm_set_wrap(dac_iq, DAC_RANGE-1); // Set cycle length; nr of counts until wrap, i.e. 125/DAC_RANGE MHz - pwm_set_enabled(dac_iq, true); // Set the PWM running - - gpio_set_function(22, GPIO_FUNC_PWM); // GP22 is PWM for Audio DAC (Slice 3, Channel A) - dac_audio = pwm_gpio_to_slice_num(22); // Find PWM slice for GP22 - pwm_set_clkdiv_int_frac (dac_audio, 1, 0); // clock divide by 1: 125MHz - pwm_set_wrap(dac_audio, DAC_RANGE-1); // Set cycle length; nr of counts until wrap, i.e. 125/DAC_RANGE MHz - pwm_set_enabled(dac_audio, true); // Set the PWM running - - /* Initialize ADCs */ - adc_init(); // Initialize ADC to known state - adc_set_clkdiv(0); // Fastest clock (500 kSps) - adc_gpio_init(26); // GP26 is ADC 0 for Q channel - adc_gpio_init(27); // GP27 is ADC 1 for I channel - adc_gpio_init(28); // GP28 is ADC 2 for Audio channel - adc_select_input(0); // Start with ADC0 - adc_next = 0; - - adc_set_round_robin(0x01+0x02+0x04); // Sequence ADC 0-1-2 (GP 26, 27, 28) free running - adc_fifo_setup(true,false,1,false,false); // IRQ for every result (fifo threshold = 1) - irq_set_exclusive_handler(ADC0_IRQ_FIFO, adcfifo_handler); - adc_irq_set_enabled(true); - irq_set_enabled(ADC0_IRQ_FIFO, true); + /* + * Initialize ADCs, use in round robin mode (3 channels) + * samples are stored in array through IRQ callback + */ + adc_init(); // Initialize ADC to known state + adc_set_clkdiv(0.0); // Fastest clock (500 kSps) + adc_gpio_init(26); // GP26 is ADC 0 for Q channel + adc_gpio_init(27); // GP27 is ADC 1 for I channel + adc_gpio_init(28); // GP28 is ADC 2 for Audio channel + adc_set_round_robin(0x01+0x02+0x04); // Sequence ADC 0-1-2 (GP 26, 27, 28) free running + adc_select_input(0); // Start with ADC0 + adc_fifo_setup(true,false,3,false,false); // IRQ result, fifo threshold = 1, so IRQ after each ADC0..2 + irq_set_exclusive_handler(ADC_IRQ_FIFO, adcfifo_handler); // Install ISR at ADC_IRQ_FIFO vector (22) + irq_set_priority (ADC_IRQ_FIFO, PICO_HIGHEST_IRQ_PRIORITY); // Prevent race condition with timer + irq_set_enabled(ADC_IRQ_FIFO, true); // Enable interrupt vector in NVIC + adc_irq_set_enabled(true); // Enable the ADC FIFO interrupt adc_run(true); + adc_level[0] = ADC_BIAS/2; + adc_level[1] = ADC_BIAS/2; + adc_level[2] = ADC_BIAS/2; - // Consider using alarm_pool_add_repeating_timer_us() for a core1 associated timer - // First create an alarm pool on core1: alarm_pool_create(HWalarm, Ntimers) - // For the core1 alarm pool don't use default HWalarm (usually 3) but e.g. 1 - // Timer callback signals semaphore, while loop blocks on getting it + // Use alarm_pool_add_repeating_timer_us() for a core1 associated timer + // First create an alarm pool on core1: + // alarm_pool_t *alarm_pool_create( uint hardware_alarm_num, + // uint max_timers); + // For the core1 alarm pool don't use the default alarm_num (usually 3) but e.g. 1 + // Timer callback signals semaphore, while loop blocks on getting it. + // Initialize repeating timer on core1: + // bool alarm_pool_add_repeating_timer_us( alarm_pool_t *pool, + // int64_t delay_us, + // repeating_timer_callback_t callback, + // void *user_data, + // repeating_timer_t *out); + + sem_init(&dsp_sem, 0, 1); + ap = alarm_pool_create(1, 4); + alarm_pool_add_repeating_timer_us( ap, -TIM_US, dsp_callback, NULL, &dsp_timer); + + dsp_overrun = 0; while(1) { - cmd = multicore_fifo_pop_blocking(); // Wait for fifo output + sem_acquire_blocking(&dsp_sem); // Wait until timer callback releases sem + dsp_overrun--; // Decrement overrun counter - tx_enabled = vox() || ptt_active; // Sample audio and check level - if (tx_enabled) + // Use adc_level[2] for VOX + vox_active = false; // Normally false + if (vox_level != 0) // Only when VOX is enabled { - if (vox_level != VOX_OFF) // Only when vox is enabled - gpio_put(GP_PTT, false); // drive PTT low (active) - tx(); + if ((adc_level[2]>>LSH) > vox_level) // AND level > limit + { + vox_count = S_RATE * VOX_LINGER / 1000; // While audio present, reset linger timer + vox_active = true; // and keep TX active + } + else if (--vox_count>0) // else decrement linger counter + vox_active = true; // and keep TX active until 0 + + } + + if (tx_enabled) // Use previous setting + { + gpio_put(GP_PTT, false); // Drive PTT low (active) + tx(); // Do TX signal processing } else { - if (vox_level != VOX_OFF) // Only when vox is enabled - gpio_put(GP_PTT, true); // drive PTT high (inactive) - rx(); + gpio_put(GP_PTT, true); // Drive PTT high (inactive) + rx(); // Do RX signal processing } - if (multicore_fifo_rvalid()) - fifo_overrun++; // Check for missed events - } + + tx_enabled = vox_active || ptt_active; // Check RX or TX + +#if DSP_FFT == 1 + dsp_tickx = dsp_tick; +#endif + } } -/* - * CORE0: - * Timer callback, triggers core1 through inter-core fifo. - * Either TX or RX, but could do both when testing in loopback on I+Q channels. - */ -struct repeating_timer dsp_timer; -bool dsp_callback(struct repeating_timer *t) -{ - if (tx_enabled) - multicore_fifo_push_blocking(DSP_TX); // Send TX to core 1 through fifo - else - multicore_fifo_push_blocking(DSP_RX); // Send RX to core 1 through fifo - fifo_incnt++; - return true; -} -/* - * CORE0: - * Initialize dsp context and spawn CORE1 process - * - * Some CORE1 code parts should not run from Flash, but be loaded in SRAM at boot time - * See platform.h for function qualifier macro's - * for example: - * void __not_in_flash_func(funcname)(int arg1, float arg2) - * { - * } - * - * Need to set BUS_PRIORITY of Core 1 to high - * #include bus_ctrl.h - * bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_PROC1_BITS; // Set Core 1 prio high - */ +/** CORE0: Initialize dsp context and spawn CORE1 process **/ void dsp_init() { - multicore_launch_core1(dsp_loop); // Start processing on core1 - add_repeating_timer_us(-DSP_US, dsp_callback, NULL, &dsp_timer); + bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_PROC1_BITS; // Set Core 1 prio on bus to high + multicore_launch_core1(dsp_loop); // Start processing on Core 1 } + + +/* DMA EXAMPLE, should convert to chained DMA to reload after 3 words + // Init GPIO for analogue use: hi-Z, no pulls, disable digital input buffer. + adc_gpio_init(26 + CAPTURE_CHANNEL); + + adc_init(); + adc_select_input(CAPTURE_CHANNEL); + adc_fifo_setup( + true, // Write each completed conversion to the sample FIFO + true, // Enable DMA data request (DREQ) + 1, // DREQ (and IRQ) asserted when at least 1 sample present + false, // We won't see the ERR bit because of 8 bit reads; disable. + true // Shift each sample to 8 bits when pushing to FIFO + ); + + // Divisor of 0 -> full speed. Free-running capture with the divider is + // equivalent to pressing the ADC_CS_START_ONCE button once per `div + 1` + // cycles (div not necessarily an integer). Each conversion takes 96 + // cycles, so in general you want a divider of 0 (hold down the button + // continuously) or > 95 (take samples less frequently than 96 cycle + // intervals). This is all timed by the 48 MHz ADC clock. + adc_set_clkdiv(0); + + printf("Arming DMA\n"); + sleep_ms(1000); + // Set up the DMA to start transferring data as soon as it appears in FIFO + uint dma_chan = dma_claim_unused_channel(true); + dma_channel_config cfg = dma_channel_get_default_config(dma_chan); + + // Reading from constant address, writing to incrementing byte addresses + channel_config_set_transfer_data_size(&cfg, DMA_SIZE_8); + channel_config_set_read_increment(&cfg, false); + channel_config_set_write_increment(&cfg, true); + + // Pace transfers based on availability of ADC samples + channel_config_set_dreq(&cfg, DREQ_ADC); + + dma_channel_configure(dma_chan, &cfg, + capture_buf, // dst + &adc_hw->fifo, // src + CAPTURE_DEPTH, // transfer count + true // start immediately + ); + + printf("Starting capture\n"); + adc_run(true); + + // Once DMA finishes, stop any new conversions from starting, and clean up + // the FIFO in case the ADC was still mid-conversion. + dma_channel_wait_for_finish_blocking(dma_chan); + printf("Capture finished\n"); + adc_run(false); + adc_fifo_drain(); + + // Print samples to stdout so you can display them in pyplot, excel, matlab + for (int i = 0; i < CAPTURE_DEPTH; ++i) { + printf("%-3d, ", capture_buf[i]); + if (i % 10 == 9) + printf("\n"); + } +*/ diff --git a/dsp.h b/dsp.h index 69a35ea..fec5767 100644 --- a/dsp.h +++ b/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 diff --git a/dsp_fft.c b/dsp_fft.c new file mode 100644 index 0000000..d16964c --- /dev/null +++ b/dsp_fft.c @@ -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 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 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 2) b = 0; // Point to oldest + ap = &A_buf[b][0]; xip = &XI_buf[0]; + for (i=0; iTO 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; +} + + + diff --git a/fix_fft.c b/fix_fft.c new file mode 100644 index 0000000..9c98d5b --- /dev/null +++ b/fix_fft.c @@ -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 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 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 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 diff --git a/fix_fft.h b/fix_fft.h new file mode 100644 index 0000000..017e5a0 --- /dev/null +++ b/fix_fft.h @@ -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 diff --git a/hmi.c b/hmi.c index b2aecd7..c992b82 100644 --- a/hmi.c +++ b/hmi.c @@ -100,36 +100,41 @@ #define HMI_NEVENTS 9 /* Sub menu option string sets */ +#define HMI_NTUNE 6 #define HMI_NMODE 4 #define HMI_NAGC 3 #define HMI_NPRE 5 #define HMI_NVOX 4 #define HMI_NBPF 5 -char hmi_o_menu[HMI_NSTATES][8] = {"Tune","Mode","AGC","Pre","VOX"}; // Indexed by hmi_state -char hmi_o_mode[HMI_NMODE][8] = {"USB","LSB","AM","CW"}; // Indexed by hmi_sub[HMI_S_MODE] -char hmi_o_agc [HMI_NAGC][8] = {"NoGC","Slow","Fast"}; // Indexed by hmi_sub[HMI_S_AGC] -char hmi_o_pre [HMI_NPRE][8] = {"-30dB","-20dB","-10dB","0dB","+10dB"}; // Indexed by hmi_sub[HMI_S_PRE] -char hmi_o_vox [HMI_NVOX][8] = {"NoVOX","VOX-L","VOX-M","VOX-H"}; // Indexed by hmi_sub[HMI_S_VOX] -char hmi_o_bpf [HMI_NBPF][8] = {"<2.5","2-6","5-12","10-24","20-40"}; +char hmi_noption[HMI_NSTATES] = {HMI_NTUNE, HMI_NMODE, HMI_NAGC, HMI_NPRE, HMI_NVOX, HMI_NBPF}; +char hmi_o_menu[HMI_NSTATES][8] = {"Tune","Mode","AGC","Pre","VOX"}; // Indexed by hmi_state +char hmi_o_mode[HMI_NMODE][8] = {"USB","LSB","AM ","CW "}; // Indexed by hmi_sub[HMI_S_MODE] +char hmi_o_agc [HMI_NAGC][8] = {"NoGC","Slow","Fast"}; // Indexed by hmi_sub[HMI_S_AGC] +char hmi_o_pre [HMI_NPRE][8] = {"-30dB","-20dB","-10dB","0dB","+10dB"}; // Indexed by hmi_sub[HMI_S_PRE] +char hmi_o_vox [HMI_NVOX][8] = {"NoVOX","VOX-L","VOX-M","VOX-H"}; // Indexed by hmi_sub[HMI_S_VOX] +char hmi_o_bpf [HMI_NBPF][8] = {"<2.5","2-6","5-12","10-24","20-40"}; // Indexed by // Map option to setting -uint8_t hmi_pre[5] = {REL_ATT_30, REL_ATT_20, REL_ATT_10, REL_ATT_00, REL_PRE_10}; -uint8_t hmi_bpf[5] = {REL_LPF2, REL_BPF6, REL_BPF12, REL_BPF24, REL_BPF40}; +int hmi_mode[4] = {MODE_USB, MODE_LSB, MODE_AM, MODE_CW}; +int hmi_agc[3] = {AGC_NONE, AGC_SLOW, AGC_FAST}; +int hmi_pre[5] = {REL_ATT_30, REL_ATT_20, REL_ATT_10, REL_ATT_00, REL_PRE_10}; +int hmi_vox[4] = {VOX_OFF, VOX_LOW, VOX_MEDIUM, VOX_HIGH}; +int hmi_bpf[5] = {REL_LPF2, REL_BPF6, REL_BPF12, REL_BPF24, REL_BPF40}; -uint8_t hmi_state, hmi_option; // Current state and option selection -uint8_t hmi_sub[HMI_NSTATES] = {4,0,0,3,0,2}; // Stored option selection per state -bool hmi_update; +int hmi_state, hmi_option; // Current state and menu option selection +int hmi_sub[HMI_NSTATES] = {4,0,0,3,0,2}; // Stored option selection per state +bool hmi_update; // LCD needs update -uint32_t hmi_freq; // Frequency from Tune state -uint32_t hmi_step[6] = {10000000, 1000000, 100000, 10000, 1000, 100}; // Frequency digit increments +uint32_t hmi_freq; // Frequency from Tune state +uint32_t hmi_step[6] = {10000000, 1000000, 100000, 10000, 1000, 100}; // Frequency digit increments #define HMI_MAXFREQ 30000000 #define HMI_MINFREQ 100 -#define HMI_MULFREQ 1 // Factor between HMI and actual frequency - // Set to 2 for certain types of mixer +#define HMI_MULFREQ 1 // Factor between HMI and actual frequency + // Set to 2 for certain types of mixer -#define PTT_DEBOUNCE 3 // Nr of cycles for debounce -int ptt_state; // Debounce counter -bool ptt_active; // Resulting state +#define PTT_DEBOUNCE 3 // Nr of cycles for debounce +int ptt_state; // Debounce counter +bool ptt_active; // Resulting state /* * Some macros @@ -151,93 +156,60 @@ void hmi_handler(uint8_t event) /* Special case for TUNE state */ if (hmi_state == HMI_S_TUNE) { - if (event==HMI_E_ENTER) // Commit current value + switch (event) { - SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Commit frequency + case HMI_E_ENTER: // Commit current value + SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Commit frequency + break; + case HMI_E_ESCAPE: // Enter submenus + hmi_sub[hmi_state] = hmi_option; // Store selection (i.e. digit) + hmi_state = HMI_S_MODE; // Should remember last one + hmi_option = hmi_sub[hmi_state]; // Restore selection of new state + break; + case HMI_E_INCREMENT: + if (hmi_freq < (HMI_MAXFREQ - hmi_step[hmi_option])) // Boundary check + hmi_freq += hmi_step[hmi_option]; // Increment selected digit + break; + case HMI_E_DECREMENT: + if (hmi_freq > (HMI_MINFREQ + hmi_step[hmi_option])) // Boundary check + hmi_freq -= hmi_step[hmi_option]; // Decrement selected digit + break; + case HMI_E_RIGHT: + hmi_option = (hmi_option<5)?hmi_option+1:5; // Digit to the right + break; + case HMI_E_LEFT: + hmi_option = (hmi_option>0)?hmi_option-1:0; // Digit to the left + break; } - if (event==HMI_E_ESCAPE) // Enter submenus - { - hmi_sub[hmi_state] = hmi_option; // Store selection (i.e. digit) - hmi_state = HMI_S_MODE; // Should remember last one - hmi_option = hmi_sub[hmi_state]; // Restore selection of new state - } - if (event==HMI_E_INCREMENT) - { - if (hmi_freq < (HMI_MAXFREQ - hmi_step[hmi_option])) // Boundary check - hmi_freq += hmi_step[hmi_option]; // Increment selected digit - } - if (event==HMI_E_DECREMENT) - { - if (hmi_freq > (hmi_step[hmi_option] + HMI_MINFREQ)) // Boundary check - hmi_freq -= hmi_step[hmi_option]; // Decrement selected digit - } - if (event==HMI_E_RIGHT) - { - hmi_option = (hmi_option<6)?hmi_option+1:6; // Digit to the right - } - if (event==HMI_E_LEFT) - { - hmi_option = (hmi_option>0)?hmi_option-1:0; // Digit to the left - } - return; // Early bail-out + return; // Early bail-out } - /* Submenu states */ - switch(hmi_state) + /* Actions for other states */ + switch (event) { - case HMI_S_MODE: - if (event==HMI_E_INCREMENT) - hmi_option = (hmi_option0)?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_option0)?hmi_option-1:0; + case HMI_E_ESCAPE: + hmi_state = HMI_S_TUNE; // Leave submenus + hmi_option = hmi_sub[hmi_state]; // Restore selection of new state break; - case HMI_S_PRE: - if (event==HMI_E_INCREMENT) - hmi_option = (hmi_option0)?hmi_option-1:0; + case HMI_E_RIGHT: + hmi_state = (hmi_state0)?hmi_option-1:0; + case HMI_E_LEFT: + hmi_state = (hmi_state>1)?(hmi_state-1):HMI_NSTATES-1; // Change submenu + hmi_option = hmi_sub[hmi_state]; // Restore selection of new state break; - case HMI_S_BPF: - if (event==HMI_E_INCREMENT) - hmi_option = (hmi_option0)?hmi_option-1:0; + case HMI_E_INCREMENT: + hmi_option = (hmi_option1)?(hmi_state-1):HMI_NSTATES-1; // Change submenu - hmi_option = hmi_sub[hmi_state]; // Restore selection of new state - } + case HMI_E_DECREMENT: + hmi_option = (hmi_option>0)?hmi_option-1:0; + break; + } } /* @@ -250,23 +222,23 @@ void hmi_callback(uint gpio, uint32_t events) switch (gpio) { - case GP_ENC_A: // Encoder + case GP_ENC_A: // Encoder if (events&GPIO_IRQ_EDGE_FALL) evt = gpio_get(GP_ENC_B)?HMI_E_INCREMENT:HMI_E_DECREMENT; break; - case GP_AUX_0: // Enter + case GP_AUX_0: // Enter if (events&GPIO_IRQ_EDGE_FALL) evt = HMI_E_ENTER; break; - case GP_AUX_1: // Escape + case GP_AUX_1: // Escape if (events&GPIO_IRQ_EDGE_FALL) evt = HMI_E_ESCAPE; break; - case GP_AUX_2: // Previous + case GP_AUX_2: // Previous if (events&GPIO_IRQ_EDGE_FALL) evt = HMI_E_LEFT; break; - case GP_AUX_3: // Next + case GP_AUX_3: // Next if (events&GPIO_IRQ_EDGE_FALL) evt = HMI_E_RIGHT; break; @@ -274,7 +246,7 @@ void hmi_callback(uint gpio, uint32_t events) return; } - hmi_handler(evt); // Invoke state machine + hmi_handler(evt); // Invoke state machine } /* @@ -301,6 +273,7 @@ void hmi_init(void) gpio_pull_up(GP_AUX_2); gpio_pull_up(GP_AUX_3); gpio_pull_up(GP_PTT); + gpio_set_oeover(GP_PTT, GPIO_OVERRIDE_HIGH); // Enable output on PTT GPIO; bidirectional // Enable interrupt on level low gpio_set_irq_enabled(GP_ENC_A, GPIO_IRQ_EDGE_ALL, true); @@ -315,13 +288,13 @@ void hmi_init(void) // Initialize LCD and set VFO hmi_state = HMI_S_TUNE; - hmi_option = 4; // Active kHz digit - hmi_freq = 7074000UL; // Initial frequency + hmi_option = 4; // Active kHz digit + hmi_freq = 7074000UL; // Initial frequency - SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Set freq to 7074 kHz (depends on mixer type) - SI_SETPHASE(0, 1); // Set phase to 90deg (depends on mixer type) + SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Set freq to 7074 kHz (depends on mixer type) + SI_SETPHASE(0, 1); // Set phase to 90deg (depends on mixer type) - ptt_state = 0; + ptt_state = PTT_DEBOUNCE; ptt_active = false; dsp_setmode(hmi_sub[HMI_S_MODE]); @@ -381,42 +354,31 @@ void hmi_evaluate(void) } /* PTT debouncing */ - if (hmi_sub[HMI_S_VOX] == 0) // No VOX active + if (gpio_get(GP_PTT)) // Get PTT level { - gpio_set_dir(GP_PTT, false); // PTT input - if (gpio_get(GP_PTT)) // Get PTT level - { - if (ptt_state0) // Decrement debounce counter when low - ptt_state--; - } - if (ptt_state == PTT_DEBOUNCE) // Reset PTT when debounced level high - ptt_active = false; - if (ptt_state == 0) // Set PTT when debounced level low - ptt_active = true; + if (ptt_state0) // Decrement debounce counter when low + ptt_state--; + } + if (ptt_state == PTT_DEBOUNCE) // Reset PTT when debounced level high ptt_active = false; - gpio_set_dir(GP_PTT, true); // PTT output - } + if (ptt_state == 0) // Set PTT when debounced level low + ptt_active = true; + /* Set parameters corresponding to latest entered option value */ - SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); - dsp_setmode(hmi_sub[HMI_S_MODE]); - dsp_setvox(hmi_sub[HMI_S_VOX]); - dsp_setagc(hmi_sub[HMI_S_AGC]); - if (hmi_update) + SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Always set frequency + if (hmi_update) // Others only when indicated { dsp_setmode(hmi_sub[HMI_S_MODE]); dsp_setvox(hmi_sub[HMI_S_VOX]); dsp_setagc(hmi_sub[HMI_S_AGC]); relay_setband(hmi_bpf[hmi_sub[HMI_S_BPF]]); - sleep_ms(1); // I2C doesn't work without... + sleep_ms(1); // I2C doesn't work without... relay_setattn(hmi_pre[hmi_sub[HMI_S_PRE]]); hmi_update = false; } diff --git a/hmi.h b/hmi.h index de0f5d0..78a2dcd 100644 --- a/hmi.h +++ b/hmi.h @@ -9,6 +9,8 @@ * See hmi.c for more information */ +#define CARRIER_OFFSET 3500 + extern bool ptt_active; void hmi_init(void); diff --git a/lcd.c b/lcd.c index 182cd97..1681022 100644 --- a/lcd.c +++ b/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 #include @@ -43,20 +49,20 @@ #include "lcd.h" /** User selectable definitions **/ -// Set I2C address -//#define I2C_LCD 0x27 -#define I2C_LCD 0x3E +// Set I2C address for your device +#define I2C_LCD 0x3E // Grove: 0x3E, 8574 backpack: 0x20..0x27 -// Select LCD type matching your HW -#define LCD_1804 0 // Seeed / Grove -#define LCD_8574_ADA 1 // Adafruit I2C backpack -#define LCD_8574_GEN 2 // Generic I2C backpack +// Select LCD type to match your device, +// or define a new one when code changes are needed. +#define LCD_1804 0 // Seeed / Grove +#define LCD_8574_ADA 1 // Adafruit I2C backpack +#define LCD_8574_GEN 2 // Generic I2C backpack #define LCD_TYPE LCD_1804 -/** HD44780 interface **/ +/** Generic HD44780 interface **/ // commands -#define LCD_CLEARDISPLAY 0x01 // Note: LCD_ENTRYINC is set +#define LCD_CLEARDISPLAY 0x01 // Note: LCD_ENTRYINC is set #define LCD_RETURNHOME 0x02 #define LCD_ENTRYMODESET 0x04 #define LCD_DISPLAYCONTROL 0x08 @@ -68,8 +74,8 @@ // flags for display entry mode: LCD_ENTRYMODESET #define LCD_ENTRYSHIFT 0x01 #define LCD_ENTRYNOSHIFT 0x00 -#define LCD_ENTRYINC 0x02 // Also applies to CGRAM writes -#define LCD_ENTRYDEC 0x00 // Also applies to CGRAM writes +#define LCD_ENTRYINC 0x02 // Also applies to CGRAM writes +#define LCD_ENTRYDEC 0x00 // Also applies to CGRAM writes // flags for display on/off control: LCD_DISPLAYCONTROL #define LCD_DISPLAYON 0x04 @@ -125,10 +131,8 @@ #endif - /** Other definitions **/ -#define LCD_DELAY 100 // Delay for regular write - +#define LCD_DELAY 100 // Delay for regular write /* @@ -144,8 +148,8 @@ uint8_t cgram[8][8] = {0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00}, // 0x03: Level 3 {0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00}, // 0x04: Level 4 {0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00}, // 0x05: Level 5 - {0x00, 0x04, 0x04, 0x04, 0x1f, 0x0e, 0x04, 0x00}, // 0x06: Receive arrow down - {0x04, 0x0e, 0x1f, 0x04, 0x04, 0x04, 0x00, 0x00} // 0x07: Transmit arrow up + {0x00, 0x04, 0x04, 0x04, 0x15, 0x0e, 0x04, 0x00}, // 0x06: Receive arrow down + {0x04, 0x0e, 0x15, 0x04, 0x04, 0x04, 0x00, 0x00} // 0x07: Transmit arrow up }; /* @@ -202,7 +206,7 @@ void lcd_sendbyte(uint8_t command, uint8_t data) } /* - * It seems that there is too much in here, but it doesn't harm either. + * It seems that there is too much init here, but it doesn't harm either. */ void lcd_init(void) { @@ -236,9 +240,9 @@ void lcd_init(void) /* Load CGRAM */ for (i=0; i<8; i++) { - lcd_sendbyte(true, LCD_SETCGRAMADDR | (i<<3)); //Set CGRAM address + lcd_sendbyte(true, LCD_SETCGRAMADDR | (i<<3)); //Set CGRAM address for (int j=0; j<8; j++) - lcd_sendbyte(false, cgram[i][j]); // One byte at a time + lcd_sendbyte(false, cgram[i][j]); // One byte at a time } } @@ -253,7 +257,7 @@ void lcd_curxy(uint8_t x, uint8_t y, bool on) { uint8_t txdata[3]; - x &= 0x0f; // Clip range + x &= 0x0f; // Clip range y &= 0x01; lcd_sendbyte(true, (x | 0x80 | (y==1?0x40:0x00))); lcd_sendbyte(true, LCD_DISPLAYCONTROL | LCD_DISPLAYON | (on?LCD_CURSORON:LCD_CURSOROFF) | LCD_BLINKOFF); @@ -261,7 +265,7 @@ void lcd_curxy(uint8_t x, uint8_t y, bool on) void lcd_putxy(uint8_t x, uint8_t y, uint8_t c) { - x &= 0x0f; // Clip range + x &= 0x0f; // Clip range y &= 0x01; lcd_sendbyte(true, (x | 0x80 | (y==1?0x40:0x00))); lcd_sendbyte(false, c); @@ -271,12 +275,12 @@ void lcd_writexy(uint8_t x, uint8_t y, uint8_t *s) { uint8_t i, len; - x &= 0x0f; // Clip range + x &= 0x0f; // Clip range y &= 0x01; lcd_sendbyte(true, (x | 0x80 | (y==1?0x40:0x00))); len = strlen(s); - len = (len>(16-x))?(16-x):len; // Clip range + len = (len>(16-x))?(16-x):len; // Clip range for(i=0; i=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 ", "Dumps Si5351 registers"}, - {"lt", 2, &mon_lt, "lt (no parameters)", "LCD test, dumps characterset on LCD"}, - {"fo", 2, &mon_fo, "fo (no parameters)", "Returns inter core fifo overruns"}, - {"pt", 2, &mon_pt, "pt (no parameters)", "Toggles PTT status"}, - {"bp", 2, &mon_bp, "bp {r|w} ", "Read or Write BPF relays"}, - {"rx", 2, &mon_rx, "rx {r|w} ", "Read or Write RX relays"} + {"si", 2, &mon_si, "si ", "Dumps Si5351 registers"}, + {"lt", 2, &mon_lt, "lt (no parameters)", "LCD test, dumps characterset on LCD"}, + {"or", 2, &mon_or, "or (no parameters)", "Returns overrun information"}, + {"pt", 2, &mon_pt, "pt (no parameters)", "Toggles PTT status"}, + {"bp", 2, &mon_bp, "bp {r|w} ", "Read or Write BPF relays"}, + {"rx", 2, &mon_rx, "rx {r|w} ", "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 | * MSN | -- Fvco --> | / MSi | --> | / Ri | -- Fout --> +-------+ +-------+ +------+ +Details: +======== ---Derivation of Fout--- MSN determines: Fvco = Fxtal * (MSN) , where MSN = a + b/c MSi and Ri determine: Fout = Fvco / (Ri*MSi) , where MSi = a + b/c (different a, b and c) @@ -59,7 +72,6 @@ Quadrature Phase offsets (i.e. delay): - Set INV bit (reg 17) to add 180 deg. NOTE: Phase offsets only work when Ri = 1, this means minimum Fout is 4.762MHz at Fvco = 600MHz. Additional flip/flop dividers are needed to get 80m band frequencies, or Fvco must be tuned below spec. - Control Si5351 (see AN619): =========================== @@ -117,7 +129,7 @@ Control Si5351 (see AN619): ==== 183 | XTAL_CL | Reserved | ==== - + * */ #include @@ -128,7 +140,7 @@ Control Si5351 (see AN619): #include "hardware/clocks.h" #include "si5351.h" -#define I2C_VFO 0x60 // I2C address +#define I2C_VFO 0x60 // I2C address // SI5351 register address definitions #define SI_CLK_OE 3 @@ -148,34 +160,36 @@ Control Si5351 (see AN619): #define SI_XTAL_LOAD 183 // CLK_OE register 3 values -#define SI_CLK0_ENABLE 0b00000001 // Enable clock 0 output -#define SI_CLK1_ENABLE 0b00000010 // Enable clock 1 output -#define SI_CLK2_ENABLE 0b00000100 // Enable clock 2 output +#define SI_CLK0_ENABLE 0b00000001 // Enable clock 0 output +#define SI_CLK1_ENABLE 0b00000010 // Enable clock 1 output +#define SI_CLK2_ENABLE 0b00000100 // Enable clock 2 output // CLKi_CTL register 16, 17, 18 values // Normally 0x4f for clk 0 and 1, 0x6f for clk 2 -#define SI_CLK_INT 0b01000000 // Set integer mode -#define SI_CLK_PLL 0b00100000 // Select PLL B as MS source (default 0 = PLL A) -#define SI_CLK_INV 0b00010000 // Invert output (i.e. phase + 180deg) -#define SI_CLK_SRC 0b00001100 // Select output source: 11=MS, 00=XTAL direct -#define SI_CLK_DRV 0b00000011 // Select output drive, increasingly: 2-4-6-8 mA (best risetime, use max = 11) +#define SI_CLK_INT 0b01000000 // Set integer mode +#define SI_CLK_PLL 0b00100000 // Select PLL B as MS source (default 0 = PLL A) +#define SI_CLK_INV 0b00010000 // Invert output (i.e. phase + 180deg) +#define SI_CLK_SRC 0b00001100 // Select output source: 11=MS, 00=XTAL direct +#define SI_CLK_DRV 0b00000011 // Select output drive, increasingly: 2-4-6-8 mA (best risetime, use max = 11) // PLL_RESET register 177 values -#define SI_PLLB_RST 0b10000000 // Reset PLL B -#define SI_PLLA_RST 0b00100000 // Reset PLL A +#define SI_PLLB_RST 0b10000000 // Reset PLL B +#define SI_PLLA_RST 0b00100000 // Reset PLL A -#define SI_XTAL_FREQ 25001414UL // Replace with measured crystal frequency of XTAL for CL = 10pF (default) +#define SI_XTAL_FREQ 25001414UL // Replace with measured crystal frequency of XTAL for CL = 10pF (default) #define SI_MSN_LO ((0.6e9)/SI_XTAL_FREQ) #define SI_MSN_HI ((0.9e9)/SI_XTAL_FREQ) -#define SI_PLL_C 1000000UL // Parameter c for PLL-A and -B setting +#define SI_PLL_C 1000000UL // Parameter c for PLL-A and -B setting -vfo_t vfo[2]; // 0: clk0 and clk1 1: clk2 +vfo_t vfo[2]; // 0: clk0 & clk1 1: clk2 -/* read contents of SI5351 registers, from reg to reg+len-1, output in data array */ +/* + * read contents of SI5351 registers, from reg to reg+len-1, output in data array + */ int si_getreg(uint8_t *data, uint8_t reg, uint8_t len) { int ret; @@ -188,24 +202,22 @@ int si_getreg(uint8_t *data, uint8_t reg, uint8_t len) } -// Set up MSN PLL divider for vfo[i], assuming MSN has been set in vfo[i] -// Optimize for speed, this may be called with short intervals -// See also SiLabs AN619 section 3.2 +/* + * Set up MSN PLL divider for vfo[i], assuming MSN has been set in vfo[i] + * Optimize for speed, this may be called with short intervals + * See also SiLabs AN619 section 3.2 + */ void si_setmsn(uint8_t i) { - uint8_t data[16]; // I2C trx buffer - uint32_t P1, P2; // MSN parameters + uint8_t data[16]; // I2C trx buffer + uint32_t P1, P2; // MSN parameters uint32_t A; uint32_t B; i=(i>0?1:0); -/* - P1 = 128*a + Floor(128*b/c) - 512 - P2 = 128*b - c*Floor(128*b/c) - P3 = c (P3 = 1000000 for MSN tuning) -*/ - A = (uint32_t)(floor(vfo[i].msn)); // A is integer part of MSN - B = (uint32_t)((vfo[i].msn - (float)A) * SI_PLL_C); // B is C * fraction part of MSN (C is a constant) + + A = (uint32_t)(floor(vfo[i].msn)); // A is integer part of MSN + B = (uint32_t)((vfo[i].msn - (float)A) * SI_PLL_C); // B is C * fraction part of MSN (C is a constant) P2 = (uint32_t)(floor((float)(128 * B) / (float)SI_PLL_C)); P1 = (uint32_t)(128 * A + P2 - 512); P2 = (uint32_t)(128 * B - SI_PLL_C * P2); @@ -223,21 +235,19 @@ void si_setmsn(uint8_t i) i2c_write_blocking(i2c0, I2C_VFO, data, 9, false); } -// Set up registers with MS and R divider for vfo[i], assuming values have been set in vfo[i] -// In this implementation we only use integer mode, i.e. b=0 and P3=1 -// See also SiLabs AN619 section 4.1 +/* + * Set up registers with MS and R divider for vfo[i], assuming values have been set in vfo[i] + * In this implementation we only use integer mode, i.e. b=0 and P3=1 + * See also SiLabs AN619 section 4.1 + */ void si_setmsi(uint8_t i) { - uint8_t data[16]; // I2C trx buffer + uint8_t data[16]; // I2C trx buffer uint32_t P1; uint8_t R; i=(i>0?1:0); -/* - P1 = 128*a + Floor(128*b/c) - 512 - P2 = 128*b - c*Floor(128*b/c) (P2 = 0 for MSi integer mode) - P3 = c (P3 = 1 for MSi integer mode) -*/ + P1 = (uint32_t)(128*(uint32_t)floor(vfo[i].msi) - 512); R = vfo[i].ri; R = (R&0xf0) ? ((R&0xc0)?((R&0x80)?7:6):(R&0x20)?5:4) : ((R&0x0c)?((R&0x08)?3:2):(R&0x02)?1:0); // quick log2(r) @@ -256,25 +266,25 @@ void si_setmsi(uint8_t i) // If vfo[0] also set clk 1 if (i==0) { - data[0] = SI_SYNTH_MS1; // Same data in synthesizer + data[0] = SI_SYNTH_MS1; // Same data in synthesizer i2c_write_blocking(i2c0, I2C_VFO, data, 9, false); - if (vfo[0].phase&1) // Phase is either 90 or 270 deg? + if (vfo[0].phase&1) // Phase is either 90 or 270 deg? { data[0] = SI_CLK1_PHOFF; data[1] = vfo[0].msi; i2c_write_blocking(i2c0, I2C_VFO, data, 2, false); } - else // Phase is 0 or 180 deg + else // Phase is 0 or 180 deg { data[0] = SI_CLK1_PHOFF; data[1] = 0; i2c_write_blocking(i2c0, I2C_VFO, data, 2, false); } - if (vfo[0].phase&2) // Phase is 180 or 270 deg? + if (vfo[0].phase&2) // Phase is 180 or 270 deg? { data[0] = SI_CLK1_CTL; - data[1] = 0x5d; // CLK1: INT, PLLA, INV, MS, 8mA + data[1] = 0x5d; // CLK1: INT, PLLA, INV, MS, 8mA i2c_write_blocking(i2c0, I2C_VFO, data, 2, false); } } @@ -286,10 +296,12 @@ void si_setmsi(uint8_t i) } -// For each vfo, calculate required MSN setting, MSN = MSi*Ri*Fout/Fxtal -// If in range, just set MSN registers -// If not in range, recalculate MSi and Ri and also MSN -// Set MSN, MSi and Ri registers (implicitly resets PLL) +/* + * For each vfo, calculate required MSN setting, MSN = MSi*Ri*Fout/Fxtal + * If in range, just set MSN registers + * If not in range, recalculate MSi and Ri and also MSN + * Set MSN, MSi and Ri registers (implicitly resets PLL) + */ void si_evaluate(void) { float msn; @@ -327,22 +339,24 @@ void si_evaluate(void) } -// Initialize the Si5351 VFO registers +/* + * Initialize the Si5351 VFO registers + * Hard initialize Synth registers to: 7.074MHz, CLK1 90 deg ahead, PLLA for CLK 0&1, PLLB for CLK2 + | Ri=1, + | MSi=68, P1=8192, P2=0, P3=1 + | MSN=27.2 P1=2969, P2=600000, P3=1000000 + */ void si_init(void) { - uint8_t data[16]; // I2C trx buffer + uint8_t data[16]; // I2C trx buffer - // Hard initialize Synth registers: 7.074MHz, CLK1 90 deg ahead, PLLA for CLK 0&1, PLLB for CLK2 - // Ri=1, - // MSi=68, P1=8192, P2=0, P3=1 - // MSN=27.2 P1=2969, P2=600000, P3=1000000 - vfo[0].freq = 10000000; + vfo[0].freq = 10000000; // Check this, should be 7074000? vfo[0].flag = 0; vfo[0].phase = 1; vfo[0].ri = 1; vfo[0].msi = 68; vfo[0].msn = 27.2; - vfo[1].freq = 10000000; + vfo[1].freq = 10000000; // Check this, should be 7074000? vfo[1].flag = 0; vfo[1].phase = 0; vfo[1].ri = 1; @@ -351,53 +365,53 @@ void si_init(void) // PLLA: MSN P1=0x00000b99, P2=0x000927c0, P3=0x000f4240 data[0] = SI_SYNTH_PLLA; - data[1] = 0x42; // MSNA_P3[15:8] - data[2] = 0x40; // MSNA_P3[7:0] - data[3] = 0x00; // 0b000000 , MSNA_P1[17:16] - data[4] = 0x0b; // MSNA_P1[15:8] - data[5] = 0x99; // MSNA_P1[7:0] - data[6] = 0xf9; // MSNA_P3[19:16] , MSNA_P2[19:16] - data[7] = 0x27; // MSNA_P2[15:8] - data[8] = 0xc0; // MSNA_P2[7:0] + data[1] = 0x42; // MSNA_P3[15:8] + data[2] = 0x40; // MSNA_P3[7:0] + data[3] = 0x00; // 0b000000 , MSNA_P1[17:16] + data[4] = 0x0b; // MSNA_P1[15:8] + data[5] = 0x99; // MSNA_P1[7:0] + data[6] = 0xf9; // MSNA_P3[19:16] , MSNA_P2[19:16] + data[7] = 0x27; // MSNA_P2[15:8] + data[8] = 0xc0; // MSNA_P2[7:0] i2c_write_blocking(i2c0, I2C_VFO, data, 9, false); // PLLB: MSN P1=0x00000b99, P2=0x000927c0, P3=0x000f4240 - data[0] = SI_SYNTH_PLLB; // Same content + data[0] = SI_SYNTH_PLLB; // Same content i2c_write_blocking(i2c0, I2C_VFO, data, 9, false); // MS0 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1 data[0] = SI_SYNTH_MS0; - data[1] = 0x00; // MS0_P3[15:8] - data[2] = 0x01; // MS0_P3[7:0] - data[3] = 0x00; // 0b0, R0_DIV[2:0] , MS0_DIVBY4[1:0] , MS0_P1[17:16] - data[4] = 0x20; // MS0_P1[15:8] - data[5] = 0x00; // MS0_P1[7:0] - data[6] = 0x00; // MS0_P3[19:16] , MS0_P2[19:16] - data[7] = 0x00; // MS0_P2[15:8] - data[8] = 0x00; // MS0_P2[7:0] + data[1] = 0x00; // MS0_P3[15:8] + data[2] = 0x01; // MS0_P3[7:0] + data[3] = 0x00; // 0b0, R0_DIV[2:0] , MS0_DIVBY4[1:0] , MS0_P1[17:16] + data[4] = 0x20; // MS0_P1[15:8] + data[5] = 0x00; // MS0_P1[7:0] + data[6] = 0x00; // MS0_P3[19:16] , MS0_P2[19:16] + data[7] = 0x00; // MS0_P2[15:8] + data[8] = 0x00; // MS0_P2[7:0] i2c_write_blocking(i2c0, I2C_VFO, data, 9, false); // MS1 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1 - data[0] = SI_SYNTH_MS1; // Same content + data[0] = SI_SYNTH_MS1; // Same content i2c_write_blocking(i2c0, I2C_VFO, data, 9, false); // MS2 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1 - data[0] = SI_SYNTH_MS2; // Same content + data[0] = SI_SYNTH_MS2; // Same content i2c_write_blocking(i2c0, I2C_VFO, data, 9, false); // Phase offsets for 3 clocks data[0] = SI_CLK0_PHOFF; - data[1] = 0x00; // CLK0: phase 0 deg - data[2] = 0x44; // CLK1: phase 90 deg (=MSi) - data[3] = 0x00; // CLK2: phase 0 deg + data[1] = 0x00; // CLK0: phase 0 deg + data[2] = 0x44; // CLK1: phase 90 deg (=MSi) + data[3] = 0x00; // CLK2: phase 0 deg i2c_write_blocking(i2c0, I2C_VFO, data, 4, false); // Output port settings for 3 clocks data[0] = SI_CLK0_CTL; - data[1] = 0x4d; // CLK0: INT, PLLA, nonINV, MS, 4mA - data[2] = 0x4d; // CLK1: INT, PLLA, nonINV, MS, 4mA - data[3] = 0x6f; // CLK2: INT, PLLB, nonINV, MS, 8mA + data[1] = 0x4d; // CLK0: INT, PLLA, nonINV, MS, 4mA + data[2] = 0x4d; // CLK1: INT, PLLA, nonINV, MS, 4mA + data[3] = 0x6f; // CLK2: INT, PLLB, nonINV, MS, 8mA i2c_write_blocking(i2c0, I2C_VFO, data, 4, false); // Disable spread spectrum (startup state is undefined) diff --git a/uSDR.c b/uSDR.c index d291b4a..88d57a8 100644 --- a/uSDR.c +++ b/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,49 +65,56 @@ bool loop_callback(struct repeating_timer *t) int main() { - /* Initialize LED pin output */ + /* + * Optional: increase core voltage (normally 1.1V) + * Optional: overclock the CPU to 250MHz (normally 125MHz) + * Note that clk_peri (e.g. I2C) is derived from the SYS PLL + * Note that clk_adc sampling clock is derived from the 48MHz USB PLL. + */ + //vreg_set_voltage(VREG_VOLTAGE_1_25); sleep_ms(10); + //set_sys_clock_khz(250000, false); sleep_ms(10); + + /* + * Initialize LED pin output + */ gpio_init(PICO_DEFAULT_LED_PIN); gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); - gpio_put(PICO_DEFAULT_LED_PIN, true); // Set LED on + gpio_put(PICO_DEFAULT_LED_PIN, true); // Set LED on add_repeating_timer_ms(-LED_MS, led_callback, NULL, &led_timer); /* * i2c0 is used for the si5351 interface * i2c1 is used for the LCD and all other interfaces + * if the display cannot keep up, try lowering the i2c1 frequency */ - - /* i2c0 initialisation at 400Khz. */ - i2c_init(i2c0, 400*1000); + i2c_init(i2c0, 400000); // i2c0 initialisation at 400Khz gpio_set_function(I2C0_SDA, GPIO_FUNC_I2C); gpio_set_function(I2C0_SCL, GPIO_FUNC_I2C); gpio_pull_up(I2C0_SDA); gpio_pull_up(I2C0_SCL); - - /* i2c1 initialisation at 400Khz. */ - i2c_init(i2c1, 100*1000); + i2c_init(i2c1, 100000); // i2c1 initialisation at 100Khz gpio_set_function(I2C1_SDA, GPIO_FUNC_I2C); gpio_set_function(I2C1_SCL, GPIO_FUNC_I2C); gpio_pull_up(I2C1_SDA); gpio_pull_up(I2C1_SCL); - - /* Initialize units */ - mon_init(); // Monitor shell on stdio - si_init(); // VFO control unit - dsp_init(); // Signal processing unit + /* Initialize the SW units */ + mon_init(); // Monitor shell on stdio + si_init(); // VFO control unit + dsp_init(); // Signal processing unit relay_init(); - lcd_init(); // LCD output unit - hmi_init(); // HMI user inputs + lcd_init(); // LCD output unit + hmi_init(); // HMI user inputs /* A simple round-robin scheduler */ sem_init(&loop_sem, 1, 1) ; add_repeating_timer_ms(-LOOP_MS, loop_callback, NULL, &loop_timer); while (1) { - sem_acquire_blocking(&loop_sem); // Wait until timer callback releases sem - mon_evaluate(); // Check monitor input - si_evaluate(); // Refresh VFO settings - hmi_evaluate(); // Refresh HMI + sem_acquire_blocking(&loop_sem); // Wait until timer callback releases sem + mon_evaluate(); // Check monitor input + si_evaluate(); // Refresh VFO settings + hmi_evaluate(); // Refresh HMI } return 0;