DIYstompboxes.com

DIY Stompboxes => Digital & DSP => Topic started by: g_u_e_s_t on January 15, 2013, 12:18:22 AM

Title: pwm audio on atmega/arduino
Post by: g_u_e_s_t on January 15, 2013, 12:18:22 AM
i just finished a tutorial on getting the most out of your pwm, and thought it might be of use to the folks around here.  it starts with a basic overview of pwm and some of the tradeoffs:
http://www.openmusiclabs.com/learning/digital/pwm-dac/
then it goes into how to setup a dual pwm:
http://www.openmusiclabs.com/learning/digital/pwm-dac/dual-pwm-circuits/
and finally there is arduino/atmeg328 sample code for building a dual pwm that takes in audio from the onboad adc and plays it back out timer1 as a dac.  stuff could easily be thrown in the middle there to make a low-fi effects peda
http://wiki.openmusiclabs.com/wiki/PWMDAC

and for those who really want to dig deep, there is a full analysis of where all the distortion comes from, and ways to minimize it:
http://www.openmusiclabs.com/learning/digital/pwm-dac/pwm-distortion-analysis/
Title: Re: pwm audio on atmega/arduino
Post by: alparent on January 15, 2013, 08:11:18 AM
Thanks for doing this.
I'm new to microcontroler and the digital side of this world.
So far I've only used my Arduino to do fancy switching.
But this is going to be interesting.

I'm building a shield with 2 jacks that will fit on my Arduino board.

Now you will have to teach us what can be dune with this other then outputing the input signal  ???

I told you I know nothing! :icon_redface:
Title: Re: pwm audio on atmega/arduino
Post by: slacker on January 15, 2013, 01:40:58 PM
Nice tutorial, thanks for taking the time to do that. I think I'll need to read it through a few times to fully understand the dual PWM concept though.
Title: Re: pwm audio on atmega/arduino
Post by: alparent on January 16, 2013, 10:08:18 AM
OK, as you will soon enough find out, I don't know what I'm talking about.
So my question is. I'm building a shield for my Arduino. I'm going to use the Input stage on your "Example Arduino Sketch" page. But for the Output stage....should I use the Output stage in the same schematic or use the one on your "Dual PWM Circuits" page, the one designed by Steve Woodward?

Would chose on over the other Output stage change anything to the example sketch you provided?

Thank you for taking the time to educate us  :)
Title: Re: pwm audio on atmega/arduino
Post by: g_u_e_s_t on January 16, 2013, 04:35:08 PM
i would use the one on the arduino tutorial / wiki page.  the steve woodward circuit is cool, but its a bit overkill, and really isnt any better for frequencies above 1khz or so.  one more thing you will want to add, is a 10uF capacitor after the mixing resistors.  there is a DC bias on the signal as it comes out of the arduino, and putting a capacitor there will get rid of it.  a lot of mixers and amps have a DC blocking capacitor already in them, but this will be just in case.

im working on another tutorial geared exclusively towards making a low cost effects pedal out of the arduino, which will use this basic circuit, but have a few modifications to make things easier.  also, since the ADC is occupied taking samples, there will need to be either rotary encoders or buttons for getting input in (like adjusting lfo times and such).
Title: Re: pwm audio on atmega/arduino
Post by: alparent on January 16, 2013, 04:40:47 PM
Thanks
So I should wait a bit before making my shield?
Title: Re: pwm audio on atmega/arduino
Post by: g_u_e_s_t on January 16, 2013, 10:50:51 PM
ill try to have something up by tomorrow night.
i wanted to add a wet/dry mix knob and a feedback knob
Title: Re: pwm audio on atmega/arduino
Post by: alparent on January 17, 2013, 08:11:44 AM
Hey! Don't stress yourself out for me.

I'm just thankfull someone is taking the time to teach this. So I'll take what ever you give ......... when you give it.

