Stereo FM transmitter using gnuradio

From Opendigitalradio
Jump to: navigation, search

Stereo FM transmitter using gnuradio.

Tools needed:

  • gnuradio 3.2
  • grc (gnuradio companion)
  • mpg123
  • USRP


In this example we will show how to make a Stereo FM software transmitter. Input is from an mp3 stream for an Internet radio. Look at GRC schema or use directly python code below.

Input file is a unix FIFO file that means that we need to feed it externally from the shell using these commands:

Creation of the FIFO file (once):

mkfifo stream_32k.fifo

Decoding of an mp3 Internet radio station stream, conversion and output of raw linear samples to the FIFO:

mpg123 -r32000 -s  http://maxxima.mine.nu:8000 >stream_32k.fifo

"-r32000" perform sample rate conversion to 32kHz.

Note: This transmitter could be used without USRP by feeding the multiplex stream to to a high samplerate soundcard connected to a hardware FM modulator. To be tested...

GRC Schema

ERROR: An error exists in this program, preemphasis block must be added on audio and not the whole signal, it is advised to use the other FM RDS Stereo transmitter using gnuradio (error on this one will be corrected in the future, orginal source has been lost)

FM transmitter stereo.png

Python code

#!/usr/bin/env python
##################################################
# Gnuradio Python Flow Graph
# Title: FM Stereo Transmitter
# Author: Mathias Coinchon
# Generated: Sat Jan  2 19:06:07 2010
##################################################

from gnuradio import blks2
from gnuradio import gr
from gnuradio.eng_option import eng_option
from gnuradio.gr import firdes
from gnuradio.wxgui import fftsink2
from gnuradio.wxgui import forms
from grc_gnuradio import usrp as grc_usrp
from grc_gnuradio import wxgui as grc_wxgui
from optparse import OptionParser
import wx

