---
header-includes:
- '`\usepackage{natbib}`{=latex}'
- '`\usepackage{longtable,booktabs}`{=latex}'
- '`\usepackage{amsmath,amssymb}`{=latex}'
- '`\usepackage{lmodern}`{=latex}'
- '`\usepackage{minted}`{=latex}'
- '`\usepackage{parskip}`{=latex}'
- '`\title{Documentació Codi Python}`{=latex}'
- '`\assignatura{Becaris TechLab}`{=latex}'
- '`\numpract{Documentació Codi Python}`{=latex}'
- '`\autor{Artur Blaya}`{=latex}'
- '`\usepackage{fvextra}`{=latex}'
- '`\DefineVerbatimEnvironment{Highlighting}{Verbatim}{breaklines,commandchars=\\\{\}}`{=latex}'
---
###### tags: `BT`
# Introducció
A continuació hi ha una breu explicació de com s'ha desenvolupat el codi del projecte. Per més informació consulteu el codi, on s'ha comentat *línia per línia* el funcionament de l'aplicatiu.
# Objectius
La idea principal és fer un filtre digital que pugui canviar la freqüència central, a temps d'ús. Per tal d'implementar el filtre digital, s'ha usat el programari *GNU RADIO*.
# Esquema GNU RADIO
L'esquema de l'aplicatiu, figura \ref{esquema0}, ha estat dissenyat usant el programari *GNU RADIO*. Està format pels següents elements:
* **Options**: Conté informació del projecte (autor, títol ...)
* **Variable samp_rate**: Freqüència de mostreig global
* **Àudio Source**: Font d'àudio, **important** configurar la font correctament, en el cas del nostre projecte s'ha configurat la font d'àudio per tal que utilitzi el *controlador* ***pulse***, que és el controlador d'àudio del sistema operatiu en el qual s'ha desenvolupat el projecte ( Ubuntu 20.04 )
* **Convertidor Float → Complex**: Converteix la sortida de la font d'àudio, de tipus *float*, al tipus *complex*, per tal de poder processar el senyal en els següents blocs
* **Filtre Pas Banda**: Filtre digital de pas banda centrat a la freqüència que determina la variable *f*. Finestra de tipus *Hamming*, amb una beta de 6.76, amb una amplada de banda de 40 Hz
* Variable f: Variable de tipus *entry* (permet canviar el valor usant un input de la interfície gràfica). Valor per defecte 440 Hz
* **Font de Senyal**: Senyal sinusoidal de freqüència f/2
* Multiplicador de senyals: Al multiplicar el senyal original filtrat, per un cosinus de freqüència f/2, aconseguim baixar una octava el senyal resultant. Útil per poder sentir el senyal obtingut si volem evitar l'efecte de realimentació
* **QT GUI TIME SINK**: Representa l'amplitud del senyal filtrat
* **Convertidor Complex → Float**: Converteix el senyal de tipus *complex* a tipus float, per tal de poder-lo escoltar
* **Audio Sink**: Es fa servir per poder sentir el senyal.

