Sine Waves On Arduino: Cleanup

In my last post I used a simple low pass filter to start generating sine waves on my Arduino Uno. The code I wrote for this project probably seems pretty clear since the program is so basic. However in my haste I took a number of shortcuts which will lead to the code becoming confusing and inefficient as I start adding complexity. Today I’m going to work on cleaning up this code by discussing some best practices. I know this isn’t as exciting as moving forward with the project but it is important. This cleanup will save me a lot of time in future by making sure I am starting with, and continuing to write, simple, efficient and readable code.

I’ll be going through these changes one by one in the body of this post. At the end of the post I’ll provide the new code in it’s entirety for your review.

Table Lookups

void loop() {
  for (i = 0; i < N; i++){
    duty_cycle = 127+127*sin(i*(2*3.14)/N);
    analogWrite(PWM_out, duty_cycle);
    delay(sample_time);
  }
}

Above I’ve shown the original loop I wrote for this program. For each step of the sine wave this loop calculates the value of sine, writes that value to the PWM and delays for a given time. The issue here is that any calculations we are doing at runtime take time. That means every time we calculate our duty cycle there is a small delay. This extra delay will cause our frequency to become less accurate.

A better solution is to use a look-up table. By conducting all of our calculations before hand and storing the results we can remove the equation from the main loop. To do this I’ll first initialize a table and then calculate it’s values in our setup function:

int sine_table[50];

void setup() {
  for (i = 0; i < N; i++){
    sine_table[i] = 127+127*sin(i*(2*3.14)/N);
  }
}

Once I’ve set up a table in this way I can simply set the PWM value based on the index of this table in our main loop:

void loop() {
  for (i = 0; i < N; i++){
    analogWrite(PWM_out, sine_table[i]);
    delay(sample_time);
  }
}

Function Extraction

void setup() {
   for (i = 0; i < N; i++){
     sine_table[i] = 127+127sin(i(2*3.14)/N);
   }
 }

Looking back at our setup function you may not see any issues. Currently it looks pretty clean. What happens though when I start doing more than just this one thing during startup? Imagine if we were initializing several wave tables, this would start to get pretty ugly pretty fast.

The technique we use here is called function extraction. Basically the idea is to create functions within your program to handle each specific task your program does. This allows you to move code specific to those tasks out of your main code. Doing this will not change the functionality of the code but makes the code significantly more clear and readable, especially as your program grows more complex.

void get_sine_table();  

First we declare our new function at the top of the code (with the global variable definitions).

void get_sine_table(){
  for (i = 0; i < N; i++){
    sine_table[i] = 127+127*sin(i*(2*3.14)/N);
  }
}

Next we can implement the function at the end of our code. Notice the contents of this function are the same as what we originally had in our setup.

void setup() {
  get_sine_table();
}

Finally we update our setup to call this function. Notice how much clearer this is. By giving the function a descriptive name we can now tell exactly what is being done in the setup.

Variables vs Definitions

int PWM_out = 5;           //PWM output at pin5

int sample_time = 2;       //ms
int PWM_frequency = 980;   //Hz
int N = 50;
int duty_cycle;            //unitless
int sine_table[50];

Next I’d like to have a look at the variable definitions of this program. These variables can be roughly separated into two categories, those which change during runtime and those that do not. Those which change are best stored as global variables however those which don’t we can handle in a better way.

When a variable is defined a location in memory is set aside for it and every time it is accessed that memory location is read (when the variable is called) or written to (when the variable is changed). This means each variable we have in the code is taking up a small amount of our system memory and each time we access them it takes time (though an infinitesimally small amount).

Using the keyword #define we can bypass this process for static variables. Define is what is known as a preprocessor directive. That’s a fancy way of saying that it is not an instruction for your Arduino, it is an instruction for the compiler which builds your program and sends it to the Arduino. When you build your program the compiler will search the code for any instances of the name you defined and replace it with the value given. this means rather than having to store and access a variable in the program the value is directly written into the code. All the while maintaining the ease of use and readability that having the value named provides.

#define PWM_PIN 5            //PWM output at pin5
#define SAMPLE_TIME 2        //ms
#define PWM_FREQ  980        //Hz
#define N 50

int sine_table[50];

Above I have changed all variables that do not need to be modified at runtime to definitions. Notice I also removed the duty cycle variable. Since I’m setting the value of the PWM directly from the table it is not needed.

