Floating Point Representation

It’s exam season again which means my build time is a bit limited. I thought I’d take the opportunity while I’m studying to write some more content content for the basics area of this site. To start out I thought I’d build on my post regarding Binary Representation. With an understanding of binary we can begin to explore one of the more confusing data types, Floating point. I know this topic is straying a little bit from audio synthesis but if you plan on working with microprocessors it’s a good thing to understand.

Binary Fractional Representation

The first piece of this puzzle is understanding how fractional numbers are represented in binary. That is to say non-integer numbers. You may remember that in binary each bits value can be determined by raising 2 to the power of that bit. So the first bit is 2^0 = 1, the second is 2^1 = 2 and so on. For numbers on the other side of the decimal point we continue the same pattern but move into negative exponents. The table below should offer some clarity.

This means if we wanted to express a decimal number like 10.625. We could do so in binary by writing 1010.101.

Conversions of Fractional Numbers

You may remember that we can convert an integer number in decimal to binary by repeatedly halving it and noting the remainders. There is a similar algorithm for converting fractional numbers. This time however we repeatedly double the number and note whether the result is greater than one. Taking the example 10.625 from earlier. For the whole number part (10).

Reading from the bottom up, this gives us the binary representation of the whole number portion (1010). Next for the fractional portion (0.625) we can repeatedly double.

This time we read from the top down to get the fractional part in binary .101. Putting it together we get the answer from above 1010.101.

Scientific Notation in Base 2

If you’ve ever had to do math involving very large or very small numbers you’ve probably encountered scientific notation. It’s a fairly straight forward concept. If you have a number with a bunch of zeros like 30,000,000. You know that dividing by 10 would remove one of the zeroes. If you divided by 10 7 times you would be left with just 3. That means 3 * 10 * 10 * 10 * 10 * 10 * 10 * 10 would be equivalent to 30,000,000. For brevity we just write 3 * 10^7.

This process also works for zeros on the other side of the decimal point. We simply use a negative exponent in these cases. for instance 0.000015 can be represented as 1.5 * 10^-5. The key here is that we multiply or divide by ten in a base ten system. So guess what we use in base 2.

You may not have encountered scientific notation in binary but it works in much the same way. You can slide the decimal point back and forth by multiplying or dividing by 2. for instance our example from earlier (1010.101) could be represented by writing 1.010101 * 2^3. This is at the core of floating point representation.

Floating Point

So that was a lot of preamble… but what actually is floating point. At a high level floating point is a way to represent fractional numbers in a digital way. Through some clever design floating point allows us to represent both incredibly large and incredibly small numbers with the same basic architecture.

A floating point number is made up of three parts. The sign, the exponent and the mantissa. The first, the sign bit, is the easiest to understand. If the sign bit is a 1, the number is negative, if it’s a 0, the number is positive.

The Mantissa

The mantissa holds the actual digits of the number. If we go back to our example of 10.625 (1010.101). We would slide the decimal to the first digit giving us 1.010101. Take note of how many decimal places we move the decimal point, we’ll need that later. Also notice that regardless of the number we do this with we will always have a 1 on the left of the decimal point. Since we can always assume this one will be there we can actually leave it out of our final representation. This leaves us with 010101 which is what you would find in the mantissa segment of a floating point number.

The Exponent

The final piece is the exponent. This represents how many digits the decimal point must be moved from the actual number to reach the mantissa. In our example we’ve moved the decimal point 3 places to the left, but there’s a little more to the story. In order for the exponent to represent both positive and negative numbers a bias is added. The bias is typically half the range of numbers that can be represented in the exponent. That means if we have 8-bits set aside for the exponent (0-255) a bias of 127 would be used. That way even if we have a negative exponent it can still be represented using an unsigned number. Adding 127 to our exponent (3) we end up with a value of 130. We can represent this in binary with 1000 0010.

Putting It Together

