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.

MSP430F5438A Sine Wave Generator Using Timer B

Other Parts Discussed in Thread: MSP430F5438A

Hey all,
I have tried getting this code to work and filter it so it is a smooth sine wave using an active 2nd order Butterworth filter or a passive RC filter and I get the attached photo. My issues is that my math is not acting correctly. I have been able to get a constant 50Hz signal out but cannot get a smooth sine wave to exist. When I have been using a look-up table the frequency will not stay constant so right now the array I have set is only at one constant output value.

Here is the math I have been using:

Output Freq= Clock Frequency /(PWM clock ticks * size of array)

PWM Frequency = (Clock frequency / PWM clock ticks)

Using either of the filters I have been able to get this at a constant Frequency but unable to get a smooth wave out. Here is the code I have been using.

/*
* Simple DAC for the MSP430F5438A
* Author: Alexander Coffin
* Date:
* Code set at 650 to allow a constant 50Hz output. Variation is done in PWM output values
*
************************************************************************/

#include <msp430f5438a.h>

unsigned char counter; // Current location in wave array
// Wave range is from 0-255. 255 will give an output around 1v pk-pk(all above 0)
//100 gives 250mv pk-pk(split between both)
//150:
//200: 800mv pk-pk(half and half)
unsigned char wave[4] =
{
100, 100, 100, 100
};


unsigned int i; // Used for 'for' loops.

void main(void)
{
WDTCTL = WDTPW + WDTHOLD; // Stop WDT

P4DIR |= BIT1; // P4.1 output
counter = 0; // Reset counter

// Initialize Timer
TB0CCTL0 = CCIE; // CCR0 interrupt enabled
TB0CCTL1 = CCIE; // CCR1 interrupt enabled
TB0CCR0 = 650; // Set PWM period to 650 clock ticks
TB0CCR1 = wave[counter]; // Set first duty cycle value
TB0CTL = TBSSEL_1 + MC_1 + TBIE + TBCLR; // ACLK, upmode, enable interrupt, clear TB1R

_BIS_SR(LPM0_bits + GIE); // Enter LPM0 w/ interrupt
}

/**
* TimerB0 interrupt service routine
**/
#pragma vector=TIMER0_B0_VECTOR
__interrupt void TIMER0_B0_ISR(void)
{
P4OUT |= BIT1; // Set P4.0

TB0CCR1 = wave[counter]; // Set next duty cycle value
counter += 1; // Add Offset to CCR0
if ( counter == 4) // If counter is at the end of the array
{
counter = 0; // Reset counter
}
}

/**
* TimerA1 Interrupt Vector (TAIV) handler
**/
#pragma vector=TIMER0_B1_VECTOR
__interrupt void TIMER0_B1_ISR(void)
{
switch( TBIV )
{
case 2: // CCR1 interrupt
P4OUT &= ~BIT1; // Clear P4.0 to determine duty cycle.
break;
default:
break;
}
}

If this code can be configured to allow for a steady wave to be produced I need to know how a long with how I could possibly build in a DC step.

