Jump to content
43oh

2 PWM when you only have 2 CCR (MSP430G2211)


Recommended Posts

No, it wasn't that one but thanks for pointing it out. Three was a post on Hackaday about a clock that was built, here's the code that person did:

/**********************************************
 Chronutator clock project using MSP430G2211
 Copyright: Doug Paradis - 2010
 All rights reserved
 This code may be used for private use, as long
 as, copyright notice retained.

 ACLK  (pin 2) --> crystal osc output (pin labeled P1.0)
                   Use when troubleshooting on Launchpad.
                   Commented out in this listing.
 TA0.0 (pin 3) --> PWM for minute meter (pin labeled P1.1)
 TA0.1 (pin 4) --> PWM for hour meter   (pin labeled P1.2)
 P1.4  (pin 6) --> minute setting button (inc one min per push)
 P1.5  (pin 7) --> hour setting button (inc one hr per push)
 RST   (pin 10) --> reset
 Xin, Xout (pins 13,12) --> 32.768 KHz crystal

 P1.3, P1.6, P1.7 (pins 5,8,9) --> not used

 compiled using IAR Embedded Workbench
**********************************************/

#include  "msp430.h"

unsigned char cntr = 0;                              // sec counter
unsigned char cntr_trip_pt = 60;                     // change every 1 sec
unsigned char debounceFlg = 0;                       // flag for debouncing

// min
unsigned char mflg = 0;                             // minute flag
unsigned int m_period = 10000;                      // minute meter PWM period (number of counts of SMCLK)
unsigned int m_on = 5;                              // minute meter PWM setting 
unsigned char m_cntr = 0;                           // min counter
unsigned int m_zero = 5;                            // minute meter PWM zero setting  (can't actually be zero)
//  calibration array for minute meter (set every 5 min mark) -- values should be divisable by 5
//  min meter             5   10  15  20  25  30  35  40  45  50  55  60 
unsigned int m_cal[12] ={640,690,740,715,655,620,635,625,600,610,620,790}; // divisible by 5 
unsigned char m_index = 0;
unsigned char m_tick = 0;

// hour                                             -- see comments above for items below --
unsigned char hflg = 0;
unsigned int h_period = 10000;
unsigned int h_on = 5;                              // hour meter PWM setting  
unsigned char h_cntr = 0;    
unsigned int h_zero = 5;                            // (can't actually be zero)
//  calibration array for hour meter (set for each hour) -- values should be divisable by 12
//   hr mtr               1   2   3   4   5   6   7   8   9   10  11  12
unsigned int h_cal[12] ={744,796,708,708,760,720,708,768,684,684,780,732};   // divisible by 12 
unsigned char h_tick = 0;
unsigned char h_index = 0;

void one_min(void);
void debounce(char button);

