Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/electrical-engineering/rc_circuit.tex
51 views
unlisted
1
\documentclass[a4paper, 11pt]{article}
2
\usepackage[utf8]{inputenc}
3
\usepackage[T1]{fontenc}
4
\usepackage{amsmath, amssymb, amsthm}
5
\usepackage{graphicx}
6
\usepackage{siunitx}
7
\usepackage{booktabs}
8
\usepackage{float}
9
\usepackage{geometry}
10
\geometry{margin=1in}
11
\usepackage[makestderr]{pythontex}
12
13
% Theorem environments
14
\newtheorem{theorem}{Theorem}[section]
15
\newtheorem{definition}[theorem]{Definition}
16
\newtheorem{example}[theorem]{Example}
17
18
\title{RC Circuit Analysis: Transient Response, Frequency Domain,\\and Filter Design Laboratory Report}
19
\author{Electrical Engineering Laboratory}
20
\date{\today}
21
22
\begin{document}
23
\maketitle
24
25
\begin{abstract}
26
This laboratory report presents a comprehensive analysis of RC circuits, covering transient response characteristics, Laplace transform methods, frequency domain analysis, and filter design applications. Through computational analysis with Python, we demonstrate charging and discharging dynamics, time constant determination, Bode plot interpretation, and the design of low-pass, high-pass, and band-pass filter configurations. All numerical results are dynamically computed, ensuring reproducibility.
27
\end{abstract}
28
29
\section{Introduction}
30
31
RC circuits form the foundation of analog signal processing and filtering. The combination of resistance (R) and capacitance (C) creates a frequency-dependent impedance that enables selective signal attenuation and phase shifting.
32
33
\begin{definition}[Time Constant]
34
The time constant $\tau$ of an RC circuit is the time required for the voltage to reach approximately 63.2\% of its final value during charging or decay to 36.8\% during discharging:
35
\begin{equation}
36
\tau = RC
37
\end{equation}
38
\end{definition}
39
40
\section{Laplace Domain Analysis}
41
42
\begin{pycode}
43
import numpy as np
44
import matplotlib.pyplot as plt
45
from scipy import signal
46
from scipy.integrate import odeint
47
48
plt.rc('text', usetex=True)
49
plt.rc('font', family='serif', size=9)
50
np.random.seed(42)
51
52
# Circuit parameters
53
R = 1000 # Resistance (Ohm)
54
C = 1e-6 # Capacitance (F)
55
tau = R * C # Time constant (s)
56
V0 = 5.0 # Source voltage (V)
57
58
# Derived parameters
59
f_cutoff = 1 / (2 * np.pi * R * C) # Cutoff frequency (Hz)
60
omega_cutoff = 2 * np.pi * f_cutoff # Angular cutoff frequency (rad/s)
61
62
# Time vector for transient analysis
63
t = np.linspace(0, 5*tau, 1000)
64
\end{pycode}
65
66
The transfer function of an RC low-pass filter in the Laplace domain is:
67
\begin{equation}
68
H(s) = \frac{V_{out}(s)}{V_{in}(s)} = \frac{1}{1 + sRC} = \frac{1}{1 + s\tau}
69
\end{equation}
70
71
\begin{theorem}[Voltage Equations via Laplace Transform]
72
For a step input of magnitude $V_0$, the capacitor voltage during charging is:
73
\begin{equation}
74
v_C(t) = V_0\left(1 - e^{-t/\tau}\right)u(t)
75
\end{equation}
76
where $u(t)$ is the unit step function.
77
\end{theorem}
78
79
\noindent\textbf{Circuit Parameters:}
80
\begin{itemize}
81
\item Resistance: $R = \py{R}$ $\Omega$
82
\item Capacitance: $C = \py{C*1e6:.1f}$ $\mu$F
83
\item Time Constant: $\tau = \py{f"{tau*1000:.3f}"}$ ms
84
\item Cutoff Frequency: $f_c = \py{f"{f_cutoff:.1f}"}$ Hz
85
\end{itemize}
86
87
\section{Transient Response Analysis}
88
89
\subsection{Charging and Discharging Dynamics}
90
91
\begin{pycode}
92
# Transient responses
93
v_charge = V0 * (1 - np.exp(-t/tau))
94
v_discharge = V0 * np.exp(-t/tau)
95
96
# Current during charging
97
i_charge = (V0/R) * np.exp(-t/tau)
98
i_discharge = -(V0/R) * np.exp(-t/tau)
99
100
# Power calculations
101
p_charge = v_charge * i_charge
102
p_resistor = i_charge**2 * R
103
104
# Energy stored in capacitor
105
energy_cap = 0.5 * C * v_charge**2
106
energy_max = 0.5 * C * V0**2
107
108
# Key time points
109
t_1tau = tau
110
t_3tau = 3 * tau
111
t_5tau = 5 * tau
112
113
v_at_1tau = V0 * (1 - np.exp(-1)) # ~63.2%
114
v_at_3tau = V0 * (1 - np.exp(-3)) # ~95.0%
115
v_at_5tau = V0 * (1 - np.exp(-5)) # ~99.3%
116
117
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
118
119
# Voltage responses
120
axes[0, 0].plot(t*1000, v_charge, 'b-', linewidth=1.5, label='Charging')
121
axes[0, 0].plot(t*1000, v_discharge, 'r--', linewidth=1.5, label='Discharging')
122
axes[0, 0].axhline(y=V0*0.632, color='gray', linestyle=':', alpha=0.7)
123
axes[0, 0].axvline(x=tau*1000, color='g', linestyle=':', alpha=0.7, label=r'$\tau$')
124
axes[0, 0].axvline(x=3*tau*1000, color='orange', linestyle=':', alpha=0.5, label=r'$3\tau$')
125
axes[0, 0].axvline(x=5*tau*1000, color='purple', linestyle=':', alpha=0.5, label=r'$5\tau$')
126
axes[0, 0].plot([tau*1000], [v_at_1tau], 'go', markersize=6)
127
axes[0, 0].set_xlabel('Time (ms)')
128
axes[0, 0].set_ylabel('Voltage (V)')
129
axes[0, 0].set_title('Capacitor Voltage')
130
axes[0, 0].legend(loc='center right', fontsize=7)
131
axes[0, 0].grid(True, alpha=0.3)
132
133
# Current responses
134
axes[0, 1].plot(t*1000, i_charge*1000, 'b-', linewidth=1.5, label='Charging')
135
axes[0, 1].plot(t*1000, i_discharge*1000, 'r--', linewidth=1.5, label='Discharging')
136
axes[0, 1].axhline(y=0, color='gray', linestyle='-', alpha=0.3)
137
axes[0, 1].axvline(x=tau*1000, color='g', linestyle=':', alpha=0.7)
138
axes[0, 1].set_xlabel('Time (ms)')
139
axes[0, 1].set_ylabel('Current (mA)')
140
axes[0, 1].set_title('Circuit Current')
141
axes[0, 1].legend(loc='upper right', fontsize=8)
142
axes[0, 1].grid(True, alpha=0.3)
143
144
# Power dissipation
145
axes[1, 0].plot(t*1000, p_resistor*1000, 'r-', linewidth=1.5, label='Resistor')
146
axes[1, 0].plot(t*1000, p_charge*1000, 'b--', linewidth=1.5, label='Capacitor Input')
147
axes[1, 0].axvline(x=tau*1000, color='g', linestyle=':', alpha=0.7)
148
axes[1, 0].set_xlabel('Time (ms)')
149
axes[1, 0].set_ylabel('Power (mW)')
150
axes[1, 0].set_title('Power Distribution')
151
axes[1, 0].legend(loc='upper right', fontsize=8)
152
axes[1, 0].grid(True, alpha=0.3)
153
154
# Energy storage
155
axes[1, 1].plot(t*1000, energy_cap*1e6, 'b-', linewidth=1.5)
156
axes[1, 1].axhline(y=energy_max*1e6, color='r', linestyle='--', alpha=0.7, label='Max Energy')
157
axes[1, 1].axvline(x=tau*1000, color='g', linestyle=':', alpha=0.7)
158
axes[1, 1].set_xlabel('Time (ms)')
159
axes[1, 1].set_ylabel(r'Energy ($\mu$J)')
160
axes[1, 1].set_title('Capacitor Energy Storage')
161
axes[1, 1].legend(loc='lower right', fontsize=8)
162
axes[1, 1].grid(True, alpha=0.3)
163
164
plt.tight_layout()
165
plt.savefig('rc_circuit_plot1.pdf', bbox_inches='tight', dpi=150)
166
plt.close()
167
\end{pycode}
168
169
\begin{figure}[H]
170
\centering
171
\includegraphics[width=0.95\textwidth]{rc_circuit_plot1.pdf}
172
\caption{Transient response characteristics: voltage, current, power, and energy.}
173
\end{figure}
174
175
\begin{table}[H]
176
\centering
177
\caption{Charging Voltage at Key Time Constants}
178
\begin{tabular}{ccc}
179
\toprule
180
\textbf{Time} & \textbf{Voltage (V)} & \textbf{Percentage of $V_0$} \\
181
\midrule
182
$\tau$ & \py{f"{v_at_1tau:.3f}"} & 63.2\% \\
183
$3\tau$ & \py{f"{v_at_3tau:.3f}"} & 95.0\% \\
184
$5\tau$ & \py{f"{v_at_5tau:.3f}"} & 99.3\% \\
185
\bottomrule
186
\end{tabular}
187
\end{table}
188
189
\subsection{Pulse Response and Duty Cycle Effects}
190
191
\begin{pycode}
192
# Pulse response with different duty cycles
193
t_pulse = np.linspace(0, 10*tau, 2000)
194
dt = t_pulse[1] - t_pulse[0]
195
196
# Generate pulse trains
197
def generate_pulse_response(t, tau, period_factor, duty_cycle):
198
period = period_factor * tau
199
v_out = np.zeros_like(t)
200
v_cap = 0 # Initial capacitor voltage
201
202
for i in range(len(t)):
203
t_in_cycle = t[i] % period
204
if t_in_cycle < period * duty_cycle:
205
v_in = V0
206
else:
207
v_in = 0
208
209
# RC circuit differential equation
210
dv = (v_in - v_cap) / tau
211
v_cap = v_cap + dv * dt
212
v_out[i] = v_cap
213
214
return v_out
215
216
# Different cases
217
v_slow_50 = generate_pulse_response(t_pulse, tau, 10, 0.5) # Period = 10*tau
218
v_fast_50 = generate_pulse_response(t_pulse, tau, 2, 0.5) # Period = 2*tau
219
v_fast_20 = generate_pulse_response(t_pulse, tau, 2, 0.2) # 20% duty cycle
220
221
# Square wave input for comparison
222
def square_wave(t, period, duty):
223
return V0 * ((t % period) < (period * duty)).astype(float)
224
225
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
226
227
# Slow pulse (period = 10*tau)
228
period_slow = 10 * tau
229
axes[0, 0].plot(t_pulse*1000, square_wave(t_pulse, period_slow, 0.5), 'gray', alpha=0.5, label='Input')
230
axes[0, 0].plot(t_pulse*1000, v_slow_50, 'b-', linewidth=1.5, label='Output')
231
axes[0, 0].set_xlabel('Time (ms)')
232
axes[0, 0].set_ylabel('Voltage (V)')
233
axes[0, 0].set_title(f'Slow Pulse: Period = 10$\\tau$, 50\\% Duty')
234
axes[0, 0].legend(loc='upper right', fontsize=8)
235
axes[0, 0].grid(True, alpha=0.3)
236
237
# Fast pulse (period = 2*tau)
238
period_fast = 2 * tau
239
axes[0, 1].plot(t_pulse*1000, square_wave(t_pulse, period_fast, 0.5), 'gray', alpha=0.5, label='Input')
240
axes[0, 1].plot(t_pulse*1000, v_fast_50, 'r-', linewidth=1.5, label='Output')
241
axes[0, 1].set_xlabel('Time (ms)')
242
axes[0, 1].set_ylabel('Voltage (V)')
243
axes[0, 1].set_title(f'Fast Pulse: Period = 2$\\tau$, 50\\% Duty')
244
axes[0, 1].legend(loc='upper right', fontsize=8)
245
axes[0, 1].grid(True, alpha=0.3)
246
247
# Low duty cycle
248
axes[1, 0].plot(t_pulse*1000, square_wave(t_pulse, period_fast, 0.2), 'gray', alpha=0.5, label='Input')
249
axes[1, 0].plot(t_pulse*1000, v_fast_20, 'g-', linewidth=1.5, label='Output')
250
axes[1, 0].set_xlabel('Time (ms)')
251
axes[1, 0].set_ylabel('Voltage (V)')
252
axes[1, 0].set_title(f'Low Duty Cycle: 20\\%')
253
axes[1, 0].legend(loc='upper right', fontsize=8)
254
axes[1, 0].grid(True, alpha=0.3)
255
256
# DC component extraction (averaging)
257
dc_values = []
258
duty_cycles = np.linspace(0.1, 0.9, 9)
259
for dc in duty_cycles:
260
v_resp = generate_pulse_response(t_pulse, tau, 2, dc)
261
dc_values.append(np.mean(v_resp[-500:])) # Steady-state average
262
263
axes[1, 1].plot(duty_cycles*100, dc_values, 'bo-', linewidth=1.5, markersize=6)
264
axes[1, 1].plot(duty_cycles*100, duty_cycles*V0, 'r--', alpha=0.7, label='Ideal: $D \\cdot V_0$')
265
axes[1, 1].set_xlabel('Duty Cycle (\\%)')
266
axes[1, 1].set_ylabel('DC Output (V)')
267
axes[1, 1].set_title('DC Component vs Duty Cycle')
268
axes[1, 1].legend(loc='upper left', fontsize=8)
269
axes[1, 1].grid(True, alpha=0.3)
270
271
plt.tight_layout()
272
plt.savefig('rc_circuit_plot2.pdf', bbox_inches='tight', dpi=150)
273
plt.close()
274
\end{pycode}
275
276
\begin{figure}[H]
277
\centering
278
\includegraphics[width=0.95\textwidth]{rc_circuit_plot2.pdf}
279
\caption{Pulse response showing the effect of pulse period and duty cycle on output waveform.}
280
\end{figure}
281
282
\section{Frequency Response Analysis}
283
284
\subsection{Bode Plot Characterization}
285
286
\begin{pycode}
287
# Frequency response analysis
288
f = np.logspace(1, 7, 1000) # 10 Hz to 10 MHz
289
omega = 2 * np.pi * f
290
291
# Transfer function magnitude and phase
292
H = 1 / (1 + 1j * omega * R * C)
293
H_mag = np.abs(H)
294
H_phase = np.angle(H, deg=True)
295
H_mag_dB = 20 * np.log10(H_mag)
296
297
# Key points
298
idx_fc = np.argmin(np.abs(f - f_cutoff))
299
mag_at_fc = H_mag_dB[idx_fc]
300
phase_at_fc = H_phase[idx_fc]
301
302
# Roll-off calculation
303
f_decade_above = 10 * f_cutoff
304
idx_decade = np.argmin(np.abs(f - f_decade_above))
305
rolloff = H_mag_dB[idx_fc] - H_mag_dB[idx_decade]
306
307
# Phase delay
308
phase_delay = -H_phase / (360 * f) # in seconds
309
group_delay = -np.gradient(np.unwrap(np.angle(H))) / np.gradient(omega)
310
311
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
312
313
# Magnitude response
314
axes[0, 0].semilogx(f, H_mag_dB, 'b-', linewidth=1.5)
315
axes[0, 0].axhline(y=-3, color='r', linestyle='--', alpha=0.7, label='-3 dB')
316
axes[0, 0].axhline(y=-20, color='gray', linestyle=':', alpha=0.5)
317
axes[0, 0].axvline(x=f_cutoff, color='g', linestyle='--', alpha=0.7, label='$f_c$')
318
axes[0, 0].plot(f_cutoff, mag_at_fc, 'go', markersize=6)
319
axes[0, 0].set_xlabel('Frequency (Hz)')
320
axes[0, 0].set_ylabel('Magnitude (dB)')
321
axes[0, 0].set_title('Bode Plot - Magnitude')
322
axes[0, 0].legend(loc='lower left', fontsize=8)
323
axes[0, 0].grid(True, which='both', alpha=0.3)
324
axes[0, 0].set_ylim([-60, 5])
325
326
# Phase response
327
axes[0, 1].semilogx(f, H_phase, 'b-', linewidth=1.5)
328
axes[0, 1].axhline(y=-45, color='r', linestyle='--', alpha=0.7, label='-45°')
329
axes[0, 1].axhline(y=-90, color='gray', linestyle=':', alpha=0.5)
330
axes[0, 1].axvline(x=f_cutoff, color='g', linestyle='--', alpha=0.7)
331
axes[0, 1].plot(f_cutoff, phase_at_fc, 'go', markersize=6)
332
axes[0, 1].set_xlabel('Frequency (Hz)')
333
axes[0, 1].set_ylabel('Phase (degrees)')
334
axes[0, 1].set_title('Bode Plot - Phase')
335
axes[0, 1].legend(loc='upper right', fontsize=8)
336
axes[0, 1].grid(True, which='both', alpha=0.3)
337
338
# Nyquist plot
339
axes[1, 0].plot(np.real(H), np.imag(H), 'b-', linewidth=1.5)
340
axes[1, 0].plot(1, 0, 'go', markersize=8, label='DC (0 Hz)')
341
axes[1, 0].plot(0.5, -0.5, 'ro', markersize=8, label='$f_c$')
342
axes[1, 0].plot(0, 0, 'ko', markersize=8, label=r'$\infty$')
343
axes[1, 0].set_xlabel('Real')
344
axes[1, 0].set_ylabel('Imaginary')
345
axes[1, 0].set_title('Nyquist Plot')
346
axes[1, 0].legend(loc='lower left', fontsize=8)
347
axes[1, 0].grid(True, alpha=0.3)
348
axes[1, 0].axis('equal')
349
axes[1, 0].set_xlim([-0.1, 1.1])
350
351
# Group delay
352
axes[1, 1].semilogx(f, group_delay*1e6, 'b-', linewidth=1.5)
353
axes[1, 1].axhline(y=tau*1e6, color='r', linestyle='--', alpha=0.7, label=r'$\tau$')
354
axes[1, 1].axvline(x=f_cutoff, color='g', linestyle='--', alpha=0.7)
355
axes[1, 1].set_xlabel('Frequency (Hz)')
356
axes[1, 1].set_ylabel(r'Group Delay ($\mu$s)')
357
axes[1, 1].set_title('Group Delay')
358
axes[1, 1].legend(loc='upper right', fontsize=8)
359
axes[1, 1].grid(True, which='both', alpha=0.3)
360
361
plt.tight_layout()
362
plt.savefig('rc_circuit_plot3.pdf', bbox_inches='tight', dpi=150)
363
plt.close()
364
\end{pycode}
365
366
\begin{figure}[H]
367
\centering
368
\includegraphics[width=0.95\textwidth]{rc_circuit_plot3.pdf}
369
\caption{Frequency response characteristics: Bode plots, Nyquist diagram, and group delay.}
370
\end{figure}
371
372
\begin{table}[H]
373
\centering
374
\caption{Frequency Response Characteristics}
375
\begin{tabular}{lcc}
376
\toprule
377
\textbf{Parameter} & \textbf{Value} & \textbf{Unit} \\
378
\midrule
379
Cutoff Frequency & \py{f"{f_cutoff:.1f}"} & Hz \\
380
Magnitude at $f_c$ & \py{f"{mag_at_fc:.1f}"} & dB \\
381
Phase at $f_c$ & \py{f"{phase_at_fc:.1f}"} & degrees \\
382
Roll-off per Decade & \py{f"{rolloff:.1f}"} & dB \\
383
DC Group Delay & \py{f"{tau*1e6:.1f}"} & $\mu$s \\
384
\bottomrule
385
\end{tabular}
386
\end{table}
387
388
\section{Filter Design Applications}
389
390
\subsection{Low-Pass and High-Pass Configurations}
391
392
\begin{pycode}
393
# Compare low-pass and high-pass configurations
394
# Low-pass: H_LP = 1/(1 + jwRC)
395
# High-pass: H_HP = jwRC/(1 + jwRC)
396
397
H_lp = 1 / (1 + 1j * omega * R * C)
398
H_hp = (1j * omega * R * C) / (1 + 1j * omega * R * C)
399
400
H_lp_dB = 20 * np.log10(np.abs(H_lp))
401
H_hp_dB = 20 * np.log10(np.abs(H_hp))
402
403
phase_lp = np.angle(H_lp, deg=True)
404
phase_hp = np.angle(H_hp, deg=True)
405
406
# Second-order filters (two RC stages cascaded)
407
H_lp2 = H_lp**2
408
H_hp2 = H_hp**2
409
410
H_lp2_dB = 20 * np.log10(np.abs(H_lp2))
411
H_hp2_dB = 20 * np.log10(np.abs(H_hp2))
412
413
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
414
415
# First-order magnitude comparison
416
axes[0, 0].semilogx(f, H_lp_dB, 'b-', linewidth=1.5, label='Low-Pass')
417
axes[0, 0].semilogx(f, H_hp_dB, 'r-', linewidth=1.5, label='High-Pass')
418
axes[0, 0].axhline(y=-3, color='gray', linestyle='--', alpha=0.7)
419
axes[0, 0].axvline(x=f_cutoff, color='g', linestyle=':', alpha=0.7)
420
axes[0, 0].set_xlabel('Frequency (Hz)')
421
axes[0, 0].set_ylabel('Magnitude (dB)')
422
axes[0, 0].set_title('First-Order Filter Comparison')
423
axes[0, 0].legend(loc='center left', fontsize=8)
424
axes[0, 0].grid(True, which='both', alpha=0.3)
425
axes[0, 0].set_ylim([-40, 5])
426
427
# Phase comparison
428
axes[0, 1].semilogx(f, phase_lp, 'b-', linewidth=1.5, label='Low-Pass')
429
axes[0, 1].semilogx(f, phase_hp, 'r-', linewidth=1.5, label='High-Pass')
430
axes[0, 1].axhline(y=-45, color='gray', linestyle='--', alpha=0.7)
431
axes[0, 1].axhline(y=45, color='gray', linestyle='--', alpha=0.7)
432
axes[0, 1].axvline(x=f_cutoff, color='g', linestyle=':', alpha=0.7)
433
axes[0, 1].set_xlabel('Frequency (Hz)')
434
axes[0, 1].set_ylabel('Phase (degrees)')
435
axes[0, 1].set_title('Phase Response Comparison')
436
axes[0, 1].legend(loc='center right', fontsize=8)
437
axes[0, 1].grid(True, which='both', alpha=0.3)
438
439
# Second-order comparison
440
axes[1, 0].semilogx(f, H_lp_dB, 'b--', linewidth=1, alpha=0.7, label='1st Order LP')
441
axes[1, 0].semilogx(f, H_lp2_dB, 'b-', linewidth=1.5, label='2nd Order LP')
442
axes[1, 0].semilogx(f, H_hp_dB, 'r--', linewidth=1, alpha=0.7, label='1st Order HP')
443
axes[1, 0].semilogx(f, H_hp2_dB, 'r-', linewidth=1.5, label='2nd Order HP')
444
axes[1, 0].axhline(y=-3, color='gray', linestyle='--', alpha=0.5)
445
axes[1, 0].axvline(x=f_cutoff, color='g', linestyle=':', alpha=0.7)
446
axes[1, 0].set_xlabel('Frequency (Hz)')
447
axes[1, 0].set_ylabel('Magnitude (dB)')
448
axes[1, 0].set_title('Order Comparison')
449
axes[1, 0].legend(loc='lower left', fontsize=7)
450
axes[1, 0].grid(True, which='both', alpha=0.3)
451
axes[1, 0].set_ylim([-60, 5])
452
453
# Selectivity (Q-factor) for different filter orders
454
orders = [1, 2, 3, 4]
455
colors = ['b', 'r', 'g', 'orange']
456
for n, color in zip(orders, colors):
457
H_n = H_lp**n
458
axes[1, 1].semilogx(f, 20*np.log10(np.abs(H_n)), color=color,
459
linewidth=1.5, label=f'n={n}')
460
461
axes[1, 1].axhline(y=-3, color='gray', linestyle='--', alpha=0.5)
462
axes[1, 1].axvline(x=f_cutoff, color='k', linestyle=':', alpha=0.7)
463
axes[1, 1].set_xlabel('Frequency (Hz)')
464
axes[1, 1].set_ylabel('Magnitude (dB)')
465
axes[1, 1].set_title('Low-Pass Filter Order Effects')
466
axes[1, 1].legend(loc='lower left', fontsize=8)
467
axes[1, 1].grid(True, which='both', alpha=0.3)
468
axes[1, 1].set_ylim([-80, 5])
469
470
plt.tight_layout()
471
plt.savefig('rc_circuit_plot4.pdf', bbox_inches='tight', dpi=150)
472
plt.close()
473
\end{pycode}
474
475
\begin{figure}[H]
476
\centering
477
\includegraphics[width=0.95\textwidth]{rc_circuit_plot4.pdf}
478
\caption{Filter configurations: low-pass and high-pass comparison with order effects.}
479
\end{figure}
480
481
\subsection{Band-Pass Filter Design}
482
483
\begin{pycode}
484
# Band-pass filter: cascaded HP and LP
485
# Different cutoff frequencies for HP and LP
486
R_hp = 10000 # Higher R for lower cutoff
487
R_lp = 1000 # Lower R for higher cutoff
488
C_common = 100e-9 # 100 nF
489
490
f_low = 1 / (2 * np.pi * R_hp * C_common) # Lower cutoff
491
f_high = 1 / (2 * np.pi * R_lp * C_common) # Upper cutoff
492
f_center = np.sqrt(f_low * f_high)
493
bandwidth = f_high - f_low
494
Q_factor = f_center / bandwidth
495
496
# Transfer functions
497
H_hp_bp = (1j * omega * R_hp * C_common) / (1 + 1j * omega * R_hp * C_common)
498
H_lp_bp = 1 / (1 + 1j * omega * R_lp * C_common)
499
H_bp = H_hp_bp * H_lp_bp
500
501
H_bp_dB = 20 * np.log10(np.abs(H_bp))
502
phase_bp = np.angle(H_bp, deg=True)
503
504
# Find -3dB bandwidth
505
max_gain = np.max(H_bp_dB)
506
idx_3db_low = np.where(H_bp_dB[:np.argmax(H_bp_dB)] < max_gain - 3)[0][-1]
507
idx_3db_high = np.argmax(H_bp_dB) + np.where(H_bp_dB[np.argmax(H_bp_dB):] < max_gain - 3)[0][0]
508
f_3db_low = f[idx_3db_low]
509
f_3db_high = f[idx_3db_high]
510
measured_bw = f_3db_high - f_3db_low
511
512
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
513
514
# Band-pass magnitude
515
axes[0, 0].semilogx(f, H_bp_dB, 'b-', linewidth=1.5)
516
axes[0, 0].axhline(y=max_gain-3, color='r', linestyle='--', alpha=0.7, label='-3 dB')
517
axes[0, 0].axvline(x=f_low, color='g', linestyle=':', alpha=0.7, label='$f_L$')
518
axes[0, 0].axvline(x=f_high, color='orange', linestyle=':', alpha=0.7, label='$f_H$')
519
axes[0, 0].axvline(x=f_center, color='purple', linestyle='--', alpha=0.7, label='$f_0$')
520
axes[0, 0].set_xlabel('Frequency (Hz)')
521
axes[0, 0].set_ylabel('Magnitude (dB)')
522
axes[0, 0].set_title('Band-Pass Filter Response')
523
axes[0, 0].legend(loc='lower right', fontsize=7)
524
axes[0, 0].grid(True, which='both', alpha=0.3)
525
axes[0, 0].set_ylim([-40, 5])
526
527
# Band-pass phase
528
axes[0, 1].semilogx(f, phase_bp, 'b-', linewidth=1.5)
529
axes[0, 1].axhline(y=0, color='gray', linestyle='--', alpha=0.5)
530
axes[0, 1].axvline(x=f_center, color='purple', linestyle='--', alpha=0.7)
531
axes[0, 1].set_xlabel('Frequency (Hz)')
532
axes[0, 1].set_ylabel('Phase (degrees)')
533
axes[0, 1].set_title('Band-Pass Phase Response')
534
axes[0, 1].grid(True, which='both', alpha=0.3)
535
536
# Component contributions
537
axes[1, 0].semilogx(f, 20*np.log10(np.abs(H_hp_bp)), 'r--', linewidth=1, label='HP Section')
538
axes[1, 0].semilogx(f, 20*np.log10(np.abs(H_lp_bp)), 'b--', linewidth=1, label='LP Section')
539
axes[1, 0].semilogx(f, H_bp_dB, 'g-', linewidth=1.5, label='Combined')
540
axes[1, 0].axvline(x=f_low, color='r', linestyle=':', alpha=0.5)
541
axes[1, 0].axvline(x=f_high, color='b', linestyle=':', alpha=0.5)
542
axes[1, 0].set_xlabel('Frequency (Hz)')
543
axes[1, 0].set_ylabel('Magnitude (dB)')
544
axes[1, 0].set_title('Filter Section Contributions')
545
axes[1, 0].legend(loc='lower left', fontsize=8)
546
axes[1, 0].grid(True, which='both', alpha=0.3)
547
axes[1, 0].set_ylim([-40, 5])
548
549
# Impulse response of band-pass
550
num_bp = [R_hp * C_common, 0]
551
den_bp = np.convolve([R_hp * C_common, 1], [R_lp * C_common, 1])
552
bp_sys = signal.TransferFunction(num_bp, den_bp)
553
t_imp = np.linspace(0, 0.01, 1000)
554
t_i, y_imp = signal.impulse(bp_sys, T=t_imp)
555
556
axes[1, 1].plot(t_i*1000, y_imp, 'b-', linewidth=1.5)
557
axes[1, 1].set_xlabel('Time (ms)')
558
axes[1, 1].set_ylabel('Amplitude')
559
axes[1, 1].set_title('Band-Pass Impulse Response')
560
axes[1, 1].grid(True, alpha=0.3)
561
562
plt.tight_layout()
563
plt.savefig('rc_circuit_plot5.pdf', bbox_inches='tight', dpi=150)
564
plt.close()
565
\end{pycode}
566
567
\begin{figure}[H]
568
\centering
569
\includegraphics[width=0.95\textwidth]{rc_circuit_plot5.pdf}
570
\caption{Band-pass filter design and frequency response analysis.}
571
\end{figure}
572
573
\begin{table}[H]
574
\centering
575
\caption{Band-Pass Filter Parameters}
576
\begin{tabular}{lcc}
577
\toprule
578
\textbf{Parameter} & \textbf{Value} & \textbf{Unit} \\
579
\midrule
580
Lower Cutoff $f_L$ & \py{f"{f_low:.1f}"} & Hz \\
581
Upper Cutoff $f_H$ & \py{f"{f_high:.1f}"} & Hz \\
582
Center Frequency $f_0$ & \py{f"{f_center:.1f}"} & Hz \\
583
Bandwidth & \py{f"{bandwidth:.1f}"} & Hz \\
584
Quality Factor Q & \py{f"{Q_factor:.2f}"} & -- \\
585
\bottomrule
586
\end{tabular}
587
\end{table}
588
589
\section{Signal Processing Applications}
590
591
\subsection{Noise Filtering and Signal Conditioning}
592
593
\begin{pycode}
594
# Signal processing demonstration
595
np.random.seed(42)
596
t_sig = np.linspace(0, 0.01, 2000)
597
dt_sig = t_sig[1] - t_sig[0]
598
fs = 1 / dt_sig
599
600
# Create signal with noise
601
f_signal = 100 # 100 Hz signal
602
signal_clean = np.sin(2 * np.pi * f_signal * t_sig)
603
noise = 0.5 * np.random.randn(len(t_sig))
604
noise += 0.3 * np.sin(2 * np.pi * 5000 * t_sig) # High-frequency interference
605
signal_noisy = signal_clean + noise
606
607
# Apply RC low-pass filter using convolution
608
# Impulse response of RC filter: h(t) = (1/tau) * exp(-t/tau)
609
tau_filter = 1 / (2 * np.pi * 500) # fc = 500 Hz
610
t_ir = np.arange(0, 5*tau_filter, dt_sig)
611
h_ir = (1/tau_filter) * np.exp(-t_ir/tau_filter) * dt_sig
612
613
signal_filtered = np.convolve(signal_noisy, h_ir, mode='same')
614
615
# FFT analysis
616
n_fft = len(t_sig)
617
freqs_fft = np.fft.fftfreq(n_fft, dt_sig)[:n_fft//2]
618
fft_noisy = np.abs(np.fft.fft(signal_noisy))[:n_fft//2] * 2/n_fft
619
fft_filtered = np.abs(np.fft.fft(signal_filtered))[:n_fft//2] * 2/n_fft
620
fft_clean = np.abs(np.fft.fft(signal_clean))[:n_fft//2] * 2/n_fft
621
622
# SNR calculation
623
noise_power_before = np.var(signal_noisy - signal_clean)
624
noise_power_after = np.var(signal_filtered - signal_clean)
625
snr_before = 10 * np.log10(np.var(signal_clean) / noise_power_before)
626
snr_after = 10 * np.log10(np.var(signal_clean) / noise_power_after)
627
628
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
629
630
# Time domain signals
631
axes[0, 0].plot(t_sig*1000, signal_noisy, 'gray', alpha=0.5, linewidth=0.5, label='Noisy')
632
axes[0, 0].plot(t_sig*1000, signal_filtered, 'b-', linewidth=1.5, label='Filtered')
633
axes[0, 0].plot(t_sig*1000, signal_clean, 'r--', linewidth=1, label='Original')
634
axes[0, 0].set_xlabel('Time (ms)')
635
axes[0, 0].set_ylabel('Amplitude')
636
axes[0, 0].set_title('Time Domain Filtering')
637
axes[0, 0].legend(loc='upper right', fontsize=8)
638
axes[0, 0].grid(True, alpha=0.3)
639
axes[0, 0].set_xlim([0, 5])
640
641
# Frequency spectrum
642
axes[0, 1].semilogy(freqs_fft, fft_noisy, 'gray', alpha=0.5, linewidth=0.5, label='Noisy')
643
axes[0, 1].semilogy(freqs_fft, fft_filtered, 'b-', linewidth=1.5, label='Filtered')
644
axes[0, 1].axvline(x=500, color='r', linestyle='--', alpha=0.7, label='$f_c$')
645
axes[0, 1].set_xlabel('Frequency (Hz)')
646
axes[0, 1].set_ylabel('Magnitude')
647
axes[0, 1].set_title('Frequency Spectrum')
648
axes[0, 1].legend(loc='upper right', fontsize=8)
649
axes[0, 1].grid(True, which='both', alpha=0.3)
650
axes[0, 1].set_xlim([0, 10000])
651
652
# Filter impulse response
653
axes[1, 0].plot(t_ir*1000, h_ir/dt_sig, 'b-', linewidth=1.5)
654
axes[1, 0].axvline(x=tau_filter*1000, color='r', linestyle='--', alpha=0.7, label=r'$\tau$')
655
axes[1, 0].set_xlabel('Time (ms)')
656
axes[1, 0].set_ylabel('Amplitude')
657
axes[1, 0].set_title('Filter Impulse Response')
658
axes[1, 0].legend(loc='upper right', fontsize=8)
659
axes[1, 0].grid(True, alpha=0.3)
660
661
# SNR comparison
662
bars = axes[1, 1].bar(['Before', 'After'], [snr_before, snr_after],
663
color=['gray', 'blue'], alpha=0.7)
664
axes[1, 1].set_ylabel('SNR (dB)')
665
axes[1, 1].set_title('Signal-to-Noise Ratio Improvement')
666
axes[1, 1].grid(True, alpha=0.3, axis='y')
667
668
# Add value labels on bars
669
for bar, val in zip(bars, [snr_before, snr_after]):
670
axes[1, 1].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
671
f'{val:.1f} dB', ha='center', fontsize=9)
672
673
plt.tight_layout()
674
plt.savefig('rc_circuit_plot6.pdf', bbox_inches='tight', dpi=150)
675
plt.close()
676
677
snr_improvement = snr_after - snr_before
678
\end{pycode}
679
680
\begin{figure}[H]
681
\centering
682
\includegraphics[width=0.95\textwidth]{rc_circuit_plot6.pdf}
683
\caption{Signal processing application: noise filtering with RC low-pass filter.}
684
\end{figure}
685
686
\noindent\textbf{Signal Processing Results:}
687
\begin{itemize}
688
\item SNR Before Filtering: \py{f"{snr_before:.1f}"} dB
689
\item SNR After Filtering: \py{f"{snr_after:.1f}"} dB
690
\item SNR Improvement: \py{f"{snr_improvement:.1f}"} dB
691
\end{itemize}
692
693
\section{Component Sensitivity Analysis}
694
695
\begin{pycode}
696
# Sensitivity analysis: effect of component variations
697
R_nom = 1000
698
C_nom = 1e-6
699
700
# Tolerance ranges
701
tolerances = [1, 5, 10, 20] # percent
702
703
# Monte Carlo simulation for cutoff frequency variation
704
n_samples = 1000
705
f_c_samples = []
706
tolerance_labels = []
707
708
for tol in tolerances:
709
R_samples = R_nom * (1 + (np.random.rand(n_samples) - 0.5) * 2 * tol/100)
710
C_samples = C_nom * (1 + (np.random.rand(n_samples) - 0.5) * 2 * tol/100)
711
f_c_samples.append(1 / (2 * np.pi * R_samples * C_samples))
712
tolerance_labels.append(f'{tol}%')
713
714
# Nominal cutoff
715
f_c_nom = 1 / (2 * np.pi * R_nom * C_nom)
716
717
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
718
719
# Histogram of cutoff frequencies
720
for i, (fc, label) in enumerate(zip(f_c_samples, tolerance_labels)):
721
axes[0, 0].hist(fc, bins=30, alpha=0.5, label=label)
722
723
axes[0, 0].axvline(x=f_c_nom, color='r', linestyle='--', linewidth=2, label='Nominal')
724
axes[0, 0].set_xlabel('Cutoff Frequency (Hz)')
725
axes[0, 0].set_ylabel('Count')
726
axes[0, 0].set_title('Cutoff Frequency Distribution')
727
axes[0, 0].legend(loc='upper right', fontsize=8)
728
729
# Standard deviation vs tolerance
730
stds = [np.std(fc) for fc in f_c_samples]
731
axes[0, 1].plot(tolerances, stds, 'bo-', linewidth=1.5, markersize=8)
732
axes[0, 1].set_xlabel('Component Tolerance (%)')
733
axes[0, 1].set_ylabel('Std Dev of $f_c$ (Hz)')
734
axes[0, 1].set_title('Cutoff Frequency Variability')
735
axes[0, 1].grid(True, alpha=0.3)
736
737
# Frequency response with tolerance bands (5%)
738
R_min, R_max = R_nom * 0.95, R_nom * 1.05
739
C_min, C_max = C_nom * 0.95, C_nom * 1.05
740
741
# Worst cases
742
H_nom = 1 / (1 + 1j * omega * R_nom * C_nom)
743
H_low_fc = 1 / (1 + 1j * omega * R_max * C_max) # Lowest cutoff
744
H_high_fc = 1 / (1 + 1j * omega * R_min * C_min) # Highest cutoff
745
746
axes[1, 0].semilogx(f, 20*np.log10(np.abs(H_nom)), 'b-', linewidth=1.5, label='Nominal')
747
axes[1, 0].fill_between(f, 20*np.log10(np.abs(H_low_fc)),
748
20*np.log10(np.abs(H_high_fc)),
749
alpha=0.3, color='blue', label='5% Tolerance')
750
axes[1, 0].axhline(y=-3, color='r', linestyle='--', alpha=0.7)
751
axes[1, 0].set_xlabel('Frequency (Hz)')
752
axes[1, 0].set_ylabel('Magnitude (dB)')
753
axes[1, 0].set_title('Frequency Response with Tolerance')
754
axes[1, 0].legend(loc='lower left', fontsize=8)
755
axes[1, 0].grid(True, which='both', alpha=0.3)
756
axes[1, 0].set_ylim([-40, 5])
757
758
# Temperature coefficient effects
759
temps = np.linspace(-40, 85, 100) # Temperature range (C)
760
# Typical temperature coefficients
761
tc_r = 100e-6 # 100 ppm/C for metal film
762
tc_c = -750e-6 # -750 ppm/C for ceramic capacitor (X7R)
763
764
R_temp = R_nom * (1 + tc_r * (temps - 25))
765
C_temp = C_nom * (1 + tc_c * (temps - 25))
766
f_c_temp = 1 / (2 * np.pi * R_temp * C_temp)
767
768
axes[1, 1].plot(temps, f_c_temp, 'b-', linewidth=1.5)
769
axes[1, 1].axhline(y=f_c_nom, color='r', linestyle='--', alpha=0.7, label='Nominal')
770
axes[1, 1].axvline(x=25, color='g', linestyle=':', alpha=0.7, label='25°C')
771
axes[1, 1].set_xlabel('Temperature (°C)')
772
axes[1, 1].set_ylabel('Cutoff Frequency (Hz)')
773
axes[1, 1].set_title('Temperature Dependence')
774
axes[1, 1].legend(loc='upper right', fontsize=8)
775
axes[1, 1].grid(True, alpha=0.3)
776
777
plt.tight_layout()
778
plt.savefig('rc_circuit_plot7.pdf', bbox_inches='tight', dpi=150)
779
plt.close()
780
781
# Calculate statistics
782
fc_5pct_std = stds[1] # 5% tolerance
783
fc_temp_range = np.max(f_c_temp) - np.min(f_c_temp)
784
\end{pycode}
785
786
\begin{figure}[H]
787
\centering
788
\includegraphics[width=0.95\textwidth]{rc_circuit_plot7.pdf}
789
\caption{Component sensitivity analysis: tolerance and temperature effects.}
790
\end{figure}
791
792
\begin{table}[H]
793
\centering
794
\caption{Sensitivity Analysis Summary}
795
\begin{tabular}{lcc}
796
\toprule
797
\textbf{Parameter} & \textbf{Value} & \textbf{Unit} \\
798
\midrule
799
Nominal $f_c$ & \py{f"{f_c_nom:.1f}"} & Hz \\
800
Std Dev (5\% tolerance) & \py{f"{fc_5pct_std:.1f}"} & Hz \\
801
$f_c$ Range (-40 to 85°C) & \py{f"{fc_temp_range:.1f}"} & Hz \\
802
\bottomrule
803
\end{tabular}
804
\end{table}
805
806
\section{Conclusions}
807
808
This laboratory analysis of RC circuits demonstrated:
809
810
\begin{enumerate}
811
\item \textbf{Transient Response}: The time constant $\tau = \py{f"{tau*1000:.3f}"}$ ms governs charging/discharging dynamics, with the circuit reaching 99.3\% of final value in $5\tau$.
812
813
\item \textbf{Frequency Response}: The first-order RC filter exhibits a cutoff frequency of $f_c = \py{f"{f_cutoff:.1f}"}$ Hz with -20 dB/decade roll-off and -45° phase shift at cutoff.
814
815
\item \textbf{Filter Design}: Low-pass, high-pass, and band-pass configurations were analyzed, showing how cascaded stages increase selectivity with -20n dB/decade roll-off for n stages.
816
817
\item \textbf{Signal Processing}: RC filtering improved SNR by \py{f"{snr_improvement:.1f}"} dB for the test signal with high-frequency noise.
818
819
\item \textbf{Sensitivity}: Component tolerances of 5\% result in cutoff frequency standard deviation of \py{f"{fc_5pct_std:.1f}"} Hz, requiring careful component selection for precision applications.
820
\end{enumerate}
821
822
The computational analysis ensures all results are reproducible and can be easily modified for different circuit parameters.
823
824
\end{document}
825
826