Compare commits
37 commits
Author | SHA1 | Date | |
---|---|---|---|
2195c46dbf | |||
68c65fdb54 | |||
1fe4633601 | |||
347582cfc1 | |||
49cf85006d | |||
f866c97fcb | |||
82c1c12195 | |||
5868a1ade9 | |||
3410740b4a | |||
6b444587b6 | |||
7382677af8 | |||
71543c70da | |||
2068d3a01f | |||
0db366b602 | |||
f5fb02c190 | |||
24d6acdb53 | |||
f7d2dd3629 | |||
f294b09c98 | |||
178ba7bee0 | |||
63ca3c6439 | |||
884d84bbf9 | |||
5a9a0f7acc | |||
b52f772845 | |||
8974b812f2 | |||
3054904768 | |||
821cd9189d | |||
9997b63c7e | |||
518d6f55db | |||
4e6a29abbc | |||
09b4c2c169 | |||
34a4df9b1a | |||
dd66571945 | |||
bc96b5a9ee | |||
0e1fc91279 | |||
eaad670abb | |||
574c474c6e | |||
38b7ec34f6 |
8 changed files with 483 additions and 311 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
/build/
|
/build/
|
||||||
/grc/*.py
|
/grc/*.py
|
||||||
|
/src/.clangd
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
|
|
||||||
Using RP2040 / Raspberry Pi Pico as a software-defined radio receiver.
|
Using RP2040 / Raspberry Pi Pico as a software-defined radio receiver.
|
||||||
|
|
||||||
See the [blog post](https://blog.porucha.net/2024/pico-sdr/) for more information. Older code the article is mostly referring to can be found in the branch `old`.
|
See the [blog post](https://blog.porucha.net/2024/pico-sdr/) for more information. Older code the article is mostly referring to can be found in the branch `old` and a more up-to-date approach in the branch `master`.
|
||||||
|
|
||||||
|
This branch contains code to use RP2040 and some passives as a superheterodyne receiver. It is still very much work in progress.
|
||||||
|
|
||||||
## Circuit
|
## Circuit
|
||||||
|
|
||||||

|
Please refer to [the simulation](https://www.falstad.com/circuit/circuitjs.html?ctz=CQAgjCBMB0CsCmBaMAGEAOW0AsLboHYA2SWSIsWMIkAtFaFFCWNBZMAKEoE5wV06ENiE8ekYULZpUTOfM4BzfoMnhU2NfU4AlED2ybERTeh41jmmfpHyZ0IrHAOn9OJ0goC4AMyQhiOa+RGiBNE4AkgBi3ESihiBh-LiJQehgBlAgPtA+INp6BkZ+NprUwiBOPnZy2bn5DQywnABOtGSJGRIEHUkydpwA7smhXSOpNCicAMaVIRMgjhZBMkjeyA7YYjzoXiGwPuJGYC6cADaLsMs0PRJ9+Yzy288vz4k5B8REPGAi2HSQHzoNb5GbtO5BW6NcBIIQnEzbIgmMCQOiwWDEZyOJTghpQ8raYZQpLoEQLKbDUkBIJFcmtbJxBbYEzkypTTwQVEQmiQSBGFY2ELySS1BhPV4SsTgcRyTgRcbIcSlTpK6w5HxDOahIJXNBkqYANygBG5DOpk3AGHQnmF9Ea7jaGm1NDAYB81warBxbv8DR9cItU1m-oWIbtYFhiTFzDi4lYkDA1oIQJQVnO4EBppD92jcklkqjj2YifQPhN5BIBgIgm8FOViokVJVEjrTYbuPbUwARhgiOs+TJsK79LXOAAPXtORCkPgGPjTq4VPkgHQABU8uHHtBNiU8eWtRhtFXKAHFVxEAPIAHQAzs0J8R56gaNaIMgUHkyjQz5fb+hNU6iQsoBJR1mIEhkrqWhbgQBAQIcGCAsIEHgN+57XjePCalBSTMvykycD2GRvpQEDMnkiBAqCE7ugE1A0ORnREJ+UCaAAggAdgALvAHEcQAhmCrruqGmasrmTAQPCQJXL8sDiD4PhuhiWLNAA9tklQon6PiwNgPhWHAZZIjsyImO6PCuEWEBoBIeQakAA).
|
||||||
|
|
||||||
## Software
|
## Software
|
||||||
|
|
||||||
|
@ -32,4 +34,4 @@ See the [blog post](https://blog.porucha.net/2024/pico-sdr/) for more informatio
|
||||||
|
|
||||||
4. Open `grc/PicoSDR-WBFM.grc` in GNU Radio Companion, adjust carrier frequency to match your favorite FM radio station and press `F6`.
|
4. Open `grc/PicoSDR-WBFM.grc` in GNU Radio Companion, adjust carrier frequency to match your favorite FM radio station and press `F6`.
|
||||||
|
|
||||||
5. Alternatively [gqrx](https://www.gqrx.dk/) works fine with `rtl_tcp` input mode. Maximum sample rate seem to be 400 ksps, above that the samples are dropped. Make sure to adjust LNA gain to +30 dB. It's not accurate, but it does control bias strength which in turn does affect analog gain.
|
5. Alternatively [gqrx](https://www.gqrx.dk/) works fine with `rtl_tcp` input mode. Maximum sample rate seem to be 400 ksps, above that the samples are dropped. Make sure to set LNA gain to 0, gain control is digital and does not provide any benefits unless you lower your sampling rate significantly.
|
||||||
|
|
|
@ -49,7 +49,7 @@ blocks:
|
||||||
id: variable
|
id: variable
|
||||||
parameters:
|
parameters:
|
||||||
comment: ''
|
comment: ''
|
||||||
value: '200_000'
|
value: '192_000'
|
||||||
states:
|
states:
|
||||||
bus_sink: false
|
bus_sink: false
|
||||||
bus_source: false
|
bus_source: false
|
||||||
|
@ -78,7 +78,7 @@ blocks:
|
||||||
parameters:
|
parameters:
|
||||||
affinity: ''
|
affinity: ''
|
||||||
alias: ''
|
alias: ''
|
||||||
audio_decimation: '8'
|
audio_decimation: '4'
|
||||||
comment: ''
|
comment: ''
|
||||||
deemph_tau: 75e-6
|
deemph_tau: 75e-6
|
||||||
maxoutbuf: '0'
|
maxoutbuf: '0'
|
||||||
|
@ -100,7 +100,7 @@ blocks:
|
||||||
device_name: ''
|
device_name: ''
|
||||||
num_inputs: '2'
|
num_inputs: '2'
|
||||||
ok_to_block: 'True'
|
ok_to_block: 'True'
|
||||||
samp_rate: samp_rate // 8
|
samp_rate: samp_rate // 4
|
||||||
states:
|
states:
|
||||||
bus_sink: false
|
bus_sink: false
|
||||||
bus_source: false
|
bus_source: false
|
||||||
|
@ -150,15 +150,15 @@ blocks:
|
||||||
alias: ''
|
alias: ''
|
||||||
beta: '6.76'
|
beta: '6.76'
|
||||||
comment: ''
|
comment: ''
|
||||||
cutoff_freq: samp_rate / 16
|
cutoff_freq: samp_rate / 8
|
||||||
decim: '8'
|
decim: '4'
|
||||||
gain: '1'
|
gain: '1'
|
||||||
interp: '1'
|
interp: '1'
|
||||||
maxoutbuf: '0'
|
maxoutbuf: '0'
|
||||||
minoutbuf: '0'
|
minoutbuf: '0'
|
||||||
samp_rate: samp_rate
|
samp_rate: samp_rate
|
||||||
type: fir_filter_fff
|
type: fir_filter_fff
|
||||||
width: samp_rate / 16
|
width: samp_rate / 8
|
||||||
win: window.WIN_HAMMING
|
win: window.WIN_HAMMING
|
||||||
states:
|
states:
|
||||||
bus_sink: false
|
bus_sink: false
|
||||||
|
@ -374,7 +374,7 @@ blocks:
|
||||||
freq7: 100e6
|
freq7: 100e6
|
||||||
freq8: 100e6
|
freq8: 100e6
|
||||||
freq9: 100e6
|
freq9: 100e6
|
||||||
gain0: '0'
|
gain0: '30'
|
||||||
gain1: '10'
|
gain1: '10'
|
||||||
gain10: '10'
|
gain10: '10'
|
||||||
gain11: '10'
|
gain11: '10'
|
||||||
|
@ -677,7 +677,7 @@ blocks:
|
||||||
name: '"FM Demodulation"'
|
name: '"FM Demodulation"'
|
||||||
nconnections: '1'
|
nconnections: '1'
|
||||||
size: '512'
|
size: '512'
|
||||||
srate: samp_rate // 8
|
srate: samp_rate // (2 ** 3)
|
||||||
stemplot: 'False'
|
stemplot: 'False'
|
||||||
style1: '1'
|
style1: '1'
|
||||||
style10: '1'
|
style10: '1'
|
||||||
|
@ -856,4 +856,4 @@ connections:
|
||||||
|
|
||||||
metadata:
|
metadata:
|
||||||
file_format: 1
|
file_format: 1
|
||||||
grc_version: 3.10.11.0
|
grc_version: 3.10.9.2
|
||||||
|
|
|
@ -401,7 +401,7 @@ blocks:
|
||||||
freq7: 100e6
|
freq7: 100e6
|
||||||
freq8: 100e6
|
freq8: 100e6
|
||||||
freq9: 100e6
|
freq9: 100e6
|
||||||
gain0: '0'
|
gain0: '30'
|
||||||
gain1: '10'
|
gain1: '10'
|
||||||
gain10: '10'
|
gain10: '10'
|
||||||
gain11: '10'
|
gain11: '10'
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
CompileFlags:
|
|
||||||
CompilationDatabase: ../build
|
|
|
@ -1,5 +1,4 @@
|
||||||
cmake_minimum_required(VERSION 3.21)
|
cmake_minimum_required(VERSION 3.21)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
||||||
|
|
||||||
include($ENV{PICO_SDK_PATH}/pico_sdk_init.cmake)
|
include($ENV{PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||||
|
|
||||||
|
@ -36,16 +35,5 @@ target_compile_definitions(pico_sdr PUBLIC PICO_STDIO_DEFAULT_CRLF=0)
|
||||||
|
|
||||||
target_include_directories(pico_sdr PRIVATE include)
|
target_include_directories(pico_sdr PRIVATE include)
|
||||||
|
|
||||||
target_compile_definitions(pico_sdr PRIVATE
|
|
||||||
PLL_SYS_REFDIV=1
|
|
||||||
PLL_SYS_VCO_FREQ_HZ=2400000000
|
|
||||||
PLL_SYS_POSTDIV1=5
|
|
||||||
PLL_SYS_POSTDIV2=2
|
|
||||||
SYS_CLK_HZ=240000000
|
|
||||||
|
|
||||||
SYS_CLK_VREG_VOLTAGE_AUTO_ADJUST=1
|
|
||||||
SYS_CLK_VREG_VOLTAGE_MIN=VREG_VOLTAGE_1_30
|
|
||||||
)
|
|
||||||
|
|
||||||
#pico_set_binary_type(pico_sdr no_flash)
|
#pico_set_binary_type(pico_sdr no_flash)
|
||||||
pico_set_binary_type(pico_sdr copy_to_ram)
|
pico_set_binary_type(pico_sdr copy_to_ram)
|
||||||
|
|
701
src/main.c
701
src/main.c
|
@ -1,4 +1,3 @@
|
||||||
#include <math.h>
|
|
||||||
#include <pico/stdlib.h>
|
#include <pico/stdlib.h>
|
||||||
#include <pico/stdio_usb.h>
|
#include <pico/stdio_usb.h>
|
||||||
#include <pico/multicore.h>
|
#include <pico/multicore.h>
|
||||||
|
@ -17,61 +16,99 @@
|
||||||
#include <hardware/regs/clocks.h>
|
#include <hardware/regs/clocks.h>
|
||||||
#include <hardware/structs/bus_ctrl.h>
|
#include <hardware/structs/bus_ctrl.h>
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#define RX_PIN 10
|
#define VREG_VOLTAGE VREG_VOLTAGE_1_20
|
||||||
#define FB_PIN 6
|
#define CLK_SYS_HZ (300 * MHZ)
|
||||||
|
|
||||||
|
#define INIT_SAMPLE_RATE 200000
|
||||||
|
#define INIT_FREQ 94600000
|
||||||
|
#define INIT_GAIN 127
|
||||||
|
|
||||||
|
#define LO_PIN 9
|
||||||
|
#define RX_PIN 10
|
||||||
|
#define FB_PIN 11
|
||||||
#define PSU_PIN 23
|
#define PSU_PIN 23
|
||||||
|
|
||||||
#define PIO pio0
|
#define PIO pio1
|
||||||
#define SM_RX 0
|
#define SM_LO 0
|
||||||
#define SM_BIAS 1
|
#define SM_FB 1
|
||||||
#define SM_COS 2
|
#define SM_RX 2
|
||||||
#define SM_SIN 3
|
#define SM_AD 3
|
||||||
|
|
||||||
#define IQ_SAMPLES 32
|
#define IQ_SAMPLES 32
|
||||||
#define IQ_BLOCK_LEN (2 * IQ_SAMPLES)
|
#define IQ_BLOCK_LEN (2 * IQ_SAMPLES)
|
||||||
#define IQ_QUEUE_LEN 16
|
#define IQ_QUEUE_LEN 8
|
||||||
|
|
||||||
#define XOR_ADDR 0x1000
|
/*
|
||||||
#define LO_BITS_DEPTH 15
|
* NOTE: Must have 256 phases with 256 bytes each.
|
||||||
#define LO_WORDS (1 << (LO_BITS_DEPTH - 2))
|
* Otherwise the DMA 1-byte write trick wouldn't work.
|
||||||
#define LO_COS_ACCUMULATOR (&PIO->sm[SM_COS].pinctrl)
|
*/
|
||||||
#define LO_SIN_ACCUMULATOR (&PIO->sm[SM_SIN].pinctrl)
|
|
||||||
#define SIN_PHASE (0u)
|
|
||||||
#define COS_PHASE (3u << 30)
|
|
||||||
|
|
||||||
static uint32_t lo_cos[LO_WORDS] __aligned(1 << LO_BITS_DEPTH);
|
#define LO_NUM_PHASES 256
|
||||||
static uint32_t lo_sin[LO_WORDS] __aligned(1 << LO_BITS_DEPTH);
|
#define LO_PHASE_BITS 8
|
||||||
|
#define LO_PHASE_WORDS (1 << (LO_PHASE_BITS - 2))
|
||||||
|
#define STEP_BASE ((UINT_MAX + 1.0) / CLK_SYS_HZ)
|
||||||
|
|
||||||
#define INIT_SAMPLE_RATE 100000
|
static uint32_t nco_step = (uint32_t)(STEP_BASE * INIT_FREQ) * 32 * LO_PHASE_WORDS;
|
||||||
#define INIT_FREQ 94600000
|
static uint32_t nco_null = 0;
|
||||||
|
|
||||||
static int frequency = INIT_FREQ;
|
static uint32_t lo_phase[LO_NUM_PHASES][LO_PHASE_WORDS]
|
||||||
|
__attribute__((__aligned__(LO_NUM_PHASES * 4 * LO_PHASE_WORDS)));
|
||||||
|
|
||||||
|
static uint32_t nco_addr = (uint32_t)lo_phase;
|
||||||
|
|
||||||
|
#define DECIMATE 16
|
||||||
|
#define RX_BITS_DEPTH 8
|
||||||
|
#define RX_WORDS (1 << (RX_BITS_DEPTH - 2))
|
||||||
|
#define RX_STRIDE (2 * DECIMATE)
|
||||||
|
|
||||||
|
static_assert(RX_WORDS >= 2 * RX_STRIDE, "RX_WORDS >= 2 * RX_STRIDE");
|
||||||
|
|
||||||
|
static uint32_t rx_cos[RX_WORDS] __attribute__((__aligned__(1 << RX_BITS_DEPTH)));
|
||||||
|
|
||||||
|
#define NUM_GAINS 29
|
||||||
|
static int gains[NUM_GAINS] = { 0, 9, 14, 27, 37, 77, 87, 125, 144, 157,
|
||||||
|
166, 197, 207, 229, 254, 280, 297, 328, 338, 364,
|
||||||
|
372, 386, 402, 421, 434, 439, 445, 480, 496 };
|
||||||
static int sample_rate = INIT_SAMPLE_RATE;
|
static int sample_rate = INIT_SAMPLE_RATE;
|
||||||
|
static int max_amplitude = CLK_SYS_HZ / INIT_SAMPLE_RATE / 2;
|
||||||
|
static int max_amplitude_mul = 65536 / (CLK_SYS_HZ / INIT_SAMPLE_RATE / 2);
|
||||||
|
static int gain = INIT_GAIN;
|
||||||
|
static int frequency = INIT_FREQ;
|
||||||
|
|
||||||
#define ATTN_BITS 16
|
static int dma_ch_rx1 = -1;
|
||||||
#define BASE_GAIN (1 << 23)
|
static int dma_ch_rx2 = -1;
|
||||||
#define DC_OFFSET (int)(127.4 * (1 << ATTN_BITS))
|
|
||||||
|
|
||||||
static int gain = BASE_GAIN / (SYS_CLK_HZ / INIT_SAMPLE_RATE);
|
static int dma_ch_nco1 = -1;
|
||||||
|
static int dma_ch_nco2 = -1;
|
||||||
|
static int dma_ch_nco3 = -1;
|
||||||
|
static int dma_ch_mix = -1;
|
||||||
|
|
||||||
|
static int dma_ch_samp_cos = -1;
|
||||||
|
|
||||||
|
static int dma_t_samp = -1;
|
||||||
|
|
||||||
|
static int dma_ch_in_cos = -1;
|
||||||
|
|
||||||
static queue_t iq_queue;
|
static queue_t iq_queue;
|
||||||
static uint8_t iq_queue_buffer[IQ_QUEUE_LEN][IQ_BLOCK_LEN];
|
static uint8_t iq_queue_buffer[IQ_QUEUE_LEN][IQ_BLOCK_LEN];
|
||||||
static size_t iq_queue_pos = 0;
|
static size_t iq_queue_pos = 0;
|
||||||
|
|
||||||
static uint32_t xorshift_seed;
|
static uint32_t rnd = 0;
|
||||||
|
|
||||||
static inline __unused uint32_t xorshift(void)
|
static int origin_lo = -1;
|
||||||
|
static int origin_rx = -1;
|
||||||
|
static int origin_fb = -1;
|
||||||
|
static int origin_ad = 0;
|
||||||
|
|
||||||
|
inline static __unused uint32_t rnd_next()
|
||||||
{
|
{
|
||||||
uint32_t x = xorshift_seed;
|
rnd = rnd * 0x41c64e6d + 12345;
|
||||||
x ^= x << 13;
|
return rnd;
|
||||||
x ^= x >> 17;
|
|
||||||
x ^= x << 5;
|
|
||||||
xorshift_seed = x;
|
|
||||||
return x;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dma_channel_clear_chain_to(int ch)
|
static void dma_channel_clear_chain_to(int ch)
|
||||||
|
@ -82,22 +119,78 @@ static void dma_channel_clear_chain_to(int ch)
|
||||||
dma_hw->ch[ch].al1_ctrl = ctrl;
|
dma_hw->ch[ch].al1_ctrl = ctrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* rx -> cp -> cos -> sin -> pio_cos -> pio_sin -> rx ... */
|
static void init_lo()
|
||||||
static int dma_ch_rx = -1;
|
{
|
||||||
static int dma_ch_cp = -1;
|
gpio_disable_pulls(LO_PIN);
|
||||||
static int dma_ch_cos = -1;
|
pio_gpio_init(PIO, LO_PIN);
|
||||||
static int dma_ch_sin = -1;
|
|
||||||
static int dma_ch_pio_cos = -1;
|
|
||||||
static int dma_ch_pio_sin = -1;
|
|
||||||
|
|
||||||
static int dma_ch_samp_cos = -1;
|
gpio_set_drive_strength(LO_PIN, GPIO_DRIVE_STRENGTH_12MA);
|
||||||
static int dma_ch_samp_sin = -1;
|
gpio_set_slew_rate(LO_PIN, GPIO_SLEW_RATE_FAST);
|
||||||
|
|
||||||
static int dma_t_samp = -1;
|
const uint16_t insn[] = {
|
||||||
|
pio_encode_out(pio_pindirs, 1),
|
||||||
|
};
|
||||||
|
|
||||||
static int origin_rx = -1;
|
pio_program_t prog = {
|
||||||
static int origin_bias = -1;
|
.instructions = insn,
|
||||||
static int origin_adder = 0;
|
.length = sizeof(insn) / sizeof(*insn),
|
||||||
|
.origin = origin_lo,
|
||||||
|
};
|
||||||
|
|
||||||
|
pio_sm_restart(PIO, SM_LO);
|
||||||
|
pio_sm_clear_fifos(PIO, SM_LO);
|
||||||
|
|
||||||
|
if (pio_can_add_program(PIO, &prog))
|
||||||
|
origin_lo = pio_add_program(PIO, &prog);
|
||||||
|
|
||||||
|
pio_sm_config pc = pio_get_default_sm_config();
|
||||||
|
sm_config_set_out_pins(&pc, LO_PIN, 1);
|
||||||
|
sm_config_set_set_pins(&pc, LO_PIN, 1);
|
||||||
|
sm_config_set_wrap(&pc, origin_lo, origin_lo + prog.length - 1);
|
||||||
|
sm_config_set_clkdiv_int_frac(&pc, 1, 0);
|
||||||
|
sm_config_set_fifo_join(&pc, PIO_FIFO_JOIN_TX);
|
||||||
|
sm_config_set_out_shift(&pc, false, true, 32);
|
||||||
|
pio_sm_init(PIO, SM_LO, origin_lo, &pc);
|
||||||
|
|
||||||
|
pio_sm_set_consecutive_pindirs(PIO, SM_LO, LO_PIN, 1, GPIO_IN);
|
||||||
|
pio_sm_exec_wait_blocking(PIO, SM_LO, pio_encode_set(pio_pins, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_fb()
|
||||||
|
{
|
||||||
|
gpio_disable_pulls(FB_PIN);
|
||||||
|
pio_gpio_init(PIO, FB_PIN);
|
||||||
|
|
||||||
|
gpio_set_input_hysteresis_enabled(RX_PIN, false);
|
||||||
|
gpio_set_drive_strength(FB_PIN, GPIO_DRIVE_STRENGTH_2MA);
|
||||||
|
gpio_set_slew_rate(FB_PIN, GPIO_SLEW_RATE_SLOW);
|
||||||
|
|
||||||
|
const uint16_t insn[] = {
|
||||||
|
pio_encode_mov_not(pio_pins, pio_pins) | pio_encode_delay(3),
|
||||||
|
};
|
||||||
|
|
||||||
|
pio_program_t prog = {
|
||||||
|
.instructions = insn,
|
||||||
|
.length = sizeof(insn) / sizeof(*insn),
|
||||||
|
.origin = origin_fb,
|
||||||
|
};
|
||||||
|
|
||||||
|
pio_sm_restart(PIO, SM_FB);
|
||||||
|
pio_sm_clear_fifos(PIO, SM_FB);
|
||||||
|
|
||||||
|
if (pio_can_add_program(PIO, &prog))
|
||||||
|
origin_fb = pio_add_program(PIO, &prog);
|
||||||
|
|
||||||
|
pio_sm_config pc = pio_get_default_sm_config();
|
||||||
|
sm_config_set_in_pins(&pc, RX_PIN);
|
||||||
|
sm_config_set_out_pins(&pc, FB_PIN, 1);
|
||||||
|
sm_config_set_set_pins(&pc, FB_PIN, 1);
|
||||||
|
sm_config_set_wrap(&pc, origin_fb, origin_fb + prog.length - 1);
|
||||||
|
sm_config_set_clkdiv_int_frac(&pc, 1, 0);
|
||||||
|
pio_sm_init(PIO, SM_FB, origin_fb, &pc);
|
||||||
|
|
||||||
|
pio_sm_set_consecutive_pindirs(PIO, SM_FB, FB_PIN, 1, GPIO_OUT);
|
||||||
|
}
|
||||||
|
|
||||||
static void init_rx()
|
static void init_rx()
|
||||||
{
|
{
|
||||||
|
@ -105,7 +198,7 @@ static void init_rx()
|
||||||
pio_gpio_init(PIO, RX_PIN);
|
pio_gpio_init(PIO, RX_PIN);
|
||||||
|
|
||||||
const uint16_t insn[] = {
|
const uint16_t insn[] = {
|
||||||
pio_encode_in(pio_pins, 1),
|
pio_encode_in(pio_pins, 1) | pio_encode_delay(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
pio_program_t prog = {
|
pio_program_t prog = {
|
||||||
|
@ -114,6 +207,9 @@ static void init_rx()
|
||||||
.origin = origin_rx,
|
.origin = origin_rx,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pio_sm_restart(PIO, SM_RX);
|
||||||
|
pio_sm_clear_fifos(PIO, SM_RX);
|
||||||
|
|
||||||
if (pio_can_add_program(PIO, &prog))
|
if (pio_can_add_program(PIO, &prog))
|
||||||
origin_rx = pio_add_program(PIO, &prog);
|
origin_rx = pio_add_program(PIO, &prog);
|
||||||
|
|
||||||
|
@ -128,54 +224,11 @@ static void init_rx()
|
||||||
pio_sm_set_consecutive_pindirs(PIO, SM_RX, RX_PIN, 1, GPIO_IN);
|
pio_sm_set_consecutive_pindirs(PIO, SM_RX, RX_PIN, 1, GPIO_IN);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void init_bias()
|
|
||||||
{
|
|
||||||
gpio_disable_pulls(RX_PIN);
|
|
||||||
gpio_disable_pulls(FB_PIN);
|
|
||||||
|
|
||||||
pio_gpio_init(PIO, FB_PIN);
|
|
||||||
|
|
||||||
gpio_set_input_hysteresis_enabled(RX_PIN, false);
|
|
||||||
gpio_set_drive_strength(FB_PIN, GPIO_DRIVE_STRENGTH_2MA);
|
|
||||||
gpio_set_slew_rate(FB_PIN, GPIO_SLEW_RATE_SLOW);
|
|
||||||
|
|
||||||
const uint16_t insn[] = {
|
|
||||||
pio_encode_mov_not(pio_pins, pio_pins) | pio_encode_sideset(1, 1) |
|
|
||||||
pio_encode_delay(0),
|
|
||||||
};
|
|
||||||
|
|
||||||
pio_program_t prog = {
|
|
||||||
.instructions = insn,
|
|
||||||
.length = sizeof(insn) / sizeof(*insn),
|
|
||||||
.origin = origin_bias,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (pio_can_add_program(PIO, &prog))
|
|
||||||
origin_bias = pio_add_program(PIO, &prog);
|
|
||||||
|
|
||||||
pio_sm_config pc = pio_get_default_sm_config();
|
|
||||||
sm_config_set_in_shift(&pc, false, false, 32);
|
|
||||||
sm_config_set_sideset(&pc, 1, false, true);
|
|
||||||
sm_config_set_sideset_pins(&pc, FB_PIN);
|
|
||||||
sm_config_set_in_pins(&pc, RX_PIN);
|
|
||||||
sm_config_set_out_pins(&pc, FB_PIN, 1);
|
|
||||||
sm_config_set_set_pins(&pc, RX_PIN, 1);
|
|
||||||
|
|
||||||
sm_config_set_wrap(&pc, origin_bias, origin_bias + prog.length - 1);
|
|
||||||
|
|
||||||
sm_config_set_clkdiv_int_frac(&pc, 1, 0);
|
|
||||||
pio_sm_init(PIO, SM_BIAS, origin_bias, &pc);
|
|
||||||
|
|
||||||
pio_sm_exec_wait_blocking(PIO, SM_BIAS, pio_encode_set(pio_y, 31));
|
|
||||||
pio_sm_set_consecutive_pindirs(PIO, SM_BIAS, FB_PIN, 1, GPIO_OUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const uint32_t samp_insn = 16;
|
static const uint32_t samp_insn = 16;
|
||||||
|
|
||||||
static void init_adder()
|
static void init_ad()
|
||||||
{
|
{
|
||||||
const uint16_t insn[] = {
|
const uint16_t insn[] = {
|
||||||
/* y has weight of 2, x has weight of 1 */
|
|
||||||
pio_encode_out(pio_pc, 4), // 0000 +0
|
pio_encode_out(pio_pc, 4), // 0000 +0
|
||||||
pio_encode_jmp_x_dec(0), // 0001 +1
|
pio_encode_jmp_x_dec(0), // 0001 +1
|
||||||
pio_encode_jmp_x_dec(0), // 0010 +1
|
pio_encode_jmp_x_dec(0), // 0010 +1
|
||||||
|
@ -197,28 +250,33 @@ static void init_adder()
|
||||||
* Should wrap here.
|
* Should wrap here.
|
||||||
* Jump to this portion must be inserted from the outside.
|
* Jump to this portion must be inserted from the outside.
|
||||||
*/
|
*/
|
||||||
pio_encode_in(pio_x, 16),
|
pio_encode_in(pio_y, 32),
|
||||||
pio_encode_in(pio_y, 16),
|
pio_encode_in(pio_x, 32),
|
||||||
|
pio_encode_set(pio_y, 0),
|
||||||
|
pio_encode_set(pio_x, 0),
|
||||||
|
pio_encode_jmp_y_dec(21),
|
||||||
|
pio_encode_jmp_x_dec(22),
|
||||||
pio_encode_out(pio_pc, 4),
|
pio_encode_out(pio_pc, 4),
|
||||||
};
|
};
|
||||||
|
|
||||||
pio_program_t prog = {
|
pio_program_t prog = {
|
||||||
.instructions = insn,
|
.instructions = insn,
|
||||||
.length = sizeof(insn) / sizeof(*insn),
|
.length = sizeof(insn) / sizeof(*insn),
|
||||||
.origin = origin_adder,
|
.origin = origin_ad,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pio_sm_restart(PIO, SM_AD);
|
||||||
|
pio_sm_clear_fifos(PIO, SM_AD);
|
||||||
|
|
||||||
if (pio_can_add_program(PIO, &prog))
|
if (pio_can_add_program(PIO, &prog))
|
||||||
origin_adder = pio_add_program(PIO, &prog);
|
pio_add_program(PIO, &prog);
|
||||||
|
|
||||||
pio_sm_config pc = pio_get_default_sm_config();
|
pio_sm_config pc = pio_get_default_sm_config();
|
||||||
sm_config_set_wrap(&pc, origin_adder, origin_adder + 15);
|
sm_config_set_wrap(&pc, origin_ad, origin_ad + 15);
|
||||||
sm_config_set_clkdiv_int_frac(&pc, 1, 0);
|
sm_config_set_clkdiv_int_frac(&pc, 1, 0);
|
||||||
sm_config_set_in_shift(&pc, false, true, 32);
|
sm_config_set_in_shift(&pc, false, true, 32);
|
||||||
sm_config_set_out_shift(&pc, false, true, 32);
|
sm_config_set_out_shift(&pc, false, true, 32);
|
||||||
|
pio_sm_init(PIO, SM_AD, origin_ad, &pc);
|
||||||
pio_sm_init(PIO, SM_COS, origin_adder, &pc);
|
|
||||||
pio_sm_init(PIO, SM_SIN, origin_adder, &pc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lo_generate_phase(uint32_t *buf, size_t len, uint32_t step, uint32_t phase)
|
static void lo_generate_phase(uint32_t *buf, size_t len, uint32_t step, uint32_t phase)
|
||||||
|
@ -238,111 +296,102 @@ static void lo_generate_phase(uint32_t *buf, size_t len, uint32_t step, uint32_t
|
||||||
|
|
||||||
static void rx_lo_init(double freq)
|
static void rx_lo_init(double freq)
|
||||||
{
|
{
|
||||||
double n = round(freq * (8 << LO_BITS_DEPTH) / SYS_CLK_HZ);
|
uint32_t step = STEP_BASE * freq;
|
||||||
freq = n * SYS_CLK_HZ / (8 << LO_BITS_DEPTH);
|
|
||||||
uint32_t step = freq * 4294967296.0 / SYS_CLK_HZ;
|
for (uint32_t i = 0; i < LO_NUM_PHASES; i++)
|
||||||
lo_generate_phase(lo_cos, LO_WORDS, step, COS_PHASE);
|
lo_generate_phase(lo_phase[i], LO_PHASE_WORDS, step, i << 24);
|
||||||
lo_generate_phase(lo_sin, LO_WORDS, step, SIN_PHASE);
|
|
||||||
|
nco_step = step * 32 * LO_PHASE_WORDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rf_rx_start()
|
static void rf_rx_start()
|
||||||
{
|
{
|
||||||
dma_ch_rx = dma_claim_unused_channel(true);
|
dma_ch_rx1 = dma_claim_unused_channel(true);
|
||||||
dma_ch_cp = dma_claim_unused_channel(true);
|
dma_ch_rx2 = dma_claim_unused_channel(true);
|
||||||
dma_ch_cos = dma_claim_unused_channel(true);
|
|
||||||
dma_ch_sin = dma_claim_unused_channel(true);
|
dma_ch_nco1 = dma_claim_unused_channel(true);
|
||||||
dma_ch_pio_cos = dma_claim_unused_channel(true);
|
dma_ch_nco2 = dma_claim_unused_channel(true);
|
||||||
dma_ch_pio_sin = dma_claim_unused_channel(true);
|
dma_ch_nco3 = dma_claim_unused_channel(true);
|
||||||
|
dma_ch_mix = dma_claim_unused_channel(true);
|
||||||
|
|
||||||
dma_ch_samp_cos = dma_claim_unused_channel(true);
|
dma_ch_samp_cos = dma_claim_unused_channel(true);
|
||||||
dma_ch_samp_sin = dma_claim_unused_channel(true);
|
|
||||||
|
|
||||||
dma_channel_config dma_conf;
|
dma_channel_config dma_conf;
|
||||||
|
|
||||||
/* Read received word into accumulator I. */
|
/* Copy PDM bitstream into decimator. */
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_rx);
|
dma_conf = dma_channel_get_default_config(dma_ch_rx1);
|
||||||
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
||||||
channel_config_set_read_increment(&dma_conf, false);
|
channel_config_set_read_increment(&dma_conf, false);
|
||||||
channel_config_set_write_increment(&dma_conf, false);
|
channel_config_set_write_increment(&dma_conf, false);
|
||||||
channel_config_set_dreq(&dma_conf, pio_get_dreq(PIO, SM_RX, false));
|
channel_config_set_dreq(&dma_conf, pio_get_dreq(PIO, SM_RX, GPIO_IN));
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_cp);
|
channel_config_set_chain_to(&dma_conf, dma_ch_rx2);
|
||||||
dma_channel_configure(dma_ch_rx, &dma_conf, LO_COS_ACCUMULATOR, &PIO->rxf[SM_RX], 1, false);
|
dma_channel_configure(dma_ch_rx1, &dma_conf, &PIO->txf[SM_AD], &PIO->rxf[SM_RX], UINT_MAX,
|
||||||
|
false);
|
||||||
|
|
||||||
/* Copy accumulator I to accumulator Q. */
|
dma_conf = dma_channel_get_default_config(dma_ch_rx2);
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_cp);
|
|
||||||
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
||||||
channel_config_set_read_increment(&dma_conf, false);
|
channel_config_set_read_increment(&dma_conf, false);
|
||||||
channel_config_set_write_increment(&dma_conf, false);
|
channel_config_set_write_increment(&dma_conf, false);
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_cos);
|
channel_config_set_dreq(&dma_conf, pio_get_dreq(PIO, SM_RX, GPIO_IN));
|
||||||
dma_channel_configure(dma_ch_cp, &dma_conf, LO_SIN_ACCUMULATOR, LO_COS_ACCUMULATOR, 1,
|
channel_config_set_chain_to(&dma_conf, dma_ch_rx1);
|
||||||
false);
|
dma_channel_configure(dma_ch_rx2, &dma_conf, &PIO->txf[SM_AD], &PIO->rxf[SM_RX], UINT_MAX,
|
||||||
|
false);
|
||||||
|
|
||||||
/* Read lo_cos into accumulator I with XOR. */
|
/* Step the NCO. */
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_cos);
|
dma_conf = dma_channel_get_default_config(dma_ch_nco1);
|
||||||
|
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
||||||
|
channel_config_set_read_increment(&dma_conf, false);
|
||||||
|
channel_config_set_write_increment(&dma_conf, false);
|
||||||
|
channel_config_set_chain_to(&dma_conf, dma_ch_nco2);
|
||||||
|
dma_channel_configure(dma_ch_nco1, &dma_conf, &nco_null, &nco_step, 1, false);
|
||||||
|
|
||||||
|
/* DMA above will increment the phase accumulator. */
|
||||||
|
dma_sniffer_enable(dma_ch_nco1, DMA_SNIFF_CTRL_CALC_VALUE_SUM, true);
|
||||||
|
|
||||||
|
/* Prepare the phase address. */
|
||||||
|
dma_conf = dma_channel_get_default_config(dma_ch_nco2);
|
||||||
|
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_8);
|
||||||
|
channel_config_set_read_increment(&dma_conf, false);
|
||||||
|
channel_config_set_write_increment(&dma_conf, false);
|
||||||
|
channel_config_set_chain_to(&dma_conf, dma_ch_nco3);
|
||||||
|
dma_channel_configure(dma_ch_nco2, &dma_conf, (uint8_t *)(&nco_addr) + 1,
|
||||||
|
((uint8_t *)&dma_hw->sniff_data) + 3, 1, false);
|
||||||
|
|
||||||
|
/* Trigger LO using the address. */
|
||||||
|
dma_conf = dma_channel_get_default_config(dma_ch_nco3);
|
||||||
|
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
||||||
|
channel_config_set_read_increment(&dma_conf, false);
|
||||||
|
channel_config_set_write_increment(&dma_conf, false);
|
||||||
|
dma_channel_configure(dma_ch_nco3, &dma_conf, &dma_hw->ch[dma_ch_mix].al3_read_addr_trig,
|
||||||
|
&nco_addr, 1, false);
|
||||||
|
|
||||||
|
/* Drive the LO capacitor. */
|
||||||
|
dma_conf = dma_channel_get_default_config(dma_ch_mix);
|
||||||
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
||||||
channel_config_set_read_increment(&dma_conf, true);
|
channel_config_set_read_increment(&dma_conf, true);
|
||||||
channel_config_set_write_increment(&dma_conf, false);
|
channel_config_set_write_increment(&dma_conf, false);
|
||||||
channel_config_set_ring(&dma_conf, false, LO_BITS_DEPTH);
|
channel_config_set_dreq(&dma_conf, pio_get_dreq(PIO, SM_LO, GPIO_OUT));
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_sin);
|
channel_config_set_chain_to(&dma_conf, dma_ch_nco1);
|
||||||
dma_channel_configure(dma_ch_cos, &dma_conf, LO_COS_ACCUMULATOR + XOR_ADDR / 4, lo_cos, 1,
|
dma_channel_configure(dma_ch_mix, &dma_conf, &PIO->txf[SM_LO], lo_phase, LO_PHASE_WORDS,
|
||||||
false);
|
false);
|
||||||
|
|
||||||
/* Read lo_sin into accumulator Q with XOR. */
|
/* Trigger accumulator values push. */
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_sin);
|
|
||||||
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
|
||||||
channel_config_set_read_increment(&dma_conf, true);
|
|
||||||
channel_config_set_write_increment(&dma_conf, false);
|
|
||||||
channel_config_set_ring(&dma_conf, false, LO_BITS_DEPTH);
|
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_pio_cos);
|
|
||||||
dma_channel_configure(dma_ch_sin, &dma_conf, LO_SIN_ACCUMULATOR + XOR_ADDR / 4, lo_sin, 1,
|
|
||||||
false);
|
|
||||||
|
|
||||||
/* Copy mixed I accumulator to PIO adder I. */
|
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_pio_cos);
|
|
||||||
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
|
||||||
channel_config_set_read_increment(&dma_conf, false);
|
|
||||||
channel_config_set_write_increment(&dma_conf, false);
|
|
||||||
channel_config_set_dreq(&dma_conf, pio_get_dreq(PIO, SM_COS, true));
|
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_pio_sin);
|
|
||||||
dma_channel_configure(dma_ch_pio_cos, &dma_conf, &PIO->txf[SM_COS], LO_COS_ACCUMULATOR, 1,
|
|
||||||
false);
|
|
||||||
|
|
||||||
/* Copy mixed Q accumulator to PIO adder Q. */
|
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_pio_sin);
|
|
||||||
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
|
||||||
channel_config_set_read_increment(&dma_conf, false);
|
|
||||||
channel_config_set_write_increment(&dma_conf, false);
|
|
||||||
channel_config_set_dreq(&dma_conf, pio_get_dreq(PIO, SM_SIN, true));
|
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_rx);
|
|
||||||
dma_channel_configure(dma_ch_pio_sin, &dma_conf, &PIO->txf[SM_SIN], LO_SIN_ACCUMULATOR, 1,
|
|
||||||
false);
|
|
||||||
|
|
||||||
/* Trigger I accumulator values push. */
|
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_samp_cos);
|
dma_conf = dma_channel_get_default_config(dma_ch_samp_cos);
|
||||||
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
||||||
channel_config_set_read_increment(&dma_conf, false);
|
channel_config_set_read_increment(&dma_conf, false);
|
||||||
channel_config_set_write_increment(&dma_conf, false);
|
channel_config_set_write_increment(&dma_conf, false);
|
||||||
|
channel_config_set_high_priority(&dma_conf, true);
|
||||||
channel_config_set_dreq(&dma_conf, dma_get_timer_dreq(dma_t_samp));
|
channel_config_set_dreq(&dma_conf, dma_get_timer_dreq(dma_t_samp));
|
||||||
channel_config_set_high_priority(&dma_conf, true);
|
dma_channel_configure(dma_ch_samp_cos, &dma_conf, &PIO->sm[SM_AD].instr, &samp_insn,
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_samp_sin);
|
UINT_MAX, false);
|
||||||
dma_channel_configure(dma_ch_samp_cos, &dma_conf, &PIO->sm[SM_COS].instr, &samp_insn, 1,
|
|
||||||
false);
|
|
||||||
|
|
||||||
/* Trigger Q accumulator values push. */
|
init_ad();
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_samp_sin);
|
init_lo();
|
||||||
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
init_fb();
|
||||||
channel_config_set_read_increment(&dma_conf, false);
|
|
||||||
channel_config_set_write_increment(&dma_conf, false);
|
|
||||||
channel_config_set_high_priority(&dma_conf, true);
|
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_samp_cos);
|
|
||||||
dma_channel_configure(dma_ch_samp_sin, &dma_conf, &PIO->sm[SM_SIN].instr, &samp_insn, 1,
|
|
||||||
false);
|
|
||||||
|
|
||||||
init_bias();
|
|
||||||
init_adder();
|
|
||||||
init_rx();
|
init_rx();
|
||||||
|
|
||||||
dma_channel_start(dma_ch_rx);
|
dma_channel_start(dma_ch_rx1);
|
||||||
|
dma_channel_start(dma_ch_nco1);
|
||||||
dma_channel_start(dma_ch_samp_cos);
|
dma_channel_start(dma_ch_samp_cos);
|
||||||
|
|
||||||
pio_set_sm_mask_enabled(PIO, 0x0f, true);
|
pio_set_sm_mask_enabled(PIO, 0x0f, true);
|
||||||
|
@ -352,113 +401,167 @@ static void rf_rx_stop(void)
|
||||||
{
|
{
|
||||||
pio_set_sm_mask_enabled(PIO, 0x0f, false);
|
pio_set_sm_mask_enabled(PIO, 0x0f, false);
|
||||||
|
|
||||||
pio_sm_restart(PIO, 0);
|
|
||||||
pio_sm_restart(PIO, 1);
|
|
||||||
pio_sm_restart(PIO, 2);
|
|
||||||
pio_sm_restart(PIO, 3);
|
|
||||||
|
|
||||||
pio_sm_clear_fifos(PIO, 0);
|
|
||||||
pio_sm_clear_fifos(PIO, 1);
|
|
||||||
pio_sm_clear_fifos(PIO, 2);
|
|
||||||
pio_sm_clear_fifos(PIO, 3);
|
|
||||||
|
|
||||||
sleep_us(10);
|
sleep_us(10);
|
||||||
|
|
||||||
dma_channel_clear_chain_to(dma_ch_rx);
|
dma_channel_clear_chain_to(dma_ch_rx1);
|
||||||
dma_channel_clear_chain_to(dma_ch_cp);
|
dma_channel_clear_chain_to(dma_ch_rx2);
|
||||||
dma_channel_clear_chain_to(dma_ch_cos);
|
dma_channel_clear_chain_to(dma_ch_nco1);
|
||||||
dma_channel_clear_chain_to(dma_ch_sin);
|
dma_channel_clear_chain_to(dma_ch_nco2);
|
||||||
dma_channel_clear_chain_to(dma_ch_pio_cos);
|
dma_channel_clear_chain_to(dma_ch_nco3);
|
||||||
dma_channel_clear_chain_to(dma_ch_pio_sin);
|
dma_channel_clear_chain_to(dma_ch_mix);
|
||||||
dma_channel_clear_chain_to(dma_ch_samp_cos);
|
dma_channel_clear_chain_to(dma_ch_samp_cos);
|
||||||
dma_channel_clear_chain_to(dma_ch_samp_sin);
|
|
||||||
|
|
||||||
dma_channel_abort(dma_ch_rx);
|
dma_channel_abort(dma_ch_rx1);
|
||||||
dma_channel_abort(dma_ch_cp);
|
dma_channel_abort(dma_ch_rx2);
|
||||||
dma_channel_abort(dma_ch_cos);
|
dma_channel_abort(dma_ch_nco1);
|
||||||
dma_channel_abort(dma_ch_sin);
|
dma_channel_abort(dma_ch_nco2);
|
||||||
dma_channel_abort(dma_ch_pio_cos);
|
dma_channel_abort(dma_ch_nco3);
|
||||||
dma_channel_abort(dma_ch_pio_sin);
|
dma_channel_abort(dma_ch_mix);
|
||||||
dma_channel_abort(dma_ch_samp_cos);
|
dma_channel_abort(dma_ch_samp_cos);
|
||||||
dma_channel_abort(dma_ch_samp_sin);
|
|
||||||
|
|
||||||
dma_channel_cleanup(dma_ch_rx);
|
dma_channel_cleanup(dma_ch_rx1);
|
||||||
dma_channel_cleanup(dma_ch_cp);
|
dma_channel_cleanup(dma_ch_rx2);
|
||||||
dma_channel_cleanup(dma_ch_cos);
|
dma_channel_cleanup(dma_ch_nco1);
|
||||||
dma_channel_cleanup(dma_ch_sin);
|
dma_channel_cleanup(dma_ch_nco2);
|
||||||
dma_channel_cleanup(dma_ch_pio_cos);
|
dma_channel_cleanup(dma_ch_nco3);
|
||||||
dma_channel_cleanup(dma_ch_pio_sin);
|
dma_channel_cleanup(dma_ch_mix);
|
||||||
dma_channel_cleanup(dma_ch_samp_cos);
|
dma_channel_cleanup(dma_ch_samp_cos);
|
||||||
dma_channel_cleanup(dma_ch_samp_sin);
|
|
||||||
|
|
||||||
dma_channel_unclaim(dma_ch_rx);
|
dma_channel_unclaim(dma_ch_rx1);
|
||||||
dma_channel_unclaim(dma_ch_cp);
|
dma_channel_unclaim(dma_ch_rx2);
|
||||||
dma_channel_unclaim(dma_ch_cos);
|
dma_channel_unclaim(dma_ch_nco1);
|
||||||
dma_channel_unclaim(dma_ch_sin);
|
dma_channel_unclaim(dma_ch_nco2);
|
||||||
dma_channel_unclaim(dma_ch_pio_cos);
|
dma_channel_unclaim(dma_ch_nco3);
|
||||||
dma_channel_unclaim(dma_ch_pio_sin);
|
dma_channel_unclaim(dma_ch_mix);
|
||||||
dma_channel_unclaim(dma_ch_samp_cos);
|
dma_channel_unclaim(dma_ch_samp_cos);
|
||||||
dma_channel_unclaim(dma_ch_samp_sin);
|
|
||||||
|
|
||||||
dma_ch_rx = -1;
|
dma_ch_rx1 = -1;
|
||||||
dma_ch_cp = -1;
|
dma_ch_rx2 = -1;
|
||||||
dma_ch_cos = -1;
|
dma_ch_nco1 = -1;
|
||||||
dma_ch_sin = -1;
|
dma_ch_nco2 = -1;
|
||||||
dma_ch_pio_cos = -1;
|
dma_ch_nco3 = -1;
|
||||||
dma_ch_pio_sin = -1;
|
dma_ch_mix = -1;
|
||||||
dma_ch_samp_cos = -1;
|
dma_ch_samp_cos = -1;
|
||||||
dma_ch_samp_sin = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline static void led_set(bool on)
|
struct IQ {
|
||||||
|
int I, Q;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline static const uint32_t *next_stride()
|
||||||
{
|
{
|
||||||
gpio_put(PICO_DEFAULT_LED_PIN, on);
|
static int tail = 0;
|
||||||
|
|
||||||
|
int head, delta;
|
||||||
|
|
||||||
|
loop:
|
||||||
|
head = (dma_hw->ch[dma_ch_in_cos].write_addr >> 2) & (RX_WORDS - 1);
|
||||||
|
delta = head - tail;
|
||||||
|
|
||||||
|
if (delta < 0)
|
||||||
|
delta += RX_WORDS;
|
||||||
|
|
||||||
|
if (delta < RX_STRIDE)
|
||||||
|
goto loop;
|
||||||
|
|
||||||
|
const uint32_t *stride = rx_cos + tail;
|
||||||
|
|
||||||
|
tail = (tail + RX_STRIDE) & (RX_WORDS - 1);
|
||||||
|
|
||||||
|
return stride;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline static int getI()
|
inline static int nextQ(const uint32_t **stride)
|
||||||
{
|
{
|
||||||
static uint16_t py, px;
|
int x2 = *(*stride)++;
|
||||||
|
int x1 = *(*stride)++;
|
||||||
|
|
||||||
uint32_t yx = pio_sm_get_blocking(PIO, SM_COS);
|
return x2 + x2 + x1 + max_amplitude;
|
||||||
uint16_t y = yx >> 16;
|
|
||||||
uint16_t x = yx;
|
|
||||||
|
|
||||||
uint16_t ny = py - y;
|
|
||||||
uint16_t nx = px - x;
|
|
||||||
|
|
||||||
py = y;
|
|
||||||
px = x;
|
|
||||||
|
|
||||||
int s = ((ny << 1) + nx) * gain;
|
|
||||||
|
|
||||||
static int dc;
|
|
||||||
dc += (s - dc) >> ATTN_BITS;
|
|
||||||
s -= dc;
|
|
||||||
|
|
||||||
return (s + DC_OFFSET) >> ATTN_BITS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline static int getQ()
|
inline static struct IQ next_sample()
|
||||||
{
|
{
|
||||||
static uint16_t py, px;
|
int I = 0, Q = 0;
|
||||||
|
|
||||||
uint32_t yx = pio_sm_get_blocking(PIO, SM_SIN);
|
const uint32_t *stride = next_stride();
|
||||||
uint16_t y = yx >> 16;
|
|
||||||
uint16_t x = yx;
|
|
||||||
|
|
||||||
uint16_t ny = py - y;
|
int x15 = nextQ(&stride);
|
||||||
uint16_t nx = px - x;
|
I += 93 * x15;
|
||||||
|
Q += 39 * x15;
|
||||||
|
|
||||||
py = y;
|
int x14 = nextQ(&stride);
|
||||||
px = x;
|
I += 71 * x14;
|
||||||
|
Q += 71 * x14;
|
||||||
|
|
||||||
int s = ((ny << 1) + nx) * gain;
|
int x13 = nextQ(&stride);
|
||||||
|
I += 39 * x13;
|
||||||
|
Q += 93 * x13;
|
||||||
|
|
||||||
static int dc;
|
int x12 = nextQ(&stride);
|
||||||
dc += (s - dc) >> ATTN_BITS;
|
I += 0 * x12;
|
||||||
s -= dc;
|
Q += 101 * x12;
|
||||||
|
|
||||||
return (s + DC_OFFSET) >> ATTN_BITS;
|
int x11 = nextQ(&stride);
|
||||||
|
I += -39 * x11;
|
||||||
|
Q += 93 * x11;
|
||||||
|
|
||||||
|
int x10 = nextQ(&stride);
|
||||||
|
I += -71 * x10;
|
||||||
|
Q += 71 * x10;
|
||||||
|
|
||||||
|
int x09 = nextQ(&stride);
|
||||||
|
I += -93 * x09;
|
||||||
|
Q += 39 * x09;
|
||||||
|
|
||||||
|
int x08 = nextQ(&stride);
|
||||||
|
I += -101 * x08;
|
||||||
|
Q += 0 * x08;
|
||||||
|
|
||||||
|
int x07 = nextQ(&stride);
|
||||||
|
I += -93 * x07;
|
||||||
|
Q += -39 * x07;
|
||||||
|
|
||||||
|
int x06 = nextQ(&stride);
|
||||||
|
I += -71 * x06;
|
||||||
|
Q += -71 * x06;
|
||||||
|
|
||||||
|
int x05 = nextQ(&stride);
|
||||||
|
I += -39 * x05;
|
||||||
|
Q += -93 * x05;
|
||||||
|
|
||||||
|
int x04 = nextQ(&stride);
|
||||||
|
I += 0 * x04;
|
||||||
|
Q += -101 * x04;
|
||||||
|
|
||||||
|
int x03 = nextQ(&stride);
|
||||||
|
I += 39 * x03;
|
||||||
|
Q += -93 * x03;
|
||||||
|
|
||||||
|
int x02 = nextQ(&stride);
|
||||||
|
I += 71 * x02;
|
||||||
|
Q += -71 * x02;
|
||||||
|
|
||||||
|
int x01 = nextQ(&stride);
|
||||||
|
I += 93 * x01;
|
||||||
|
Q += -39 * x01;
|
||||||
|
|
||||||
|
int x00 = nextQ(&stride);
|
||||||
|
I += 101 * x00;
|
||||||
|
Q += 0 * x00;
|
||||||
|
|
||||||
|
I *= gain;
|
||||||
|
I /= 1024;
|
||||||
|
I *= max_amplitude_mul;
|
||||||
|
I += 127.4 * (1 << 16);
|
||||||
|
I /= (1 << 16);
|
||||||
|
|
||||||
|
Q *= gain;
|
||||||
|
Q /= 1024;
|
||||||
|
Q *= max_amplitude_mul;
|
||||||
|
Q += 127.4 * (1 << 16);
|
||||||
|
Q /= (1 << 16);
|
||||||
|
|
||||||
|
return (struct IQ){ I, Q };
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rf_rx(void)
|
static void rf_rx(void)
|
||||||
|
@ -474,28 +577,27 @@ static void rf_rx(void)
|
||||||
uint8_t *blockptr = block;
|
uint8_t *blockptr = block;
|
||||||
|
|
||||||
for (int i = 0; i < IQ_SAMPLES; i++) {
|
for (int i = 0; i < IQ_SAMPLES; i++) {
|
||||||
int I = getI();
|
struct IQ IQ = next_sample();
|
||||||
int Q = getQ();
|
int64_t I = IQ.I;
|
||||||
|
int64_t Q = IQ.Q;
|
||||||
|
|
||||||
if (I < 0)
|
if (I < 0)
|
||||||
I = 0;
|
I = 0;
|
||||||
else if (I > 255)
|
else if (I > 255)
|
||||||
I = 255;
|
I = 255;
|
||||||
|
|
||||||
|
*blockptr++ = I;
|
||||||
|
|
||||||
if (Q < 0)
|
if (Q < 0)
|
||||||
Q = 0;
|
Q = 0;
|
||||||
else if (Q > 255)
|
else if (Q > 255)
|
||||||
Q = 255;
|
Q = 255;
|
||||||
|
|
||||||
*blockptr++ = I;
|
|
||||||
*blockptr++ = Q;
|
*blockptr++ = Q;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queue_try_add(&iq_queue, &block)) {
|
if (queue_try_add(&iq_queue, &block)) {
|
||||||
iq_queue_pos = (iq_queue_pos + 1) % IQ_QUEUE_LEN;
|
iq_queue_pos = (iq_queue_pos + 1) & (IQ_QUEUE_LEN - 1);
|
||||||
led_set(0);
|
|
||||||
} else {
|
|
||||||
led_set(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -505,13 +607,24 @@ static void run_command(uint8_t cmd, uint32_t arg)
|
||||||
if (0x01 == cmd) {
|
if (0x01 == cmd) {
|
||||||
/* Tune to a new center frequency */
|
/* Tune to a new center frequency */
|
||||||
frequency = arg;
|
frequency = arg;
|
||||||
rx_lo_init(frequency);
|
rx_lo_init(frequency + sample_rate);
|
||||||
} else if (0x02 == cmd) {
|
} else if (0x02 == cmd) {
|
||||||
/* Set the rate at which IQ sample pairs are sent */
|
/* Set the rate at which IQ sample pairs are sent */
|
||||||
sample_rate = arg;
|
sample_rate = arg;
|
||||||
gain = BASE_GAIN / (SYS_CLK_HZ / sample_rate);
|
max_amplitude = CLK_SYS_HZ / sample_rate / 2;
|
||||||
dma_timer_set_fraction(dma_t_samp, 1, SYS_CLK_HZ / sample_rate);
|
max_amplitude_mul = 65536 / max_amplitude;
|
||||||
rx_lo_init(frequency);
|
dma_timer_set_fraction(dma_t_samp, 1, CLK_SYS_HZ / (sample_rate * DECIMATE));
|
||||||
|
rx_lo_init(frequency + sample_rate);
|
||||||
|
} else if (0x04 == cmd) {
|
||||||
|
/* Set the tuner gain level */
|
||||||
|
gain = INIT_GAIN * powf(10.0f, arg / 200.0f);
|
||||||
|
} else if (0x0d == cmd) {
|
||||||
|
/* Set tuner gain by the tuner's gain index */
|
||||||
|
|
||||||
|
if (arg >= NUM_GAINS)
|
||||||
|
arg = NUM_GAINS - 1;
|
||||||
|
|
||||||
|
gain = INIT_GAIN * powf(10.0f, gains[arg] / 200.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,16 +654,28 @@ static int check_command(void)
|
||||||
|
|
||||||
static void do_rx()
|
static void do_rx()
|
||||||
{
|
{
|
||||||
|
rf_rx_start();
|
||||||
|
sleep_us(100);
|
||||||
|
|
||||||
|
dma_ch_in_cos = dma_claim_unused_channel(true);
|
||||||
|
|
||||||
|
dma_channel_config dma_conf;
|
||||||
|
|
||||||
|
dma_conf = dma_channel_get_default_config(dma_ch_in_cos);
|
||||||
|
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32);
|
||||||
|
channel_config_set_read_increment(&dma_conf, false);
|
||||||
|
channel_config_set_write_increment(&dma_conf, true);
|
||||||
|
channel_config_set_ring(&dma_conf, GPIO_OUT, RX_BITS_DEPTH);
|
||||||
|
channel_config_set_dreq(&dma_conf, pio_get_dreq(PIO, SM_AD, false));
|
||||||
|
dma_channel_configure(dma_ch_in_cos, &dma_conf, rx_cos, &PIO->rxf[SM_AD], UINT_MAX, true);
|
||||||
|
|
||||||
|
multicore_launch_core1(rf_rx);
|
||||||
|
|
||||||
const uint8_t *block;
|
const uint8_t *block;
|
||||||
|
|
||||||
while (queue_try_remove(&iq_queue, &block))
|
while (queue_try_remove(&iq_queue, &block))
|
||||||
/* Flush the queue */;
|
/* Flush the queue */;
|
||||||
|
|
||||||
rf_rx_start();
|
|
||||||
sleep_us(100);
|
|
||||||
|
|
||||||
multicore_launch_core1(rf_rx);
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int cmd;
|
int cmd;
|
||||||
|
|
||||||
|
@ -562,31 +687,40 @@ static void do_rx()
|
||||||
fwrite(block, IQ_BLOCK_LEN, 1, stdout);
|
fwrite(block, IQ_BLOCK_LEN, 1, stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
} else {
|
} else {
|
||||||
sleep_us(10);
|
int wait = rnd_next() & 0x1fff;
|
||||||
|
|
||||||
|
for (int i = 0; i < wait; i++)
|
||||||
|
asm volatile("nop");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
multicore_fifo_push_blocking(0);
|
multicore_fifo_push_blocking(0);
|
||||||
multicore_fifo_pop_blocking();
|
multicore_fifo_pop_blocking();
|
||||||
sleep_us(100);
|
sleep_us(10);
|
||||||
multicore_reset_core1();
|
multicore_reset_core1();
|
||||||
|
|
||||||
rf_rx_stop();
|
rf_rx_stop();
|
||||||
|
|
||||||
|
dma_channel_clear_chain_to(dma_ch_in_cos);
|
||||||
|
dma_channel_abort(dma_ch_in_cos);
|
||||||
|
dma_channel_cleanup(dma_ch_in_cos);
|
||||||
|
dma_channel_unclaim(dma_ch_in_cos);
|
||||||
|
dma_ch_in_cos = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
vreg_set_voltage(VREG_VOLTAGE);
|
||||||
|
set_sys_clock_khz(CLK_SYS_HZ / KHZ, true);
|
||||||
|
clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS, CLK_SYS_HZ,
|
||||||
|
CLK_SYS_HZ);
|
||||||
|
|
||||||
/* Enable PSU PWM mode. */
|
/* Enable PSU PWM mode. */
|
||||||
gpio_init(PSU_PIN);
|
gpio_init(PSU_PIN);
|
||||||
gpio_set_dir(PSU_PIN, GPIO_OUT);
|
gpio_set_dir(PSU_PIN, GPIO_OUT);
|
||||||
gpio_put(PSU_PIN, 1);
|
gpio_put(PSU_PIN, 1);
|
||||||
|
|
||||||
gpio_init(PICO_DEFAULT_LED_PIN);
|
|
||||||
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
|
|
||||||
gpio_put(PICO_DEFAULT_LED_PIN, 0);
|
|
||||||
|
|
||||||
/* Prioritize DMA over CPU. */
|
|
||||||
bus_ctrl_hw->priority |= BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS;
|
bus_ctrl_hw->priority |= BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS;
|
||||||
|
|
||||||
stdio_usb_init();
|
stdio_usb_init();
|
||||||
|
@ -594,17 +728,16 @@ int main()
|
||||||
|
|
||||||
queue_init(&iq_queue, sizeof(uint8_t *), IQ_QUEUE_LEN);
|
queue_init(&iq_queue, sizeof(uint8_t *), IQ_QUEUE_LEN);
|
||||||
|
|
||||||
rx_lo_init(frequency);
|
rx_lo_init(frequency + sample_rate);
|
||||||
|
|
||||||
/* We need to have the sampling timer ready. */
|
|
||||||
dma_t_samp = dma_claim_unused_timer(true);
|
dma_t_samp = dma_claim_unused_timer(true);
|
||||||
dma_timer_set_fraction(dma_t_samp, 1, SYS_CLK_HZ / sample_rate);
|
dma_timer_set_fraction(dma_t_samp, 1, CLK_SYS_HZ / (sample_rate * DECIMATE));
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (check_command() > 0) {
|
if (check_command() > 0) {
|
||||||
static const uint32_t header[3] = { __builtin_bswap32(0x52544c30),
|
static const uint32_t header[3] = { __builtin_bswap32(0x52544c30),
|
||||||
__builtin_bswap32(5),
|
__builtin_bswap32(5),
|
||||||
__builtin_bswap32(29) };
|
__builtin_bswap32(NUM_GAINS) };
|
||||||
fwrite(header, sizeof header, 1, stdout);
|
fwrite(header, sizeof header, 1, stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
|
|
50
tools/clangd.py
Normal file
50
tools/clangd.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
import click
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
def clangd():
|
||||||
|
"""Generate .clangd file for local development."""
|
||||||
|
|
||||||
|
assert "PICO_SDK_PATH" in os.environ, "PICO_SDK_PATH not set"
|
||||||
|
|
||||||
|
pico_sdk_path = os.path.realpath(os.environ["PICO_SDK_PATH"])
|
||||||
|
cwd = os.path.realpath(os.getcwd())
|
||||||
|
|
||||||
|
includes = [
|
||||||
|
*glob(f"{pico_sdk_path}/src/common/*/include"),
|
||||||
|
*glob(f"{pico_sdk_path}/src/rp2040/*/include"),
|
||||||
|
*glob(f"{pico_sdk_path}/src/rp2_common/*/include"),
|
||||||
|
f"{pico_sdk_path}/lib/tinyusb/src",
|
||||||
|
*glob(f"{cwd}/src/**/include", recursive=True),
|
||||||
|
f"{cwd}/build/generated/pico_base",
|
||||||
|
f"{cwd}/build/sdk",
|
||||||
|
]
|
||||||
|
|
||||||
|
flags = [
|
||||||
|
"-Wall",
|
||||||
|
"-Wextra",
|
||||||
|
"-xc",
|
||||||
|
"-DCFG_TUSB_MCU=OPT_MCU_RP2040",
|
||||||
|
"-I/usr/arm-none-eabi/include",
|
||||||
|
]
|
||||||
|
|
||||||
|
yaml.safe_dump(
|
||||||
|
{
|
||||||
|
"CompileFlags": {
|
||||||
|
"Compiler": "arm-none-eabi-gcc",
|
||||||
|
"Add": [*flags, *[f"-I{inc}" for inc in includes]],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sys.stdout,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
clangd()
|
Loading…
Reference in a new issue