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.

MSP430G2131: Generating 2 different PWMs using the timer on the MCU

Part Number: MSP430G2131
Other Parts Discussed in Thread: DRV8838, DRV8838EVM

Hi,

I have a question about MSP430G2131 timer. This link 'MSP430 Multiple Time Bases on Single Timer.pdf' mentions “the number of frequencies and duty cycles that can be simultaneously produced on a particular MSP430 device is dependent on the total number of all TxCCRx registers on the device” (for multiple time base method). We are using MSP430G2131 on the DRV8838 EVM. It has 2 TxCCRx registers and hence we assume it can create 2 different frequencies and duty cycles.  

For our project we need to generate:

  1. One PWM with frequency = 20 Hz and Duty Cycle = 20%. We can generate this one correctly.
  2. Another PWM (to drive the motor) with a set frequency preferable in KHz and a varying duty cycle depending on the input from the user.

We are not using the potentiometer on the DRV8838EVM to change the duty cycle. We have SPI communication established between MSP430G2131 and a CH341 chip. So, the duty cycle is changed via a software on PC (sent to the MSP430G2131 via CH341 using SPI).

We are facing a problem generating the PWM to drive the motor. For each frequency, there are few duty cycle values for which the PWM will be generated fine (the duty cycle changes according to the input from the PC). However, for the remaining duty cycle values the PWM will either not be stable with big gaps in between or it will not produce right PWM signal. We have noted these effects in the attached excel file. However, we are not able to understand the reason behind this.

DRV88 PWM.xlsx

Attached is also our CCS code.

#include <msp430.h> 
#include "msp430g2131.h"

//------------------------------------------------------------------------------
// Definitions
//------------------------------------------------------------------------------
#define LED         BIT1                // P1.1
#define PHASE       BIT2                // P1.2
#define DIRECTION   BIT3                // P1.3
#define ADC_VREF    BIT4                // P1.4
#define ENABLE      BIT6                // P2.6


//------------------------------------------------------------------------------
// Global variables
//------------------------------------------------------------------------------
unsigned int i, m_cnt, counter, flag = 0;
unsigned int DutyCycle = 0;                  // PWM duty cycle
unsigned int high, low = 0;

unsigned int master_data[12] ;               // array to store the 12B data from the master
unsigned int slave_data[12] = {0x7E, 0x5D, 0x78, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x00};  // array to store the 12B data from the slave
unsigned int *pslaveArray = slave_data;      // pointer pointing to slave_data array


//------------------------------------------------------------------------------
// Declare functions
//------------------------------------------------------------------------------

/*
 * Function to calculate checksum
 */
unsigned int checksum(unsigned int array[], int size)
{
    unsigned int sum = 0;
    unsigned int j = size;

    for( ; j > 0; j--)
    {
        sum += array[j-1];
    }
    return sum;
}

/*
 * Function to check the master array
 */
void master_check(unsigned int master_array[12])
{
    // Combine both the duty cycle (DC) values from the master as integer values to compare them later
    unsigned int asked_DC = master_array[3] + master_array[2];
    unsigned const int safe_DC = 0x00 + 0x6D;

        // check if the asked DC from the master is less than the limit DC.
//        if(asked_DC <= safe_DC) // Yes then change that value into the slave data array accordingly
//        {
            DutyCycle = asked_DC;
            high = DutyCycle;
            low = 250 - high;
/**        }
        else                    // No then put the limit DC value into the slave data array accordingly
        {
            DutyCycle = safe_DC;
            high = DutyCycle;
            low = 250 - high;
        }
**/
}

/*
 * Function to initialize values
 */
