2  Experiment 4 - Design of Analog Filters

2.1 Brief theory and motivation

Second order filters (or biquard filters) are important since they are the building blocks in the construction of \(N^{th}\) order filters, for \(N > 2\). When \(N\) is odd, the \(N^{th}\) order filter can be realized using \(N - 1\) second order filters and one first order filter. When \(N\) is even, we need \(N-1\) second order filters.

Second order filter can be used to construct four different types of filters. The transfer functions for the different filter types are shown in Section 2.1.1, where \(f_0 = \frac{1}{2 \pi RC}\) and \(H_0\) is the low frequency gain of the transfer function. The filter names are often abbreviated as LPF (lowpass filter), HPF (highpass filter), BPF (bandpass filter), and BSF (bandstop filter).

2.1.1 Transfer funtions of active filters

  • Lowpass Filter (LPF):

\[ \frac{V_{03}}{V_i} = \frac{H_0} {\left( 1 + \frac{s}{\omega_0 Q} + \frac{s^2}{\omega^2_0} \right)} \]

  • Highpass Filter (HPF):

\[ \frac{V_{01}}{V_i} = \frac{\left( H_0 \frac{s^2}{\omega_0^2} \right)} {\left( 1 + \frac{s}{\omega_0 Q} + \frac{s^2}{\omega^2_0} \right)} \]

  • Bandpass Filter (BPF):

\[ \frac{V_{02}}{V_i} = \frac{\left( -H_0 \frac{s}{\omega_0} \right)} {\left( 1 + \frac{s}{\omega_0 Q} + \frac{s^2}{\omega^2_0} \right)} \]

  • Bandstop Filter (BSF):

\[ \frac{V_{01}}{V_i} = \frac{H_0 \left( 1 + \frac{s^2}{\omega_0^2} \right)} {\left( 1 + \frac{s}{\omega_0 Q} + \frac{s^2}{\omega^2_0} \right)} \]

# -*- coding: utf-8 -*-
"""
Magnitude and phase response of active filters

"""

# %% Init
import numpy as np
# https://docs.scipy.org/doc/scipy/reference/signal.html#
import scipy.signal as sig
import matplotlib.pyplot as plt

# %% Definition der TFs
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.tf2zpk.html#scipy.signal.tf2zpk
H0 = 1
Q = 10
f0 = 1e3
w0 = 2*np.pi*f0
f = np.logspace(2, 4, 400)

LPF = sig.lti([H0], [1/w0**2, 1/(w0*Q), 1])
w, magLPF, phaseLPF = LPF.bode(2*np.pi*f)

HPF = sig.lti([H0/w0**2, 0, 0], [1/w0**2, 1/(w0*Q), 1])
w, magHPF, phaseHPF = HPF.bode(2*np.pi*f)

BPF = sig.lti([-H0/w0, 0], [1/w0**2, 1/(w0*Q), 1])
w, magBPF, phaseBPF = BPF.bode(2*np.pi*f)

BSF = sig.lti([H0/w0**2, 0, H0], [1/w0**2, 1/(w0*Q), 1])
w, magBSF, phaseBSF = BSF.bode(2*np.pi*f)

# %% Erzeugen des Bode-Diagramms
plt.subplot(2, 1, 1)
plt.semilogx(f, magLPF, label='LPF')
plt.semilogx(f, magHPF, label='HPF')
plt.semilogx(f, magBPF, label='BPF')
plt.semilogx(f, magBSF, label='BSF')
plt.ylabel(r'$\vert H_v \vert$/dB')
plt.grid()
plt.legend()
plt.subplot(2, 1, 2)
plt.semilogx(f, phaseLPF, label='LPF')
plt.semilogx(f, phaseHPF, label='HPF')
plt.semilogx(f, phaseBPF, label='BPF')
plt.semilogx(f, phaseBSF, label='BSF')
plt.ylabel(r'$arg(H_v)$/deg')
plt.xlabel('f/Hz')
plt.grid()
plt.show()

In this experiment, we will describe a universal active filter, which provides all four filter functionalities. Figure 2.1 shows a second order universal filter realized using two integrators. Note that there are different outputs of the circuit that realize LPF, HPF, BPF and BSF functions.