Putting it Together

//www.SamVsSound.com 11/08/2020

#define PWM_PIN 5            //PWM output at pin5
#define SAMPLE_TIME 2        //ms
#define PWM_FREQ  980        //Hz
#define N 50

int sine_table[50];

void get_sine_table();

void setup() {
  get_sine_table();
}

void loop() {
  for (int i = 0; i < N; i++){
    analogWrite(PWM_PIN, sine_table[i]);
    delay(SAMPLE_TIME);
  }
}

void get_sine_table(){
  for (int i = 0; i < N; i++){
    sine_table[i] = 127+127*sin(i*(2*3.14)/N);
  }
}

Here we have our new cleaner code. I know with a program this small these changes are not striking. Still, you can imagine the difference these will make as we add complexity going forward. I’m sorry this turned into more of a coding tutorial than an audio project but I’ll be back soon to start building this project up into something cool.

Sine Waves on Arduino

I’ve spent the last few weeks going over the fundamentals of PWM and implementing them on my Arduino Uno. Today it’s time to apply those basics to something a little more interesting. I want to get my Arduino outputting sine waves. I’m going to focus on creating a workable 10Hz sine wave and then in forthcoming posts I’ll look at manipulating the frequency and modelling more interesting wave-forms.

Know Thy Limits

Before I go to far into this project I want to talk a little bit about the limitations of the Arduino. The Arduino has a maximum PWM frequency of 980Hz which is quite low, especially when working in the audio space. As an example if I wanted a sin wave with a frequency of 100Hz I would only be able to use approximately 10 PWM cycles per sine wave. The fewer cycles you have per wave the more misshapen your output wave will be. I’ve had some success creating “relatively” clean 100Hz waves but going much higher than this results in the wave breaking down entirely.

One nice thing about the sine wave is that the changes in voltage through the wave are continuous. This means we can push our samples much closer than the normal settling time discussed in my last post. However, the idea of creating an full audio oscillator with this method is an impossibility. An LFO or envelope generator though are still realistic goals.

If you are interested in creating an audio oscillator using your Arduino I would recommend looking into off-board DACs (Digital to Analog Converters) which use serial communication with the board to provide much higher output speeds and resolutions.

The Filter

Low Pass Filter With RC=0.0047

To get started I set up an RC Low Pass Filter. After the experiments in my last post I settled on an RC value of 0.005 (or 0.0047 more accurately). This should give me a good balance between stability and low settling time. I used a 47K ohm resistor and a 0.1uF capacitor to accomplish this.

The Sine Wave

Sine Wave Equation with DC Offset

I want to have a quick look at the sine wave equation before I start coding. There are a number of variables which we can use to adjust the waves properties. The first thing to note is the variable n. If you’ve worked with continuous sine waves you may have seen this formula as a function of t. Where t represents time (a continuous variable), n represents discreet samples. This means n will always be a whole number (0, 1, 2, …) which lends itself to working in a digital environment.

The next variable we have to look at is w which represents the angular velocity (also called normalized frequency depending on your background). This represents how quickly your sine wave will move through a full cycle. We can determine the value for w with the following formula:

In the above formula N represents the total number of samples it will take to traverse a full cycle of the sine wave.

Next up is A which is known as the amplitude or scaling factor of the wave and C which represents the offset. A typical sine wave varies between 1 and -1 centered at 0. However, on the Arduino the duty cycle of a PWM signal is set by an integer between 0 and 255. To get our sine wave to fill this range we first multiply the sine wave by 127 (A). This creates a wave which varies between -127 and 127. Next we add the offset of 127 (C) to the output which gives us our final sine values between 0 and 254.

Choosing Your Frequency

Two factors affect the frequency of the sine wave. N (Discussed above) is the first. If you are moving through your samples at a constant rate the more samples you have the longer it will take. The trade off here is as you might imagine, the more samples you have the cleaner your sine wave will look.

Sample time (given in ms) is the other. This is the time the Arduino will wait between each step of the sine wave. This again is something of a balancing act as you must allow enough time for your signal to stabilize at each new voltage level.

Taken together we can create the following formula to determine our final frequency:

Discreet Frequency Calculation

You may recognize that this is essentially the calculation to find frequency from the period (f = 1/p). In this equation N*(sample time) represents the period (the number of samples * the time it takes for each sample). Since sample time is in ms we use 1000 as a conversion factor.

