welcome: please sign in
location: PWMDAC

Arduino (ATmega) PWM audio DAC

About PWM on the Arduino (ATmega)

This page describes all the nitty gritty of getting your Arduino set up to output decent audio on its PWM via Timer1. For more in-depth information on what the settings mean, and what values are right for your application, please check out our PWM Tutorial on the main site.

Timer1 outputs its data on pins 9 and 10 on the Arduino (pin 9/10 are OC1A/B and PORTB1/2 on the ATmega328p). For all of the following, it will be assumed that pin 9 is used for Single PWM, and is the high byte for Dual PWM. Pin 10 is not used for Single PWM, and is the low byte for Dual PWM. You can also set up for Dual PWM, and output 2 different signals, one on each output.

Other timers can be similarly setup to run PWM, but we use Timer1 because it is a 16 bit timer, so it can do greater bit depth at lower frequencies (if desired). The register settings for other timers is similar, but check the ATmega328p datasheet to be make sure its all correct. Also, be careful when using Timer0 on the Arduino, as it is already in use for the delay() and millis() functions, and its overflow interrupt can not be reused.

Register settings

The following charts all assume a microcontroller clock frequency of 16MHz (standard on the Arduino). If you are using a different oscillator, just multiply the frequencies listed by Fcpu/16MHz, where Fcpu is your oscillator frequency. For example, if you were running at 8MHz, you would multiply by 1/2, and if you were running at 20MHz, you would multiply by 5/4. Everything else stays the same.

There are only 3 values to set in order to configure your PWM. they are:


Fast - PWM_MODE 1

Phase Correct - PWM_MODE 0

Frequency

PWM_FREQ

Bit Depth

PWM_FREQ

Bit Depth

Fcpu = 16MHz

Value

Single

Dual

Value

Single

Dual

250kHz

0x003F

6 bit

12 bit

0x001F

5 bit

10 bit

125kHz

0x007F

7 bit

14 bit

0x003F

6 bit

12 bit

62.5kHz

0x00FF

8 bit

16 bit

0x007F

7 bit

14 bit

31.3kHz

0x01FF

9 bit

N/A

0x00FF

8 bit

16 bit

15.6kHz

0x03FF

10 bit

N/A

0x01FF

9 bit

N/A

7.81kHz

0x07FF

11 bit

N/A

0x03FF

10 bit

N/A

3.91kHz

0x0FFF

12 bit

N/A

0x07FF

11 bit

N/A

1.95kHz

0x1FFF

13 bit

N/A

0x0FFF

12 bit

N/A

976Hz

0x3FFF

14 bit

N/A

0x1FFF

13 bit

N/A

488Hz

0x7FFF

15 bit

N/A

0x3FFF

14 bit

N/A

244Hz

0xFFFF

16 bit

N/A

0x7FFF

15 bit

N/A

122Hz

N/A

N/A

N/A

0xFFFF

16 bit

N/A

Dual PWMs

Dual PWMs just mix an upper and lower value together, to make one analog value. This mixing occurs through 2 resistors, one of which is 2^n times the other, where n is the bit depth. For 2 - 8 bit PWMs (16b), use 1:256, or 3.9k and 1M (as shown below). For 2 - 7 bit PWMs (14b), use 1:128, or 3.9k and 499k, etc. The 3.9k is used so that the output resistance of the microcontroller is much less than the total resistance, reducing errors. And be sure to use 1% resistors for 14b and higher PWMs.

Example Arduino Sketch

This skecth uses the above chart to set the Arduino PWM to 31.25kHz, Dual PWM, Phase Correct, at 16 bit depth. It takes in audio on ADC0, and plays it out the PWM, so you can hear the sort of quality you can expect from 10 bit data (10 bits is the depth of the ADC). For ATmega programmers, the Arduino sketch is written in C, and is essentially usable if copied into your programming interface of choice. The following low-pass filter and summing circuit should be used, along with a the bias circuit for the ADC.

pwm_schem_sm.jpg

   1 // adc_to_pwm.pde
   2 // ADC to PWM converter
   3 // guest - openmusiclabs.com - 1.9.13
   4 // options table at http://wiki.openmusiclabs.com/wiki/PWMDAC
   5 // takes in audio data from the ADC and plays it out on
   6 // Timer1 PWM.  16b, Phase Correct, 31.25kHz - although ADC is 10b.
   7 
   8 #define PWM_FREQ 0x00FF // pwm frequency - see table
   9 #define PWM_MODE 0 // Fast (1) or Phase Correct (0)
  10 #define PWM_QTY 2 // number of pwms, either 1 or 2
  11 
  12 void setup() {
  13   // setup ADC
  14   ADMUX = 0x60; // left adjust, adc0, internal vcc
  15   ADCSRA = 0xe5; // turn on adc, ck/32, auto trigger
  16   ADCSRB =0x07; // t1 capture for trigger
  17   DIDR0 = 0x01; // turn off digital inputs for adc0
  18   
  19   // setup PWM
  20   TCCR1A = (((PWM_QTY - 1) << 5) | 0x80 | (PWM_MODE << 1)); // 
  21   TCCR1B = ((PWM_MODE << 3) | 0x11); // ck/1
  22   TIMSK1 = 0x20; // interrupt on capture interrupt
  23   ICR1H = (PWM_FREQ >> 8);
  24   ICR1L = (PWM_FREQ & 0xff);
  25   DDRB |= ((PWM_QTY << 1) | 0x02); // turn on outputs
  26   
  27   sei(); // turn on interrupts - not really necessary with arduino
  28 }
  29 
  30 void loop() {
  31   while(1); // gets rid of jitter
  32 }
  33 
  34 ISR(TIMER1_CAPT_vect) {
  35   
  36   // get ADC data
  37   unsigned int temp1 = ADCL; // you need to fetch the low byte first
  38   unsigned int temp2 = ADCH;
  39   // although ADCH and ADCL are 8b numbers, they are represented
  40   // here by unsigned ints, just to demonstrate how you would
  41   // use numbers larger than 8b.  also, be sure you use unsigned
  42   // ints for this operation.  if you have a signed int (a regular
  43   // int), add 0x8000 and cast it to an unsigned int before sending
  44   // it out to OCR1AH or OCR1AL.
  45   // example:
  46   // int temp3 = 87;
  47   // unsigned int temp4 = temp3 + 0x8000;
  48   // OCR1AH = temp4 >> 8;
  49   // OCR1AL = temp4;
  50   
  51   // output high byte on OC1A
  52   OCR1AH = temp2 >> 8; // takes top 8 bits
  53   OCR1AL = temp2; // takes bottom 8 bits
  54   
  55   // output low byte on OC1B
  56   OCR1BH = temp1 >> 8;
  57   OCR1BL = temp1;
  58 }
  59 

PWMDAC (last edited 2013-01-15 05:27:27 by guest)