Pages: [1]   Go Down
Author Topic: Using timer1 and 2 for great PCM Drums (6 voices)  (Read 1996 times)
0 Members and 1 Guest are viewing this topic.
Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
/*

  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);
}
« Last Edit: April 03, 2013, 08:52:36 am by WilliamK Govinda » Logged


Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
/*

  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
Logged


Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged


Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged


Offline Offline
Full Member
***
Karma: 0
Posts: 236
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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?
Logged

Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged


Offline Offline
Newbie
*
Karma: 0
Posts: 1
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Oh, very nice, thank you!  smiley-cool
Logged


Brazil
Offline Offline
God Member
*****
Karma: 3
Posts: 616
Wusik Dot Com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged


Pages: [1]   Go Up
Jump to: