Compare commits

..

3 commits

Author SHA1 Message Date
4ce91c54b3 Fix frequency rounding 2024-08-02 20:34:51 +02:00
a78bdc509f Bunch of fixes and improvements
- Use less DMAs
- Improve bit counting
- Fix sample_rate changes and other fixes
2024-08-02 19:33:15 +02:00
2bd44e45df Track vendor/pico-stdio-usb-simple 2024-08-02 15:41:03 +02:00
7 changed files with 384 additions and 290 deletions

1
.gitignore vendored
View file

@ -1,3 +1,2 @@
/build/ /build/
/grc/*.py /grc/*.py
/src/.clangd

View file

@ -2,13 +2,11 @@
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` and a more up-to-date approach in the branch `master`. 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`.
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). ![](circuit.svg)
## Software ## Software
@ -34,4 +32,4 @@ Please refer to [the simulation](https://www.falstad.com/circuit/circuitjs.html?
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 set LNA gain to 0, gain control is digital and does not provide any benefits unless you lower your sampling rate significantly. 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.

View file

@ -49,7 +49,7 @@ blocks:
id: variable id: variable
parameters: parameters:
comment: '' comment: ''
value: '300_000' value: '192_000'
states: states:
bus_sink: false bus_sink: false
bus_source: false bus_source: false
@ -70,7 +70,7 @@ blocks:
bus_sink: false bus_sink: false
bus_source: false bus_source: false
bus_structure: null bus_structure: null
coordinate: [832, 336.0] coordinate: [640, 336.0]
rotation: 0 rotation: 0
state: true state: true
- name: analog_wfm_rcv_pll_0 - name: analog_wfm_rcv_pll_0
@ -88,7 +88,7 @@ blocks:
bus_sink: false bus_sink: false
bus_source: false bus_source: false
bus_structure: null bus_structure: null
coordinate: [832, 440.0] coordinate: [640, 440.0]
rotation: 0 rotation: 0
state: true state: true
- name: audio_sink_0 - name: audio_sink_0
@ -105,7 +105,7 @@ blocks:
bus_sink: false bus_sink: false
bus_source: false bus_source: false
bus_structure: null bus_structure: null
coordinate: [1120, 448.0] coordinate: [928, 448.0]
rotation: 0 rotation: 0
state: true state: true
- name: blocks_message_debug_0 - name: blocks_message_debug_0
@ -120,7 +120,7 @@ blocks:
bus_sink: false bus_sink: false
bus_source: false bus_source: false
bus_structure: null bus_structure: null
coordinate: [1120, 32.0] coordinate: [928, 32.0]
rotation: 0 rotation: 0
state: true state: true
- name: blocks_probe_rate_0 - name: blocks_probe_rate_0
@ -140,7 +140,7 @@ blocks:
bus_sink: false bus_sink: false
bus_source: false bus_source: false
bus_structure: null bus_structure: null
coordinate: [832, 40.0] coordinate: [640, 40.0]
rotation: 0 rotation: 0
state: true state: true
- name: low_pass_filter_0 - name: low_pass_filter_0
@ -164,7 +164,7 @@ blocks:
bus_sink: false bus_sink: false
bus_source: false bus_source: false
bus_structure: null bus_structure: null
coordinate: [1120, 284.0] coordinate: [928, 284.0]
rotation: 0 rotation: 0
state: enabled state: enabled
- name: osmosdr_source_0 - name: osmosdr_source_0
@ -618,7 +618,7 @@ blocks:
bus_sink: false bus_sink: false
bus_source: false bus_source: false
bus_structure: null bus_structure: null
coordinate: [832, 232.0] coordinate: [640, 232.0]
rotation: 0 rotation: 0
state: true state: true
- name: qtgui_time_sink_x_0_0 - name: qtgui_time_sink_x_0_0
@ -676,7 +676,7 @@ blocks:
marker9: '-1' marker9: '-1'
name: '"FM Demodulation"' name: '"FM Demodulation"'
nconnections: '1' nconnections: '1'
size: '128' size: '512'
srate: samp_rate // (2 ** 3) srate: samp_rate // (2 ** 3)
stemplot: 'False' stemplot: 'False'
style1: '1' style1: '1'
@ -715,7 +715,7 @@ blocks:
bus_sink: false bus_sink: false
bus_source: false bus_source: false
bus_structure: null bus_structure: null
coordinate: [1320, 312.0] coordinate: [1128, 312.0]
rotation: 0 rotation: 0
state: true state: true
- name: qtgui_waterfall_sink_x_0_0 - name: qtgui_waterfall_sink_x_0_0
@ -776,7 +776,7 @@ blocks:
bus_sink: false bus_sink: false
bus_source: false bus_source: false
bus_structure: null bus_structure: null
coordinate: [832, 128.0] coordinate: [640, 128.0]
rotation: 0 rotation: 0
state: true state: true
- name: qtgui_waterfall_sink_x_0_0_0_0 - name: qtgui_waterfall_sink_x_0_0_0_0
@ -837,7 +837,7 @@ blocks:
bus_sink: false bus_sink: false
bus_source: false bus_source: false
bus_structure: null bus_structure: null
coordinate: [1120, 184.0] coordinate: [928, 184.0]
rotation: 0 rotation: 0
state: true state: true

View file

@ -401,7 +401,7 @@ blocks:
freq7: 100e6 freq7: 100e6
freq8: 100e6 freq8: 100e6
freq9: 100e6 freq9: 100e6
gain0: '30' gain0: '0'
gain1: '10' gain1: '10'
gain10: '10' gain10: '10'
gain11: '10' gain11: '10'

View file

@ -17,7 +17,6 @@ target_link_libraries(
pico_stdlib pico_stdlib
pico_util pico_util
hardware_divider hardware_divider
hardware_adc
hardware_dma hardware_dma
hardware_pio hardware_pio
hardware_pwm hardware_pwm

View file

@ -4,7 +4,6 @@
#include <pico/util/queue.h> #include <pico/util/queue.h>
#include <hardware/clocks.h> #include <hardware/clocks.h>
#include <hardware/adc.h>
#include <hardware/dma.h> #include <hardware/dma.h>
#include <hardware/gpio.h> #include <hardware/gpio.h>
#include <hardware/pll.h> #include <hardware/pll.h>
@ -23,64 +22,54 @@
#include <stdlib.h> #include <stdlib.h>
#define VREG_VOLTAGE VREG_VOLTAGE_1_20 #define VREG_VOLTAGE VREG_VOLTAGE_1_20
#define CLK_SYS_HZ (288 * MHZ) #define CLK_SYS_HZ (306 * MHZ)
#define INIT_SAMPLE_RATE 200000 #define RX_PIN 10
#define INIT_FREQ 94600000 #define FB_PIN 11
#define INIT_GAIN 127
#define LO_PIN 21
#define RX_PIN 26
#define PSU_PIN 23 #define PSU_PIN 23
#define PIO pio1 #define PIO pio0
#define SM_LO 0 #define SM_RX 0
#define SM_BIAS 1
#define SM_COS 2
#define SM_SIN 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 8 #define IQ_QUEUE_LEN 8
#define ADC_RATE (2 * MHZ) #define XOR_ADDR 0x1000
#define LO_BITS_DEPTH 15
#define LO_WORDS (1 << (LO_BITS_DEPTH - 2))
#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] __attribute__((__aligned__(1 << LO_BITS_DEPTH)));
static uint32_t lo_sin[LO_WORDS] __attribute__((__aligned__(1 << LO_BITS_DEPTH)));
#define DECIMATE 4 #define DECIMATE 4
#define NCO_NUM_PHASES (1 << 8) #define INIT_SAMPLE_RATE 100000
#define NCO_PHASE_BITS 8 #define INIT_FREQ 94600000
#define NCO_PHASE_WORDS (1 << (NCO_PHASE_BITS - 2)) #define INIT_GAIN 127
#define STEP_BASE ((UINT_MAX + 1.0) / CLK_SYS_HZ)
static uint32_t nco_step = (uint32_t)(STEP_BASE * INIT_FREQ) * 32 * NCO_PHASE_WORDS;
static uint32_t nco_null = 0;
static uint32_t nco_mask = (1 << NCO_PHASE_BITS) - 1;
static uint32_t nco_phase[NCO_NUM_PHASES][NCO_PHASE_WORDS]
__attribute__((__aligned__(NCO_NUM_PHASES * 4 * NCO_PHASE_WORDS)));
static_assert(sizeof(nco_phase) == 65536, "sizeof(nco_phase) == 65536");
static uint32_t *nco_addr = &nco_phase[0][0];
#define NUM_GAINS 29 #define NUM_GAINS 29
static int gains[NUM_GAINS] = { 0, 9, 14, 27, 37, 77, 87, 125, 144, 157, 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, 166, 197, 207, 229, 254, 280, 297, 328, 338, 364,
372, 386, 402, 421, 434, 439, 445, 480, 496 }; 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 gain = INIT_GAIN; static int gain = INIT_GAIN;
static int frequency = INIT_FREQ; static int frequency = INIT_FREQ;
static int dma_ch_nco1 = -1;
static int dma_ch_nco2 = -1;
static int dma_ch_nco3 = -1;
static int dma_ch_nco4 = -1;
static int dma_ch_mix = -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 rnd = 0; static uint32_t rnd = 0;
static int origin_lo = -1;
inline static __unused uint32_t rnd_next() inline static __unused uint32_t rnd_next()
{ {
rnd = rnd * 0x41c64e6d + 12345; rnd = rnd * 0x41c64e6d + 12345;
@ -95,60 +84,167 @@ static void dma_channel_clear_chain_to(int ch)
dma_hw->ch[ch].al1_ctrl = ctrl; dma_hw->ch[ch].al1_ctrl = ctrl;
} }
static void init_lo() /* rx -> cp -> cos -> sin -> pio_cos -> pio_sin -> rx ... */
{ static int dma_ch_rx = -1;
gpio_disable_pulls(LO_PIN); static int dma_ch_cp = -1;
pio_gpio_init(PIO, LO_PIN); static int dma_ch_cos = -1;
static int dma_ch_sin = -1;
static int dma_ch_pio_cos = -1;
static int dma_ch_pio_sin = -1;
gpio_set_drive_strength(LO_PIN, GPIO_DRIVE_STRENGTH_12MA); static int dma_ch_samp_cos = -1;
gpio_set_slew_rate(LO_PIN, GPIO_SLEW_RATE_FAST); static int dma_ch_samp_sin = -1;
static int dma_t_samp = -1;
static int origin_rx = -1;
static int origin_bias = -1;
static int origin_adder = 0;
static void init_rx()
{
gpio_disable_pulls(RX_PIN);
pio_gpio_init(PIO, RX_PIN);
const uint16_t insn[] = { const uint16_t insn[] = {
pio_encode_out(pio_pindirs, 1), pio_encode_in(pio_pins, 1) | pio_encode_delay(0),
}; };
pio_program_t prog = { pio_program_t prog = {
.instructions = insn, .instructions = insn,
.length = sizeof(insn) / sizeof(*insn), .length = sizeof(insn) / sizeof(*insn),
.origin = origin_lo, .origin = origin_rx,
}; };
pio_sm_restart(PIO, SM_LO);
pio_sm_clear_fifos(PIO, SM_LO);
if (pio_can_add_program(PIO, &prog)) if (pio_can_add_program(PIO, &prog))
origin_lo = pio_add_program(PIO, &prog); origin_rx = 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_out_pins(&pc, LO_PIN, 1); sm_config_set_in_pins(&pc, RX_PIN);
sm_config_set_set_pins(&pc, LO_PIN, 1); sm_config_set_wrap(&pc, origin_rx, origin_rx + prog.length - 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_clkdiv_int_frac(&pc, 1, 0);
sm_config_set_fifo_join(&pc, PIO_FIFO_JOIN_TX); sm_config_set_fifo_join(&pc, PIO_FIFO_JOIN_RX);
sm_config_set_out_shift(&pc, false, true, 32); sm_config_set_in_shift(&pc, false, true, 32);
pio_sm_init(PIO, SM_LO, origin_lo, &pc); pio_sm_init(PIO, SM_RX, origin_rx, &pc);
pio_sm_set_consecutive_pindirs(PIO, SM_LO, LO_PIN, 1, GPIO_IN); pio_sm_set_consecutive_pindirs(PIO, SM_RX, RX_PIN, 1, GPIO_IN);
pio_sm_exec_wait_blocking(PIO, SM_LO, pio_encode_set(pio_pins, 0));
} }
inline static uint32_t phase_bit(uint32_t phase, uint32_t step) static void init_bias()
{ {
uint32_t next = phase + step; gpio_disable_pulls(RX_PIN);
gpio_disable_pulls(FB_PIN);
if ((next & 0x7fffffff) > (step >> 1)) pio_gpio_init(PIO, FB_PIN);
return next >> 31;
return phase >> 31; 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);
PIO->input_sync_bypass = 1u << RX_PIN;
const uint16_t insn[] = {
pio_encode_mov(pio_isr, pio_null),
pio_encode_in(pio_y, 4),
pio_encode_in(pio_pins, 1) | pio_encode_delay(15),
pio_encode_in(pio_pins, 1) | pio_encode_delay(15),
pio_encode_mov(pio_y, pio_isr),
pio_encode_mov(pio_x, pio_isr),
pio_encode_jmp_x_dec(6),
pio_encode_mov_not(pio_pins, pio_pins) | pio_encode_sideset(1, 1),
};
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 void nco_generate_phase(uint32_t *buf, size_t len, uint32_t step, uint32_t phase) static const uint32_t samp_insn = 16;
static void init_adder()
{
const uint16_t insn[] = {
pio_encode_out(pio_pc, 4), // 0000 +0
pio_encode_jmp_x_dec(0), // 0001 +1
pio_encode_jmp_x_dec(0), // 0010 +1
pio_encode_jmp_y_dec(0), // 0011 +2
pio_encode_jmp_x_dec(0), // 0100 +1
pio_encode_jmp_y_dec(0), // 0101 +2
pio_encode_jmp_y_dec(0), // 0110 +2
pio_encode_jmp_y_dec(1), // 0111 +2 +1
pio_encode_jmp_x_dec(0), // 1000 +1
pio_encode_jmp_y_dec(0), // 1001 +2
pio_encode_jmp_y_dec(0), // 1010 +2
pio_encode_jmp_y_dec(1), // 1011 +2 +1
pio_encode_jmp_y_dec(0), // 1100 +2
pio_encode_jmp_y_dec(1), // 1101 +2 +1
pio_encode_jmp_y_dec(1), // 1110 +2 +1
pio_encode_jmp_y_dec(3), // 1111 +2 +2
/*
* Should wrap here.
* Jump to this portion must be inserted from the outside.
*/
pio_encode_in(pio_y, 32),
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_program_t prog = {
.instructions = insn,
.length = sizeof(insn) / sizeof(*insn),
.origin = origin_adder,
};
if (pio_can_add_program(PIO, &prog))
origin_adder = pio_add_program(PIO, &prog);
pio_sm_config pc = pio_get_default_sm_config();
sm_config_set_wrap(&pc, origin_adder, origin_adder + 15);
sm_config_set_clkdiv_int_frac(&pc, 1, 0);
sm_config_set_in_shift(&pc, false, true, 32);
sm_config_set_out_shift(&pc, false, true, 32);
pio_sm_init(PIO, SM_COS, origin_adder, &pc);
pio_sm_init(PIO, SM_SIN, origin_adder, &pc);
}
#define STEP_BASE ((UINT_MAX + 1.0) / CLK_SYS_HZ)
static void lo_generate_phase(uint32_t *buf, size_t len, uint32_t step, uint32_t phase)
{ {
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
uint32_t bits = 0; uint32_t bits = 0;
for (int j = 0; j < 32; j++) { for (int j = 0; j < 32; j++) {
bits |= phase_bit(phase, step); bits |= phase >> 31;
bits <<= 1; bits <<= 1;
phase += step; phase += step;
} }
@ -157,163 +253,219 @@ static void nco_generate_phase(uint32_t *buf, size_t len, uint32_t step, uint32_
} }
} }
static void rx_lo_init(double freq) static void rx_lo_init(double req_freq, bool align)
{ {
const double step_hz = (double)CLK_SYS_HZ / ((8 << LO_BITS_DEPTH) / 2.0);
double freq = req_freq;
if (align)
freq = round(freq / step_hz) * step_hz;
uint32_t step = STEP_BASE * freq; uint32_t step = STEP_BASE * freq;
for (uint32_t i = 0; i < NCO_NUM_PHASES; i++) lo_generate_phase(lo_cos, LO_WORDS, step, COS_PHASE);
nco_generate_phase(nco_phase[i], NCO_PHASE_WORDS, step, lo_generate_phase(lo_sin, LO_WORDS, step, SIN_PHASE);
i << (__builtin_clz(NCO_NUM_PHASES) + 1));
nco_step = step * 32 * NCO_PHASE_WORDS;
} }
static void rf_rx_start() static void rf_rx_start()
{ {
dma_ch_nco1 = dma_claim_unused_channel(true); dma_ch_rx = dma_claim_unused_channel(true);
dma_ch_nco2 = dma_claim_unused_channel(true); dma_ch_cp = dma_claim_unused_channel(true);
dma_ch_nco3 = dma_claim_unused_channel(true); dma_ch_cos = dma_claim_unused_channel(true);
dma_ch_nco4 = dma_claim_unused_channel(true); dma_ch_sin = dma_claim_unused_channel(true);
dma_ch_mix = dma_claim_unused_channel(true); dma_ch_pio_cos = dma_claim_unused_channel(true);
dma_ch_pio_sin = 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;
/* Step the NCO. */ /* Read received word into accumulator I. */
dma_conf = dma_channel_get_default_config(dma_ch_nco1); dma_conf = dma_channel_get_default_config(dma_ch_rx);
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_nco2); channel_config_set_dreq(&dma_conf, pio_get_dreq(PIO, SM_RX, false));
dma_channel_configure(dma_ch_nco1, &dma_conf, &nco_null, &nco_step, 1, false); channel_config_set_chain_to(&dma_conf, dma_ch_cp);
dma_channel_configure(dma_ch_rx, &dma_conf, LO_COS_ACCUMULATOR, &PIO->rxf[SM_RX], 1, false);
/* DMA above will increment the phase accumulator. */ /* Copy accumulator I to accumulator Q. */
dma_sniffer_enable(dma_ch_nco1, DMA_SNIFF_CTRL_CALC_VALUE_SUM, true); dma_conf = dma_channel_get_default_config(dma_ch_cp);
/* Prepare the phase address. */
dma_conf = dma_channel_get_default_config(dma_ch_nco2);
channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_16);
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, (void *)(&nco_addr) + 0,
((void *)&dma_hw->sniff_data) + 2, 1, false);
/* Copy it to the DMA. */
dma_conf = dma_channel_get_default_config(dma_ch_nco3);
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_nco4); channel_config_set_chain_to(&dma_conf, dma_ch_cos);
dma_channel_configure(dma_ch_nco3, &dma_conf, &dma_hw->ch[dma_ch_mix].read_addr, &nco_addr, dma_channel_configure(dma_ch_cp, &dma_conf, LO_SIN_ACCUMULATOR, LO_COS_ACCUMULATOR, 1,
1, false); false);
/* Trigger LO by clearing the bottom bits. */ /* Read lo_cos into accumulator I with XOR. */
dma_conf = dma_channel_get_default_config(dma_ch_nco4); dma_conf = dma_channel_get_default_config(dma_ch_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);
dma_channel_configure(dma_ch_nco4, &dma_conf,
(void *)&dma_hw->ch[dma_ch_mix].al3_read_addr_trig +
REG_ALIAS_CLR_BITS,
&nco_mask, 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_dreq(&dma_conf, pio_get_dreq(PIO, SM_LO, GPIO_OUT)); channel_config_set_ring(&dma_conf, false, LO_BITS_DEPTH);
channel_config_set_chain_to(&dma_conf, dma_ch_nco1); channel_config_set_chain_to(&dma_conf, dma_ch_sin);
dma_channel_configure(dma_ch_mix, &dma_conf, &PIO->txf[SM_LO], &nco_phase[0][0], dma_channel_configure(dma_ch_cos, &dma_conf, LO_COS_ACCUMULATOR + XOR_ADDR / 4, lo_cos, 1,
NCO_PHASE_WORDS, false); false);
init_lo(); /* Read lo_sin into accumulator Q with XOR. */
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);
dma_channel_start(dma_ch_nco1); /* 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);
pio_sm_set_enabled(PIO, SM_LO, true); /* 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);
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, dma_get_timer_dreq(dma_t_samp));
channel_config_set_high_priority(&dma_conf, true);
channel_config_set_chain_to(&dma_conf, dma_ch_samp_sin);
dma_channel_configure(dma_ch_samp_cos, &dma_conf, &PIO->sm[SM_COS].instr, &samp_insn, 1,
false);
/* Trigger Q accumulator values push. */
dma_conf = dma_channel_get_default_config(dma_ch_samp_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_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();
dma_channel_start(dma_ch_rx);
dma_channel_start(dma_ch_samp_cos);
pio_set_sm_mask_enabled(PIO, 0x0f, true);
} }
static void rf_rx_stop(void) static void rf_rx_stop(void)
{ {
pio_sm_set_enabled(PIO, SM_LO, 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_nco1); dma_channel_clear_chain_to(dma_ch_rx);
dma_channel_clear_chain_to(dma_ch_nco2); dma_channel_clear_chain_to(dma_ch_cp);
dma_channel_clear_chain_to(dma_ch_nco3); dma_channel_clear_chain_to(dma_ch_cos);
dma_channel_clear_chain_to(dma_ch_nco4); dma_channel_clear_chain_to(dma_ch_sin);
dma_channel_clear_chain_to(dma_ch_mix); dma_channel_clear_chain_to(dma_ch_pio_cos);
dma_channel_clear_chain_to(dma_ch_pio_sin);
dma_channel_clear_chain_to(dma_ch_samp_cos);
dma_channel_clear_chain_to(dma_ch_samp_sin);
dma_channel_abort(dma_ch_nco1); dma_channel_abort(dma_ch_rx);
dma_channel_abort(dma_ch_nco2); dma_channel_abort(dma_ch_cp);
dma_channel_abort(dma_ch_nco3); dma_channel_abort(dma_ch_cos);
dma_channel_abort(dma_ch_nco4); dma_channel_abort(dma_ch_sin);
dma_channel_abort(dma_ch_mix); dma_channel_abort(dma_ch_pio_cos);
dma_channel_abort(dma_ch_pio_sin);
dma_channel_abort(dma_ch_samp_cos);
dma_channel_abort(dma_ch_samp_sin);
dma_channel_cleanup(dma_ch_nco1); dma_channel_cleanup(dma_ch_rx);
dma_channel_cleanup(dma_ch_nco2); dma_channel_cleanup(dma_ch_cp);
dma_channel_cleanup(dma_ch_nco3); dma_channel_cleanup(dma_ch_cos);
dma_channel_cleanup(dma_ch_nco4); dma_channel_cleanup(dma_ch_sin);
dma_channel_cleanup(dma_ch_mix); dma_channel_cleanup(dma_ch_pio_cos);
dma_channel_cleanup(dma_ch_pio_sin);
dma_channel_cleanup(dma_ch_samp_cos);
dma_channel_cleanup(dma_ch_samp_sin);
dma_channel_unclaim(dma_ch_nco1); dma_channel_unclaim(dma_ch_rx);
dma_channel_unclaim(dma_ch_nco2); dma_channel_unclaim(dma_ch_cp);
dma_channel_unclaim(dma_ch_nco3); dma_channel_unclaim(dma_ch_cos);
dma_channel_unclaim(dma_ch_nco4); dma_channel_unclaim(dma_ch_sin);
dma_channel_unclaim(dma_ch_mix); dma_channel_unclaim(dma_ch_pio_cos);
dma_channel_unclaim(dma_ch_pio_sin);
dma_channel_unclaim(dma_ch_samp_cos);
dma_channel_unclaim(dma_ch_samp_sin);
dma_ch_nco1 = -1; dma_ch_rx = -1;
dma_ch_nco2 = -1; dma_ch_cp = -1;
dma_ch_nco3 = -1; dma_ch_cos = -1;
dma_ch_nco4 = -1; dma_ch_sin = -1;
dma_ch_mix = -1; dma_ch_pio_cos = -1;
dma_ch_pio_sin = -1;
dma_ch_samp_cos = -1;
dma_ch_samp_sin = -1;
} }
struct IQ { inline static uint32_t pio_sm_get_blocking_unsafe(pio_hw_t *pio, int sm)
int I, Q;
};
inline static int nextQ(void)
{ {
static int x4, x3, x2, x1; while (pio->fstat & (1u << (PIO_FSTAT_RXEMPTY_LSB + sm)))
asm volatile("nop");
int x0 = gain * adc_fifo_get_blocking(); return pio->rxf[sm];
int x = x2 + x2 - x4 - x0;
x4 = x3;
x3 = x2;
x2 = x1;
x1 = x0;
return x;
} }
inline static struct IQ next_sample() inline static int nextI()
{ {
int I = 0, Q = 0; static int prevI = 0;
I += nextQ(); int sI = 0;
Q += nextQ(); sI -= 2 * pio_sm_get_blocking_unsafe(PIO, SM_COS);
I -= nextQ(); sI -= pio_sm_get_blocking_unsafe(PIO, SM_COS);
Q -= nextQ();
static int dcI, dcQ; int I = sI - prevI;
prevI = sI;
I = ((I << 12) - dcI) >> 12; return I;
dcI += I; }
Q = ((Q << 12) - dcQ) >> 12; inline static int nextQ()
dcQ += Q; {
static int prevQ = 0;
I += 127.4 * 512; int sQ = 0;
I /= 512; sQ -= 2 * pio_sm_get_blocking_unsafe(PIO, SM_SIN);
sQ -= pio_sm_get_blocking_unsafe(PIO, SM_SIN);
Q += 127.4 * 512; int Q = sQ - prevQ;
Q /= 512; prevQ = sQ;
return (struct IQ){ I, Q }; return Q;
} }
static void rf_rx(void) static void rf_rx(void)
@ -329,23 +481,39 @@ 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++) {
struct IQ IQ = next_sample(); int I = 0, Q = 0;
int64_t I = IQ.I;
int64_t Q = IQ.Q;
if (I < 0) Q += nextQ();
I = 0; I += nextI();
else if (I > 255)
I = 255;
*blockptr++ = I; I -= nextQ();
Q += nextI();
if (Q < 0) Q -= nextQ();
Q = 0; I -= nextI();
else if (Q > 255)
Q = 255;
*blockptr++ = Q; I += nextQ();
Q -= nextI();
I *= gain;
I /= max_amplitude;
if (I > 127)
I = 127;
else if (I < -128)
I = -128;
*blockptr++ = (uint8_t)I + 128;
Q *= gain;
Q /= max_amplitude;
if (Q > 127)
Q = 127;
else if (Q < -128)
Q = -128;
*blockptr++ = (uint8_t)Q + 128;
} }
if (queue_try_add(&iq_queue, &block)) { if (queue_try_add(&iq_queue, &block)) {
@ -359,24 +527,20 @@ 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 + sample_rate); rx_lo_init(frequency + sample_rate, true);
} 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 */
if (arg > (ADC_RATE / DECIMATE))
arg = ADC_RATE / DECIMATE;
sample_rate = arg; sample_rate = arg;
adc_set_clkdiv(96.0f * ((float)ADC_RATE / (sample_rate * DECIMATE)) - 1.0f); max_amplitude = CLK_SYS_HZ / sample_rate / 2;
dma_timer_set_fraction(dma_t_samp, 1, CLK_SYS_HZ / (sample_rate * DECIMATE));
rx_lo_init(frequency + sample_rate, true);
} else if (0x04 == cmd) { } else if (0x04 == cmd) {
/* Set the tuner gain level */ /* Set the tuner gain level */
gain = INIT_GAIN * powf(10.0f, arg / 200.0f); gain = INIT_GAIN * pow(10.0, arg / 200.0);
} else if (0x0d == cmd) { } else if (0x0d == cmd) {
/* Set tuner gain by the tuner's gain index */ /* Set tuner gain by the tuner's gain index */
if (arg <= NUM_GAINS)
if (arg >= NUM_GAINS) gain = INIT_GAIN * pow(10.0, gains[arg] / 200.0);
arg = NUM_GAINS - 1;
gain = INIT_GAIN * powf(10.0f, gains[arg] / 200.0f);
} }
} }
@ -406,16 +570,16 @@ static int check_command(void)
static void do_rx() static void do_rx()
{ {
rf_rx_start();
sleep_us(100);
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;
@ -437,7 +601,7 @@ static void do_rx()
done: done:
multicore_fifo_push_blocking(0); multicore_fifo_push_blocking(0);
multicore_fifo_pop_blocking(); multicore_fifo_pop_blocking();
sleep_us(10); sleep_us(100);
multicore_reset_core1(); multicore_reset_core1();
rf_rx_stop(); rf_rx_stop();
@ -446,19 +610,7 @@ done:
int main() int main()
{ {
vreg_set_voltage(VREG_VOLTAGE); vreg_set_voltage(VREG_VOLTAGE);
/* Step the USB PLL up to 192 MHz and overclock ADC with it. */
pll_init(pll_usb, 1, 1536 * MHZ, 4, 2);
clock_configure(clk_usb, 0, CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
4 * USB_CLK_KHZ * KHZ, USB_CLK_KHZ * KHZ);
clock_configure(clk_adc, 0, CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
4 * USB_CLK_KHZ * KHZ, 4 * USB_CLK_KHZ * KHZ);
/* Adjust system clock as well. */
set_sys_clock_khz(CLK_SYS_HZ / KHZ, true); 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, clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS, CLK_SYS_HZ,
CLK_SYS_HZ); CLK_SYS_HZ);
@ -467,6 +619,7 @@ int main()
gpio_set_dir(PSU_PIN, GPIO_OUT); gpio_set_dir(PSU_PIN, GPIO_OUT);
gpio_put(PSU_PIN, 1); gpio_put(PSU_PIN, 1);
/* 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();
@ -474,16 +627,11 @@ int main()
queue_init(&iq_queue, sizeof(uint8_t *), IQ_QUEUE_LEN); queue_init(&iq_queue, sizeof(uint8_t *), IQ_QUEUE_LEN);
/* Init ADC */ rx_lo_init(frequency + sample_rate, true);
adc_init();
gpio_disable_pulls(RX_PIN);
adc_gpio_init(RX_PIN);
adc_select_input(RX_PIN - 26);
adc_fifo_setup(true, true, 1, false, false);
adc_set_clkdiv(96.0f * ((float)ADC_RATE / (sample_rate * DECIMATE)) - 1.0f);
adc_run(true);
rx_lo_init(frequency + sample_rate); /* We need to have the sampling timer ready. */
dma_t_samp = dma_claim_unused_timer(true);
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) {

View file

@ -1,50 +0,0 @@
#!/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()