Arduino R2R DAC: Frequency

Modified R2R DAC With Added Analog Input

As promised I’m back with another update for this project. Today I’ve been playing around with my Arduino trying to get frequency control working. It took a bit of experimentation due to my use of timer interrupts but I got there. Additionally I’ve made some slight modifications to the other code to clean things up a bit.

The Big Idea

I played around with a few different approaches to adjust the frequency. After some exploration I settled on modifying the overflow counter (OCR1A) value. If you remember from my article on Timer Interrupts this is the value which the timer counts up to before outputting an interrupt. By manipulating this value using a potentiometer we can adjust the frequency in real time.

Some Math

To get started we need to figure out what values we’ll need to set the OCR1A register to. My goal is to get a frequency range of approximately 50Hz – 1kHz. Luckily the calculation to find the OCR value given a frequency is fairly straight forward.

By inputting the known values for clock frequency (16MHz on Arduino), prescaler (8) and N (50) I can solve for my upper and lower frequency bounds.

From here I know that the minimum OCR value I want to use is going to be 40. Additionally, the range of values between the lowest and highest value on the potentiometer should be 760. To set the lower limit we can add 40 to whatever our reading is on the potentiometer. For the upper limit we have to do a little more fancy footwork. Since the analog inputs on the Arduino output a value between 0 and 1023, I need to normalize this value. To achieve this I multiply the reading by 760.0 and then divide by 1023. Note the .0 is important as it forces the division to be handled as a float (maintains decimal points). Once the calculation is complete I can type cast it back to an int to be used by the program.

The final implementation of this algorithm is as follows. Note FREQ is the name I defined for the analog input at the start of my code.

ARR_check = int(((analogRead(FREQ)*760.0)/1023)+40);

Dealing With Ripple

You may notice in the code above I do not write the value directly to the OCR. I ran into problems as the oscillations in the circuit caused a fair amount of noise on my ground and voltage lines. This issue was compounded when connected to a speaker. This caused the analogRead value to bounce around and the OCR to be constantly updating with different values. The constant updates broke the flow of my waveform and left the output frequency bouncing around even when the pot wasn’t being touched.

By adding a set of decoupling capacitors I was able to limit this noise but not completely remove it. I used a 0.1uF capacitor and a 100uF capacitor between the ground and power rails.

My final solution in the program itself was to set the value of the analog read (after normalizing it to the desired range) to a variable (ARR_check). I then took the absolute value of this check minus the current value of the OCR. With this I could set the OCR to update only if the difference reached a certain margin. In my experimentation I found 25 was a large enough margin to stabilize things. You can see the resulting main loop below.

void loop(){
  ARR_check = int(((analogRead(FREQ)*760.0)/1023)+40);
  if (abs(ARR_check - OCR1A) > 25){
    OCR1A = ARR_check;  
    }
  
  delay(50);
  }

Note that this is not an ideal situation. The use of this buffer means I will not have a smooth range of frequencies as I turn the potentiometer. The output is more like a step tone generator with the value of the margin defining the step size. One interesting experiment might be to add a second potentiometer to adjust the margin value which would produce something similar to an Atari Punk Console. Only this APC would be capable of outputting sine and triangle waves. I’ll be looking into better ways to isolate this circuit and minimize the effect of this noise in future posts. For now though this solution still gets the variance I was after.

Some Optimization

One of my experiments while I was trying to get the frequency adjustment working involved increasing the size of the tables and adjusting how quickly I iterated through them. Attempting to do this however I ran into some problems with the Arduino Uno’s memory constraints. This led me to make some changes to the code to optimize the memory use.

uint8_t wave_table[N];

The first thing I did was do away with the 4 separate table arrays and replace them with a single array. I additionally changed the type of this array from int (32-bits) to uint8_t (8-bits). Since the DAC’s values range from 0-15 the uint8 is plenty to handle them. In fact even if I move up to 8-bits the uint8 will still be sufficient.

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

void get_rsaw_table(){
  for (int i = 0; i < N; i++){
    wave_table[i] = int(i*(15.0/N));
  }
}