Just don't want to make a nice shield and remake a new one in a week, if you know something else is comming.

Thanks again
Title: Re: pwm audio on atmega/arduino
Post by: FiveseveN on January 17, 2013, 09:09:00 AM
I don't have anything useful to add, just wanted to congratulate you on being featured on HaD (http://hackaday.com/2013/01/17/making-better-noises-with-dual-pwm/)  :icon_mrgreen:
Title: Re: pwm audio on atmega/arduino
Post by: g_u_e_s_t on January 18, 2013, 01:59:10 AM
thanks FiveseveN

so i dont think ill have time to do the really nice version for a little while, so i put up a quick example schematic and code to get things started.  the button interface is a bit wonky, but otherwise it doesnt sound half bad.

http://wiki.openmusiclabs.com/wiki/MiniArDSP

right now its a very bare bones arduino library.  as it grows, i will probably hide more things in the library, so the main code doesnt have to have as much in it.
Title: Re: pwm audio on atmega/arduino
Post by: earthtonesaudio on January 18, 2013, 03:09:06 PM
Great info, thanks g_u_e_s_t.
Title: Re: pwm audio on atmega/arduino
Post by: artifus on April 17, 2013, 12:38:47 PM
nice one.
Title: Re: pwm audio on atmega/arduino
Post by: earthtonesaudio on May 13, 2013, 05:27:41 PM
What determines the rate (in Hz) of the tremolo in the MiniArDSP code?
Title: Re: pwm audio on atmega/arduino
Post by: artifus on May 13, 2013, 05:46:34 PM
// mini_tremolo.pde
// minimalisitic tremelo program
// guest - openmusiclabs.com - 1.13.13
// takes in audio data from the ADC and plays it out on
// Timer1 PWM.  16b, Phase Correct, 31.25kHz.  applies a variable
// frequency tremolo to the sound.

#include "MiniArDSP.h"

#define PWM_FREQ 0x00FF // pwm frequency - see table
#define PWM_MODE 0 // Fast (1) or Phase Correct (0)
#define PWM_QTY 2 // number of pwms, either 1 or 2

// create sinewave lookup table
// PROGMEM stores the values in the program memory
// it is automatically included with MiniArDSP.h
PROGMEM  prog_int16_t sinewave[]  = {
  // this file is stored in MiniArDSP and is a 1024 value
  // sinewave lookup table of signed 16bit integers
  // you can replace it with your own waveform if you like
  #include <sinetable.inc>
};
unsigned int location; // lookup table value location

byte rate = 5; // tremolo rate
byte rate_counter; // modifiable version of rate
unsigned int amplitude; // current tremolo amplitude
unsigned int button;

void setup() {
  // setup button pins
  PORTD |= 0x0c; // turn on pullups for pins 2 and 3
 
  // setup ADC
  ADMUX = 0x60; // left adjust, adc0, internal vcc
  ADCSRA = 0xe5; // turn on adc, ck/32, auto trigger
  ADCSRB =0x07; // t1 capture for trigger
  DIDR0 = 0x01; // turn off digital inputs for adc0
 
  // setup PWM
  TCCR1A = (((PWM_QTY - 1) << 5) | 0x80 | (PWM_MODE << 1)); //
  TCCR1B = ((PWM_MODE << 3) | 0x11); // ck/1
  TIMSK1 = 0x20; // interrupt on capture interrupt
  ICR1H = (PWM_FREQ >> 8);
  ICR1L = (PWM_FREQ & 0xff);
  DDRB |= ((PWM_QTY << 1) | 0x02); // turn on outputs
 
  TIMSK0 = 0; // turn of t0 - no delay() or millis()
  sei(); // turn on interrupts - not really necessary with arduino
}

void loop() {
  while(1); // gets rid of jitter
  // nothing happens up here.  if you want to put code up here
  // get rid of the ISR_NAKED and the reti(); below
}

