Experiment with FM radio reception

This commit is contained in:
Jan Hamal Dvořák 2024-03-03 13:58:33 +01:00
parent 99e3dfbf7f
commit 1a68043531
2 changed files with 809 additions and 5 deletions

763
grc/PicoSDR-WBFM.grc Normal file
View file

@ -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

View file

@ -37,18 +37,33 @@
#include <limits.h>
#include <stdlib.h>
/* 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);