The Code

Now that we’ve got everything set up it’s time to write the code. I’ll start out by declaring the necessary variables:

int PWM_out = 5;           //PWM output at pin5

int sample_time = 5;       //ms
int N = 20;                //int
int duty_cycle;            //unitless

int i;                     //Counter variable

Here you can see I’ve set the sample_time to 5 and N to 20. Using the formula I showed earlier this should give me a frequency of 10Hz (1000/(5*20) = 1000/100 = 10Hz).

For this program I don’t need to run any kind of set-up. That means I can leave the set-up block blank:

void setup() {
}

Next comes our main loop where all the magic happens:

void loop() {
  for (i = 0; i < N; i++){
    duty_cycle = 127+127*sin(i*(2*3.14)/N);
    analogWrite(PWM_out, duty_cycle);
    delay(sample_time);
  }
}

Lets go through this one in a bit more detail. First I set up a for loop which will iterate through the code N times incrementing i each time.

Next I calculate the duty_cycle value using the sine wave equation. Notice I have placed the angular velocity equation directly inside the sine equation (2*pi/N) to simplify the line. From there we write that duty cycle value to the PWM_out pin (pin5) using analogWrite.

Finally there is a delay command. The delay waits for the amount of time defined by sample_time before beginning the next iteration of the loop.

10 Hz Sine Wave From Arduino

And there we have it. As you can see from my oscilloscope output the wave is far from perfect, nonetheless we’ve created an analog sine wave using a digital microcontroller! This is no small accomplishment and I can’t wait to build on it further. I encourage you all to set up this code and play around. Try changing the variables and values to see how each impacts the design. I’ll be back soon with more!

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

Filtering PWM Signals

In a recent post I talked about how you can use Pulse Width Modulation to create a simple voltage controller. However PWM is only half the story. Once we have our PWM signal how do we transform this from a malformed square wave into a nice steady DC voltage? There are many different techniques that can be used to do this but today I’d like to introduce you to one of the simplest, Low Pass Filters.

Low Pass Filters

I briefly introduced RC low pass filters in my post on Square Waves in RC Circuits. Put simply these are circuits which allow low frequency signals to pass through while attenuating high frequency signals. The reason for this has to do with the capacitors charging. If you recall from my previous post the time a capacitor takes to charge in an RC circuit is approximately equal to 5 times the time constant of the circuit (RC). When the frequency is low enough that the capacitor has the chance to fully charge and discharge each cycle the signal will pass through. If however the capacitor cannot fully charge/discharge a certain amount of the current will always be passing through the capacitor to ground which attenuates the signal at the output. The higher the frequency the greater the attenuation.

The key understanding here is what happens to the signal when it is being attenuated. You might expect if you pass a 5V square wave (0-5V) through a filter which reduces the amplitude by 50% that the resulting 2.5V signal would be from 0-2.5V. However, this is not the case. Since the capacitor cannot fully charge or discharge the signal will stabilize at the average voltage of the incoming signal. In the case of this example the signal would travel between 1.25V and 3.75V. As we attenuate it further we can get a smaller and smaller peak to peak voltage (always centered around the average voltage). The smaller these peaks, the closer you get to your target DC voltage.

Ripple Vs Stabilization Time

So that all sounds great but how do we know what capacitance and resistance to use? This actually gets a bit more complicated. When choosing our resistors and capacitors we often find ourselves balancing two undesirable characteristics of the circuit.

Consider this first filter. Obviously this is a long way from a smooth DC voltage. There is a distinct ripple in the output with the voltage moving up and down in a sharp triangle pattern. As discussed earlier we should be able to reduce this ripple by increasing the time the capacitor takes to charge (increasing the resistance or capacitance) to further attenuate the signal. This will however unfortunately introduce a new problem.

Here we can see that by increasing the attenuation of the signal we are able to produce a much smoother output signal. The issue here is on the left side of the simulation output. Since the capacitor is only charging and discharging a small amount the signal takes significantly longer to stabilize at the average voltage.

Where on this spectrum your filter falls depends largely on your application. If you need a very stable signal which will not vary over time you can use a large capacitor and/or resistance. If on the other hand your signal strength needs to change quickly over time and your circuit can handle a bit more ripple you may opt for a smaller capacitor to support this behavior.

