GeorgeIoak 3 Posted February 18, 2015 Share Posted February 18, 2015 I thought I had seen some examples of this but searching I cannot find a discussion about it. Does anyone know of some examples you can point me too? Thanks, George Quote Link to post Share on other sites
tripwire 139 Posted February 19, 2015 Share Posted February 19, 2015 Was it this post, by any chance? http://forum.43oh.com/topic/5619-pwm-using-timer-output-toggle-mode/ Quote Link to post Share on other sites
GeorgeIoak 3 Posted February 20, 2015 Author Share Posted February 20, 2015 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; } } Quote Link to post Share on other sites
KatiePier 73 Posted February 20, 2015 Share Posted February 20, 2015 There's also this app note here: www.ti.com/lit/pdf/slaa513 (Note that it will soon be updated though because of this E2E thread: e2e.ti.com/support/microcontrollers/msp430/f/166/p/403099/1428736#1428736 - so make note. I think this issue shouldn't matter on G2211 though because of its limited pin and timer options) -Katie Quote Link to post Share on other sites
GeorgeIoak 3 Posted February 20, 2015 Author Share Posted February 20, 2015 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. Quote Link to post Share on other sites
tripwire 139 Posted February 20, 2015 Share Posted February 20, 2015 Do you still need to add another PWM output to your program, or was 1 PWM + 1 square wave what you wanted to achieve? Quote Link to post Share on other sites
GeorgeIoak 3 Posted February 20, 2015 Author Share Posted February 20, 2015 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. Quote Link to post Share on other sites
tripwire 139 Posted February 20, 2015 Share Posted February 20, 2015 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. Quote Link to post Share on other sites
GeorgeIoak 3 Posted February 20, 2015 Author Share Posted February 20, 2015 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! Quote Link to post Share on other sites
tripwire 139 Posted February 20, 2015 Share Posted February 20, 2015 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. Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.