Thanks!

  • I don't think the code can do what you want.

    PWM means you're '1' for some time and '0' for the rest of one PWM cycle.

    However, what you code does is to set the cycle time to 651(!) timer ticks. So the timer runs from 0 to 100, triggers TIMER0_B1_ISR which will invert the port pin, then counts to 650, triggers TIMER0_B0_ISR, which changes the TBCCR1 settign to (currently) the same as before. Then counts to 0, triggering TIMER0_B1_ISR again, but this time you don't handle the itnerrupt (it is case 14 or so, timer overflow, since you did set TBIE). When the timer continued to 100 again, and the output is toggled.

    However, if you do it this way, the port pin is only toggled with a duty cycle of 50% (toggled each time the timer reaches 100 on its way to 650). With 651*2 being the cycle time. Changing the value for TBCCR1 only shifts the offset to TBR==0 (the 'phase') but has no effect on the duty cycle and therefore not on the output signal.

    To change the duty cycle, you'll have to change the output on two points of a cycle. E.g. reset it on the B1_ISR and set it in the B0_ISR. Then TBCCR1 determines the amount of ticks the output is on during a full cycle of 651 ticks. However, the ISR latency will not allow for high precision and may also introduce quirks when you get near 0 or 100% duty cycle.
    Hint: read abotu the OUTMODE bits. The hardware PWM does this in hardware without any ISR. But you should change the DC then inside the B0_ISR to synchronize it.

    Anyway: PWM output is always rectangular. You can filter it to a sinus-like signal by a low pass with ~ 1/2 of the PWM frequency, or to a DC voltage (equivalent to the Duty cycle) with a low-pass with a very low cutoff frequency. But then you cannot change the output signal fast, as these changes will be low-passed too.

    If you want to 'form' the output wave by constantly changing the PWM DC, you'll need a PWM frequency much higher than the output frequency (at least factor 4, better more) and a low-pass filter with a cutoff frequency slightly above the desired output frequency.

  • Alexander Coffin said:
    unsigned char wave[4] =
    {
    100, 100, 100, 100
    };

     Hi Alexander, if you wish to generate a sinusoidal signal I think you need some sinusoidal shaped samples, more than 4 and not constant.

     

  • Hi Alexander,

     

    I'm attaching a couple of examples I tested some time ago.

    - One of them uses a 2 timers, one used to generate the PWM, and the other as a time base which triggers the DMA automatically to send the next sample, so there's no CPU intervention

    - The other one uses the same timer to generate the PWM and update the duty cycle on every period interrupt

     

    I'm also attaching a picture of the sine wave at 100Hz using an external RC filter. Note that you can achieve better resolutions and faster frequencies depending on the number of steps in the sine wave and the PWM frequency.

     

    I hope you find it useful.

    Regards,

    Luis R

     

    /** \file msp430_test_sine_wave_pwm.c
     *  File performing Sine Wave generator using PWM
     *  This test generates a sine wave using the TimerB PWM, with a look-up table
     *  in Flash accessed by DMA
     *
      \section Pseudo-algorithm
      \verbatim
        Init_MCU
          Init_PowerManagement
          Init_Ports
          Init_Clocks
          Init_Timer_PWM
          Init_Timer_DMA
          Init_DMA
        Start transfers
        Enable_Interrupt
        LPM3
    
        IF Timer_Int
    	  DMA updates duty cycle
    
      \endverbatim
      \section Schematic
      \verbatim
                    MSP430F5438A
                 -----------------
             /|\|              XIN|-
              | |                 |
              --|RST          XOUT|-
                |       P4.1/PB0.1|--> Sine Wave
     \endverbatim
     *
     * \author L.Reynoso \n
     *         Texas Instruments Inc.
     * \date September 2010
     * \note Built with IAR Embedded Workbench Version: 5.10.4
     */
    
    #include "msp430x54xA.h"
    
    const unsigned char sine_table[] = {
    255, 254, 246, 234, 219, 199, 177, 153, 128, 103, 79, 57, 37, 22, 10, 2, 1, 2,
    10, 22, 37, 57, 79, 103, 128, 153, 177, 199, 219, 234, 246, 255
    };
    
    #define SINE_FREQ 100                    // Select Sine wave frequency
    #define SAMPLE_FREQ (32768/32)          // ACLK/#samples
    #define DELTA (SAMPLE_FREQ/SINE_FREQ)   // TimerA frequency
    
    
    int main( void )
    {
      WDTCTL = WDTPW + WDTHOLD;           // Stop Watchdog
    
      PMMCTL0_H = 0xA5;                   // Unlock password to access PMM
      SVSMHCTL = 0x00;                    // No high-side supply monitoring
      SVSMLCTL = 0x00;                    // No low-side supply monitoring
      PMMCTL0_H = 0x00;                   // Lock access to PMM
    
      PADIR = 0xFFFF; PAOUT = 0;          // Init all ports as output low
      P4SEL = BIT1;                       // P4.1 = TB0.1
      PBDIR = 0xFFFF; PBOUT = 0;
      PCDIR = 0xFFFF; PCOUT = 0;
      PDDIR = 0xFFFF; PDOUT = 0;
      PEDIR = 0xFFFF; PEOUT = 0;
      PFDIR = 0xFFFF; PFOUT = 0;
      PJDIR = 0xFFFF; PJOUT = 0;
    
        // Modify DCO initialization according to the desired sine wave and resolution
      UCSCTL3 |= SELREF_2;                  // Set DCO FLL reference = REFO
      UCSCTL1 = DCORSEL_0;                  // Select DCO range 1MHz operation
      UCSCTL2 = 31;                        // FLLDx=0, DCO = 1MHz (*31+1)
      UCSCTL4 = SELA__REFOCLK + SELS__DCOCLK + SELM__DCOCLK; /* ACLK=REFO=32.768Khz
                                                                 SMCLK=MCLK=DCO=1.048Mhz */
      __delay_cycles(50000);                // Delay for FLL lock
    
    
      TB0CCR0 = 255;                        // Period = 1Mhz/256 = ~3.9Khz (8-bit DAC)
      TB0CCTL1 = CLLD_1 + OUTMOD_7;         // Use TB0.1 to output Sine Wave
      TB0CCR1 = sine_table [0];             // Load first sample value
      TB0CTL = TBSSEL_2 + MC_1;             // Timer uses SMCLK in up mode
    
      DMA0SA = (unsigned short) &sine_table;  // Src address = Sine table
      DMA0DA = TB0CCR1_;                      // Dst address = TB0.1
      DMA0SZ = sizeof(sine_table);            // Transfer size = Sine table
      DMACTL0 = DMA0TSEL_1;                   // TA0CCR0_CCIFG triggers DMA0
      /* Repeated single transfer mode, increment src, access as bytes, enable DMA*/
      DMA0CTL = DMADT_4 + DMASRCINCR_3 + DMADSTBYTE + DMASRCBYTE + DMAEN;
    
      TA0CCR0 = (DELTA-1);                    // Period = 32.768Khz/(32samples*SineFreq)
      TA0CTL = TASSEL_1 + MC_1;               // Timer uses ACLK in up mode
    
      __bis_SR_register(LPM3_bits);           // Enter LPM0, interrupts enabled
      __no_operation();                         // For debugger
    
      return 0;
    }
    
    

    /** \file msp430_test_sine_wave_pwm_sw.c
     *  File performing a Software based Sine Wave generator using PWM
     *  This test generates a sine wave using the TimerB PWM, with a look-up table
     *  in Flash accessed by ISR
     *
      \section Pseudo-algorithm
      \verbatim
        Init_MCU
          Init_PowerManagement
          Init_Ports
          Init_Clocks
          Init_Timer_PWM
        Start transfers
        Enable_Interrupts
        IF Timer_Int
    	  Update Duty cycle
    	  LPM3
        ELSE
         LPM3
      \endverbatim
      \section Schematic
      \verbatim
                    MSP430F5438A
                 -----------------
             /|\|              XIN|-
              | |                 |
              --|RST          XOUT|-
                |       P4.1/PB0.1|--> Sine Wave
     \endverbatim
     *
     * \author L.Reynoso \n
     *         Texas Instruments Inc.
     * \date September 2010
     * \note Built with IAR Embedded Workbench Version: 5.10.4
     */
    
    #include "msp430x54xA.h"
    
    const unsigned char sine_table[] = {
    255, 254, 246, 234, 219, 199, 177, 153, 128, 103, 79, 57, 37, 22, 10, 2, 1, 2,
    10, 22, 37, 57, 79, 103, 128, 153, 177, 199, 219, 234, 246, 255
    };
    
    #define SINE_FREQ 100                             // Select Sine wave frequency
    #define SAMPLE_FREQ (1048576/256)                 // SMCLK/TBCCR0
    #define DELTA (0x2000/(SAMPLE_FREQ/SINE_FREQ))    //Pointer increment
    
    int main( void )
    {
      WDTCTL = WDTPW + WDTHOLD;           // Stop Watchdog
    
      PMMCTL0_H = 0xA5;                   // Unlock password to access PMM
      SVSMHCTL = 0x00;                    // No high-side supply monitoring
      SVSMLCTL = 0x00;                    // No low-side supply monitoring
      PMMCTL0_H = 0x00;                   // Lock access to PMM
    
      PADIR = 0xFFFF; PAOUT = 0;          // Init all ports as output low
      P4SEL = BIT1;                       // P4.1 = TB0.1
      PBDIR = 0xFFFF; PBOUT = 0;
      PCDIR = 0xFFFF; PCOUT = 0;
      PDDIR = 0xFFFF; PDOUT = 0;
      PEDIR = 0xFFFF; PEOUT = 0;
      PFDIR = 0xFFFF; PFOUT = 0;
      PJDIR = 0xFFFF; PJOUT = 0;
    
        // Modify DCO initialization according to the desired sine wave and resolution
      UCSCTL3 |= SELREF_2;                  // Set DCO FLL reference = REFO
      UCSCTL1 = DCORSEL_0;                  // Select DCO range 1MHz operation
      UCSCTL2 = 31;                        // FLLDx=0, DCO = 1MHz (*31+1)
      UCSCTL4 = SELA__REFOCLK + SELS__DCOCLK + SELM__DCOCLK; /* ACLK=REFO=32.768Khz
                                                                 SMCLK=MCLK=DCO=1.048Mhz */
      __delay_cycles(50000);                // Delay for FLL lock
    
      TB0CCR0 = 255;                        // Period = 1Mhz/256 = ~4.1Khz (8-bit DAC)
      TB0CCTL1 = CLLD_1 + OUTMOD_7;         // Use TB0.1 to output Sine Wave
      TB0CCTL0 = CCIE;                      // Enable Interrupt on Period
      TB0CCR1 = sine_table [0];             // Load first sample value
      TB0CTL = TBSSEL__SMCLK + MC_1;        // Timer uses SMCLK in up mode
    
      __bis_SR_register(LPM3_bits + GIE);       // Enter LPM3, interrupts enabled
      __no_operation();                         // For debugger
    
      return 0;
    }
    
    // Timer B0 interrupt service routine
    #pragma vector=TIMER0_B0_VECTOR
    __interrupt void TIMER0_B0_ISR(void)
    {
      static unsigned short counter =0;
    
      counter += DELTA;
      TB0CCR1 = sine_table[(counter&0x1F00)>>8];
    }
    

  • Hello Sir,

    Can u plz elaborate on the working of your second code. I am an undergraduate student and is a complete newbee to all this stuff. Moreover sir can u tell me how do i modify the code so as to generate sine wave of some other frequency,

    Thank u in advance. :)

  • Hi Amruta,

    The basic idea is that you have a single sine cycle and the frequency of the sine wave will depend on how fast/slow you send each value.

    I.e.

    - if I have only 8 values: 0, 50, 100, 50, 0, -50, -100, -50

    - If I send each value at 1ms (1Khz), the whole signal will be sent in 8ms (125Hz)

    - If I send each value at half the period = 0.5ms (2Khz), the signal will be sent faster in 4ms (250Hz)

    - But if I send it at twice the period=2ms, the signal will be twice as slow (62.5Hz)

    - On the other side, if I still send every 1ms, but I send each value twice, then the signal will be sent in 16ms (62.5Hz)

    - And if I skip every other sample, the signal will be sent in 4ms (250Hz)

    So, by adjusting the DELTA (which is how fast you go from one value to the next), you can change the frequency.

    You can find more info here:

    http://www.ti.com/lit/pdf/slaa116

    http://www.ti.com/lit/pdf/slaa497 

    http://www.msp430launchpad.com/2011/06/simple-launchpad-dac.html 

    Regards,

    Luis R

  • You can get pretty close to shaping a square-wave in to what resembles a sine, by 3 orders RC filtering.
    But freq can not be changed as Resistors and Caps are fine tuned to mangle the square wave in to sine


    But as you have PWM (timer compare) and also PDM (SPI out) to your disposable, you would only need one
    simple RC filter so put the msp430 to use.

  • Dear Sir,

    Thank you so much for your prompt and detailed reply. This post made my concepts very clear
    .

    I would like to know sir which kind of low pass filter (I mean what values of Resistance and Capacitance should i use externally to port 4.0 so as to get a smooth sine wave as shown in your the results of your previous post.)

    Thanking you in anticipation.

    Regards,

    Amruta

  • Sir,

    Can u explain what exactly the ISR is doing. Thank u very much. :)

**Attention** This is a public forum