As you might imagine there are additions we can make to this circuit to improve both of these behaviors. By adding additional complexity to this filter we can develop a more robust digital to analog converter. I hope though that this has provided something of a starting point to begin generating analog signals from your digital devices.

Pulse Width Modulation

You may have encountered Pulse Width Modulation when working with Arduino or other microcontrollers. PWM is a powerful tool that is often implemented to control motors and dim LEDs in electronics projects. While it’s typical applications are not always audio-centric PWM can still be a useful tool in our tool belt. PWM can be used to modify the sound of our humble square wave oscillators and is foundational to many digital to analog converters.

Duty Cycle

The duty cycle of a square wave is the ratio of time the wave spends in its high state to the time it spends low. A perfect square wave would have a duty cycle of 50%. If you increase the time the wave spends high the duty cycle increases, if you decrease it the duty cycle decreases. A duty cycle of 100% would represent a DC voltage (at the output voltage of your source). Meanwhile a duty cycle of 0% would represent a steady 0V.

Below I have graphed some basic PWM signals (blue) along with their duty cycle and average voltage (orange):

50% Duty Cycle
75% Duty Cycle
25% Duty Cycle

Average Voltage

The key to understanding how to use PWM often lies in understanding average voltage. In the graph above you can see the average voltage (orange line) is highest when the duty cycle is high. This should make intuitive sense since with, for example, a 75% duty cycle the voltage is high for 75% of the time and 0V for 25% of the time. You can find the average voltage of any PWM signal by multiplying the duty cycle by the max voltage. In a 5V circuit for instance, with a 20% duty cycle, the average voltage would be 1V (5*0.2=1).

A Quick Word On Frequency

As you might guess the frequency of a PWM signal can be critical to your system design. In an audio oscillator the role of frequency is largely unchanged, The frequency will set the tone of your output while the pulse width will allow you to adjust the fullness of your sound.

When you are using PWM to create an analog signal things get a bit more complicated. Typically a specific frequency is chosen and the subsequent filters are designed based on it (I will go further into this process when I discuss filtering PWM signals in a future post) . In choosing this frequency we would be looking for a balance between speed and control. At faster frequencies it is easier to filter the signal (to obtain a steady voltage) and you can change the output voltage quicker. However, the faster the signal the harder it becomes to control the exact duty cycle. You want to maintain the fastest frequency possible where you can still adjust the duty cycle with the precision your application requires.

I hope this has given you a framework to begin working with PWM. Keep an eye out for my next post; I will be looking at filtering these PWM signals to obtain a steady voltage.

Square Waves In RC Circuits

Last week I introduced the Step Response in RC Circuits and we looked at a simple example of turning on a power switch. Today I’d like to extend this intuition to investigate the response of an RC circuit supplied with a square wave signal. This intuition forms the basis of understanding more complex concepts like filters and pulse width modulation.

Square Waves

We’ve used square waves quite a bit when working with simple oscillators. They are easy to understand and easy to create, but how do they relate to the step response?

A square wave can be visualized as nothing more than a series of steps. First stepping up to the high voltage mark, then after some time stepping back down. Consider our example from last week, If instead of turning the power on and leaving it, you turned it on, waited for a period, then turned it back off. Repeating this process over and over would produce a square wave. It follows that we should be able to apply our step equation to the steps in a square wave.

A quick word about pulse width: One way square waves can be manipulated is by adjusting the “Duty Cycle.” This means changing the ratio of how long the signal is high versus how long the signal is low. To keep things simple today we will work with a 50% duty cycle meaning the signal will be high for the the same period it is low.

Frequency vs Period

When working with synthesizers you’ve probably heard signals described in terms of frequency. The frequency of a sound wave (Or any wave for that matter) is the number of times the signal cycles (goes up and back down) in a second and is expressed in Hz. For these calculations we need a slightly different descriptor for the speed of the waves. By taking the inverse of the frequency (1/f) we can find the period. The period describes the time in seconds that the wave takes to cycle. In a square wave with a 50% duty cycle we know that a step will take place twice every period (once up and once down). Further, these steps are evenly spaced. They take place at t=np and t=p(n+1/2) (Where n is any whole number).

Time Constant

Relationship Between Capacitor Charging (5V circuit) and Time Constant

In my last post I introduced the time constant. In an RC circuit the time constant is defined as the product of the resistance and capacitance (RC). It is used to determine the speed at which a capacitor will fully charge or discharge. Note the time constant is the same for both charging and discharging.

