Arduino Nano A-Law PCM Project

Hello,

I’m stuck on a A-law PCM project. It’s just a prototype for a future project, but I can’t seem to get my code working. I’ve copied code from the PCM example and the G711.c and then tweaked them to my needs.

I successfully encoded a wav file to a raw A-law file using Audacity and converted that into a binary header file to copy into sounddata.h.

The PCM example uses the 16-bit Timer1 to load each sample in at 8kHz and Timer2 as the sample PCM output. But because the G711.c decodes to a 16-bit PCM I need to switch to Timer1 and use Timer2 as the sample loader. I read through the datasheet to set the register values for each timer to get them in the correct modes (Fast PWM for Timer1 / CTC for Timer2).

I’ve been able to verify that the registers are setting in the correct modes, and I ran the Serial.print to verify that OCR1A is loading the right values, but it doesn’t pulse. The OC1A pin just goes high for the duration of the sample.

Below is my code. The comments that are far left justified where lines that I was toggling on and off for debug purposes. Any help is greatly appreciated.

#include "sounddata.h"

int ledPin = 13;
int speakerPin = 9; // Can be either 3 or 11, two PWM outputs connected to Timer 2
volatile uint16_t sample;
byte lastSample;
int a_val;
int decodeByte;

#define SIGN_BIT  (0x80)    /* Sign bit for a A-law byte. */
#define QUANT_MASK  (0xf)   /* Quantization field mask. */
#define NSEGS   (8)   /* Number of A-law segments. */
#define SEG_SHIFT (4)   /* Left shift for segment number. */
#define SEG_MASK  (0x70)    /* Segment field mask. */

/*
 * alaw2linear() - Convert an A-law value to 16-bit linear PCM
 *
 */
int alaw2linear(int  a_val)    
{
  int   t;      /* changed from "short" *drago* */
  int   seg;    /* changed from "short" *drago* */

  a_val ^= 0x55;

  t = (a_val & QUANT_MASK) << 4;
  seg = ((unsigned)a_val & SEG_MASK) >> SEG_SHIFT;
  switch (seg) {
  case 0:
    t += 8;
    break;
  case 1:
    t += 0x108;
    break;
  default:
    t += 0x108;
    t <<= seg - 1;
  }
  return ((a_val & SIGN_BIT) ? t : -t);
}
void stopPlayback()
{
    // Disable playback per-sample interrupt.
    TIMSK2 &= ~_BV(OCIE2A);

    // Disable the per-sample timer completely.
    TCCR2B &= ~_BV(CS10);

    // Disable the PWM timer.
    TCCR1B &= ~_BV(CS10);

    digitalWrite(speakerPin, LOW);
}

// This is called at 8000 Hz to load the next sample.
ISR(TIMER2_COMPA_vect) {  
    if (sample >= sounddata_length) {
        if (sample == sounddata_length + lastSample) {
            stopPlayback();
        }
        else {
            if(speakerPin==9){
                // Ramp down to zero to reduce the click at the end of playback.
                OCR1A = sounddata_length + lastSample - sample;  
            } else {
                OCR1B = sounddata_length + lastSample - sample;                
            }
        }
    }
    else {
        if(speakerPin==9){
			
//			cli();  	// Tried experimenting with a clear interrupt command, 
						// but actually prevented the sample to be loaded into OCR1A
						
//          OCR1A = pgm_read_byte(&sounddata_data[sample]);
			OCR1A = alaw2linear(pgm_read_byte(&sounddata_data[sample]));

//            Serial.print("sound_data[sample]: ");
//            Serial.print(OCR1A, DEC);
//            Serial.print("\n");
            
//            sei();
            
        } else {
			// Not used
            OCR1B = alaw2linear(pgm_read_byte(&sounddata_data[sample]));            
        }
    }

    ++sample;
}

void startPlayback()
{
    pinMode(speakerPin, OUTPUT);


    // Set up Timer 1 to do to do pulse width modulation on the speaker pin.

    cli();

    // Set fast PWM mode  (p.134 - 136)
    // Mode 15, WGM13 set 1, WGM12 set 1, WGM11 set 1, WGM10 set 1
    TCCR1A |= _BV(WGM11) | _BV(WGM10);
    TCCR1B |= _BV(WGM13) | _BV(WGM12);

    /* 	To set the output mode to inverted mode, 
		the COM1A1 and COM1A0 bits are set.         
		To set the output mode to non-inverted mode, 
		the COM1A1 bit is set, but the COM1A0 is not set. */
	   
    // Do non-inverting PWM on pin OC2A (p.135)
    // On the Arduino this is pin 11.
    TCCR1A = (TCCR1A | _BV(COM1A1)) & ~_BV(COM1A0);
    TCCR1A = (TCCR1A | _BV(COM1B1)) & ~_BV(COM1B0);
	
    // No prescaler (p.137)
    TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

//    Serial.print("TCCR1A: ");
//    Serial.print(TCCR1A, HEX);
//    Serial.print("\n");
//
//    Serial.print("TCCR2A: ");
//    Serial.print(TCCR1B, HEX);
//    Serial.print("\n");

    if(speakerPin==9){
		// Have to set OCR1A *after*, otherwise it gets reset to 0!
		
        // OCR1B and OCR1A p. 138 16-bit register r/w instructions
        // Set initial pulse width to the first sample.
        
//        OCR1A = pgm_read_byte(&sounddata_data[0]);
        OCR1A = alaw2linear(pgm_read_byte(&sounddata_data[0]));
//        Serial.print(OCR1A, HEX);
//        Serial.print("\n");		
        
    } else {
        // Set initial pulse width to the first sample.
//		  OCR1B = pgm_read_byte(&sounddata_data[0]);
        OCR1B = alaw2linear(pgm_read_byte(&sounddata_data[0]));        
    }

    sei();

    // Set up Timer 2 to send a sample every interrupt. Changed 1s to 2s
    
	// Set Timer 2 to clock from I/O clock (p. 164)
    ASSR &= ~(_BV(EXCLK) | _BV(AS2));

    // Set CTC mode (Clear Timer on Compare Match) (p.160)        
    TCCR2B = (TCCR2B & ~_BV(WGM22));
    TCCR2A = (TCCR2A | _BV(WGM21)) & ~(_BV(WGM20));

    // clk /8 prescaler (p.162)
    TCCR2B = (TCCR2B & ~(_BV(CS22) | _BV(CS20))) | _BV(CS21);

    // Set the compare register (OCR2A).
	// OCR2A = FCPU / (Prescaler * SAMPLE_RATE)
    OCR2A = 250;    // 16e6 / (8 * 8000) = 250

    // Enable interrupt when TCNT2 == OCR2A (p.163)
    TIMSK2 |= _BV(OCIE2A);

//    lastSample = pgm_read_byte(&sounddata_data[sounddata_length-1]);
    lastSample = alaw2linear(pgm_read_byte(&sounddata_data[sounddata_length-1]));
    sample = 0;

}


void setup()
{
//    Serial.begin(250000);
    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, HIGH);
    startPlayback();
}

void loop()
{
    while (true){
//      startPlayback();
//      delay(5000);
//      stopPlayback();
//      delay(1000);      
    }    
}

Thanks!
-J