ISR(TIMER1_CAPT_vect, ISR_NAKED) { // ISR_NAKED is used to save
// clock cycles, but prohibits code in the loop() section.
 
  // get ADC data
  byte temp1 = ADCL; // you need to fetch the low byte first
  byte temp2 = ADCH; // yes it needs to be done this way
  int input = (temp2 << 8) | temp1; // make a signed 16b value
 
  button--; // check buttons every so often
  if (button == 0) {
    byte temp3 = PIND & 0x0c; // get pin 2 and 3 values
    if (temp3 == 0) { // both buttons pressed
    } // do nothing
    else if (temp3 == 0x08) { // up button pressed
      if (rate == 1); // do nothing
      else rate--; // make tremolo faster
    }
    else if (temp3 == 0x04) { // down button pressed
      if (rate == 255); // do nothing
      else rate++; // make tremolo slower
    }
    button = 0x0400; // reset counter
  }

  rate_counter--; // decrement our counter
  if (rate_counter == 0) {
    // create a variable frequency and amplitude sinewave
    // fetch a sample from the lookup table
    amplitude = pgm_read_word_near(sinewave + location) + 0x8000;
    // the + 0x8000 turns the signed value to unsigned
    location++;
    // if weve gone over the table boundary -> loop back
    // around to the other side.
    location &= 0x03ff; // fast way of doing rollover for 2^n numbers
    rate_counter = rate; // reset rate counter
  }
 

  // multiply our input by the sinewave value
  // note the use of the special multiply macro
  // this is faster than the normal * method
  // this is a signed * unsigned, returning the high 16 bits
  int output; // define variable before using it
  MultiSU16X16toH16(output, input, amplitude);
 
  // output data
  OCR1AL = (output + 0x8000) >> 8; // convert to unsigned
  // and only output the top byte
  OCR1BL = output; // output the bottom byte
  reti(); // return from interrupt - required because of ISR_NAKED
}
Title: Re: pwm audio on atmega/arduino
Post by: SISKO on May 13, 2013, 05:57:28 PM
I think that you would have to sum all the instruction from line 58 to 109 (including those in the MultiSU16X16toH16 macro) and multiply them by the instruction cycle period to get the time it takes the code to finish one loop.
In every loop, the counter gets decremented untill it gets to zero.
Then based on the value of rate_counter you can do some more math to get your freq.

So if rate_counter = 255 and *supose* the time it takes to finish one loop is 1ms, then the period of the tremolo is 255*1ms = 0.255ms -> 4Hz

Keep in mind the some macros as the ones that gets the byte from the adc may or may not consume several cycles.

Hope it helps!
Title: Re: pwm audio on atmega/arduino
Post by: earthtonesaudio on May 13, 2013, 07:56:36 PM
@sisko: That was my first guess, but I also was wondering if the capture/compare interrupt had something to do with the timing.

Specifically these parts:



  TIMSK1 = 0x20; // interrupt on capture interrupt
  ICR1H = (PWM_FREQ >> 8);
  ICR1L = (PWM_FREQ & 0xff);
Title: Re: pwm audio on atmega/arduino
Post by: g_u_e_s_t on May 15, 2013, 07:13:02 PM
everything is driven off the timer1 interrupt.  in the setup routine, the ADC is configured to take a sample everytime the timer1 capture event happens, so this just happens in the background, which makes everything easier and faster.  timer1 is set to interrput every 256*2 clock cycles.  the 2 comes from it running in phase correct mode, so it has to count up, and then back down for a full cycle.  since the arduino runs at 16MHz, this gives 16MHz/512 = 31.25kHz interrupt rate.  so this is the ADC sample rate, the PWM data playback rate, and the data processing rate.