The graph above shows the relationship between the charging/discharging of a capacitor and the time constant in a 5V circuit. Since this is an exponential equation the capacitor will never fully charge or discharge (in theory) however for calculations we say that the capacitor is fully charged after 5 time steps. This charge would represent 99.3% of the maximum value. This means if you had a time constant of 0.2 the capacitor would take 1s to fully charge.

Filter Circuit

Passive Low Pass Filter Circuit

To illustrate how this all comes together lets have a look at the low pass filter circuit above. Let’s pass a square wave at 1Hz (50% duty cycle) into this circuit and see what happens. The first thing we need is our time constant for this circuit:

So if our time constant is 0.1 we know that it will take 0.5s for the capacitor to fully charge or discharge (5*0.1). With our frequency of 1Hz we know that the square wave will cycle every 1s (1/1). The steps in the square wave will occur every 0.5s (switching between stepping up and stepping down). This means every step will happen exactly when the capacitor reaches full or zero charge. So what does that look like?

Effect Of Low Pass Filter on 1Hz Square Wave

Here we can see the charging and discharging cycles have turned our square wave into a sawtooth. This is only the beginning of what filters can do for us though.

The important question here is what would happen if we changed the frequency? If we doubled the frequency to 2Hz the period would become 0.5s (1/2). This means the steps would be occurring every 0.25s instead of every 0.5s. If we bring back the equation used in my last post we can see how much voltage would build across the capacitor in this time:

Here we can see that with a frequency of 2Hz the output voltage will only reach 4.59V before the input steps back down to 0V. This attenuation only gets worse as the frequency increases. At a frequency of 4Hz the peak voltage falls to 3.57V. At 8Hz you only see a peak voltage of 0.304V.

Note: This is a slight oversimplification, since we are shortening both the high and low periods. This means not only the charging but also the discharging state will be cut short. The capacitor will be unable to fully charge or discharge so the attenuated signal will stabilize at an offset from zero volts.

It is this attenuation that makes this a low pass filter. Low frequency signals (below a certain critical point) are able to pass at full amplitude while high frequency signals are attenuated or even eliminated from the output. We’ll explore filters further at a later date but this should give you some idea of the mechanisms which allow them to function.

Step Response in RC Circuits

In passing I’ve referred to the fact that capacitors and inductors are time-dependent components but I never really gave any explanation of this assertion. I’d like to go over a simple case of time-dependent circuitry to clarify exactly what this means and how it differs from time-independent circuitry (resistors, diodes ect.).

Step Response?

Time dependent circuits in a nut shell are circuits which respond to changes in voltage or current over time. These changes can come in many forms but the easiest to wrap your head around (and do the math for) is a step. A step simply refers to any time the voltage (and by extension the current) in your circuit immediately changes from one value to another.

The most basic example of this is when you turn on a power switch on a device. The voltage in the device before the power was connected is typically 0V and after the switch is turned it immediately jumps to the devices operating voltage.

When we look at how the circuit responds to this change (i.e. how the current and voltage in different parts of the circuit change after this step occurs) we are looking at the step response. In a simple resistive circuit the change occurs immediately throughout the circuit however once you introduce capacitors and inductors the story gets a little more complicated.

RC Circuit?

A Simple RC Series Circuit

An RC Circuit is a circuit which contains only resistors and capacitors. These Resistors and capacitors can be arranged in series, parallel, or some combination of the two.

Capacitors

Before we get into the math it’s important to understand some of the properties of capacitors to understand how they react in circuits. A capacitor is made up of two conductive plates separated by a non-conductive layer referred to as a dielectric. These plates collect charge as current flows through the capacitor. When there is no charge on the plates current flows freely through the capacitor but as charge builds less and less current can pass through.

Once a capacitor becomes fully charged no current can flow through it and it behaves as an open circuit. Going back to our power switch example, when the power is off there is no charge on the capacitor. Once the switch is turned the current begins flowing around the circuit. This charges the capacitor until it reaches steady state. When the capacitor is fully charged no current flows and the voltage across the capacitor is maximized.

Math Time

Step Response Equation for RC Circuits

Wait! Don’t run, I promise it’s not as bad as it looks. There is a fairly satisfying calculus derivation for this equation but in the interest of keeping things high level I’ve skipped right to the final formula. That means all we have to do is find values for the unknowns above and we can plug them right in. So how do we find these unknowns?

