Skip to content

Instantly share code, notes, and snippets.

@safiire
Created February 10, 2014 18:40
Show Gist options
  • Select an option

  • Save safiire/8921627 to your computer and use it in GitHub Desktop.

Select an option

Save safiire/8921627 to your computer and use it in GitHub Desktop.
RC Lowpass Filter Simulation in both Ruby and Rust for comparison.
#!/usr/bin/env ruby
# encoding: UTF-8
require 'complex'
Tau = 2 * Math::PI
TableWidth = 20
#######################################################################
## This is a script I was using to simulate an analog Resistor
## Capacitor in series filter, to see if I had the math right.
## It generates a table showing the Voltage, Current, and Impedance
## Across a resistor R1, and Capacitor R2.
##
## Sample Output:
## Sinusoidal AC signal exactly at the cutoff 723.4315595086151 is attenuated by -6.020599913279624 dB
##
## AC Voltage: 1.0V @ 723.4315595086151 hz
## R1 = 220.0 Ω, C1 = 1.0e-06 F
##
## R1 C1 Total
## ------------------------------------------------------------------
## E | 0.70711 ∠45.0°| 0.70711 ∠-45.0°| 1.00000 ∠0.0°| V
## I | 0.00321 ∠45.0°| 0.00321 ∠45.0°| 0.00321 ∠45.0°| A
## Z | 220.00000 ∠0.0°| 220.00000 ∠-90.0°| 311.12698 ∠-45.0°| Ω
## ------------------------------------------------------------------
## The cutoff of this RC filter is at 723.4315595086151 hz
## It is attenuating the 1.0V input sine to -6.020599913279624 dB
##
## Frequency Response:
## ----------------------------------------------------------------
## | |
## |****************************** |
## | *** |
## | *** |
## | ** |
## | * |
## | ** |
## | ** |
## | ** |
## | **** |
## | ***************|
## ----------------------------------------------------------------
##
## By Saf Allen, 2013
## Get deciBels between ref and val
def db(ref, val)
20 * Math.log10(val / ref)
end
####
## I want a complex number to display in polar form with degrees
class Complex
def to_s
degrees = self.arg / Tau * 360.0
"#{"%.5f" % self.abs} ∠#{degrees}°".rjust(TableWidth)
end
end
####
## Class to represent a resistor
class Resistor
def initialize(ohms)
@r = ohms
end
## Get value
def value
@r
end
## Just the value in ohms
def resistance
@r.to_c
end
## Impedance is the same thing as resistance for a resistor
## because resistors have no reactance
def impedance(frequency = 0)
@r.to_c
end
end
####
## Class to represent a capacitor
class Capacitor
def initialize(farads)
@c = farads
end
## Get value
def value
@c
end
## Ideal capacitors have no resistance
def resistance
0.to_c
end
## Impedance is R + jX, X is reactance
## Reactance, X, of a capacitor is X = 1/(2πfC)
def impedance(frequency = 0)
Complex(0, -(Tau * frequency * @c)**-1)
end
end
####
## Class to simulate an RC filter circuit
class RCFilter
## Set the values of the RC Filter
def initialize(resistor, capacitor)
set_input(1.0)
@resistor = Resistor.new(resistor)
@capacitor = Capacitor.new(capacitor)
end
## Set the input AC voltage and frequency
def set_input(voltage = 1.0, frequency = 0.0)
@voltage = voltage
@frequency = frequency
end
## What is the cutoff of this RC filter? 1/(2πRC)
def cutoff
(Tau * @resistor.value * @capacitor.value)**-1
end
## What is the total impedance, Z, of this circuit?
def total_impedance
@resistor.impedance(@frequency) + @capacitor.impedance(@frequency)
end
## Total current in this circuit, similar to I = V/R, I = V/Z
def total_current
@voltage / total_impedance
end
## Voltage across the resistor
def voltage_across_resistor
total_current * @resistor.impedance(@frequency)
end
## Voltage across the capacitor
def voltage_across_capacitor
total_current * @capacitor.impedance(@frequency)
end
## Do a frequency sweep, I'm using MIDI notes because they are logarithmic
def frequency_sweep
0.step(127, 2).map do |midi|
## Find the frequency of this midi note number
## Set the AC voltage to this frequency, 1 volt
f = 440 * 2**((midi - 69) / 12.0)
set_input(1.0, f)
## Now check the voltage across the capacitor to see how
## the filter attenuates the amplitude of that frequency
voltage_across_capacitor.real
end
end
## print an ascii graph of the frequency response
def print_frequency_response_graph
line = " #{"-" * 64}"
## First map the 0.0 - 1.0 voltages to 0 - 10 integers
frequency_response = frequency_sweep.map{ |voltage| (voltage * 10).to_i }
## Print out a 64x10 ASCII graph
puts "Frequency Response:"
puts line
10.downto(0) do |voltage|
print "|"
frequency_response.each do |response|
print (voltage == response ? "*" : " ")
end
print "|\n"
end
puts line
end
## print a table
def print_table
divider = '-' * (TableWidth * 3 + 6)
current = total_current
puts
puts "AC Voltage: #{@voltage}V @ #{@frequency} hz"
puts "R1 = #{@resistor.value} Ω, C1 = #{@capacitor.value} F\n\n"
puts "#{"R1".rjust(TableWidth)}#{"C1".rjust(TableWidth)}#{"Total".rjust(TableWidth)}"
puts divider
puts "E |#{voltage_across_resistor}|#{voltage_across_capacitor}|#{@voltage.to_c}| V"
puts "I |#{current}|#{current}|#{current}| A"
puts "Z |#{@resistor.impedance(@frequency)}|#{@capacitor.impedance(@frequency)}|#{total_impedance}| Ω"
puts divider
puts "The cutoff of this RC filter is at #{cutoff} hz"
puts "It is attenuating the #{@voltage}V input sine to #{db(@voltage, voltage_across_capacitor.real)} dB"
end
end
####
## Let's test this to see if it works
## Variables, Change these here to change the circuit
r1 = 220.0 # R1 = 220 Ohms
c1 = 0.000001 # C1 = 1uF
## Let's create a circuit with those R C values
filter = RCFilter.new(r1, c1)
## The RC values determine the filter's cutoff frequency
cutoff_frequency = filter.cutoff
## If I send an AC signal at exactly the cutoff frequency into this circuit,
## the voltage across the capacitor should be attenuated by ~6.02 dB
filter.set_input(1.0, cutoff_frequency)
attenuation = db(1.0, filter.voltage_across_capacitor.real)
puts "Sinusoidal AC signal exactly at the cutoff #{cutoff_frequency} is attenuated by #{attenuation} dB"
## Now I can print out a full table of the voltages,
## currents, impedances, and their totals in the whole circuit
filter.print_table
## And, by doing a frequency sweep on the AC input voltage, I can
## manually draw a frequency response of this filter
puts
puts filter.print_frequency_response_graph
extern mod extra;
use extra::complex::Cmplx;
use std::float;
use std::num::{pow};
use std::iter::{range_step};
////
// A resistor
struct Resistor {
value: float
}
impl Resistor {
fn new(ohms: float) -> Resistor {
Resistor{value: ohms}
}
// Just the value in ohms
fn resistance(&self) -> Cmplx<float> {
Cmplx::new(self.value, 0.0)
}
// Impedance is the same thing as resistance for a resistor because resistors have no reactance
fn impedance(&self) -> Cmplx<float> {
self.resistance()
}
}
////
// A capacitor
struct Capacitor {
value: float
}
impl Capacitor {
fn new(farads: float) -> Capacitor {
Capacitor{value: farads}
}
fn resistance() -> Cmplx<float> {
Cmplx::new(0.0, 0.0)
}
// Impedance is R + jX, X is reactance
// Reactance, X, of a capacitor is X = 1/(2πfC)
fn impedance(&self, frequency: float) -> Cmplx<float> {
let Tau = 2.0 * float::consts::pi;
let reactance = 1.0 / (Tau * frequency * self.value);
Cmplx::new(0.0, -reactance)
}
}
////
// Simulate an RC filter circuit
struct RCFilter {
capacitor: Capacitor,
resistor: Resistor,
voltage: float,
frequency: float
}
impl RCFilter {
// Constructor
fn new(resistor: Resistor, capacitor: Capacitor) -> RCFilter {
RCFilter{resistor: resistor, capacitor: capacitor, voltage: 1.0, frequency: 0.0}
}
// Set the filter's input
fn set_input(&mut self, voltage: float, frequency: float) {
self.voltage = voltage;
self.frequency = frequency;
}
// What is the cutoff of this RC filter? 1/(2πRC)
fn cutoff(&self) -> float {
let Tau = 2.0 * float::consts::pi;
1.0 / (Tau * self.resistor.value * self.capacitor.value)
}
// What is the total impedance, Z, of this circuit?
fn total_impedance(&self) -> Cmplx<float> {
self.resistor.impedance() + self.capacitor.impedance(self.frequency)
}
// Total current in this circuit, similar to I = V/R, I = V/Z
fn total_current(&self) -> Cmplx<float> {
Cmplx::new(self.voltage, 0.0) / self.total_impedance()
}
// Voltage across the resistor
fn voltage_across_resistor(&self) -> Cmplx<float> {
self.total_current() * self.resistor.impedance()
}
// Voltage across the capacitor
fn voltage_across_capacitor(&self) -> Cmplx<float> {
self.total_current() * self.capacitor.impedance(self.frequency)
}
// Do a frequency sweep, I'm using MIDI notes because they are logarithmic
fn frequency_sweep(&mut self) -> () {
let frequency_response = @mut std::vec::from_elem(64, 0);
for i in range(0, 64) {
let midi = i * 2;
let f = 440.0 * pow(2.0, ((midi as float) - 69.0) / 12.0);
self.set_input(1.0, f);
frequency_response[i] = (self.voltage_across_capacitor().re * 10.0) as int;
}
// Print out a 64x10 ASCII graph
let line = " ----------------------------------------------------------------";
println(line);
for voltage in range_step(10, -1, -1) {
print("|");
for response in frequency_response.iter() {
if voltage == *response {
print("*");
}else{
print(" ");
}
}
print("|\n");
};
println(line);
}
}
fn main() {
let resistor = Resistor::new(220.0); // Ohms
let capacitor = Capacitor::new(0.000001); // Farads
let mut filter = RCFilter::new(resistor, capacitor);
filter.frequency_sweep();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment