kopia lustrzana https://github.com/ArjanteMarvelde/uSDR-pico
Porównaj commity
7 Commity
61e658c4ac
...
04a3fc2d78
Autor | SHA1 | Data |
---|---|---|
Arjan te Marvelde | 04a3fc2d78 | |
ArjanteMarvelde | 2b957e3f85 | |
Arjan te Marvelde | 02cc18dbd2 | |
Arjan te Marvelde | 1cdfcf3cb1 | |
ArjanteMarvelde | ff81d02993 | |
ArjanteMarvelde | 4a76b70b22 | |
ArjanteMarvelde | f31ae4a79c |
|
@ -1,4 +1,8 @@
|
|||
# Generated Cmake Pico project file
|
||||
#
|
||||
# After changing this file, empty the build folder and execute from there:
|
||||
# cmake -G "NMake Makefiles" ..
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
|
@ -12,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
|
||||
|
@ -45,5 +55,5 @@ target_link_libraries(uSDR
|
|||
hardware_adc
|
||||
)
|
||||
|
||||
pico_add_extra_outputs(uSDR)
|
||||
pico_add_extra_outputs(uSDR-FFT)
|
||||
|
||||
|
|
Plik binarny nie jest wyświetlany.
31
README.md
31
README.md
|
@ -3,28 +3,13 @@ This Git repository contains a Micro-SDR implementation, based on a RP2040 Pi Pi
|
|||
Furthermore, the repository contains the electronic design of some modules that cover the mixing, filtering and RF amplification.
|
||||
|
||||
The ZIP files contain a consistent package, but the latest code is in the files in the main directory.
|
||||
Please refer to the doc folder for a full description.
|
||||
|
||||
The platform used is a Pi Pico module with an RP2040 processor. This processor has dual cores, running default at 125MHz each, and a very configurable I/O which eases the HW design.
|
||||
The software is distributed over the two cores: *core0* takes care of all user I/O and control functions, while *core1* performs all of the signal processing. The *core1* functionality consists of a TX-branch and an RX-branch, each called from a function that waits for inter-core FIFO words popping out. This happens every 16usec, because on *core0* a 16usec timer callback ISR pushes the RX/TX status into that FIFO. Hence the signal processing rythm on *core1* effectively is 62.5kHz.
|
||||
On *core1* the three ADC channels are continuously sampled at maximum speed in round-robin mode. Samples are therefore taken every 6usec for each channel, maximum jitter between I and Q channels is 4usec, which has a negligible effect in the audio domain.
|
||||
|
||||
The TX-branch
|
||||
- takes latest audio audio sample input from ADC2 (rate = 62.5 kHz),
|
||||
- applies a low-pass filter at Fc=3kHz,
|
||||
- reduces sampling by 4 to get better low frequency response Hilbert xform (rate = 15.625 kHz),
|
||||
- splits into an I-channel 7 sample delay line and a Q-channel 15-tap Discrete Hilbert Transform
|
||||
- scales, filters and outputs I and Q samples on PWM based DACs, towards QSE output
|
||||
|
||||
The RX-branch
|
||||
- takes latest Q and I samples from QSD on ADC0 and ADC1 (rate = 62.5 kHz)
|
||||
- applies a low-pass filter at Fc=3kHz,
|
||||
- reduces sampling by 4 to get better low frequency response Hilbert xform (rate = 15.625 kHz),
|
||||
- demodulates, e.g. SSB:
|
||||
-- applies 15-tap DHT on Q channel and 7 sample delay on I channel
|
||||
-- subtracts I and Q samples
|
||||
- scales, filters and outputs audio on an PWM based DAC, towards audio output
|
||||
The V3.00 package contains two signal processing engines, selectable with a compile switch in dsp.h. The first engine is the old time domain processor, more or less as in V2, and the second engine is a new FFT based processor.
|
||||
For a more detailed description of the software and the hardware, please refer to the elaborate documentation.
|
||||
|
||||
The platform used is a Pi Pico module with an RP2040 processor. This processor has dual cores, running default at 125MHz each, and a very configurable I/O which eases the HW design. The platform can be overclocked, but some functions seem to become unstable when pushed too far.
|
||||
The software is distributed over the two cores: *core0* takes care of all user I/O and control functions, while *core1* performs all of the signal processing. The *core1* functionality consists of a TX-branch and an RX-branch, each called from a function that is synchronized by a timer every 64usec. Hence the signal processing rythm on *core1* effectively is 15.625kHz.
|
||||
On *core1* the three ADC channels are continuously sampled at maximum speed in round-robin mode. Samples are therefore taken every 6usec for each channel, maximum jitter between I and Q channels is 2usec, which has a negligible effect in the audio domain.
|
||||
For the time domain processing the TX and RX functions are called every timeslot, but for the frequency domain processing the samples are collected until half an FFT buffer is filled (512 samples), and hence this happens every 32msec.
|
||||
On *core0* the main loop takes care of user I/O, all other controls and the monitor port. There is also a LED flashing timer callback functioning as a heartbeat.
|
||||
|
||||
The Pico controls an Si5351A clock module to obtain the switching clock for the QSE and QSD. The module outputs two synchronous square wave clocks on ch 0 and 1, whith selectable phase difference (0, 90, 180 or 270 degrees). The clock on ch2 is free to be used for other goals. The module is controlled over the **i2c0** channel.
|
||||
|
@ -41,9 +26,9 @@ The display is a standard 16x2 LCD, but with an I2C interface. The display is co
|
|||
- [ ] implement RSSI
|
||||
- [x] design a set of PCBs
|
||||
- [x] sort out the new HW modules
|
||||
- [ ] improve speed: better dual-core management for memory and timer
|
||||
- [ ] improve speed: overclock processor 2x or so
|
||||
- [x] improve speed: better dual-core management for memory and timer
|
||||
- [x] add control for new HW: BPF and pre-amp/attenuator switching
|
||||
- [x] add frequency domain processing
|
||||
|
||||
## Installing and using the SDK for Windows:
|
||||
Please refer to https://github.com/ndabas/pico-setup-windows/releases where the latest installer can be downloaded (e.g. **pico-setup-windows-0.3-x64.exe**).
|
||||
|
|
Plik binarny nie jest wyświetlany.
820
dsp.c
820
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<<LSH,10<<LSH,10<<LSH}; // Levels for ADC channels
|
||||
volatile int adccnt = 0;
|
||||
void __not_in_flash_func(adcfifo_handler)(void)
|
||||
{
|
||||
int16_t q_sample, i_sample, a_sample;
|
||||
int32_t q_accu, i_accu;
|
||||
int16_t qh;
|
||||
uint16_t i;
|
||||
int16_t k;
|
||||
if (++adccnt >= ADC_INT) // Nr of integration samples reached?
|
||||
{
|
||||
adc_irq_set_enabled(false); // Disable interrupts
|
||||
adc_run(false); // Stop freerunning
|
||||
}
|
||||
adc_sample[0] = adc_sample[0] + adc_fifo_get() - ADC_BIAS; // Read first three samples from FIFO
|
||||
adc_sample[1] = adc_sample[1] + adc_fifo_get() - ADC_BIAS;
|
||||
adc_sample[2] = adc_sample[2] + adc_fifo_get() - ADC_BIAS;
|
||||
adccnt++;
|
||||
}
|
||||
|
||||
/*** SAMPLING ***/
|
||||
|
||||
|
||||
/** CORE1: Timer callback routine **/
|
||||
/*
|
||||
* This runs every TIM_US , i.e. 16usec
|
||||
* First the decimation filter is applied on latest ADC results
|
||||
* The filtered samples are set aside, so a new ADC cycle can be started.
|
||||
* The ADC cycle takes 8usec to complete (3x ADC0..2 + 1x stray ADC0 conversion).
|
||||
* The timing is critical, it assumes that the ADC is finished.
|
||||
* Once every 4 TIM_US intervals signal preprocessing is done, and DSP may be invoked.
|
||||
* Do not put any other stuff in this callback routine.
|
||||
*/
|
||||
semaphore_t dsp_sem;
|
||||
repeating_timer_t dsp_timer;
|
||||
volatile int cnt = 4;
|
||||
volatile int32_t rx_agc = 1, tx_agc = 1; // Factor as AGC
|
||||
bool __not_in_flash_func(dsp_callback)(repeating_timer_t *t)
|
||||
{
|
||||
int32_t temp;
|
||||
|
||||
q_sample = adc_result[0]; // Take last ADC 0 result, connected to Q input
|
||||
i_sample = adc_result[1]; // Take last ADC 1 result, connected to I input
|
||||
|
||||
/*
|
||||
* Remove DC and store new sample
|
||||
* IIR filter: dc = a*sample + (1-a)*dc where a = 1/128
|
||||
* Amplitude of samples should fit inside [-2048, 2047]
|
||||
*/
|
||||
q_sample = (q_sample&0x0fff) - ADC_BIAS; // Clip to 12 bits and subtract mid-range
|
||||
q_dc += q_sample/128 - q_dc/128; // then IIR running average
|
||||
q_sample -= q_dc; // and subtract DC
|
||||
i_sample = (i_sample&0x0fff) - ADC_BIAS; // Same for I sample
|
||||
i_dc += i_sample/128 - i_dc/128;
|
||||
i_sample -= i_dc;
|
||||
|
||||
/*
|
||||
* Shift with AGC feedback from AUDIO GENERATION stage
|
||||
* Note: bitshift does not work with negative numbers, so need to MPY/DIV
|
||||
* This behavior in essence is exponential, complementing the logarithmic peak detector
|
||||
*/
|
||||
if (agc_gain > 0)
|
||||
{
|
||||
q_sample = q_sample * (1<<agc_gain);
|
||||
i_sample = i_sample * (1<<agc_gain);
|
||||
}
|
||||
else if (agc_gain < 0)
|
||||
{
|
||||
q_sample = q_sample / (1<<(-agc_gain));
|
||||
i_sample = i_sample / (1<<(-agc_gain));
|
||||
}
|
||||
|
||||
/*
|
||||
* Shift-in I and Q raw samples
|
||||
* Here the rate is 15625Hz
|
||||
*/
|
||||
for (i=0; i<14; i++)
|
||||
{
|
||||
q_s_raw[i] = q_s_raw[i+1]; // Q raw samples shift register
|
||||
i_s_raw[i] = i_s_raw[i+1]; // I raw samples shift register
|
||||
}
|
||||
q_s_raw[14] = q_sample; // Store in shift registers
|
||||
i_s_raw[14] = i_sample;
|
||||
|
||||
|
||||
/*
|
||||
* Low pass filter + decimation
|
||||
*/
|
||||
rx_cnt = (rx_cnt+1)&3; // Calculate only every fourth sample
|
||||
if (rx_cnt>0) return (true); // So net sample time is 64us or 15.625 kHz
|
||||
|
||||
for (i=0; i<14; i++) // Shift decimated samples
|
||||
{
|
||||
q_s[i] = q_s[i+1];
|
||||
i_s[i] = i_s[i+1];
|
||||
}
|
||||
q_accu = 0; // Initialize accumulators
|
||||
i_accu = 0;
|
||||
for (i=0; i<15; i++) // Low pass FIR filter
|
||||
{
|
||||
q_accu += (int32_t)q_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz raw sampling
|
||||
i_accu += (int32_t)i_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz raw sampling
|
||||
}
|
||||
q_accu = q_accu/256;
|
||||
i_accu = i_accu/256;
|
||||
|
||||
q_s[14] = q_accu;
|
||||
i_s[14] = i_accu;
|
||||
|
||||
|
||||
/*** DEMODULATION ***/
|
||||
switch (dsp_mode)
|
||||
// Get DC bias corrected samples
|
||||
adc_result[0] = adc_sample[0];
|
||||
adc_result[1] = adc_sample[1];
|
||||
adc_result[2] = adc_sample[2];
|
||||
|
||||
// Re-start ADCs
|
||||
while (!adc_fifo_is_empty()) adc_fifo_get(); // Empty leftovers from fifo
|
||||
adc_sample[0] = 0;
|
||||
adc_sample[1] = 0;
|
||||
adc_sample[2] = 0;
|
||||
adc_set_round_robin(0x01+0x02+0x04); // Sequence ADC 0-1-2 (GP 26, 27, 28) free running
|
||||
adc_select_input(0); // Start with ADC0
|
||||
adccnt=0; // Check for ADC FIFO interrupt overruns
|
||||
adc_run(true); // Start ADC
|
||||
adc_irq_set_enabled(true); // Enable ADC interrupts
|
||||
|
||||
// Calculate and save level, left shifted by LSH
|
||||
// a=1/1024 : RC = 1023*64usec = 65msec (15Hz)
|
||||
adc_level[0] = (1023*adc_level[0] + (ABS(adc_result[0])<<LSH))/1024;
|
||||
adc_level[1] = (1023*adc_level[1] + (ABS(adc_result[1])<<LSH))/1024;
|
||||
adc_level[2] = (1023*adc_level[2] + (ABS(adc_result[2])<<LSH))/1024;
|
||||
|
||||
// Crude AGC mechanism
|
||||
if (!tx_enabled)
|
||||
{
|
||||
case 0: //USB
|
||||
/*
|
||||
* USB demodulate: I[7] - Qh,
|
||||
* Qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (q_s[0]-q_s[14])*315L + (q_s[2]-q_s[12])*440L + (q_s[4]-q_s[10])*734L + (q_s[6]-q_s[ 8])*2202L;
|
||||
qh = q_accu / 4096L;
|
||||
a_sample = i_s[7] - qh;
|
||||
break;
|
||||
case 1: //LSB
|
||||
/*
|
||||
* LSB demodulate: I[7] + Qh,
|
||||
* Qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (q_s[0]-q_s[14])*315L + (q_s[2]-q_s[12])*440L + (q_s[4]-q_s[10])*734L + (q_s[6]-q_s[ 8])*2202L;
|
||||
qh = q_accu / 4096L;
|
||||
a_sample = i_s[7] + qh;
|
||||
break;
|
||||
case 2: //AM
|
||||
/*
|
||||
* AM demodulate: sqrt(sqr(i)+sqr(q))
|
||||
* Approximated with MAG(i,q)
|
||||
*/
|
||||
a_sample = MAG(i_s[14], q_s[14]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
temp = (MAX(adc_level[1], adc_level[0]))>>LSH; // Max I or Q
|
||||
rx_agc = (temp==0) ? AGC_TOP : AGC_TOP/temp; // calculate required AGC factor
|
||||
}
|
||||
|
||||
#if DSP_FFT == 1
|
||||
|
||||
if (tx_enabled)
|
||||
{
|
||||
A_buf[dsp_active][dsp_tick] = (int16_t)(tx_agc*adc_result[2]);
|
||||
pwm_set_gpio_level(21, I_buf[dsp_active][dsp_tick] + DAC_BIAS); // Output I to DAC
|
||||
pwm_set_gpio_level(20, Q_buf[dsp_active][dsp_tick] + DAC_BIAS); // Output Q to DAC
|
||||
}
|
||||
else
|
||||
{
|
||||
I_buf[dsp_active][dsp_tick] = (int16_t)(rx_agc*adc_result[1]);
|
||||
Q_buf[dsp_active][dsp_tick] = (int16_t)(rx_agc*adc_result[0]);
|
||||
pwm_set_gpio_level(22, A_buf[dsp_active][dsp_tick] + DAC_BIAS); // Output A to DAC
|
||||
}
|
||||
|
||||
/*** AUDIO GENERATION ***/
|
||||
/*
|
||||
* AGC, peak detector
|
||||
* Sample speed is 15625 per second
|
||||
*/
|
||||
peak += (ABS(a_sample))/128 - peak/128; // Running average level detect, a=1/128
|
||||
k=0; i=peak; // Logarithmic peak detection
|
||||
if (i&0xff00) {k+=8; i>>=8;} // k=log2(peak), find highest bit set
|
||||
if (i&0x00f0) {k+=4; i>>=4;}
|
||||
if (i&0x000c) {k+=2; i>>=2;}
|
||||
if (i&0x0002) {k+=1;}
|
||||
agc_accu += (k - AGC_REF); // Add difference with target to integrator (Acc += Xn - R)
|
||||
if (agc_accu > agc_attack) // Attack time, gain correction in case of high level
|
||||
// When sample buffer is full, move pointer and signal DSP loop
|
||||
if (++dsp_tick >= BUFSIZE) // Increment tick and check range
|
||||
{
|
||||
agc_gain--; // Decrease gain
|
||||
agc_accu -= agc_attack; // Reset integrator
|
||||
} else if (agc_accu < -(agc_decay)) // Decay time, gain correction in case of low level
|
||||
{
|
||||
agc_gain++; // Increase gain
|
||||
agc_accu += agc_decay; // Reset integrator
|
||||
dsp_tick = 0; // Reset counter
|
||||
if (++dsp_active > 2) dsp_active = 0; // Rotate offset
|
||||
dsp_overrun++; // Increment overrun counter
|
||||
sem_release(&dsp_sem); // Signal DSP loop semaphore
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/*
|
||||
* Scale and clip output,
|
||||
* Send to audio DAC output
|
||||
*/
|
||||
a_sample += DAC_BIAS; // Add bias level
|
||||
if (a_sample > DAC_RANGE) // Clip to DAC range
|
||||
a_sample = DAC_RANGE;
|
||||
else if (a_sample<0)
|
||||
a_sample = 0;
|
||||
pwm_set_chan_level(dac_audio, PWM_CHAN_A, a_sample);
|
||||
if (tx_enabled)
|
||||
{
|
||||
a_sample = tx_agc * adc_result[2]; // Store A for DSP use
|
||||
pwm_set_gpio_level(21, i_sample); // Output I to DAC
|
||||
pwm_set_gpio_level(20, q_sample); // Output Q to DAC
|
||||
}
|
||||
else
|
||||
{
|
||||
pwm_set_gpio_level(22, a_sample); // Output Q to DAC
|
||||
q_sample = rx_agc * adc_result[0]; // Store Q for DSP use
|
||||
i_sample = rx_agc * adc_result[1]; // Store I for DSP use
|
||||
}
|
||||
dsp_overrun++; // Increment overrun counter
|
||||
sem_release(&dsp_sem); // Signal DSP loop semaphore
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CORE1:
|
||||
* The VOX function is called separately every cycle, to check audio level.
|
||||
* Execute TX branch signal processing when tx enabled
|
||||
*/
|
||||
volatile int16_t a_s_raw[15]; // Raw samples, minus DC bias
|
||||
volatile int16_t a_level=0; // Average level of raw sample stream
|
||||
volatile int16_t a_s[15]; // Filtered and decimated samples
|
||||
volatile int16_t a_dc; // DC level
|
||||
volatile int tx_cnt=0; // Decimation counter
|
||||
|
||||
bool vox(void)
|
||||
{
|
||||
int16_t a_sample;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Get sample and shift into delay line
|
||||
*/
|
||||
a_sample = adc_result[2]; // Get latest ADC 2 result
|
||||
|
||||
|
||||
/*
|
||||
* Remove DC and store new raw sample
|
||||
* IIR filter: dc = a*sample + (1-a)*dc where a = 1/128
|
||||
*/
|
||||
a_sample = (a_sample&0x0fff) - ADC_BIAS; // Clip and subtract mid-range
|
||||
a_dc += (a_sample - a_dc)/128; // then IIR running average
|
||||
a_sample -= a_dc; // subtract DC
|
||||
for (i=0; i<14; i++) // and store in shift register
|
||||
a_s_raw[i] = a_s_raw[i+1];
|
||||
a_s_raw[14] = a_sample;
|
||||
|
||||
/*
|
||||
* Detect level of audio signal
|
||||
* Return true if VOX enabled and:
|
||||
* - Audio level higher than threshold
|
||||
* - Linger time sill active
|
||||
*/
|
||||
if (a_sample<0) a_sample = -a_sample; // Absolute value
|
||||
a_level += (a_sample - a_level)/128; // running average, 16usec * 128 = 2msec
|
||||
|
||||
if (vox_level != VOX_OFF) // Only when VOX is enabled
|
||||
{
|
||||
if (a_level > vox_level)
|
||||
{
|
||||
vox_count = VOX_LINGER; // While audio present, reset linger timer
|
||||
return(true); // and keep TX active
|
||||
}
|
||||
if (vox_count>0)
|
||||
{
|
||||
vox_count--; // No audio; decrement linger timer
|
||||
return(true); // but keep TX active
|
||||
}
|
||||
}
|
||||
return(false); // All other cases: no TX
|
||||
}
|
||||
|
||||
bool tx(void)
|
||||
{
|
||||
int32_t a_accu, q_accu;
|
||||
int16_t qh;
|
||||
int i;
|
||||
uint16_t i_dac, q_dac;
|
||||
|
||||
/*** RAW Audio SAMPLES from VOX function ***/
|
||||
/*** Low pass filter + decimation ***/
|
||||
tx_cnt = (tx_cnt+1)&3; // Calculate only every fourth sample
|
||||
if (tx_cnt>0) return true; // So effective sample rate will be 15625Hz
|
||||
|
||||
a_accu = 0; // Initialize accumulator
|
||||
for (i=0; i<15; i++) // Low pass FIR filter, using raw samples
|
||||
a_accu += (int32_t)a_s_raw[i]*lpf3_62[i]; // Fc=3kHz, at 62.5 kHz sampling
|
||||
|
||||
for (i=0; i<14; i++) // Shift decimated samples
|
||||
a_s[i] = a_s[i+1];
|
||||
a_s[14] = a_accu / 256; // Store rescaled accumulator
|
||||
|
||||
/*** MODULATION ***/
|
||||
switch (dsp_mode)
|
||||
{
|
||||
case 0: // USB
|
||||
/*
|
||||
* qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (a_s[0]-a_s[14])*315L + (a_s[2]-a_s[12])*440L + (a_s[4]-a_s[10])*734L + (a_s[6]-a_s[ 8])*2202L;
|
||||
qh = -(q_accu / 4096L); // USB: sign is negative
|
||||
break;
|
||||
case 1: // LSB
|
||||
/*
|
||||
* qh is Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (a_s[0]-a_s[14])*315L + (a_s[2]-a_s[12])*440L + (a_s[4]-a_s[10])*734L + (a_s[6]-a_s[ 8])*2202L;
|
||||
qh = q_accu / 4096L; // LSB: sign is positive
|
||||
break;
|
||||
case 2: // AM
|
||||
/*
|
||||
* I and Q values are identical
|
||||
*/
|
||||
qh = a_s[7];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write I and Q to QSE DACs, phase is 7 samples back.
|
||||
* Need to multiply AC with DAC_RANGE/ADC_RANGE (appr 1/8)
|
||||
* Any case: clip to range
|
||||
*/
|
||||
a_accu = DAC_BIAS - (qh/8);
|
||||
if (a_accu<0)
|
||||
q_dac = 0;
|
||||
else if (a_accu>(DAC_RANGE-1))
|
||||
q_dac = DAC_RANGE-1;
|
||||
else
|
||||
q_dac = a_accu;
|
||||
|
||||
a_accu = DAC_BIAS + (a_s[7]/8);
|
||||
if (a_accu<0)
|
||||
i_dac = 0;
|
||||
else if (a_accu>(DAC_RANGE-1))
|
||||
i_dac = DAC_RANGE-1;
|
||||
else
|
||||
i_dac = a_accu;
|
||||
|
||||
// pwm_set_both_levels(dac_iq, q_dac, i_dac); // Set both channels of the IQ slice simultaneously
|
||||
// pwm_set_chan_level(dac_iq, PWM_CHAN_A, q_dac);
|
||||
// pwm_set_chan_level(dac_iq, PWM_CHAN_B, i_dac);
|
||||
pwm_set_gpio_level(21, i_dac);
|
||||
pwm_set_gpio_level(20, q_dac);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CORE1:
|
||||
* Timing loop, triggered through inter-core fifo
|
||||
*/
|
||||
void dsp_loop()
|
||||
/** CORE1: DSP loop, triggered through repeating timer/semaphore **/
|
||||
void __not_in_flash_func(dsp_loop)()
|
||||
{
|
||||
uint32_t cmd;
|
||||
uint16_t slice_num;
|
||||
alarm_pool_t *ap;
|
||||
|
||||
tx_enabled = false;
|
||||
fifo_overrun = 0;
|
||||
fifo_rx = 0;
|
||||
fifo_tx = 0;
|
||||
fifo_xx = 0;
|
||||
fifo_incnt++;
|
||||
tx_enabled = false;
|
||||
vox_active = false;
|
||||
|
||||
/*
|
||||
* Initialize DACs,
|
||||
* default mode is free running,
|
||||
* A and B pins are output
|
||||
*/
|
||||
gpio_set_function(20, GPIO_FUNC_PWM); // GP20 is PWM for Q DAC (Slice 2, Channel A)
|
||||
gpio_set_function(21, GPIO_FUNC_PWM); // GP21 is PWM for I DAC (Slice 2, Channel B)
|
||||
dac_iq = pwm_gpio_to_slice_num(20); // Get PWM slice for GP20 (Same for GP21)
|
||||
pwm_set_clkdiv_int_frac (dac_iq, 1, 0); // clock divide by 1: full system clock
|
||||
pwm_set_wrap(dac_iq, DAC_RANGE-1); // Set cycle length; nr of counts until wrap, i.e. 125/DAC_RANGE MHz
|
||||
pwm_set_enabled(dac_iq, true); // Set the PWM running
|
||||
|
||||
gpio_set_function(22, GPIO_FUNC_PWM); // GP22 is PWM for Audio DAC (Slice 3, Channel A)
|
||||
dac_audio = pwm_gpio_to_slice_num(22); // Find PWM slice for GP22
|
||||
pwm_set_clkdiv_int_frac (dac_audio, 1, 0); // clock divide by 1: full system clock
|
||||
pwm_set_wrap(dac_audio, DAC_RANGE-1); // Set cycle length; nr of counts until wrap, i.e. 125/DAC_RANGE MHz
|
||||
pwm_set_enabled(dac_audio, true); // Set the PWM running
|
||||
|
||||
/* Initialize DACs, default mode is free running, A and B pins are output */
|
||||
gpio_set_function(20, GPIO_FUNC_PWM); // GP20 is PWM for Q DAC (Slice 2, Channel A)
|
||||
gpio_set_function(21, GPIO_FUNC_PWM); // GP21 is PWM for I DAC (Slice 2, Channel B)
|
||||
dac_iq = pwm_gpio_to_slice_num(20); // Get PWM slice for GP20 (Same for GP21)
|
||||
pwm_set_clkdiv_int_frac (dac_iq, 1, 0); // clock divide by 1: 125MHz
|
||||
pwm_set_wrap(dac_iq, DAC_RANGE-1); // Set cycle length; nr of counts until wrap, i.e. 125/DAC_RANGE MHz
|
||||
pwm_set_enabled(dac_iq, true); // Set the PWM running
|
||||
|
||||
gpio_set_function(22, GPIO_FUNC_PWM); // GP22 is PWM for Audio DAC (Slice 3, Channel A)
|
||||
dac_audio = pwm_gpio_to_slice_num(22); // Find PWM slice for GP22
|
||||
pwm_set_clkdiv_int_frac (dac_audio, 1, 0); // clock divide by 1: 125MHz
|
||||
pwm_set_wrap(dac_audio, DAC_RANGE-1); // Set cycle length; nr of counts until wrap, i.e. 125/DAC_RANGE MHz
|
||||
pwm_set_enabled(dac_audio, true); // Set the PWM running
|
||||
|
||||
/* Initialize ADCs */
|
||||
adc_init(); // Initialize ADC to known state
|
||||
adc_set_clkdiv(0); // Fastest clock (500 kSps)
|
||||
adc_gpio_init(26); // GP26 is ADC 0 for Q channel
|
||||
adc_gpio_init(27); // GP27 is ADC 1 for I channel
|
||||
adc_gpio_init(28); // GP28 is ADC 2 for Audio channel
|
||||
adc_select_input(0); // Start with ADC0
|
||||
adc_next = 0;
|
||||
|
||||
adc_set_round_robin(0x01+0x02+0x04); // Sequence ADC 0-1-2 (GP 26, 27, 28) free running
|
||||
adc_fifo_setup(true,false,1,false,false); // IRQ for every result (fifo threshold = 1)
|
||||
irq_set_exclusive_handler(ADC0_IRQ_FIFO, adcfifo_handler);
|
||||
adc_irq_set_enabled(true);
|
||||
irq_set_enabled(ADC0_IRQ_FIFO, true);
|
||||
/*
|
||||
* Initialize ADCs, use in round robin mode (3 channels)
|
||||
* samples are stored in array through IRQ callback
|
||||
*/
|
||||
adc_init(); // Initialize ADC to known state
|
||||
adc_set_clkdiv(0.0); // Fastest clock (500 kSps)
|
||||
adc_gpio_init(26); // GP26 is ADC 0 for Q channel
|
||||
adc_gpio_init(27); // GP27 is ADC 1 for I channel
|
||||
adc_gpio_init(28); // GP28 is ADC 2 for Audio channel
|
||||
adc_set_round_robin(0x01+0x02+0x04); // Sequence ADC 0-1-2 (GP 26, 27, 28) free running
|
||||
adc_select_input(0); // Start with ADC0
|
||||
adc_fifo_setup(true,false,3,false,false); // IRQ result, fifo threshold = 1, so IRQ after each ADC0..2
|
||||
irq_set_exclusive_handler(ADC_IRQ_FIFO, adcfifo_handler); // Install ISR at ADC_IRQ_FIFO vector (22)
|
||||
irq_set_priority (ADC_IRQ_FIFO, PICO_HIGHEST_IRQ_PRIORITY); // Prevent race condition with timer
|
||||
irq_set_enabled(ADC_IRQ_FIFO, true); // Enable interrupt vector in NVIC
|
||||
adc_irq_set_enabled(true); // Enable the ADC FIFO interrupt
|
||||
adc_run(true);
|
||||
adc_level[0] = ADC_BIAS/2;
|
||||
adc_level[1] = ADC_BIAS/2;
|
||||
adc_level[2] = ADC_BIAS/2;
|
||||
|
||||
// Consider using alarm_pool_add_repeating_timer_us() for a core1 associated timer
|
||||
// First create an alarm pool on core1: alarm_pool_create(HWalarm, Ntimers)
|
||||
// For the core1 alarm pool don't use default HWalarm (usually 3) but e.g. 1
|
||||
// Timer callback signals semaphore, while loop blocks on getting it
|
||||
// Use alarm_pool_add_repeating_timer_us() for a core1 associated timer
|
||||
// First create an alarm pool on core1:
|
||||
// alarm_pool_t *alarm_pool_create( uint hardware_alarm_num,
|
||||
// uint max_timers);
|
||||
// For the core1 alarm pool don't use the default alarm_num (usually 3) but e.g. 1
|
||||
// Timer callback signals semaphore, while loop blocks on getting it.
|
||||
// Initialize repeating timer on core1:
|
||||
// bool alarm_pool_add_repeating_timer_us( alarm_pool_t *pool,
|
||||
// int64_t delay_us,
|
||||
// repeating_timer_callback_t callback,
|
||||
// void *user_data,
|
||||
// repeating_timer_t *out);
|
||||
|
||||
sem_init(&dsp_sem, 0, 1);
|
||||
ap = alarm_pool_create(1, 4);
|
||||
alarm_pool_add_repeating_timer_us( ap, -TIM_US, dsp_callback, NULL, &dsp_timer);
|
||||
|
||||
dsp_overrun = 0;
|
||||
|
||||
while(1)
|
||||
{
|
||||
cmd = multicore_fifo_pop_blocking(); // Wait for fifo output
|
||||
sem_acquire_blocking(&dsp_sem); // Wait until timer callback releases sem
|
||||
dsp_overrun--; // Decrement overrun counter
|
||||
|
||||
tx_enabled = vox() || ptt_active; // Sample audio and check level
|
||||
if (tx_enabled)
|
||||
// Use adc_level[2] for VOX
|
||||
vox_active = false; // Normally false
|
||||
if (vox_level != 0) // Only when VOX is enabled
|
||||
{
|
||||
if (vox_level != VOX_OFF) // Only when vox is enabled
|
||||
gpio_put(GP_PTT, false); // drive PTT low (active)
|
||||
tx();
|
||||
if ((adc_level[2]>>LSH) > vox_level) // AND level > limit
|
||||
{
|
||||
vox_count = S_RATE * VOX_LINGER / 1000; // While audio present, reset linger timer
|
||||
vox_active = true; // and keep TX active
|
||||
}
|
||||
else if (--vox_count>0) // else decrement linger counter
|
||||
vox_active = true; // and keep TX active until 0
|
||||
|
||||
}
|
||||
|
||||
if (tx_enabled) // Use previous setting
|
||||
{
|
||||
gpio_put(GP_PTT, false); // Drive PTT low (active)
|
||||
tx(); // Do TX signal processing
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vox_level != VOX_OFF) // Only when vox is enabled
|
||||
gpio_put(GP_PTT, true); // drive PTT high (inactive)
|
||||
rx();
|
||||
gpio_put(GP_PTT, true); // Drive PTT high (inactive)
|
||||
rx(); // Do RX signal processing
|
||||
}
|
||||
if (multicore_fifo_rvalid())
|
||||
fifo_overrun++; // Check for missed events
|
||||
}
|
||||
|
||||
tx_enabled = vox_active || ptt_active; // Check RX or TX
|
||||
|
||||
#if DSP_FFT == 1
|
||||
dsp_tickx = dsp_tick;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CORE0:
|
||||
* Timer callback, triggers core1 through inter-core fifo.
|
||||
* Either TX or RX, but could do both when testing in loopback on I+Q channels.
|
||||
*/
|
||||
struct repeating_timer dsp_timer;
|
||||
bool dsp_callback(struct repeating_timer *t)
|
||||
{
|
||||
if (tx_enabled)
|
||||
multicore_fifo_push_blocking(DSP_TX); // Send TX to core 1 through fifo
|
||||
else
|
||||
multicore_fifo_push_blocking(DSP_RX); // Send RX to core 1 through fifo
|
||||
fifo_incnt++;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CORE0:
|
||||
* Initialize dsp context and spawn CORE1 process
|
||||
*
|
||||
* Some CORE1 code parts should not run from Flash, but be loaded in SRAM at boot time
|
||||
* See platform.h for function qualifier macro's
|
||||
* for example:
|
||||
* void __not_in_flash_func(funcname)(int arg1, float arg2)
|
||||
* {
|
||||
* }
|
||||
*
|
||||
* Need to set BUS_PRIORITY of Core 1 to high
|
||||
* #include bus_ctrl.h
|
||||
* bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_PROC1_BITS; // Set Core 1 prio high
|
||||
*/
|
||||
/** CORE0: Initialize dsp context and spawn CORE1 process **/
|
||||
void dsp_init()
|
||||
{
|
||||
multicore_launch_core1(dsp_loop); // Start processing on core1
|
||||
add_repeating_timer_us(-DSP_US, dsp_callback, NULL, &dsp_timer);
|
||||
bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_PROC1_BITS; // Set Core 1 prio on bus to high
|
||||
multicore_launch_core1(dsp_loop); // Start processing on Core 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* DMA EXAMPLE, should convert to chained DMA to reload after 3 words
|
||||
// Init GPIO for analogue use: hi-Z, no pulls, disable digital input buffer.
|
||||
adc_gpio_init(26 + CAPTURE_CHANNEL);
|
||||
|
||||
adc_init();
|
||||
adc_select_input(CAPTURE_CHANNEL);
|
||||
adc_fifo_setup(
|
||||
true, // Write each completed conversion to the sample FIFO
|
||||
true, // Enable DMA data request (DREQ)
|
||||
1, // DREQ (and IRQ) asserted when at least 1 sample present
|
||||
false, // We won't see the ERR bit because of 8 bit reads; disable.
|
||||
true // Shift each sample to 8 bits when pushing to FIFO
|
||||
);
|
||||
|
||||
// Divisor of 0 -> full speed. Free-running capture with the divider is
|
||||
// equivalent to pressing the ADC_CS_START_ONCE button once per `div + 1`
|
||||
// cycles (div not necessarily an integer). Each conversion takes 96
|
||||
// cycles, so in general you want a divider of 0 (hold down the button
|
||||
// continuously) or > 95 (take samples less frequently than 96 cycle
|
||||
// intervals). This is all timed by the 48 MHz ADC clock.
|
||||
adc_set_clkdiv(0);
|
||||
|
||||
printf("Arming DMA\n");
|
||||
sleep_ms(1000);
|
||||
// Set up the DMA to start transferring data as soon as it appears in FIFO
|
||||
uint dma_chan = dma_claim_unused_channel(true);
|
||||
dma_channel_config cfg = dma_channel_get_default_config(dma_chan);
|
||||
|
||||
// Reading from constant address, writing to incrementing byte addresses
|
||||
channel_config_set_transfer_data_size(&cfg, DMA_SIZE_8);
|
||||
channel_config_set_read_increment(&cfg, false);
|
||||
channel_config_set_write_increment(&cfg, true);
|
||||
|
||||
// Pace transfers based on availability of ADC samples
|
||||
channel_config_set_dreq(&cfg, DREQ_ADC);
|
||||
|
||||
dma_channel_configure(dma_chan, &cfg,
|
||||
capture_buf, // dst
|
||||
&adc_hw->fifo, // src
|
||||
CAPTURE_DEPTH, // transfer count
|
||||
true // start immediately
|
||||
);
|
||||
|
||||
printf("Starting capture\n");
|
||||
adc_run(true);
|
||||
|
||||
// Once DMA finishes, stop any new conversions from starting, and clean up
|
||||
// the FIFO in case the ADC was still mid-conversion.
|
||||
dma_channel_wait_for_finish_blocking(dma_chan);
|
||||
printf("Capture finished\n");
|
||||
adc_run(false);
|
||||
adc_fifo_drain();
|
||||
|
||||
// Print samples to stdout so you can display them in pyplot, excel, matlab
|
||||
for (int i = 0; i < CAPTURE_DEPTH; ++i) {
|
||||
printf("%-3d, ", capture_buf[i]);
|
||||
if (i % 10 == 9)
|
||||
printf("\n");
|
||||
}
|
||||
*/
|
||||
|
|
51
dsp.h
51
dsp.h
|
@ -1,5 +1,5 @@
|
|||
#ifndef __DSP_H__
|
||||
#define __DSP_H__
|
||||
#ifndef __DSP_FFT_H__
|
||||
#define __DSP_FFT_H__
|
||||
/*
|
||||
* dsp.h
|
||||
*
|
||||
|
@ -9,21 +9,50 @@
|
|||
* See dsp.c for more information
|
||||
*/
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/adc.h"
|
||||
#include "hardware/pwm.h"
|
||||
#define DSP_FFT 1 // 1 for FFT, 0 for time domain processing
|
||||
|
||||
|
||||
void dsp_setagc(int agc);
|
||||
void dsp_setmode(int mode);
|
||||
/*
|
||||
* Callback timeout is TIM_US, value in usec
|
||||
* The carrier offset is !=0 only in FFT case.
|
||||
*/
|
||||
|
||||
#if DSP_FFT == 1
|
||||
|
||||
#define TIM_US 64
|
||||
#define S_RATE 15625 // 1e6/TIM_US
|
||||
#define FC_OFFSET 3906 // in bin FFT_SIZE/4 ==> S_RATE/4
|
||||
|
||||
#else
|
||||
|
||||
#define TIM_US 64
|
||||
#define S_RATE 15625 // 1e6/TIM_US
|
||||
#define FC_OFFSET 0 // Must be 0 for time-domain DSP
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/** DSP module interface **/
|
||||
|
||||
extern volatile bool tx_enabled; // Determined by (vox_active || ptt_active)
|
||||
|
||||
#define VOX_OFF 0
|
||||
#define VOX_LOW 1
|
||||
#define VOX_MEDIUM 2
|
||||
#define VOX_HIGH 3
|
||||
void dsp_setvox(int vox);
|
||||
|
||||
extern volatile bool tx_enabled;
|
||||
#define DSP_SETPTT(on) tx_enabled = (on)
|
||||
#define MODE_USB 0
|
||||
#define MODE_LSB 1
|
||||
#define MODE_AM 2
|
||||
#define MODE_CW 3
|
||||
void dsp_setmode(int mode);
|
||||
|
||||
#define AGC_NONE 0
|
||||
#define AGC_SLOW 1
|
||||
#define AGC_FAST 2
|
||||
void dsp_setagc(int agc);
|
||||
|
||||
void dsp_init();
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* dsp_fft.c
|
||||
* ==>TO BE INCLUDED IN dsp.c
|
||||
*
|
||||
* Created: May 2022
|
||||
* Author: Arjan te Marvelde
|
||||
*
|
||||
* Signal processing of RX and TX branch, to be run on the second processor core (CORE1).
|
||||
* A branch has a dedicated routine that must run on set times.
|
||||
* In this case it runs when half FFT_SIZE of samples is ready to be processed.
|
||||
*
|
||||
*
|
||||
* The pace for sampling is set by a timer at 64usec (15.625 kHz)
|
||||
* The associated timer callback routine:
|
||||
* - handles data transfer to/from physical interfaces
|
||||
* - starts a new ADC conversion sequence
|
||||
* - maintains dsp_tick counter
|
||||
* - when dsp_tick == FFT_SIZE/2 (one buffer), the dsp-loop is triggered.
|
||||
*
|
||||
* The ADC functions in round-robin and fifo mode, triggering IRQ after 3 conversions (ADC[0..2])
|
||||
* The ADC FIFO IRQ handler reads the 3 samples from the fifo after stopping the ADC
|
||||
*
|
||||
* Buffer structure, built from half FFT_SIZE buffers.
|
||||
* The I, Q and A external interfaces communicate each through 3x buffers.
|
||||
* One buffer is being filled or emptied, depending on data direction.
|
||||
* The other two are swapped with the FFT signal processing buffers.
|
||||
* Since we use complex FFT, the algorithm uses 4x buffers.
|
||||
*
|
||||
* I, Q and A buffers used as queues. RX case looks like:
|
||||
*
|
||||
* +--+--+--+
|
||||
* i --> | | | |
|
||||
* +--+--+--+
|
||||
* \ \ \ +--+--+
|
||||
* --------> | | | +--+--+--+
|
||||
* +--+--+ FFT-DSP-iFFT --> | | | | --> a
|
||||
* --------> | | | +--+--+--+
|
||||
* / / / +--+--+
|
||||
* +--+--+--+
|
||||
* q --> | | | |
|
||||
* +--+--+--+
|
||||
*
|
||||
* RX, when triggered by timer callback:
|
||||
* - The oldest real FFT buffer is moved to the output queue (check this)
|
||||
* - The oldest two I and Q buffers are copied into the FFT buffers
|
||||
* - FFT is executed
|
||||
* - Signal processing is done
|
||||
* - iFFT is executed
|
||||
*
|
||||
* The bin step is the sampling frequency divided by the FFT_SIZE.
|
||||
* So for S_RATE=15625 and FFT_SIZE=1024 this step is 15625/1024=15.259 Hz
|
||||
* The Carrier offset (Fc) is at about half the Nyquist frequency: bin 256 or 3906 Hz
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* FFT buffer allocation
|
||||
* Buffer size is FFT_SIZE/2 (see fix_fft.h).
|
||||
* In case FFT_SIZE of 1024, a buffer is 1kB
|
||||
* RX: 3 buffers for I samples, 3 buffers for Q samples, 3 buffers for Audio
|
||||
* DSP: 4 buffers for FFT, complex samples and these have to be consecutive!
|
||||
* TX: re-use RX buffers in reverse order
|
||||
* Total of 13kByte RAM is required.
|
||||
* Samples are 16 bit signed integer, but align buffers on 32bit boundaries
|
||||
* dsp_tick points into I, Q and A buffers, so wrap once per two FFTs
|
||||
* When tick==FFT_SIZE/2: do buffer copy
|
||||
*/
|
||||
#define BUFSIZE FFT_SIZE/2
|
||||
int16_t I_buf[3][BUFSIZE] __attribute__((aligned(4))); // I sample queue, 3x buffer of FFT_SIZE/2
|
||||
int16_t Q_buf[3][BUFSIZE] __attribute__((aligned(4))); // Q sample queue, 3x buffer of FFT_SIZE/2
|
||||
int16_t A_buf[3][BUFSIZE] __attribute__((aligned(4))); // A sample queue, 3x buffer of FFT_SIZE/2
|
||||
int16_t XI_buf[FFT_SIZE] __attribute__((aligned(4))); // Re FFT buffer, 1x buffer of FFT_SIZE
|
||||
int16_t XQ_buf[FFT_SIZE] __attribute__((aligned(4))); // Im FFT buffer, 1x buffer of FFT_SIZE
|
||||
|
||||
// Sample buffer indexes, updated by timer callback
|
||||
volatile int dsp_active = 0; // I, Q, A active buffer number (0..2)
|
||||
volatile uint32_t dsp_tick = 0; // Index in active buffer
|
||||
volatile uint32_t dsp_tickx = 0; // Load indicator DSP loop
|
||||
|
||||
// Spectrum bins for a frequency
|
||||
#define BIN(f) (int)(((f)*FFT_SIZE+S_RATE/2)/S_RATE)
|
||||
#define BIN_FC 256
|
||||
#define BIN_100 7
|
||||
#define BIN_300 20
|
||||
#define BIN_900 59
|
||||
#define BIN_3000 197
|
||||
|
||||
/*
|
||||
* This applies a bandpass filter to XI and XQ buffers
|
||||
* lowbin and highbin edges must be between 3 and FFT_SIZE/2 - 3
|
||||
* Edge is a 7 bin raised cosine flank, i.e. 100Hz wide
|
||||
* Coefficients are: 0, 0.067, 0.25, 0.5, 0.75, 0.933, 1
|
||||
* where the edge bin is in the center of this flank
|
||||
* Note: maybe make slope less steep, e.g. 9 or 11 bins
|
||||
*/
|
||||
inline void dsp_bandpass(int lowbin, int highbin)
|
||||
{
|
||||
int i;
|
||||
|
||||
if ((lowbin<3)||(highbin>(FFT_SIZE/2-3))||(highbin-lowbin<6)) return;
|
||||
|
||||
XI_buf[0] = 0; XQ_buf[0] = 0;
|
||||
for (i=1; i<lowbin-2; i++)
|
||||
{
|
||||
XI_buf[i] = 0; XI_buf[FFT_SIZE-i] = 0;
|
||||
XQ_buf[i] = 0; XQ_buf[FFT_SIZE-i] = 0;
|
||||
}
|
||||
for (i=highbin+3; i<FFT_SIZE-highbin-2; i++)
|
||||
{
|
||||
XI_buf[i] = 0;
|
||||
XQ_buf[i] = 0;
|
||||
}
|
||||
|
||||
// Note: There is not much difference between using or discarding Q bins
|
||||
i=lowbin-2;
|
||||
XI_buf[i] = XI_buf[i]*0.067; XQ_buf[i] = XQ_buf[i]*0.067; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.250; XQ_buf[i] = XQ_buf[i]*0.250; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.500; XQ_buf[i] = XQ_buf[i]*0.500; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.750; XQ_buf[i] = XQ_buf[i]*0.750; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.933; XQ_buf[i] = XQ_buf[i]*0.933;
|
||||
i=highbin-2;
|
||||
XI_buf[i] = XI_buf[i]*0.933; XQ_buf[i] = XQ_buf[i]*0.933; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.250; XQ_buf[i] = XQ_buf[i]*0.250; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.500; XQ_buf[i] = XQ_buf[i]*0.500; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.750; XQ_buf[i] = XQ_buf[i]*0.750; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.067; XQ_buf[i] = XQ_buf[i]*0.067;
|
||||
i=FFT_SIZE-highbin-2;
|
||||
XI_buf[i] = XI_buf[i]*0.067; XQ_buf[i] = XQ_buf[i]*0.067; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.250; XQ_buf[i] = XQ_buf[i]*0.250; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.500; XQ_buf[i] = XQ_buf[i]*0.500; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.750; XQ_buf[i] = XQ_buf[i]*0.750; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.933; XQ_buf[i] = XQ_buf[i]*0.933;
|
||||
i=FFT_SIZE-lowbin-2;
|
||||
XI_buf[i] = XI_buf[i]*0.933; XQ_buf[i] = XQ_buf[i]*0.933; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.250; XQ_buf[i] = XQ_buf[i]*0.250; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.500; XQ_buf[i] = XQ_buf[i]*0.500; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.750; XQ_buf[i] = XQ_buf[i]*0.750; i++;
|
||||
XI_buf[i] = XI_buf[i]*0.067; XQ_buf[i] = XQ_buf[i]*0.067;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** CORE1: RX branch **/
|
||||
/*
|
||||
* Execute RX branch signal processing
|
||||
*/
|
||||
volatile int scale0;
|
||||
volatile int scale1;
|
||||
bool __not_in_flash_func(rx)(void)
|
||||
{
|
||||
int b;
|
||||
int i;
|
||||
int16_t *ip, *qp, *ap, *xip, *xqp;
|
||||
int16_t peak;
|
||||
|
||||
b = dsp_active; // Point to Active buffer
|
||||
|
||||
/*** Copy saved I/Q buffers to FFT buffer ***/
|
||||
if (++b > 2) b = 0; // Point to Old Saved buffer
|
||||
ip = &I_buf[b][0]; xip = &XI_buf[0];
|
||||
qp = &Q_buf[b][0]; xqp = &XQ_buf[0];
|
||||
for (i=0; i<BUFSIZE; i++)
|
||||
{
|
||||
*xip++ = *ip++;
|
||||
*xqp++ = *qp++;
|
||||
}
|
||||
if (++b > 2) b = 0; // Point to New Saved buffer
|
||||
ip = &I_buf[b][0]; xip = &XI_buf[BUFSIZE];
|
||||
qp = &Q_buf[b][0]; xqp = &XQ_buf[BUFSIZE];
|
||||
for (i=0; i<BUFSIZE; i++)
|
||||
{
|
||||
*xip++ = *ip++;
|
||||
*xqp++ = *qp++;
|
||||
}
|
||||
|
||||
|
||||
/*** Execute FFT ***/
|
||||
scale0 = fix_fft(&XI_buf[0], &XQ_buf[0], false);
|
||||
|
||||
|
||||
/*** Shift and filter sidebands ***/
|
||||
XI_buf[0] = 0;
|
||||
XQ_buf[0] = 0;
|
||||
switch (dsp_mode)
|
||||
{
|
||||
case MODE_USB:
|
||||
// Shift Fc to 0Hz
|
||||
for (i=1; i<BIN_3000; i++)
|
||||
{
|
||||
XI_buf[i] = XI_buf[i+BIN_FC];
|
||||
XI_buf[FFT_SIZE-i] = XI_buf[FFT_SIZE-BIN_FC-i];
|
||||
XQ_buf[i] = XQ_buf[i+BIN_FC];
|
||||
XQ_buf[FFT_SIZE-i] = XQ_buf[FFT_SIZE-BIN_FC-i];
|
||||
}
|
||||
// Bandpass DSB (2x USB)
|
||||
dsp_bandpass(BIN_100, BIN_3000);
|
||||
break;
|
||||
case MODE_LSB:
|
||||
// Shift Fc to 0Hz, i.e. swap buffers
|
||||
for (i=1; i<BIN_3000; i++)
|
||||
{
|
||||
XI_buf[BUFSIZE-i] = XI_buf[BIN_FC-i];
|
||||
XI_buf[i] = XI_buf[FFT_SIZE-BIN_FC+i];
|
||||
XI_buf[FFT_SIZE-i] = XI_buf[BUFSIZE-i];
|
||||
XQ_buf[BUFSIZE-i] = XQ_buf[BIN_FC-i];
|
||||
XQ_buf[i] = XQ_buf[FFT_SIZE-BIN_FC+i];
|
||||
XQ_buf[FFT_SIZE-i] = XQ_buf[BUFSIZE-i];
|
||||
}
|
||||
// Bandpass DSB (2x LSB)
|
||||
dsp_bandpass(BIN_100, BIN_3000);
|
||||
break;
|
||||
case MODE_AM:
|
||||
// Shift the rest to the right place
|
||||
for (i=1; i<BIN_3000; i++)
|
||||
{
|
||||
XI_buf[FFT_SIZE-i] = XI_buf[BIN_FC-i];
|
||||
XI_buf[i] = XI_buf[BIN_FC+i];
|
||||
XQ_buf[FFT_SIZE-i] = XQ_buf[BIN_FC-i];
|
||||
XQ_buf[i] = XQ_buf[BIN_FC+i];
|
||||
}
|
||||
// Bandpass DSB (LSB + USB)
|
||||
dsp_bandpass(BIN_100, BIN_3000);
|
||||
break;
|
||||
case MODE_CW:
|
||||
// Shift carrier from Fc to 900Hz
|
||||
for (i=-BIN_900+1; i<BIN_900-1; i++)
|
||||
{
|
||||
XI_buf[i+BIN_900] = XI_buf[BIN_FC+i];
|
||||
XI_buf[FFT_SIZE-i-BIN_900] = XI_buf[FFT_SIZE-BIN_FC-i];
|
||||
XQ_buf[i+BIN_900] = XQ_buf[BIN_FC+i];
|
||||
XQ_buf[FFT_SIZE-i-BIN_900] = XQ_buf[FFT_SIZE-BIN_FC-i];
|
||||
}
|
||||
// Bandpass CW
|
||||
dsp_bandpass(BIN_900-BIN_300, BIN_900+BIN_300);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/*** Execute inverse FFT ***/
|
||||
scale1 = fix_fft(&XI_buf[0], &XQ_buf[0], true);
|
||||
|
||||
|
||||
/*** Export FFT buffer to A ***/
|
||||
b = dsp_active; // Assume active buffer not changed, i.e. no overruns
|
||||
if (++b > 2) b = 0; // Point to oldest
|
||||
ap = &A_buf[b][0]; xip = &XI_buf[0];
|
||||
for (i=0; i<BUFSIZE; i++)
|
||||
{
|
||||
*ap++ = *xip++; // Copy oldest results
|
||||
}
|
||||
|
||||
|
||||
/*** Scale down into DAC_RANGE! ***/
|
||||
peak = 128;
|
||||
for (i=0; i<BUFSIZE; i++)
|
||||
{
|
||||
A_buf[b][i] /= peak;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/** CORE1: TX branch **/
|
||||
/*
|
||||
* Execute TX branch signal processing
|
||||
*/
|
||||
bool __not_in_flash_func(tx)(void)
|
||||
{
|
||||
// Export FFT buffers to I/Q
|
||||
|
||||
// Import A buffers
|
||||
|
||||
// FFT
|
||||
|
||||
// Filter
|
||||
|
||||
// iFFT
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* dsp_tim.c
|
||||
* ==>TO BE INCLUDED IN dsp.c
|
||||
*
|
||||
* Created: May 2022
|
||||
* Author: Arjan te Marvelde
|
||||
*
|
||||
* Signal processing of RX and TX branch, to be run on the second processor core.
|
||||
* Each branch has a dedicated routine that must run on set times.
|
||||
* The period is determined by reads from the inter-core fifo, by the dsp_loop() routine.
|
||||
* This fifo is written from core0 from a 16us timer callback routine (i.e. 62.5kHz)
|
||||
*
|
||||
* The RX branch:
|
||||
* - Sample I and Q QSD channels, and shift into I and Q delay line (62.5 kHz per channel)
|
||||
* - Low pass filter: Fc=4kHz
|
||||
* - Quarter rate (15.625 kHz) to improve low freq behavior of Hilbert transform
|
||||
* - Calculate 15 tap Hilbert transform on Q
|
||||
* - Demodulate, taking proper delays into account
|
||||
* - Push to Audio output DAC
|
||||
*
|
||||
* Always perform audio sampling (62.5kHz) and level detections, in case of VOX active
|
||||
*
|
||||
* The TX branch (if VOX or PTT):
|
||||
* - Low pass filter: Fc=3kHz
|
||||
* - Eight rate (7.8125 kHz) to improve low F behavior of Hilbert transform
|
||||
* - Generate Q samples by doing a Hilbert transform
|
||||
* - Push I and Q to QSE output DACs
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Low pass FIR filters Fc=3, 7 and 15 kHz (see http://t-filter.engineerjs.com/)
|
||||
* Settings: sample rates 62500, 31250 or 15625 Hz, stopband -40dB, passband ripple 5dB
|
||||
* Note: 8 bit precision, so divide sum by 256 (this could be improved when 32bit accumulator)
|
||||
*/
|
||||
int16_t lpf3_62[15] = { 3, 3, 5, 7, 9, 10, 11, 11, 11, 10, 9, 7, 5, 3, 3}; // Pass: 0-3000, Stop: 6000-31250
|
||||
int16_t lpf3_31[15] = { -2, -3, -3, 1, 10, 21, 31, 35, 31, 21, 10, 1, -3, -3, -2}; // Pass: 0-3000, Stop: 6000-15625
|
||||
int16_t lpf3_15[15] = { 3, 4, -3,-14,-15, 6, 38, 53, 38, 6,-15,-14, -3, 4, 3}; // Pass: 0-3000, Stop: 4500-7812
|
||||
int16_t lpf7_62[15] = { -2, -1, 1, 7, 16, 26, 33, 36, 33, 26, 16, 7, 1, -1, -2}; // Pass: 0-7000, Stop: 10000-31250
|
||||
int16_t lpf7_31[15] = { -1, 4, 9, 2,-12, -2, 40, 66, 40, -2,-12, 2, 9, 4, -1}; // Pass: 0-7000, Stop: 10000-15625
|
||||
int16_t lpf15_62[15] = { -1, 3, 12, 6,-12, -4, 40, 69, 40, -4,-12, 6, 12, 3, -1}; // Pass: 0-15000, Stop: 20000-31250
|
||||
|
||||
|
||||
|
||||
|
||||
/** CORE1: RX Branch **/
|
||||
|
||||
/*
|
||||
* Execute RX branch signal processing
|
||||
*/
|
||||
volatile int32_t i_s_raw[15], q_s_raw[15]; // Raw I/Q samples minus DC bias
|
||||
volatile uint16_t peak=0; // Peak detector running value
|
||||
volatile int16_t agc_gain=0; // AGC gain (left-shift value)
|
||||
volatile int16_t agc_accu=0; // Log peak level integrator
|
||||
volatile int32_t i_s[15], q_s[15]; // Filtered I/Q samples
|
||||
volatile int32_t i2, q2; // Squared samples
|
||||
bool __not_in_flash_func(rx)(void)
|
||||
{
|
||||
int32_t q_accu, i_accu;
|
||||
int32_t qh;
|
||||
uint16_t i;
|
||||
int16_t k;
|
||||
|
||||
/*** SAMPLING ***/
|
||||
/*
|
||||
* Shift-in I and Q raw samples
|
||||
*/
|
||||
for (i=0; i<14; i++)
|
||||
{
|
||||
q_s_raw[i] = q_s_raw[i+1];
|
||||
i_s_raw[i] = i_s_raw[i+1];
|
||||
}
|
||||
q_s_raw[14] = q_sample; // Store decimated samples in shift registers
|
||||
i_s_raw[14] = i_sample;
|
||||
|
||||
|
||||
/*
|
||||
* Low pass FIR filter
|
||||
*/
|
||||
q_accu = 0; // Initialize accumulators
|
||||
i_accu = 0;
|
||||
for (i=0; i<15; i++) // Low pass FIR filter
|
||||
{
|
||||
q_accu += (int32_t)q_s_raw[i]*lpf3_15[i]; // Fc=3kHz, at 15625 Hz sampling
|
||||
i_accu += (int32_t)i_s_raw[i]*lpf3_15[i]; // Fc=3kHz, at 15625 Hz sampling
|
||||
}
|
||||
q_accu = q_accu/256;
|
||||
i_accu = i_accu/256;
|
||||
|
||||
for (i=0; i<14; i++) // Shift filtered samples
|
||||
{
|
||||
q_s[i] = q_s[i+1];
|
||||
i_s[i] = i_s[i+1];
|
||||
}
|
||||
q_s[14] = q_accu;
|
||||
i_s[14] = i_accu;
|
||||
|
||||
|
||||
/*** DEMODULATION ***/
|
||||
switch (dsp_mode)
|
||||
{
|
||||
case MODE_USB:
|
||||
/*
|
||||
* USB demodulate: I[7] - Qh,
|
||||
* Qh is Classic Hilbert transform 15 taps, 12 bits
|
||||
* (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (q_s[0]-q_s[14])*315L + (q_s[2]-q_s[12])*440L +
|
||||
(q_s[4]-q_s[10])*734L + (q_s[6]-q_s[ 8])*2202L;
|
||||
qh = q_accu / 4096L;
|
||||
a_sample = i_s[7] - qh;
|
||||
break;
|
||||
case MODE_LSB:
|
||||
/*
|
||||
* LSB demodulate: I[7] + Qh,
|
||||
* Qh is Classic Hilbert transform 15 taps, 12 bits
|
||||
* (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (q_s[0]-q_s[14])*315L + (q_s[2]-q_s[12])*440L +
|
||||
(q_s[4]-q_s[10])*734L + (q_s[6]-q_s[ 8])*2202L;
|
||||
qh = q_accu / 4096L;
|
||||
a_sample = i_s[7] + qh;
|
||||
break;
|
||||
case MODE_AM:
|
||||
/*
|
||||
* AM demodulate: sqrt(sqr(i)+sqr(q))
|
||||
* Approximated with mag(i,q)
|
||||
*/
|
||||
a_sample = mag(i_s[7], q_s[7]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/*** AUDIO GENERATION ***/
|
||||
|
||||
/*
|
||||
* Scale and clip output,
|
||||
* Send to audio DAC output
|
||||
*/
|
||||
a_sample = (a_sample/32) + DAC_BIAS; // -15dB and add bias level
|
||||
if (a_sample > DAC_RANGE) // Clip to DAC range
|
||||
a_sample = DAC_RANGE;
|
||||
else if (a_sample<0)
|
||||
a_sample = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/** CORE1: TX branch **/
|
||||
/*
|
||||
* Execute TX branch signal processing,
|
||||
* max time to spend is <16us, i.e. rate is 62.5 kHz
|
||||
* The audio sampling has already been done in vox()
|
||||
*/
|
||||
volatile int16_t a_s_raw[15]; // Raw samples, minus DC bias
|
||||
volatile int16_t a_s[15]; // Filtered and decimated samplesvolatile int16_t a_dc; // DC level
|
||||
bool __not_in_flash_func(tx)(void)
|
||||
{
|
||||
int32_t a_accu, q_accu;
|
||||
int16_t qh;
|
||||
int i;
|
||||
uint16_t i_dac, q_dac;
|
||||
|
||||
/*** RAW Audio ***/
|
||||
for (i=0; i<14; i++) // and store in shift register
|
||||
a_s_raw[i] = a_s_raw[i+1];
|
||||
a_s_raw[14] = a_sample;
|
||||
|
||||
/*** Low pass filter ***/
|
||||
a_accu = 0; // Initialize accumulator
|
||||
for (i=0; i<15; i++) // Low pass FIR filter, using raw samples
|
||||
a_accu += (int32_t)a_s_raw[i]*lpf3_15[i]; // Fc=3kHz, at 15.625 kHz sampling
|
||||
|
||||
for (i=0; i<14; i++) // Shift decimated samples
|
||||
a_s[i] = a_s[i+1];
|
||||
a_s[14] = a_accu / 256; // Store rescaled accumulator
|
||||
|
||||
/*** MODULATION ***/
|
||||
switch (dsp_mode)
|
||||
{
|
||||
case 0: // USB
|
||||
/*
|
||||
* qh is Classic Hilbert transform 15 taps, 12 bits
|
||||
* (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (a_s[0]-a_s[14])*315L + (a_s[2]-a_s[12])*440L +
|
||||
(a_s[4]-a_s[10])*734L + (a_s[6]-a_s[ 8])*2202L;
|
||||
qh = -(q_accu / 4096L); // USB: sign is negative
|
||||
break;
|
||||
case 1: // LSB
|
||||
/*
|
||||
* qh is Classic Hilbert transform 15 taps, 12 bits
|
||||
* (see Iowa Hills calculator)
|
||||
*/
|
||||
q_accu = (a_s[0]-a_s[14])*315L + (a_s[2]-a_s[12])*440L +
|
||||
(a_s[4]-a_s[10])*734L + (a_s[6]-a_s[ 8])*2202L;
|
||||
qh = q_accu / 4096L; // LSB: sign is positive
|
||||
break;
|
||||
case 2: // AM
|
||||
/*
|
||||
* I and Q values are identical
|
||||
*/
|
||||
qh = a_s[7];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write I and Q to QSE DACs, phase is 7 samples back.
|
||||
* Need to multiply AC with DAC_RANGE/ADC_RANGE (appr 1/8)
|
||||
* Any case: clip to range
|
||||
*/
|
||||
a_accu = DAC_BIAS - (qh/8);
|
||||
if (a_accu<0)
|
||||
q_sample = 0;
|
||||
else if (a_accu>(DAC_RANGE-1))
|
||||
q_sample = DAC_RANGE-1;
|
||||
else
|
||||
q_sample = a_accu;
|
||||
|
||||
a_accu = DAC_BIAS + (a_s[7]/8);
|
||||
if (a_accu<0)
|
||||
i_sample = 0;
|
||||
else if (a_accu>(DAC_RANGE-1))
|
||||
i_sample = DAC_RANGE-1;
|
||||
else
|
||||
i_sample = a_accu;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,373 @@
|
|||
/* fix_fft.c - Fixed-point in-place DIT Fast Fourier Transform */
|
||||
/*
|
||||
All data are fixed-point uint16_t integers, in which -32768
|
||||
to +32768 represent -1.0 to +1.0 respectively. Integer
|
||||
arithmetic is used for speed, instead of the more natural
|
||||
floating-point.
|
||||
|
||||
For the forward FFT (time -> freq), fixed scaling is
|
||||
performed to prevent arithmetic overflow, and to map a 0dB
|
||||
sine/cosine wave (i.e. amplitude = 32767) to two -6dB freq
|
||||
coefficients. The return value is always 0.
|
||||
|
||||
For the inverse FFT (freq -> time), fixed scaling cannot be
|
||||
done, as two 0dB coefficients would sum to a peak amplitude
|
||||
of 64K, overflowing the 32k range of the fixed-point integers.
|
||||
Thus, the fix_fft() routine performs variable scaling, and
|
||||
returns a value which is the number of bits LEFT by which
|
||||
the output must be shifted to get the actual amplitude
|
||||
(i.e. if fix_fft() returns 3, each value of fr[] and fi[]
|
||||
must be multiplied by 8 (2**3) for proper scaling.
|
||||
Clearly, this cannot be done within fixed-point uint16_t
|
||||
integers. In practice, if the result is to be used as a
|
||||
filter, the scale_shift can usually be ignored, as the
|
||||
result will be approximately correctly normalized as is.
|
||||
|
||||
Written by: Tom Roberts 11/8/89
|
||||
Made portable: Malcolm Slaney 12/15/94 malcolm@interval.com
|
||||
Enhanced: Dimitrios P. Bouras 14 Jun 2006 dbouras@ieee.org
|
||||
*/
|
||||
/*
|
||||
This implementation uses a lookup table for bit reverse sorting,
|
||||
which adds 2kbyte to the memory footprint.
|
||||
The iFFT range detector has been optimized.
|
||||
The bitshifting of signed integers is undefined, so these have been
|
||||
replaced by divisions. The compiler will optimize it.
|
||||
The size is fixed at 1024.
|
||||
*/
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/multicore.h"
|
||||
#include "pico/platform.h"
|
||||
#include "fix_fft.h"
|
||||
|
||||
|
||||
/** Fixed point Sine lookup table, [-1, 1] == [-32766, 32767] **/
|
||||
int16_t Sine[3*FFT_SIZE/4] =
|
||||
{
|
||||
0, 201, 402, 603, 804, 1005, 1206, 1406,
|
||||
1607, 1808, 2009, 2209, 2410, 2610, 2811, 3011,
|
||||
3211, 3411, 3611, 3811, 4011, 4210, 4409, 4608,
|
||||
4807, 5006, 5205, 5403, 5601, 5799, 5997, 6195,
|
||||
6392, 6589, 6786, 6982, 7179, 7375, 7571, 7766,
|
||||
7961, 8156, 8351, 8545, 8739, 8932, 9126, 9319,
|
||||
9511, 9703, 9895, 10087, 10278, 10469, 10659, 10849,
|
||||
11038, 11227, 11416, 11604, 11792, 11980, 12166, 12353,
|
||||
12539, 12724, 12909, 13094, 13278, 13462, 13645, 13827,
|
||||
14009, 14191, 14372, 14552, 14732, 14911, 15090, 15268,
|
||||
15446, 15623, 15799, 15975, 16150, 16325, 16499, 16672,
|
||||
16845, 17017, 17189, 17360, 17530, 17699, 17868, 18036,
|
||||
18204, 18371, 18537, 18702, 18867, 19031, 19194, 19357,
|
||||
19519, 19680, 19840, 20000, 20159, 20317, 20474, 20631,
|
||||
20787, 20942, 21096, 21249, 21402, 21554, 21705, 21855,
|
||||
22004, 22153, 22301, 22448, 22594, 22739, 22883, 23027,
|
||||
23169, 23311, 23452, 23592, 23731, 23869, 24006, 24143,
|
||||
24278, 24413, 24546, 24679, 24811, 24942, 25072, 25201,
|
||||
25329, 25456, 25582, 25707, 25831, 25954, 26077, 26198,
|
||||
26318, 26437, 26556, 26673, 26789, 26905, 27019, 27132,
|
||||
27244, 27355, 27466, 27575, 27683, 27790, 27896, 28001,
|
||||
28105, 28208, 28309, 28410, 28510, 28608, 28706, 28802,
|
||||
28897, 28992, 29085, 29177, 29268, 29358, 29446, 29534,
|
||||
29621, 29706, 29790, 29873, 29955, 30036, 30116, 30195,
|
||||
30272, 30349, 30424, 30498, 30571, 30643, 30713, 30783,
|
||||
30851, 30918, 30984, 31049, 31113, 31175, 31236, 31297,
|
||||
31356, 31413, 31470, 31525, 31580, 31633, 31684, 31735,
|
||||
31785, 31833, 31880, 31926, 31970, 32014, 32056, 32097,
|
||||
32137, 32176, 32213, 32249, 32284, 32318, 32350, 32382,
|
||||
32412, 32441, 32468, 32495, 32520, 32544, 32567, 32588,
|
||||
32609, 32628, 32646, 32662, 32678, 32692, 32705, 32717,
|
||||
32727, 32736, 32744, 32751, 32757, 32761, 32764, 32766,
|
||||
32767, 32766, 32764, 32761, 32757, 32751, 32744, 32736,
|
||||
32727, 32717, 32705, 32692, 32678, 32662, 32646, 32628,
|
||||
32609, 32588, 32567, 32544, 32520, 32495, 32468, 32441,
|
||||
32412, 32382, 32350, 32318, 32284, 32249, 32213, 32176,
|
||||
32137, 32097, 32056, 32014, 31970, 31926, 31880, 31833,
|
||||
31785, 31735, 31684, 31633, 31580, 31525, 31470, 31413,
|
||||
31356, 31297, 31236, 31175, 31113, 31049, 30984, 30918,
|
||||
30851, 30783, 30713, 30643, 30571, 30498, 30424, 30349,
|
||||
30272, 30195, 30116, 30036, 29955, 29873, 29790, 29706,
|
||||
29621, 29534, 29446, 29358, 29268, 29177, 29085, 28992,
|
||||
28897, 28802, 28706, 28608, 28510, 28410, 28309, 28208,
|
||||
28105, 28001, 27896, 27790, 27683, 27575, 27466, 27355,
|
||||
27244, 27132, 27019, 26905, 26789, 26673, 26556, 26437,
|
||||
26318, 26198, 26077, 25954, 25831, 25707, 25582, 25456,
|
||||
25329, 25201, 25072, 24942, 24811, 24679, 24546, 24413,
|
||||
24278, 24143, 24006, 23869, 23731, 23592, 23452, 23311,
|
||||
23169, 23027, 22883, 22739, 22594, 22448, 22301, 22153,
|
||||
22004, 21855, 21705, 21554, 21402, 21249, 21096, 20942,
|
||||
20787, 20631, 20474, 20317, 20159, 20000, 19840, 19680,
|
||||
19519, 19357, 19194, 19031, 18867, 18702, 18537, 18371,
|
||||
18204, 18036, 17868, 17699, 17530, 17360, 17189, 17017,
|
||||
16845, 16672, 16499, 16325, 16150, 15975, 15799, 15623,
|
||||
15446, 15268, 15090, 14911, 14732, 14552, 14372, 14191,
|
||||
14009, 13827, 13645, 13462, 13278, 13094, 12909, 12724,
|
||||
12539, 12353, 12166, 11980, 11792, 11604, 11416, 11227,
|
||||
11038, 10849, 10659, 10469, 10278, 10087, 9895, 9703,
|
||||
9511, 9319, 9126, 8932, 8739, 8545, 8351, 8156,
|
||||
7961, 7766, 7571, 7375, 7179, 6982, 6786, 6589,
|
||||
6392, 6195, 5997, 5799, 5601, 5403, 5205, 5006,
|
||||
4807, 4608, 4409, 4210, 4011, 3811, 3611, 3411,
|
||||
3211, 3011, 2811, 2610, 2410, 2209, 2009, 1808,
|
||||
1607, 1406, 1206, 1005, 804, 603, 402, 201,
|
||||
0, -201, -402, -603, -804, -1005, -1206, -1406,
|
||||
-1607, -1808, -2009, -2209, -2410, -2610, -2811, -3011,
|
||||
-3211, -3411, -3611, -3811, -4011, -4210, -4409, -4608,
|
||||
-4807, -5006, -5205, -5403, -5601, -5799, -5997, -6195,
|
||||
-6392, -6589, -6786, -6982, -7179, -7375, -7571, -7766,
|
||||
-7961, -8156, -8351, -8545, -8739, -8932, -9126, -9319,
|
||||
-9511, -9703, -9895, -10087, -10278, -10469, -10659, -10849,
|
||||
-11038, -11227, -11416, -11604, -11792, -11980, -12166, -12353,
|
||||
-12539, -12724, -12909, -13094, -13278, -13462, -13645, -13827,
|
||||
-14009, -14191, -14372, -14552, -14732, -14911, -15090, -15268,
|
||||
-15446, -15623, -15799, -15975, -16150, -16325, -16499, -16672,
|
||||
-16845, -17017, -17189, -17360, -17530, -17699, -17868, -18036,
|
||||
-18204, -18371, -18537, -18702, -18867, -19031, -19194, -19357,
|
||||
-19519, -19680, -19840, -20000, -20159, -20317, -20474, -20631,
|
||||
-20787, -20942, -21096, -21249, -21402, -21554, -21705, -21855,
|
||||
-22004, -22153, -22301, -22448, -22594, -22739, -22883, -23027,
|
||||
-23169, -23311, -23452, -23592, -23731, -23869, -24006, -24143,
|
||||
-24278, -24413, -24546, -24679, -24811, -24942, -25072, -25201,
|
||||
-25329, -25456, -25582, -25707, -25831, -25954, -26077, -26198,
|
||||
-26318, -26437, -26556, -26673, -26789, -26905, -27019, -27132,
|
||||
-27244, -27355, -27466, -27575, -27683, -27790, -27896, -28001,
|
||||
-28105, -28208, -28309, -28410, -28510, -28608, -28706, -28802,
|
||||
-28897, -28992, -29085, -29177, -29268, -29358, -29446, -29534,
|
||||
-29621, -29706, -29790, -29873, -29955, -30036, -30116, -30195,
|
||||
-30272, -30349, -30424, -30498, -30571, -30643, -30713, -30783,
|
||||
-30851, -30918, -30984, -31049, -31113, -31175, -31236, -31297,
|
||||
-31356, -31413, -31470, -31525, -31580, -31633, -31684, -31735,
|
||||
-31785, -31833, -31880, -31926, -31970, -32014, -32056, -32097,
|
||||
-32137, -32176, -32213, -32249, -32284, -32318, -32350, -32382,
|
||||
-32412, -32441, -32468, -32495, -32520, -32544, -32567, -32588,
|
||||
-32609, -32628, -32646, -32662, -32678, -32692, -32705, -32717,
|
||||
-32727, -32736, -32744, -32751, -32757, -32761, -32764, -32766
|
||||
};
|
||||
|
||||
|
||||
static int16_t bitrev[FFT_SIZE] =
|
||||
{
|
||||
0x000, 0x200, 0x100, 0x300, 0x080, 0x280, 0x180, 0x380, 0x040, 0x240, 0x140, 0x340, 0x0c0, 0x2c0, 0x1c0, 0x3c0,
|
||||
0x020, 0x220, 0x120, 0x320, 0x0a0, 0x2a0, 0x1a0, 0x3a0, 0x060, 0x260, 0x160, 0x360, 0x0e0, 0x2e0, 0x1e0, 0x3e0,
|
||||
0x010, 0x210, 0x110, 0x310, 0x090, 0x290, 0x190, 0x390, 0x050, 0x250, 0x150, 0x350, 0x0d0, 0x2d0, 0x1d0, 0x3d0,
|
||||
0x030, 0x230, 0x130, 0x330, 0x0b0, 0x2b0, 0x1b0, 0x3b0, 0x070, 0x270, 0x170, 0x370, 0x0f0, 0x2f0, 0x1f0, 0x3f0,
|
||||
0x008, 0x208, 0x108, 0x308, 0x088, 0x288, 0x188, 0x388, 0x048, 0x248, 0x148, 0x348, 0x0c8, 0x2c8, 0x1c8, 0x3c8,
|
||||
0x028, 0x228, 0x128, 0x328, 0x0a8, 0x2a8, 0x1a8, 0x3a8, 0x068, 0x268, 0x168, 0x368, 0x0e8, 0x2e8, 0x1e8, 0x3e8,
|
||||
0x018, 0x218, 0x118, 0x318, 0x098, 0x298, 0x198, 0x398, 0x058, 0x258, 0x158, 0x358, 0x0d8, 0x2d8, 0x1d8, 0x3d8,
|
||||
0x038, 0x238, 0x138, 0x338, 0x0b8, 0x2b8, 0x1b8, 0x3b8, 0x078, 0x278, 0x178, 0x378, 0x0f8, 0x2f8, 0x1f8, 0x3f8,
|
||||
0x004, 0x204, 0x104, 0x304, 0x084, 0x284, 0x184, 0x384, 0x044, 0x244, 0x144, 0x344, 0x0c4, 0x2c4, 0x1c4, 0x3c4,
|
||||
0x024, 0x224, 0x124, 0x324, 0x0a4, 0x2a4, 0x1a4, 0x3a4, 0x064, 0x264, 0x164, 0x364, 0x0e4, 0x2e4, 0x1e4, 0x3e4,
|
||||
0x014, 0x214, 0x114, 0x314, 0x094, 0x294, 0x194, 0x394, 0x054, 0x254, 0x154, 0x354, 0x0d4, 0x2d4, 0x1d4, 0x3d4,
|
||||
0x034, 0x234, 0x134, 0x334, 0x0b4, 0x2b4, 0x1b4, 0x3b4, 0x074, 0x274, 0x174, 0x374, 0x0f4, 0x2f4, 0x1f4, 0x3f4,
|
||||
0x00c, 0x20c, 0x10c, 0x30c, 0x08c, 0x28c, 0x18c, 0x38c, 0x04c, 0x24c, 0x14c, 0x34c, 0x0cc, 0x2cc, 0x1cc, 0x3cc,
|
||||
0x02c, 0x22c, 0x12c, 0x32c, 0x0ac, 0x2ac, 0x1ac, 0x3ac, 0x06c, 0x26c, 0x16c, 0x36c, 0x0ec, 0x2ec, 0x1ec, 0x3ec,
|
||||
0x01c, 0x21c, 0x11c, 0x31c, 0x09c, 0x29c, 0x19c, 0x39c, 0x05c, 0x25c, 0x15c, 0x35c, 0x0dc, 0x2dc, 0x1dc, 0x3dc,
|
||||
0x03c, 0x23c, 0x13c, 0x33c, 0x0bc, 0x2bc, 0x1bc, 0x3bc, 0x07c, 0x27c, 0x17c, 0x37c, 0x0fc, 0x2fc, 0x1fc, 0x3fc,
|
||||
0x002, 0x202, 0x102, 0x302, 0x082, 0x282, 0x182, 0x382, 0x042, 0x242, 0x142, 0x342, 0x0c2, 0x2c2, 0x1c2, 0x3c2,
|
||||
0x022, 0x222, 0x122, 0x322, 0x0a2, 0x2a2, 0x1a2, 0x3a2, 0x062, 0x262, 0x162, 0x362, 0x0e2, 0x2e2, 0x1e2, 0x3e2,
|
||||
0x012, 0x212, 0x112, 0x312, 0x092, 0x292, 0x192, 0x392, 0x052, 0x252, 0x152, 0x352, 0x0d2, 0x2d2, 0x1d2, 0x3d2,
|
||||
0x032, 0x232, 0x132, 0x332, 0x0b2, 0x2b2, 0x1b2, 0x3b2, 0x072, 0x272, 0x172, 0x372, 0x0f2, 0x2f2, 0x1f2, 0x3f2,
|
||||
0x00a, 0x20a, 0x10a, 0x30a, 0x08a, 0x28a, 0x18a, 0x38a, 0x04a, 0x24a, 0x14a, 0x34a, 0x0ca, 0x2ca, 0x1ca, 0x3ca,
|
||||
0x02a, 0x22a, 0x12a, 0x32a, 0x0aa, 0x2aa, 0x1aa, 0x3aa, 0x06a, 0x26a, 0x16a, 0x36a, 0x0ea, 0x2ea, 0x1ea, 0x3ea,
|
||||
0x01a, 0x21a, 0x11a, 0x31a, 0x09a, 0x29a, 0x19a, 0x39a, 0x05a, 0x25a, 0x15a, 0x35a, 0x0da, 0x2da, 0x1da, 0x3da,
|
||||
0x03a, 0x23a, 0x13a, 0x33a, 0x0ba, 0x2ba, 0x1ba, 0x3ba, 0x07a, 0x27a, 0x17a, 0x37a, 0x0fa, 0x2fa, 0x1fa, 0x3fa,
|
||||
0x006, 0x206, 0x106, 0x306, 0x086, 0x286, 0x186, 0x386, 0x046, 0x246, 0x146, 0x346, 0x0c6, 0x2c6, 0x1c6, 0x3c6,
|
||||
0x026, 0x226, 0x126, 0x326, 0x0a6, 0x2a6, 0x1a6, 0x3a6, 0x066, 0x266, 0x166, 0x366, 0x0e6, 0x2e6, 0x1e6, 0x3e6,
|
||||
0x016, 0x216, 0x116, 0x316, 0x096, 0x296, 0x196, 0x396, 0x056, 0x256, 0x156, 0x356, 0x0d6, 0x2d6, 0x1d6, 0x3d6,
|
||||
0x036, 0x236, 0x136, 0x336, 0x0b6, 0x2b6, 0x1b6, 0x3b6, 0x076, 0x276, 0x176, 0x376, 0x0f6, 0x2f6, 0x1f6, 0x3f6,
|
||||
0x00e, 0x20e, 0x10e, 0x30e, 0x08e, 0x28e, 0x18e, 0x38e, 0x04e, 0x24e, 0x14e, 0x34e, 0x0ce, 0x2ce, 0x1ce, 0x3ce,
|
||||
0x02e, 0x22e, 0x12e, 0x32e, 0x0ae, 0x2ae, 0x1ae, 0x3ae, 0x06e, 0x26e, 0x16e, 0x36e, 0x0ee, 0x2ee, 0x1ee, 0x3ee,
|
||||
0x01e, 0x21e, 0x11e, 0x31e, 0x09e, 0x29e, 0x19e, 0x39e, 0x05e, 0x25e, 0x15e, 0x35e, 0x0de, 0x2de, 0x1de, 0x3de,
|
||||
0x03e, 0x23e, 0x13e, 0x33e, 0x0be, 0x2be, 0x1be, 0x3be, 0x07e, 0x27e, 0x17e, 0x37e, 0x0fe, 0x2fe, 0x1fe, 0x3fe,
|
||||
0x001, 0x201, 0x101, 0x301, 0x081, 0x281, 0x181, 0x381, 0x041, 0x241, 0x141, 0x341, 0x0c1, 0x2c1, 0x1c1, 0x3c1,
|
||||
0x021, 0x221, 0x121, 0x321, 0x0a1, 0x2a1, 0x1a1, 0x3a1, 0x061, 0x261, 0x161, 0x361, 0x0e1, 0x2e1, 0x1e1, 0x3e1,
|
||||
0x011, 0x211, 0x111, 0x311, 0x091, 0x291, 0x191, 0x391, 0x051, 0x251, 0x151, 0x351, 0x0d1, 0x2d1, 0x1d1, 0x3d1,
|
||||
0x031, 0x231, 0x131, 0x331, 0x0b1, 0x2b1, 0x1b1, 0x3b1, 0x071, 0x271, 0x171, 0x371, 0x0f1, 0x2f1, 0x1f1, 0x3f1,
|
||||
0x009, 0x209, 0x109, 0x309, 0x089, 0x289, 0x189, 0x389, 0x049, 0x249, 0x149, 0x349, 0x0c9, 0x2c9, 0x1c9, 0x3c9,
|
||||
0x029, 0x229, 0x129, 0x329, 0x0a9, 0x2a9, 0x1a9, 0x3a9, 0x069, 0x269, 0x169, 0x369, 0x0e9, 0x2e9, 0x1e9, 0x3e9,
|
||||
0x019, 0x219, 0x119, 0x319, 0x099, 0x299, 0x199, 0x399, 0x059, 0x259, 0x159, 0x359, 0x0d9, 0x2d9, 0x1d9, 0x3d9,
|
||||
0x039, 0x239, 0x139, 0x339, 0x0b9, 0x2b9, 0x1b9, 0x3b9, 0x079, 0x279, 0x179, 0x379, 0x0f9, 0x2f9, 0x1f9, 0x3f9,
|
||||
0x005, 0x205, 0x105, 0x305, 0x085, 0x285, 0x185, 0x385, 0x045, 0x245, 0x145, 0x345, 0x0c5, 0x2c5, 0x1c5, 0x3c5,
|
||||
0x025, 0x225, 0x125, 0x325, 0x0a5, 0x2a5, 0x1a5, 0x3a5, 0x065, 0x265, 0x165, 0x365, 0x0e5, 0x2e5, 0x1e5, 0x3e5,
|
||||
0x015, 0x215, 0x115, 0x315, 0x095, 0x295, 0x195, 0x395, 0x055, 0x255, 0x155, 0x355, 0x0d5, 0x2d5, 0x1d5, 0x3d5,
|
||||
0x035, 0x235, 0x135, 0x335, 0x0b5, 0x2b5, 0x1b5, 0x3b5, 0x075, 0x275, 0x175, 0x375, 0x0f5, 0x2f5, 0x1f5, 0x3f5,
|
||||
0x00d, 0x20d, 0x10d, 0x30d, 0x08d, 0x28d, 0x18d, 0x38d, 0x04d, 0x24d, 0x14d, 0x34d, 0x0cd, 0x2cd, 0x1cd, 0x3cd,
|
||||
0x02d, 0x22d, 0x12d, 0x32d, 0x0ad, 0x2ad, 0x1ad, 0x3ad, 0x06d, 0x26d, 0x16d, 0x36d, 0x0ed, 0x2ed, 0x1ed, 0x3ed,
|
||||
0x01d, 0x21d, 0x11d, 0x31d, 0x09d, 0x29d, 0x19d, 0x39d, 0x05d, 0x25d, 0x15d, 0x35d, 0x0dd, 0x2dd, 0x1dd, 0x3dd,
|
||||
0x03d, 0x23d, 0x13d, 0x33d, 0x0bd, 0x2bd, 0x1bd, 0x3bd, 0x07d, 0x27d, 0x17d, 0x37d, 0x0fd, 0x2fd, 0x1fd, 0x3fd,
|
||||
0x003, 0x203, 0x103, 0x303, 0x083, 0x283, 0x183, 0x383, 0x043, 0x243, 0x143, 0x343, 0x0c3, 0x2c3, 0x1c3, 0x3c3,
|
||||
0x023, 0x223, 0x123, 0x323, 0x0a3, 0x2a3, 0x1a3, 0x3a3, 0x063, 0x263, 0x163, 0x363, 0x0e3, 0x2e3, 0x1e3, 0x3e3,
|
||||
0x013, 0x213, 0x113, 0x313, 0x093, 0x293, 0x193, 0x393, 0x053, 0x253, 0x153, 0x353, 0x0d3, 0x2d3, 0x1d3, 0x3d3,
|
||||
0x033, 0x233, 0x133, 0x333, 0x0b3, 0x2b3, 0x1b3, 0x3b3, 0x073, 0x273, 0x173, 0x373, 0x0f3, 0x2f3, 0x1f3, 0x3f3,
|
||||
0x00b, 0x20b, 0x10b, 0x30b, 0x08b, 0x28b, 0x18b, 0x38b, 0x04b, 0x24b, 0x14b, 0x34b, 0x0cb, 0x2cb, 0x1cb, 0x3cb,
|
||||
0x02b, 0x22b, 0x12b, 0x32b, 0x0ab, 0x2ab, 0x1ab, 0x3ab, 0x06b, 0x26b, 0x16b, 0x36b, 0x0eb, 0x2eb, 0x1eb, 0x3eb,
|
||||
0x01b, 0x21b, 0x11b, 0x31b, 0x09b, 0x29b, 0x19b, 0x39b, 0x05b, 0x25b, 0x15b, 0x35b, 0x0db, 0x2db, 0x1db, 0x3db,
|
||||
0x03b, 0x23b, 0x13b, 0x33b, 0x0bb, 0x2bb, 0x1bb, 0x3bb, 0x07b, 0x27b, 0x17b, 0x37b, 0x0fb, 0x2fb, 0x1fb, 0x3fb,
|
||||
0x007, 0x207, 0x107, 0x307, 0x087, 0x287, 0x187, 0x387, 0x047, 0x247, 0x147, 0x347, 0x0c7, 0x2c7, 0x1c7, 0x3c7,
|
||||
0x027, 0x227, 0x127, 0x327, 0x0a7, 0x2a7, 0x1a7, 0x3a7, 0x067, 0x267, 0x167, 0x367, 0x0e7, 0x2e7, 0x1e7, 0x3e7,
|
||||
0x017, 0x217, 0x117, 0x317, 0x097, 0x297, 0x197, 0x397, 0x057, 0x257, 0x157, 0x357, 0x0d7, 0x2d7, 0x1d7, 0x3d7,
|
||||
0x037, 0x237, 0x137, 0x337, 0x0b7, 0x2b7, 0x1b7, 0x3b7, 0x077, 0x277, 0x177, 0x377, 0x0f7, 0x2f7, 0x1f7, 0x3f7,
|
||||
0x00f, 0x20f, 0x10f, 0x30f, 0x08f, 0x28f, 0x18f, 0x38f, 0x04f, 0x24f, 0x14f, 0x34f, 0x0cf, 0x2cf, 0x1cf, 0x3cf,
|
||||
0x02f, 0x22f, 0x12f, 0x32f, 0x0af, 0x2af, 0x1af, 0x3af, 0x06f, 0x26f, 0x16f, 0x36f, 0x0ef, 0x2ef, 0x1ef, 0x3ef,
|
||||
0x01f, 0x21f, 0x11f, 0x31f, 0x09f, 0x29f, 0x19f, 0x39f, 0x05f, 0x25f, 0x15f, 0x35f, 0x0df, 0x2df, 0x1df, 0x3df,
|
||||
0x03f, 0x23f, 0x13f, 0x33f, 0x0bf, 0x2bf, 0x1bf, 0x3bf, 0x07f, 0x27f, 0x17f, 0x37f, 0x0ff, 0x2ff, 0x1ff, 0x3ff
|
||||
};
|
||||
|
||||
|
||||
/** FIX_MPY() **/
|
||||
/*
|
||||
* Assume Q(0,15) notation, 1 sign, 0 int, 15 frac bits
|
||||
*/
|
||||
int16_t __not_in_flash_func(FIX_MPY)(int16_t a, int16_t b) // Fixed-point mpy and scaling
|
||||
{
|
||||
int32_t c;
|
||||
|
||||
c = (int32_t)a * (int32_t)b; // multiply
|
||||
c = c + 0x4000; // and round up
|
||||
c = c >>15; // Shift right fractional bits
|
||||
return((int16_t)c); // Return scaled product
|
||||
}
|
||||
|
||||
|
||||
/** FIX_FFT() **/
|
||||
/*
|
||||
* fr[] i samples [1024]
|
||||
* fi[] q samples [1024]
|
||||
* inverse true: iFFT
|
||||
* Note: i-FFT could also be calculated by exchanging the arrays for FFT (fxtbook.pdf 21.7)
|
||||
*/
|
||||
int __not_in_flash_func(fix_fft)(int16_t *fr, int16_t *fi, bool inverse)
|
||||
{
|
||||
uint16_t i, j, m, k, step, scale;
|
||||
bool shift;
|
||||
int16_t qr, qi, tr, ti, wr, wi;
|
||||
int16_t *bp;
|
||||
|
||||
/* Decimation in time: re-order samples */
|
||||
bp=&bitrev[0];
|
||||
for (i=0; i<FFT_SIZE; i++)
|
||||
{
|
||||
if (*bp > i)
|
||||
{
|
||||
tr = fr[i]; fr[i] = fr[*bp]; fr[*bp] = tr;
|
||||
ti = fi[i]; fi[i] = fi[*bp]; fi[*bp] = ti;
|
||||
}
|
||||
bp++;
|
||||
}
|
||||
|
||||
|
||||
scale = 0;
|
||||
step = 1; // Counting up: 1, 2, 4, 8, ...
|
||||
/* FFT Stages */
|
||||
for (k=FFT_ORDER; k>0; k--) // #cycles: FFT_ORDER
|
||||
{
|
||||
/* Scaling
|
||||
* Variable scaling, depends on current data
|
||||
* --> it seems quite CPU intensive to go through complete array
|
||||
* FFT_ORDER times, could this be optimized?
|
||||
* If always scaling:
|
||||
* --> the main loop has log_2(FFT_SIZE) cycles,
|
||||
* resulting in an overall factor of 1/FFT_SIZE,
|
||||
* distributed over cycles to maximize accuracy.
|
||||
*/
|
||||
shift = false; // No shift, unless...
|
||||
for (i=0; i<FFT_SIZE; ++i) // Range test all samples
|
||||
{
|
||||
if ((fr[i] > 0x3fff) || (fr[i] < -0x4000) || (fi[i] > 0x3fff) || (fi[i] < -0x4000))
|
||||
{
|
||||
shift = true;
|
||||
scale++;
|
||||
break; // Bail out at first detect
|
||||
}
|
||||
}
|
||||
|
||||
/* Inner loops resolving the butterflies for each stage*/
|
||||
for (m=0; m<step; m++) // #cycles: step
|
||||
{
|
||||
// Determine wiggle factors
|
||||
j = m << (k-1); // 0 <= j < FFT_SIZE/2
|
||||
wr = Sine[j+FFT_SIZE/4]; // Real part, i.e. Cosine
|
||||
wi = inverse ? Sine[j] : -Sine[j]; // Imaginary part
|
||||
if (shift) { wr = wr/2; wi = wi/2; } // Scale factors by 1/2
|
||||
|
||||
for (i=m; i<FFT_SIZE; i+=(step*2)) // #cycles: FFT_SIZE/step
|
||||
{
|
||||
j = i + step; // re-assign j !
|
||||
tr = FIX_MPY(wr,fr[j]) - FIX_MPY(wi,fi[j]); // Complex multiply
|
||||
ti = FIX_MPY(wr,fi[j]) + FIX_MPY(wi,fr[j]);
|
||||
if (shift)
|
||||
{ qr = fr[i]/2; qi = fi[i]/2; }
|
||||
else
|
||||
{ qr = fr[i]; qi = fi[i]; }
|
||||
fr[i] = qr + tr;
|
||||
fi[i] = qi + ti;
|
||||
fr[j] = qr - tr;
|
||||
fi[j] = qi - ti;
|
||||
} // #total: FFT_ORDER * step * FFT_SIZE/step
|
||||
}
|
||||
|
||||
step = step<<1;
|
||||
}
|
||||
return scale;
|
||||
}
|
||||
|
||||
|
||||
#ifdef BLAH
|
||||
// int16_t contains signed fixed point representation Q(1,14)
|
||||
// 1 sign bit, 1 int bit and 14 frac bits
|
||||
// precomputed value K represents 0.5
|
||||
#define Q 14
|
||||
#define K (1 << (Q - 1))
|
||||
|
||||
// a + b
|
||||
int16_t q_add(int16_t a, int16_t b)
|
||||
{
|
||||
int32_t tmp;
|
||||
|
||||
tmp = (int32_t)a + (int32_t)b;
|
||||
|
||||
if (tmp > 0x7FFF) // Clip result
|
||||
tmp = 0x7FFF;
|
||||
else if (tmp < -0x8000)
|
||||
tmp = -0x8000;
|
||||
|
||||
return (int16_t)tmp;
|
||||
}
|
||||
|
||||
// a - b
|
||||
int16_t q_sub(int16_t a, int16_t b)
|
||||
{
|
||||
return a - b;
|
||||
}
|
||||
|
||||
// a * b
|
||||
int16_t q_mul(int16_t a, int16_t b)
|
||||
{
|
||||
int32_t tmp;
|
||||
|
||||
tmp = (int32_t)a * (int32_t)b;
|
||||
tmp += K; // Rounding; mid values are rounded up
|
||||
tmp = tmp >> Q; // Correct by dividing by base
|
||||
|
||||
if (tmp > 0x7FFF) // Clip result
|
||||
tmp = 0x7FFF;
|
||||
else if (tmp < -0x8000)
|
||||
tmp = -0x8000;
|
||||
|
||||
return (int16_t)tmp;
|
||||
}
|
||||
|
||||
// a / b
|
||||
int16_t q_div(int16_t a, int16_t b)
|
||||
{
|
||||
int32_t tmp;
|
||||
|
||||
tmp = (int32_t)a << Q; // Pre multiply with base
|
||||
|
||||
if ((tmp >= 0 && b >= 0) || (tmp < 0 && b < 0)) // Rounding; mid values are rounded up
|
||||
tmp += (b >> 2);
|
||||
else // or down...
|
||||
tmp -= (b >> 2);
|
||||
|
||||
return (int16_t)(tmp / b);
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef __FIX_FFT_H__
|
||||
#define __FIX_FFT_H__
|
||||
/*
|
||||
* fix_fft.h
|
||||
*
|
||||
* Created: Apr 2022
|
||||
* Author: Arjan te Marvelde
|
||||
*
|
||||
* See fix_fft.c for more information
|
||||
*/
|
||||
|
||||
#define FFT_SIZE 1024 // Use this for buffer allocations
|
||||
#define FFT_ORDER 10 // FFT_SIZE = 1 << FFT_ORDER
|
||||
|
||||
int fix_fft(int16_t *fr, int16_t *fi, bool inverse);
|
||||
|
||||
#endif
|
224
hmi.c
224
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_option<HMI_NMODE-1)?hmi_option+1:HMI_NMODE-1;
|
||||
if (event==HMI_E_DECREMENT)
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
case HMI_E_ENTER:
|
||||
hmi_sub[hmi_state] = hmi_option; // Store value for selected option
|
||||
hmi_update = true; // Mark HMI updated: activate value
|
||||
break;
|
||||
case HMI_S_AGC:
|
||||
if (event==HMI_E_INCREMENT)
|
||||
hmi_option = (hmi_option<HMI_NAGC-1)?hmi_option+1:HMI_NAGC-1;
|
||||
if (event==HMI_E_DECREMENT)
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
case HMI_E_ESCAPE:
|
||||
hmi_state = HMI_S_TUNE; // Leave submenus
|
||||
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
|
||||
break;
|
||||
case HMI_S_PRE:
|
||||
if (event==HMI_E_INCREMENT)
|
||||
hmi_option = (hmi_option<HMI_NPRE-1)?hmi_option+1:HMI_NPRE-1;
|
||||
if (event==HMI_E_DECREMENT)
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
case HMI_E_RIGHT:
|
||||
hmi_state = (hmi_state<HMI_NSTATES-1)?(hmi_state+1):1; // Change submenu
|
||||
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
|
||||
break;
|
||||
case HMI_S_VOX:
|
||||
if (event==HMI_E_INCREMENT)
|
||||
hmi_option = (hmi_option<HMI_NVOX-1)?hmi_option+1:HMI_NVOX-1;
|
||||
if (event==HMI_E_DECREMENT)
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
case HMI_E_LEFT:
|
||||
hmi_state = (hmi_state>1)?(hmi_state-1):HMI_NSTATES-1; // Change submenu
|
||||
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
|
||||
break;
|
||||
case HMI_S_BPF:
|
||||
if (event==HMI_E_INCREMENT)
|
||||
hmi_option = (hmi_option<HMI_NBPF-1)?hmi_option+1:HMI_NBPF-1;
|
||||
if (event==HMI_E_DECREMENT)
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
case HMI_E_INCREMENT:
|
||||
hmi_option = (hmi_option<hmi_noption[hmi_state]-1)?hmi_option+1:hmi_noption[hmi_state]-1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* General actions for submenus */
|
||||
if (event==HMI_E_ENTER)
|
||||
{
|
||||
hmi_sub[hmi_state] = hmi_option; // Store selected option
|
||||
hmi_update = true; // Mark HMI updated
|
||||
}
|
||||
if (event==HMI_E_ESCAPE)
|
||||
{
|
||||
hmi_state = HMI_S_TUNE; // Leave submenus
|
||||
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
|
||||
}
|
||||
if (event==HMI_E_RIGHT)
|
||||
{
|
||||
hmi_state = (hmi_state<HMI_NSTATES-1)?(hmi_state+1):1; // Change submenu
|
||||
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
|
||||
}
|
||||
if (event==HMI_E_LEFT)
|
||||
{
|
||||
hmi_state = (hmi_state>1)?(hmi_state-1):HMI_NSTATES-1; // Change submenu
|
||||
hmi_option = hmi_sub[hmi_state]; // Restore selection of new state
|
||||
}
|
||||
case HMI_E_DECREMENT:
|
||||
hmi_option = (hmi_option>0)?hmi_option-1:0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -250,23 +222,23 @@ void hmi_callback(uint gpio, uint32_t events)
|
|||
|
||||
switch (gpio)
|
||||
{
|
||||
case GP_ENC_A: // Encoder
|
||||
case GP_ENC_A: // Encoder
|
||||
if (events&GPIO_IRQ_EDGE_FALL)
|
||||
evt = gpio_get(GP_ENC_B)?HMI_E_INCREMENT:HMI_E_DECREMENT;
|
||||
break;
|
||||
case GP_AUX_0: // Enter
|
||||
case GP_AUX_0: // Enter
|
||||
if (events&GPIO_IRQ_EDGE_FALL)
|
||||
evt = HMI_E_ENTER;
|
||||
break;
|
||||
case GP_AUX_1: // Escape
|
||||
case GP_AUX_1: // Escape
|
||||
if (events&GPIO_IRQ_EDGE_FALL)
|
||||
evt = HMI_E_ESCAPE;
|
||||
break;
|
||||
case GP_AUX_2: // Previous
|
||||
case GP_AUX_2: // Previous
|
||||
if (events&GPIO_IRQ_EDGE_FALL)
|
||||
evt = HMI_E_LEFT;
|
||||
break;
|
||||
case GP_AUX_3: // Next
|
||||
case GP_AUX_3: // Next
|
||||
if (events&GPIO_IRQ_EDGE_FALL)
|
||||
evt = HMI_E_RIGHT;
|
||||
break;
|
||||
|
@ -274,7 +246,7 @@ void hmi_callback(uint gpio, uint32_t events)
|
|||
return;
|
||||
}
|
||||
|
||||
hmi_handler(evt); // Invoke state machine
|
||||
hmi_handler(evt); // Invoke state machine
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -301,6 +273,7 @@ void hmi_init(void)
|
|||
gpio_pull_up(GP_AUX_2);
|
||||
gpio_pull_up(GP_AUX_3);
|
||||
gpio_pull_up(GP_PTT);
|
||||
gpio_set_oeover(GP_PTT, GPIO_OVERRIDE_HIGH); // Enable output on PTT GPIO; bidirectional
|
||||
|
||||
// Enable interrupt on level low
|
||||
gpio_set_irq_enabled(GP_ENC_A, GPIO_IRQ_EDGE_ALL, true);
|
||||
|
@ -315,13 +288,13 @@ void hmi_init(void)
|
|||
|
||||
// Initialize LCD and set VFO
|
||||
hmi_state = HMI_S_TUNE;
|
||||
hmi_option = 4; // Active kHz digit
|
||||
hmi_freq = 7074000UL; // Initial frequency
|
||||
hmi_option = 4; // Active kHz digit
|
||||
hmi_freq = 7074000UL; // Initial frequency
|
||||
|
||||
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq); // Set freq to 7074 kHz (depends on mixer type)
|
||||
SI_SETPHASE(0, 1); // Set phase to 90deg (depends on mixer type)
|
||||
SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Set freq to 7074 kHz (depends on mixer type)
|
||||
SI_SETPHASE(0, 1); // Set phase to 90deg (depends on mixer type)
|
||||
|
||||
ptt_state = 0;
|
||||
ptt_state = PTT_DEBOUNCE;
|
||||
ptt_active = false;
|
||||
|
||||
dsp_setmode(hmi_sub[HMI_S_MODE]);
|
||||
|
@ -381,42 +354,31 @@ void hmi_evaluate(void)
|
|||
}
|
||||
|
||||
/* PTT debouncing */
|
||||
if (hmi_sub[HMI_S_VOX] == 0) // No VOX active
|
||||
if (gpio_get(GP_PTT)) // Get PTT level
|
||||
{
|
||||
gpio_set_dir(GP_PTT, false); // PTT input
|
||||
if (gpio_get(GP_PTT)) // Get PTT level
|
||||
{
|
||||
if (ptt_state<PTT_DEBOUNCE) // Increment debounce counter when high
|
||||
ptt_state++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ptt_state>0) // Decrement debounce counter when low
|
||||
ptt_state--;
|
||||
}
|
||||
if (ptt_state == PTT_DEBOUNCE) // Reset PTT when debounced level high
|
||||
ptt_active = false;
|
||||
if (ptt_state == 0) // Set PTT when debounced level low
|
||||
ptt_active = true;
|
||||
if (ptt_state<PTT_DEBOUNCE) // Increment debounce counter when high
|
||||
ptt_state++;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
if (ptt_state>0) // Decrement debounce counter when low
|
||||
ptt_state--;
|
||||
}
|
||||
if (ptt_state == PTT_DEBOUNCE) // Reset PTT when debounced level high
|
||||
ptt_active = false;
|
||||
gpio_set_dir(GP_PTT, true); // PTT output
|
||||
}
|
||||
if (ptt_state == 0) // Set PTT when debounced level low
|
||||
ptt_active = true;
|
||||
|
||||
|
||||
/* Set parameters corresponding to latest entered option value */
|
||||
SI_SETFREQ(0, HMI_MULFREQ*hmi_freq);
|
||||
dsp_setmode(hmi_sub[HMI_S_MODE]);
|
||||
dsp_setvox(hmi_sub[HMI_S_VOX]);
|
||||
dsp_setagc(hmi_sub[HMI_S_AGC]);
|
||||
if (hmi_update)
|
||||
SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Always set frequency
|
||||
if (hmi_update) // Others only when indicated
|
||||
{
|
||||
dsp_setmode(hmi_sub[HMI_S_MODE]);
|
||||
dsp_setvox(hmi_sub[HMI_S_VOX]);
|
||||
dsp_setagc(hmi_sub[HMI_S_AGC]);
|
||||
relay_setband(hmi_bpf[hmi_sub[HMI_S_BPF]]);
|
||||
sleep_ms(1); // I2C doesn't work without...
|
||||
sleep_ms(1); // I2C doesn't work without...
|
||||
relay_setattn(hmi_pre[hmi_sub[HMI_S_PRE]]);
|
||||
hmi_update = false;
|
||||
}
|
||||
|
|
2
hmi.h
2
hmi.h
|
@ -9,6 +9,8 @@
|
|||
* See hmi.c for more information
|
||||
*/
|
||||
|
||||
#define CARRIER_OFFSET 3500
|
||||
|
||||
extern bool ptt_active;
|
||||
|
||||
void hmi_init(void);
|
||||
|
|
54
lcd.c
54
lcd.c
|
@ -7,13 +7,20 @@
|
|||
* --> Set I2C address below!
|
||||
* --> Select LCD_TYPE below!
|
||||
*
|
||||
* Driver for 16x2 HD44780 based LCD displays.
|
||||
* There exist many different types, so you may need to adapt some of the code.
|
||||
* Most notably the startup sequence and the way bytes are sent over the I2C interface.
|
||||
* But also the register mappings as described below.
|
||||
*
|
||||
* LCD_1804:
|
||||
* ---------
|
||||
* Grove 16x2 LCD HD44780, with integrated JHD1804 I2C bridge (@ 0x3E)
|
||||
* 2 byte interface,
|
||||
* byte0 contains coomand/data,
|
||||
* byte1 contains 8-bit command or data word
|
||||
*
|
||||
* LCD_8574_ADA:
|
||||
* -------------
|
||||
* Standard 16x2 LCD HD44780, with PCF8574 based Adafruit backpack I2C bridge (@ 0x27)
|
||||
* Same registers, but interface uses 4-bit transfers for data/comand, in bits 3..6
|
||||
* bit 0 is unused
|
||||
|
@ -23,6 +30,7 @@
|
|||
* bit 7 is backlight (1 for on)
|
||||
*
|
||||
* LCD_8574_GEN:
|
||||
* -------------
|
||||
* Standard 16x2 LCD HD44780, with PCF8574 based Generic backpack I2C bridge (@ 0x27)
|
||||
* Same registers, but interface uses 4-bit transfers for data/comand, in bits 4..7
|
||||
* bit 0 is Register Select (0 for command, 1 for data)
|
||||
|
@ -31,8 +39,6 @@
|
|||
* bit 3 is backlight (1 for on)
|
||||
* bit 4..7 data or command nibble (write high nibble first)
|
||||
*
|
||||
* Note: There may be other bit-mappings, code needs to be adjusted to that...
|
||||
*
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
@ -43,20 +49,20 @@
|
|||
#include "lcd.h"
|
||||
|
||||
/** User selectable definitions **/
|
||||
// Set I2C address
|
||||
//#define I2C_LCD 0x27
|
||||
#define I2C_LCD 0x3E
|
||||
// Set I2C address for your device
|
||||
#define I2C_LCD 0x3E // Grove: 0x3E, 8574 backpack: 0x20..0x27
|
||||
|
||||
// Select LCD type matching your HW
|
||||
#define LCD_1804 0 // Seeed / Grove
|
||||
#define LCD_8574_ADA 1 // Adafruit I2C backpack
|
||||
#define LCD_8574_GEN 2 // Generic I2C backpack
|
||||
// Select LCD type to match your device,
|
||||
// or define a new one when code changes are needed.
|
||||
#define LCD_1804 0 // Seeed / Grove
|
||||
#define LCD_8574_ADA 1 // Adafruit I2C backpack
|
||||
#define LCD_8574_GEN 2 // Generic I2C backpack
|
||||
#define LCD_TYPE LCD_1804
|
||||
|
||||
|
||||
/** HD44780 interface **/
|
||||
/** Generic HD44780 interface **/
|
||||
// commands
|
||||
#define LCD_CLEARDISPLAY 0x01 // Note: LCD_ENTRYINC is set
|
||||
#define LCD_CLEARDISPLAY 0x01 // Note: LCD_ENTRYINC is set
|
||||
#define LCD_RETURNHOME 0x02
|
||||
#define LCD_ENTRYMODESET 0x04
|
||||
#define LCD_DISPLAYCONTROL 0x08
|
||||
|
@ -68,8 +74,8 @@
|
|||
// flags for display entry mode: LCD_ENTRYMODESET
|
||||
#define LCD_ENTRYSHIFT 0x01
|
||||
#define LCD_ENTRYNOSHIFT 0x00
|
||||
#define LCD_ENTRYINC 0x02 // Also applies to CGRAM writes
|
||||
#define LCD_ENTRYDEC 0x00 // Also applies to CGRAM writes
|
||||
#define LCD_ENTRYINC 0x02 // Also applies to CGRAM writes
|
||||
#define LCD_ENTRYDEC 0x00 // Also applies to CGRAM writes
|
||||
|
||||
// flags for display on/off control: LCD_DISPLAYCONTROL
|
||||
#define LCD_DISPLAYON 0x04
|
||||
|
@ -125,10 +131,8 @@
|
|||
#endif
|
||||
|
||||
|
||||
|
||||
/** Other definitions **/
|
||||
#define LCD_DELAY 100 // Delay for regular write
|
||||
|
||||
#define LCD_DELAY 100 // Delay for regular write
|
||||
|
||||
|
||||
/*
|
||||
|
@ -144,8 +148,8 @@ uint8_t cgram[8][8] =
|
|||
{0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x00, 0x00}, // 0x03: Level 3
|
||||
{0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00}, // 0x04: Level 4
|
||||
{0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00}, // 0x05: Level 5
|
||||
{0x00, 0x04, 0x04, 0x04, 0x1f, 0x0e, 0x04, 0x00}, // 0x06: Receive arrow down
|
||||
{0x04, 0x0e, 0x1f, 0x04, 0x04, 0x04, 0x00, 0x00} // 0x07: Transmit arrow up
|
||||
{0x00, 0x04, 0x04, 0x04, 0x15, 0x0e, 0x04, 0x00}, // 0x06: Receive arrow down
|
||||
{0x04, 0x0e, 0x15, 0x04, 0x04, 0x04, 0x00, 0x00} // 0x07: Transmit arrow up
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -202,7 +206,7 @@ void lcd_sendbyte(uint8_t command, uint8_t data)
|
|||
}
|
||||
|
||||
/*
|
||||
* It seems that there is too much in here, but it doesn't harm either.
|
||||
* It seems that there is too much init here, but it doesn't harm either.
|
||||
*/
|
||||
void lcd_init(void)
|
||||
{
|
||||
|
@ -236,9 +240,9 @@ void lcd_init(void)
|
|||
/* Load CGRAM */
|
||||
for (i=0; i<8; i++)
|
||||
{
|
||||
lcd_sendbyte(true, LCD_SETCGRAMADDR | (i<<3)); //Set CGRAM address
|
||||
lcd_sendbyte(true, LCD_SETCGRAMADDR | (i<<3)); //Set CGRAM address
|
||||
for (int j=0; j<8; j++)
|
||||
lcd_sendbyte(false, cgram[i][j]); // One byte at a time
|
||||
lcd_sendbyte(false, cgram[i][j]); // One byte at a time
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -253,7 +257,7 @@ void lcd_curxy(uint8_t x, uint8_t y, bool on)
|
|||
{
|
||||
uint8_t txdata[3];
|
||||
|
||||
x &= 0x0f; // Clip range
|
||||
x &= 0x0f; // Clip range
|
||||
y &= 0x01;
|
||||
lcd_sendbyte(true, (x | 0x80 | (y==1?0x40:0x00)));
|
||||
lcd_sendbyte(true, LCD_DISPLAYCONTROL | LCD_DISPLAYON | (on?LCD_CURSORON:LCD_CURSOROFF) | LCD_BLINKOFF);
|
||||
|
@ -261,7 +265,7 @@ void lcd_curxy(uint8_t x, uint8_t y, bool on)
|
|||
|
||||
void lcd_putxy(uint8_t x, uint8_t y, uint8_t c)
|
||||
{
|
||||
x &= 0x0f; // Clip range
|
||||
x &= 0x0f; // Clip range
|
||||
y &= 0x01;
|
||||
lcd_sendbyte(true, (x | 0x80 | (y==1?0x40:0x00)));
|
||||
lcd_sendbyte(false, c);
|
||||
|
@ -271,12 +275,12 @@ void lcd_writexy(uint8_t x, uint8_t y, uint8_t *s)
|
|||
{
|
||||
uint8_t i, len;
|
||||
|
||||
x &= 0x0f; // Clip range
|
||||
x &= 0x0f; // Clip range
|
||||
y &= 0x01;
|
||||
lcd_sendbyte(true, (x | 0x80 | (y==1?0x40:0x00)));
|
||||
|
||||
len = strlen(s);
|
||||
len = (len>(16-x))?(16-x):len; // Clip range
|
||||
len = (len>(16-x))?(16-x):len; // Clip range
|
||||
for(i=0; i<len; i++)
|
||||
lcd_sendbyte(false, s[i]);
|
||||
}
|
||||
|
|
92
monitor.c
92
monitor.c
|
@ -89,20 +89,6 @@ void mon_lt(void)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* Checks for inter-core fifo overruns
|
||||
*/
|
||||
extern volatile uint32_t fifo_overrun, fifo_rx, fifo_tx, fifo_xx, fifo_incnt;
|
||||
void mon_fo(void)
|
||||
{
|
||||
printf("Fifo input: %lu\n", fifo_incnt);
|
||||
printf("Fifo rx: %lu\n", fifo_rx);
|
||||
printf("Fifo tx: %lu\n", fifo_tx);
|
||||
printf("Fifo unknown: %lu\n", fifo_xx);
|
||||
printf("Fifo overruns: %lu\n", fifo_overrun);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Toggles the PTT status, overriding the HW signal
|
||||
*/
|
||||
|
@ -131,12 +117,13 @@ void mon_bp(void)
|
|||
|
||||
if (*argv[1]=='w')
|
||||
{
|
||||
if (nargs>=2)
|
||||
if (nargs>2)
|
||||
{
|
||||
ret = atoi(argv[2]);
|
||||
relay_setband((uint8_t)ret);
|
||||
relay_setband(ret);
|
||||
}
|
||||
}
|
||||
sleep_ms(1);
|
||||
ret = relay_getband();
|
||||
if (ret<0)
|
||||
printf ("I2C read error\n");
|
||||
|
@ -153,12 +140,13 @@ void mon_rx(void)
|
|||
|
||||
if (*argv[1]=='w')
|
||||
{
|
||||
if (nargs>=2)
|
||||
if (nargs>2)
|
||||
{
|
||||
ret = atoi(argv[2]);
|
||||
relay_setattn((uint8_t)ret);
|
||||
relay_setattn(ret);
|
||||
}
|
||||
}
|
||||
sleep_ms(1);
|
||||
ret = relay_getattn();
|
||||
if (ret<0)
|
||||
printf ("I2C read error\n");
|
||||
|
@ -167,18 +155,56 @@ void mon_rx(void)
|
|||
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks for overruns
|
||||
*/
|
||||
extern volatile uint32_t dsp_overrun;
|
||||
#if DSP_FFT == 1
|
||||
extern volatile uint32_t dsp_tickx;
|
||||
extern volatile int scale0;
|
||||
extern volatile int scale1;
|
||||
#endif
|
||||
void mon_or(void)
|
||||
{
|
||||
printf("DSP overruns : %d\n", dsp_overrun);
|
||||
#if DSP_FFT == 1
|
||||
printf("DSP loop load : %lu%%\n", (100*dsp_tickx)/512);
|
||||
printf("FFT scale = %d, iFFT scale = %d\n", scale0, scale1);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ADC and AGC levels
|
||||
*/
|
||||
extern volatile uint32_t adc_level[3];
|
||||
extern volatile int32_t rx_agc;
|
||||
extern volatile int adccnt;
|
||||
void mon_adc(void)
|
||||
{
|
||||
// Print results
|
||||
printf("ADC0: %5u/2048\n", adc_level[0]>>8);
|
||||
printf("ADC1: %5u/2048\n", adc_level[1]>>8);
|
||||
printf("ADC2: %5u/2048\n", adc_level[2]>>8);
|
||||
printf("AGC : %7d\n", rx_agc);
|
||||
printf("ADCc: %5d\n", adccnt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Command shell table, organize the command functions above
|
||||
*/
|
||||
#define NCMD 6
|
||||
#define NCMD 7
|
||||
shell_t shell[NCMD]=
|
||||
{
|
||||
{"si", 2, &mon_si, "si <start> <nr of reg>", "Dumps Si5351 registers"},
|
||||
{"lt", 2, &mon_lt, "lt (no parameters)", "LCD test, dumps characterset on LCD"},
|
||||
{"fo", 2, &mon_fo, "fo (no parameters)", "Returns inter core fifo overruns"},
|
||||
{"pt", 2, &mon_pt, "pt (no parameters)", "Toggles PTT status"},
|
||||
{"bp", 2, &mon_bp, "bp {r|w} <value>", "Read or Write BPF relays"},
|
||||
{"rx", 2, &mon_rx, "rx {r|w} <value>", "Read or Write RX relays"}
|
||||
{"si", 2, &mon_si, "si <start> <nr of reg>", "Dumps Si5351 registers"},
|
||||
{"lt", 2, &mon_lt, "lt (no parameters)", "LCD test, dumps characterset on LCD"},
|
||||
{"or", 2, &mon_or, "or (no parameters)", "Returns overrun information"},
|
||||
{"pt", 2, &mon_pt, "pt (no parameters)", "Toggles PTT status"},
|
||||
{"bp", 2, &mon_bp, "bp {r|w} <value>", "Read or Write BPF relays"},
|
||||
{"rx", 2, &mon_rx, "rx {r|w} <value>", "Read or Write RX relays"},
|
||||
{"adc", 3, &mon_adc, "adc (no parameters)", "Dump latest ADC readouts"}
|
||||
};
|
||||
|
||||
|
||||
|
@ -187,6 +213,9 @@ shell_t shell[NCMD]=
|
|||
/*** Commandstring parser and monitor process ***/
|
||||
/*** ---------------------------------------- ***/
|
||||
|
||||
#define ISALPHANUM(c) (((c)>' ') && ((c)<127))
|
||||
#define ISWHITESP(c) (((c)!='\0') && ((c)<=' '))
|
||||
#define ISEOL(c) ((c)=='\0')
|
||||
/*
|
||||
* Command line parser
|
||||
*/
|
||||
|
@ -197,18 +226,19 @@ void mon_parse(char* s)
|
|||
|
||||
p = s; // Set to start of string
|
||||
nargs = 0;
|
||||
while (*p!='\0') // Assume stringlength >0
|
||||
while (ISWHITESP(*p)) p++; // Skip leading whitespace
|
||||
while (!ISEOL(*p)) // Check remaining stringlength >0
|
||||
{
|
||||
while (*p==' ') p++; // Skip whitespace
|
||||
if (*p=='\0') break; // String might end in spaces
|
||||
argv[nargs++] = p; // Store first valid char loc after whitespace
|
||||
while ((*p!=' ')&&(*p!='\0')) p++; // Skip non-whitespace
|
||||
while (ISALPHANUM(*p)) p++; // Skip non-whitespace
|
||||
while (ISWHITESP(*p)) p++; // Skip separating whitespace
|
||||
}
|
||||
if (nargs==0) return; // No command or parameter
|
||||
if (nargs==0) return; // Nothing to do
|
||||
|
||||
for (i=0; i<NCMD; i++) // Lookup shell command
|
||||
if (strncmp(argv[0], shell[i].cmdstr, shell[i].cmdlen) == 0) break;
|
||||
if (i<NCMD)
|
||||
(*shell[i].cmd)();
|
||||
(*shell[i].cmd)(); // Execute if found
|
||||
else // Unknown command
|
||||
{
|
||||
for (i=0; i<NCMD; i++) // Print help if no match
|
||||
|
|
10
relay.c
10
relay.c
|
@ -30,14 +30,15 @@
|
|||
#define I2C_BPF 0x20
|
||||
|
||||
|
||||
void relay_setband(uint8_t val)
|
||||
void relay_setband(int val)
|
||||
{
|
||||
uint8_t data[2];
|
||||
int ret;
|
||||
|
||||
data[0] = val&0x1f;
|
||||
data[0] = ((uint8_t)val)&0x1f;
|
||||
if (i2c_write_blocking(i2c1, I2C_BPF, data, 1, false) < 0)
|
||||
i2c_write_blocking(i2c1, I2C_BPF, data, 1, false);
|
||||
sleep_ms(1);
|
||||
}
|
||||
|
||||
int relay_getband(void)
|
||||
|
@ -51,13 +52,14 @@ int relay_getband(void)
|
|||
return(ret);
|
||||
}
|
||||
|
||||
void relay_setattn(uint8_t val)
|
||||
void relay_setattn(int val)
|
||||
{
|
||||
uint8_t data[2];
|
||||
|
||||
data[0] = val&0x07;
|
||||
data[0] = ((uint8_t)val)&0x07;
|
||||
if (i2c_write_blocking(i2c1, I2C_RX, data, 1, false) < 0)
|
||||
i2c_write_blocking(i2c1, I2C_RX, data, 1, false);
|
||||
sleep_ms(1);
|
||||
}
|
||||
|
||||
int relay_getattn(void)
|
||||
|
|
12
relay.h
12
relay.h
|
@ -9,22 +9,24 @@
|
|||
* See relay.c for more information
|
||||
*/
|
||||
|
||||
/* relay_setband() values */
|
||||
#define REL_LPF2 0x01
|
||||
#define REL_BPF6 0x02
|
||||
#define REL_BPF12 0x04
|
||||
#define REL_BPF24 0x08
|
||||
#define REL_BPF40 0x10
|
||||
|
||||
/* relay_setattn() values */
|
||||
#define REL_ATT_30 0x03
|
||||
#define REL_ATT_20 0x01
|
||||
#define REL_ATT_10 0x02
|
||||
#define REL_ATT_00 0x00
|
||||
#define REL_PRE_10 0x04
|
||||
|
||||
extern void relay_setband(uint8_t val);
|
||||
extern void relay_setattn(uint8_t val);
|
||||
extern int relay_getband(void);
|
||||
extern int relay_getattn(void);
|
||||
extern void relay_init(void);
|
||||
void relay_setband(int val);
|
||||
void relay_setattn(int val);
|
||||
int relay_getband(void);
|
||||
int relay_getattn(void);
|
||||
void relay_init(void);
|
||||
|
||||
#endif
|
||||
|
|
178
si5351.c
178
si5351.c
|
@ -3,13 +3,26 @@
|
|||
*
|
||||
* Created: Jan 2020
|
||||
* Author: Arjan
|
||||
*
|
||||
* Driver for the SI5351A VCO
|
||||
*
|
||||
|
||||
Si5351 principle:
|
||||
=================
|
||||
Si5351 principle of operation:
|
||||
==============================
|
||||
Crystal frequency Fxtal (usually 25MHz) is multiplied in a PLL by MSN to obtain Fvco.
|
||||
PLL A and B have independent MSN values, the Fout channel i can be derived from either.
|
||||
Fvco is between 600MHz and 900MHz, but the spec in reality is more relaxed.
|
||||
Fvco is divided by MSi and Ri to obtain the output frequency Fout.
|
||||
MSi and Ri are selected to be in the ballpark of desired frequency range.
|
||||
MSN is then used for tuning.
|
||||
Only certain values of MSi and Ri are allowed when quadrature output is needed.
|
||||
|
||||
+-------+ +-------+ +------+
|
||||
- Fxtal --> | * MSN | -- Fvco --> | / MSi | --> | / Ri | -- Fout -->
|
||||
+-------+ +-------+ +------+
|
||||
|
||||
Details:
|
||||
========
|
||||
---Derivation of Fout---
|
||||
MSN determines: Fvco = Fxtal * (MSN) , where MSN = a + b/c
|
||||
MSi and Ri determine: Fout = Fvco / (Ri*MSi) , where MSi = a + b/c (different a, b and c)
|
||||
|
@ -59,7 +72,6 @@ Quadrature Phase offsets (i.e. delay):
|
|||
- Set INV bit (reg 17) to add 180 deg.
|
||||
NOTE: Phase offsets only work when Ri = 1, this means minimum Fout is 4.762MHz at Fvco = 600MHz. Additional flip/flop dividers are needed to get 80m band frequencies, or Fvco must be tuned below spec.
|
||||
|
||||
|
||||
|
||||
Control Si5351 (see AN619):
|
||||
===========================
|
||||
|
@ -117,7 +129,7 @@ Control Si5351 (see AN619):
|
|||
====
|
||||
183 | XTAL_CL | Reserved |
|
||||
====
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
@ -128,7 +140,7 @@ Control Si5351 (see AN619):
|
|||
#include "hardware/clocks.h"
|
||||
#include "si5351.h"
|
||||
|
||||
#define I2C_VFO 0x60 // I2C address
|
||||
#define I2C_VFO 0x60 // I2C address
|
||||
|
||||
// SI5351 register address definitions
|
||||
#define SI_CLK_OE 3
|
||||
|
@ -148,34 +160,36 @@ Control Si5351 (see AN619):
|
|||
#define SI_XTAL_LOAD 183
|
||||
|
||||
// CLK_OE register 3 values
|
||||
#define SI_CLK0_ENABLE 0b00000001 // Enable clock 0 output
|
||||
#define SI_CLK1_ENABLE 0b00000010 // Enable clock 1 output
|
||||
#define SI_CLK2_ENABLE 0b00000100 // Enable clock 2 output
|
||||
#define SI_CLK0_ENABLE 0b00000001 // Enable clock 0 output
|
||||
#define SI_CLK1_ENABLE 0b00000010 // Enable clock 1 output
|
||||
#define SI_CLK2_ENABLE 0b00000100 // Enable clock 2 output
|
||||
|
||||
// CLKi_CTL register 16, 17, 18 values
|
||||
// Normally 0x4f for clk 0 and 1, 0x6f for clk 2
|
||||
#define SI_CLK_INT 0b01000000 // Set integer mode
|
||||
#define SI_CLK_PLL 0b00100000 // Select PLL B as MS source (default 0 = PLL A)
|
||||
#define SI_CLK_INV 0b00010000 // Invert output (i.e. phase + 180deg)
|
||||
#define SI_CLK_SRC 0b00001100 // Select output source: 11=MS, 00=XTAL direct
|
||||
#define SI_CLK_DRV 0b00000011 // Select output drive, increasingly: 2-4-6-8 mA (best risetime, use max = 11)
|
||||
#define SI_CLK_INT 0b01000000 // Set integer mode
|
||||
#define SI_CLK_PLL 0b00100000 // Select PLL B as MS source (default 0 = PLL A)
|
||||
#define SI_CLK_INV 0b00010000 // Invert output (i.e. phase + 180deg)
|
||||
#define SI_CLK_SRC 0b00001100 // Select output source: 11=MS, 00=XTAL direct
|
||||
#define SI_CLK_DRV 0b00000011 // Select output drive, increasingly: 2-4-6-8 mA (best risetime, use max = 11)
|
||||
|
||||
// PLL_RESET register 177 values
|
||||
#define SI_PLLB_RST 0b10000000 // Reset PLL B
|
||||
#define SI_PLLA_RST 0b00100000 // Reset PLL A
|
||||
#define SI_PLLB_RST 0b10000000 // Reset PLL B
|
||||
#define SI_PLLA_RST 0b00100000 // Reset PLL A
|
||||
|
||||
|
||||
|
||||
#define SI_XTAL_FREQ 25001414UL // Replace with measured crystal frequency of XTAL for CL = 10pF (default)
|
||||
#define SI_XTAL_FREQ 25001414UL // Replace with measured crystal frequency of XTAL for CL = 10pF (default)
|
||||
#define SI_MSN_LO ((0.6e9)/SI_XTAL_FREQ)
|
||||
#define SI_MSN_HI ((0.9e9)/SI_XTAL_FREQ)
|
||||
#define SI_PLL_C 1000000UL // Parameter c for PLL-A and -B setting
|
||||
#define SI_PLL_C 1000000UL // Parameter c for PLL-A and -B setting
|
||||
|
||||
|
||||
|
||||
vfo_t vfo[2]; // 0: clk0 and clk1 1: clk2
|
||||
vfo_t vfo[2]; // 0: clk0 & clk1 1: clk2
|
||||
|
||||
/* read contents of SI5351 registers, from reg to reg+len-1, output in data array */
|
||||
/*
|
||||
* read contents of SI5351 registers, from reg to reg+len-1, output in data array
|
||||
*/
|
||||
int si_getreg(uint8_t *data, uint8_t reg, uint8_t len)
|
||||
{
|
||||
int ret;
|
||||
|
@ -188,24 +202,22 @@ int si_getreg(uint8_t *data, uint8_t reg, uint8_t len)
|
|||
}
|
||||
|
||||
|
||||
// Set up MSN PLL divider for vfo[i], assuming MSN has been set in vfo[i]
|
||||
// Optimize for speed, this may be called with short intervals
|
||||
// See also SiLabs AN619 section 3.2
|
||||
/*
|
||||
* Set up MSN PLL divider for vfo[i], assuming MSN has been set in vfo[i]
|
||||
* Optimize for speed, this may be called with short intervals
|
||||
* See also SiLabs AN619 section 3.2
|
||||
*/
|
||||
void si_setmsn(uint8_t i)
|
||||
{
|
||||
uint8_t data[16]; // I2C trx buffer
|
||||
uint32_t P1, P2; // MSN parameters
|
||||
uint8_t data[16]; // I2C trx buffer
|
||||
uint32_t P1, P2; // MSN parameters
|
||||
uint32_t A;
|
||||
uint32_t B;
|
||||
|
||||
i=(i>0?1:0);
|
||||
/*
|
||||
P1 = 128*a + Floor(128*b/c) - 512
|
||||
P2 = 128*b - c*Floor(128*b/c)
|
||||
P3 = c (P3 = 1000000 for MSN tuning)
|
||||
*/
|
||||
A = (uint32_t)(floor(vfo[i].msn)); // A is integer part of MSN
|
||||
B = (uint32_t)((vfo[i].msn - (float)A) * SI_PLL_C); // B is C * fraction part of MSN (C is a constant)
|
||||
|
||||
A = (uint32_t)(floor(vfo[i].msn)); // A is integer part of MSN
|
||||
B = (uint32_t)((vfo[i].msn - (float)A) * SI_PLL_C); // B is C * fraction part of MSN (C is a constant)
|
||||
P2 = (uint32_t)(floor((float)(128 * B) / (float)SI_PLL_C));
|
||||
P1 = (uint32_t)(128 * A + P2 - 512);
|
||||
P2 = (uint32_t)(128 * B - SI_PLL_C * P2);
|
||||
|
@ -223,21 +235,19 @@ void si_setmsn(uint8_t i)
|
|||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
}
|
||||
|
||||
// Set up registers with MS and R divider for vfo[i], assuming values have been set in vfo[i]
|
||||
// In this implementation we only use integer mode, i.e. b=0 and P3=1
|
||||
// See also SiLabs AN619 section 4.1
|
||||
/*
|
||||
* Set up registers with MS and R divider for vfo[i], assuming values have been set in vfo[i]
|
||||
* In this implementation we only use integer mode, i.e. b=0 and P3=1
|
||||
* See also SiLabs AN619 section 4.1
|
||||
*/
|
||||
void si_setmsi(uint8_t i)
|
||||
{
|
||||
uint8_t data[16]; // I2C trx buffer
|
||||
uint8_t data[16]; // I2C trx buffer
|
||||
uint32_t P1;
|
||||
uint8_t R;
|
||||
|
||||
i=(i>0?1:0);
|
||||
/*
|
||||
P1 = 128*a + Floor(128*b/c) - 512
|
||||
P2 = 128*b - c*Floor(128*b/c) (P2 = 0 for MSi integer mode)
|
||||
P3 = c (P3 = 1 for MSi integer mode)
|
||||
*/
|
||||
|
||||
P1 = (uint32_t)(128*(uint32_t)floor(vfo[i].msi) - 512);
|
||||
R = vfo[i].ri;
|
||||
R = (R&0xf0) ? ((R&0xc0)?((R&0x80)?7:6):(R&0x20)?5:4) : ((R&0x0c)?((R&0x08)?3:2):(R&0x02)?1:0); // quick log2(r)
|
||||
|
@ -256,25 +266,25 @@ void si_setmsi(uint8_t i)
|
|||
// If vfo[0] also set clk 1
|
||||
if (i==0)
|
||||
{
|
||||
data[0] = SI_SYNTH_MS1; // Same data in synthesizer
|
||||
data[0] = SI_SYNTH_MS1; // Same data in synthesizer
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
if (vfo[0].phase&1) // Phase is either 90 or 270 deg?
|
||||
if (vfo[0].phase&1) // Phase is either 90 or 270 deg?
|
||||
{
|
||||
data[0] = SI_CLK1_PHOFF;
|
||||
data[1] = vfo[0].msi;
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
|
||||
}
|
||||
else // Phase is 0 or 180 deg
|
||||
else // Phase is 0 or 180 deg
|
||||
{
|
||||
data[0] = SI_CLK1_PHOFF;
|
||||
data[1] = 0;
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
|
||||
}
|
||||
if (vfo[0].phase&2) // Phase is 180 or 270 deg?
|
||||
if (vfo[0].phase&2) // Phase is 180 or 270 deg?
|
||||
{
|
||||
data[0] = SI_CLK1_CTL;
|
||||
data[1] = 0x5d; // CLK1: INT, PLLA, INV, MS, 8mA
|
||||
data[1] = 0x5d; // CLK1: INT, PLLA, INV, MS, 8mA
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 2, false);
|
||||
}
|
||||
}
|
||||
|
@ -286,10 +296,12 @@ void si_setmsi(uint8_t i)
|
|||
}
|
||||
|
||||
|
||||
// For each vfo, calculate required MSN setting, MSN = MSi*Ri*Fout/Fxtal
|
||||
// If in range, just set MSN registers
|
||||
// If not in range, recalculate MSi and Ri and also MSN
|
||||
// Set MSN, MSi and Ri registers (implicitly resets PLL)
|
||||
/*
|
||||
* For each vfo, calculate required MSN setting, MSN = MSi*Ri*Fout/Fxtal
|
||||
* If in range, just set MSN registers
|
||||
* If not in range, recalculate MSi and Ri and also MSN
|
||||
* Set MSN, MSi and Ri registers (implicitly resets PLL)
|
||||
*/
|
||||
void si_evaluate(void)
|
||||
{
|
||||
float msn;
|
||||
|
@ -327,22 +339,24 @@ void si_evaluate(void)
|
|||
}
|
||||
|
||||
|
||||
// Initialize the Si5351 VFO registers
|
||||
/*
|
||||
* Initialize the Si5351 VFO registers
|
||||
* Hard initialize Synth registers to: 7.074MHz, CLK1 90 deg ahead, PLLA for CLK 0&1, PLLB for CLK2
|
||||
| Ri=1,
|
||||
| MSi=68, P1=8192, P2=0, P3=1
|
||||
| MSN=27.2 P1=2969, P2=600000, P3=1000000
|
||||
*/
|
||||
void si_init(void)
|
||||
{
|
||||
uint8_t data[16]; // I2C trx buffer
|
||||
uint8_t data[16]; // I2C trx buffer
|
||||
|
||||
// Hard initialize Synth registers: 7.074MHz, CLK1 90 deg ahead, PLLA for CLK 0&1, PLLB for CLK2
|
||||
// Ri=1,
|
||||
// MSi=68, P1=8192, P2=0, P3=1
|
||||
// MSN=27.2 P1=2969, P2=600000, P3=1000000
|
||||
vfo[0].freq = 10000000;
|
||||
vfo[0].freq = 10000000; // Check this, should be 7074000?
|
||||
vfo[0].flag = 0;
|
||||
vfo[0].phase = 1;
|
||||
vfo[0].ri = 1;
|
||||
vfo[0].msi = 68;
|
||||
vfo[0].msn = 27.2;
|
||||
vfo[1].freq = 10000000;
|
||||
vfo[1].freq = 10000000; // Check this, should be 7074000?
|
||||
vfo[1].flag = 0;
|
||||
vfo[1].phase = 0;
|
||||
vfo[1].ri = 1;
|
||||
|
@ -351,53 +365,53 @@ void si_init(void)
|
|||
|
||||
// PLLA: MSN P1=0x00000b99, P2=0x000927c0, P3=0x000f4240
|
||||
data[0] = SI_SYNTH_PLLA;
|
||||
data[1] = 0x42; // MSNA_P3[15:8]
|
||||
data[2] = 0x40; // MSNA_P3[7:0]
|
||||
data[3] = 0x00; // 0b000000 , MSNA_P1[17:16]
|
||||
data[4] = 0x0b; // MSNA_P1[15:8]
|
||||
data[5] = 0x99; // MSNA_P1[7:0]
|
||||
data[6] = 0xf9; // MSNA_P3[19:16] , MSNA_P2[19:16]
|
||||
data[7] = 0x27; // MSNA_P2[15:8]
|
||||
data[8] = 0xc0; // MSNA_P2[7:0]
|
||||
data[1] = 0x42; // MSNA_P3[15:8]
|
||||
data[2] = 0x40; // MSNA_P3[7:0]
|
||||
data[3] = 0x00; // 0b000000 , MSNA_P1[17:16]
|
||||
data[4] = 0x0b; // MSNA_P1[15:8]
|
||||
data[5] = 0x99; // MSNA_P1[7:0]
|
||||
data[6] = 0xf9; // MSNA_P3[19:16] , MSNA_P2[19:16]
|
||||
data[7] = 0x27; // MSNA_P2[15:8]
|
||||
data[8] = 0xc0; // MSNA_P2[7:0]
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
|
||||
// PLLB: MSN P1=0x00000b99, P2=0x000927c0, P3=0x000f4240
|
||||
data[0] = SI_SYNTH_PLLB; // Same content
|
||||
data[0] = SI_SYNTH_PLLB; // Same content
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
// MS0 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1
|
||||
data[0] = SI_SYNTH_MS0;
|
||||
data[1] = 0x00; // MS0_P3[15:8]
|
||||
data[2] = 0x01; // MS0_P3[7:0]
|
||||
data[3] = 0x00; // 0b0, R0_DIV[2:0] , MS0_DIVBY4[1:0] , MS0_P1[17:16]
|
||||
data[4] = 0x20; // MS0_P1[15:8]
|
||||
data[5] = 0x00; // MS0_P1[7:0]
|
||||
data[6] = 0x00; // MS0_P3[19:16] , MS0_P2[19:16]
|
||||
data[7] = 0x00; // MS0_P2[15:8]
|
||||
data[8] = 0x00; // MS0_P2[7:0]
|
||||
data[1] = 0x00; // MS0_P3[15:8]
|
||||
data[2] = 0x01; // MS0_P3[7:0]
|
||||
data[3] = 0x00; // 0b0, R0_DIV[2:0] , MS0_DIVBY4[1:0] , MS0_P1[17:16]
|
||||
data[4] = 0x20; // MS0_P1[15:8]
|
||||
data[5] = 0x00; // MS0_P1[7:0]
|
||||
data[6] = 0x00; // MS0_P3[19:16] , MS0_P2[19:16]
|
||||
data[7] = 0x00; // MS0_P2[15:8]
|
||||
data[8] = 0x00; // MS0_P2[7:0]
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
// MS1 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1
|
||||
data[0] = SI_SYNTH_MS1; // Same content
|
||||
data[0] = SI_SYNTH_MS1; // Same content
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
// MS2 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1
|
||||
data[0] = SI_SYNTH_MS2; // Same content
|
||||
data[0] = SI_SYNTH_MS2; // Same content
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 9, false);
|
||||
|
||||
// Phase offsets for 3 clocks
|
||||
data[0] = SI_CLK0_PHOFF;
|
||||
data[1] = 0x00; // CLK0: phase 0 deg
|
||||
data[2] = 0x44; // CLK1: phase 90 deg (=MSi)
|
||||
data[3] = 0x00; // CLK2: phase 0 deg
|
||||
data[1] = 0x00; // CLK0: phase 0 deg
|
||||
data[2] = 0x44; // CLK1: phase 90 deg (=MSi)
|
||||
data[3] = 0x00; // CLK2: phase 0 deg
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 4, false);
|
||||
|
||||
// Output port settings for 3 clocks
|
||||
data[0] = SI_CLK0_CTL;
|
||||
data[1] = 0x4d; // CLK0: INT, PLLA, nonINV, MS, 4mA
|
||||
data[2] = 0x4d; // CLK1: INT, PLLA, nonINV, MS, 4mA
|
||||
data[3] = 0x6f; // CLK2: INT, PLLB, nonINV, MS, 8mA
|
||||
data[1] = 0x4d; // CLK0: INT, PLLA, nonINV, MS, 4mA
|
||||
data[2] = 0x4d; // CLK1: INT, PLLA, nonINV, MS, 4mA
|
||||
data[3] = 0x6f; // CLK2: INT, PLLB, nonINV, MS, 8mA
|
||||
i2c_write_blocking(i2c0, I2C_VFO, data, 4, false);
|
||||
|
||||
// Disable spread spectrum (startup state is undefined)
|
||||
|
|
49
uSDR.c
49
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;
|
||||
|
|
Ładowanie…
Reference in New Issue