The Time Constant – The first thing we want to find is a value called the time constant. This represents the speed at which the circuit reaches steady state. The time constant is R*C. The negative of the time is divided by the time constant in the exponent. We simply plug in the resistance and capacitance values from the circuit and we’re all set.

Vo – Vo is the starting voltage of the circuit. This would be the voltage before the step takes place. In our basic switching example this would be equal to 0V since there is no voltage across the capacitor before the switch is flipped.

Vs – Vs represents the steady state voltage. This would be the voltage in the circuit after a long time has passed (Once the capacitor is fully charged). To find this we can replace the capacitor with an open circuit and determine the voltage between the two points.

Now that we’ve defined these variables lets have a look at an example to see them in action.

Example

Example Circuit

Above is a basic example of a simple RC circuit. What we want to find out is how the circuit responds when the switch is closed at t=0. If we complete the equation given above for this circuit we can find the voltage at any time after the switch is flipped by entering the desired value of t.

The first thing we can fill in is the time constant. As I said above the time constant is equal to RC. This would be (10*100uF) for our circuit. Converting uF to F gives us 10*0.0001 and taking the inverse of this (since time is divided by it) gives us 1000. You can see above I have filled in this value above the exponent.

Next up is Vo. For a simple series circuit like this this step is very easy. Before the switch is closed the battery is disconnected from the circuit so no voltage is present in the circuit. This makes Vo equal to zero and it can be removed from the equation.

Finally we can calculate Vs which represents the steady state voltage of the system. Remember that when the capacitor is fully charged it allows no current to flow through the circuit. Since the voltage drop across a resistor is defined by Ohm’s Law (V=IR) and there is no current flowing through the circuit, there is no voltage drop across the resistor. This means the steady state voltage across the cap is the full battery voltage (9V).

Now we’ve got a fully defined function for the voltage at any time after t=0. The trick here is that the exponent (e^(-1000t)) becomes smaller the larger t becomes. This means the more time that passes the larger the voltage becomes until it reaches steady state. Graphing this function we get the following:

NAND Drone

NAND Drone Synth

I’ve built a lot of square wave synthesizers. They are some of my favorite projects. They’re generally fairly straight forward but can provide hours of fun. Further they provide a great starting point for newcomers to audio electronics. That being said after stringing together so many 555 timers and 40106 inverters I was looking for a way to breath some new life into these builds. I decided to start playing with some simple digital logic to see if I could develop a deeper sound while still sticking with basic square oscillators. What I came up with was a super-simple drone synth using NAND gates to combine three 40106 oscillators.

Parts List:

2 – 40106 Hex Schmitt Inverter ICs (One will work but 2 is ideal)
1 – 4011 Quad NAND Gate IC
3 – 0.01 uf Capacitors
3 – 500K ohm Potentiometers
6 – 20K ohm resistors
3 – 10K ohm resistors

Pretty short parts list right? This one comes together pretty easy thanks to the 40106 oscillators low parts count. Even with so few parts though, it still sounds awesome. So lets have a look at the circuit.

Circuit Diagram

NAND Drone Synth Circuit Diagram

Note : I’ve split the 40106 inverters between two chips. Since the 40106 includes 6 inverters this can be completed with 1 40106 chip. However, the 40106 can act unpredictably when all 6 inverters are switching at high speed so the behavior may not be optimal.

This circuit can be split into a few simple parts which I’d like to have a look at seperately.

Square Wave Oscillators

This circuit uses 3 40106 Inverter based oscillators. I used these oscillators primarily for their relative simplicity though essentially any square source could be fairly easily implemented in this design. I used a 0.01uF capacitor to set the frequency range for the oscillation but feel free to experiment with other values to get the frequency you desire. I then limited the current using a 500K ohm potentiometer to allow me to move through a wide band of frequencies.

Buffers

After the square wave is created each signal is passed through an additional inverter. The frequency of the 40106 oscillator is defined by the charging speed of the capacitor. This capacitors charging is a directly result of the current flowing out of the output and through the resistor. This means any addition power draw we add to this output will impact the frequency. To avoid this we send the signal through the additional inverter which isolates any further components from the oscillator circuit.

Attenuation

