481 lines
15 KiB
TeX
481 lines
15 KiB
TeX
\documentclass{article}
|
|
|
|
\usepackage[margin=1in]{geometry}
|
|
\usepackage{graphicx}
|
|
\usepackage{minted}
|
|
\usepackage{amsmath}
|
|
\usepackage{hyperref}
|
|
\newcommand{\RE}[1]{\mathrm{Re} \left \{ #1 \right \}}
|
|
\newcommand{\IM}[1]{\mathrm{Im} \left \{ #1 \right \}}
|
|
\usepackage{longtable,booktabs,array}
|
|
\usepackage{siunitx}
|
|
|
|
\usepackage{commath}
|
|
\newcommand\numberthis{\addtocounter{equation}{1}\tag{\theequation}}
|
|
\usepackage{amsfonts}
|
|
\usepackage{cancel}
|
|
|
|
\usepackage[T1]{fontenc}
|
|
\usepackage{framed}
|
|
\usepackage{caption}
|
|
\usepackage{gensymb}
|
|
|
|
\title{Lab 3, EE3150}
|
|
\author{Martin Kennedy and DJ}
|
|
|
|
\begin{document}
|
|
\maketitle
|
|
|
|
|
|
\section{Introduction}
|
|
In this lab, we investigate the response of a circuit (seen in figure
|
|
\ref{img:circuit_diagram}) to different sinusoidal signals. We apply
|
|
both symbolic analysis and numeric analysis (simulation) to affirm
|
|
that this causal LTI system will always generate a sinusoidal response
|
|
of the same frequency as that provided to it.
|
|
|
|
\begin{figure}[h]
|
|
\caption{A diagram of our two-stage RC circuit, with a fixed-gain amplifier between the two stages to act as a buffer}
|
|
\label{img:circuit_diagram}
|
|
\centering
|
|
\includegraphics[width=0.8\textwidth]{circuit_diagram}
|
|
\end{figure}
|
|
|
|
\section{Discussion}
|
|
A number of different sinusoidal input signals are considered.
|
|
|
|
From the Lab3 description, we have:
|
|
|
|
\subsubsection{Lab3 section 2.2.a.}
|
|
|
|
$x(t) = A \sin(\omega t)$, for $A = 1 V_{pp}, f \in \left \{1, 5, 10, 100 \right \} \SI{}{\hertz}$.
|
|
|
|
\subsubsection{Lab3 section 2.2.b.}
|
|
|
|
$x_1(t) + x_2(t)$, with $x_1(t) = A_1 \sin(\omega_1 t)$,
|
|
$x_2(t) = A_2 \sin(\omega_2 t)$, and $(f_1, f_2, A_1, A_2)$ with such
|
|
values as
|
|
$(\SI{50}{\hertz}, \SI{100}{\hertz}, \SI{1}{V}, \SI{0.5}{V})$ and
|
|
$(\SI{50}{\hertz}, \SI{100}{\hertz}, \SI{2}{V}, \SI{4}{V})$.
|
|
|
|
\subsubsection{Lab3 section 2.2.c.}
|
|
|
|
As with 2.2.a, but with $x(t) = A \sin(\omega t - 0.025)$, for
|
|
$A = 1 V_{pp}, f = \SI{10}{\hertz}$ (i.e., a 0.025 second delay).
|
|
|
|
\section{Measurement data and/or Results}
|
|
|
|
\subsubsection{Lab3 section 2.2.a.}
|
|
|
|
Figures \ref{img:1hz_show}, \ref{img:5hz_show}, \ref{img:10hz_show}
|
|
and \ref{img:100hz_show} depict a theoretical $1V_{pp}$ input at
|
|
$f \in \left \{1, 5, 10, 100 \right \} \SI{}{\hertz}$, with output
|
|
calculated first by convolution with the impulse response, then by
|
|
solution of the representative ODE, and finally our experimentally
|
|
measured input and output.
|
|
|
|
This yields the following RMSE values in Table \ref{table:rmse1}.
|
|
|
|
\begin{longtable}[]{@{}ll@{}}
|
|
\toprule
|
|
\caption {RMSE of our measured response versus our convolution prediction}
|
|
\label{table:rmse1}
|
|
\endhead
|
|
\bottomrule
|
|
\endlastfoot
|
|
|
|
Signal & RMSE, when compared to $(h*x)$ \\
|
|
f = \SI{1}{\hertz} & 3.021 \\
|
|
f = \SI{5}{\hertz} & 2.957 \\
|
|
f = \SI{10}{\hertz} & 2.691 \\
|
|
f = \SI{100}{\hertz} & 0.295 \\
|
|
\end{longtable}
|
|
|
|
|
|
\begin{figure}[h]
|
|
\caption{A $1V_{pp}$ @ $\SI{1}{\hertz}$ input to our circuit, in theory and in practice, and respective outputs}
|
|
\label{img:1hz_show}
|
|
\centering
|
|
\includegraphics[width=0.8\textwidth]{img/1hz_show}
|
|
\end{figure}
|
|
|
|
\begin{figure}[h]
|
|
\caption{A $1V_{pp}$ @ $\SI{5}{\hertz}$ input to our circuit, in theory and in practice, and respective outputs}
|
|
\label{img:5hz_show}
|
|
\centering
|
|
\includegraphics[width=0.8\textwidth]{img/5hz_show}
|
|
\end{figure}
|
|
|
|
\begin{figure}[h]
|
|
\caption{A $1V_{pp}$ @ $\SI{10}{\hertz}$ input to our circuit, in theory and in practice, and respective outputs}
|
|
\label{img:10hz_show}
|
|
\centering
|
|
\includegraphics[width=0.8\textwidth]{img/10hz_show}
|
|
\end{figure}
|
|
|
|
\begin{figure}[h]
|
|
\caption{A $1V_{pp}$ @ $\SI{100}{\hertz}$ input to our circuit, in theory and in practice, and respective outputs}
|
|
\label{img:100hz_show}
|
|
\centering
|
|
\includegraphics[width=0.8\textwidth]{img/100hz_show}
|
|
\end{figure}
|
|
|
|
This section was generated using the function
|
|
\texttt{run\_2\_2\_a\_conv\_pred()} as seen in the Source code section
|
|
below, as of commit cad74a446ac2ebbde58e587b93dc2d2b53da3404.
|
|
|
|
\subsubsection{Lab3 section 2.2.b.}
|
|
This next section was more difficult. Because of the very coarse
|
|
measurement, synchronizing automatically eluded the techniques we had
|
|
on hand in Python, so calculating sums was very difficult. Had we
|
|
known this would be so difficult in software, a hardware approach
|
|
would have been taken to trigger at zero-crossings and ensure all
|
|
samples were recorded in a synchronous manner in the first place.
|
|
|
|
\begin{figure}[h]
|
|
\caption{A combination of sinusoids}
|
|
\label{img:2_2_b_combined}
|
|
\centering
|
|
\includegraphics[width=0.8\textwidth]{img/2_2_b_combined}
|
|
\end{figure}
|
|
|
|
The imagery in this section was created using the function
|
|
\texttt{run\_2\_2\_b\_show()} from the Source code section below.
|
|
|
|
\subsubsection{Lab3 section 2.2.c.}
|
|
Incomplete.
|
|
|
|
\section{Discussion of Measurements, experiments and/or simulations}
|
|
|
|
Regarding section 2.2.a., the RMSE values were enormous, likely
|
|
because of a failure to assemble the circuit correctly. In figure
|
|
\ref{img:circuit_assembled}, we can clearly see a pair of
|
|
$\SI{4.7}{\kohm}$ resistors R4 and R5; one should instead have been a
|
|
$\SI{1}{\kohm}$ resistor. This would have impacted our gain value from
|
|
the buffer, which we calculate as $1 + \frac{R5}{R4} \approx 5$.
|
|
|
|
\begin{figure}[h]
|
|
\caption{Our circuit, assembled.}
|
|
\label{img:circuit_assembled}
|
|
\centering
|
|
\includegraphics[width=0.8\textwidth]{img/circuit_assembled}
|
|
\end{figure}
|
|
|
|
|
|
\subsection{Source code}
|
|
|
|
The source code for this lab, and the source code below, can be found
|
|
at \url{https://git.laboratoryb.org/hurricos/EE3150-LAB3}.
|
|
|
|
\begin{minted}[xleftmargin=\parindent,linenos]{python}
|
|
# ==== Lab #3: Convolution & Sinusoids ====
|
|
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
from scipy.integrate import solve_ivp
|
|
import csv
|
|
import io
|
|
|
|
# Components (same as Lab #2)
|
|
R1, C1 = 10e3, 1e-6
|
|
R2, C2 = 4.7e3, 1e-6
|
|
tau1, tau2 = R1*C1, R2*C2
|
|
R4, R5 = 1e3, 4.7e3
|
|
G = 1 + R5/R4
|
|
|
|
# Time grid
|
|
dt = 0.0001
|
|
t = np.arange(0, 5 + dt/2, dt) # Includes the final element.
|
|
N = t.size
|
|
|
|
# Impulse responses (causal)
|
|
h1 = (1/tau1) * np.exp(-t/tau1) # u(t) implicit (0 for t<0)
|
|
h2 = (1/tau2) * np.exp(-t/tau2)
|
|
|
|
# Overall impulse response for cascaded stages (discrete conv scaled by dt)
|
|
h = G * dt * np.convolve(h1, h2, mode='full')
|
|
|
|
# Helper: numeric convolution with step dt (Riemann sums)
|
|
def conv_num(a, b, dt):
|
|
"""Full-length discrete convolution approximating integrals via Riemann sums with step dt."""
|
|
return np.convolve(a, b, mode='full') * dt
|
|
|
|
def plot_single_sinusoid(f, dur, dt, do_plot):
|
|
# ======================
|
|
# 1) Single sinusoids
|
|
# ======================
|
|
A = 1.0
|
|
xmax = dur
|
|
t = np.arange(0, dur + dt/2, dt) # Includes the final element.
|
|
N = t.size
|
|
|
|
plt.figure(num='Window')
|
|
plt.clf()
|
|
w = 2*np.pi*f
|
|
x = A * np.sin(w*t) * (t >= 0)
|
|
y_full = conv_num(x, h, dt)
|
|
y = y_full[:N] # keep same length as x
|
|
|
|
# ODE: tau1*tau2*y'' + (tau1+tau2)*y' + y = G*x(t)
|
|
def rhs(ti, z):
|
|
x_t = A*np.sin(w*ti) * (ti >= 0.0)
|
|
return [
|
|
z[1],
|
|
(G*x_t - (tau1 + tau2)*z[1] - z[0]) / (tau1*tau2)
|
|
]
|
|
z0 = [0.0, 0.0] # zero initial conditions
|
|
sol = solve_ivp(rhs, (t[0], t[-1]), z0, t_eval=t, rtol=1e-9, atol=1e-12)
|
|
y_ode = sol.y[0]
|
|
|
|
ax = plt.subplot(1, 1, 1)
|
|
ax.plot(t, x, label='input')
|
|
ax.plot(t, y, label='output (h*x)')
|
|
ax.plot(t, y_ode, '--', label='output (ODE)', color='r')
|
|
ax.grid(True)
|
|
ax.set_xlabel('t (s)')
|
|
ax.set_ylabel('V')
|
|
ax.set_xlim([0, xmax])
|
|
ax.set_title(f'Single sinusoid: f = {f:g} Hz')
|
|
ax.legend()
|
|
xmax = xmax/2
|
|
plt.tight_layout()
|
|
if (do_plot):
|
|
plt.show()
|
|
|
|
return plt, t, x, y
|
|
|
|
def plot_single_sinusoids():
|
|
# ======================
|
|
# 1) Single sinusoids
|
|
# ======================
|
|
A = 1.0
|
|
freqs = [1, 5, 10, 50]
|
|
xmax = 2.0
|
|
|
|
plt.figure(1)
|
|
plt.clf()
|
|
for k, f in enumerate(freqs, start=1):
|
|
w = 2*np.pi*f
|
|
x = A * np.sin(w*t) * (t >= 0)
|
|
y_full = conv_num(x, h, dt)
|
|
y = y_full[:N] # keep same length as x
|
|
|
|
# ODE: tau1*tau2*y'' + (tau1+tau2)*y' + y = G*x(t)
|
|
def rhs(ti, z):
|
|
x_t = A*np.sin(w*ti) * (ti >= 0.0)
|
|
return [
|
|
z[1],
|
|
(G*x_t - (tau1 + tau2)*z[1] - z[0]) / (tau1*tau2)
|
|
]
|
|
z0 = [0.0, 0.0] # zero initial conditions
|
|
sol = solve_ivp(rhs, (t[0], t[-1]), z0, t_eval=t, rtol=1e-9, atol=1e-12)
|
|
y_ode = sol.y[0]
|
|
|
|
ax = plt.subplot(len(freqs), 1, k)
|
|
ax.plot(t, x, label='input')
|
|
ax.plot(t, y, label='output (h*x)')
|
|
ax.plot(t, y_ode, '--', label='output (ODE)', color='r')
|
|
ax.grid(True)
|
|
ax.set_xlabel('t (s)')
|
|
ax.set_ylabel('V')
|
|
ax.set_xlim([0, xmax])
|
|
ax.set_title(f'Single sinusoid: f = {f:g} Hz')
|
|
ax.legend()
|
|
xmax = xmax/2
|
|
plt.tight_layout()
|
|
plt.show()
|
|
|
|
#plot_single_sinusoids()
|
|
#exit()
|
|
|
|
def plot_sum_sinusoids():
|
|
# =========================================
|
|
# 2) Sum of sinusoids
|
|
# =========================================
|
|
A1, f1 = 1.0, 5.0
|
|
A2, f2 = 0.5, 20.0
|
|
x1 = A1 * np.sin(2*np.pi*f1*t) * (t >= 0)
|
|
x2 = A2 * np.sin(2*np.pi*f2*t) * (t >= 0)
|
|
alpha, beta = 1.5, 2.0
|
|
x = alpha*x1 + beta*x2
|
|
Nx = x.size
|
|
|
|
y = conv_num(x, h, dt)[:Nx]
|
|
y1 = conv_num(x1, h, dt)[:Nx]
|
|
y2 = conv_num(x2, h, dt)[:Nx]
|
|
|
|
plt.figure(2)
|
|
plt.clf()
|
|
plt.plot(t, y, label='y = h*(x1+x2)')
|
|
plt.plot(t, alpha*y1 + beta*y2, '--', label='alpha*y1 + beta*y2')
|
|
plt.grid(True)
|
|
plt.xlabel('t (s)')
|
|
plt.ylabel('V')
|
|
plt.xlim([0, 1])
|
|
plt.legend()
|
|
plt.title('Linearity check: y[u1+u2] vs y[u1]+y[u2]')
|
|
plt.show()
|
|
|
|
#plot_sum_sinusoids()
|
|
#exit()
|
|
|
|
def plot_time_invariance():
|
|
# ==================================
|
|
# 3) Time invariance (delay \Delta)
|
|
# ==================================
|
|
A1, f1 = 1.0, 5.0
|
|
A2, f2 = 0.5, 20.0
|
|
x1 = A1 * np.sin(2*np.pi*f1*t) * (t >= 0)
|
|
x2 = A2 * np.sin(2*np.pi*f2*t) * (t >= 0)
|
|
alpha, beta = 1.5, 2.0
|
|
x = alpha*x1 + beta*x2
|
|
Nx = x.size
|
|
Delta = 0.1
|
|
nD = int(round(Delta/dt))
|
|
x_del = np.concatenate([np.zeros(nD), x[:-nD]]) if nD < Nx else np.zeros_like(x)
|
|
y_del = conv_num(x_del, h, dt)[:x_del.size]
|
|
y = conv_num(x, h, dt)[:Nx]
|
|
|
|
plt.figure(3)
|
|
plt.clf()
|
|
plt.plot(t, y, label='y(t)')
|
|
plt.plot(t, np.concatenate([np.zeros(nD), y[:-nD]]), label=r'$y(t-\Delta)$')
|
|
plt.plot(t, y_del, '--', label=r'$h*u(t-\Delta)$')
|
|
plt.grid(True)
|
|
plt.xlabel('t (s)')
|
|
plt.ylabel('V')
|
|
plt.xlim([0, 1])
|
|
plt.legend()
|
|
plt.title('Time invariance check')
|
|
plt.tight_layout()
|
|
plt.show()
|
|
|
|
#plot_time_invariance()
|
|
#exit()
|
|
|
|
def plot_commutativity():
|
|
# =======================================
|
|
# 4) Commutativity of stages (h1*h2)
|
|
# =======================================
|
|
h12 = G * dt * np.convolve(h1, h2, mode='full')
|
|
h21 = G * dt * np.convolve(h2, h1, mode='full')
|
|
rmse = np.sqrt(np.mean((h12 - h21)**2))
|
|
|
|
plt.figure(4)
|
|
plt.clf()
|
|
plt.plot(t, h12[:N], label=r'$h_1 \ast h_2$')
|
|
plt.plot(t, h21[:N], '--', label=r'$h_2 \ast h_1$')
|
|
plt.grid(True)
|
|
plt.xlim([0, 0.1])
|
|
plt.xlabel('t (s)')
|
|
plt.ylabel('V')
|
|
plt.legend()
|
|
plt.title(rf'Commutativity - RMSE of $ e = h_{{12}} - h_{{21}}: \sqrt{{\mathrm{{mean}}(e^2)}} = {rmse:.3e} $')
|
|
|
|
plt.tight_layout()
|
|
plt.show()
|
|
|
|
#plot_commutativity()
|
|
#exit()
|
|
|
|
def load_scope_data(filename):
|
|
f = open(filename, 'r', newline='')
|
|
reader = csv.reader(f)
|
|
# 3. Read the data rows
|
|
timearr, v1arr, v2arr = [], [], []
|
|
trigger = False
|
|
last_last_v1 = 1
|
|
last_v1 = 1
|
|
for row in reader:
|
|
# Check if the row is not empty
|
|
if row:
|
|
try:
|
|
# Extract Time (column index 1) and Voltage (column index 2)
|
|
time = float(row[1])
|
|
v1 = float(row[2])
|
|
v2 = float(row[3])
|
|
|
|
if v1 > 0.0 and last_v1 <= 0.0 and last_last_v1 <= 0.0:
|
|
trigger = True
|
|
print ("TRIGGERED")
|
|
last_last_v1 = last_v1
|
|
last_v1 = v1
|
|
if not(trigger):
|
|
continue
|
|
|
|
# Add the pair to our data list
|
|
timearr.append(time)
|
|
v1arr.append(v1)
|
|
v2arr.append(v2)
|
|
except (ValueError, IndexError):
|
|
# This handles any malformed lines or conversion errors
|
|
print(f"Skipping malformed row: {row}")
|
|
return np.array(timearr), np.array(v1arr), np.array(v2arr)
|
|
|
|
def calc_rmse(predicted_t, predicted_v, actual_t, actual_v): # The frame of reference is t1.
|
|
actual_v_interp = np.interp(predicted_t, actual_t, actual_v)
|
|
# Now, we can directly compare actual_v_interp with predicted_v.
|
|
squared_error = (actual_v_interp - predicted_v) ** 2
|
|
mse = np.mean(squared_error)
|
|
rmse = np.sqrt(mse)
|
|
return rmse
|
|
|
|
def plot_single_sinusoid_freq(freq):
|
|
t2, x2, y2 = load_scope_data('/home/hurricos/Sync/org/School/EE3150/LAB3/data/2_2_a/' + str(freq) + 'hz.csv')
|
|
t2 += 0 - t2[0]
|
|
plt, t, x, y = plot_single_sinusoid(freq, t2[-1], dt, False)
|
|
plt.plot(t2, x2, label=r'v_in')
|
|
plt.plot(t2, y2, label=r'v_c2')
|
|
print("RMSE @ ", str(freq), ": ", calc_rmse(t, y, t2, y2))
|
|
plt.legend()
|
|
plt.show()
|
|
|
|
def run_2_2_a_conv_pred():
|
|
plot_single_sinusoid_freq(1)
|
|
plot_single_sinusoid_freq(5)
|
|
plot_single_sinusoid_freq(10)
|
|
plot_single_sinusoid_freq(100)
|
|
|
|
def run_2_2_b_show():
|
|
plt.figure(num='Window')
|
|
plt.xlabel('t (s)')
|
|
plt.title(f'Sum of sinusoids')
|
|
plt.ylabel('V')
|
|
plt.grid(True)
|
|
t2, x2, y2 = load_scope_data('/home/hurricos/Sync/org/School/EE3150/LAB3/data/2_2_b/y1.csv')
|
|
plt.plot(t2, x2, label=r'v_in1')
|
|
plt.plot(t2, y2, label=r'v_c21')
|
|
|
|
t2, x2, y2 = load_scope_data('/home/hurricos/Sync/org/School/EE3150/LAB3/data/2_2_b/y2.csv')
|
|
plt.plot(t2, x2, label=r'v_in2')
|
|
plt.plot(t2, y2, label=r'v_c22')
|
|
|
|
t2, x2, y2 = load_scope_data('/home/hurricos/Sync/org/School/EE3150/LAB3/data/2_2_b/y1_y2_sum.csv')
|
|
plt.plot(t2, x2, label=r'v_in')
|
|
plt.plot(t2, y2, label=r'v_c2')
|
|
|
|
plt.legend()
|
|
plt.show()
|
|
|
|
run_2_2_b_show()
|
|
\end{minted}
|
|
|
|
\section{Summary and Conclusions}
|
|
We encountered many difficulties in this lab. The difficulties fell into three categories:
|
|
|
|
\begin{itemize}
|
|
\item Incorrect circuit assembly delayed our ability to collect data, and ultimately hampered
|
|
|
|
\item In the absence of context for calculations to be done later in our report, the collection of data was not done in a way conducive to consumption of later day (e.g. nonsynchronization)
|
|
|
|
\item The calculations themselves were difficult to do, and ultimately consumed the bulk of the time we had available to complete the lab report.
|
|
|
|
\end{itemize}
|
|
|
|
Nevertheless, we were able to exercise techniques which process signal
|
|
data, and review and confirm \textit{at least} that the signal
|
|
responses of sinusoidal inputs were also sinusoidal, and shared the
|
|
same frequency.
|
|
|
|
\end{document}
|