void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;                        // Stop watchdog timer
  BCSCTL1 = RSEL0 + RSEL3;                         // Select DCOCLK freq range of ~2.3 MHz (get rid of meter jitter)
  BCSCTL2 = DIVS_2 ;                               // MCLK = DCOCLK, SMCLK = DCOCLK/8
  // crystal Cload capacitor adjustment - only one line of next four should be uncommented
  // BCSCTL3 = LFXT1S_0 + XCAP_3;                     // 32768KHz crystal, 12.5 pF
  // BCSCTL3 = LFXT1S_0 + XCAP_2;                     // 32768KHz crystal, 10 pF
     BCSCTL3 = LFXT1S_0 + XCAP_1;                     // 32768KHz crystal, 6 pF
  // BCSCTL3 = LFXT1S_0 + XCAP_0;                     // 32768KHz crystal, 1 pF
  WDTCTL = WDTPW + WDTTMSEL + WDTCNTCL + WDTSSEL;  // WDT inv mode, ACK clk,
                                                   // 1000mS interval (assume 32KHz crystal)
                                                   // equivalent to "WDT_ADLY_1000" defined in h file
  IE1 |= WDTIE;                                    // Enable WDT interrupt
  TACTL = TASSEL_2 + MC_2;                         // TA clock = SMCLK, mode control = continuous
  TACCTL0 = OUTMOD_4 + CCIE;                       // output mode = toggle, interrupt enabled
  TACCTL1 = OUTMOD_1 + CCIE;                       // output mode = set, interrupt enabled
  P1SEL =  BIT1 + BIT2;                            //  P1.1 to TA0.0, P1.2 to TA0.1
  P1DIR =  BIT1 + BIT2;                            // P1.1 (min) and P1.2 (hr) to output direction
  
  // P1DIR =  BIT0 + BIT1 + BIT2;                  // P1.0 (flash led when using Launchpad), 
                                                   // P1.5 (min) and P1.6 (hr) to output direction
   
  P1IE = BIT4 + BIT5;                              // Enable Port 1 interrupt for P1.4 and P1.5
  //P1IES = BIT4 + BIT5;                            // Interupt edge select, high-low (falling edge)
  P1OUT = BIT4 + BIT5;                             // Time chg buttons set to high
  P1REN = BIT4 + BIT5;                             // pull up resistors enable on time chg button pis

  TACCR0 = m_on;
  TACCR1 = h_on;

  _BIS_SR(LPM0_bits + GIE);                        // Enter LPM0 w/interrupt
}
// TimerA CCR0 interrupt service routine
#pragma vector=TIMERA0_VECTOR
__interrupt void timer_A0(void)
{
  if (mflg == 0)
  {
    TACCR0 = TAR + m_on;
    mflg = 1;
  }
  else
  {
    TACCR0 = TAR + (m_period - m_on);
    mflg = 0;
  }
}
// TimerA CCR1 interrupt service routine
#pragma vector=TIMERA1_VECTOR
__interrupt void timer_A1(void)
{
  if (hflg == 0)
  {
    TACCTL1 = OUTMOD_5 + CCIE;                     // output mode = reset, interrupt enabled
    TACCR1 = TAR + h_on;
    hflg = 1;
  }
  else
  {
    TACCTL1 = OUTMOD_1 + CCIE;                     // output mode = set, interrupt enabled
    TACCR1 = TAR + (h_period - h_on);
    hflg = 0;
  }
}
// Watchdog Timer interrupt service routine
// interrupt occurs on both leading and falling edge
#pragma vector=WDT_VECTOR
__interrupt void watchdog_timer(void)
{
 
  if (debounceFlg == 0)
  {
    // P1OUT ^= BIT0;                     // Toggle P1.0 using exclusive-OR (Use when monitoring ACLK)
    
    if (cntr == cntr_trip_pt)          // change pwm duty cycle
    {
       one_min();  
    }
    cntr++;
  } 
  else                                  // part of debounce routine 
  {
    IFG1 &= ~WDTIFG;                    // clear WDT timer+ interrupt pending (do when changing WDT settings)
    WDTCTL = WDT_ADLY_1000;             // Set Watchdog Timer delay to 1000 mS
    // IE1 |= WDTIE;                      // Enable WDT interrupt
    P1IFG = 0;                          // clear all p1 interrupts
    P1IE |= BIT4 + BIT5;                // Enable Port 1 interrupt for P1.4 and P1.5
    debounceFlg = 0;
  }
}
// Port 1 interrupt service routine
#pragma vector=PORT1_VECTOR
__interrupt void port_1 (void)
{
  // P1IFG &= ~BIT0;                      // clear any interrupt on P1.0 (only needed if using Launchpad led)
  switch (P1IFG) {
  case BIT4:                           // P1.4 (increment mins)
    cntr = 0;                          // set seconds to 0
//    for (int j = 1; j <= 4; j++)      // inc 4 mins  Uncomment these 4 lines when tuning m_cal            
//    {
//      one_min();
//    }  
    one_min();                         // increment 1 min
    debounce(BIT4);
    break;

  case BIT5:                           // P1.5 (increment hrs)
    // cntr = 0;                       // set seconds to 0
    for (int j = 1; j <= 60; j++)      // inc 1 hr                
    {
      one_min();
    }  
    debounce(BIT5);                    
    break;
  default:
    P1IFG = 0;                         // clear all p1 interrupts (note: pushing both buttons at same
                                       // time will end up here)
  }
}