void get_lsaw_table(){
  for (int i = 0; i < N; i++){
    wave_table[i] = int(15 - i*(15.0/N));
  }
}

void get_triangle_table(){
  for (int i = 0; i < N; i++){
    if (i<(N/2)){
      wave_table[i] = int(i*(15.0/(N/2)));
      }
    else{
      wave_table[i] = int(15 - (i-N/2)*(15.0/(N/2)));
      }
  }
}

Next I modified the get table functions to write directly to this single wave table rather than their respective tables.

void switch_pin_ISR(){
  curr_table = (curr_table + 1)%4;
    switch(curr_table){
    case 0:
      get_sine_table();
      break;
    
    case 1:
      get_rsaw_table();
      break;
    
    case 2:
      get_lsaw_table();
      break;

    case 3:
      get_triangle_table();
      break;
    }
  }

I then removed the calls to each get table function from set-up. To replace them I reated a switch statement in the button interrupt. This switch causes the wave_table array to be overwritten with a new table each time the button is pressed. The curr_table variable still tracks which table is currently selected and governs which get table function is called after each press.

ISR(TIMER1_COMPA_vect){
  v_level = wave_table[table_index];
  PORTB = v_level;         
  table_index = (table_index+1)%N;
  
}

Finally I removed the switch statement in the timer interrupt and set v_level to be updated directly from the wave_table array.

That wraps it up for today. I’m going to be doing some work to stabilize the circuit and hopefully generate a cleaner frequency sweep. As always I’ll provide any updates as I find them.

#define PIN_8 8
#define PIN_9 9
#define PIN_10 10
#define PIN_11 11

#define FREQ A0
#define SWITCH_PIN 2         //Push button to switch waveform

#define N 50            //Samples per cycle

volatile int switch_state = HIGH;

int table_index = 0;
uint8_t v_level;

uint16_t ARR_check = 35;

//Lookup tables for waveforms
uint8_t wave_table[N];

uint8_t curr_table = 1;

void timer1_init();

//Function definitions to generate wave tables
void get_sine_table();
void get_rsaw_table();
void get_lsaw_table();
void get_triangle_table();

void setup() {
  DDRB = B1111;      //Set Pins 8-11 as Inputs
  
  timer1_init();    //Initiate timer

  //initiate button interrupt to change waveform
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(SWITCH_PIN), switch_pin_ISR, FALLING);

  //Initialize wave tables
  get_sine_table();
}

void loop(){
  ARR_check = int(((analogRead(FREQ)*760.0)/1023)+40);
  if (abs(ARR_check - OCR1A) > 25){
    OCR1A = ARR_check;  
    }
  
  delay(50);
  }

void timer1_init(){
  noInterrupts();        
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 40;               //Compare Value
  TCCR1B |= (1 << WGM12);    // CTC mode
  TCCR1B |= (1<<CS11);       // 8 prescaler 
  TIMSK1 |= (1 << OCIE1A);   // enable timer compare interrupt

  interrupts();     
}

ISR(TIMER1_COMPA_vect){
  v_level = wave_table[table_index];
  PORTB = v_level;         
  table_index = (table_index+1)%N;
  
}

void switch_pin_ISR(){
  curr_table = (curr_table + 1)%4;
    switch(curr_table){
    case 0:
      get_sine_table();
      break;
    
    case 1:
      get_rsaw_table();
      break;
    
    case 2:
      get_lsaw_table();
      break;

    case 3:
      get_triangle_table();
      break;
    }
  }

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

void get_rsaw_table(){
  for (int i = 0; i < N; i++){
    wave_table[i] = int(i*(15.0/N));
  }
}

void get_lsaw_table(){
  for (int i = 0; i < N; i++){
    wave_table[i] = int(15 - i*(15.0/N));
  }
}

void get_triangle_table(){
  for (int i = 0; i < N; i++){
    if (i<(N/2)){
      wave_table[i] = int(i*(15.0/(N/2)));
      }
    else{
      wave_table[i] = int(15 - (i-N/2)*(15.0/(N/2)));
      }
  }
}

Arduino R2R DAC: Waveforms

Modified R2R DAC Schematic with Push Button Control