Figure 2.1: A second-order universal active filter

2.2 Specification

Design a band-pass (BPF) and a band-stop (BSF) filter. * For the BPF, assume \(f_0 = 1\,kHz\) and \(Q=1\). * For the BSF, assume \(f_0 = 10\,kHz\) and \(Q=10\).

2.3 Measurements to be taken

2.3.1 Steady-state response

Apply a square wave input (Try \(f\) = 1 kHz and \(f\) = 10 kHz to both BPF and BSF circuits and observe the outputs.

  • Band-pass output will output the fundamental frequency of the square wave multiplied by the gain at the centre frequency. The amplitude at this frequency is given by \(\frac{4 V_p}{\pi H_0 Q}\), where \(V_p\) is the peak amplitude of the input square wave.

  • The band-stop filter’s output will carry all the harmonics of the square wave, other than fundamental. This illustrates the application of BSF as a distortion analyzer.

2.3.2 Frequency response

Apply a sine wave input and obtain the magnitude and the phase response.

2.4 What you should submit

2.4.1 Simulation with KiCad (ngspice)

Simulate the circuits and obtain the steady-state response and frequency response.

2.4.2 Measurements with Redpitaya STEMlab

Take the plots of the steady-state and frequency response with STEMlab oscilloscope or SCPI and compare it with simulation results. For frequency response, apply a sine wave input and vary its input frequency to obtain the phase and magnitude error.

2.4.2.1 SCPI mit Python


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Measurements for Bode plots
Input signal: DF_IN1
Output signal: DF_IN2

@author: Mirco Meiners (HSB)
"""

# %% Init
import time
# from datetime import datetime
import redpitaya_scpi as scpi
import numpy as np
import pandas as pd
import scipy.io as sio
import matplotlib.pyplot as plt

# %% Connection params
# IP of your STEMlab
IP = '192.168.111.182'
rp_s = scpi.scpi(IP)

# %% Measurement / Data Accquisition
DF_IN1 = pd.DataFrame()
DF_IN2 = pd.DataFrame()

# Parameters
func = 'SINE'
ampl = 0.2
offset = 0.0
freqs = np.arange(500, 2000, 100)


for freq in freqs:

    rp_s.tx_txt('GEN:RST')  # Signal Generator reset
    rp_s.tx_txt('SOUR1:FUNC ' + str(func).upper())  # Wave form
    rp_s.tx_txt('SOUR1:VOLT ' + str(ampl))  # Magnitude
    rp_s.tx_txt('SOUR1:VOLT:OFFS ' + str(offset))  # Offset
    rp_s.tx_txt('SOUR1:FREQ:FIX ' + str(freq))  # Frequency
    rp_s.tx_txt('OUTPUT1:STATE ON')  # Output
    rp_s.tx_txt('SOUR1:TRig:INT')
    time.sleep(1)

    # Trigger
    rp_s.tx_txt('ACQ:RST')  # Input reset
    rp_s.tx_txt('ACQ:DEC 64')  # Decimation
    rp_s.tx_txt('ACQ:TRIG:LEV 0.5')  # Trigger level
    rp_s.tx_txt('ACQ:TRIG:DLY 8192')  # Delay
    rp_s.tx_txt('ACQ:START')  # Start measurement
    rp_s.tx_txt('ACQ:TRIG NOW')

    # Input IN1
    time.sleep(0.1)  # in seconds
    rp_s.tx_txt('ACQ:SOUR1:DATA?')  # Readout buffer IN1
    IN1str = rp_s.rx_txt()
    IN1str = IN1str.strip('{}\n\r').replace("  ", "").split(',')
    IN1 = np.array(list(map(float, IN1str)))
    DF_IN1[str(freq)] = IN1

    # Input IN2
    time.sleep(0.1)  # in seconds
    rp_s.tx_txt('ACQ:SOUR2:DATA?')  # Readout buffer IN2
    IN2str = rp_s.rx_txt()
    IN2str = IN2str.strip('{}\n\r').replace("  ", "").split(',')
    IN2 = np.array(list(map(float, IN2str)))
    DF_IN2[str(freq)] = IN2

    rp_s.tx_txt('OUTPUT2:STATE OFF')

# %% Data storage
Data_IN1 = 'data/DF_IN1'  # + str(datetime.now().strftime('%Y-%m-%d_%H_%M'))
Data_IN2 = 'data/DF_IN2'  #+ str(datetime.now().strftime('%Y-%m-%d_%H_%M'))

# %% Store data on disk as comma-seperated-values
DF_IN1.to_csv(Data_IN1 + '.csv', index=False)
DF_IN2.to_csv(Data_IN2 + '.csv', index=False)

# %% Store data on disk as excel sheet
# with pd.ExcelWriter(Data_IN + '.xlsx') as writer:
#     DF_IN1.to_excel(writer, sheet_name='IN1', index=False)
#     DF_IN2.to_excel(writer, sheet_name='IN2', index=False)

# DF_IN1.to_excel(Data_IN1 + '.xlsx', index=False)
# DF_IN2.to_excel(Data_IN2 + '.xlsx', index=False)

# %% Store data on disk as mat-file
# Ref. https://blog.finxter.com/5-best-ways-to-convert-pandas-dataframe-to-matlab/

# Convert the DataFrame to a dictionary with col names as keys
# dict_IN1 = DF_IN1.to_dict('list')
# dict_IN2 = DF_IN2.to_dict('list')

# Save the dictionary as a .mat file
# sio.savemat(DF_IN1 + '.mat', dict_IN1)
# sio.savemat(DF_IN2 + '.mat', dict_IN2)

# %% Store data on disk as HDF5
# DF_IN1.to_hdf(Data_IN1 + ".h5", "table", append=True)
# DF_IN2.to_hdf(Data_IN2 + ".h5", "table", append=True)

# %% Store data on disk as apache parquet
# DF_IN1.to_parquet(Data_IN1 + ".parquet", index=False)
# DF_IN2.to_parquet(Data_IN2 + ".parquet", index=False)

# %% Store data on disk as apache feather
# DF_IN1.to_feather(Data_IN1 + ".feather")
# DF_IN2.to_feather(Data_IN2 + ".feather")

# %% Test plot
# plt.plot(DF_IN1['900'], label='IN1')
# plt.plot(DF_IN2['900'], label='IN2')
# plt.legend()
# plt.show()

2.4.2.2 SCPI mit MATLAB


%% STEMlab Measurements for Bode plots
% @author: Mirco Meiners (HSB)
% Input signal: DF_IN1
% Output signal: DF_IN2
clear;

%% Define Red Pitaya as TCP client object
IP = '192.168.111.183';  % IP of your Red Pitaya ...
port = 5000;
RP = tcpclient(IP, port);
RP.InputBufferSize = 16384*32;

%% Open connection to Red Pitaya
RP.ByteOrder = "big-endian";
configureTerminator(RP, "CR/LF");

flush(RP);

%% Generate continous signal
func = "SINE";  % {sine, square, triangle, sawu, sawd, pwm}
ampl = 0.5;  % Set amplitude
offset = 0.0;  % Set offset
freqs = [500:100:2000];  % Set frequencies


%% Loop to measure multiple tones
for n = 1:length(freqs)
    % Send SCPI command to Red Pitaya to turn ON generator
    writeline(RP,'GEN:RST');  % Reset Generator
    writeline(RP,strcat("SOUR1:FUNC ", func));  % Set function of output signal
    writeline(RP,strcat("SOUR1:VOLT ", num2str(ampl)));  % Set amplitude
    writeline(RP,strcat("SOUR1:VOLT:OFFS ", num2str(offset)));  % Set offset
    writeline(RP,strcat("SOUR1:FREQ:FIX ", num2str(freqs(n))));  % Set frequency
    writeline(RP,'OUTPUT1:STATE ON');  % Turn on output OUT2
    
    writeline(RP,'SOUR1:TRig:INT');  % Generate trigger
    
    pause(1);

    % Trigger
    writeline(RP,'ACQ:RST');  % Input reset
    writeline(RP,'ACQ:DATA:FORMAT ASCII')
    writeline(RP,'ACQ:DATA:UNITS VOLTS')
    writeline(RP,'ACQ:DEC 64');  % Decimation 64
    writeline(RP,'ACQ:TRIG:LEV 0.5');  % Trigger level
    
    % Set trigger delay to 0 samples
    % 0 samples delay sets trigger to the center of the buffer
    % Signal on your graph will have the trigger in the center (symmetrical)
    % Samples from left to the center are samples before trigger
    % Samples from center to the right are samples after trigger
    
    writeline(RP,'ACQ:TRIG:DLY 8192');  % Delay
    writeline(RP,'ACQ:SOUR1:GAIN LV');  % Sets gain to LV/HV (should be the same as jumpers)
    writeline(RP,'ACQ:SOUR2:GAIN LV');  % Sets gain to LV/HV (should be the same as jumpers)
    
    % Start & Trigger
    % Trigger source setting must be after ACQ:START
    % Set trigger to source 1 positive edge

    writeline(RP,'ACQ:START');

    % After acquisition is started some time delay is needed in order to acquire fresh samples in the buffer
    pause(1);
    % Here we have used time delay of one second, but you can calculate the exact value by taking into account buffer
    % length and sampling rate

    writeline(RP,'ACQ:TRIG NOW');  % Instant data aquisition
        
    % Wait for trigger
    % Until trigger is true wait with acquiring
    % Be aware of the while loop if trigger is not achieved
    % Ctrl+C will stop code execution in MATLAB

    while 1
       trig_rsp = writeread(RP,'ACQ:TRIG:STAT?');
       if strcmp('TD',trig_rsp(1:2))  % Read only TD
           break;
       end
    end

    % Read data from buffer
    IN1 = writeread(RP,'ACQ:SOUR1:DATA?');
    IN2 = writeread(RP,'ACQ:SOUR2:DATA?');

    % Convert values to numbers.
    % The first character in string is "{" and the last 3 are 2 spaces and "}".
    
    IN1_num = str2num(IN1(1, 2:length(IN1)-3));
    DF_IN1(:,n) = IN1_num';

    IN2_num = str2num(IN2(1, 2:length(IN2)-3));
    DF_IN2(:,n) = IN2_num';
    
    % Turn off generator OUT1
    writeline(RP,'OUTPUT1:STATE OFF');
end

    
%% Close connection to Red Pitaya
clear RP;


%% Save data as mat file
save('./data/IN_INT.mat', 'DF_IN1', 'DF_IN2');
% save('./data/IN1_INT.mat', 'DF_IN1');
% save('./data/IN2_INT.mat', 'DF_IN2');

%% Save data as parquet file
% parquet data is of type table, no matrix operations
% parquetwrite('data/IN1_INT.parquet', array2table(DF_IN1));
% parquetwrite('data/IN2_INT.parquet', array2table(DF_IN2));

%% Save data as excel sheet
% data is table data, no matrix operations
% writematrix(DF_IN1, './data/IN1_UB_VBS_mat.xlsx');
% writematrix(DF_IN1, './data/IN2_UB_VBS_mat.xlsx');
% writematrix(DF_IN1, './data/IN_UB_VBS_VBP_mat.xlsx', 'Sheet', 1);
% writematrix(DF_IN2, './data/IN_UB_VBS_VBP_mat.xlsx', 'Sheet', 2);

2.5 Exercise Set 4

2.5.1 3rd order butterworth

Higher order filters are normally designed by cascading second order filters and, if needed, one first-order filter. Design a third order Butterworth lowpass filter using Python or Matlab and obtain the frequency response as well as the transient response of the filter. The specifications are bandwidth of the filter \(f_0 = 10\,kHz\) and gain \(H_0=10\).

2.5.2 Notch filter

Design a notch filter (band-stop filter) to eliminate the \(50\,H\)z power life frequency. In order to test this circuit, synthesize a waveform \(v(t) = \sin(100\pi t) + 0.1 \sin(200 \pi t)\) Volts and use it as the input to the filter. What output did you obtain?