Now that we’ve determined the values for our sign bit, exponent and mantissa we can put them together into a floating point number. We can do so in one of two ways. Floating point numbers are divided into either singles or doubles which refers to the level of precision each has available. A single is made of 32 bits (4-bytes) while a double takes 64 bits (8-bytes). I will go over each of them with our current example.

Single Precision

In single precision we use 32 bits. Of these 32-bits, 1 is used for the sign, 8 for the exponent and 23 for the mantissa. Using the values we determined previously that means the single point representation of 10.625 would be:

S | Exponent | Mantissa

0 | 1000 0010 | 0101 0100 0000 0000 0000 000

Notice I have added zeros to the tail end of the mantissa to fill the available bits. This won’t affect the number. It’s also pretty clear looking at this example that even with the lower precision of a single we can represent an incredible spectrum of numbers. We can use exponents from 127 to -127. Just to give an idea of the scale there that would approximately translate to a number with 38 zeros before or after the decimal point in base 10.

Double Precision

In case single doesn’t provide the range or precision necessary another larger option is available. With double precision we use 64 bits to represent a number. This is divided into, 1 sign bit, 11 bits for the exponent and 52 bits for the mantissa.

One important note is that since we now have more than 8 bits for the exponent we need to adjust our bias. 11 bits provides a range from 0-2047. Half of that range gives us a bias of 1023. This makes the exponent for our example 1026 (1023+3). In binary 1026 is 1000 0000 010. By putting this together with the other results calculated earlier we get a double precision floating point representation as follows:

S | Exponent | Mantissa

0 | 1000 0000 010 | 0101 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

With 11 bits available for the exponent the available number range has grown exponentially (pun intended). We can now use exponents anywhere from 1023 to -1023. These numbers actually go beyond what my poor Casio calculator is capable of outputting. In base 10 they equate to approximately 307 zeroes on either side of the decimal place!

In Closing

Floating point representation can seem really intimidating at first. That being said with some practice they start to make sense and you’ll begin to develop an intuition while working with them. As you go further working with micro-controllers you will start encountering situations where you have to send and receive data, read or write from registers or make bare-metal conversions between data-types. In any of these situations a strong grasp on floating point (and other common data types) will serve you well.

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: Timer Interrupts

Up to this point I’ve been handling all of the timing in my Arduino projects using the delay (or delayMicroseconds) function. This has worked pretty well but isn’t without it’s issues. Primarily there are two problems that need to be addressed. First, when using the delay function my timing doesn’t take into account the time it takes to process the rest of the code. And second when using delay my Arduino isn’t able to do anything else. If I wanted to, for instance, run two oscillators simultaneously at different speeds everything would break down.

The solution to these problems is to leverage timer interrupts. This essentially sets up a clock that calls a function at regular intervals. This means my timing will have much higher accuracy and I can have the Arduino do whatever I want in between updates.

Arduino Timers

The Arduino Uno has three timers appropriately named Timer0, Timer1 and Timer2. each of these runs off the 16MHz clock oscillator within the Arduino.

Each timer has a couple important parameters we can control. The first is something known as a prescaler. The prescaler allows us to slow down the clock speed from 16MHz to a more manageable speed. The prescaler values available are 1 (no change), 8 (sets timer to 16MHZ/8 = 2MHz), 64 (slows the timer to 250kHz), 256 (62.5kHz) and 1024 (15.625kHz). These prescaler are set by writing ones different combinations of three registers (CSx0, CSx1 and CSx2) as seen in the following grid.

Clock Selector Bits

Note: the x in these register names should be replaced with the timer number.

The second parameter that will impact our timing is the value in the output compare register (OCR). The timer will count up based on the clock frequency and prescaler. Each time it reaches the value in the OCR register it will reset and send an interrupt. On the Arduino Uno Timers 0 and 2 have an 8 bit counter meaning the OCR can be any value between 1 and 256. Timer 1 is a 16 bit timer meaning it can count as high as 65536.

Math Time