void one_min (void)
{
      m_tick++;                                      // subdivsion between 2 - 5 min marks on min meter
      m_cntr++;                                      // number of minutes in this hour
      m_on += m_cal[m_index] / 5;                    // increase 1 min
      if (m_tick == 5)                               // is min meter at a 5 min mark on min meter?
      {  
        m_index++;                                   // increase index in m_cal array
        m_tick = 0;                                  // at a 5 min mark on min meter reset m_tick
        h_tick++;
        h_on += h_cal[h_index] / 12;                 // move hr meter one subdivision (12 per hr) between 2 hr marks
      }  
      if (h_tick == 12)                              // has hr meter reached an hr mark?
      {
        h_index++;                                   // increment the number of hours
        h_tick = 0;                                  // reset subdivisions of min meter
      }  
      cntr = 0;                                      // set second count to 0

         

    if (m_cntr >= 60)                                // reset on the hour (every 60 mins)
    {
      m_on = m_zero;                                 // move to zero on min meter
      m_index = 0;                                   // set number of mins this hr to zero
      m_tick = 0;                                    // set subdivisions of min meter to 0
      m_cntr = 0;                                    // set number of mins this hour to 0
      h_tick = 0;                                    // set subdivisions of hour meter to 0
      h_cntr++;                                      // increment hour count
    }
    if (h_cntr >= 12)                                // reset every 12 hours
    {
      h_on = h_zero;                                 // set hour meter to 0
      // m_index = 0;                                   // reset m_cal array index
      h_index = 0;                                   // reset h_cal array index
      // m_tick = 0;                                    // reset subdivisions of min meter 
      // h_tick = 0;                                    // reset subdivisions of hr meter
      h_cntr = 0;                                    // reset hour count
    } 
}  

// debounce routine assmes the use of
// 47k res to Vcc and .1uF cap to gnd, connected to button
void debounce(char button)
{
    debounceFlg = 1;
    P1IFG &= ~(button);                // clear interrupt flag for button pushed (p1.2 or p1.3)
    P1IE &= ~(button);                 // temporarily disable the interrupt
    IFG1 &= ~WDTIFG;                   // clear WDT timer+ interrupt pending (do when changing WDT settings)
    IE1 &= ~WDTIE;                     // disable WDT interrupt
    WDTCTL = WDTPW + WDTHOLD;          // temporarily stop Watchdog Timer
    WDTCTL = WDT_ADLY_250;             // Set Watchdog Timer delay to 250 mS
    IE1 |= WDTIE;                      // Enable WDT interrupt
}    

I'm definitely more of a hardware guy and haven't had to work on timers in a while so it took some reading and experimenting to wrap my head around this. Here's what I came up with for my application so far. Still needs a little work and experimenting. I've found that most code examples assume you know what all the registers are and what the predefines are. Since I don't, I tried to add a little extra in the comments in case someone else new runs across this and is trying to get a handle on it.

//******************************************************************************
//  MSP430G2211 Driving DRV8834 H-Bridge Driver and DC Brushed Motot
//
//  Description: This program generates one PWM output on P1.2 using
//  Timer_A configured for up/down mode. The value in CCR0, (20-1), defines the PWM
//  period/2 and the value in CCR1 the PWM duty cycles.
//  A 37% duty cycle is on P1.2 with a frequency of 50kHz
//  SMCLK = MCLK = TACLK = default DCO
//  Good hints in http://www.ti.com/lit/an/slaa513/slaa513.pdf
//  Datasheet at http://www.ti.com/lit/ds/symlink/msp430g2101.pdf
//  User guide (Rev J) at http://www.ti.com/lit/ug/slau144j/slau144j.pdf
//
//                                MSP430G2211
//                                -----------
//             DVCC               |1      14|  DVSS
//  Dir<-----  P1.0/TA0CLK/ACLK   |2      13|  XIN/P2.6/TA0.1
//             P1.1/TA0.0         |3      12|  XOUT/P2.7             /|\
//  Speed<---  P1.2/TA0.1         |4      11|  TEST/SBWTCK            |
//             P1.3               |5      10|  RST/NMI/SBWTDIO      ---
//             P1.4/SMCLK/TCK     |6       9|  P1.7/TDO/TDI
//             P1.5/TA0.0/TMS     |7       8|  P1.6/TA0.1/TDI/TCLK
//                                -----------
//
//  George I
//  February 2015
//  Built with CCS Version 6
//******************************************************************************

