Compare commits
43 commits
master
...
superhet-a
Author | SHA1 | Date | |
---|---|---|---|
e02806f3cc | |||
758488c054 | |||
f10f8d5ceb | |||
a61748c6b5 | |||
b31fc626e3 | |||
63d9a59fe6 | |||
fcc69f6a3e | |||
36ca06f31b | |||
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 |
7 changed files with 321 additions and 508 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.
|
||||||
|
|
|
@ -37,7 +37,7 @@ blocks:
|
||||||
id: variable
|
id: variable
|
||||||
parameters:
|
parameters:
|
||||||
comment: ''
|
comment: ''
|
||||||
value: '88_200_000'
|
value: '94_600_000'
|
||||||
states:
|
states:
|
||||||
bus_sink: false
|
bus_sink: false
|
||||||
bus_source: false
|
bus_source: false
|
||||||
|
@ -49,7 +49,7 @@ blocks:
|
||||||
id: variable
|
id: variable
|
||||||
parameters:
|
parameters:
|
||||||
comment: ''
|
comment: ''
|
||||||
value: '192_000'
|
value: '300_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: [640, 336.0]
|
coordinate: [832, 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: [640, 440.0]
|
coordinate: [832, 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: [928, 448.0]
|
coordinate: [1120, 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: [928, 32.0]
|
coordinate: [1120, 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: [640, 40.0]
|
coordinate: [832, 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: [928, 284.0]
|
coordinate: [1120, 284.0]
|
||||||
rotation: 0
|
rotation: 0
|
||||||
state: enabled
|
state: enabled
|
||||||
- name: osmosdr_source_0
|
- name: osmosdr_source_0
|
||||||
|
@ -374,7 +374,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'
|
||||||
|
@ -618,7 +618,7 @@ blocks:
|
||||||
bus_sink: false
|
bus_sink: false
|
||||||
bus_source: false
|
bus_source: false
|
||||||
bus_structure: null
|
bus_structure: null
|
||||||
coordinate: [640, 232.0]
|
coordinate: [832, 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: '512'
|
size: '128'
|
||||||
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: [1128, 312.0]
|
coordinate: [1320, 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: [640, 128.0]
|
coordinate: [832, 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: [928, 184.0]
|
coordinate: [1120, 184.0]
|
||||||
rotation: 0
|
rotation: 0
|
||||||
state: true
|
state: true
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ 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
|
||||||
|
|
739
src/main.c
739
src/main.c
|
@ -4,6 +4,7 @@
|
||||||
#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>
|
||||||
|
@ -22,60 +23,55 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#define VREG_VOLTAGE VREG_VOLTAGE_1_20
|
#define VREG_VOLTAGE VREG_VOLTAGE_1_20
|
||||||
#define CLK_SYS_HZ (300 * MHZ)
|
#define CLK_SYS_HZ (288 * MHZ)
|
||||||
|
|
||||||
|
#define INIT_SAMPLE_RATE 200000
|
||||||
|
#define INIT_FREQ 94600000
|
||||||
|
#define INIT_GAIN 127
|
||||||
|
|
||||||
|
#define LO_PIN 21
|
||||||
|
#define RX_PIN 26
|
||||||
#define PSU_PIN 23
|
#define PSU_PIN 23
|
||||||
|
|
||||||
|
#define PIO pio1
|
||||||
|
#define SM_LO 0
|
||||||
|
|
||||||
#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 4
|
#define IQ_QUEUE_LEN 8
|
||||||
|
|
||||||
#define XOR_ADDR 0x1000
|
|
||||||
#define LO_COS_ACCUMULATOR (&pio1->sm[2].pinctrl)
|
|
||||||
#define LO_SIN_ACCUMULATOR (&pio1->sm[3].pinctrl)
|
|
||||||
|
|
||||||
#define LO_BITS_DEPTH 15
|
|
||||||
#define LO_WORDS (1 << (LO_BITS_DEPTH - 2))
|
|
||||||
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 ADC_RATE (2 * MHZ)
|
||||||
#define DECIMATE 4
|
#define DECIMATE 4
|
||||||
#define RX_STRIDE (2 * IQ_SAMPLES * DECIMATE)
|
|
||||||
#define RX_BITS_DEPTH 13
|
|
||||||
#define RX_WORDS (1 << (RX_BITS_DEPTH - 2))
|
|
||||||
|
|
||||||
static_assert(RX_STRIDE * 4 < RX_WORDS, "RX_STRIDE * 4 < RX_WORDS");
|
#define NCO_NUM_PHASES (1 << 8)
|
||||||
|
#define NCO_PHASE_BITS 8
|
||||||
|
#define NCO_PHASE_WORDS (1 << (NCO_PHASE_BITS - 2))
|
||||||
|
#define STEP_BASE ((UINT_MAX + 1.0) / CLK_SYS_HZ)
|
||||||
|
|
||||||
static uint32_t rx_cos[RX_WORDS] __attribute__((__aligned__(1 << RX_BITS_DEPTH)));
|
static uint32_t nco_step = (uint32_t)(STEP_BASE * INIT_FREQ) * 32 * NCO_PHASE_WORDS;
|
||||||
static uint32_t rx_sin[RX_WORDS] __attribute__((__aligned__(1 << RX_BITS_DEPTH)));
|
static uint32_t nco_null = 0;
|
||||||
|
static uint32_t nco_mask = (1 << NCO_PHASE_BITS) - 1;
|
||||||
|
|
||||||
#define INIT_SAMPLE_RATE 100000
|
static uint32_t nco_phase[NCO_NUM_PHASES][NCO_PHASE_WORDS]
|
||||||
#define INIT_FREQ 94600000
|
__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 gain = INIT_GAIN;
|
||||||
|
static int frequency = INIT_FREQ;
|
||||||
|
|
||||||
#define SIN_PHASE (0u)
|
static int dma_ch_nco1 = -1;
|
||||||
#define COS_PHASE (3u << 30)
|
static int dma_ch_nco2 = -1;
|
||||||
|
static int dma_ch_nco3 = -1;
|
||||||
/* rx -> cp -> cos -> sin -> pio_cos -> pio_sin -> rx ... */
|
static int dma_ch_nco4 = -1;
|
||||||
static int dma_ch_rx = -1;
|
static int dma_ch_mix = -1;
|
||||||
static int dma_ch_cp = -1;
|
|
||||||
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;
|
|
||||||
|
|
||||||
static int dma_ch_samp_trig = -1;
|
|
||||||
static int dma_ch_samp_cos = -1;
|
|
||||||
static int dma_ch_samp_sin = -1;
|
|
||||||
|
|
||||||
static int dma_t_samp = -1;
|
|
||||||
|
|
||||||
static int dma_ch_in_cos = -1;
|
|
||||||
static int dma_ch_in_sin = -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];
|
||||||
|
@ -83,404 +79,245 @@ static size_t iq_queue_pos = 0;
|
||||||
|
|
||||||
static uint32_t rnd = 0;
|
static uint32_t rnd = 0;
|
||||||
|
|
||||||
inline static uint32_t rnd_next()
|
static int origin_lo = -1;
|
||||||
|
|
||||||
|
inline static __unused uint32_t rnd_next()
|
||||||
{
|
{
|
||||||
rnd = rnd * 0x41c64e6d + 12345;
|
rnd = rnd * 0x41c64e6d + 12345;
|
||||||
return rnd;
|
return rnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bias_set_gain(int gain)
|
static void dma_channel_clear_chain_to(int ch)
|
||||||
{
|
{
|
||||||
if (gain > 9)
|
uint32_t ctrl = dma_hw->ch[ch].al1_ctrl;
|
||||||
gain = 9;
|
ctrl &= ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS;
|
||||||
else if (gain < 0)
|
ctrl |= ch << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB;
|
||||||
gain = 0;
|
dma_hw->ch[ch].al1_ctrl = ctrl;
|
||||||
|
|
||||||
pio1->sm[0].execctrl = (pio1->sm[0].execctrl & ~PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS) |
|
|
||||||
((19 - gain) << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bias_init(int in_pin, int out_pin)
|
static void init_lo()
|
||||||
{
|
{
|
||||||
gpio_disable_pulls(in_pin);
|
gpio_disable_pulls(LO_PIN);
|
||||||
gpio_disable_pulls(out_pin);
|
pio_gpio_init(PIO, LO_PIN);
|
||||||
|
|
||||||
pio_gpio_init(pio1, out_pin);
|
gpio_set_drive_strength(LO_PIN, GPIO_DRIVE_STRENGTH_12MA);
|
||||||
|
gpio_set_slew_rate(LO_PIN, GPIO_SLEW_RATE_FAST);
|
||||||
gpio_set_input_hysteresis_enabled(in_pin, false);
|
|
||||||
gpio_set_drive_strength(out_pin, GPIO_DRIVE_STRENGTH_2MA);
|
|
||||||
gpio_set_slew_rate(out_pin, GPIO_SLEW_RATE_SLOW);
|
|
||||||
|
|
||||||
const uint16_t insn[] = {
|
const uint16_t insn[] = {
|
||||||
pio_encode_in(pio_pins, 1),
|
pio_encode_out(pio_pindirs, 1),
|
||||||
pio_encode_in(pio_pins, 1),
|
|
||||||
pio_encode_in(pio_pins, 1),
|
|
||||||
pio_encode_in(pio_pins, 1),
|
|
||||||
pio_encode_in(pio_pins, 1),
|
|
||||||
pio_encode_in(pio_pins, 1),
|
|
||||||
pio_encode_in(pio_pins, 1),
|
|
||||||
pio_encode_in(pio_pins, 1),
|
|
||||||
pio_encode_in(pio_pins, 1),
|
|
||||||
|
|
||||||
pio_encode_mov(pio_x, pio_isr),
|
|
||||||
pio_encode_mov(pio_isr, pio_null),
|
|
||||||
|
|
||||||
pio_encode_jmp_x_dec(11),
|
|
||||||
pio_encode_mov_not(pio_pins, pio_pins) | pio_encode_sideset(1, 1),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pio_program_t prog = {
|
pio_program_t prog = {
|
||||||
.instructions = insn,
|
.instructions = insn,
|
||||||
.length = sizeof(insn) / sizeof(*insn),
|
.length = sizeof(insn) / sizeof(*insn),
|
||||||
.origin = 10,
|
.origin = origin_lo,
|
||||||
};
|
};
|
||||||
|
|
||||||
pio_sm_set_enabled(pio1, 0, false);
|
pio_sm_restart(PIO, SM_LO);
|
||||||
pio_sm_restart(pio1, 0);
|
pio_sm_clear_fifos(PIO, SM_LO);
|
||||||
pio_sm_clear_fifos(pio1, 0);
|
|
||||||
|
|
||||||
if (pio_can_add_program(pio1, &prog))
|
if (pio_can_add_program(PIO, &prog))
|
||||||
pio_add_program(pio1, &prog);
|
origin_lo = 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_in_shift(&pc, false, false, 32);
|
sm_config_set_out_pins(&pc, LO_PIN, 1);
|
||||||
sm_config_set_sideset(&pc, 1, false, true);
|
sm_config_set_set_pins(&pc, LO_PIN, 1);
|
||||||
sm_config_set_sideset_pins(&pc, out_pin);
|
sm_config_set_wrap(&pc, origin_lo, origin_lo + prog.length - 1);
|
||||||
sm_config_set_in_pins(&pc, in_pin);
|
|
||||||
sm_config_set_out_pins(&pc, out_pin, 1);
|
|
||||||
sm_config_set_set_pins(&pc, out_pin, 1);
|
|
||||||
|
|
||||||
sm_config_set_wrap(&pc, prog.origin, prog.origin + prog.length - 1);
|
|
||||||
|
|
||||||
sm_config_set_clkdiv_int_frac(&pc, 1, 0);
|
sm_config_set_clkdiv_int_frac(&pc, 1, 0);
|
||||||
pio_sm_init(pio1, 0, prog.origin, &pc);
|
sm_config_set_fifo_join(&pc, PIO_FIFO_JOIN_TX);
|
||||||
|
|
||||||
pio_sm_set_consecutive_pindirs(pio1, 0, out_pin, 1, GPIO_OUT);
|
|
||||||
|
|
||||||
pio_sm_set_enabled(pio1, 0, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void watch_init(int in_pin)
|
|
||||||
{
|
|
||||||
const uint16_t insn[] = {
|
|
||||||
pio_encode_in(pio_pins, 1),
|
|
||||||
};
|
|
||||||
|
|
||||||
pio_program_t prog = {
|
|
||||||
.instructions = insn,
|
|
||||||
.length = 1,
|
|
||||||
.origin = 6,
|
|
||||||
};
|
|
||||||
|
|
||||||
pio_sm_set_enabled(pio1, 1, false);
|
|
||||||
pio_sm_restart(pio1, 1);
|
|
||||||
pio_sm_clear_fifos(pio1, 1);
|
|
||||||
|
|
||||||
if (pio_can_add_program(pio1, &prog))
|
|
||||||
pio_add_program(pio1, &prog);
|
|
||||||
|
|
||||||
pio_sm_config pc = pio_get_default_sm_config();
|
|
||||||
sm_config_set_in_pins(&pc, in_pin);
|
|
||||||
sm_config_set_wrap(&pc, prog.origin, prog.origin + prog.length - 1);
|
|
||||||
sm_config_set_clkdiv_int_frac(&pc, 1, 0);
|
|
||||||
sm_config_set_fifo_join(&pc, PIO_FIFO_JOIN_RX);
|
|
||||||
sm_config_set_in_shift(&pc, false, true, 32);
|
|
||||||
pio_sm_init(pio1, 1, prog.origin, &pc);
|
|
||||||
|
|
||||||
pio_sm_set_enabled(pio1, 1, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void adder_init()
|
|
||||||
{
|
|
||||||
const uint16_t insn[] = {
|
|
||||||
pio_encode_jmp_y_dec(1),
|
|
||||||
pio_encode_out(pio_pc, 2),
|
|
||||||
pio_encode_out(pio_pc, 2),
|
|
||||||
pio_encode_jmp_x_dec(2),
|
|
||||||
|
|
||||||
/* Avoid Y-- on wrap. */
|
|
||||||
pio_encode_out(pio_pc, 2),
|
|
||||||
};
|
|
||||||
|
|
||||||
pio_program_t prog = {
|
|
||||||
.instructions = insn,
|
|
||||||
.length = sizeof(insn) / sizeof(*insn),
|
|
||||||
.origin = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
pio_sm_set_enabled(pio1, 2, false);
|
|
||||||
pio_sm_set_enabled(pio1, 3, false);
|
|
||||||
|
|
||||||
pio_sm_restart(pio1, 2);
|
|
||||||
pio_sm_restart(pio1, 3);
|
|
||||||
|
|
||||||
pio_sm_clear_fifos(pio1, 2);
|
|
||||||
pio_sm_clear_fifos(pio1, 3);
|
|
||||||
|
|
||||||
if (pio_can_add_program(pio1, &prog))
|
|
||||||
pio_add_program(pio1, &prog);
|
|
||||||
|
|
||||||
pio_sm_config pc = pio_get_default_sm_config();
|
|
||||||
sm_config_set_wrap(&pc, prog.origin, prog.origin + prog.length - 1);
|
|
||||||
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);
|
sm_config_set_out_shift(&pc, false, true, 32);
|
||||||
pio_sm_init(pio1, 2, prog.origin + prog.length - 1, &pc);
|
pio_sm_init(PIO, SM_LO, origin_lo, &pc);
|
||||||
pio_sm_init(pio1, 3, prog.origin + prog.length - 1, &pc);
|
|
||||||
|
|
||||||
pio_sm_set_enabled(pio1, 2, true);
|
pio_sm_set_consecutive_pindirs(PIO, SM_LO, LO_PIN, 1, GPIO_IN);
|
||||||
pio_sm_set_enabled(pio1, 3, true);
|
pio_sm_exec_wait_blocking(PIO, SM_LO, pio_encode_set(pio_pins, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#define STEP_BASE ((UINT_MAX + 1.0) / CLK_SYS_HZ)
|
inline static uint32_t phase_bit(uint32_t phase, uint32_t step)
|
||||||
static uint32_t freq_step = 1;
|
|
||||||
|
|
||||||
static void lo_generate(uint32_t *buf, double freq, uint32_t phase)
|
|
||||||
{
|
{
|
||||||
freq_step = STEP_BASE * freq;
|
uint32_t next = phase + step;
|
||||||
|
|
||||||
unsigned down = 2 + __builtin_clz(freq_step);
|
if ((next & 0x7fffffff) > (step >> 1))
|
||||||
|
return next >> 31;
|
||||||
|
|
||||||
for (size_t i = 0; i < LO_WORDS; i++) {
|
return phase >> 31;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nco_generate_phase(uint32_t *buf, size_t len, uint32_t step, uint32_t phase)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
uint32_t bits = 0;
|
uint32_t bits = 0;
|
||||||
int shift = (rnd_next() >> down) - (rnd_next() >> down);
|
|
||||||
|
|
||||||
for (int j = 0; j < 32; j++) {
|
for (int j = 0; j < 32; j++) {
|
||||||
bits |= (phase + shift) >> 31;
|
bits |= phase_bit(phase, step);
|
||||||
bits <<= 1;
|
bits <<= 1;
|
||||||
phase += freq_step;
|
phase += step;
|
||||||
}
|
}
|
||||||
|
|
||||||
buf[i] = bits;
|
buf[i] = bits;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lo_tweak(uint32_t *buf, uint32_t phase)
|
static void rx_lo_init(double freq)
|
||||||
{
|
{
|
||||||
static size_t i = 0;
|
uint32_t step = STEP_BASE * freq;
|
||||||
uint32_t bits = 0;
|
|
||||||
unsigned down = 2 + __builtin_clz(freq_step);
|
|
||||||
|
|
||||||
phase += freq_step * i * 32;
|
for (uint32_t i = 0; i < NCO_NUM_PHASES; i++)
|
||||||
|
nco_generate_phase(nco_phase[i], NCO_PHASE_WORDS, step,
|
||||||
|
i << (__builtin_clz(NCO_NUM_PHASES) + 1));
|
||||||
|
|
||||||
for (int j = 0; j < 32; j++) {
|
nco_step = step * 32 * NCO_PHASE_WORDS;
|
||||||
int shift = (rnd_next() >> down) - (rnd_next() >> down);
|
|
||||||
bits |= (phase + shift) >> 31;
|
|
||||||
bits <<= 1;
|
|
||||||
phase += freq_step;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf[i] = bits;
|
|
||||||
i = (i + 1) & (LO_WORDS - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rx_lo_init(double req_freq, bool align)
|
static void rf_rx_start()
|
||||||
{
|
{
|
||||||
const double step_hz = (double)CLK_SYS_HZ / (8 << LO_BITS_DEPTH);
|
dma_ch_nco1 = dma_claim_unused_channel(true);
|
||||||
double freq = req_freq;
|
dma_ch_nco2 = dma_claim_unused_channel(true);
|
||||||
|
dma_ch_nco3 = dma_claim_unused_channel(true);
|
||||||
if (align)
|
dma_ch_nco4 = dma_claim_unused_channel(true);
|
||||||
freq = round(freq / step_hz) * step_hz;
|
dma_ch_mix = dma_claim_unused_channel(true);
|
||||||
|
|
||||||
lo_generate(lo_cos, freq, COS_PHASE);
|
|
||||||
lo_generate(lo_sin, freq, SIN_PHASE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const uint32_t samp_insn[4] __attribute__((__aligned__(16)));
|
|
||||||
static const uint32_t samp_insn[4] = {
|
|
||||||
0x4040, /* IN Y, 32 */
|
|
||||||
0x4020, /* IN X, 32 */
|
|
||||||
0xe040, /* SET Y, 0 */
|
|
||||||
0xe020, /* SET X, 0 */
|
|
||||||
};
|
|
||||||
|
|
||||||
static uint32_t null, one = 1;
|
|
||||||
|
|
||||||
static void rf_rx_start(int rx_pin, int bias_pin)
|
|
||||||
{
|
|
||||||
dma_ch_rx = dma_claim_unused_channel(true);
|
|
||||||
dma_ch_cp = dma_claim_unused_channel(true);
|
|
||||||
dma_ch_cos = dma_claim_unused_channel(true);
|
|
||||||
dma_ch_sin = 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_ch_samp_trig = dma_claim_unused_channel(true);
|
|
||||||
|
|
||||||
dma_t_samp = dma_claim_unused_timer(true);
|
|
||||||
|
|
||||||
dma_channel_config dma_conf;
|
dma_channel_config dma_conf;
|
||||||
|
|
||||||
/* Read received word into accumulator I. */
|
/* Step the NCO. */
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_rx);
|
dma_conf = dma_channel_get_default_config(dma_ch_nco1);
|
||||||
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(pio1, 1, false));
|
channel_config_set_chain_to(&dma_conf, dma_ch_nco2);
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_cp);
|
dma_channel_configure(dma_ch_nco1, &dma_conf, &nco_null, &nco_step, 1, false);
|
||||||
dma_channel_configure(dma_ch_rx, &dma_conf, LO_COS_ACCUMULATOR, &pio1->rxf[1], 1, false);
|
|
||||||
|
|
||||||
/* Copy accumulator I to accumulator Q. */
|
/* DMA above will increment the phase accumulator. */
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_cp);
|
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_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_cos);
|
channel_config_set_chain_to(&dma_conf, dma_ch_nco4);
|
||||||
dma_channel_configure(dma_ch_cp, &dma_conf, LO_SIN_ACCUMULATOR, LO_COS_ACCUMULATOR, 1,
|
dma_channel_configure(dma_ch_nco3, &dma_conf, &dma_hw->ch[dma_ch_mix].read_addr, &nco_addr,
|
||||||
false);
|
1, false);
|
||||||
|
|
||||||
/* Read lo_cos into accumulator I with XOR. */
|
/* Trigger LO by clearing the bottom bits. */
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_cos);
|
dma_conf = dma_channel_get_default_config(dma_ch_nco4);
|
||||||
|
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_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], &nco_phase[0][0],
|
||||||
false);
|
NCO_PHASE_WORDS, false);
|
||||||
|
|
||||||
/* Read lo_sin into accumulator Q with XOR. */
|
init_lo();
|
||||||
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_channel_start(dma_ch_nco1);
|
||||||
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(pio1, 2, true));
|
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_pio_sin);
|
|
||||||
dma_channel_configure(dma_ch_pio_cos, &dma_conf, &pio1->txf[2], LO_COS_ACCUMULATOR, 1,
|
|
||||||
false);
|
|
||||||
|
|
||||||
/* Copy mixed Q accumulator to PIO adder Q. */
|
pio_sm_set_enabled(PIO, SM_LO, true);
|
||||||
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(pio1, 3, true));
|
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_rx);
|
|
||||||
dma_channel_configure(dma_ch_pio_sin, &dma_conf, &pio1->txf[3], LO_SIN_ACCUMULATOR, 1,
|
|
||||||
false);
|
|
||||||
|
|
||||||
/* Pacing timer for the sampling script trigger channel. */
|
|
||||||
dma_timer_set_fraction(dma_t_samp, 1, CLK_SYS_HZ / (sample_rate * DECIMATE));
|
|
||||||
|
|
||||||
/* Sampling trigger channel. */
|
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_samp_trig);
|
|
||||||
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_cos);
|
|
||||||
dma_channel_configure(dma_ch_samp_trig, &dma_conf, &null, &one, 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, true);
|
|
||||||
channel_config_set_write_increment(&dma_conf, false);
|
|
||||||
channel_config_set_ring(&dma_conf, false, 4);
|
|
||||||
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, &pio1->sm[2].instr, samp_insn, 4, 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, true);
|
|
||||||
channel_config_set_write_increment(&dma_conf, false);
|
|
||||||
channel_config_set_ring(&dma_conf, false, 4);
|
|
||||||
channel_config_set_high_priority(&dma_conf, true);
|
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_samp_trig);
|
|
||||||
dma_channel_configure(dma_ch_samp_sin, &dma_conf, &pio1->sm[3].instr, samp_insn, 4, false);
|
|
||||||
|
|
||||||
bias_init(rx_pin, bias_pin);
|
|
||||||
adder_init();
|
|
||||||
|
|
||||||
dma_channel_start(dma_ch_rx);
|
|
||||||
dma_channel_start(dma_ch_samp_trig);
|
|
||||||
watch_init(rx_pin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rf_rx_stop(void)
|
static void rf_rx_stop(void)
|
||||||
{
|
{
|
||||||
pio_sm_set_enabled(pio1, 0, false);
|
pio_sm_set_enabled(PIO, SM_LO, false);
|
||||||
pio_sm_set_enabled(pio1, 1, false);
|
|
||||||
pio_sm_set_enabled(pio1, 2, false);
|
|
||||||
pio_sm_set_enabled(pio1, 3, false);
|
|
||||||
|
|
||||||
pio_sm_restart(pio1, 0);
|
|
||||||
pio_sm_restart(pio1, 1);
|
|
||||||
pio_sm_restart(pio1, 2);
|
|
||||||
pio_sm_restart(pio1, 3);
|
|
||||||
|
|
||||||
pio_sm_clear_fifos(pio1, 0);
|
|
||||||
pio_sm_clear_fifos(pio1, 1);
|
|
||||||
pio_sm_clear_fifos(pio1, 2);
|
|
||||||
pio_sm_clear_fifos(pio1, 3);
|
|
||||||
|
|
||||||
sleep_us(10);
|
sleep_us(10);
|
||||||
|
|
||||||
dma_channel_abort(dma_ch_rx);
|
dma_channel_clear_chain_to(dma_ch_nco1);
|
||||||
dma_channel_abort(dma_ch_cp);
|
dma_channel_clear_chain_to(dma_ch_nco2);
|
||||||
dma_channel_abort(dma_ch_cos);
|
dma_channel_clear_chain_to(dma_ch_nco3);
|
||||||
dma_channel_abort(dma_ch_sin);
|
dma_channel_clear_chain_to(dma_ch_nco4);
|
||||||
dma_channel_abort(dma_ch_pio_cos);
|
dma_channel_clear_chain_to(dma_ch_mix);
|
||||||
dma_channel_abort(dma_ch_pio_sin);
|
|
||||||
dma_channel_abort(dma_ch_samp_cos);
|
|
||||||
dma_channel_abort(dma_ch_samp_sin);
|
|
||||||
dma_channel_abort(dma_ch_samp_trig);
|
|
||||||
|
|
||||||
dma_channel_cleanup(dma_ch_rx);
|
dma_channel_abort(dma_ch_nco1);
|
||||||
dma_channel_cleanup(dma_ch_cp);
|
dma_channel_abort(dma_ch_nco2);
|
||||||
dma_channel_cleanup(dma_ch_cos);
|
dma_channel_abort(dma_ch_nco3);
|
||||||
dma_channel_cleanup(dma_ch_sin);
|
dma_channel_abort(dma_ch_nco4);
|
||||||
dma_channel_cleanup(dma_ch_pio_cos);
|
dma_channel_abort(dma_ch_mix);
|
||||||
dma_channel_cleanup(dma_ch_pio_sin);
|
|
||||||
dma_channel_cleanup(dma_ch_samp_cos);
|
|
||||||
dma_channel_cleanup(dma_ch_samp_sin);
|
|
||||||
dma_channel_cleanup(dma_ch_samp_trig);
|
|
||||||
|
|
||||||
dma_channel_unclaim(dma_ch_rx);
|
dma_channel_cleanup(dma_ch_nco1);
|
||||||
dma_channel_unclaim(dma_ch_cp);
|
dma_channel_cleanup(dma_ch_nco2);
|
||||||
dma_channel_unclaim(dma_ch_cos);
|
dma_channel_cleanup(dma_ch_nco3);
|
||||||
dma_channel_unclaim(dma_ch_sin);
|
dma_channel_cleanup(dma_ch_nco4);
|
||||||
dma_channel_unclaim(dma_ch_pio_cos);
|
dma_channel_cleanup(dma_ch_mix);
|
||||||
dma_channel_unclaim(dma_ch_pio_sin);
|
|
||||||
dma_channel_unclaim(dma_ch_samp_cos);
|
|
||||||
dma_channel_unclaim(dma_ch_samp_sin);
|
|
||||||
dma_channel_unclaim(dma_ch_samp_trig);
|
|
||||||
|
|
||||||
dma_timer_unclaim(dma_t_samp);
|
dma_channel_unclaim(dma_ch_nco1);
|
||||||
|
dma_channel_unclaim(dma_ch_nco2);
|
||||||
|
dma_channel_unclaim(dma_ch_nco3);
|
||||||
|
dma_channel_unclaim(dma_ch_nco4);
|
||||||
|
dma_channel_unclaim(dma_ch_mix);
|
||||||
|
|
||||||
dma_ch_rx = -1;
|
dma_ch_nco1 = -1;
|
||||||
dma_ch_cp = -1;
|
dma_ch_nco2 = -1;
|
||||||
dma_ch_cos = -1;
|
dma_ch_nco3 = -1;
|
||||||
dma_ch_sin = -1;
|
dma_ch_nco4 = -1;
|
||||||
dma_ch_pio_cos = -1;
|
dma_ch_mix = -1;
|
||||||
dma_ch_pio_sin = -1;
|
}
|
||||||
dma_ch_samp_cos = -1;
|
|
||||||
dma_ch_samp_sin = -1;
|
|
||||||
dma_ch_samp_trig = -1;
|
|
||||||
|
|
||||||
dma_t_samp = -1;
|
struct IQ {
|
||||||
|
int I, Q;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline static int nextQ(void)
|
||||||
|
{
|
||||||
|
static int x4, x3, x2, x1;
|
||||||
|
|
||||||
|
int x0 = gain * adc_fifo_get_blocking();
|
||||||
|
|
||||||
|
int x = x2 + x2 - x4 - x0;
|
||||||
|
x4 = x3;
|
||||||
|
x3 = x2;
|
||||||
|
x2 = x1;
|
||||||
|
x1 = x0;
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static struct IQ next_sample()
|
||||||
|
{
|
||||||
|
int I = 0, Q = 0;
|
||||||
|
|
||||||
|
I += nextQ();
|
||||||
|
Q += nextQ();
|
||||||
|
I -= nextQ();
|
||||||
|
Q -= nextQ();
|
||||||
|
|
||||||
|
static int dcI, dcQ;
|
||||||
|
|
||||||
|
I = ((I << 12) - dcI) >> 12;
|
||||||
|
dcI += I;
|
||||||
|
|
||||||
|
Q = ((Q << 12) - dcQ) >> 12;
|
||||||
|
dcQ += Q;
|
||||||
|
|
||||||
|
I += 127.4 * 512;
|
||||||
|
I /= 512;
|
||||||
|
|
||||||
|
Q += 127.4 * 512;
|
||||||
|
Q /= 512;
|
||||||
|
|
||||||
|
return (struct IQ){ I, Q };
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rf_rx(void)
|
static void rf_rx(void)
|
||||||
{
|
{
|
||||||
const uint32_t base = (uint32_t)rx_cos;
|
|
||||||
int pos = 0;
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (multicore_fifo_rvalid()) {
|
if (multicore_fifo_rvalid()) {
|
||||||
multicore_fifo_pop_blocking();
|
multicore_fifo_pop_blocking();
|
||||||
|
@ -488,111 +325,32 @@ static void rf_rx(void)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int head = (dma_hw->ch[dma_ch_in_cos].write_addr - base) / 4;
|
|
||||||
int delta = (head < pos ? head + RX_WORDS : head) - pos;
|
|
||||||
|
|
||||||
while (delta < RX_STRIDE) {
|
|
||||||
sleep_us(1);
|
|
||||||
head = (dma_hw->ch[dma_ch_in_cos].write_addr - base) / 4;
|
|
||||||
delta = (head < pos ? head + RX_WORDS : head) - pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t *cos_ptr = rx_cos + pos;
|
|
||||||
const uint32_t *sin_ptr = rx_sin + pos;
|
|
||||||
|
|
||||||
pos = (pos + RX_STRIDE) & (RX_WORDS - 1);
|
|
||||||
|
|
||||||
uint8_t *block = iq_queue_buffer[iq_queue_pos];
|
uint8_t *block = iq_queue_buffer[iq_queue_pos];
|
||||||
uint8_t *blockptr = block;
|
uint8_t *blockptr = block;
|
||||||
|
|
||||||
/*
|
|
||||||
* Since every 2 samples add to either +1 or -1,
|
|
||||||
* the maximum amplitude in one direction is 1/2.
|
|
||||||
*/
|
|
||||||
int64_t max_amplitude = CLK_SYS_HZ / 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Since the waveform is normally half of the time
|
|
||||||
* above zero, we can halve once more.
|
|
||||||
*
|
|
||||||
* This is not perfect, so we do not max out the base
|
|
||||||
* gain but keep it slightly below the maximum to make
|
|
||||||
* sure we do not overshoot often.
|
|
||||||
*/
|
|
||||||
max_amplitude /= 2;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We are allowing the counters to only go as high
|
|
||||||
* as sampling rate.
|
|
||||||
*/
|
|
||||||
max_amplitude /= sample_rate;
|
|
||||||
|
|
||||||
for (int i = 0; i < IQ_SAMPLES; i++) {
|
for (int i = 0; i < IQ_SAMPLES; i++) {
|
||||||
int sI = 0, sQ = 0;
|
struct IQ IQ = next_sample();
|
||||||
|
int64_t I = IQ.I;
|
||||||
|
int64_t Q = IQ.Q;
|
||||||
|
|
||||||
/*
|
if (I < 0)
|
||||||
* I: +I1 -I3 +Q2 -Q4
|
I = 0;
|
||||||
* Q: +Q1 -Q3 -I2 +I4
|
else if (I > 255)
|
||||||
*/
|
I = 255;
|
||||||
sI += *cos_ptr++;
|
|
||||||
sI -= *cos_ptr++;
|
|
||||||
|
|
||||||
sQ -= *cos_ptr++;
|
*blockptr++ = I;
|
||||||
sQ += *cos_ptr++;
|
|
||||||
|
|
||||||
sI -= *cos_ptr++;
|
if (Q < 0)
|
||||||
sI += *cos_ptr++;
|
Q = 0;
|
||||||
|
else if (Q > 255)
|
||||||
|
Q = 255;
|
||||||
|
|
||||||
sQ += *cos_ptr++;
|
*blockptr++ = Q;
|
||||||
sQ -= *cos_ptr++;
|
|
||||||
|
|
||||||
sQ += *sin_ptr++;
|
|
||||||
sQ -= *sin_ptr++;
|
|
||||||
|
|
||||||
sI += *sin_ptr++;
|
|
||||||
sI -= *sin_ptr++;
|
|
||||||
|
|
||||||
sQ -= *sin_ptr++;
|
|
||||||
sQ += *sin_ptr++;
|
|
||||||
|
|
||||||
sI -= *sin_ptr++;
|
|
||||||
sI += *sin_ptr++;
|
|
||||||
|
|
||||||
int64_t I = sI;
|
|
||||||
int64_t Q = sQ;
|
|
||||||
|
|
||||||
I *= 127;
|
|
||||||
I -= (max_amplitude * 181) / 256;
|
|
||||||
I /= max_amplitude;
|
|
||||||
|
|
||||||
if (I > 127)
|
|
||||||
I = 127;
|
|
||||||
else if (I < -128)
|
|
||||||
I = -128;
|
|
||||||
|
|
||||||
*blockptr++ = (uint8_t)I + 128;
|
|
||||||
|
|
||||||
Q *= 127;
|
|
||||||
Q -= (max_amplitude * 181) / 256;
|
|
||||||
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)) {
|
||||||
iq_queue_pos = (iq_queue_pos + 1) & (IQ_QUEUE_LEN - 1);
|
iq_queue_pos = (iq_queue_pos + 1) & (IQ_QUEUE_LEN - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Randomize LO phase in the next word. */
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
lo_tweak(lo_cos, COS_PHASE);
|
|
||||||
lo_tweak(lo_sin, SIN_PHASE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,18 +358,25 @@ 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 */
|
||||||
rx_lo_init(arg - sample_rate, true);
|
frequency = arg;
|
||||||
|
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 */
|
||||||
|
if (arg > (ADC_RATE / DECIMATE))
|
||||||
|
arg = ADC_RATE / DECIMATE;
|
||||||
|
|
||||||
sample_rate = arg;
|
sample_rate = arg;
|
||||||
dma_timer_set_fraction(dma_t_samp, 1, CLK_SYS_HZ / (sample_rate * DECIMATE));
|
adc_set_clkdiv(96.0f * ((float)ADC_RATE / (sample_rate * DECIMATE)) - 1.0f);
|
||||||
rx_lo_init(arg - sample_rate, true);
|
|
||||||
} else if (0x04 == cmd) {
|
} else if (0x04 == cmd) {
|
||||||
/* Set the tuner gain level */
|
/* Set the tuner gain level */
|
||||||
bias_set_gain((arg + 14) / 30);
|
gain = INIT_GAIN * powf(10.0f, arg / 200.0f);
|
||||||
} 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 */
|
||||||
bias_set_gain((gains[arg] + 14) / 30);
|
|
||||||
|
if (arg >= NUM_GAINS)
|
||||||
|
arg = NUM_GAINS - 1;
|
||||||
|
|
||||||
|
gain = INIT_GAIN * powf(10.0f, gains[arg] / 200.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,34 +404,11 @@ static int check_command(void)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void do_rx(int rx_pin, int bias_pin)
|
static void do_rx()
|
||||||
{
|
{
|
||||||
rf_rx_start(rx_pin, bias_pin);
|
rf_rx_start();
|
||||||
sleep_us(100);
|
sleep_us(100);
|
||||||
|
|
||||||
dma_ch_in_cos = dma_claim_unused_channel(true);
|
|
||||||
dma_ch_in_sin = 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, true, RX_BITS_DEPTH);
|
|
||||||
channel_config_set_dreq(&dma_conf, pio_get_dreq(pio1, 2, false));
|
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_in_sin);
|
|
||||||
dma_channel_configure(dma_ch_in_cos, &dma_conf, rx_cos, &pio1->rxf[2], 2, false);
|
|
||||||
|
|
||||||
dma_conf = dma_channel_get_default_config(dma_ch_in_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, true);
|
|
||||||
channel_config_set_ring(&dma_conf, true, RX_BITS_DEPTH);
|
|
||||||
channel_config_set_dreq(&dma_conf, pio_get_dreq(pio1, 3, false));
|
|
||||||
channel_config_set_chain_to(&dma_conf, dma_ch_in_cos);
|
|
||||||
dma_channel_configure(dma_ch_in_sin, &dma_conf, rx_sin, &pio1->rxf[3], 2, true);
|
|
||||||
|
|
||||||
multicore_launch_core1(rf_rx);
|
multicore_launch_core1(rf_rx);
|
||||||
|
|
||||||
const uint8_t *block;
|
const uint8_t *block;
|
||||||
|
@ -684,6 +426,11 @@ static void do_rx(int rx_pin, int bias_pin)
|
||||||
if (queue_try_remove(&iq_queue, &block)) {
|
if (queue_try_remove(&iq_queue, &block)) {
|
||||||
fwrite(block, IQ_BLOCK_LEN, 1, stdout);
|
fwrite(block, IQ_BLOCK_LEN, 1, stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
} else {
|
||||||
|
int wait = rnd_next() & 0x1fff;
|
||||||
|
|
||||||
|
for (int i = 0; i < wait; i++)
|
||||||
|
asm volatile("nop");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -694,21 +441,24 @@ done:
|
||||||
multicore_reset_core1();
|
multicore_reset_core1();
|
||||||
|
|
||||||
rf_rx_stop();
|
rf_rx_stop();
|
||||||
|
|
||||||
dma_channel_abort(dma_ch_in_cos);
|
|
||||||
dma_channel_abort(dma_ch_in_sin);
|
|
||||||
dma_channel_cleanup(dma_ch_in_cos);
|
|
||||||
dma_channel_cleanup(dma_ch_in_sin);
|
|
||||||
dma_channel_unclaim(dma_ch_in_cos);
|
|
||||||
dma_channel_unclaim(dma_ch_in_sin);
|
|
||||||
dma_ch_in_cos = -1;
|
|
||||||
dma_ch_in_sin = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
@ -724,7 +474,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(INIT_FREQ - INIT_SAMPLE_RATE, true);
|
/* Init ADC */
|
||||||
|
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);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (check_command() > 0) {
|
if (check_command() > 0) {
|
||||||
|
@ -734,7 +493,7 @@ int main()
|
||||||
fwrite(header, sizeof header, 1, stdout);
|
fwrite(header, sizeof header, 1, stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
do_rx(10, 11);
|
do_rx();
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep_ms(10);
|
sleep_ms(10);
|
||||||
|
|
2
src/vendor/pico-stdio-usb-simple
vendored
2
src/vendor/pico-stdio-usb-simple
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit b6b09e34b844326a156bc6734146a88111138473
|
Subproject commit ed4858dda407ec66626aade9b7c6ad6016539f4c
|
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