Using timer1 and 2 for great PCM Drums (6 voices)

Edit: this project was updated and moved to: https://github.com/Beat707/BeatVox


So, I updated my code and now I got 6 voices using Pins 9 and 10 PWM. Thanks to the Timer1 16-bit I was able to get near 10-bit resolution, so I could just mix 3 8-bit voices without having to apply any / which would reduce audio-quality.

The example includes a simple loop I created in the Loop() area. :wink:

Timer2 was used to read samples, while Timer1 does the PWM at near 10-bits.

Just "mix" the output of Pins 9 and 10, add 10k resistors and a filter (capacitor) which I don't know the value yet, sorry.

Download files:
http://www.wusik.com/arduino/Libraries/Multi_PCM/Sound_Test_16Bits.pde
http://www.wusik.com/arduino/Libraries/Multi_PCM/PCM_Data.h

Please, contribute to the project at: http://arduino.wusik.com

/*

  Created by WilliamK @ Wusik Dot Com (c) 2011 - http://arduino.wusik.com
  
  Code excerpts from: http://www.arduino.cc/playground/Code/PCMAudio - Michael Smith <michael@hurts.ca>

  8-Bit PCM Sound using PWM on Pin 9 and Pin 10 - Uses the 16-Bit timer to sum 3 voices in each Pin
  Uses Timer2 and Timer1
  
*/

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <EEPROM.h>
#include "PCM_Data.h"

// ======================================================================================= //
#define SAMPLE_RATE 32000
unsigned int sample[6] = {2047,2047,2047,2047,2047,2047};
unsigned long mixer[2] = {0,0};
byte inByte = 0;

// ======================================================================================= //
void setup()
{    
    Serial.begin(9600);
    Serial.println("Type 1 to 6 for Sample Playback");
    
    pinMode(9,OUTPUT);
    pinMode(10,OUTPUT);
    
    // 16 Bit Timer Setup //
    // 16000000 (CPU CLOCK) / 765 (near 10 Bits) = 20.9khz (put a filter to remove anything above 16khz) //
    TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);
    TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10);
    ICR1 = 765;
    OCR1A = (128*3);
    OCR1B = (128*3);
        
    /*
    
      Timer 1 Registers - ATmega328 Datasheet Page 113 (16 Bit Timer)
      
      Bit        7       6       5       4       3      2      1      0
      TCCR1A = COM1A1  COM1A0  COM1B1  COM1B0    R      R    WGM11  WGM10
      TCCR1B = ICNC1   ICES1   R       WGM13   WGM12  CS12   CS11   CS10
      
      COM1A = Out Pin 9
      COM1B = Out Pin 10
      WGM12 WGM10 = Mode 5 Fast PWM 8 Bits
      WGM12 WGM11 WGM10 = Mode 7 Fast PWM 10 Bits
      WGM13 WGM12 WGM11 = Mode 14 Fast PWM Top=ICR1
      CS10 = No PreScaler
      
    */
    
    // Timer 2 - used to load Samples //
    startTimer();
}

// ======================================================================================= //
void loop()
{
  sample[0] = 0 ;
  delay(255);
  sample[2] = 0 ;
  delay(255);
  sample[0] = 0 ;
  sample[1] = 0 ;
  delay(255);
  sample[2] = 0 ;
  delay(255);
  sample[0] = 0 ;
  delay(255);
  sample[2] = 0 ;
  delay(255);
  sample[0] = 0 ;
  sample[1] = 0 ;
  delay(255/2);
  sample[2] = 0 ;
  delay(255/2);
  sample[3] = 0 ;
  delay(255/2);
  sample[4] = 0 ;
  delay(255/2);
  
  if (Serial.available() > 0) 
  {
    inByte = Serial.read();
  
    switch (inByte)
    {
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
        sample[inByte-'1'] = 0 ;
        break;
    }
  }
} 

// ======================================================================================= //
ISR(TIMER2_COMPA_vect) 
{  
  if (sample[0] < 2047) { mixer[0]  = (unsigned int)pgm_read_byte(&PCM_Data[0][sample[0]]); sample[0]++; } else mixer[0] = 128;
  if (sample[1] < 2047) { mixer[0] += (unsigned int)pgm_read_byte(&PCM_Data[1][sample[1]]); sample[1]++; } else mixer[0] += 128;
  if (sample[2] < 2047) { mixer[0] += (unsigned int)pgm_read_byte(&PCM_Data[2][sample[2]]); sample[2]++; } else mixer[0] += 128;
  
  if (sample[3] < 2047) { mixer[1]  = (unsigned int)pgm_read_byte(&PCM_Data[3][sample[3]]); sample[3]++; } else mixer[1] = 128;
  if (sample[4] < 2047) { mixer[1] += (unsigned int)pgm_read_byte(&PCM_Data[4][sample[4]]); sample[4]++; } else mixer[1] += 128;
  if (sample[5] < 2047) { mixer[1] += (unsigned int)pgm_read_byte(&PCM_Data[5][sample[5]]); sample[5]++; } else mixer[1] += 128;
  
  OCR1A = mixer[0];
  OCR1B = mixer[1];
}