\clearpage
# Codi Esquema GNU RADIO
GNU RADIO permet exportar l'esquema a codi Python, a continuació es troba el codi resultant.
```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: GPL-3.0
#
# GNU Radio Python Flow Graph
# Title: Not titled yet
# Author: artur
# GNU Radio version: 3.8.2.0
from distutils.version import StrictVersion
if __name__ == '__main__':
import ctypes
import sys
if sys.platform.startswith('linux'):
try:
x11 = ctypes.cdll.LoadLibrary('libX11.so')
x11.XInitThreads()
except:
print("Warning: failed to XInitThreads()")
from PyQt5 import Qt
from gnuradio import eng_notation
from gnuradio import qtgui
from gnuradio.filter import firdes
import sip
from gnuradio import analog
from gnuradio import audio
from gnuradio import blocks
from gnuradio import filter
from gnuradio import gr
import sys
import signal
from argparse import ArgumentParser
from gnuradio.eng_arg import eng_float, intx
from gnuradio import qtgui
class app(gr.top_block, Qt.QWidget):
def __init__(self):
gr.top_block.__init__(self, "Not titled yet")
Qt.QWidget.__init__(self)
self.setWindowTitle("Not titled yet")
qtgui.util.check_set_qss()
try:
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))
except:
pass
self.top_scroll_layout = Qt.QVBoxLayout()
self.setLayout(self.top_scroll_layout)
self.top_scroll = Qt.QScrollArea()
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)
self.top_scroll_layout.addWidget(self.top_scroll)
self.top_scroll.setWidgetResizable(True)
self.top_widget = Qt.QWidget()
self.top_scroll.setWidget(self.top_widget)
self.top_layout = Qt.QVBoxLayout(self.top_widget)
self.top_grid_layout = Qt.QGridLayout()
self.top_layout.addLayout(self.top_grid_layout)
self.settings = Qt.QSettings("GNU Radio", "app")
try:
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):
self.restoreGeometry(self.settings.value("geometry").toByteArray())
else:
self.restoreGeometry(self.settings.value("geometry"))
except:
pass
##################################################
# Variables
##################################################
self.samp_rate = samp_rate = 32000
self.f = f = 440
##################################################
# Blocks
##################################################
self._f_tool_bar = Qt.QToolBar(self)
self._f_tool_bar.addWidget(Qt.QLabel('f' + ": "))
self._f_line_edit = Qt.QLineEdit(str(self.f))
self._f_tool_bar.addWidget(self._f_line_edit)
self._f_line_edit.returnPressed.connect(
lambda: self.set_f(int(str(self._f_line_edit.text()))))
self.top_grid_layout.addWidget(self._f_tool_bar)
self.qtgui_time_sink_x_0 = qtgui.time_sink_c(
1024, #size
samp_rate, #samp_rate
"", #name
1 #number of inputs
)
self.qtgui_time_sink_x_0.set_update_time(0.10)
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")
self.qtgui_time_sink_x_0.enable_tags(True)
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")
self.qtgui_time_sink_x_0.enable_autoscale(False)
self.qtgui_time_sink_x_0.enable_grid(False)
self.qtgui_time_sink_x_0.enable_axis_labels(True)
self.qtgui_time_sink_x_0.enable_control_panel(False)
self.qtgui_time_sink_x_0.enable_stem_plot(False)
labels = ['Signal 1', 'Signal 2', 'Signal 3', 'Signal 4', 'Signal 5',
'Signal 6', 'Signal 7', 'Signal 8', 'Signal 9', 'Signal 10']
widths = [1, 1, 1, 1, 1,
1, 1, 1, 1, 1]
colors = ['blue', 'red', 'green', 'black', 'cyan',
'magenta', 'yellow', 'dark red', 'dark green', 'dark blue']
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0, 1.0]
styles = [1, 1, 1, 1, 1,
1, 1, 1, 1, 1]
markers = [-1, -1, -1, -1, -1,
-1, -1, -1, -1, -1]
for i in range(2):
if len(labels[i]) == 0:
if (i % 2 == 0):
self.qtgui_time_sink_x_0.set_line_label(i, "Re{{Data {0}}}".format(i/2))
else:
self.qtgui_time_sink_x_0.set_line_label(i, "Im{{Data {0}}}".format(i/2))
else:
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)
self.blocks_multiply_xx_0 = blocks.multiply_vcc(1)
self.blocks_float_to_complex_0 = blocks.float_to_complex(1)
self.blocks_complex_to_float_0 = blocks.complex_to_float(1)
self.band_pass_filter_0_0 = filter.fir_filter_ccf(
1,
firdes.band_pass(
1,
samp_rate,
f-20,
f+20,
20,
firdes.WIN_HAMMING,
6.76))
self.audio_source_0 = audio.source(samp_rate, 'pulse', True)
self.audio_sink_0 = audio.sink(samp_rate, '', True)
self.analog_sig_source_x_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, f/2, 1, 0, 0)
##################################################
# Connections
##################################################
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_multiply_xx_0, 1))
self.connect((self.audio_source_0, 0), (self.blocks_float_to_complex_0, 0))
self.connect((self.band_pass_filter_0_0, 0), (self.blocks_multiply_xx_0, 0))
self.connect((self.band_pass_filter_0_0, 0), (self.qtgui_time_sink_x_0, 0))
self.connect((self.blocks_complex_to_float_0, 0), (self.audio_sink_0, 0))
self.connect((self.blocks_float_to_complex_0, 0), (self.band_pass_filter_0_0, 0))
self.connect((self.blocks_multiply_xx_0, 0), (self.blocks_complex_to_float_0, 0))
def closeEvent(self, event):
self.settings = Qt.QSettings("GNU Radio", "app")
self.settings.setValue("geometry", self.saveGeometry())
event.accept()
def get_samp_rate(self):
return self.samp_rate
def set_samp_rate(self, samp_rate):
self.samp_rate = samp_rate
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)
self.band_pass_filter_0_0.set_taps(firdes.band_pass(1, self.samp_rate, self.f-20, self.f+20, 20, firdes.WIN_HAMMING, 6.76))
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)
def get_f(self):
return self.f
def set_f(self, f):
self.f = f
Qt.QMetaObject.invokeMethod(self._f_line_edit, "setText", Qt.Q_ARG("QString", str(self.f)))
self.analog_sig_source_x_0.set_frequency(self.f/2)
self.band_pass_filter_0_0.set_taps(firdes.band_pass(1, self.samp_rate, self.f-20, self.f+20, 20, firdes.WIN_HAMMING, 6.76))
def main(top_block_cls=app, options=None):
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):
style = gr.prefs().get_string('qtgui', 'style', 'raster')
Qt.QApplication.setGraphicsSystem(style)
qapp = Qt.QApplication(sys.argv)
tb = top_block_cls()
tb.start()
tb.show()
def sig_handler(sig=None, frame=None):
Qt.QApplication.quit()
signal.signal(signal.SIGINT, sig_handler)
signal.signal(signal.SIGTERM, sig_handler)
timer = Qt.QTimer()
timer.start(500)
timer.timeout.connect(lambda: None)
def quitting():
tb.stop()
tb.wait()
qapp.aboutToQuit.connect(quitting)
qapp.exec_()
if __name__ == '__main__':
main()
```
# Threads
Per tal d'aconseguir el funcionament desitjat, s'ha de poder canviar la freqüència central del filtre. Des del codi Python original, no es pot fer, ja que el codi es basa a declarar mètodes i atributs, fins a arribar a la crida d'una funció bloquejant ***qapp.exec()***. Aquesta funció s'encarrega d'engegar el filtre i es manté bloquejant fins que l'aplicatiu es tanca.
Per tal de poder solucionar aquest inconvenient, hem implementat un thread que pot accedir a la variable de la qual depèn el valor central del filtre. En el cas del nostre esquema, el filtre depèn d'una variable, de tipus *entry*, *f*.
Així doncs, s'ha implementat un thread que neix just avanç que l'aplicació, i s'encarrega exclusivament de canviar el valor del filtre.
Per poder fer notori el canvi en l'aplicatiu, s'ha fet ús d'un mètode que ja venia definit ***final.setf(tb, freqüència Desitjada)*** que s'encarrega de modificar el valor de la variable, *f*, de l'esquema.
En resum, s'ha implementat un thread que depenent d'unes condicions ( esdeveniments produïts externament; selectors, botons ... ) canvia el valor central del filtre fent ús de la funció ***final.setf(tb, ftemp)***.
# Esdeveniments externs
L'encarregat de generar els esdeveniments externs és un Arduino el qual té un potenciòmetre i polsadors. Depenent de la posició del potenciòmetre, i els polsadors, l'Arduino envia un caràcter del 0 al 7 usant el port sèrie. Pel que fa al thread, llegeix constantment el valor del port sèrie, i mou el valor central del filtre seguint els següents casos:
```python
if valor_selector == "0":
f_temp=LA
elif valor_selector == "1":
f_temp=SI
elif valor_selector == "2":
f_temp=DO
elif valor_selector == "3":
f_temp=RE
elif valor_selector == "4":
f_temp=MI
elif valor_selector == "5":
f_temp=FA
elif valor_selector == "6":
f_temp=SOL
elif valor_selector == "7":
f_temp=SOL
final.set_f(tb, f_temp)
```
A continuació es mostren les modificacions que se li han afegit a l'arxiu original Python, per tal que funcioni com s'ha esmentat.
```python
# Freq Definides
LA = 440 * 2
SI = 494 * 2
DO = 523 * 2
RE = 587 * 2
MI = 659 * 2
FA = 698 * 2
SOL = 784 * 2
def main(top_block_cls=final, options=None):
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):
style = gr.prefs().get_string('qtgui', 'style', 'raster')
Qt.QApplication.setGraphicsSystem(style)
qapp = Qt.QApplication(sys.argv)
tb = top_block_cls()
tb.start() # Inicia el filtre
# tb.show() # Arranca la interfície gràfica, Desactivat
exit_event = threading.Event() # Defineix un event per poder finalitzar el thread
def worker(): # Funció principal del Thread
ser = serial.Serial('/dev/ttyACM0') # Configurem el port serie
ser.baudrate = 115200
final.set_f(tb,LA) # Configurem la f INICIAL
while (True):
if exit_event.is_set():
ser.close()
break
valor_selector = ser.readline() # Llegim el valor del port serie
op = valor_selector.decode()+"\n"
# Depenent del valor del selector, canviem la freqüència central del filtre
if op == "0":
final.set_f(tb,LA)
elif op == "1":
final.set_f(tb,SI)
elif op == "2":
final.set_f(tb,DO)
elif op == "3":
final.set_f(tb,RE)
elif op == "4":
final.set_f(tb,MI)
elif op == "5":
final.set_f(tb,FA)
elif op == "6":
final.set_f(tb,SOL)
elif op == "7":
final.set_f(tb,LA)
# Configuració del thread, un cop configurat, es crea un thread
t = threading.Thread(target=worker)
t.start()
# Funcio que tracta el senyal d'exit ctrl-c
def sig_handler(sig=None, frame=None):
exit_event.set() # event que tanca al thread
Qt.QApplication.quit()
signal.signal(signal.SIGINT, sig_handler)
signal.signal(signal.SIGTERM, sig_handler)
timer = Qt.QTimer()
timer.start(500)
timer.timeout.connect(lambda: None)
# Funcio que tracta el tancament de l'App
def quitting():
t.join() # Recuperem els recursos del thread
tb.stop()
tb.wait()
qapp.aboutToQuit.connect(quitting)
qapp.exec_()
if __name__ == '__main__':
main()
```