This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

MSP430G2452: Audio Playback from Internal Flash

I am trying to play some WAV files turned into hex code as in the TI application note SLAA405 and this question.

My setup is like this:

MSP4302452

P1.0 -> LED

P1.2 -> RC filter (8khz low pass) -> Speaker

P1.3 <- Button

I can get it to work with a simple while loop like this in the button ISR:

#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void)
{
 while (lAudioSampleCnt <= lAudioSampleLength) {
        TA0CCR1 = Audio1[lAudioSampleCnt];
        lAudioSampleCnt++;
        delay_ms(368);
    }
 P1IFG &= ~BIT3;
}

But I understand this is not optimal or accurate with its timing. I want to but cannot get it to work with the code provided by the application note and the question. There is no sound. For reference:

#include "msp430g2452.h"
#include "Audio1.h"

char * pAudio;

unsigned int uAvgCntr;
unsigned int uAudioSample1;
unsigned int uAudioSample2;
int iAudioSampleDifference;
unsigned long lAudioSampleCnt;
unsigned long lAudioSampleLength;

void main (void)
{
    WDTCTL = WDTPW | WDTHOLD;   // stop watchdog timer

   P1DIR |= BIT2 | BIT0;                          // P1.0 and P1.2 output
   P1SEL |= BIT2 | BIT0;                         // P1.0 and P1.2 options select
   P1OUT |= BIT0;

   /* Set up button interrupt */
   P1DIR &= ~BIT3;
   P1OUT |= BIT3;
   P1REN |= BIT3;
   P1IES |= BIT3;
   P1IE |= BIT3;  // P1.3 interrupt enabled
   P1IFG &= ~BIT3;  // P1.3 interrupt flag cleared

   uAudioSample1 = Audio1[0];
   uAudioSample2 = Audio1[1];
   lAudioSampleLength = 2012;

   __bis_SR_register(SCG0);
   BCSCTL1 = CALBC1_16MHZ;
   DCOCTL = CALDCO_16MHZ;
   __delay_cycles(3);
   __bic_SR_register(SCG0);

   P1OUT &= ~BIT0;

   // Initialize Timer
   //TA0CCTL0 = CCIE;                               // CCR0 interrupt enabled
   TA0CCTL1 = CM_0 | OUTMOD_0 | CCIE;                           // CCR1 Output Mode reset/set
   //TA0CCTL2 = OUTMOD_7;                           // CCR2 Output Mode reset/set
   TA0CCR0 = 256;                                 // Set PWM period to 256 clock ticks
   TA0CCR1 = 0;
   TACTL = TASSEL_2 + MC_1 + TACLR + ID_1;

   __bis_SR_register(GIE);
    // Mainloop

  while (1)
  {
      P1IE &= ~BIT3;
      P1OUT |= BIT0;

      pAudio = (char *) &Audio1;            // set pointer to Audio1 data byte

      *(((char*)(&lAudioSampleLength))+0) = * (pAudio++);
      *(((char*)(&lAudioSampleLength))+1) = *(pAudio++);
      *(((char*)(&lAudioSampleLength))+2) = *(pAudio++);
      *(((char*)(&lAudioSampleLength))+3) = *(pAudio++);

      uAudioSample1 = *(pAudio++);
      uAudioSample2 = *(pAudio++);

      TACCTL1 &= ~(OUTMOD_7 | OUT);         // PWM output = LOW
      P1DIR |= BIT2;                        // TA1/P1.2 is output
      P1SEL |= BIT2;                        // TA1/P1.2 is TA1 output
      TACCTL1 |= OUTMOD_7;

      uAvgCntr = 0;
      TACTL |= MC_1;

      TACTL |= MC_1;                         // Start Timer
      for (lAudioSampleCnt = lAudioSampleLength-1; lAudioSampleCnt > 0; lAudioSampleCnt--)
      {
          __bis_SR_register(LPM0_bits + GIE);

          uAudioSample1 = uAudioSample2;
          uAudioSample2 =  *(pAudio++);
      }

      TACTL &= ~MC_0;                        // Stop Timer
      TACCTL1 &= ~(OUTMOD_7 | OUT);                                      // and wait until putton pressed
      P1OUT  &= ~BIT2;                  // PWM GPIO-Pin = LOW
      P1DIR  &= ~BIT2;                  // TA2/P2.0 is input
      P1SEL  &= ~BIT2;                  // TA2/P2.0 is P2.0 input

      P1OUT &= ~BIT0;
      P1IFG &= ~BIT3;
      P1IE |= BIT3;

      __bis_SR_register(LPM4_bits + GIE);
  }
}