in the timer1 interrupt, the data gets processed.  each time the interrupt happens, rate_counter gets decremented, and when it equals 0, the amplitude is modified to the next level of the LFO.  the LFO is stored as a 1k lookup table.  so, for rate = 1, it would take 31.25kHz/1k samples = 31.25Hz to go through the whole lookup table, and get a single cycle.  for rate = 2, this would take twice as long, and so forth.  in the example, rate is initially set to 5, so that gives 31.25Hz/5 = 6.2Hz.  so, as its currently setup, its good for longer LFO times, but starts to loose frequency resolution as you get above a few Hz.
Title: Re: pwm audio on atmega/arduino
Post by: earthtonesaudio on May 16, 2013, 08:08:14 AM
Cheers, thanks.
Title: Re: pwm audio on atmega/arduino
Post by: g_u_e_s_t on May 17, 2013, 12:56:10 AM
i just realized there might be a mistake in the code.  i was messing around with it tonight, and i had to change this line:

int input = (temp2 << 8) | temp1; // make a signed 16b value
to
int input = ((temp2 << 8) | temp1) + 0x8000; // make a signed 16b value

i also changed a bunch of other stuff, so who knows.  but if its giving you trouble, try making that change.  the first line doesnt actually give a signed value, so it probably was a mistake.  the second line does make a signed value
Title: Re: pwm audio on atmega/arduino
Post by: earthtonesaudio on June 09, 2013, 05:17:24 PM
One more question:
If I set the timer1 frequency as in the example, will later calls to analogWrite() be at that frequency?
Title: Re: pwm audio on atmega/arduino
Post by: g_u_e_s_t on June 09, 2013, 07:43:44 PM
im not certain what will happen.  i would expect any analogwrite to timer1's output pins will be at that frequency, but it all depends upon when the arduino code does the timer setup.  it might do it every time analogwrite is called?  i dont know.
Title: Re: pwm audio on atmega/arduino
Post by: g_u_e_s_t on July 12, 2013, 03:11:23 PM
i finally got around to cleaning up the hardware for this project.  i recorded a short sound sample of processing the audio through the arduino.  there is a link below to the wikipage with the sound sample.  its under the "Sound Sample" heading about halfway down the page.  again, webstuff confuses me, and this seemed simplest to do.  the first few bits are just straight passthrough, to give an idea of noise floor and frequency response.  the last bit is with a flanger effect.

http://wiki.openmusiclabs.com/wiki/MiniArDSP

im working on an arduino shield for it right now that fits in a 1590bb.
Title: Re: pwm audio on atmega/arduino
Post by: earthtonesaudio on July 12, 2013, 03:22:44 PM
The fidelity is way better than I expected.  Is there enough processing power left over to do tap tempo or other parameter adjustment?
Title: Re: pwm audio on atmega/arduino
Post by: g_u_e_s_t on July 12, 2013, 03:35:34 PM
right now i have a rotary encoder to change the LFO.  i think a tap tempo would work, especially if it is connected to one of the timer inputs, so it can run off an interrupt.  the shield im designing has a rotary encoder with switch on it.  maybe i should send that switch to the timer input.
Title: Re: pwm audio on atmega/arduino
Post by: earthtonesaudio on July 12, 2013, 03:52:56 PM
That would be cool.  I like rotary encoders.  Easy enough to put a footswitch in parallel with it.
I assume feedback and depth controls (if any) are analog/external to the DSP.

How big is the flanger program?  Would it be feasible to select multiple programs without re-flashing?


If you can't already tell, my interest level in this project just increased exponentially. :D

Also if you want any help with the analog wrapper let me know.
Title: Re: pwm audio on atmega/arduino
Post by: g_u_e_s_t on July 12, 2013, 05:02:07 PM
right now the shield has feedback, gain, mix, and volume.  so feedback and depth are done in analog, which offloads a lot of the processing, which is nice.  there are 3 pole anti-aliasing filters on the input and output, with a cutoff frequency of 10kHz.

ive written a tremolo, flanger, vco, delay, and bitcrusher.  each program probably takes up less than 1KB, so you could get quite a few of them on there.