I’ve spent my last few posts tuning my R2R DAC and cleaning up the code. With the changes I’ve made I’ve got things running pretty smoothly. Now I’m ready to start building up the functionality of this circuit to see what I can get it to do.

All The Waveforms

First up I’d like to set up some waveform tables so that I can start switching between states as necessary. In order to do this I will have to set up a button interrupt to select waves, create the state tables and develop logic to select between them. This process will mirror fairly closely what was done in my PWM based oscillator so it may seem somewhat familiar. There are some important differences however due to the limited resolution of the DAC and the use of timer interrupts. I’ll get into these differences in more detail later in this post.

Declarations

#define SWITCH_PIN 2         //Push button to switch waveform
#define N 50                 //Samples per cycle

volatile int switch_state = HIGH;   

int table_index = 0;
int curr_table = 1;
int v_level;

//Lookup tables for waveforms
int sine_table[N];
int rsaw_table[N];
int lsaw_table[N];
int triangle_table[N];

//Function definitions to generate wave tables
void get_sine_table();
void get_rsaw_table();
void get_lsaw_table();
void get_triangle_table();

Lots going on there! Lets go through it step by step.

Definitions:

First in the definitions I define my switch pin and table size (N). These definitions are not strictly necessary but make the code a lot easier to read and allow quick modifications when necessary.

Switch State:

Next up we’ve got the switch_state variable. This is part of the implementation of the push button. Note the keyword volatile which lets the Arduino know this variable can change at any time (even without the program changing it). This will allow the variable to pick up changes to the push button during runtime.

Tracking Variables:

Next up we’ve got some tracking variables. Since I’m using timer interrupts rather than a loop with delays I need to track where in the wave I am. That is the function of table_index. Each time I write a voltage to the DAC table_index will be incremented to move to the next value in the table. Curr_table is similar except that it will track which table is being used currently. Finally v_level is used as a place holder which will receive values from the tables to be written to the PORTB register.

Tables:

Last but certainly not least we initialize the tables and functions to generate them. Note each table is of size N. This means if I change my N value the table size will automatically be updated.

Setup


void setup() {
  DDRB = B1111;      //Set Pins 8-11 as Inputs
  
  timer1_init();    //Initiate timer

  //initiate button interrupt to change waveform
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(SWITCH_PIN), switch_pin_ISR, FALLING);

  //Initialize wave tables
  get_sine_table();
  get_rsaw_table();
  get_lsaw_table();
  get_triangle_table();
}

The setup function has a couple new additions since my last post. First I needed to setup and attach the button interrupt which will let me change waves. This is done by first setting the pinMode of the previously defined SWITCH_PIN. Once set up we attach an interrupt to this pin using the attachInterrupt() Arduino function. If you remember from my PWM build this function takes three arguments. First we call digitalPinToInterrupt(SWITCH_PIN) to show what pin is being attached. Then we give the name of the function to be called and finally that the interrupt should trigger on the falling edge of a button press.

Additionally I’ll be calling my four get table functions (which I’ll be setting up soon). These functions populate the tables with values and get them ready to be indexed as the oscillator oscillates.

Switch Interrupt and Tables

void switch_pin_ISR(){
  curr_table = (curr_table + 1)%4;
  }

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

void get_rsaw_table(){
  for (int i = 0; i < N; i++){
    rsaw_table[i] = int(i*(15.0/N));
  }
}

void get_lsaw_table(){
  for (int i = 0; i < N; i++){
    lsaw_table[i] = int(15 - i*(15.0/N));
  }
}

void get_triangle_table(){
  for (int i = 0; i < N; i++){
    if (i<(N/2)){
      triangle_table[i] = int(i*(15.0/(N/2)));
      }
    else{
      triangle_table[i] = int(15 - (i-N/2)*(15.0/(N/2)));
      }
  }
}

There are five new user defined functions needed for this program. The switch_pin_ISR function is identical to the one I used in my PWM project. It increments the curr_table variable and uses the modulo opperator to ensure the value is always between 0 and 3.

