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.

Issue using PWM Timer output to create a sine wave...

I am working with a customer that is trying to use App Note SLAA116 - "Using PWM Timer_B as a DAC" to create a sine wave output implemented in an MSP430 timer.  He is targeting an MSP430F2xxx family device. 

Here is the customers code for a function he is calling to attempt to generate a sine wave:

void generate_sine (uint16_t periods)
{
 uint16_t p_table_ptr;

   
    // Clear TimerA
    TACTL |= TACLR;
   
    // TimerA Setup:
    // SMCLK Source | Divided by 0 | Count Up Mode
    TACTL = TASSEL_2 | MC_1;

    // Load timer to set period of PWM to 256 counts (8-bits)
    TACCR0 = 0xff;

    // Load first table value
    TACCR1 = sine_table[0];
   
    // Set CCR0 in compare mode and enable interrupt
    TACCTL0 = CCIE;
   
    // Set CCR1 in compare mode, output mode 7 (reset/set)
    TACCTL1 = OUTMOD_7;

 
 while (periods)
 {

  for (p_table_ptr = 1; p_table_ptr < SINE_TABLE_SIZE; ++p_table_ptr)
  {
   // Turn off CPU until timer interrupt
   LPM0;

           // Load next table value
           //TACCR1 = sine_table[p_table_ptr];
           TACCR1 = 31 - p_table_ptr;


  }   

  --periods;
 }

    // TimerA OFF
    TACCTL1 = 0;
    TACCTL0 = 0;
    TACTL = 0;
 

}

Here is the customer's comments on the problem he is seeing with the above code:

"I’m just decreasing the pulse widths as I go through the for loop.  What I found is that any time I decrease the pulse widths and get to around a value of 20-25 (to load into TACCR1) I get a full-period pulse.  So, if you can see the output on the pin you would see decreasing pulse widths until pulse number 7, which would be a max-width pulse.  Then the pulse widths continue decreasing as they should.  This does not occur if I increase pulse-widths through the same range of values.

 

For added reference, here is the main program where he is calling the above function:

 

void main (void)
{

    // Stop WDT
    WDTCTL = WDTPW + WDTHOLD;

    // External LF crystal with 10pF loading
    BCSCTL3 = XCAP_2;
    BCSCTL1 = XT2OFF;

    // Set FLASH-writes timing generator ->must be in range ~257kHz - ~476kHz
    // Source = MCLK / 3 = 333kHz
    FCTL2 = FWKEY | FSSEL_1 | FN1;
   
    for (volatile int cnt=0; cnt<0xfff; cnt++)
    {
        // Delay for XTAL stabilization
    }

    // Clear oscillator fault flag
    IFG1 = 0;

    // Perform initial calibration of DCO
    set_dco();

    // Setup WDT as interval timer
    // sourced from ACLK (32kHz)
    WDTCTL = WDT_ADLY_250;

    // IO config
    P1OUT = 0; // All outs are zero
    P2OUT = 0; // All outs are zero
    P1DIR = 0xFF; // p1.x Direction - output
    P2DIR = 0xFF; // p2.x Direction - output

    //Set P1.2 as TA1 output
    P1SEL |= BIT2;

    // Enable interrupts
    _BIS_SR(GIE);


    // Enable WDT interrupt
    IE1 |= WDTIE;


    for(;;)
    {
  // Sleep until WDT expires
  LPM3;

  //if (hap_timer == 0)
          //{
               generate_sine(1);
               //hap_timer = rand() % 40;
          //}
          //else
          //{
               //--hap_timer;
          //}

    } // end for(;;)

}

  • Kevin Ross said:
    I decrease the pulse widths and get to around a value of 20-25 (to load into TACCR1) I get a full-period pulse. 

    That's a common pitfall when changing the PWM duty cycle.

    The problem is that when the new value is written to TACCR1, TAR has already counted past 25. the CCRs will trigger on the exact value and not for greater or equal. So it won't trigger at this moment, nor will it trigger at the old threshold, which has just been overwritten. It will tigger next time when one complete cycle has passed and TAR reaches he new threshold again.

    In this case, entering the ISR and changing the value takes as long as 20..25 clock cycles.

    To prevent this, you should update the CCRx value on CCRX interrupt, not CCR0 or Timer Overflow. This will still ignore changes which are too close, but then it won't hurt but just delay the change by one cycle.

    Changing from near 100% to near 0% wills till create an oversized cycle, if you didn't manage to do the update before the timer gets past the new threshold. But in this case the difference of the original (near 100%) and the oversized (100%+x) isn't as devastating as it is when you have a low value and get an oversized spike before changing to an even lower threshold..

    Another possibility is to use the DMA. Arm the DMA with the new value and trigger it by the TA0CCR1 interrupt (which then cannot trigger an ISR, os only DAM OR ISR are possible, depending on CCRIE bit - but you can use the DMA interrupt instead which will happen 4 MCLK cycles later then). This way you will have the new value written within 4 MCLK cycles after CCR1IFG. It's possible that you'll need to reset CCR0IFG manually to arm the DMA, as it isn't cleared by an ISR entry (since there is no ISR called).

    This way you cannot use 100% duty cycle (CCR1 > CCR0), as this setting means that no CCR1 interrupt is ever triggered, blocking further changes. In this case, however, you can simply manually write the new value without DMA or synchronisation with the current timer, as it makes no difference anyway.

  • This post answered my customer's question - again; thanks so much for the help!  Note from customer:

    Got it Kevin!  Needed to use the CCRx interrupt instead of CCR0.

     

    Thanks for the help!

**Attention** This is a public forum