Created
February 10, 2014 18:40
-
-
Save safiire/8921627 to your computer and use it in GitHub Desktop.
RC Lowpass Filter Simulation in both Ruby and Rust for comparison.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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