So how do we put this all together? Lets say we want our saw wave to run at 1kHz. The first thing we have to correct for is the steps in the saw itself. Right now we’ve got the saw wave set up to cycle through 16 voltage levels, so right away our 1kHz signal will require the timer to fire at 16Hz. No problem!

So to get the 16MHz clock input down to 16Hz we first need to apply a prescaler. Lets divide the 16MHz by a prescaler of 8 (16MHz/8 = 2MHz). Now to get that 2MHz down to 16kHz we need to set our OCR. if we divide the timer speed by the desired speed we’ll find the value we need (2MHz/16kHz = 125).

Lets Write It

Enough theory, let’s write some code! First things first, I define a function to initiate the timer and call it in the setup function.

void timer1_init();

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

  timer1_init();    //Initiate timer
}

For the timer function we want to start by disabling all interrupts and setting the following three registers to zero. This initiates the timer.

  noInterrupts();        

  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

Next we set the OCR value.

  OCR1A = 125;                //Compare Value

We set the timer into CTC mode using the following command. CTC mode allows us to trigger our interrupts each time the counter hits the OCR value. This is immediately followed by a command setting the prescaler and a final command enabling the interrupt itself. Last we reenable the interrupts.

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

  interrupts();     

So that sets up the interrupt but how does it get called? Essentially what happens is each time the interrupt triggers it calls a specific function known as ISR. We can set up the ISR function as follows.

ISR(TIMER1_COMPA_vect){
  PORTB = v_level;         
  v_level = (v_level + 1) % 16;
}

And there we have it. We’ve done away with the evil delay function and replaced it with a timer interrupt. I’ll finish off with the complete code and I’ll see you all soon.

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

#define N 15

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

int v_level = 0;

void timer1_init();

void draw_saw();

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

  timer1_init();    //Initiate timer
}

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){
  PORTB = v_level;         
  v_level = (v_level + 1) % 16;
}

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!

Binary Representation

Since I’ve been working so much lately in the digital space I thought it would be pertinent to do a quick review today. I want to spend some time on one of the most fundamental ideas in digital logic, Binary Representation. I know it’s not the most exciting topic but understanding binary numbers intuitively is critical to understanding the inner workings of digital devices.

Why Do We Care?

You’ve undoubtedly run into binary numbers in pop culture. They seem to appear any time a screenwriter wants to convey that a character “speaks computer.” But what do these zeros and ones mean? And more importantly, why do we care?

At the core the answer is that a computer has no idea what a 7 is. Computers are made up of millions (or billions) of transistors. These transistors only have two states, High and Low, or if you prefer zero and one. This means every thing you store in memory and any instructions you send into your processor need to be written as a series of these zeroes and ones. That goes for all your video files, pictures, video games and even your operating system itself. As far as your computer is concerned it’s all binary.

We can make it a long way working in high level languages like C or Python but inevitably there will come a time when you have to write directly to a register or transmit raw data. This is when binary will serve you. These situations are doubly likely to occur if you are working with microprocessors as both memory and power are limited. Further in time sensitive situations (like audio processing) writing straight to a register is typically faster and more efficient than using high level code.

How Does It Work?

The numbers we are familiar with are known as base 10 (or decimal numbers). This means each digit can be one of 10 possible values (0-9). If I add one to 9 the ones digit resets to zero and the tens digit is incremented to 1 (Giving you 10). When you were first learning to add numbers together you may have been taught to write the two numbers one atop the other and add each digit individually carrying to the next digit when your answer was more than 9. This gets at the core of the base 10 system.

The binary system is no great magic trick. We simply change the base to 2. This means each digit can only hold one of two values (0 or 1). As you count upwards you start with 0. Adding one gives you 1. When you try to add another one you have to carry over to the next digit (just like adding one to 9) giving you 10. To help clarify this process I’ve written out the binary representations of the numbers 0 to 15.

Binary Representation of 0-15

It’s good to note that there are other bases commonly used as well. In computation hexadecimal (base 16) is frequently used to make very large numbers manageable. In Hexadecimal we use the letters A-F to represent 10-15.

