Filtering PWM: Continued

In an earlier post I spoke in a fairly abstract way about filtering Pulse Width Modulated signals. I explained how when using a basic RC filter we have to compromise between the stability of the DC output and the time needed for the filtered signal to reach the average voltage of the source. Today I’d like to return to this problem with a slightly more analytical approach. My goal in doing this is to show you the process I will use to select the filter values for my Arduino PWM project.

Since this is ultimately for my Arduino based project I will be using a frequency of 980Hz for my calculations. This gives me a period of 0.00102s per cycle.

The Method

Step Response Equation for RC Circuits

My approach to this problem is fairly straightforward, if a bit computationally expensive. By using the step equation for voltage I introduced in my post regarding the step response of RC circuits, I should be able to move through each step of the PWM signal for a given RC value until the high and low voltages stabilize. Each step I will set V0 as the output of the last step and Vs to 0 or 5 Volts (Based on whether the step is up or down). At that point I can note how many steps it’s taken and what the remaining peak to peak voltage is. For my initial calculations I will use a duty cycle of 50% meaning a step occurs every 0.00051s.

Crunching Some Numbers

I started out attempting to do this project in Excel. I set up a cell formula and began populating results but before I knew it my table had grown well beyond a workable size and I decided to step back and regroup.

My solution was to write a python script that given a frequency, duty cycle, array of RC values and high/low voltage values would run the calculations for me and output a plot of the resulting ripple voltages and stabilization (settling) times. I will add my code at the end of this post so you can play with it yourself.

I started out with a fairly wide range of RC values from 0.0001 to 0.1 and stopped the calculations when the high voltage was within 0.01mV of the previous high voltage. This gave me the following two plots:

RC Values vs Ripple Voltage and Settling Time

Here we can get an idea of the nature of these two values. We can identify that (as expected) the ripple exponentially decreases as the RC value increases. Meanwhile the settling time appears to be a fairly linear function increasing as the RC values increase. It is also clear looking at these graphs that my initial range was much too large. I am going to eliminate all RC values where the peak to peak voltage is far greater than 1V by starting the scale at RC = 0.001. Meanwhile I will eliminate all values with a settling time greater than 0.1s (approximately RC = 0.01). Additionally I modified the voltage resolution to be 10mV.

RC Values vs Ripple Voltage and Settling Time (Modified Range)

This has made the picture significantly clearer. Particularly with the voltage ripple we can now see the slope of the exponential decay.

Sanity Check

Low Pass Filter Simulation Circuit

Before finishing up for today I wanted to make sure my math made sense. To do so I built a simple RC filter in NI Multisim and fed it a steady square wave signal at 980Hz. I chose an RC of 0.006 which should give me a peak to peak voltage after filtering of 0.2V and a settling time of approximately 0.024s (24ms). The output I received from the oscilloscope was as follows:

Ripple Voltage Test

The peak to peak voltage of the ripple appears to be just over 200mV (0.2V) which is exactly what we predicted!

Settling Time Test

The settling time is a little bit more up to interpretation. I found based on how I set the v_resolution in my program (value that v_high – v_high_last must be under to be seen as stable) I got profoundly different values for this reading. Basically what’s happening is that the output voltage is exponentially increasing towards the average voltage. This means even though the increases stop being visible on the oscilloscope output they are still proceeding each step in exponentially smaller and smaller amounts. That being said we can see that within the 25ms timeframe the output has absolutely stabilized. In practice we can likely get by with a much smaller timeframe based on the oscilloscope output above.

That’s probably enough science for today. I’ll be building further on these concepts in my next post where I will be filtering the PWM signal from my Arduino to see what I can create with it.

Code

Finally as promised here is the code I used for these calculations. Full disclosure I am not a software designer by any stretch so this may not be the prettiest program in the world but I hope it is helpful.

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import math
import pandas as pd


#Generate an array of rc values between rc_start and rc_stop with a step size of rc resolution
def create_rc_array(rc_start, rc_stop, rc_resolution):
    array = np.arange(rc_start, rc_stop, rc_resolution, dtype=float)
    return array


#Calculates period, time high and time low based on frequency and duty cycle
def get_period(frequency, duty_cycle):
    period = 1/frequency
    time_high = period*duty_cycle
    time_low = period*(1-duty_cycle)

    return period, time_high, time_low


#Given starting voltage, final voltage, rc and time solves step responce equation
def v_calc(v0, vf, rc, time):
    exponent = -(time/rc)
    v1 = vf
    v2 = v0 - vf

    return v1 + v2*math.exp(exponent)


#Iterates through list of rc values
#For each rc value will calculate v_high and_v_low for each step until v_high-v_high_last < v_resolution
def generate_values(rc_values, period, time_high, time_low, v_resolution, v_low, v_high):
    delta_v_list = []
    settling_times = []

    for rc in rc_values:
        time = 0
        v_start = v_low
        v_up_old = 5
        v_up = 0
        v_down = 0
        delta_v = 5
        while abs(v_up_old - v_up) > v_resolution:
            time = time + period
            v_up_old = v_up
            v_up = v_calc(v_down, v_high, rc, time_high)
            v_down = v_calc(v_up, v_low, rc, time_low)
            delta_v = abs(v_up-v_down)

        delta_v_list.append(delta_v)
        settling_times.append(time)

    return delta_v_list, settling_times


def main():
    #Declare parameters
    frequency = 980
    duty_cycle = 0.5

    rc_start = 0.001
    rc_stop = 0.01
    rc_resolution = 0.00001

    v_low = 0
    v_high = 5
    v_resolution = 0.01

    #create derived values
    rc_values = create_rc_array(rc_start, rc_stop, rc_resolution)
    period, time_high, time_low = get_period(frequency, duty_cycle)
    
    #Run calculations
    delta_v_list, settling_times = generate_values(rc_values, period, time_high, time_low, v_resolution, v_low, v_high)


    #Plotting
    sns.set_theme()

    ripple_frame = pd.DataFrame({'RC Values (1/s)': rc_values, 'Peak to Peak Voltage Ripple (V)': delta_v_list})
    settle_frame = pd.DataFrame({'RC Values (1/s)': rc_values, 'Settling Time (s)': settling_times})

    fig, ax = plt.subplots(1,2)

    sns.lineplot(x='RC Values (1/s)', y='Peak to Peak Voltage Ripple (V)', data=ripple_frame, ax=ax[0])

    sns.lineplot(x='RC Values (1/s)', y='Settling Time (s)', data=settle_frame, ax=ax[1])


    plt.show()



main()