diff --git a/grc/PicoSDR-WBFM.grc b/grc/PicoSDR-WBFM.grc new file mode 100644 index 0000000..d1a93da --- /dev/null +++ b/grc/PicoSDR-WBFM.grc @@ -0,0 +1,763 @@ +options: + parameters: + author: '' + catch_exceptions: 'True' + category: '[GRC Hier Blocks]' + cmake_opt: '' + comment: '' + copyright: '' + description: '' + gen_cmake: 'On' + gen_linking: dynamic + generate_options: qt_gui + hier_block_src_path: '.:' + id: PicoSDR + max_nouts: '0' + output_language: python + placement: (0,0) + qt_qss_theme: '' + realtime_scheduling: '' + run: 'True' + run_command: '{python} -u {filename}' + run_options: prompt + sizing_mode: fixed + thread_safe_setters: '' + title: Pico SDR + window_size: (1000,1000) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 8] + rotation: 0 + state: enabled + +blocks: +- name: bpsk + id: variable_constellation + parameters: + comment: '' + const_points: '[-1-1j, -1+1j, 1+1j, 1-1j]' + dims: '1' + normalization: digital.constellation.AMPLITUDE_NORMALIZATION + precision: '8' + rot_sym: '4' + soft_dec_lut: None + sym_map: '[0, 1, 3, 2]' + type: bpsk + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [280, 8.0] + rotation: 0 + state: true +- name: samp_rate + id: variable + parameters: + comment: '' + value: 1_536_000 // (1 << 3) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [176, 8.0] + rotation: 0 + state: enabled +- name: analog_quadrature_demod_cf_0 + id: analog_quadrature_demod_cf + parameters: + affinity: '' + alias: '' + comment: '' + gain: samp_rate/(2 * math.pi * (samp_rate / 2)) + maxoutbuf: '0' + minoutbuf: '0' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [712, 480.0] + rotation: 0 + state: true +- name: analog_wfm_rcv_pll_0 + id: analog_wfm_rcv_pll + parameters: + affinity: '' + alias: '' + audio_decimation: '4' + comment: '' + deemph_tau: 75e-6 + maxoutbuf: '0' + minoutbuf: '0' + quad_rate: samp_rate + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [704, 552.0] + rotation: 0 + state: true +- name: audio_sink_0 + id: audio_sink + parameters: + affinity: '' + alias: '' + comment: '' + device_name: '' + num_inputs: '2' + ok_to_block: 'True' + samp_rate: samp_rate // 4 + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1000, 560.0] + rotation: 0 + state: true +- name: blocks_add_xx_0 + id: blocks_add_xx + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + num_inputs: '2' + type: float + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1000, 632.0] + rotation: 0 + state: true +- name: blocks_interleaved_char_to_complex_0 + id: blocks_interleaved_char_to_complex + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + scale_factor: '127' + 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: + affinity: '' + alias: '' + comment: '' + en_uvec: 'True' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1000, 32.0] + rotation: 0 + state: true +- name: blocks_probe_rate_0 + id: blocks_probe_rate + parameters: + affinity: '' + alias: '' + alpha: '0.05' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + mintime: '2500' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [704, 16.0] + rotation: 0 + state: true +- name: digital_costas_loop_cc_0 + id: digital_costas_loop_cc + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + order: '2' + use_snr: 'False' + w: 2 * math.pi / 100 + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [704, 296.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] + rotation: 0 + state: true +- name: network_tcp_source_0 + id: network_tcp_source + parameters: + addr: 127.0.0.1 + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + port: '1234' + server: 'True' + type: byte + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [48, 200.0] + rotation: 0 + state: true +- name: qtgui_const_sink_x_0 + id: qtgui_const_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + autoscale: 'False' + axislabels: 'True' + color1: '"blue"' + color10: '"red"' + color2: '"red"' + color3: '"red"' + color4: '"red"' + color5: '"red"' + color6: '"red"' + color7: '"red"' + color8: '"red"' + color9: '"red"' + comment: '' + grid: 'True' + gui_hint: (2, 1, 1, 1) + label1: '' + label10: '' + label2: '' + label3: '' + label4: '' + label5: '' + label6: '' + label7: '' + label8: '' + label9: '' + legend: 'False' + marker1: '0' + marker10: '0' + marker2: '0' + marker3: '0' + marker4: '0' + marker5: '0' + marker6: '0' + marker7: '0' + marker8: '0' + marker9: '0' + name: '""' + nconnections: '1' + size: int(samp_rate // 30) + style1: '0' + style10: '0' + style2: '0' + style3: '0' + style4: '0' + style5: '0' + style6: '0' + style7: '0' + style8: '0' + style9: '0' + tr_chan: '0' + tr_level: '0.0' + tr_mode: qtgui.TRIG_MODE_FREE + tr_slope: qtgui.TRIG_SLOPE_POS + tr_tag: '""' + type: complex + update_time: 1/30 + width1: '1' + width10: '1' + width2: '1' + width3: '1' + width4: '1' + width5: '1' + width6: '1' + width7: '1' + width8: '1' + width9: '1' + xmax: '1' + xmin: '-1' + ymax: '1' + ymin: '-1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1000, 280.0] + rotation: 0 + state: true +- name: qtgui_time_sink_x_0 + id: qtgui_time_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + autoscale: 'False' + axislabels: 'True' + color1: blue + color10: dark blue + color2: red + color3: green + color4: black + color5: cyan + color6: magenta + color7: yellow + color8: dark red + color9: dark green + comment: '' + ctrlpanel: 'False' + entags: 'False' + grid: 'True' + gui_hint: (1, 0, 1, 1) + label1: I + label10: Signal 10 + label2: Q + label3: Signal 3 + label4: Signal 4 + label5: Signal 5 + label6: Signal 6 + label7: Signal 7 + label8: Signal 8 + label9: Signal 9 + legend: 'False' + marker1: '-1' + marker10: '-1' + marker2: '-1' + marker3: '-1' + marker4: '-1' + marker5: '-1' + marker6: '-1' + marker7: '-1' + marker8: '-1' + marker9: '-1' + name: '"IQ"' + nconnections: '1' + size: int(samp_rate // 30) + srate: samp_rate + stemplot: 'False' + style1: '1' + style10: '1' + style2: '1' + style3: '1' + style4: '1' + style5: '1' + style6: '1' + style7: '1' + style8: '1' + style9: '1' + tr_chan: '0' + tr_delay: '0' + tr_level: '0.0' + tr_mode: qtgui.TRIG_MODE_FREE + tr_slope: qtgui.TRIG_SLOPE_POS + tr_tag: '""' + type: complex + update_time: 1/30 + width1: '1' + width10: '1' + width2: '1' + width3: '1' + width4: '1' + width5: '1' + width6: '1' + width7: '1' + width8: '1' + width9: '1' + ylabel: Amplitude + ymax: '1' + ymin: '-1' + yunit: '""' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [704, 192.0] + rotation: 0 + state: true +- name: qtgui_time_sink_x_0_0 + id: qtgui_time_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + autoscale: 'False' + axislabels: 'True' + color1: blue + color10: dark blue + color2: red + color3: green + color4: black + color5: cyan + color6: magenta + color7: yellow + color8: dark red + color9: dark green + comment: '' + ctrlpanel: 'False' + entags: 'False' + grid: 'True' + gui_hint: (1, 1, 1, 1) + label1: "\u0394f" + label10: Signal 10 + label2: Signal 2 + label3: Signal 3 + label4: Signal 4 + label5: Signal 5 + label6: Signal 6 + label7: Signal 7 + label8: Signal 8 + label9: Signal 9 + legend: 'False' + marker1: '-1' + marker10: '-1' + marker2: '-1' + marker3: '-1' + marker4: '-1' + marker5: '-1' + marker6: '-1' + marker7: '-1' + marker8: '-1' + marker9: '-1' + name: '"FM Demodulation"' + nconnections: '1' + size: (samp_rate // 30) + srate: samp_rate // 2 + stemplot: 'False' + style1: '1' + style10: '1' + style2: '1' + style3: '1' + style4: '1' + style5: '1' + style6: '1' + style7: '1' + style8: '1' + style9: '1' + tr_chan: '0' + tr_delay: '0' + tr_level: '0.0' + tr_mode: qtgui.TRIG_MODE_FREE + tr_slope: qtgui.TRIG_SLOPE_POS + tr_tag: '""' + type: float + update_time: 1/30 + width1: '1' + width10: '1' + width2: '1' + width3: '1' + width4: '1' + width5: '1' + width6: '1' + width7: '1' + width8: '1' + width9: '1' + ylabel: Frequency Offset + ymax: '1' + ymin: '-1' + yunit: '""' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1000, 456.0] + rotation: 0 + state: true +- name: qtgui_time_sink_x_0_1 + id: qtgui_time_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + autoscale: 'False' + axislabels: 'True' + color1: blue + color10: dark blue + color2: red + color3: green + color4: black + color5: cyan + color6: magenta + color7: yellow + color8: dark red + color9: dark green + comment: '' + ctrlpanel: 'False' + entags: 'False' + grid: 'True' + gui_hint: (2, 0, 1, 1) + label1: I + label10: Signal 10 + label2: Q + label3: Signal 3 + label4: Signal 4 + label5: Signal 5 + label6: Signal 6 + label7: Signal 7 + label8: Signal 8 + label9: Signal 9 + legend: 'False' + marker1: '-1' + marker10: '-1' + marker2: '-1' + marker3: '-1' + marker4: '-1' + marker5: '-1' + marker6: '-1' + marker7: '-1' + marker8: '-1' + marker9: '-1' + name: '"IQ / Loop"' + nconnections: '1' + size: int(samp_rate // 30) + srate: samp_rate + stemplot: 'False' + style1: '1' + style10: '1' + style2: '1' + style3: '1' + style4: '1' + style5: '1' + style6: '1' + style7: '1' + style8: '1' + style9: '1' + tr_chan: '0' + tr_delay: '0' + tr_level: '0.0' + tr_mode: qtgui.TRIG_MODE_FREE + tr_slope: qtgui.TRIG_SLOPE_POS + tr_tag: '""' + type: complex + update_time: 1/30 + width1: '1' + width10: '1' + width2: '1' + width3: '1' + width4: '1' + width5: '1' + width6: '1' + width7: '1' + width8: '1' + width9: '1' + ylabel: Amplitude + ymax: '1' + ymin: '-1' + yunit: '""' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1000, 352.0] + rotation: 0 + state: true +- name: qtgui_waterfall_sink_x_0_0 + id: qtgui_waterfall_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + axislabels: 'True' + bw: samp_rate + color1: '0' + color10: '0' + color2: '0' + color3: '0' + color4: '0' + color5: '0' + color6: '0' + color7: '0' + color8: '0' + color9: '0' + comment: '' + fc: '0' + fftsize: '1024' + freqhalf: 'True' + grid: 'True' + gui_hint: (0, 0, 1, 2) + int_max: '0' + int_min: '-90' + label1: '' + label10: '' + label2: '' + label3: '' + label4: '' + label5: '' + label6: '' + label7: '' + label8: '' + label9: '' + legend: 'True' + maxoutbuf: '0' + minoutbuf: '0' + name: '"RF"' + nconnections: '1' + showports: 'False' + type: complex + update_time: 1/30 + wintype: window.WIN_BLACKMAN_hARRIS + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [704, 88.0] + rotation: 0 + state: true +- name: qtgui_waterfall_sink_x_0_0_0 + id: qtgui_waterfall_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + axislabels: 'True' + bw: samp_rate // 4 + color1: '0' + color10: '0' + color2: '0' + color3: '0' + color4: '0' + color5: '0' + color6: '0' + color7: '0' + color8: '0' + color9: '0' + comment: '' + fc: '0' + fftsize: '1024' + freqhalf: 'True' + grid: 'True' + gui_hint: (3, 0, 1, 2) + int_max: '0' + int_min: '-90' + label1: '' + label10: '' + label2: '' + label3: '' + label4: '' + label5: '' + label6: '' + label7: '' + label8: '' + label9: '' + legend: 'True' + maxoutbuf: '0' + minoutbuf: '0' + name: '"Audio"' + nconnections: '1' + showports: 'False' + type: float + update_time: 1/30 + wintype: window.WIN_BLACKMAN_hARRIS + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1144, 616.0] + rotation: 0 + state: true + +connections: +- [analog_quadrature_demod_cf_0, '0', qtgui_time_sink_x_0_0, '0'] +- [analog_wfm_rcv_pll_0, '0', audio_sink_0, '0'] +- [analog_wfm_rcv_pll_0, '0', blocks_add_xx_0, '0'] +- [analog_wfm_rcv_pll_0, '1', audio_sink_0, '1'] +- [analog_wfm_rcv_pll_0, '1', blocks_add_xx_0, '1'] +- [blocks_add_xx_0, '0', qtgui_waterfall_sink_x_0_0_0, '0'] +- [blocks_interleaved_char_to_complex_0, '0', analog_quadrature_demod_cf_0, '0'] +- [blocks_interleaved_char_to_complex_0, '0', analog_wfm_rcv_pll_0, '0'] +- [blocks_interleaved_char_to_complex_0, '0', blocks_probe_rate_0, '0'] +- [blocks_interleaved_char_to_complex_0, '0', digital_costas_loop_cc_0, '0'] +- [blocks_interleaved_char_to_complex_0, '0', qtgui_time_sink_x_0, '0'] +- [blocks_interleaved_char_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_char_to_complex_0, '0'] + +metadata: + file_format: 1 + grc_version: 3.10.6.0 diff --git a/src/main.c b/src/main.c index 383950e..91b67ff 100644 --- a/src/main.c +++ b/src/main.c @@ -37,18 +37,33 @@ #include #include +/* FM Radio */ +#if 1 +#define CLK_SYS_HZ (266 * MHZ) +#define BANDWIDTH 1536000 +#define DECIMATION_BITS 3 +#define LPF_ORDER 2 +#define AGC_DECAY_BITS 20 +#define BIAS_STRENGTH 0 +#endif + +/* Digital Data */ +#if 0 #define CLK_SYS_HZ (250 * MHZ) #define BANDWIDTH 1280000 #define DECIMATION_BITS 6 -#define IQ_BLOCK_LEN 64 #define LPF_ORDER 3 #define AGC_DECAY_BITS 16 +#define BIAS_STRENGTH 5 +#endif +#define IQ_BLOCK_LEN 64 #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 <= 3, "LPF_ORDER must be 0-3"); +static_assert(BIAS_STRENGTH >= 0 && BIAS_STRENGTH <= 9, "BIAS_STRENGTH must be 0-9"); #define XOR_ADDR 0x1000 #define LO_COS_ACCUMULATOR (&pio1->sm[2].pinctrl) @@ -111,6 +126,29 @@ 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_mov_not(pio_pins, pio_pins) | pio_encode_sideset(1, 1), + + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(0), /* 1 */ + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(0), /* 2 */ + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(1), /* 4 */ + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(3), /* 8 */ + + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(7), /* 16 */ + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), /* 32 */ + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), /* 48 */ + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), /* 64 */ + + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), + + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), + + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), + pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), pio_encode_nop() | pio_encode_sideset(1, 0) | pio_encode_delay(15), }; @@ -118,7 +156,7 @@ static void bias_init(int in_pin, int out_pin) pio_program_t prog = { .instructions = insn, .length = sizeof(insn) / sizeof(*insn), - .origin = 16, + .origin = 10, }; pio_sm_set_enabled(pio1, 0, false); @@ -134,7 +172,10 @@ static void bias_init(int in_pin, int out_pin) 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); + + int nops[10] = { 20, 12, 8, 6, 5, 4, 3, 2, 1, 0 }; + sm_config_set_wrap(&pc, prog.origin, prog.origin + 1 + nops[BIAS_STRENGTH]); + sm_config_set_clkdiv_int_frac(&pc, 1, 0); pio_sm_init(pio1, 0, prog.origin, &pc); @@ -156,7 +197,7 @@ static void watch_init(int in_pin) pio_program_t prog = { .instructions = insn, .length = 1, - .origin = 31, + .origin = 6, }; pio_sm_set_enabled(pio1, 1, false); @@ -191,7 +232,7 @@ static void send_init(int out_pin) pio_program_t prog = { .instructions = insn, .length = 1, - .origin = 30, + .origin = 5, }; pio_sm_set_enabled(pio1, 1, false);