The table definitions are slightly different. Where with my PWM project I had a range from 0-255 to define my voltage levels I now only have 0-15 (for the 16 states of the 4bit DAC). You may notice as well I’ve added some strange typecasting to these functions. The issue I ran into was that for any N greater than 15 the result of 15/N returned 0. To get around this I used 15.0/N which gave a float result. Once I had finished the calculations I type casted this float back to an int so I could write it to the PORT register.

Timer Interrupt

ISR(TIMER1_COMPA_vect){
  switch(curr_table){
    case 0: 
      v_level = sine_table[table_index];
      break;
    
    case 1:
      v_level = rsaw_table[table_index];
      break;
    
    case 2:
      v_level = lsaw_table[table_index];
      break;

    case 3:
      v_level = triangle_table[table_index];
      break;
    }
  PORTB = v_level;         
  table_index = (table_index + 1)%(N);
}

Finally I modified the timer interrupt function. By adding a switch statement I allow the v_level variable to be set based on the value of curr_table. Once this v_level is set I write it to PORTB to generate the output voltage. Last I increment the table_index variable to move to the next wave step. Again I use the modulo operator to reset table_index back to zero once it reaches N.

That about covers it for today, In my next post I’ll be modifying the code again to add the ability to adjust the frequency. As always I’ll finish by posting the full code. Till next time!

#define PIN_8 8
#define PIN_9 9
#define PIN_10 10
#define PIN_11 11

#define SWITCH_PIN 2         //Push button to switch waveform

#define N 50                 //Samples per cycle

volatile int switch_state = HIGH;

int table_index = 0;
int v_level;

//Lookup tables for waveforms
int sine_table[N];
int rsaw_table[N];
int lsaw_table[N];
int triangle_table[N];

int curr_table = 1;

void timer1_init();

//Function definitions to generate wave tables
void get_sine_table();
void get_rsaw_table();
void get_lsaw_table();
void get_triangle_table();


void setup() {
  DDRB = B1111;      //Set Pins 8-11 as Inputs
  
  timer1_init();    //Initiate timer

  //initiate button interrupt to change waveform
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(SWITCH_PIN), switch_pin_ISR, FALLING);

  //Initialize wave tables
  get_sine_table();
  get_rsaw_table();
  get_lsaw_table();
  get_triangle_table();
}

void loop(){
  }

void timer1_init(){
  noInterrupts();        
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 125;                //Compare Value
  TCCR1B |= (1 << WGM12);      // CTC mode
  TCCR1B |= (1<<CS11);    // 8 prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt

  interrupts();     
}

ISR(TIMER1_COMPA_vect){
  switch(curr_table){
    case 0:
      v_level = sine_table[table_index];
      break;
    
    case 1:
      v_level = rsaw_table[table_index];
      break;
    
    case 2:
      v_level = lsaw_table[table_index];
      break;

    case 3:
      v_level = triangle_table[table_index];
      break;
    }
  PORTB = v_level;         
  table_index = (table_index + 1)%(N);
}

void switch_pin_ISR(){
  curr_table = (curr_table + 1)%4;
  }

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

void get_rsaw_table(){
  for (int i = 0; i < N; i++){
    rsaw_table[i] = int(i*(15.0/N));
  }
}

void get_lsaw_table(){
  for (int i = 0; i < N; i++){
    lsaw_table[i] = int(15 - i*(15.0/N));
  }
}

void get_triangle_table(){
  for (int i = 0; i < N; i++){
    if (i<(N/2)){
      triangle_table[i] = int(i*(15.0/(N/2)));
      }
    else{
      triangle_table[i] = int(15 - (i-N/2)*(15.0/(N/2)));
      }
  }
}

Arduino R2R DAC: Circuit Tuning

I’ve spent my last two posts making some important improvements to the code for my Digital to Analog converter. I’ve converter the code to write directly to the PORT register to speed up my updates and implemented timer interrupts to improve my timing. These are both vast improvements but there’s still one more area that desperately needs my attention before I start building out the features of my new oscillator. That area is the circuit.

Today I’ll be working to hammer out two big issues I’ve been having with the circuit. The first I identified back when I first introduced this project, that is the lack of consistency in the values of the various voltage steps. Once that’s squared away I’d like to have a look at buffering the output so I can start driving a speaker (or other analog load) with the DAC.