How Many Bits?

Notice in the previous table I used 4 digits and was able to represent numbers from 0 to 15 before I ran out of space. These digits are usually referred to as bits and they govern how large a number you can represent. This shouldn’t be too surprising as this is exactly how binary works (2 digits can represent numbers up to 99, 4 digits can represent up to 9999).

So how do we know how many bits we need? In decimal each new digit has a ten times higher value than the previous digit (1. In binary we can use a similar rule except since it’s base 2 each new digit is double the previous one. Here I have shown the value of a one in each of the first 8 digits to illustrate this rule.

Values of Binary Digits

Additionally in decimal you can find the number maximum value you can obtain with a number of digits using the following formula:

Where n is the number of digits and N is the highest value possible. We can do the same in binary by swapping the 10 for a 2:

Using this formula we can calculate the range of numbers available given any number of bits:

Maximum Value Based on Quantity Of Bits

Conversions

There are various methods to convert between decimal and binary. The route I have always found easiest though involves repeatedly dividing a number by two. Each time you divide by two you check if there is a remainder and note that remainder (it will always be 1 if it exists). If there is no remainder (ie. the number is even) note a zero. When you finish you can reverse the order of the numbers you have noted to see the binary representation.

Lets try applying this algorithm for 42:

Binary Conversion of 42

We can see that the binary conversion of 42 is 101010 by reading the remainders from the bottom up. Additionally you can verify your answer by multiplying each digit by the values determined earlier in this article (1*32 + 1*8 + 1*4). Doing this you should get back your original number.

Closing

Before I finish up for the day I have one final question. How high can you count on your fingers? If you answered 10 you’re not thinking with portals yet. We have 10 fingers, each of which can be either extended or folded. If we use binary counting we can reach 2^10 – 1. That’s 1023! You’ll never need a calculator again!

That’s all for me today. I hope you’ve found this refresher helpful, I’ll be back soon with further updates to my Arduino R2R DAC project.

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);
  }
}

Arduino Function Generator

Alright… We’ve put the work in, lets bring this one home. We’ve got a fairly reliable (if a little bit low res) sine wave generator, but we can make our Arduino do a lot more. Today I’d like to modify my Arduino based sine wave generator to output a variety of predefined waveforms.

To accomplish this I need two things. First I need to program a button to let me select a waveform. Then I’ll need to create tables for these additional waveforms and implement logic to pull values from the correct table.

The Circuit

Arduino Function Generator Circuit

The circuit for this one is essentially the same as the circuit from my last post. The only change I’ve made is the addition of a push button connected between pin 2 on the Arduino and ground. In the code I will set this pin to “pull up” meaning the pin is kept high until the button is pressed. When the button is pressed the pin is connected to ground which will force it low.

Polling Vs Interrupts

When setting up the button press I have two choices. We can either use polling to check for a button press or set up an interrupt. In polling we would add a check in the main loop so that each time the Arduino cycles through the loop it will check the status of the button. This option is very easy to implement but is not terribly efficient.

The analogy I often hear is to imagine you are waiting for a phone call. Polling would be the equivalent of disabling your ringer and checking the phone every 30 seconds to see if the call is coming in. There are a few obvious issues with this approach. What if the call comes in in between the times when you check? you would miss it. Further what if you have a homework assignment your trying to get done at the same time? You wouldn’t make much progress if you stopped every 30 seconds to check your phone.

A better option would be to turn your ringer on and set your phone to the side while you work on other things. This is what happens when we set an interrupt. We tell the Arduino to let us know when an event happens. When something does happen the Arduino takes note of what it’s working on, stops, and proceeds to take care of the instructions we left it for the event.

Setting up an Interrupt

There are a few steps to set up an interrupt for the button press. First I define the pin and create a variable for the pins state. Notice I use the keyword volatile to communicate to the Arduino that this value may change at any time.

#define SWITCH_PIN 2         //Push button to switch waveform
volatile int switch_state = HIGH;