// ======================================================================================= //
void startTimer()
{
    TCCR2A = 0;
    TCCR2B = 0;
    bitWrite(TCCR2A, WGM21, 1);
    bitWrite(TCCR2B, CS20, 1);

	uint32_t ocr = F_CPU / SAMPLE_RATE - 1;
	uint8_t prescalarbits = 0b001;

	if (ocr > 255)
	{
		ocr = F_CPU / SAMPLE_RATE / 8 - 1;
		prescalarbits = 0b010;

		if (ocr > 255)
		{
			ocr = F_CPU / SAMPLE_RATE / 32 - 1;
			prescalarbits = 0b011;
		}

		if (ocr > 255)
		{
			ocr = F_CPU / SAMPLE_RATE / 64 - 1;
			prescalarbits = 0b100;

			if (ocr > 255)
			{
				ocr = F_CPU / SAMPLE_RATE / 128 - 1;
				prescalarbits = 0b101;
			}

			if (ocr > 255)
			{
				ocr = F_CPU / SAMPLE_RATE / 256 - 1;
				prescalarbits = 0b110;
				
				if (ocr > 255)
				{
					ocr = F_CPU / SAMPLE_RATE / 1024 - 1;
					prescalarbits = 0b111;

					if (ocr > 255)
					{
						return;
					}
				}
			}
		}
	}

	TCCR2B = prescalarbits;
	OCR2A = ocr;
	bitWrite(TIMSK2, OCIE2A, 1);
}

Actually, since my "mentor" told me I could do it, I managed to get everything done with a single timer. :fearful:

Now it uses the 16-bit timer instead for everything.

/*

  Created by WilliamK @ Wusik Dot Com (c) 2011 - http://arduino.wusik.com
  
  8-Bit PCM Sound using PWM on Pin 9 and Pin 10 - Uses the 16-Bit Timer1 to sum 3 voices in each Pin
  
*/

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <EEPROM.h>
#include "PCM_Data.h"

// ======================================================================================= //
unsigned int sample[6] = {2047,2047,2047,2047,2047,2047};
unsigned long mixer[2] = {(128*3),(128*3)};
byte inByte = 0;

// ======================================================================================= //
void setup()
{   
    pinMode(9,OUTPUT);
    pinMode(10,OUTPUT);
  
    Serial.begin(9600);
            
    // 16 Bit Timer Setup //
    // 16000000 (CPU CLOCK) / 760 (10 Bits) = 21052 samples-per-second (put a filter to remove anything above 10.5khz)
    TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11); // Fast PWM Timer on Pins 9 and 10
    TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10); // Non-Phase Inverting - No Prescaler
    ICR1 = 760; // Max value of 760 (9-bits is 511 and 10-bits is 1023)
    TIMSK1 = _BV(TOIE1); // timer overflow interrupt
    sei(); // enable global interrupts
    OCR1A = (128*3); // Initial PWM Pin 9
    OCR1B = (128*3); // Initial PWM Pin 10
}

// ======================================================================================= //
void loop()
{
  if (Serial.available() > 0)
  {
    inByte = Serial.read();
  
    switch (inByte)
    {
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
        sample[inByte-'1'] = 0 ;
        break;
    }
  }
}

// ======================================================================================= //
ISR(TIMER1_OVF_vect)
{   
  // Do this first so the PWM is updated faster //
  OCR1A = mixer[0];
  OCR1B = mixer[1];

  // Now Calculate the next sample //
  if (sample[0] < 2047) { mixer[0]  = (unsigned int)pgm_read_byte(&PCM_Data[0][sample[0]]); sample[0]++; } else mixer[0] = 128;
  if (sample[1] < 2047) { mixer[0] += (unsigned int)pgm_read_byte(&PCM_Data[1][sample[1]]); sample[1]++; } else mixer[0] += 128;
  if (sample[2] < 2047) { mixer[0] += (unsigned int)pgm_read_byte(&PCM_Data[2][sample[2]]); sample[2]++; } else mixer[0] += 128;

  if (sample[3] < 2047) { mixer[1]  = (unsigned int)pgm_read_byte(&PCM_Data[3][sample[3]]); sample[3]++; } else mixer[1] = 128;
  if (sample[4] < 2047) { mixer[1] += (unsigned int)pgm_read_byte(&PCM_Data[4][sample[4]]); sample[4]++; } else mixer[1] += 128;
  if (sample[5] < 2047) { mixer[1] += (unsigned int)pgm_read_byte(&PCM_Data[5][sample[5]]); sample[5]++; } else mixer[1] += 128; 
}

I just need to re-do the samples now...

Wk

The next step is to try a 256k External EEPROM and see if I can read it fast enough. :wink: If this works, it would be possible to add 8-bit sound to the Wusik Arduino Drum Machine! :sweat_smile: :drooling_face:

Wk

Nah, the EEPROM is not fast enough, its only 400khz, while we would need something faster.

Now I wonder about another thing, creating a sine/saw/pulse based oscillator controlled via midi. I wonder if I would be able to get 6 voices, or if I would need to build a wavetable and read like drum sounds instead.

Not sure when I will have some extra free time to work on this, but I'm sure curious to see what could be done with this idea. :wink:

Wk

so all I need for this test example is 2 speakers connected to ground and pins 9 and 10? Oh and a capacitor connected in series?

And what do you mean by drums? Just drum sounds or what? Can I produce tones of music notes?

You could just use a Resistor, 10K, for each Pin, them merge both 10K into a single speaker. The Capacitor is to filter high-frequencies out, but it still sound great without it, haven't even tested with it yet. :wink:

Wk

Check out this site: http://sensorium.github.com/Mozzi/

It's got great synthesis stuff for arduino. We tried it out, and theres some cool sounds in it.

Oh, very nice, thank you! 8)

Actually, this whole project is now on http://beat707.com/ and its called BeatVox, and has a GitHub url now.

https://github.com/Beat707/BeatVox