//******************************************************************************
// Timer A0 interrupt service routine
//******************************************************************************
#pragma vector=TIMER0_A1_VECTOR
__interrupt void TIMER0_A1_ISR (void)
{                                           // Timer interupt (PWM, one sample)
    switch (__even_in_range(TAIV,10))
    {
        case 2:                             // Vector 2: TACCR1
            __no_operation();
            iAudioSampleDifference = (int) uAudioSample2 - (int) uAudioSample1;
            if (iAudioSampleDifference > 68)
            {
                TACTL   &= ~TAIFG;            // clear overflow flag TAIFG IFG
                TACTL   |= TAIE;              // enable overflow interrupt TAIFG
                TACCTL1 &= ~CCIE;             // turn off TACCR1 IFG
            }
            switch (__even_in_range(uAvgCntr,6))
            {
                case 0: TACCR1 = (uAudioSample1 + uAudioSample1 + uAudioSample1 + uAudioSample2) >> 2;
                        break;
                case 2: TACCR1 = (uAudioSample1 + uAudioSample2) >> 1;
                        break;
                case 4: TACCR1 = (uAudioSample1 + uAudioSample2 + uAudioSample2 + uAudioSample2) >> 2;
                        break;
                case 6: TACCR1 = uAudioSample2;
                        __bis_SR_register_on_exit(LPM4_bits);
                        break;
            }
            uAvgCntr += 2;                     // increment averaging counter always by 2
            uAvgCntr = uAvgCntr & 0x06;        // averaging counter range: 0, 2, 4, 6
            break;
        case 4:
            break;
        case 10:
            TACCTL1 &= ~CCIFG;
            TACCTL1 |= CCIE;
            TACTL &= ~TAIE;
            break;
    }
} // __interrupt void TimerA1 (void)

//******************************************************************************
// P1 interrupt service routine
//******************************************************************************
#pragma vector=PORT1_VECTOR
__interrupt void Port_1 (void)
{
    P1IFG = 0x00;                             // reset all interupt flags
    P1IE &= ~BIT3;                  // Disable Port Interrupt
    _bic_SR_register_on_exit(LPM4_bits);     // Clear all LPM bits from 0(SR)
                                         // this restarts main loop and
                                        // starts next audio playback
    __no_operation();
}