class FM_stereo_tx(grc_wxgui.top_block_gui):

	def __init__(self):
		grc_wxgui.top_block_gui.__init__(self, title="FM Stereo Transmitter")

		##################################################
		# Variables
		##################################################
		self.st_gain = st_gain = 10
		self.samp_rate = samp_rate = 32000
		self.pilot_gain = pilot_gain = 80e-3
		self.mpx_rate = mpx_rate = 160000
		self.Mono_gain = Mono_gain = 0.3
		self.FM_freq = FM_freq = 107900000

		##################################################
		# Controls
		##################################################
		_st_gain_sizer = wx.BoxSizer(wx.VERTICAL)
		self._st_gain_text_box = forms.text_box(
			parent=self.GetWin(),
			sizer=_st_gain_sizer,
			value=self.st_gain,
			callback=self.set_st_gain,
			label="Stereo gain",
			converter=forms.float_converter(),
			proportion=0,
		)
		self._st_gain_slider = forms.slider(
			parent=self.GetWin(),
			sizer=_st_gain_sizer,
			value=self.st_gain,
			callback=self.set_st_gain,
			minimum=0,
			maximum=100,
			num_steps=100,
			style=wx.SL_HORIZONTAL,
			cast=float,
			proportion=1,
		)
		self.Add(_st_gain_sizer)
		_pilot_gain_sizer = wx.BoxSizer(wx.VERTICAL)
		self._pilot_gain_text_box = forms.text_box(
			parent=self.GetWin(),
			sizer=_pilot_gain_sizer,
			value=self.pilot_gain,
			callback=self.set_pilot_gain,
			label='pilot_gain',
			converter=forms.float_converter(),
			proportion=0,
		)
		self._pilot_gain_slider = forms.slider(
			parent=self.GetWin(),
			sizer=_pilot_gain_sizer,
			value=self.pilot_gain,
			callback=self.set_pilot_gain,
			minimum=0,
			maximum=1,
			num_steps=100,
			style=wx.SL_HORIZONTAL,
			cast=float,
			proportion=1,
		)
		self.Add(_pilot_gain_sizer)
		_Mono_gain_sizer = wx.BoxSizer(wx.VERTICAL)
		self._Mono_gain_text_box = forms.text_box(
			parent=self.GetWin(),
			sizer=_Mono_gain_sizer,
			value=self.Mono_gain,
			callback=self.set_Mono_gain,
			label="Mono signal gain",
			converter=forms.float_converter(),
			proportion=0,
		)
		self._Mono_gain_slider = forms.slider(
			parent=self.GetWin(),
			sizer=_Mono_gain_sizer,
			value=self.Mono_gain,
			callback=self.set_Mono_gain,
			minimum=0,
			maximum=1,
			num_steps=100,
			style=wx.SL_HORIZONTAL,
			cast=float,
			proportion=1,
		)
		self.Add(_Mono_gain_sizer)
		_FM_freq_sizer = wx.BoxSizer(wx.VERTICAL)
		self._FM_freq_text_box = forms.text_box(
			parent=self.GetWin(),
			sizer=_FM_freq_sizer,
			value=self.FM_freq,
			callback=self.set_FM_freq,
			label="FM Frequency",
			converter=forms.float_converter(),
			proportion=0,
		)
		self._FM_freq_slider = forms.slider(
			parent=self.GetWin(),
			sizer=_FM_freq_sizer,
			value=self.FM_freq,
			callback=self.set_FM_freq,
			minimum=87500000,
			maximum=108000000,
			num_steps=205,
			style=wx.SL_HORIZONTAL,
			cast=float,
			proportion=1,
		)
		self.Add(_FM_freq_sizer)

		##################################################
		# Blocks
		##################################################
		self.Audio_A_resampler = blks2.rational_resampler_fff(
			interpolation=5,
			decimation=1,
			taps=None,
			fractional_bw=None,
		)
		self.Audio_B_resampler = blks2.rational_resampler_fff(
			interpolation=5,
			decimation=1,
			taps=None,
			fractional_bw=None,
		)
		self.MPX_upsampler = blks2.rational_resampler_fff(
			interpolation=4,
			decimation=1,
			taps=None,
			fractional_bw=None,
		)
		self.Pilot = gr.sig_source_f(mpx_rate, gr.GR_SIN_WAVE, 19000, pilot_gain, 0)
		self.band_pass_filter_0 = gr.fir_filter_fff(1, firdes.band_pass(
			st_gain, mpx_rate, 23e3, 53e3, 2e3, firdes.WIN_HAMMING, 6.76))
		self.blks2_fm_preemph_0 = blks2.fm_preemph(fs=mpx_rate, tau=50e-6)
		self.gr_add_xx_0 = gr.add_vff(1)
		self.gr_add_xx_1 = gr.add_vff(1)
		self.gr_file_source_0 = gr.file_source(gr.sizeof_short*2, "/home/mc/FM_demo/stream_32k.fifo", True)
		self.gr_frequency_modulator_fc_0 = gr.frequency_modulator_fc(0.98)
		self.gr_multiply_const_vxx_0 = gr.multiply_const_vcc((32768, ))
		self.gr_multiply_const_vxx_1 = gr.multiply_const_vff((0.00003, ))
		self.gr_multiply_const_vxx_3 = gr.multiply_const_vff((0.00003, ))
		self.gr_multiply_xx_1 = gr.multiply_vff(1)
		self.gr_short_to_float_0 = gr.short_to_float()
		self.gr_short_to_float_1 = gr.short_to_float()
		self.gr_sub_xx_0 = gr.sub_ff(1)
		self.gr_vector_to_streams_0 = gr.vector_to_streams(gr.sizeof_short*1, 2)
		self.low_pass_filter_0 = gr.fir_filter_fff(1, firdes.low_pass(
			Mono_gain, mpx_rate, 15e3, 2e3, firdes.WIN_HAMMING, 6.76))
		self.st_38k_carrier = gr.sig_source_f(mpx_rate, gr.GR_SIN_WAVE, 38000, 30e-3, 0)
		self.usrp_simple_sink_x_0 = grc_usrp.simple_sink_c(which=0, side="B")
		self.usrp_simple_sink_x_0.set_interp_rate(200)
		self.usrp_simple_sink_x_0.set_frequency(-FM_freq, verbose=True)
		self.usrp_simple_sink_x_0.set_gain(0)
		self.usrp_simple_sink_x_0.set_enable(True)
		self.wxgui_fftsink2_0 = fftsink2.fft_sink_f(
			self.GetWin(),
			baseband_freq=0,
			y_per_div=10,
			y_divs=10,
			ref_level=50,
			sample_rate=mpx_rate,
			fft_size=1024,
			fft_rate=30,
			average=False,
			avg_alpha=None,
			title="Baseband spectrum",
			peak_hold=False,
		)
		self.Add(self.wxgui_fftsink2_0.win)

		##################################################
		# Connections
		##################################################
		self.connect((self.Audio_A_resampler, 0), (self.gr_add_xx_1, 0))
		self.connect((self.Audio_B_resampler, 0), (self.gr_add_xx_1, 1))
		self.connect((self.Audio_A_resampler, 0), (self.gr_sub_xx_0, 0))
		self.connect((self.gr_multiply_xx_1, 0), (self.band_pass_filter_0, 0))
		self.connect((self.st_38k_carrier, 0), (self.gr_multiply_xx_1, 1))
		self.connect((self.low_pass_filter_0, 0), (self.gr_add_xx_0, 0))
		self.connect((self.gr_add_xx_1, 0), (self.low_pass_filter_0, 0))
		self.connect((self.gr_frequency_modulator_fc_0, 0), (self.gr_multiply_const_vxx_0, 0))
		self.connect((self.MPX_upsampler, 0), (self.gr_frequency_modulator_fc_0, 0))
		self.connect((self.blks2_fm_preemph_0, 0), (self.MPX_upsampler, 0))
		self.connect((self.gr_add_xx_0, 0), (self.blks2_fm_preemph_0, 0))
		self.connect((self.Pilot, 0), (self.gr_add_xx_0, 2))
		self.connect((self.band_pass_filter_0, 0), (self.gr_add_xx_0, 1))
		self.connect((self.gr_add_xx_0, 0), (self.wxgui_fftsink2_0, 0))
		self.connect((self.gr_sub_xx_0, 0), (self.gr_multiply_xx_1, 0))
		self.connect((self.Audio_B_resampler, 0), (self.gr_sub_xx_0, 1))
		self.connect((self.gr_multiply_const_vxx_0, 0), (self.usrp_simple_sink_x_0, 0))
		self.connect((self.gr_file_source_0, 0), (self.gr_vector_to_streams_0, 0))
		self.connect((self.gr_short_to_float_1, 0), (self.gr_multiply_const_vxx_1, 0))
		self.connect((self.gr_multiply_const_vxx_1, 0), (self.Audio_A_resampler, 0))
		self.connect((self.gr_short_to_float_0, 0), (self.gr_multiply_const_vxx_3, 0))
		self.connect((self.gr_multiply_const_vxx_3, 0), (self.Audio_B_resampler, 0))
		self.connect((self.gr_vector_to_streams_0, 1), (self.gr_short_to_float_1, 0))
		self.connect((self.gr_vector_to_streams_0, 0), (self.gr_short_to_float_0, 0))

	def set_st_gain(self, st_gain):
		self.st_gain = st_gain
		self.band_pass_filter_0.set_taps(firdes.band_pass(self.st_gain, self.mpx_rate, 23e3, 53e3, 2e3, firdes.WIN_HAMMING, 6.76))
		self._st_gain_slider.set_value(self.st_gain)
		self._st_gain_text_box.set_value(self.st_gain)

	def set_samp_rate(self, samp_rate):
		self.samp_rate = samp_rate

	def set_pilot_gain(self, pilot_gain):
		self.pilot_gain = pilot_gain
		self.Pilot.set_amplitude(self.pilot_gain)
		self._pilot_gain_slider.set_value(self.pilot_gain)
		self._pilot_gain_text_box.set_value(self.pilot_gain)

	def set_mpx_rate(self, mpx_rate):
		self.mpx_rate = mpx_rate
		self.band_pass_filter_0.set_taps(firdes.band_pass(self.st_gain, self.mpx_rate, 23e3, 53e3, 2e3, firdes.WIN_HAMMING, 6.76))
		self.low_pass_filter_0.set_taps(firdes.low_pass(self.Mono_gain, self.mpx_rate, 15e3, 2e3, firdes.WIN_HAMMING, 6.76))
		self.Pilot.set_sampling_freq(self.mpx_rate)
		self.st_38k_carrier.set_sampling_freq(self.mpx_rate)
		self.wxgui_fftsink2_0.set_sample_rate(self.mpx_rate)

	def set_Mono_gain(self, Mono_gain):
		self.Mono_gain = Mono_gain
		self.low_pass_filter_0.set_taps(firdes.low_pass(self.Mono_gain, self.mpx_rate, 15e3, 2e3, firdes.WIN_HAMMING, 6.76))
		self._Mono_gain_slider.set_value(self.Mono_gain)
		self._Mono_gain_text_box.set_value(self.Mono_gain)

	def set_FM_freq(self, FM_freq):
		self.FM_freq = FM_freq
		self.usrp_simple_sink_x_0.set_frequency(-self.FM_freq)
		self._FM_freq_slider.set_value(self.FM_freq)
		self._FM_freq_text_box.set_value(self.FM_freq)

if __name__ == '__main__':
	parser = OptionParser(option_class=eng_option, usage="%prog: [options]")
	(options, args) = parser.parse_args()
	tb = FM_stereo_tx()
	tb.Run(True)
Personal tools