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()

PWM on Arduino

I’ve spent a lot of time recently talking about different aspects of PWM and filtering. Today I’d like to take a step back from the theory and build what you might call the “Hello World” of digital synthesis, generating PWM signals with an Arduino. This should be a fairly straightforward project but will give me a vehicle to start experimenting with pulse width modulation in a more concrete setting.

Wiring It Up

Arduino PWM Circuit

The wiring for this one is pretty simple. On one side of the Arduino I’ve placed a 10K potentiometer between 5V and ground with the wiper connected to pin A0. This is the analog input that I will use to set the duty cycle of my pulse. The potentiometer will function as a basic voltage divider allowing us to control the voltage at the pin (between 0 and 5V).

On the other side of the Arduino we find the digital pins. These are where we can output our PWM signal. You’ll notice some of the pins (3, 5, 6, 10 and 11) have a tilde beside them. These are the pins capable of outputting PWM signals. An additional consideration is that Arduino UNO boards offer two different PWM frequencies. Pins 3, 10 and 11 output PWM at 490Hz while pins 5 and 6 output 980Hz. For this project I’ll be connecting my scope to pin 5 to read a signal output at that pin.

Writing The Code

The code for this project should be pretty straight forward as well. I’m writing it using the Arduino IDE which is available free from the Arduino website. I’m going to break it into a few sections and explain each one.

int duty_cycle_in = A0;
int PWM_out = 5;

int duty_cycle;

The first step is to declare a few variables. Here we define our duty cycle control to be at pin A0 and our PWM output to be at pin 5. This step can be skipped if you refer to the pins directly when you use them in your program however as your code grows this can make things really confusing. For the sake of keeping clear and concise code I always alias my pins in this way.

Additionally I create an integer variable to store the duty cycle. This variable will be updated with a value from duty_cycle_in and be used to write the PWM signal to PWM_out.

void setup() {
  pinMode(PWM_out, OUTPUT);
  pinMode(duty_cycle_in, INPUT);
}

Next up is our setup function. This function will run once when the Arduino powers on before moving into the main loop. Here we call the pinMode function twice. This function tells the Arduino what kind of information to expect from/send to each pin. We set PWM_out (pin 5) as an output and duty_cycle_in (pin A0) as an input.

void loop() {
  duty_cycle = analogRead(duty_cycle_in)/4;
  analogWrite(PWM_out, duty_cycle);
}

Finally we come to the main loop of the program. This is where the magic happens, the main loop is the code which runs on a repeating cycle as long as the Arduino is powered on.

The first step is to read from the duty_cycle_in pin (A0) and store that value into the duty_cycle variable. Notice that I divide the result by 4, this has to do with a difference in resolution between the analog input pins and PWM output pins. the analogRead function returns a value between 0 and 1023 depending on where the voltage falls between 0V and 5V. Meanwhile the analogWrite takes a value between 0 (always off) and 255 (always on). To deal with this discrepancy we simply divide the input by 4 to bring it from 0-1023 down to 0-255.

Next we take that duty_cycle value and write it to the PWM_out pin using analogWrite. together these two lines mean the Arduino will be constantly checking the value at the potentiometer and writing that value to the PWM pin as a duty cycle (with an average voltage equal to the value read from the potentiometer).