Go Down

Topic: Using timer1 and 2 for great PCM Drums (6 voices) (Read 2472 times) previous topic - next topic

WilliamK Govinda

Feb 16, 2011, 07:42 pm Last Edit: Apr 03, 2013, 03:52 pm by WilliamK Govinda Reason: 1
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. ;-)

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

Code: [Select]
/*

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

WilliamK Govinda

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

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

Code: [Select]
/*

  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

WilliamK Govinda

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

Wk

WilliamK Govinda

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

Wk

polishdude20

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?

WilliamK Govinda

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

Wk

Thalium

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.


WilliamK Govinda

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

https://github.com/Beat707/

https://github.com/Beat707/BeatVox

Go Up
 


Please enter a valid email to subscribe

Confirm your email address

We need to confirm your email address.
To complete the subscription, please click the link in the email we just sent you.

Thank you for subscribing!

Arduino
via Egeo 16
Torino, 10131
Italy