Next comes the setup function for the Arduino. Here I need to set the pinMode. I set this pin as an INPUT_PULLUP so that the pin is held high until the button is pressed.

Also in the setup we will call the attachInterrupt function. This tells the Arduino how to handle this pin. This function takes three arguments. The first of these sets the pin we are attaching the interrupt to. The second gives the name of the function to be called when the interrupt happens. The third identifies the event at the pin which will trigger the interrupt, I have used FALLING here meaning the Arduino will trigger the event when the pin goes from high to low.

void setup() {
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(SWITCH_PIN), switch_pin_ISR, FALLING);
  
  get_sine_table();
}

Finally I need to implement the function mentioned in the setup (switch_pin_ISR). My goal here is to use a new variable (table) to track what table is being used. When the button is pressed I want this variable to increment by one which will tell the main loop to start pulling from the next waveform’s table (more on this later). Additionally I don’t want this variable to ever go beyond the number of tables I have so I will use the modulo operator. This ensures that if the variable reaches beyond the number of tables I have defined it will reset to zero.

int table = 0;
/* Identifies current waveform table
 * 0 - Sine
 * 1 - Right Saw
 * 2 - Left Saw
 * 3 - Triangle
 */


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

Tables Tables Tables

Now that I’ve built up the logic to handle the button control I’d better set up some tables to create the different waveforms. At the time of writing I have 4 waveforms I’m using. These are the sine we originally created, right and left biased saw waves and a triangle. I generate each of these during the set-up function.

int sine_table[100];
int rsaw_table[100];
int lsaw_table[100];
int triangle_table[100];


void setup() {
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(SWITCH_PIN), switch_pin_ISR, FALLING);
  
  get_sine_table();
  get_rsaw_table();
  get_lsaw_table();
  get_triangle_table();
}

For each wave I have created a function to create the table. The logic behind these waveforms is fairly straightforward. For the right sine wave I created loop from 0 to N setting each value to i*(255/N). The left sine is identical except that I subtract the result of that equation from 255 to invert the result. The triangle is slightly more interesting, I used an if statement to separate the values before N/2 from those after. Those before I set equal to i*(255/(N/2)). Those after I set equal to 255- i*(255/(N/2)).

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

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

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

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

If the logic of these functions seems unclear don’t worry. These kind of algorithms are not necessarily intuitive at first. My best advice is to work through them, take an N value of 5 and calculate the result for each i less than N. Remember that a value of 0 equates to 0V output and 255 equates to 5V output. Your results should mimic the desired waveform.

The Switch

The final piece of this puzzle comes in the main loop. We have tables for each waveform, a variable to track which table we’re using and an interrupt to modify that variable. Now we just have to read that variable and pull our PWM values from the correct table. Here I’ve used a switch statement. If you’ve programmed mostly in Python or Java you may not have encountered switches before. A switch is essentially a clean way to write a very ugly if else statement. We define a variable to evaluate and then give a list of “cases” for different values of that variable. Each case describes the actions to take if the variable equals that value. If you’d like to learn more about switch statements Geeks for Geeks have a great article about them.

void loop() {
  for (int i = 0; i < N; i++){
    sample_time = analogRead(ANALOG_0);
    sample_time = (sample_time/16)+2;

    switch (table) {
      case 0:
        analogWrite(PWM_PIN, sine_table[i]);
        break;

      case 1:
        analogWrite(PWM_PIN, rsaw_table[i]);
        break;

      case 2:
        analogWrite(PWM_PIN, lsaw_table[i]);
        break;

      case 3:
        analogWrite(PWM_PIN, triangle_table[i]);
        break;


      
      }
      
    delay(sample_time);
  }
}

All The Code

With that we’ve done it! We’ve turned our Sine Wave Generator into a proper function generator. There’s no reason to stop here though, I’ve only scratched the surface of the available waveforms. Additionally there are better filters and better algorithms begging to be explored. Now that I have this up and running I’d also like to spend some time in a future post exploring how to interface it with other oscillators to use it as an LFO. For today I’ll finish up by providing my code in full, Take care. I’ll see you all soon.