The Resistance

Original DAC Output

Ideally when stepping through the voltages of my DAC (as I do with my saw wave function) I should be seeing a smooth staircase on my oscilloscope. Unfortunately looking at the output above we see this is not exactly the case. The issue here has to do with the tolerance of the resistors. Had this original design been more than a proof of concept I would have ordered low tolerance resistors special for the task. Instead in my rush I used what was available in my parts drawer. Unfortunately this meant the values of my resistors varied quite widely from the 100 and 200 ohms I had planned.

My approach here is pretty low-tech. I drew a quick schematic of the circuit and using my multimeter marked down the actual resistance of each resistor. Diagram in hand I started measuring the other 100 ohm resistors I had on hand. When I found one closer to the target resistance than what was on my diagram I switched them out and updated the diagram. Since I had limited resistors available I wasn’t able to get the values exactly but it still made a substantial difference.

New DAC output

Looking at the new output above the steps still aren’t perfect but they are substantially closer to the idealized staircase we are looking for. Each of the 16 steps is now clearly visible and differentiated.

Another approach you can take to bypass these issues is trimmer pots. If you replace each resistor with a trimmer potentiometer you can dial in the exact resistance you want up to the resolution of the trimmer.

Buffering

If you know anything about me it’s that I like to make noise. It’s sort of my jam. Sadly though if you’ve tried to connect this circuit as it stands to a speaker you may have been sorely disappointed to find nothing coming out. This simply won’t do!

The issue here is with the current in my circuit. Digital pins (both on the Arduino and otherwise) put out a tiny amount of current. The absolute maximum available from any digital pin on the Arduino is 40.0 mA.

If we consider the case of a 1W 8ohm speaker (similar to a cheap computer speaker) being driven at 5V we’ll find that it needs about 200mA to run (1W/5V = 0.2A = 200mA). Clearly we need to step things up a notch.

4-bit DAC Circuit

My solution here was to add an LM358 op-amp to buffer the signal. Since the output of the Arduino is already 5V I didn’t need any gain so I set this up as a “Unity Gain Amplifier”. One issue I did encounter is that the output range of the LM358 does not cover the full 5V range I’m supplying it with. I found the signal became saturated at about 4.1-4.2V. To combat this I added the potentiometer to work as a voltage divider and attenuate the signal slightly before it enters the amplifier.

Arduino R2R DAC: Writing to Ports

A few days ago I wrote a post outlining the R2R Digital to Analog Converter. This project is in it’s early stages, but there were a few refinements I wanted to make prior to expanding it’s functionality. The first thing I want to modify is the way that the code handles writing values to the digital pins.

The digitalWrite function on Arduino is only capable of changing the state of one pin at a time. This means we are currently writing to PIN_8, then PIN_9, then 10 and so on. This becomes an issue at high speeds since I am setting my voltage levels based on the sum of these pin’s outputs. The time in between the first and final pin switching will not match the desired voltage and can cause the wave to break down.

The solution to this problem involves pulling back a layer of abstraction from the Arduino. By bypassing the digitalWrite function and writing directly to the ports of the Arduino we can manipulate these pins at the same time. This will provide us faster and more efficient code.

What’s a Port

In the architecture of ATMEGA chips (The chips that run Arduinos) as well as most microprocessors the pins are organized into a set of Ports. These ports are a way of organizing the i/o pins and simplify the chips internal logic.

In the case of the Arduino we have 3 ports. They are as follows:

  • Port B: Digital Pins 8 – 13
  • Port C: Analog Input Pins
  • Port D: Digital Pins 0-7

This project uses pins 8-11 so I am interested in Port B. But how do I access this port?

Port Registers

Each port functions with the aid of three registers. A register is a special section of volatile (non-permanent) memory. Registers hold parameters or values for the system. Each of these three registers has a different task relating to the port. Further each of these registers is made up of 8 bits with each bit corresponding to a pin in the port.

