From af2ce901ebede99aec61f4efada55ec882d5f036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Thu, 6 Jun 2024 11:24:31 +0200 Subject: [PATCH 01/10] Convert into rtl_tcp emulator --- grc/PicoSDR-WBFM.grc | 384 ++++++++++++++++++-- src/main.c | 807 ++++++++----------------------------------- util/bridge.py | 75 ++-- 3 files changed, 537 insertions(+), 729 deletions(-) diff --git a/grc/PicoSDR-WBFM.grc b/grc/PicoSDR-WBFM.grc index 1579c71..ae58040 100644 --- a/grc/PicoSDR-WBFM.grc +++ b/grc/PicoSDR-WBFM.grc @@ -116,24 +116,6 @@ blocks: coordinate: [704, 384.0] rotation: 0 state: true -- name: blocks_interleaved_short_to_complex_0 - id: blocks_interleaved_short_to_complex - parameters: - affinity: '' - alias: '' - comment: '' - maxoutbuf: '0' - minoutbuf: '0' - scale_factor: (1 << 15) - 1 - swap: 'False' - vector_input: 'False' - states: - bus_sink: false - bus_source: false - bus_structure: null - coordinate: [224, 208.0] - rotation: 0 - state: true - name: blocks_message_debug_0 id: blocks_message_debug parameters: @@ -206,26 +188,363 @@ blocks: coordinate: [992, 284.0] rotation: 0 state: enabled -- name: network_tcp_source_0 - id: network_tcp_source +- name: osmosdr_source_0 + id: osmosdr_source parameters: - addr: 127.0.0.1 affinity: '' alias: '' + ant0: '' + ant1: '' + ant10: '' + ant11: '' + ant12: '' + ant13: '' + ant14: '' + ant15: '' + ant16: '' + ant17: '' + ant18: '' + ant19: '' + ant2: '' + ant20: '' + ant21: '' + ant22: '' + ant23: '' + ant24: '' + ant25: '' + ant26: '' + ant27: '' + ant28: '' + ant29: '' + ant3: '' + ant30: '' + ant31: '' + ant4: '' + ant5: '' + ant6: '' + ant7: '' + ant8: '' + ant9: '' + args: '"rtl_tcp"' + bb_gain0: '20' + bb_gain1: '20' + bb_gain10: '20' + bb_gain11: '20' + bb_gain12: '20' + bb_gain13: '20' + bb_gain14: '20' + bb_gain15: '20' + bb_gain16: '20' + bb_gain17: '20' + bb_gain18: '20' + bb_gain19: '20' + bb_gain2: '20' + bb_gain20: '20' + bb_gain21: '20' + bb_gain22: '20' + bb_gain23: '20' + bb_gain24: '20' + bb_gain25: '20' + bb_gain26: '20' + bb_gain27: '20' + bb_gain28: '20' + bb_gain29: '20' + bb_gain3: '20' + bb_gain30: '20' + bb_gain31: '20' + bb_gain4: '20' + bb_gain5: '20' + bb_gain6: '20' + bb_gain7: '20' + bb_gain8: '20' + bb_gain9: '20' + bw0: '0' + bw1: '0' + bw10: '0' + bw11: '0' + bw12: '0' + bw13: '0' + bw14: '0' + bw15: '0' + bw16: '0' + bw17: '0' + bw18: '0' + bw19: '0' + bw2: '0' + bw20: '0' + bw21: '0' + bw22: '0' + bw23: '0' + bw24: '0' + bw25: '0' + bw26: '0' + bw27: '0' + bw28: '0' + bw29: '0' + bw3: '0' + bw30: '0' + bw31: '0' + bw4: '0' + bw5: '0' + bw6: '0' + bw7: '0' + bw8: '0' + bw9: '0' + clock_source0: '' + clock_source1: '' + clock_source2: '' + clock_source3: '' + clock_source4: '' + clock_source5: '' + clock_source6: '' + clock_source7: '' comment: '' + corr0: '0' + corr1: '0' + corr10: '0' + corr11: '0' + corr12: '0' + corr13: '0' + corr14: '0' + corr15: '0' + corr16: '0' + corr17: '0' + corr18: '0' + corr19: '0' + corr2: '0' + corr20: '0' + corr21: '0' + corr22: '0' + corr23: '0' + corr24: '0' + corr25: '0' + corr26: '0' + corr27: '0' + corr28: '0' + corr29: '0' + corr3: '0' + corr30: '0' + corr31: '0' + corr4: '0' + corr5: '0' + corr6: '0' + corr7: '0' + corr8: '0' + corr9: '0' + dc_offset_mode0: '0' + dc_offset_mode1: '0' + dc_offset_mode10: '0' + dc_offset_mode11: '0' + dc_offset_mode12: '0' + dc_offset_mode13: '0' + dc_offset_mode14: '0' + dc_offset_mode15: '0' + dc_offset_mode16: '0' + dc_offset_mode17: '0' + dc_offset_mode18: '0' + dc_offset_mode19: '0' + dc_offset_mode2: '0' + dc_offset_mode20: '0' + dc_offset_mode21: '0' + dc_offset_mode22: '0' + dc_offset_mode23: '0' + dc_offset_mode24: '0' + dc_offset_mode25: '0' + dc_offset_mode26: '0' + dc_offset_mode27: '0' + dc_offset_mode28: '0' + dc_offset_mode29: '0' + dc_offset_mode3: '0' + dc_offset_mode30: '0' + dc_offset_mode31: '0' + dc_offset_mode4: '0' + dc_offset_mode5: '0' + dc_offset_mode6: '0' + dc_offset_mode7: '0' + dc_offset_mode8: '0' + dc_offset_mode9: '0' + freq0: '88_200_000' + freq1: 100e6 + freq10: 100e6 + freq11: 100e6 + freq12: 100e6 + freq13: 100e6 + freq14: 100e6 + freq15: 100e6 + freq16: 100e6 + freq17: 100e6 + freq18: 100e6 + freq19: 100e6 + freq2: 100e6 + freq20: 100e6 + freq21: 100e6 + freq22: 100e6 + freq23: 100e6 + freq24: 100e6 + freq25: 100e6 + freq26: 100e6 + freq27: 100e6 + freq28: 100e6 + freq29: 100e6 + freq3: 100e6 + freq30: 100e6 + freq31: 100e6 + freq4: 100e6 + freq5: 100e6 + freq6: 100e6 + freq7: 100e6 + freq8: 100e6 + freq9: 100e6 + gain0: '10' + gain1: '10' + gain10: '10' + gain11: '10' + gain12: '10' + gain13: '10' + gain14: '10' + gain15: '10' + gain16: '10' + gain17: '10' + gain18: '10' + gain19: '10' + gain2: '10' + gain20: '10' + gain21: '10' + gain22: '10' + gain23: '10' + gain24: '10' + gain25: '10' + gain26: '10' + gain27: '10' + gain28: '10' + gain29: '10' + gain3: '10' + gain30: '10' + gain31: '10' + gain4: '10' + gain5: '10' + gain6: '10' + gain7: '10' + gain8: '10' + gain9: '10' + gain_mode0: 'False' + gain_mode1: 'False' + gain_mode10: 'False' + gain_mode11: 'False' + gain_mode12: 'False' + gain_mode13: 'False' + gain_mode14: 'False' + gain_mode15: 'False' + gain_mode16: 'False' + gain_mode17: 'False' + gain_mode18: 'False' + gain_mode19: 'False' + gain_mode2: 'False' + gain_mode20: 'False' + gain_mode21: 'False' + gain_mode22: 'False' + gain_mode23: 'False' + gain_mode24: 'False' + gain_mode25: 'False' + gain_mode26: 'False' + gain_mode27: 'False' + gain_mode28: 'False' + gain_mode29: 'False' + gain_mode3: 'False' + gain_mode30: 'False' + gain_mode31: 'False' + gain_mode4: 'False' + gain_mode5: 'False' + gain_mode6: 'False' + gain_mode7: 'False' + gain_mode8: 'False' + gain_mode9: 'False' + if_gain0: '20' + if_gain1: '20' + if_gain10: '20' + if_gain11: '20' + if_gain12: '20' + if_gain13: '20' + if_gain14: '20' + if_gain15: '20' + if_gain16: '20' + if_gain17: '20' + if_gain18: '20' + if_gain19: '20' + if_gain2: '20' + if_gain20: '20' + if_gain21: '20' + if_gain22: '20' + if_gain23: '20' + if_gain24: '20' + if_gain25: '20' + if_gain26: '20' + if_gain27: '20' + if_gain28: '20' + if_gain29: '20' + if_gain3: '20' + if_gain30: '20' + if_gain31: '20' + if_gain4: '20' + if_gain5: '20' + if_gain6: '20' + if_gain7: '20' + if_gain8: '20' + if_gain9: '20' + iq_balance_mode0: '0' + iq_balance_mode1: '0' + iq_balance_mode10: '0' + iq_balance_mode11: '0' + iq_balance_mode12: '0' + iq_balance_mode13: '0' + iq_balance_mode14: '0' + iq_balance_mode15: '0' + iq_balance_mode16: '0' + iq_balance_mode17: '0' + iq_balance_mode18: '0' + iq_balance_mode19: '0' + iq_balance_mode2: '0' + iq_balance_mode20: '0' + iq_balance_mode21: '0' + iq_balance_mode22: '0' + iq_balance_mode23: '0' + iq_balance_mode24: '0' + iq_balance_mode25: '0' + iq_balance_mode26: '0' + iq_balance_mode27: '0' + iq_balance_mode28: '0' + iq_balance_mode29: '0' + iq_balance_mode3: '0' + iq_balance_mode30: '0' + iq_balance_mode31: '0' + iq_balance_mode4: '0' + iq_balance_mode5: '0' + iq_balance_mode6: '0' + iq_balance_mode7: '0' + iq_balance_mode8: '0' + iq_balance_mode9: '0' maxoutbuf: '0' minoutbuf: '0' - port: '1234' - server: 'True' - type: short - vlen: '1' + nchan: '1' + num_mboards: '1' + sample_rate: samp_rate + sync: sync + time_source0: '' + time_source1: '' + time_source2: '' + time_source3: '' + time_source4: '' + time_source5: '' + time_source6: '' + time_source7: '' + type: fc32 states: bus_sink: false bus_source: false bus_structure: null - coordinate: [48, 200.0] + coordinate: [152, 164.0] rotation: 0 - state: true + state: enabled - name: qtgui_time_sink_x_0 id: qtgui_time_sink_x parameters: @@ -548,14 +867,13 @@ connections: - [analog_quadrature_demod_cf_0, '0', qtgui_waterfall_sink_x_0_0_0_0, '0'] - [analog_wfm_rcv_pll_0, '0', audio_sink_0, '0'] - [analog_wfm_rcv_pll_0, '1', audio_sink_0, '1'] -- [blocks_interleaved_short_to_complex_0, '0', analog_quadrature_demod_cf_0, '0'] -- [blocks_interleaved_short_to_complex_0, '0', analog_wfm_rcv_pll_0, '0'] -- [blocks_interleaved_short_to_complex_0, '0', blocks_probe_rate_0, '0'] -- [blocks_interleaved_short_to_complex_0, '0', qtgui_time_sink_x_0, '0'] -- [blocks_interleaved_short_to_complex_0, '0', qtgui_waterfall_sink_x_0_0, '0'] - [blocks_probe_rate_0, rate, blocks_message_debug_0, print] - [low_pass_filter_0, '0', qtgui_time_sink_x_0_0, '0'] -- [network_tcp_source_0, '0', blocks_interleaved_short_to_complex_0, '0'] +- [osmosdr_source_0, '0', analog_quadrature_demod_cf_0, '0'] +- [osmosdr_source_0, '0', analog_wfm_rcv_pll_0, '0'] +- [osmosdr_source_0, '0', blocks_probe_rate_0, '0'] +- [osmosdr_source_0, '0', qtgui_time_sink_x_0, '0'] +- [osmosdr_source_0, '0', qtgui_waterfall_sink_x_0_0, '0'] metadata: file_format: 1 diff --git a/src/main.c b/src/main.c index 3360a13..c2f5f63 100644 --- a/src/main.c +++ b/src/main.c @@ -21,52 +21,38 @@ #include #include -/* FM Radio */ -#if 1 #define VREG_VOLTAGE VREG_VOLTAGE_1_20 #define CLK_SYS_HZ (300 * MHZ) -#define BANDWIDTH 1536000 -#define DECIMATION_BITS 3 -#define LPF_ORDER 4 -#define AGC_DECAY_BITS 20 #define LO_DITHER 1 -#endif +#define PSU_PIN 23 -/* Digital Data */ -#if 0 -#define VREG_VOLTAGE VREG_VOLTAGE_DEFAULT -#define CLK_SYS_HZ (252 * MHZ) -#define BANDWIDTH 1280000 -#define DECIMATION_BITS 6 -#define LPF_ORDER 4 -#define AGC_DECAY_BITS 16 -#define LO_DITHER 1 -#endif - -#define IQ_BLOCK_LEN 32 -#define RX_SLEEP_US (DECIMATION * BANDWIDTH / (1 * MHZ) / 4) -#define DECIMATION (1 << DECIMATION_BITS) - -static_assert(RX_SLEEP_US > 0, "RX_SLEEP_US must be positive"); -static_assert(LPF_ORDER <= 4, "LPF_ORDER must be 0-4"); +#define IQ_SAMPLES 32 +#define IQ_BLOCK_LEN (2 * IQ_SAMPLES) #define XOR_ADDR 0x1000 #define LO_COS_ACCUMULATOR (&pio1->sm[2].pinctrl) #define LO_SIN_ACCUMULATOR (&pio1->sm[3].pinctrl) -#define LO_BITS_DEPTH 13 -#define LO_WORDS (1 << LO_BITS_DEPTH) -static uint32_t lo_cos[LO_WORDS] __attribute__((__aligned__(LO_WORDS * 4))); -static uint32_t lo_sin[LO_WORDS] __attribute__((__aligned__(LO_WORDS * 4))); +#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))); -#if DECIMATION_BITS >= 5 -#define RX_BITS_DEPTH (DECIMATION_BITS + 2) -#else -#define RX_BITS_DEPTH 7 -#endif -#define RX_WORDS (1 << RX_BITS_DEPTH) -static uint32_t rx_cos[RX_WORDS] __attribute__((__aligned__(RX_WORDS * 4))); -static uint32_t rx_sin[RX_WORDS] __attribute__((__aligned__(RX_WORDS * 4))); +#define RX_STRIDE (2 * IQ_SAMPLES) +#define RX_BITS_DEPTH 12 +#define RX_WORDS (1 << (RX_BITS_DEPTH - 2)) +static uint32_t rx_cos[RX_WORDS] __attribute__((__aligned__(1 << RX_BITS_DEPTH))); +static uint32_t rx_sin[RX_WORDS] __attribute__((__aligned__(1 << RX_BITS_DEPTH))); + +#define INIT_GAIN 256 +#define INIT_SAMPLE_RATE 100000 + +#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 gain = INIT_GAIN; +static int sample_rate = INIT_SAMPLE_RATE; #define SIN_PHASE (UINT_MAX / 4) #define COS_PHASE (0) @@ -88,13 +74,16 @@ static int dma_t_samp = -1; static int dma_ch_in_cos = -1; static int dma_ch_in_sin = -1; -static int dma_ch_tx_cos = -1; - static queue_t iq_queue; -static int gap = 0; -static int agc = 0; -#define PSU_PIN 23 +static uint32_t read_arg(void) +{ + uint32_t a = getchar_timeout_us(100); + uint32_t b = getchar_timeout_us(100); + uint32_t c = getchar_timeout_us(100); + uint32_t d = getchar_timeout_us(100); + return (a << 24) | (b << 16) | (c << 8) | d; +} static void bias_init(int in_pin, int out_pin) { @@ -110,12 +99,8 @@ static void bias_init(int in_pin, int out_pin) const uint16_t insn[] = { pio_encode_mov_not(pio_pins, pio_pins) | pio_encode_sideset(1, 1), - pio_encode_set(pio_x, 4) | pio_encode_sideset(1, 0) | pio_encode_delay(15), + pio_encode_set(pio_x, 31) | pio_encode_sideset(1, 0) | pio_encode_delay(15), pio_encode_jmp_x_dec(2) | pio_encode_sideset(1, 0) | pio_encode_delay(15), - - pio_encode_mov_not(pio_pins, pio_pins) | pio_encode_sideset(1, 1), - pio_encode_set(pio_x, 4) | pio_encode_sideset(1, 0) | pio_encode_delay(15), - pio_encode_jmp_x_dec(5) | pio_encode_sideset(1, 0) | pio_encode_delay(15), }; pio_program_t prog = { @@ -182,46 +167,6 @@ static void watch_init(int in_pin) pio_sm_set_enabled(pio1, 1, true); } -static void send_init(int out_pin) -{ - gpio_disable_pulls(out_pin); - pio_gpio_init(pio1, out_pin); - gpio_set_drive_strength(out_pin, GPIO_DRIVE_STRENGTH_2MA); - gpio_set_slew_rate(out_pin, GPIO_SLEW_RATE_SLOW); - - const uint16_t insn[] = { - pio_encode_out(pio_pins, 1) | pio_encode_sideset(1, 1), - }; - - pio_program_t prog = { - .instructions = insn, - .length = 1, - .origin = 5, - }; - - 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_sideset(&pc, 1, false, true); - sm_config_set_sideset_pins(&pc, out_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_fifo_join(&pc, PIO_FIFO_JOIN_TX); - sm_config_set_out_shift(&pc, false, true, 32); - pio_sm_init(pio1, 1, prog.origin, &pc); - - pio_sm_set_consecutive_pindirs(pio1, 1, out_pin, 1, GPIO_OUT); - - pio_sm_set_enabled(pio1, 1, true); -} - static void adder_init() { const uint16_t insn[] = { @@ -264,12 +209,6 @@ static void adder_init() pio_sm_set_enabled(pio1, 3, true); } -inline static float lo_round_freq(size_t bits, float req_freq) -{ - const double step_hz = (double)CLK_SYS_HZ / bits; - return round(req_freq / step_hz) * step_hz; -} - static void lo_generate(uint32_t *buf, size_t len, double freq, unsigned phase) { unsigned step = ((double)UINT_MAX + 1.0) / (double)CLK_SYS_HZ * freq; @@ -296,75 +235,26 @@ static void lo_generate(uint32_t *buf, size_t len, double freq, unsigned phase) } } -static float rx_lo_init(double req_freq) +static void rx_lo_init(double req_freq) { - float freq = lo_round_freq(LO_WORDS * 32, req_freq); + const double step_hz = (double)CLK_SYS_HZ / (4 << LO_BITS_DEPTH); + double freq = round(req_freq / step_hz) * step_hz; lo_generate(lo_cos, LO_WORDS, freq, COS_PHASE); lo_generate(lo_sin, LO_WORDS, freq, SIN_PHASE); - - return freq; } -static float tx_fsk_lo_init(float req_freq, float separation) -{ - float hi = lo_round_freq(LO_WORDS * 32, req_freq + separation / 2); - float lo = lo_round_freq(LO_WORDS * 32, hi - separation); - - lo_generate(lo_cos, LO_WORDS, hi, COS_PHASE); - lo_generate(lo_sin, LO_WORDS, lo, SIN_PHASE); - - return (hi + lo) / 2.0f; -} - -inline static __unused int cheap_atan2(int y, int x) -{ - if (y > 0) { - if (x > 0) { - if (y > x) - return 16 << 24; - return 0; - } else { - if (-x > y) - return 48 << 24; - return 32 << 24; - } - } else { - if (x < 0) { - if (y < x) - return 80 << 24; - return 64 << 24; - } else { - if (x > -y) - return 112 << 24; - return 96 << 24; - } - } -} - -inline static __unused int cheap_angle_diff(int angle1, int angle2) -{ - int diff = angle2 - angle1; - - if (diff > INT_MAX / 2) - return diff - INT_MAX; - - if (diff < INT_MIN / 2) - return diff + INT_MAX; - - return diff; -} - -static const uint32_t samp_insn[] __attribute__((__aligned__(16))) = { - 0x4020, /* IN X, 32 */ +static const uint32_t samp_insn[4] __attribute__((__aligned__(16))); +static const uint32_t samp_insn[4] = { 0x4040, /* IN Y, 32 */ - 0xe020, /* SET X, 0 */ + 0x4020, /* IN X, 32 */ 0xe040, /* SET Y, 0 */ + 0xe020, /* SET X, 0 */ }; static uint32_t null, one = 1; -static float rf_rx_start(int rx_pin, int bias_pin, float freq, int frac_num, int frac_denom) +static void rf_rx_start(int rx_pin, int bias_pin, double freq, int rate) { dma_ch_rx = dma_claim_unused_channel(true); dma_ch_cp = dma_claim_unused_channel(true); @@ -404,7 +294,7 @@ static float rf_rx_start(int rx_pin, int bias_pin, float freq, int frac_num, int 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 + 2); + channel_config_set_ring(&dma_conf, false, LO_BITS_DEPTH); channel_config_set_chain_to(&dma_conf, dma_ch_sin); dma_channel_configure(dma_ch_cos, &dma_conf, LO_COS_ACCUMULATOR + XOR_ADDR / 4, lo_cos, 1, false); @@ -414,7 +304,7 @@ static float rf_rx_start(int rx_pin, int bias_pin, float freq, int frac_num, int 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 + 2); + 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); @@ -440,7 +330,7 @@ static float rf_rx_start(int rx_pin, int bias_pin, float freq, int frac_num, int false); /* Pacing timer for the sampling script trigger channel. */ - dma_timer_set_fraction(dma_t_samp, frac_num, frac_denom); + dma_timer_set_fraction(dma_t_samp, 1, CLK_SYS_HZ / rate); /* Sampling trigger channel. */ dma_conf = dma_channel_get_default_config(dma_ch_samp_trig); @@ -448,6 +338,7 @@ static float rf_rx_start(int rx_pin, int bias_pin, float freq, int frac_num, int 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); @@ -457,6 +348,7 @@ static float rf_rx_start(int rx_pin, int bias_pin, float freq, int frac_num, int 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); @@ -466,19 +358,17 @@ static float rf_rx_start(int rx_pin, int bias_pin, float freq, int frac_num, int 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(); - float actual = rx_lo_init(freq); - + rx_lo_init(freq); dma_channel_start(dma_ch_rx); dma_channel_start(dma_ch_samp_trig); watch_init(rx_pin); - - return actual; } static void rf_rx_stop(void) @@ -545,74 +435,12 @@ static void rf_rx_stop(void) dma_t_samp = -1; } -static void rf_tx_start(int tx_pin) -{ - send_init(tx_pin); - - dma_ch_tx_cos = dma_claim_unused_channel(true); - - dma_channel_config dma_conf = dma_channel_get_default_config(dma_ch_tx_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, LO_BITS_DEPTH + 2); - channel_config_set_dreq(&dma_conf, pio_get_dreq(pio1, 1, true)); - dma_channel_configure(dma_ch_tx_cos, &dma_conf, &pio1->txf[1], lo_cos, UINT_MAX, true); -} - -static void rf_tx_stop() -{ - puts("Stopping TX..."); - pio_sm_set_enabled(pio1, 1, false); - pio_sm_restart(pio1, 1); - pio_sm_clear_fifos(pio1, 1); - - puts("Stopping DMA..."); - dma_channel_abort(dma_ch_tx_cos); - dma_channel_cleanup(dma_ch_tx_cos); - dma_channel_unclaim(dma_ch_tx_cos); - dma_ch_tx_cos = -1; -} - static void rf_rx(void) { - const int amp_max = CLK_SYS_HZ / 2 / BANDWIDTH * DECIMATION; - - /* Scale down 2× to accomodate for jitter. */ - const int amp_scale = INT_MAX / amp_max / 2; - - static int16_t block[IQ_BLOCK_LEN]; + static uint8_t block[IQ_BLOCK_LEN]; uint32_t prev_transfers = dma_hw->ch[dma_ch_in_cos].transfer_count; unsigned pos = 0; -#if LPF_ORDER >= 1 - static int lpIh1[DECIMATION]; - static int lpQh1[DECIMATION]; - int lpIa1 = 0; - int lpQa1 = 0; -#endif - -#if LPF_ORDER >= 2 - static int lpIh2[DECIMATION]; - static int lpQh2[DECIMATION]; - int lpIa2 = 0; - int lpQa2 = 0; -#endif - -#if LPF_ORDER >= 3 - static int lpIh3[DECIMATION]; - static int lpQh3[DECIMATION]; - int lpIa3 = 0; - int lpQa3 = 0; -#endif - -#if LPF_ORDER >= 4 - static int lpIh4[DECIMATION]; - static int lpQh4[DECIMATION]; - int lpIa4 = 0; - int lpQa4 = 0; -#endif - int64_t dcI = 0, dcQ = 0; while (true) { @@ -622,169 +450,77 @@ static void rf_rx(void) return; } - int16_t *blockptr = block; + int delta = prev_transfers - dma_hw->ch[dma_ch_in_cos].transfer_count; - for (int i = 0; i < IQ_BLOCK_LEN / 2; i++) { - int delta = prev_transfers - dma_hw->ch[dma_ch_in_cos].transfer_count; - gap = 2 * DECIMATION - delta; + while (delta < RX_STRIDE * 2) { + delta = prev_transfers - dma_hw->ch[dma_ch_in_cos].transfer_count; + sleep_us(1); + } - while (delta < 2 * DECIMATION) { - delta = prev_transfers - dma_hw->ch[dma_ch_in_cos].transfer_count; - sleep_us(RX_SLEEP_US); - } + prev_transfers -= RX_STRIDE; - prev_transfers -= 2 * DECIMATION; + uint32_t *cos_ptr = rx_cos + pos; + uint32_t *sin_ptr = rx_sin + pos; - uint32_t *cos_ptr = rx_cos + pos; - uint32_t *sin_ptr = rx_sin + pos; + pos = (pos + RX_STRIDE) & (RX_WORDS - 1); - pos = (pos + 2 * DECIMATION) & (RX_WORDS - 1); + uint8_t *blockptr = block; - int dI = 0; + /* + * 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; - for (int d = 0; d < DECIMATION; d++) { - uint32_t cos_pos = *cos_ptr++; - uint32_t cos_neg = *cos_ptr++; - int I = cos_neg - cos_pos; - I = I * amp_scale; + /* + * Since the waveform is normally half of the time + * above zero, we could halve once more. + * + * Instead we use 2/3 to provide 1/3 reserve. + */ + max_amplitude = max_amplitude * 2 / 3; -#if LPF_ORDER >= 1 - lpIa1 += I - lpIh1[d]; - lpIh1[d] = I; - I = lpIa1 / DECIMATION; -#endif -#if LPF_ORDER >= 2 - lpIa2 += I - lpIh2[d]; - lpIh2[d] = I; - I = lpIa2 / DECIMATION; -#endif -#if LPF_ORDER >= 3 - lpIa3 += I - lpIh3[d]; - lpIh3[d] = I; - I = lpIa3 / DECIMATION; -#endif -#if LPF_ORDER >= 4 - lpIa4 += I - lpIh4[d]; - lpIh4[d] = I; - I = lpIa4 / DECIMATION; -#endif - dI += I; - } + /* + * We are allowing the counters to only go as high + * as sampling rate. + */ + max_amplitude /= sample_rate; - int dQ = 0; + for (int i = 0; i < IQ_SAMPLES; i++) { + uint32_t cos_neg = *cos_ptr++; + uint32_t cos_pos = *cos_ptr++; + int I32 = cos_neg - cos_pos; + int64_t I = I32; - /* - * Original dI/dQ are scaled to 32 bits. - * These "<< 19" are part of DC removal alpha. - */ - int64_t dI19 = (int64_t)dI << 19; - dcI = ((dcI << 13) - dcI + dI19) >> 13; - dI = (dI19 - dcI) >> 19; + dcI = ((dcI << 16) - dcI + (I << 16)) >> 16; + I -= dcI >> 16; - for (int d = 0; d < DECIMATION; d++) { - uint32_t sin_pos = *sin_ptr++; - uint32_t sin_neg = *sin_ptr++; - int Q = sin_neg - sin_pos; - Q = Q * amp_scale; + I *= gain; + I /= max_amplitude; -#if LPF_ORDER >= 1 - lpQa1 += Q - lpQh1[d]; - lpQh1[d] = Q; - Q = lpQa1 / DECIMATION; -#endif -#if LPF_ORDER >= 2 - lpQa2 += Q - lpQh2[d]; - lpQh2[d] = Q; + *blockptr++ = I + 128; - Q = lpQa2 / DECIMATION; -#endif -#if LPF_ORDER >= 3 - lpQa3 += Q - lpQh3[d]; - lpQh3[d] = Q; - Q = lpQa3 / DECIMATION; -#endif -#if LPF_ORDER >= 4 - lpQa4 += Q - lpQh4[d]; - lpQh4[d] = Q; - Q = lpQa4 / DECIMATION; -#endif - dQ += Q; - } + uint32_t sin_neg = *sin_ptr++; + uint32_t sin_pos = *sin_ptr++; + int Q32 = sin_neg - sin_pos; + int64_t Q = Q32; - int64_t dQ19 = (int64_t)dQ << 19; - dcQ = ((dcQ << 13) - dcQ + dQ19) >> 13; - dQ = (dQ19 - dcQ) >> 19; + dcQ = ((dcQ << 16) - dcQ + (Q << 16)) >> 16; + Q -= dcQ >> 16; - /* Slowly decay AGC amplitude. */ - agc -= (agc >> AGC_DECAY_BITS) | 1; + Q *= gain; + Q /= max_amplitude; - if (abs(dI) > agc) - agc = abs(dI); - - if (abs(dQ) > agc) - agc = abs(dQ); - - int agc_div = (agc >> (8 + 7)) + (agc >> (8 + 14)); - - *blockptr++ = dI / agc_div; - *blockptr++ = dQ / agc_div; + *blockptr++ = Q + 128; } (void)queue_try_add(&iq_queue, block); } } -inline static int icopysign(int x, int s) +static void do_rx(int rx_pin, int bias_pin, double freq) { - return s >= 0 ? abs(x) : -abs(x); -} - -static void __unused plot_IQ(int I, int Q) -{ - int mag = I ? icopysign(32 - __builtin_clz(abs(I)), I) : 0; - - if (mag < 0) { - for (int l = -mag; l < 8; l++) - putchar(' '); - - for (int l = 0; l < -mag; l++) - putchar('#'); - - printf("%8s", ""); - } else { - printf("%8s", ""); - - for (int l = 0; l < mag; l++) - putchar('#'); - - for (int l = mag; l < 8; l++) - putchar(' '); - } - - mag = Q ? icopysign(32 - __builtin_clz(abs(Q)), Q) : 0; - - if (mag < 0) { - for (int l = -mag; l < 8; l++) - putchar(' '); - - for (int l = 0; l < -mag; l++) - putchar('#'); - - printf("%8s", ""); - } else { - printf("%8s", ""); - - for (int l = 0; l < mag; l++) - putchar('#'); - - for (int l = mag; l < 8; l++) - putchar(' '); - } -} - -static void do_rx(int rx_pin, int bias_pin, float freq, char mode) -{ - float actual = rf_rx_start(rx_pin, bias_pin, freq, 1, CLK_SYS_HZ / BANDWIDTH); + rf_rx_start(rx_pin, bias_pin, freq, sample_rate); sleep_us(100); dma_ch_in_cos = dma_claim_unused_channel(true); @@ -796,7 +532,7 @@ static void do_rx(int rx_pin, int bias_pin, float freq, char mode) 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 + 2); + channel_config_set_ring(&dma_conf, true, RX_BITS_DEPTH); channel_config_set_dreq(&dma_conf, pio_get_dreq(pio1, 2, false)); dma_channel_configure(dma_ch_in_cos, &dma_conf, rx_cos, &pio1->rxf[2], UINT_MAX, false); @@ -804,7 +540,7 @@ static void do_rx(int rx_pin, int bias_pin, float freq, char mode) 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 + 2); + channel_config_set_ring(&dma_conf, true, RX_BITS_DEPTH); channel_config_set_dreq(&dma_conf, pio_get_dreq(pio1, 3, false)); dma_channel_configure(dma_ch_in_sin, &dma_conf, rx_sin, &pio1->rxf[3], UINT_MAX, false); @@ -812,57 +548,56 @@ static void do_rx(int rx_pin, int bias_pin, float freq, char mode) multicore_launch_core1(rf_rx); - printf("Frequency: %.0f\n", actual); - - static int16_t block[IQ_BLOCK_LEN]; + static uint8_t block[IQ_BLOCK_LEN]; while (queue_try_remove(&iq_queue, block)) /* Flush the queue */; - if ('b' == mode) { - setvbuf(stdout, NULL, _IONBF, 0); - putchar('$'); - } - while (true) { int c = getchar_timeout_us(0); - if ('\r' == c) - break; - bool overflow = queue_is_full(&iq_queue); + if (c >= 0) { + if (0x00 == c) { + break; + } else if (0x01 == c) { + /* Tune to a new center frequency */ + rx_lo_init(read_arg()); + } else if (0x02 == c) { + /* Set the rate at which IQ sample pairs are sent */ + sample_rate = read_arg(); + dma_timer_set_fraction(dma_t_samp, 1, CLK_SYS_HZ / sample_rate); + } else if (0x04 == c) { + /* Set the tuner gain level */ + gain = INIT_GAIN * powf(10.0f, 0.01f * read_arg()); + } else if (0x0d == c) { + /* Set tuner gain by the tuner's gain index */ + uint32_t arg = read_arg(); - if (queue_try_remove(&iq_queue, block)) { - if ('b' == mode) { + if (arg < NUM_GAINS) { + gain = INIT_GAIN * powf(10.0f, 0.01f * gains[arg]); + } + } else { + (void)read_arg(); + } + } + + for (int i = 0; i < 32; i++) { + if (queue_try_remove(&iq_queue, block)) { fwrite(block, sizeof block, 1, stdout); fflush(stdout); } else { - /* Because AGC is kept 1 bit below to accomodate for jitter. */ - float agc_frac = 2.0f * (float)agc / (float)INT_MAX; - float rssi = 10.0f * log10f(powf(agc_frac, 2)); - - for (int i = 0; i < IQ_BLOCK_LEN / 2; i += 2) { - int I = block[i] >> 8; - int Q = block[i + 1] >> 8; - printf("%i %+5i | %+5.1f dBm | %+4i %+4i | ", overflow, - RX_WORDS / 2 + gap, rssi, I, Q); - plot_IQ(I, Q); - putchar('\n'); - } + break; } } } - putchar('\n'); - puts("Stopping core1..."); multicore_fifo_push_blocking(0); multicore_fifo_pop_blocking(); sleep_us(10); multicore_reset_core1(); - puts("Stopping RX..."); rf_rx_stop(); - puts("Stopping readout DMAs..."); dma_channel_abort(dma_ch_in_cos); dma_channel_abort(dma_ch_in_sin); dma_channel_cleanup(dma_ch_in_cos); @@ -871,236 +606,6 @@ static void do_rx(int rx_pin, int bias_pin, float freq, char mode) dma_channel_unclaim(dma_ch_in_sin); dma_ch_in_cos = -1; dma_ch_in_sin = -1; - - puts("Done."); -} - -static void command(const char *cmd) -{ - static char tmp[83]; - int n, x; - float f, g; - - if (1 == sscanf(cmd, " help %[\a]", tmp)) { - puts("help - this help"); - puts("drive N X - set GPIO pin drive strength"); - puts("bias I O - output negated I to O"); - puts("rx N B FREQ - receive on pin N, biasing with pin B"); - puts("brx N FREQ - receive on pin N, binary output"); - puts("bpsk N FREQ - transmit on pin N with BPSK"); - puts("fsk N FREQ - transmit on pin N with FSK"); - puts("ook N FREQ - transmit on pin N with OOK"); - puts("sweep N F G S - sweep from F to G with given step"); - puts("noise N - transmit random noise"); - return; - } - - if (3 == sscanf(cmd, " drive %i %i %[\a]", &n, &x, tmp)) { - if ((x < 0) || (x > 3)) { - puts("invalid drive strength, use 0-3 for 2, 4, 8, 12 mA"); - return; - } - - gpio_set_drive_strength(n, x); - static int strength[] = { 2, 4, 8, 12 }; - printf("gpio%i: %i mA\n", n, strength[x]); - return; - } - - if (3 == sscanf(cmd, " bias %i %i %[\a]", &n, &x, tmp)) { - bias_init(n, x); - return; - } - - if (4 == sscanf(cmd, " rx %i %i %f %[\a]", &n, &x, &f, tmp)) { - do_rx(n, x, f, 'a'); - return; - } - - if (4 == sscanf(cmd, " brx %i %i %f %[\a]", &n, &x, &f, tmp)) { - do_rx(n, x, f, 'b'); - return; - } - - if (3 == sscanf(cmd, " bpsk %i %f %[\a]", &n, &f, tmp)) { - float actual = rx_lo_init(f); - printf("Frequency: %.0f\n", actual); - - rf_tx_start(n); - puts("Transmitting, press ENTER to stop."); - - bool phase = false; - const double step_hz = (double)CLK_SYS_HZ / (LO_WORDS * 32); - - while (true) { - int c = getchar_timeout_us(10000); - - if ('\r' == c) { - break; - } else if (' ' == c) { - phase = !phase; - gpio_set_outover(n, phase); - } else if ('+' == c) { - actual = rx_lo_init(actual + step_hz); - printf("Frequency: %.0f\n", actual); - } else if ('-' == c) { - actual = rx_lo_init(actual - step_hz); - printf("Frequency: %.0f\n", actual); - } else if ((c >= '1') && (c <= '9')) { - for (int i = 0; i < 1000; i++) { - phase = !phase; - gpio_set_outover(n, phase); - sleep_us(1000 / (c - '0')); - } - } - } - - rf_tx_stop(); - gpio_set_outover(n, 0); - puts("Done."); - return; - } - - if (4 == sscanf(cmd, " fsk %i %f %f %[\a]", &n, &f, &g, tmp)) { - g = lo_round_freq(LO_WORDS * 32, g); - f = tx_fsk_lo_init(f, g); - printf("Frequency: %.0f +/- %.f\n", f, g / 2.0f); - - rf_tx_start(n); - puts("Transmitting, press ENTER to stop."); - - bool high = true; - const double step_hz = (double)CLK_SYS_HZ / (LO_WORDS * 32); - - while (true) { - int c = getchar_timeout_us(10000); - - if ('\r' == c) { - break; - } else if (' ' == c) { - high = !high; - - if (high) { - dma_hw->ch[dma_ch_tx_cos].read_addr = (uint32_t)lo_cos; - } else { - dma_hw->ch[dma_ch_tx_cos].read_addr = (uint32_t)lo_sin; - } - } else if ('+' == c) { - f = tx_fsk_lo_init(f + step_hz, g); - printf("Frequency: %.0f +/- %.f\n", f, g / 2.0f); - } else if ('-' == c) { - f = tx_fsk_lo_init(f - step_hz, g); - printf("Frequency: %.0f +/- %.f\n", f, g / 2.0f); - } else if ((c >= '1') && (c <= '9')) { - for (int i = 0; i < 1000; i++) { - high = !high; - - if (high) { - dma_hw->ch[dma_ch_tx_cos].read_addr = - (uint32_t)lo_cos; - } else { - dma_hw->ch[dma_ch_tx_cos].read_addr = - (uint32_t)lo_sin; - } - - sleep_us(1000 / (c - '0')); - } - } - } - - rf_tx_stop(); - puts("Done."); - return; - } - - if (3 == sscanf(cmd, " ook %i %f %[\a]", &n, &f, tmp)) { - float actual = rx_lo_init(f); - printf("Frequency: %.0f\n", actual); - - rf_tx_start(n); - puts("Transmitting, press ENTER to stop."); - - bool off = false; - const double step_hz = (double)CLK_SYS_HZ / (LO_WORDS * 32); - - while (true) { - int c = getchar_timeout_us(10000); - - if ('\r' == c) { - break; - } else if (' ' == c) { - off = !off; - gpio_set_oeover(n, off * 2); - } else if ('+' == c) { - actual = rx_lo_init(actual + step_hz); - printf("Frequency: %.0f\n", actual); - } else if ('-' == c) { - actual = rx_lo_init(actual - step_hz); - printf("Frequency: %.0f\n", actual); - } else if ((c >= '1') && (c <= '9')) { - for (int i = 0; i < 1000; i++) { - off = !off; - gpio_set_oeover(n, off * 2); - sleep_us(1000 / (c - '0')); - } - } - } - - rf_tx_stop(); - gpio_set_oeover(n, 0); - puts("Done."); - return; - } - - if (5 == sscanf(cmd, " sweep %i %f %f %i %[\a]", &n, &f, &g, &x, tmp)) { - const float step_hz = (float)CLK_SYS_HZ / (LO_WORDS * 32); - const float start = roundf(f / step_hz) * step_hz; - const float stop = roundf(g / step_hz) * step_hz; - - int steps = roundf((stop - start) / step_hz); - - for (int i = 0; i < LO_WORDS; i++) - lo_cos[i] = 0; - - rf_tx_start(n); - - for (int i = 0; i < steps; i += x) { - int c = getchar_timeout_us(10000); - if ('\r' == c) - break; - - float actual = rx_lo_init(start + i * step_hz); - printf("Frequency: %.0f\n", actual); - } - - rf_tx_stop(); - puts("Done."); - return; - } - - if (2 == sscanf(cmd, " noise %i %[\a]", &n, tmp)) { - for (int i = 0; i < LO_WORDS; i++) - lo_cos[i] = rand(); - - rf_tx_start(n); - - puts("Transmitting noise, press ENTER to stop."); - - while (true) { - int c = getchar_timeout_us(100); - if ('\r' == c) - break; - - for (int i = 0; i < LO_WORDS; i++) - lo_cos[i] = rand(); - } - - rf_tx_stop(); - puts("Done."); - return; - } - - puts("unknown command"); } int main() @@ -1118,53 +623,31 @@ int main() bus_ctrl_hw->priority |= BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS; stdio_usb_init(); + setvbuf(stdout, NULL, _IONBF, 0); - for (int i = 0; i < 30; i++) { - if (stdio_usb_connected()) - break; - - sleep_ms(100); - } - - printf("\nPuppet Online!\n"); - printf("clk_sys = %10.6f MHz\n", (float)clock_get_hz(clk_sys) / MHZ); - - queue_init(&iq_queue, IQ_BLOCK_LEN * sizeof(int16_t), 256); - - static char cmd[83]; - int cmdlen = 0; - - printf("> "); + queue_init(&iq_queue, IQ_BLOCK_LEN, 256); while (true) { - int c; + int c = getchar_timeout_us(0); - while ((c = getchar_timeout_us(10000)) >= 0) { - if ('\r' == c) { - /* Enter */ - } else if ((8 == c) && (cmdlen > 0)) { - cmd[--cmdlen] = 0; - printf("\b \b"); - } else if ((' ' == c) && (0 == cmdlen)) { - /* No leading spaces. */ - continue; - } else if (c < ' ') { - continue; - } else { - cmd[cmdlen++] = c; - putchar(c); - } + if (0 == c) { + continue; + } else if (1 == c) { + /* Tune to a new center frequency */ + uint32_t arg; + fread(&arg, sizeof arg, 1, stdin); + arg = __builtin_bswap32(arg); - if (('\r' == c) || cmdlen == 80) { - printf("\n"); - if (cmdlen > 0) { - cmd[cmdlen] = '\a'; - cmd[cmdlen + 1] = 0; - command(cmd); - cmdlen = 0; - } - printf("> "); - } + static const uint32_t header[3] = { __builtin_bswap32(0x52544c30), + __builtin_bswap32(6), + __builtin_bswap32(NUM_GAINS) }; + fwrite(header, sizeof header, 1, stdout); + fflush(stdout); + + gain = INIT_GAIN; + sample_rate = INIT_SAMPLE_RATE; + + do_rx(10, 11, arg); } } } diff --git a/util/bridge.py b/util/bridge.py index 79cf7ca..76a415a 100755 --- a/util/bridge.py +++ b/util/bridge.py @@ -1,57 +1,64 @@ #!/usr/bin/env python -import time -from codecs import encode -from socket import AF_INET, SO_SNDBUF, SOCK_STREAM, SOL_SOCKET, socket +import binascii +import struct +from socket import (AF_INET, MSG_DONTWAIT, SO_REUSEADDR, SO_SNDBUF, + SOCK_STREAM, SOL_SOCKET, socket) import click import serial @click.command() -@click.option("-f", "--frequency", default=40680000, help="Frequency to tune to") -@click.option("--rx", default=10, help="Receive pin") -@click.option("--bias", default=11, help="Bias pin") -def bridge(frequency, rx, bias): +@click.option("-f", "--frequency", default=88200000, help="Frequency to tune to") +def bridge(frequency): sock = socket(AF_INET, SOCK_STREAM) + sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.setsockopt(SOL_SOCKET, SO_SNDBUF, 1024 * 100) - with serial.Serial("/dev/ttyACM0", baudrate=10_000_000) as fp: - print("Resetting...") - fp.write(b"\r\n") + print("Posing as rtl_tcp at tcp://127.0.0.1:1234") + sock.bind(("127.0.0.1", 1234)) + sock.listen(3) - time.sleep(0.1) + while True: + peer, addr = sock.accept() + print("Client connected:", addr) - while fp.in_waiting: - fp.read(fp.in_waiting) - time.sleep(0.1) + with serial.Serial("/dev/ttyACM0", baudrate=10_000_000, timeout=0.1) as fp: + print(f"Starting RX @ {frequency}") + fp.write(struct.pack(">BBL", 0, 1, int(frequency))) + fp.flush() - print("Connecting to localhost:1234...") - sock.connect(("localhost", 1234)) + print("Begin") - print(f"Starting RX {rx}/{bias} at {frequency}...") - fp.write(f"brx {rx} {bias} {frequency}\r\n".encode("ascii")) - fp.read_until(b"$") + try: + cmd = b"" - try: - while True: - bstr = fp.read(64) - assert len(bstr) == 64 + while True: + try: + cmd += peer.recv(1, MSG_DONTWAIT) + except BlockingIOError: + pass - try: - assert len(bstr) == sock.send(bstr) - except ConnectionRefusedError: - pass + while len(cmd) >= 5: + fp.write(cmd[:5]) + info = struct.unpack(">BL", cmd[:5]) + print("->", hex(info[0]), info[1]) + cmd = cmd[5:] - except ConnectionError: - pass + data = fp.read(64) + if data: + peer.send(data) - except KeyboardInterrupt: - pass + except ConnectionError: + pass - finally: - fp.write(b"\r\n") - print("Bye.") + except KeyboardInterrupt: + pass + + finally: + fp.write(b"\x00") + print("Bye.") if __name__ == "__main__": From 7e41420d144f5c72574c650bff8872b1c410c935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Thu, 6 Jun 2024 20:02:30 +0200 Subject: [PATCH 02/10] Make commands more reliable --- src/main.c | 80 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/src/main.c b/src/main.c index c2f5f63..373f198 100644 --- a/src/main.c +++ b/src/main.c @@ -76,15 +76,6 @@ static int dma_ch_in_sin = -1; static queue_t iq_queue; -static uint32_t read_arg(void) -{ - uint32_t a = getchar_timeout_us(100); - uint32_t b = getchar_timeout_us(100); - uint32_t c = getchar_timeout_us(100); - uint32_t d = getchar_timeout_us(100); - return (a << 24) | (b << 16) | (c << 8) | d; -} - static void bias_init(int in_pin, int out_pin) { gpio_disable_pulls(in_pin); @@ -518,6 +509,49 @@ static void rf_rx(void) } } +static void run_command(uint8_t cmd, uint32_t arg) +{ + if (0x01 == cmd) { + /* Tune to a new center frequency */ + rx_lo_init(arg); + } else if (0x02 == cmd) { + /* Set the rate at which IQ sample pairs are sent */ + sample_rate = arg; + dma_timer_set_fraction(dma_t_samp, 1, CLK_SYS_HZ / sample_rate); + } else if (0x04 == cmd) { + /* Set the tuner gain level */ + gain = INIT_GAIN * powf(10.0f, 0.01f * arg); + } else if (0x0d == cmd) { + /* Set tuner gain by the tuner's gain index */ + if (arg < NUM_GAINS) { + gain = INIT_GAIN * powf(10.0f, 0.01f * gains[arg]); + } + } +} + +static bool check_command(void) +{ + static uint8_t buf[5]; + static int pos = 0; + + int c; + + while ((c = getchar_timeout_us(0)) >= 0) { + if (0 == pos && 0 == c) + return true; + + buf[pos++] = c; + + if (5 == pos) { + uint32_t arg = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4]; + run_command(buf[0], arg); + pos = 0; + } + } + + return false; +} + static void do_rx(int rx_pin, int bias_pin, double freq) { rf_rx_start(rx_pin, bias_pin, freq, sample_rate); @@ -554,32 +588,8 @@ static void do_rx(int rx_pin, int bias_pin, double freq) /* Flush the queue */; while (true) { - int c = getchar_timeout_us(0); - - if (c >= 0) { - if (0x00 == c) { - break; - } else if (0x01 == c) { - /* Tune to a new center frequency */ - rx_lo_init(read_arg()); - } else if (0x02 == c) { - /* Set the rate at which IQ sample pairs are sent */ - sample_rate = read_arg(); - dma_timer_set_fraction(dma_t_samp, 1, CLK_SYS_HZ / sample_rate); - } else if (0x04 == c) { - /* Set the tuner gain level */ - gain = INIT_GAIN * powf(10.0f, 0.01f * read_arg()); - } else if (0x0d == c) { - /* Set tuner gain by the tuner's gain index */ - uint32_t arg = read_arg(); - - if (arg < NUM_GAINS) { - gain = INIT_GAIN * powf(10.0f, 0.01f * gains[arg]); - } - } else { - (void)read_arg(); - } - } + if (check_command()) + break; for (int i = 0; i < 32; i++) { if (queue_try_remove(&iq_queue, block)) { From 48c4c2dc40690443a155086f940df2b22523dc5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Thu, 6 Jun 2024 20:19:07 +0200 Subject: [PATCH 03/10] Remove LO dithering Means we will probably receive more LO harmonic content but have better SNR where there is silence. --- src/main.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main.c b/src/main.c index 373f198..75ed269 100644 --- a/src/main.c +++ b/src/main.c @@ -23,7 +23,6 @@ #define VREG_VOLTAGE VREG_VOLTAGE_1_20 #define CLK_SYS_HZ (300 * MHZ) -#define LO_DITHER 1 #define PSU_PIN 23 #define IQ_SAMPLES 32 @@ -202,22 +201,16 @@ static void adder_init() static void lo_generate(uint32_t *buf, size_t len, double freq, unsigned phase) { - unsigned step = ((double)UINT_MAX + 1.0) / (double)CLK_SYS_HZ * freq; + static const double base = (UINT_MAX + 1.0) / CLK_SYS_HZ; + + unsigned step = base * freq; unsigned accum = phase; for (size_t i = 0; i < len; i++) { unsigned bits = 0; for (int j = 0; j < 32; j++) { -#if LO_DITHER - int n0 = rand() - RAND_MAX / 2; - int n1 = rand() - RAND_MAX / 2; - int noise = (n0 + n1) / 2; - - bits |= (accum + noise) >> 31; -#else bits |= accum >> 31; -#endif bits <<= 1; accum += step; } From 7d4b890a7f8c8587b2667599eee8c83da8a9b2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Thu, 6 Jun 2024 20:59:07 +0200 Subject: [PATCH 04/10] Optimize DC removal a bit --- src/main.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main.c b/src/main.c index 75ed269..f93d970 100644 --- a/src/main.c +++ b/src/main.c @@ -476,8 +476,9 @@ static void rf_rx(void) int I32 = cos_neg - cos_pos; int64_t I = I32; - dcI = ((dcI << 16) - dcI + (I << 16)) >> 16; - I -= dcI >> 16; + int64_t I16 = I << 16; + dcI = ((dcI << 16) - dcI + I16) >> 16; + I = (I16 - dcI) >> 16; I *= gain; I /= max_amplitude; @@ -489,8 +490,9 @@ static void rf_rx(void) int Q32 = sin_neg - sin_pos; int64_t Q = Q32; - dcQ = ((dcQ << 16) - dcQ + (Q << 16)) >> 16; - Q -= dcQ >> 16; + int64_t Q16 = Q << 16; + dcQ = ((dcQ << 16) - dcQ + Q16) >> 16; + Q = (Q16 - dcQ) >> 16; Q *= gain; Q /= max_amplitude; From 5b3bf38e395f553b0f125df64c613a002b5c392c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Thu, 6 Jun 2024 21:14:16 +0200 Subject: [PATCH 05/10] Update GNU Radio Companion sheets --- grc/PicoSDR-WBFM.grc | 65 +++---- grc/PicoSDR.grc | 439 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 390 insertions(+), 114 deletions(-) diff --git a/grc/PicoSDR-WBFM.grc b/grc/PicoSDR-WBFM.grc index ae58040..fd848c6 100644 --- a/grc/PicoSDR-WBFM.grc +++ b/grc/PicoSDR-WBFM.grc @@ -33,36 +33,28 @@ options: state: enabled blocks: -- name: bpsk - id: variable_constellation +- name: carrier + id: variable parameters: comment: '' - const_points: '[-1-1j, -1+1j, 1+1j, 1-1j]' - dims: '1' - normalization: digital.constellation.AMPLITUDE_NORMALIZATION - npwr: '1.0' - precision: '8' - rot_sym: '4' - soft_dec_lut: None - sym_map: '[0, 1, 3, 2]' - type: bpsk + value: '88_200_000' states: bus_sink: false bus_source: false bus_structure: null - coordinate: [280, 8.0] + coordinate: [168, 8.0] rotation: 0 - state: true + state: enabled - name: samp_rate id: variable parameters: comment: '' - value: 1_536_000 // (1 << 3) + value: '192_000' states: bus_sink: false bus_source: false bus_structure: null - coordinate: [176, 8.0] + coordinate: [264, 8.0] rotation: 0 state: enabled - name: analog_quadrature_demod_cf_0 @@ -78,7 +70,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [704, 312.0] + coordinate: [640, 336.0] rotation: 0 state: true - name: analog_wfm_rcv_pll_0 @@ -96,7 +88,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [456, 376.0] + coordinate: [640, 440.0] rotation: 0 state: true - name: audio_sink_0 @@ -113,7 +105,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [704, 384.0] + coordinate: [928, 448.0] rotation: 0 state: true - name: blocks_message_debug_0 @@ -128,7 +120,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [992, 8.0] + coordinate: [928, 32.0] rotation: 0 state: true - name: blocks_probe_rate_0 @@ -148,20 +140,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [704, 16.0] - rotation: 0 - state: true -- name: import_0 - id: import - parameters: - alias: '' - comment: '' - imports: import math - states: - bus_sink: false - bus_source: false - bus_structure: null - coordinate: [440, 8.0] + coordinate: [640, 40.0] rotation: 0 state: true - name: low_pass_filter_0 @@ -185,7 +164,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [992, 284.0] + coordinate: [928, 284.0] rotation: 0 state: enabled - name: osmosdr_source_0 @@ -363,7 +342,7 @@ blocks: dc_offset_mode7: '0' dc_offset_mode8: '0' dc_offset_mode9: '0' - freq0: '88_200_000' + freq0: carrier freq1: 100e6 freq10: 100e6 freq11: 100e6 @@ -639,7 +618,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [704, 208.0] + coordinate: [640, 232.0] rotation: 0 state: true - name: qtgui_time_sink_x_0_0 @@ -736,7 +715,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [1192, 312.0] + coordinate: [1128, 312.0] rotation: 0 state: true - name: qtgui_waterfall_sink_x_0_0 @@ -768,12 +747,12 @@ blocks: color9: '0' comment: '' fc: '0' - fftsize: '2048' + fftsize: '4096' freqhalf: 'True' grid: 'True' gui_hint: (0, 0, 1, 2) int_max: '0' - int_min: 10 * math.log10(1 / ((2 ** 15 - 1) ** 2)) + int_min: 10 * math.log10(1 / ((2 ** 16) ** 2)) label1: '' label10: '' label2: '' @@ -797,7 +776,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [704, 104.0] + coordinate: [640, 128.0] rotation: 0 state: true - name: qtgui_waterfall_sink_x_0_0_0_0 @@ -829,12 +808,12 @@ blocks: color9: '0' comment: '' fc: '0' - fftsize: '2048' + fftsize: '1024' freqhalf: 'True' grid: 'True' gui_hint: (3, 0, 1, 2) int_max: '0' - int_min: 10 * math.log10(1 / ((2 ** 15 - 1) ** 2)) + int_min: 10 * math.log10(1 / ((2 ** 16) ** 2)) label1: '' label10: '' label2: '' @@ -858,7 +837,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [992, 184.0] + coordinate: [928, 184.0] rotation: 0 state: true diff --git a/grc/PicoSDR.grc b/grc/PicoSDR.grc index 052c66f..81987a6 100644 --- a/grc/PicoSDR.grc +++ b/grc/PicoSDR.grc @@ -33,36 +33,28 @@ options: state: enabled blocks: -- name: bpsk - id: variable_constellation +- name: carrier + id: variable parameters: comment: '' - const_points: '[-1-1j, -1+1j, 1+1j, 1-1j]' - dims: '1' - normalization: digital.constellation.AMPLITUDE_NORMALIZATION - npwr: '1.0' - precision: '8' - rot_sym: '4' - soft_dec_lut: None - sym_map: '[0, 1, 3, 2]' - type: bpsk + value: '40_680_000' states: bus_sink: false bus_source: false bus_structure: null - coordinate: [280, 8.0] + coordinate: [168, 8.0] rotation: 0 - state: true + state: enabled - name: samp_rate id: variable parameters: comment: '' - value: 1_280_000 // (1 << 6) + value: '50_000' states: bus_sink: false bus_source: false bus_structure: null - coordinate: [176, 8.0] + coordinate: [264, 8.0] rotation: 0 state: enabled - name: analog_quadrature_demod_cf_0 @@ -78,25 +70,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [704, 512.0] - rotation: 0 - state: true -- name: blocks_interleaved_short_to_complex_0 - id: blocks_interleaved_short_to_complex - parameters: - affinity: '' - alias: '' - comment: '' - maxoutbuf: '0' - minoutbuf: '0' - scale_factor: (1 << 15) - 1 - swap: 'False' - vector_input: 'False' - states: - bus_sink: false - bus_source: false - bus_structure: null - coordinate: [224, 208.0] + coordinate: [640, 536.0] rotation: 0 state: true - name: blocks_message_debug_0 @@ -111,7 +85,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [1000, 32.0] + coordinate: [928, 32.0] rotation: 0 state: true - name: blocks_probe_rate_0 @@ -123,7 +97,7 @@ blocks: comment: '' maxoutbuf: '0' minoutbuf: '0' - mintime: '1000' + mintime: '2500' name: '' type: complex vlen: '1' @@ -131,7 +105,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [704, 16.0] + coordinate: [640, 40.0] rotation: 0 state: true - name: digital_costas_loop_cc_0 @@ -149,42 +123,366 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [704, 320.0] + coordinate: [640, 344.0] rotation: 0 state: true -- name: import_0 - id: import +- name: osmosdr_source_0 + id: osmosdr_source parameters: - alias: '' - comment: '' - imports: import math - states: - bus_sink: false - bus_source: false - bus_structure: null - coordinate: [440, 8.0] - rotation: 0 - state: true -- name: network_tcp_source_0 - id: network_tcp_source - parameters: - addr: 127.0.0.1 affinity: '' alias: '' + ant0: '' + ant1: '' + ant10: '' + ant11: '' + ant12: '' + ant13: '' + ant14: '' + ant15: '' + ant16: '' + ant17: '' + ant18: '' + ant19: '' + ant2: '' + ant20: '' + ant21: '' + ant22: '' + ant23: '' + ant24: '' + ant25: '' + ant26: '' + ant27: '' + ant28: '' + ant29: '' + ant3: '' + ant30: '' + ant31: '' + ant4: '' + ant5: '' + ant6: '' + ant7: '' + ant8: '' + ant9: '' + args: '"rtl_tcp"' + bb_gain0: '20' + bb_gain1: '20' + bb_gain10: '20' + bb_gain11: '20' + bb_gain12: '20' + bb_gain13: '20' + bb_gain14: '20' + bb_gain15: '20' + bb_gain16: '20' + bb_gain17: '20' + bb_gain18: '20' + bb_gain19: '20' + bb_gain2: '20' + bb_gain20: '20' + bb_gain21: '20' + bb_gain22: '20' + bb_gain23: '20' + bb_gain24: '20' + bb_gain25: '20' + bb_gain26: '20' + bb_gain27: '20' + bb_gain28: '20' + bb_gain29: '20' + bb_gain3: '20' + bb_gain30: '20' + bb_gain31: '20' + bb_gain4: '20' + bb_gain5: '20' + bb_gain6: '20' + bb_gain7: '20' + bb_gain8: '20' + bb_gain9: '20' + bw0: '0' + bw1: '0' + bw10: '0' + bw11: '0' + bw12: '0' + bw13: '0' + bw14: '0' + bw15: '0' + bw16: '0' + bw17: '0' + bw18: '0' + bw19: '0' + bw2: '0' + bw20: '0' + bw21: '0' + bw22: '0' + bw23: '0' + bw24: '0' + bw25: '0' + bw26: '0' + bw27: '0' + bw28: '0' + bw29: '0' + bw3: '0' + bw30: '0' + bw31: '0' + bw4: '0' + bw5: '0' + bw6: '0' + bw7: '0' + bw8: '0' + bw9: '0' + clock_source0: '' + clock_source1: '' + clock_source2: '' + clock_source3: '' + clock_source4: '' + clock_source5: '' + clock_source6: '' + clock_source7: '' comment: '' + corr0: '0' + corr1: '0' + corr10: '0' + corr11: '0' + corr12: '0' + corr13: '0' + corr14: '0' + corr15: '0' + corr16: '0' + corr17: '0' + corr18: '0' + corr19: '0' + corr2: '0' + corr20: '0' + corr21: '0' + corr22: '0' + corr23: '0' + corr24: '0' + corr25: '0' + corr26: '0' + corr27: '0' + corr28: '0' + corr29: '0' + corr3: '0' + corr30: '0' + corr31: '0' + corr4: '0' + corr5: '0' + corr6: '0' + corr7: '0' + corr8: '0' + corr9: '0' + dc_offset_mode0: '0' + dc_offset_mode1: '0' + dc_offset_mode10: '0' + dc_offset_mode11: '0' + dc_offset_mode12: '0' + dc_offset_mode13: '0' + dc_offset_mode14: '0' + dc_offset_mode15: '0' + dc_offset_mode16: '0' + dc_offset_mode17: '0' + dc_offset_mode18: '0' + dc_offset_mode19: '0' + dc_offset_mode2: '0' + dc_offset_mode20: '0' + dc_offset_mode21: '0' + dc_offset_mode22: '0' + dc_offset_mode23: '0' + dc_offset_mode24: '0' + dc_offset_mode25: '0' + dc_offset_mode26: '0' + dc_offset_mode27: '0' + dc_offset_mode28: '0' + dc_offset_mode29: '0' + dc_offset_mode3: '0' + dc_offset_mode30: '0' + dc_offset_mode31: '0' + dc_offset_mode4: '0' + dc_offset_mode5: '0' + dc_offset_mode6: '0' + dc_offset_mode7: '0' + dc_offset_mode8: '0' + dc_offset_mode9: '0' + freq0: carrier + freq1: 100e6 + freq10: 100e6 + freq11: 100e6 + freq12: 100e6 + freq13: 100e6 + freq14: 100e6 + freq15: 100e6 + freq16: 100e6 + freq17: 100e6 + freq18: 100e6 + freq19: 100e6 + freq2: 100e6 + freq20: 100e6 + freq21: 100e6 + freq22: 100e6 + freq23: 100e6 + freq24: 100e6 + freq25: 100e6 + freq26: 100e6 + freq27: 100e6 + freq28: 100e6 + freq29: 100e6 + freq3: 100e6 + freq30: 100e6 + freq31: 100e6 + freq4: 100e6 + freq5: 100e6 + freq6: 100e6 + freq7: 100e6 + freq8: 100e6 + freq9: 100e6 + gain0: '10' + gain1: '10' + gain10: '10' + gain11: '10' + gain12: '10' + gain13: '10' + gain14: '10' + gain15: '10' + gain16: '10' + gain17: '10' + gain18: '10' + gain19: '10' + gain2: '10' + gain20: '10' + gain21: '10' + gain22: '10' + gain23: '10' + gain24: '10' + gain25: '10' + gain26: '10' + gain27: '10' + gain28: '10' + gain29: '10' + gain3: '10' + gain30: '10' + gain31: '10' + gain4: '10' + gain5: '10' + gain6: '10' + gain7: '10' + gain8: '10' + gain9: '10' + gain_mode0: 'False' + gain_mode1: 'False' + gain_mode10: 'False' + gain_mode11: 'False' + gain_mode12: 'False' + gain_mode13: 'False' + gain_mode14: 'False' + gain_mode15: 'False' + gain_mode16: 'False' + gain_mode17: 'False' + gain_mode18: 'False' + gain_mode19: 'False' + gain_mode2: 'False' + gain_mode20: 'False' + gain_mode21: 'False' + gain_mode22: 'False' + gain_mode23: 'False' + gain_mode24: 'False' + gain_mode25: 'False' + gain_mode26: 'False' + gain_mode27: 'False' + gain_mode28: 'False' + gain_mode29: 'False' + gain_mode3: 'False' + gain_mode30: 'False' + gain_mode31: 'False' + gain_mode4: 'False' + gain_mode5: 'False' + gain_mode6: 'False' + gain_mode7: 'False' + gain_mode8: 'False' + gain_mode9: 'False' + if_gain0: '20' + if_gain1: '20' + if_gain10: '20' + if_gain11: '20' + if_gain12: '20' + if_gain13: '20' + if_gain14: '20' + if_gain15: '20' + if_gain16: '20' + if_gain17: '20' + if_gain18: '20' + if_gain19: '20' + if_gain2: '20' + if_gain20: '20' + if_gain21: '20' + if_gain22: '20' + if_gain23: '20' + if_gain24: '20' + if_gain25: '20' + if_gain26: '20' + if_gain27: '20' + if_gain28: '20' + if_gain29: '20' + if_gain3: '20' + if_gain30: '20' + if_gain31: '20' + if_gain4: '20' + if_gain5: '20' + if_gain6: '20' + if_gain7: '20' + if_gain8: '20' + if_gain9: '20' + iq_balance_mode0: '0' + iq_balance_mode1: '0' + iq_balance_mode10: '0' + iq_balance_mode11: '0' + iq_balance_mode12: '0' + iq_balance_mode13: '0' + iq_balance_mode14: '0' + iq_balance_mode15: '0' + iq_balance_mode16: '0' + iq_balance_mode17: '0' + iq_balance_mode18: '0' + iq_balance_mode19: '0' + iq_balance_mode2: '0' + iq_balance_mode20: '0' + iq_balance_mode21: '0' + iq_balance_mode22: '0' + iq_balance_mode23: '0' + iq_balance_mode24: '0' + iq_balance_mode25: '0' + iq_balance_mode26: '0' + iq_balance_mode27: '0' + iq_balance_mode28: '0' + iq_balance_mode29: '0' + iq_balance_mode3: '0' + iq_balance_mode30: '0' + iq_balance_mode31: '0' + iq_balance_mode4: '0' + iq_balance_mode5: '0' + iq_balance_mode6: '0' + iq_balance_mode7: '0' + iq_balance_mode8: '0' + iq_balance_mode9: '0' maxoutbuf: '0' minoutbuf: '0' - port: '1234' - server: 'True' - type: short - vlen: '1' + nchan: '1' + num_mboards: '1' + sample_rate: samp_rate + sync: sync + time_source0: '' + time_source1: '' + time_source2: '' + time_source3: '' + time_source4: '' + time_source5: '' + time_source6: '' + time_source7: '' + type: fc32 states: bus_sink: false bus_source: false bus_structure: null - coordinate: [48, 200.0] + coordinate: [152, 164.0] rotation: 0 - state: true + state: enabled - name: qtgui_const_sink_x_0 id: qtgui_const_sink_x parameters: @@ -274,7 +572,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [1000, 304.0] + coordinate: [928, 328.0] rotation: 0 state: true - name: qtgui_time_sink_x_0 @@ -371,7 +669,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [704, 208.0] + coordinate: [640, 232.0] rotation: 0 state: true - name: qtgui_time_sink_x_0_0 @@ -468,7 +766,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [1000, 488.0] + coordinate: [928, 512.0] rotation: 0 state: true - name: qtgui_time_sink_x_0_1 @@ -565,7 +863,7 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [1000, 376.0] + coordinate: [928, 400.0] rotation: 0 state: true - name: qtgui_waterfall_sink_x_0_0 @@ -626,21 +924,20 @@ blocks: bus_sink: false bus_source: false bus_structure: null - coordinate: [704, 104.0] + coordinate: [640, 128.0] rotation: 0 state: true connections: - [analog_quadrature_demod_cf_0, '0', qtgui_time_sink_x_0_0, '0'] -- [blocks_interleaved_short_to_complex_0, '0', analog_quadrature_demod_cf_0, '0'] -- [blocks_interleaved_short_to_complex_0, '0', blocks_probe_rate_0, '0'] -- [blocks_interleaved_short_to_complex_0, '0', digital_costas_loop_cc_0, '0'] -- [blocks_interleaved_short_to_complex_0, '0', qtgui_time_sink_x_0, '0'] -- [blocks_interleaved_short_to_complex_0, '0', qtgui_waterfall_sink_x_0_0, '0'] - [blocks_probe_rate_0, rate, blocks_message_debug_0, print] - [digital_costas_loop_cc_0, '0', qtgui_const_sink_x_0, '0'] - [digital_costas_loop_cc_0, '0', qtgui_time_sink_x_0_1, '0'] -- [network_tcp_source_0, '0', blocks_interleaved_short_to_complex_0, '0'] +- [osmosdr_source_0, '0', analog_quadrature_demod_cf_0, '0'] +- [osmosdr_source_0, '0', blocks_probe_rate_0, '0'] +- [osmosdr_source_0, '0', digital_costas_loop_cc_0, '0'] +- [osmosdr_source_0, '0', qtgui_time_sink_x_0, '0'] +- [osmosdr_source_0, '0', qtgui_waterfall_sink_x_0_0, '0'] metadata: file_format: 1 From 4ecefa1e5b0a673f9804866beac545b192f0f451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Thu, 6 Jun 2024 21:23:36 +0200 Subject: [PATCH 06/10] README: update instructions for rtl_tcp mode --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5a440ae..7d6073e 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,12 @@ See the [blog post](https://blog.porucha.net/2024/pico-sdr/) for more informatio picotool load -f build/pico_sdr.uf2 ``` -3. Start the USB serial to TCP bridge, setting the frequency to 88.2 MHz: +3. Start the USB serial to TCP bridge: ```bash - python util/bridge.py -f 88200000 + python util/bridge.py ``` -4. Open `grc/PicoSDR-WBFM.grc` in GNU Radio Companion. +4. Open `grc/PicoSDR-WBFM.grc` in GNU Radio Companion, adjust carrier frequency to match your favorite FM radio station and press `F6`. -5. Press `F6`. +5. Alternatively [gqrx](https://www.gqrx.dk/) seems to work fine with `rtl_tcp` input mode. Maximum sample rate seem to be 400 ksps, above that the samples are dropped. From edfe86793dd74c9e240253bf4fa49557389d7bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Thu, 6 Jun 2024 22:04:47 +0200 Subject: [PATCH 07/10] util/bridge: print command names --- util/bridge.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/util/bridge.py b/util/bridge.py index 76a415a..f334d16 100755 --- a/util/bridge.py +++ b/util/bridge.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -import binascii import struct from socket import (AF_INET, MSG_DONTWAIT, SO_REUSEADDR, SO_SNDBUF, SOCK_STREAM, SOL_SOCKET, socket) @@ -8,6 +7,31 @@ from socket import (AF_INET, MSG_DONTWAIT, SO_REUSEADDR, SO_SNDBUF, import click import serial +COMMAND_NAMES = [ + "reset", + "tune_freq", + "sample_rate", + "manual_gain", + "gain", + "ppm_offset", + "if_gain", + "test_mode", + "agc", + "direct_sampling", + "offset_tuning", + "11", + "12", + "gain_index", + "bias_tee", +] + + +def describe(cmd: int, arg: int): + try: + print("->", COMMAND_NAMES[cmd], arg) + except IndexError: + print("->", cmd, arg) + @click.command() @click.option("-f", "--frequency", default=88200000, help="Frequency to tune to") @@ -43,7 +67,7 @@ def bridge(frequency): while len(cmd) >= 5: fp.write(cmd[:5]) info = struct.unpack(">BL", cmd[:5]) - print("->", hex(info[0]), info[1]) + describe(*info) cmd = cmd[5:] data = fp.read(64) From 78bb05cd1f406656ce1f4272af4df41404291047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Thu, 6 Jun 2024 22:11:17 +0200 Subject: [PATCH 08/10] Fake R820T to enable gqrx gain control --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index f93d970..b668326 100644 --- a/src/main.c +++ b/src/main.c @@ -644,7 +644,7 @@ int main() arg = __builtin_bswap32(arg); static const uint32_t header[3] = { __builtin_bswap32(0x52544c30), - __builtin_bswap32(6), + __builtin_bswap32(5), __builtin_bswap32(NUM_GAINS) }; fwrite(header, sizeof header, 1, stdout); fflush(stdout); From 40a0b843fbd25dfff41f8a5b8972d45d182803d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Thu, 6 Jun 2024 22:53:27 +0200 Subject: [PATCH 09/10] Streamline configuration --- src/main.c | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/main.c b/src/main.c index b668326..390ceea 100644 --- a/src/main.c +++ b/src/main.c @@ -238,7 +238,7 @@ static const uint32_t samp_insn[4] = { static uint32_t null, one = 1; -static void rf_rx_start(int rx_pin, int bias_pin, double freq, int rate) +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); @@ -314,7 +314,7 @@ static void rf_rx_start(int rx_pin, int bias_pin, double freq, int rate) false); /* Pacing timer for the sampling script trigger channel. */ - dma_timer_set_fraction(dma_t_samp, 1, CLK_SYS_HZ / rate); + dma_timer_set_fraction(dma_t_samp, 1, CLK_SYS_HZ / sample_rate); /* Sampling trigger channel. */ dma_conf = dma_channel_get_default_config(dma_ch_samp_trig); @@ -349,7 +349,6 @@ static void rf_rx_start(int rx_pin, int bias_pin, double freq, int rate) bias_init(rx_pin, bias_pin); adder_init(); - rx_lo_init(freq); dma_channel_start(dma_ch_rx); dma_channel_start(dma_ch_samp_trig); watch_init(rx_pin); @@ -436,7 +435,7 @@ static void rf_rx(void) int delta = prev_transfers - dma_hw->ch[dma_ch_in_cos].transfer_count; - while (delta < RX_STRIDE * 2) { + while (delta < RX_STRIDE) { delta = prev_transfers - dma_hw->ch[dma_ch_in_cos].transfer_count; sleep_us(1); } @@ -524,7 +523,7 @@ static void run_command(uint8_t cmd, uint32_t arg) } } -static bool check_command(void) +static int check_command(void) { static uint8_t buf[5]; static int pos = 0; @@ -533,7 +532,7 @@ static bool check_command(void) while ((c = getchar_timeout_us(0)) >= 0) { if (0 == pos && 0 == c) - return true; + return 0; buf[pos++] = c; @@ -541,15 +540,16 @@ static bool check_command(void) uint32_t arg = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4]; run_command(buf[0], arg); pos = 0; + return buf[0]; } } - return false; + return -1; } -static void do_rx(int rx_pin, int bias_pin, double freq) +static void do_rx(int rx_pin, int bias_pin) { - rf_rx_start(rx_pin, bias_pin, freq, sample_rate); + rf_rx_start(rx_pin, bias_pin); sleep_us(100); dma_ch_in_cos = dma_claim_unused_channel(true); @@ -583,7 +583,7 @@ static void do_rx(int rx_pin, int bias_pin, double freq) /* Flush the queue */; while (true) { - if (check_command()) + if (0 == check_command()) break; for (int i = 0; i < 32; i++) { @@ -632,27 +632,19 @@ int main() queue_init(&iq_queue, IQ_BLOCK_LEN, 256); + rx_lo_init(INIT_FREQ); + while (true) { - int c = getchar_timeout_us(0); - - if (0 == c) { - continue; - } else if (1 == c) { - /* Tune to a new center frequency */ - uint32_t arg; - fread(&arg, sizeof arg, 1, stdin); - arg = __builtin_bswap32(arg); - + if (check_command() > 0) { static const uint32_t header[3] = { __builtin_bswap32(0x52544c30), __builtin_bswap32(5), __builtin_bswap32(NUM_GAINS) }; fwrite(header, sizeof header, 1, stdout); fflush(stdout); - gain = INIT_GAIN; - sample_rate = INIT_SAMPLE_RATE; - - do_rx(10, 11, arg); + do_rx(10, 11); } + + sleep_ms(10); } } From a2104495c6d997381578cb22905747657855cb62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Thu, 6 Jun 2024 22:54:02 +0200 Subject: [PATCH 10/10] Improve gain handling Now it's possible to set gain in gqrx. --- src/main.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main.c b/src/main.c index 390ceea..1d19dbe 100644 --- a/src/main.c +++ b/src/main.c @@ -40,11 +40,15 @@ static uint32_t lo_sin[LO_WORDS] __attribute__((__aligned__(1 << LO_BITS_DEPTH)) #define RX_STRIDE (2 * IQ_SAMPLES) #define RX_BITS_DEPTH 12 #define RX_WORDS (1 << (RX_BITS_DEPTH - 2)) + +static_assert(RX_STRIDE * 4 < RX_WORDS, "RX_STRIDE * 4 < RX_WORDS"); + static uint32_t rx_cos[RX_WORDS] __attribute__((__aligned__(1 << RX_BITS_DEPTH))); static uint32_t rx_sin[RX_WORDS] __attribute__((__aligned__(1 << RX_BITS_DEPTH))); -#define INIT_GAIN 256 +#define INIT_GAIN 120 #define INIT_SAMPLE_RATE 100000 +#define INIT_FREQ 94600000 #define NUM_GAINS 29 static int gains[NUM_GAINS] = { 0, 9, 14, 27, 37, 77, 87, 125, 144, 157, @@ -457,11 +461,13 @@ static void rf_rx(void) /* * Since the waveform is normally half of the time - * above zero, we could halve once more. + * above zero, we can halve once more. * - * Instead we use 2/3 to provide 1/3 reserve. + * 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 = max_amplitude * 2 / 3; + max_amplitude = max_amplitude / 2; /* * We are allowing the counters to only go as high @@ -482,6 +488,11 @@ static void rf_rx(void) I *= gain; I /= max_amplitude; + if (I > 127) + I = 127; + else if (I < -128) + I = -128; + *blockptr++ = I + 128; uint32_t sin_neg = *sin_ptr++; @@ -496,7 +507,12 @@ static void rf_rx(void) Q *= gain; Q /= max_amplitude; - *blockptr++ = Q + 128; + if (Q > 127) + Q = 127; + else if (Q < -128) + Q = -128; + + *blockptr++ = (uint8_t)Q + 128; } (void)queue_try_add(&iq_queue, block);