void init(void)
{
    IE1 |= WDTIE;                         // Enable WDT interrupt
    BCSCTL1 = CALBC1_1MHZ;                // Set DCO to 1MHz
    DCOCTL = CALDCO_1MHZ;

    P1SEL = LED + PHASE;
    P1OUT = 0x00;                         // P1.x Reset
    P1DIR = 0x56;

    CCTL0 = CCIS_0 + OUTMOD_4 + CCIE;     // CCR0 toggle, interrupt enabled, CCI0A input on CCI
    CCTL1 = CCIS_0 + OUTMOD_4 + CCIE;
    TACTL = TASSEL_2 + MC_2 + TAIE;       // SMCLK source, continuous mode, int enabled


    ADC10CTL0 = ADC10SHT_2 + ADC10ON; //+ MSC + ADC10IE;     // VCC ref
    ADC10AE0 |= INCH_0;                   // P1.0 ADC option select


    USICTL0 |= USIPE7 + USIPE6 + USIPE5 + USIOE; // Port, SPI slave
    USICTL1 |= USIIE;                     // Counter interrupt, flag remains set
    USICTL0 &= ~USISWRST;                 // USI released for operation
    USISRL = 0xCD;                        // init-load data into the lower byte of the serial register
    USISRH = 0xEF;                        // init-load data into the higher byte of the serial register
    USICNT = USI16B + 16;                 // init-load counter + enable 16-bit shift register mode
}


//------------------------------------------------------------------------------
// Main function
//------------------------------------------------------------------------------
int main(void)
{
  WDTCTL = WDT_MDLY_8;                  // Set Watchdog Timer interval to ~30ms

// __bis_SR_register(LPM0_bits + GIE);    // Enter LPM0 w/ interrupt

  init();

  while(1)
  {
    _BIS_SR(LPM0_bits + GIE);           // Enter LPM0 with interrupts
    P1OUT |= ADC_VREF;                  // Enable poti divider

    ADC10CTL0 |= ENC + ADC10SC;         // Sampling and conversion start
    while (ADC10IFG & ADC10CTL0);       // Wait for conversion complete
    ADC10CTL0 &= ~ADC10IFG;             // Clear converstion flag
    P1OUT &= ~ADC_VREF;                 // Disable poti divider
 //   DutyCycle = 63 - (ADC10MEM>>4);     // Mask upper 6-bits
 //   LED_Blink = 63 - (ADC10MEM>>4);     // Mask upper 6-bits

//    CCR1 = DutyCycle;                   // PWM duty cycle to DRV

/**
    if (DIRECTION & P1IN)               // Forward direction?
    {
        P1OUT |= PHASE;
    }
    else                                // Reverse
    {
        P1OUT &= ~PHASE;
    }
**/
  }
}


// Watchdog Timer interrupt service routine
#pragma vector=WDT_VECTOR
__interrupt void watchdog_timer(void)
{
  _BIC_SR_IRQ(LPM3_bits);                 // Clear LPM0 bits from 0(SR)
}


// USI interrupt service routine
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=USI_VECTOR
__interrupt void universal_serial_interface(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(USI_VECTOR))) universal_serial_interface (void)
#else
#error Compiler not supported!
#endif
{
   if(flag == 0 || ((USISRH == 0x7E) && (USISRL == 0x5D)))
   {
       flag = 1;
       m_cnt = 0;
   }

   if(flag == 1)
   {
       master_data[m_cnt] = USISRH;
       master_data[m_cnt+1] = USISRL;
       m_cnt = m_cnt + 2;
   }

   if (m_cnt >= 10)
   {
       flag = 0;
       if((master_data[0] == 0x7E) && (master_data[1] == 0x5D)) //Check if the master header is right
           master_check(master_data);
   }

  if(counter >= 12)
   {
       counter = 0;
       slave_data[3] = ADC10MEM;           //sampling lower 8 bytes first
       slave_data[4] = ADC10MEM >> 8;      //shift the upper 8 bytes to the left to sample them
      // slave_data[5] = DutyCycle;
      // slave_data[6] = DutyCycle >> 8;
       slave_data[5] = CCR1;
       slave_data[6] = CCR1 >> 8;
   }

  pslaveArray = slave_data + counter;
  USISRL = *(pslaveArray+1);
  USISRH = *(pslaveArray);
  USICNT = USI16B + 16;
  counter = counter + 2;

}


// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A0 (void)             // Generate 20Hz frequency with 20% constant duty cycle
{
    if(CCTL0 & CCI)                           // If output currently high
    {
        CCR0 += 10000;                        // 20% high
    }
    else
    {
        CCR0 += 40000;                        // 80% low
    }
}