Next up we pass each signal through a basic voltage divider. This is simply to lower the amplitude of the inverter outputs before sending them into the 4011 Quad NAND chip.

NAND Gate

Here’s where the magic happens! The signals are each connected to two of the NAND gates such that each NAND gate has a unique set of two input signals (Sounds a bit weird but should be clear if you look at the circuit diagram). These NAND gates will be high by default but will go low when both inputs are high. This means any pair of your oscillators going high at the same time will cause at least one of the NAND outputs to go low.

Mixer

Finally we send each NAND output through a 10K ohm resistor and combine them into one output line. You can then wire this to the output style of your choosing and start making noise. Note the output from the 4011 is fairly low amplitude (about 1.5V peak to peak) so you may need to amplify it before connecting to a speaker.

Bigger is Better

I chose to use three oscillators here for convenience however there is no real need to stop there. Just be aware as you grow the number of NAND gates needed to have every combination will grow very quickly. For 4 oscillators you would need 6 NAND Gates, with 5 you would need 10 and so on.

Sound Waves

I just wanted to finish off by showing some of the sound waves I captured on my oscilloscope to give you an idea of what this circuit outputs:

Digital Logic

Working with digital logic may seem counter intuitive to the idea of sound synthesis. Audio is an analog phenomenon and as such we typically steer towards analog electronics to produce or manipulate it. That being said digital electronics can be incredibly valuable to us as synth enthusiasts. We can use them to breath new life and depth into humble square wave oscillators, control analog devices and interface with micro-controllers/processors. For this reason I wanted to provide an introduction to some of the more common digital logic devices you may encounter.

Digital Vs. Analog

Before going too far I’d like to reiterate the difference between analog and digital signals. An analog signal is a signal which varies across a range of voltages. Think of a sound wave, the wave is a continuous signal which rises and falls based on the intensity of the sound being captured.

A digital signal on the other hand varies between two discreet states. The signal is either high or low (0 or 1). Electronically speaking these two states are typically (though not always) 0V for low and 5V for high. Any values between these two states will either be assumed to be one of the two or ignored depending on the components used. The square waves we have produced using 555 or 40106 oscillators could be considered as digital signals as they oscillate between two distinct values.

Truth Tables

Truth Table for a Generic Inverter

Truth Tables are a tool we use to clarify the function of digital logic. The tables show a listing of all possible inputs to the system and the output which they would provide. In the inverter example above (and most of the components I will discuss today) these tables are quite straight forward however as you get into progressively more complicated digital circuits these truth tables will also become more complex.

Building Blocks

Once you have a digital signal whether its from a humble square wave oscillator or something more complex like a microprocessor, there are a number of operations you can carry out on it using digital logic chips. There are hundreds of different chips available but most of them fall into a few basic categories.

NOT Gate:

NOT Gates (commonly called inverters) are probably the most basic digital component you will encounter. As the name suggests they invert whatever signal you send to them. This means if you send in a 1, you will receive a 0 from the output. Alternately if you send in a 0 you will receive a 1. The 40106 I have used in previous projects is a special type of inverter which uses threshold voltages to interpret analog inputs.

AND Gate:

An AND Gate is a digital logic gate which takes two inputs (notice there are now two input columns on the truth table). The output of the gate is only high if both these inputs are high.

NAND Gate:

The NAND gate is actually a combination of the AND gate and the Inverter. Notice the circuit symbol is that of the AND Gate with the small circle from the inverter added. This small circle is used in circuit symbols to indicate that an output is inverted. This means the output of the NAND gate is opposite the output of the AND gate (as seen in the truth table). A NAND gate will output high at all times except when both inputs are high.

OR Gate:

The OR Gate is a gate which will output high when either (or both) of the inputs are high. This can be very useful for combining signals or triggers from multiple sources. There are also a few modified versions of the OR gate which I will look at next.

NOR Gate:

Similar to the NAND Gate this is an OR Gate with an added inverter. This means the NOR Gate will output high when no inputs are sensed. It will go low when Either (Or both) Inputs go high.

XOR Gate:

The XOR Gate is another special type of OR Gate. This time the output will go high when either input is high however if both inputs go high the output will go low.

This is by no means an exhaustive list but should give us a starting place to work from. We can also create progressively more complex gates by combining these different building blocks together. I hope this has given you some ideas about what you can accomplish using digital logic. Keep an eye out for more projects using these gates coming soon!