#include <msp430.h>

// Counter used to change motor direction
unsigned int motorDirection = 0;

int main(void)
{
  WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT
  BCSCTL1 = CALBC1_1MHZ;     				// Calibrate DCO to 1MHz, set range
  DCOCTL = CALDCO_1MHZ;     				// Set DCO step and modulation, section 5.2 of User Guide
  P1DIR |= BIT0 + BIT1 + BIT2 + BIT3;       // P1.0, P1.1, P1.2, P1.3 output
  P1SEL |= BIT2;                            // Connect P1.2 to TA0.1, see p32 of datasheet
  CCTL0 = CCIE;                       		// CCR0 interrupt enabled, defined in timera.h, called from msp430xxx.h
  CCR0 = 20-1;                              // PWM Period, SMCLK/CCR0=PWM Frequency
  CCTL1 = OUTMOD_3;                         // CCR1 set/reset, section 12.3.4 of User Guide
  CCR1 =  7;                                // CCR1 PWM duty cycle (CCR1/CCR0)
  TACTL = TASSEL_2 + MC_1;                  // use SMCLK, Up mode: the timer counts up to TACCR0

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

// Timer A0 interrupt service routine
#pragma vector = TIMERA0_VECTOR
__interrupt void Timer_A (void) {

    motorDirection++;

    if(motorDirection == 8600) {			// Equates to 400ms for each direction
        P1OUT ^= BIT0; 						// switch direction
        motorDirection = 0;
    }
}
Link to post
Share on other sites

Hi Katie:

 

Thanks, I actually did read your app note but in my case with the G2211 it only has the 2 CCR so I couldn't use your method (correct if I'm wrong on that though). I need to add in some additional code to my routine, similar to the post in the e2e thread, so I ramp up the speed of the motor when power if first applied. I also need to run some experiments using different PWM frequencies and see if I can find a sweet spot with lower current.

Link to post
Share on other sites

I think I will as we're hoping for dimming control on a LED. Getting the motor portion working was more important for now so I can run some experiments.

 

Ok, in that case you can use the same setup as in Doug's code, or the similar technique from the post I linked. With regard to the way the ISRs are written I think oPossum's code is a better example, as it uses the correct method to update TACCRx and also handles the interrupt flags properly in TIMERA1_VECTOR.

Link to post
Share on other sites

I was looking at oPossum's code and got to

TA1CCR0 += (s++ & 1) ? (pwm_period[0] - d) : (d = pwm_on[0]);

and I stopped there as I need to break that line down and figure out what he's doing!

 

Yeah, that packs a lot into a single line!

 

It's effectively doing this:

__interrupt void isr_ccr0(void)
{
    static unsigned s = 0; // State
    static unsigned d;     // On duration

    if(s == 0)
    {
        d = pwm_on[0];     // Save "on" time, which is used later to calculate the length of the "off" half of this cycle
        TA1CCR0 += d;
        s = 1;             // (0++ & 1) == (1 & 1) == 1
    }
    else
    {
        TA1CCR0 += (pwm_period[0] - d);
        s = 0;             // (1++ & 1) == (2 & 1) == 0
    }
} 

By the way, Katie's appnote can be used for your case. You need to use the "Multiple Time Base Method", which lets you produce as many PWMs as you have CCRx registers (CCR0 included). It's similar to oPossum's code, but shown with fixed PWM duty cycles rather than variable. It's worth reading through for the analysis of the limitations of the method at the end of the document.

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...