// Timer A1 Interrupt Vector (TA0IV) handler
#pragma vector=TIMER0_A1_VECTOR
__interrupt void Timer_A1(void)             // Generate 4KHz frequency for DRV motor
{
    switch( TA0IV )
     {
     case  2:  if(CCTL1 & CCI)                 // If output currently high
               {
                   //CCR1 += 50;                 // 20% high
                   //CCR1 += DutyCycle;
                   CCR1 += high;
               }
               else
               {
                   //CCR1 += 200;                // 80% low
                   //CCR1 += (250 - DutyCycle);
                   CCR1 += low;
               }
              break;
     default: break;
     }
}

Any help is appreciated.

  • This method requires an interrupt (ISR call) for every PWM edge. Thus the interrupt has to finish within the PWM phase period, or the next one will "miss" -- the CCR will be changed after the counter is past the value, and no edge will occur. This limits the shortest phase (high or low) you can manage.

    I usually estimate 20-30 CPU clocks to enter/exit an ISR, plus whatever work the function actually does. (You can probably estimate more precisely using the data in your spreadsheet, but your numbers look about right.)

    The usual solution is to speed up the CPU (DCO) -- 8MHz would probably be enough. The G2131 only has one calibrated frequency (1MHz) which complicates things. If you have a 32kHz crystal on your board, it is possible to generate other clock speed calibrations. Do you have such a crystal?

  • Hi, 

    Thank you for your reply. 

    The datasheet for G2131 states the following:  

    and the CCS header file for the MCU mentions: 

    Is it possible to generate clock upto 16MHz by tweaking the CCS code.

    Or

    Do we need to change the SMCLK source from DCOCLK to XT2CLK/LFXTCLK and attach an external crystal oscillator via the 2 pins stated in the datasheet.

    Thank you. 

  • You can generate a DCO clock (up to 16MHz) by adjusting the DCOCTL and BCSCTL1 registers [Ref User Guide (SLAU144J) Sec 5.3.1/2]. The challenge is that I can't tell you what values to use (no one can). For the G2131, TI measures/computes one set of calibrated CALDCO/CALBC1 values which, if put into the Clock registers, will run the DCO at 1MHz [Ref Data Sheet (SLAS694J) Table 9]. There's no reason you couldn't have more than one (as seen e.g. in the G2553), but TI only provides one for this device. To do this computation, you need some sort of reference signal from outside the chip.

    Computing these constants is illustrated in Example msp430g2xx1_dco_flashcal.c here:

    https://dev.ti.com/tirex/explore/node?node=AAsjUg5xPFRFUbXN9Ehc.Q__IOGqZri__LATEST

    As coded, it supposes that a 32kHz crystal is attached to P2.6/7 and it runs an FLL using Timer Capture from ACLK. (Run time is maybe 1-2 seconds.) It computes the constants and stores them in Flash (Information Segment A), so you only have to do this once.

    A variant of this technique might accept a 32kHz signal on e.g. P1.6 and use Capture from TA0.CCI1B [Ref data sheet Table 16]. A lab signal generator or another MSP430 (suitably programmed) could provide such a signal.

  • Hi, 

    Thank you for your reply. 

    We are changing things a bit. We want to use timer in the UP mode to generate a single PWM and use the timer interrupt to increment a counter variable for some other use.

    However, the value of the variable does not change. Is there any specific condition that needs to be satisfied so that the program goes inside the timer interrupt service routine? 

    Thank you, 

    Piyusha  

  • Do you reach a breakpoint set in the ISR?

    If not, make sure the (relevant) CCIE and GIE are both set.

    If so, make sure your counter is declared "volatile".

    If you can post (or attach) your code we might be able to make a better guess.

  • Hi 

    Apologies. We are still using one timer to generate 2 PWMs. Assuming one of the PWM has frequency = 20 Hz and Duty Cycle = 20%. That means that the period is 50ms (with high time = 10ms. i.e. CCR0 register goes up to 1000 counts). 

    We want to wait for 0.6ms before putting the data from the ADC10_MEM to another variable. So I assuming 0.6ms = 60 counts. However, when we try to do that the ADC only returns a value of 0. 

    Attached is our code. Any help is appreciated. 

    #include <msp430.h> 
    #include "msp430g2131.h"
    
    //------------------------------------------------------------------------------
    // Definitions
    //------------------------------------------------------------------------------
    #define SPEED_REF   BIT0                // P1.0
    #define LED         BIT1                // P1.1
    #define PHASE       BIT2                // P1.2
    #define DIRECTION   BIT3                // P1.3
    #define ADC_VREF    BIT4                // P1.4
    #define ENABLE      BIT6                // P2.6
    
    
    //------------------------------------------------------------------------------
    // Global variables
    //------------------------------------------------------------------------------
    
    
    //unsigned int s_checksum = 0;
    unsigned int i, m_cnt, counter, flag = 0;
    unsigned int cnt = 0;
    unsigned int DutyCycle = 0;                  // PWM duty cycle
    unsigned int ADC_val = 0;
    unsigned int high = 400;
    unsigned int low = 0; // 1600;
    
    unsigned int master_data[12];               // array to store the 12B data from the master
    unsigned int slave_data[12] = {0x7E, 0x5D, 0x78, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x00};  // array to store the 12B data from the slave
    unsigned int *pslaveArray = slave_data;      // pointer pointing to slave_data array
    
    
    //------------------------------------------------------------------------------
    // Declare functions
    //------------------------------------------------------------------------------
    
    /*
     * Function to calculate checksum
     */
    unsigned int checksum(unsigned int array[], int size)
    {
        unsigned int sum = 0;
        unsigned int j = size;
    
        for( ; j > 0; j--)
        {
            sum += array[j-1];
        }
        return sum;
    }
    
    /*
     * Function to check the master array
     */
    void master_check(unsigned int master_array[12])
    {
        // Combine both the duty cycle (DC) values from the master as integer values to compare them later
        unsigned int asked_DC = (master_array[3] << 8) + master_array[2];
        unsigned const int safe_DC = 0x00 + 0x1C;  ////// CHECK IF I NEED TO BIT SHIFT
    
    //   unsigned int crc_code = master_array[10] + master_array[11]; // Add the last 2 elements of master's array  ////// CHECK IF I NEED TO BIT SHIFT
    //   unsigned int m_checksum = checksum(master_array, 10);        // Calculate the value of checksum for the master's array
    
    //    if(m_checksum == crc_code) // if the calculated checksum is the same as the last 2 bytes in the master's array proceed
     //   {
            slave_data[2] = 0x02; //change the board_status to unstable(moving)
    
            // check if the asked DC from the master is less than the limit DC.
    //        if(asked_DC <= safe_DC) // Yes then change that value into the slave data array accordingly
    //        {
                CCTL1 &= ~CCIE;
                TACTL &= ~TAIE;
    
                DutyCycle = asked_DC;
                high = DutyCycle;
                //low = 2000 - high;
    
                CCTL1 |= CCIE;
                TACTL |= TAIE;
    
                /**
                DutyCycle = asked_DC;
                CCR1 = DutyCycle;
                **/
    
    //        }
    //        else                    // No then put the limit DC value into the slave data array accordingly
    //        {
    //            DutyCycle = safe_DC;
              //  high = DutyCycle;
              //  low = 100 - high;
    //            CCR1 = DutyCycle;
    //        }
    //      }
    }
    
    /*
     * Function to initialize values
     */
    void init(void)
    {
        IE1 |= WDTIE;                         // Enable WDT interrupt
        BCSCTL1 = CALBC1_1MHZ;                // Set DCO to 1MHz
        DCOCTL = CALDCO_1MHZ;
    
    //    P1SEL &= ~PHASE;                    // Make sure P1.2 peripheral functionality is disabled
        P1SEL = PHASE + LED;              // Using timer output on pins 1.1 and 1.2
        P1OUT = 0x00;                         // P1.x Reset
    //    P1DIR = 0xF6;                       // P1.3,0 inputs, else outputs
        P1DIR = 0x56;
    
        P2SEL = 0x00;                         // P2.x no options
        P2OUT = 0x00;                         // P2.x Reset
        P2DIR = 0x01;                         // P2.6 (ENX) pin output, else inputs
    
    //    P2SEL = ENABLE;                     // Select secondary functionality on P2.6, tying it to TA0.1
    //    P2OUT = 0x00;                       // P2.x reset
    //    P2DIR = 0xFF;                       // P2.x outputs
    
    //   CCTL0 = OUTMOD_7;
    //   CCTL1 = OUTMOD_7;                     // CCR1 in reset/set mode
    //   CCR0 = 64 - 1;                        // 6-bit PWM period
    //   CCR1 = 13;                            // CCR1 Duty cycle to 20%
    //   TACTL = TASSEL_2 + MC_1;              // SMCLK source, up mode
    
    
        CCTL0 = CCIS_0 + OUTMOD_4 + CCIE;     // CCR0 toggle, interrupt enabled, CCI0A input on CCI
        CCTL1 = CCIS_0 + OUTMOD_4 + CCIE;
        TACTL = TASSEL_2 + MC_2 + TAIE;       // SMCLK source, continuous mode, int enabled
    
    
        ADC10CTL0 = ADC10SHT_2 + ADC10ON; // + ADC10IE;    // VCC ref
        ADC10CTL1 = INCH_0;                            // Input A0
        ADC10AE0 |= 0x01;                              // PA.0 ADC option select
        //ADC10AE0 |= INCH_0;                            // Input A0
    
    
        USICTL0 |= USIPE7 + USIPE6 + USIPE5 + USIOE; // Port, SPI slave
        USICTL1 |= USIIE;                     // Counter interrupt, flag remains set
        USICTL0 &= ~USISWRST;                 // USI released for operation
        USISRL = 0xCD;                        // init-load data into the lower byte of the serial register
        USISRH = 0xEF;                        // init-load data into the higher byte of the serial register
        USICNT = USI16B + 16;                 // init-load counter + enable 16-bit shift register mode
    }
    
    
    //------------------------------------------------------------------------------
    // Main function
    //------------------------------------------------------------------------------
    int main(void)
    {
      WDTCTL = WDT_MDLY_8;                  // Set Watchdog Timer interval to ~30ms
    
    // __bis_SR_register(LPM0_bits + GIE);    // Enter LPM0 w/ interrupt
    
      init();
    
      while(1)
      {
        _BIS_SR(LPM0_bits + GIE);           // Enter LPM0 with interrupts
    //    P1OUT |= ADC_VREF;                  // Enable poti divider
    
        ADC10CTL0 |= ENC + ADC10SC;         // Sampling and conversion start
        while (ADC10IFG & ADC10CTL0);       // Wait for conversion complete
        ADC10CTL0 &= ~ADC10IFG;             // Clear converstion flag
    
    //    P1OUT &= ~ADC_VREF;                 // Disable poti divider
    
    //   DutyCycle = 63 - (ADC10MEM>>4);     // Mask upper 6-bits
    //   CCR1 = DutyCycle;                   // PWM duty cycle to DRV
    
    
        if (DIRECTION & P1IN)               // Forward direction?
        {
            P1OUT |= ENABLE;
        }
        else                                // Reverse
        {
            P1OUT &= ~ENABLE;
        }
    
        if (cnt == 1)
        {
            //if (CCR0 == (CCR0 + 60))
            //if (TAR == (CCR0 = 60))
            //{
            //    ADC_val == ADC10MEM;
            //}
            slave_data[11] = low;
            if ((low - slave_data[11]) == 60)
            {
                ADC_val = ADC10MEM;
                cnt = 0;
            }
        }
      }
    }
    
    
    // Watchdog Timer interrupt service routine
    #pragma vector=WDT_VECTOR
    __interrupt void watchdog_timer(void)
    {
      _BIC_SR_IRQ(LPM3_bits);                 // Clear LPM0 bits from 0(SR)
    }
    
    
    // USI interrupt service routine
    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector=USI_VECTOR
    __interrupt void universal_serial_interface(void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(USI_VECTOR))) universal_serial_interface (void)
    #else
    #error Compiler not supported!
    #endif
    {
       if(flag == 0 || ((USISRH == 0x7E) && (USISRL == 0x5D)))
       {
           flag = 1;
           m_cnt = 0;
       }
    
       if(flag == 1)
       {
           master_data[m_cnt] = USISRH;
           master_data[m_cnt+1] = USISRL;
           m_cnt = m_cnt + 2;
       }
    
       if (m_cnt >= 10)
       {
           flag = 0;
           if((master_data[0] == 0x7E) && (master_data[1] == 0x5D)) //Check if the master header is right
               master_check(master_data);
       }
    
      if(counter >= 12)
       {
           counter = 0;
           //slave_data[3] = ADC10MEM;           //sampling lower 8 bytes first
           //slave_data[4] = ADC10MEM >> 8;      //shift the upper 8 bytes to the left to sample them
           slave_data[3] = ADC_val;           //sampling lower 8 bytes first
           slave_data[4] = ADC_val >> 8;
          // slave_data[5] = DutyCycle;
          // slave_data[6] = DutyCycle >> 8;
           //slave_data[5] = CCR1;
           //slave_data[6] = CCR1 >> 8;
          // s_checksum = checksum(slave_data, 10);
          // slave_data[10] = s_checksum;
          // slave_data[11] = s_checksum >> 8;
       }
    
      pslaveArray = slave_data + counter;
      //USISRL = *pslaveArray;
      //USISRH = *(pslaveArray+1);
      USISRL = *(pslaveArray+1);
      USISRH = *(pslaveArray);
      USICNT = USI16B + 16;
      counter = counter + 2;
    
    }
    
    
    // Timer A0 interrupt service routine
    #pragma vector=TIMER0_A0_VECTOR
    __interrupt void Timer_A0 (void)             // Generate 20Hz frequency with 20% constant duty cycle for sensor
    {
        if(CCTL0 & CCI)                           // If output currently high
        {
            CCR0 += 10000;                        // 20% high
            cnt = 1;
            low = low + 1;
    //        if (CCR0 == (CCR0 + 60))
    //        {
    //            ADC_val == ADC10MEM;
    //        }
        }
        else
        {
            CCR0 += 40000;                        // 80% low
        }
    
        //low = low + 1;
    }
    
    
    // Timer A1 Interrupt Vector (TA0IV) handler
    #pragma vector=TIMER0_A1_VECTOR
    __interrupt void Timer_A1(void)             // Generate 500Hz frequency for DRV motor
    {
        switch( TA0IV )
         {
         case  2:  if(CCTL1 & CCI)                 // If output currently high
                   {
                       CCR1 += high;
                   }
                   else
                   {
                       low = 2000 - high;
                       CCR1 += low;
                   }
                  break;
         default: break;
         }
    
        //low = low + 1;
    }
    
    /**
    // ADC10 interrupt service routine
    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector=ADC10_VECTOR
    __interrupt void ADC10_ISR (void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(ADC10_VECTOR))) ADC10_ISR (void)
    #else
    #error Compiler not supported!
    #endif
    {
        //ADC_val = ADC10MEM;
        __bic_SR_register_on_exit(CPUOFF);        // Clear CPUOFF bit from 0(SR)
    }
    **/
    

  • Are you looking at ADC_val or ADC10MEM? 

    This looks a bit odd:

    >slave_data[11] = low;
    >if ((low - slave_data[11]) == 60)

    It would seem that (low-slave_data[11])  would always be 0 (or maybe 1) here. I'm not quite sure what you meant to do, but this would prevent reading ADC10MEM.

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

    >    while (ADC10IFG & ADC10CTL0);       // Wait for conversion complete

    This test is backwards, so (the way the IFG works) it will either (a) not wait at all or (b) wait forever. This will usually "work by accident" but it will get you in trouble someday. Try:

    >    while (!(ADC10IFG & ADC10CTL0));       // Wait for conversion complete

**Attention** This is a public forum