Audio1.h is the same as in the application note. Can anyone see if there is a mistake with this code or my implementation?

  • Hello,

    What happens if you just use active mode? When using deeper LPMx like LPM4, there can be startup constraints that may not meet your timing requirements.

    Regards,

    James

  • Active Mode works~ somewhat. I get timer overflows (TAIV case 10)  but I am able to at least hear something. It's not the full audio as a result. What causes an overflow?

  • >                        __bis_SR_register_on_exit(LPM4_bits);

    Typo alert: This keeps main in LPM4 so it plays the same 2 samples forever. Try:

    >                        __bic_SR_register_on_exit(LPM4_bits);

    (LPM4_bits is a superset of LPM0_bits, so this still works for LPM0.)

    ---------------

    You'll  get a TAIFG on every timer cycle. According to the appnote, it is included to avoid a race that could cause an extra edge [Ref SLAA405B p. 11 (top)] 

  • Ah a typo indeed, but it did not solve the problem. Thanks though!

    After some trial and error I got it working with this code but I think I'm hearing the 8khz pwm frequency after analyzing the frequency spectrum with Audacity. Strange. My filter should be taking care of it. If I can take care of this, my issue will be solved.

    I moved the counting to the Timer ISR and now there is no main loop.

    int main(void)
    {
        .........Same initializers as above..........
        .............................................
    
    
        // Initialize Timer
        TA0CCTL0 = CCIE;                               // CCR0 interrupt enabled
        TA0CCTL1 = OUTMOD_7;                           // CCR1 Output Mode reset/set
        TA0CCR0 = 256;                                 // Set PWM period to 256 clock ticks
        TA0CTL = TASSEL_2 + MC_1 + TACLR + ID_1;       // SMCLK, upmode, clear TA1R, with a divider of 2
    
        _BIC_SR(LPM0_bits + GIE);                      // Enter LPM0 w/ interrupt
    }

    /**
     * TimerA0 interrupt service routine
     **/
    #pragma vector=TIMER0_A0_VECTOR
    __interrupt void TIMER0_A0_ISR(void)
    {
        if (button_press != 0)
        {
    
            if (lAudioSampleCnt > NUM_ELEMENTS)
            {
                lAudioSampleCnt = 0;
                button_press = 0;
            } else {
                iAudioSampleDifference = (int) uAudioSample2 - (int) uAudioSample1;
                if (iAudioSampleDifference > 68)
                {
                    TACTL   &= ~TAIFG;            // clear overflow flag TAIFG IFG
                    TACTL   |= TAIE;              // enable overflow interrupt TAIFG
                    TACCTL1 &= ~CCIE;             // turn off TACCR1 IFG
                }
                switch (__even_in_range(uAvgCntr,6))
                {
                    case 0: TACCR1 = (uAudioSample1 + uAudioSample1 + uAudioSample1 + uAudioSample2) >> 2;
                            break;
                    case 2: TACCR1 = (uAudioSample1 + uAudioSample2) >> 1;
                            break;
                    case 4: TACCR1 = (uAudioSample1 + uAudioSample2 + uAudioSample2 + uAudioSample2) >> 2;
                            break;
                    case 6: TACCR1 = uAudioSample2;
                            //__bis_SR_register_on_exit(LPM4_bits);
                            break;
                }
                uAudioSample1 = data[lAudioSampleCnt++];
                uAudioSample2 = data[lAudioSampleCnt];
                uAvgCntr += 2;                     // increment averaging counter always by 2
                uAvgCntr = uAvgCntr & 0x06;        // averaging counter range: 0, 2, 4, 6
            }
    
        } else {
            TA0CCR1 = 0;
            lAudioSampleCnt = 0;
        }
    }
    
    /**
     * TimerA1 interrupt service routine
     * To handle the timer overflow
     **/
    #pragma vector=TIMER0_A1_VECTOR
    __interrupt void TIMER0_A1_ISR (void)
    {
        switch (__even_in_range(TAIV,10))
            {
                case 2:                             // Vector 2: TACCR1
                    __no_operation();
                    break;
                case 4:
                    __no_operation();
                    break;
                case 10:                            // Vector 10: Timer Overflow
                    TACCTL1 &= ~CCIFG;              // Reset Interrupt flag
                    //TACCTL1 |= CCIE;              // TACCR1 Capture Interrupt Enable (Don't want this)
                    TACTL &= ~TAIE;                 // Timer A Interrupt Enable
                    break;
            }
    }
    
    
    //******************************************************************************
    // P1 interrupt service routine
    //******************************************************************************
    #pragma vector=PORT1_VECTOR
    __interrupt void Port_1(void)
    {
        _DINT();
        counter = 0;
        lAudioSampleCnt = 0;
        TA0CCR1 = 0;
        button_press = 1;
        P1IFG &= ~BIT3;
        _EINT();
    }

  • >    TA0CCR0 = 256;                                 // Set PWM period to 256 clock ticks

    This actually requests a period of (256+1) [Ref User Guide (SLAU144J) Fig 12-3]. As I recall, the appnote used 0xFF (256-1). (I'm not quite sure why they didn't use (16MHz/2/(4*8000)=250)-1, but maybe the text explains that.)

    It seems like this code advances the sample on every PWM period, which as I understand it is 4x the actual 8k sample rate. Maybe you should move the sample update into "case 6" (where the LPM exit used to be)?

  • Fixed. It works well now! Here's what I changed.

    /**
     * main.c
     */
    int main(void)
    {
        
        ..... Same parameters as before ........
    
    
        TA0CCR0 = 0xFF;                                 // Set PWM period to 0xFF clock ticks
        TA0CTL = TASSEL_2 + MC_1 + TACLR + ID_1;       // SMCLK, upmode, clear TA1R, with a divider of 2
    
    }

    /**
     * TimerA0 interrupt service routine
     **/
    #pragma vector=TIMER0_A0_VECTOR
    __interrupt void TIMER0_A0_ISR(void)
    {
        if (button_press != 0)
        {
    
            if (lAudioSampleCnt > NUM_ELEMENTS)
            {
                lAudioSampleCnt = 0;
                button_press = 0;
            } else {
    
                switch (__even_in_range(uAvgCntr,6))
                {
                    case 0: TACCR1 = (uAudioSample1 + uAudioSample1 + uAudioSample1 + uAudioSample2) >> 2;
                            break;
                    case 2: TACCR1 = (uAudioSample1 + uAudioSample2) >> 1;
                            break;
                    case 4: TACCR1 = (uAudioSample1 + uAudioSample2 + uAudioSample2 + uAudioSample2) >> 2;
                            break;
                    case 6: TACCR1 = uAudioSample2;
                            iAudioSampleDifference = (int) uAudioSample2 - (int) uAudioSample1;
                            if (iAudioSampleDifference > 68)
                            {
                                TACTL   &= ~TAIFG;            // clear overflow flag TAIFG IFG
                                TACTL   |= TAIE;              // enable overflow interrupt TAIFG
                                TACCTL1 &= ~CCIE;             // turn off TACCR1 IFG
                            }
    
                            uAudioSample1 = data[lAudioSampleCnt++];
                            uAudioSample2 = data[lAudioSampleCnt];
                            break;
                }
                uAvgCntr += 2;                     // increment averaging counter always by 2
                uAvgCntr = uAvgCntr & 0x06;        // averaging counter range: 0, 2, 4, 6
            }
    
        } else {
            TA0CCR1 = 0;
            lAudioSampleCnt = 0;
        }
    }

     

**Attention** This is a public forum