/*
 * www.SamVsSound.com
 * Arduino PWM Function Generator
 * 11/14/2020
 */

#define PWM_PIN 5            //PWM output at pin5
#define ANALOG_0 A0          //Input for sample_time
#define SWITCH_PIN 2         //Push button to switch waveform

#define PWM_FREQ  980        //Hz
#define N 100

int sine_table[100];
int rsaw_table[100];
int lsaw_table[100];
int triangle_table[100];

volatile int switch_state = HIGH;

int table = 0;
/*
 * 0 - Sine
 * 1 - Right Saw
 * 2 - Left Saw
 * 3 - Triangle
 */


int sample_time;

void get_sine_table();
void get_rsaw_table();
void get_lsaw_table();
void get_triangle_table();

void setup() {
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(SWITCH_PIN), switch_pin_ISR, FALLING);
  
  get_sine_table();
  get_rsaw_table();
  get_lsaw_table();
  get_triangle_table();
}

void loop() {
  for (int i = 0; i < N; i++){
    sample_time = analogRead(ANALOG_0);
    sample_time = (sample_time/16)+2;

    switch (table) {
      case 0:
        analogWrite(PWM_PIN, sine_table[i]);
        break;

      case 1:
        analogWrite(PWM_PIN, rsaw_table[i]);
        break;

      case 2:
        analogWrite(PWM_PIN, lsaw_table[i]);
        break;

      case 3:
        analogWrite(PWM_PIN, triangle_table[i]);
        break;


      
      }
      
    delay(sample_time);
  }
}

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

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

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

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

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

Sine Waves on Arduino: Variation

Today Ill be building on my Arduino Sine Wave Generator project. My goal is simple, to add the ability to dynamically change the frequency of the wave being generated. I should be able to read the value from a potentiometer similar to what was used in my original Arduino PWM post and write that value into the delay variable to modify the timing.

Wiring it Up

Arduino Function Generator Wiring

The wiring for this one is pretty straight forward and nothing we haven’t seen before. On the left is a potentiometer connected between 5V and ground. The wiper of this potentiometer is connected to analog pin A0 on the Arduino. This pin will provide an input value between 0 and 1023 depending on the position of the pot.

On the right we have the digital pin 5 connected through the same low pass filter I’ve used in previous projects. This will take our PWM output and smooth it into the final waveform.

The Code

The code only needs some slight modification from my last post. We need to first define the analog pin and read its value using analogRead. This is where some choices need to be made though. I mentioned that the analogRead for the potentiometer gives us a value from 0 to 1023. If we were to write this directly to the sample_time variable we would be producing some extremely long sine waves. Since our sine table has 50 entries this would mean at the highest setting each wave would take 51.15 seconds (1023*50/1000) to complete. That might be a bit much. So we need to decide how large a range we want to use and process the input accordingly.

I chose to divide the input by 16, this gives me a frequency range from about 10Hz to 0.313Hz. In the code you’ll notice I added a hard limit at 10Hz by adding 2 to the input value. This ensures the delay time never drops below a range that the Arduino and our filter can handle.


#define PWM_PIN 5            //PWM output at pin5
#define ANALOG_0 A0          //Input for sample_time
#define PWM_FREQ  980        //Hz
#define N 50

int sine_table[50];
int sample_time;
void get_sine_table();

void setup() {
  get_sine_table();
}

void loop() {
  for (int i = 0; i < N; i++){
    sample_time = analogRead(ANALOG_0);
    sample_time = (sample_time/16)+2;
    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);
  }
}

And there you have it! We can now adjust our frequency on the fly. Sorry it’s a bit of a shorter post today life’s been pretty full of late. Still I think this marks a pretty substantial step forward in this project. In my next post I’ll be looking at implementing the ability to produce more waveforms beyond just the sine.