DDR – Port Data Direction Register – As it’s name suggests the DDR port controls the direction of data flow for each pin in the port. If a bit in this register is set to 1 the corresponding pin is an output. Meanwhile the pin is an input when the corresponding bit is set to zero.

PORT – Port Data Register – This port governs the data being written to the pins in the port. When working with output pins we can write zeros or ones to this register to output high or low on the corresponding pins.

PIN – Port Input Register – This register is less relevant to this project but is still good to be aware of. The input register is where you can read the values being received by any of the input pins.

Each of these registers is set up as a variable within the Arduino IDE making them very easy to access. To access one of these registers we just need to append the port name to the register name. For this project I will be using DDRB and PORTB.

Setting Pin States

void setup() {
  for (int k = 0; k < 4; k++){
    pinMode(DAC_bus[k], OUTPUT);
  }

In my original code I used the loop above to set pins 8-11 to Output. This is not very efficient. Instead we can use the register DDRB to set these pins. One important note is that the lowest pin in the port is tied to the last bit in the register. That means to set pins 8-11 to Output I need to write B00001111 into the register. The “B” here tells the compiler this is a binary number. Since the most significant bits here are all zeros we can even shorten this slightly to give us B1111. This gives us the following setup function.

void setup() {
  DDRB = B1111;      //Set Pins 8-11 as Inputs
}

This shortens our code and minimizes the amount of memory required on the Arduino. Additionally this new setup function will run much faster. As a housekeeping item, I also added a comment to clarify what is happening since it is no longer clear in the code.

Writing To A Port

void test_run2(){
  for (int i = 0; i < 15; i++){
    int value = i;
    for (int k = 0; k < 4; k++){
      digitalWrite(DAC_bus[k], value%2);
      value = value/2;
    }
    delay(1);
  }
}

Above I’ve included my original function to draw a sawtooth pattern. In addition to being inefficient this function is just plain ugly. Using the PORTB register we should be able to get rid of the nested loop and write the value directly to the register. Conveniently the variable i is stored in the Arduino in binary. This means we don’t have to do any kind of conversion, we can write the value directly into the port.

void draw_saw(){
  for (int v_level = 0; v_level < 15; v_level++){
    PORTB = v_level;         
    delayMicroseconds(500);
  }
}

That looks a lot cleaner! By making this change I’ve now assured all of the digital pins update simultaneously. This will allow the oscillator to run much more accurately at high speeds. Additionally I’ve reduced the size of the program freeing more memory in the Arduino.

Notice I also made a few other changes to the function. I have changed the function and variable names to be more descriptive. This makes it much clearer what is happening. Additionally I have swapped the Arduino delay function for delayMicroseconds. This allows me to use much smaller delay times and achieve significantly higher frequencies.

With these changes I can now create saw waves throughout the audio frequency range (20 – 20,000Hz) and well beyond it. I’ve been able to shrink the code while improving performance. Further I’ve honed what will be a valuable skill moving forward. That’s all for today but I hope you are all staying healthy. I’ll see you again soon!

Building A Better DAC

I’ve had a lot of fun playing with my PWM Oscillator but at the end of the day it’s a bit limited. 10Hz is barely going to get us very far and the waveforms aren’t exactly smooth. Surely we can do better!

Today I’d like to start exploring something a bit more interesting. An R2R Digital to Analog Converter. Here I will use multiple digital pins and a resistor ladder to create a much more versatile analog oscillator. For now I am going start with a very basic version of the converter to hammer out the logic. Once this is done I can start building the circuit and code up to see what it can do.

The Circuit

4-Bit R2R DAC

Looking at the circuit diagram gives some indication of where this converter gets it’s name. The circuit is made up of a ladder of resistors composed of 2R from each digital pin and R connecting them together (Where R is any resistor value). For mine I’ve used 200 and 100 ohms respectively.

These resistors function as a chain of Voltage Dividers. The math here gets a bit complicated but how it all pans out is this; The signal from the pin closest to the output (Pin 11 above) will give a voltage of approximately half your range at the output, each subsequent pin will reach the output with half of the voltage of the previous pin. This means if you had an output range of 5V, pin 11 would output 2.5V, pin 10 would output 1.25V, 9 would output 0.625V and 8 would output 0.3125V. When multiple pins are set high at once these voltages are added together. This gives us a wide range of voltages to play with.

It’s important to note that these are idealized values. Dependent on the tolerance of the resistors you use there will be variances between the resistor values. These variances will skew these numbers and distort your output. For the time being I will let this slide and use what I have on hand as a proof of concept. As I refine this circuit however, I will have to make allowances for this. This can be done by either testing a large number of resistors to find ones which most closely match your target resistance or adding trimmer pots to fine tune the resistances in the ladder.

Test Code

I feel like the functionality of this converter becomes clear when you see it in action. To that end I want to set up some code to test it out.

#define PIN_8 8
#define PIN_9 9
#define PIN_10 10
#define PIN_11 11

int DAC_bus[] = {PIN_8, PIN_9, PIN_10, PIN_11};

void test_run();

First up are some definitions and declarations. The first set of definitions set up my digital pins. Once they are defined I place the pins in an array to keep things organized. Finally I define a test_run function which I will use to run a simple test on the converter.

void setup() {
  for (int k = 0; k < 4; k++){
    pinMode(DAC_bus[k], OUTPUT);
  }

In the setup function I iterate through the DAC_bus and set each pin to output.

void loop() {
  test_run();
}

void test_run(){
  for (int i = 0; i < 4; i++){
    digitalWrite(DAC_bus[i], HIGH);
    delay(5);
    }
  for (int i = 0; i < 4; i++){
    digitalWrite(DAC_bus[4-i], LOW);
    delay(5);
    }
  }

Finally we create the test_run function and call it in the main loop. The test run function sets each of the pins to high 5ms apart then sets each pin back to low.

Test 1 Output

Looking at the output of this simple test you can distinctly see the pins being turned on and off and the corresponding changes in voltage.

Saw Tooth

These voltage levels only begin to tell the story. By turning different combinations of pins on and off we can actually achieve 16 distinct voltage levels (including 0V). To show this I wrote a second test function which draws a saw tooth wave.

void test_run2(){
  for (int i = 0; i < 15; i++){
    int value = i;
    for (int k = 0; k < 4; k++){
      digitalWrite(DAC_bus[k], value%2);
      value = value/2;
    }
    delay(1);
  }
}

This one may look a bit strange if you haven’t worked extensively with binary numbers. This is something I’ll explore further when we start drawing wave forms. Essentially what’s happening though is we are counting from zero to 15, converting each number to binary and writing those binary numbers to the digital pins. The output I received running this second test were as follows:

Test 2 Output

Here we can see the wide variety of voltages we can obtain with only 4 digital channels. With a little filtering we could turn this into a fairly workable saw wave.

This output also illustrates the issue with high tolerance resistors. In an idealized version of this converter this saw wave would look like a perfect staircase. Each step up would be of equal voltage. In our output we can see this is not the case. This is likely due to variations in the resistor values I used build the converter. In future posts I will look at refining my circuit to improve this performance.

The Code

I’m going to finish things off today by providing the full code to get this up and running. There are a lot of places I can go from here but I hope this introduction gives a brief illustration of how R2R DACs work and provides some idea of what we can accomplish with them.

#define PIN_8 8
#define PIN_9 9
#define PIN_10 10
#define PIN_11 11

int DAC_bus[] = {PIN_8, PIN_9, PIN_10, PIN_11};

void test_run();

void test_run2();

void setup() {
  for (int k = 0; k < 4; k++){
    pinMode(DAC_bus[k], OUTPUT);
  }

}

void loop() {
  test_run2();  
}


void test_run(){
  for (int i = 0; i < 4; i++){
    digitalWrite(DAC_bus[i], HIGH);
    delay(5);
    }
  for (int i = 0; i < 4; i++){
    digitalWrite(DAC_bus[4-i], LOW);
    delay(5);
    }
  }

//saw tooth test function
void test_run2(){
  for (int i = 0; i < 15; i++){
    int value = i;
    for (int k = 0; k < 4; k++){
      digitalWrite(DAC_bus[k], value%2);
      value = value/2;
    }
    delay(1);
  }
}

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