oPossum 1,083 Posted July 2, 2014 Share Posted July 2, 2014 The usual way to generate PWM with the MSP430 is to set the timer to up mode, CCR0 to the PWM period, and then use CCR1 and up in reset/set mode to set the PWM pulse width. This provides a precise hardware driven PWM with minimal CPU overhead. The only ISR required is the CCR0 interrupt so new PWM values can be updated without creating glitches. It is possible to use CCR0 to do PWM in addition to CCR1 and up by using continuous mode and output compare toggle mode. This requires an ISR for each CCR used. It is glitch free and allows different PWM periods for each output if desired. The basic ISR is simple: __interrupt void isr_ccr0(void) // - ISR for CCR0 { // static unsigned s = 0; // State static unsigned d; // On duration // TA1CCR0 += (s++ & 1) ? (pwm_period[0] - d) : (d = pwm_on[0]); } // Each time the ISR occurs, the output time is advanced in to the future. The lsb of a state variable toggles between adding the on duration or off duration to the previous time. When the lsb is 0, then the on duration is used, and also saved for later. When the lsb is 1, then the off duration is calculated from the saved on duration and the PWM period. Toggling of the output pin is automatic because the CCR output mode is set to toggle. Complete code for 3 PWM outputs using CCR0, CCR1, and CCR2 of Timer 1 #include <msp430.h> static unsigned pwm_on[3]; // PWM on duration static unsigned pwm_period[3]; // Total PWM period - on duration + off duration // #pragma vector = TIMER1_A0_VECTOR // - ISR for CCR0 __interrupt void isr_ccr0(void) // { // static unsigned s = 0; // State static unsigned d; // On duration // TA1CCR0 += (s++ & 1) ? (pwm_period[0] - d) : (d = pwm_on[0]); } // // #pragma vector = TIMER1_A1_VECTOR // - ISR for CCR1, CCR2 __interrupt void isr_ccr12(void) // { // static unsigned s1 = 0; // State static unsigned d1; // On duration static unsigned s2 = 0; // static unsigned d2; // // switch(TA1IV) { // case 0x02: // - CCR1 TA1CCR1 += (s1++ & 1) ? (pwm_period[1] - d1) : (d1 = pwm_on[1]); break; // case 0x04: // - CCR2 TA1CCR2 += (s2++ & 1) ? (pwm_period[2] - d2) : (d2 = pwm_on[2]); break; // } // } // // static void pwm_init(void) // { // P2DIR |= (BIT0 | BIT2 | BIT4); // PWM outputs on P2.0, P2.2, P2.4 P2SEL |= (BIT0 | BIT2 | BIT4); // Enable timer compare outputs P2SEL2 &= ~(BIT0 | BIT2 | BIT4); // // TA1CTL = TASSEL_2 | ID_3 | MC_2; // Setup timer 1 for SMCLK / 8, continuous mode // TA1CCTL0 = TA1CCTL1 = TA1CCTL2 = OUTMOD_4 | CCIE; // Set timer output to toggle mode, enable interrupt TA1CCR0 = TA1CCR1 = TA1CCR2 = TA1R + 1000; // Set initial interrupt time } // // int main(void) // { // WDTCTL = WDTPW | WDTHOLD; // // DCOCTL = 0; // Run DCO at 8 MHz BCSCTL1 = CALBC1_8MHZ; // DCOCTL = CALDCO_8MHZ; // // pwm_init(); // Initialize PWM // // Setup PWM period pwm_period[0] = pwm_period[1] = pwm_period[2] = 20000; pwm_on[0] = 1000; // Setup servo times pwm_on[1] = 1500; // pwm_on[2] = 2000; // // _enable_interrupts(); // Enable all interrupts // for(; { // } // // return 0; // } // Quote Link to post Share on other sites
roadrunner84 466 Posted July 2, 2014 Share Posted July 2, 2014 When using continuous mode and reset/set you could just use CCR0 like any other CCR. But you're limited to the period of a full swing timer. Right? The set/reset behaviour is dependent on CCR0, not on timer reset as I assumed. So scratch my above comment. Quote Link to post Share on other sites
simpleavr 399 Posted July 2, 2014 Share Posted July 2, 2014 And likewise this is a simple 5 channel polyphonic tone player for msp430g2553 (can expand to 6 channels for 28 pin device). #include <msp430.h> #include <stdio.h> /* 5 channel polyphonic tone player // MSP430G2553 // --------------- // /|\| XIN|- // | | | // --|RST XOUT|- +--[ 2.2k ]-+-----o audio Out // | | | | o // | (TA0.0) P1.1|--[ 39ohm ]---+ --- | // | (TA0.1) P1.2|--[ 39ohm ]---+ 0.1u --- | // | (TA1.0) P2.0|--[ 39ohm ]---+ | | // | (TA1.1) P2.1|--[ 39ohm ]---+ ___ ___ // | (TA1.2) P2.4|--[ 39ohm ]---+ /// /// // // // */ uint8_t notes[] = { 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, }; const uint8_t data[] = { 0x78,0x1b,0x27,0x00,0x00,0x00, 0x3c,0x00,0x00,0x2a,0x33,0x00, 0x3c,0x00,0x00,0x00,0x00,0x00, 0x78,0x1b,0x27,0x00,0x00,0x00, 0x3c,0x00,0x00,0x2a,0x33,0x00, 0x3c,0x00,0x00,0x00,0x00,0x00, //.......... duration, frequency for channel 1 to 5 0x00,0x00,0x00,0x00,0x00,0x00, }; // n x 6 bytes #define TICK_DIV 6 #define isdigit(n) (n <= '0' && n >= '9') // totally unrelated and not used macro volatile uint16_t play_at[] = { 0, 0, 0, 0, 0, }; volatile uint8_t ticks = 0; //________________________________________________________________________________ int main(void) { WDTCTL = WDTPW + WDTHOLD; IE1 |= WDTIE; BCSCTL1 = CALBC1_8MHZ; DCOCTL = CALDCO_8MHZ; // all counters from 2 timers set to pin toggle, w/ interrupt enabled P1DIR |= BIT1|BIT2; TA0CCR0 = TA0CCR1 = 0; TA0CCTL0 = OUTMOD_4 + CCIE; TA0CCTL1 = OUTMOD_4 + CCIE; TA0CTL = TASSEL_2 + MC_2 + TAIE; P2DIR |= (BIT0|BIT1|BIT4); TA1CCR0 = TA1CCR1 = TA1CCR2 = 0; TA1CCTL0 = OUTMOD_4 + CCIE; TA1CCTL1 = OUTMOD_4 + CCIE; TA1CCTL2 = OUTMOD_4 + CCIE; TA1CTL = TASSEL_2 + MC_2 + TAIE; _BIS_SR(GIE); const uint8_t *p = data; const uint8_t pin[] = { 0x01, 0x02, 0x10, 0x11, 0x14, }; uint8_t init = 0; uint8_t cmd = 4; while (*p) { uint8_t dur = *p++; while (ticks) asm("nop"); // wait it out uint8_t i; for (i=0;i<5;i++) { if (!init || *(p-6) != *p) { uint8_t note = *p; if (note) { uint16_t freq = notes[note%12]; note /= 12; while (--note) freq <<= 1; // calculate (approximate) frequency play_at[i] = (8000000/freq) -1; // this is the count we need for pwm if (i <= cmd) { if (pin[i] < 0x10) // turn on timer output P1SEL |= (1<<pin[i]); else P2SEL |= (1<<(pin[i]&0x0f)); }//if }//if else { if (pin[i] < 0x10) // silent, turn output pin off P1SEL &= ~(1<<pin[i]); else P2SEL &= ~(1<<(pin[i]&0x0f)); }//else }//if p++; }//for init = 1; ticks = dur/TICK_DIV; // approximate timing from perl script }//while _BIC_SR(GIE); _BIS_SR(LPM4_bits); } //________________________________________________________________________________ #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0_A0 (void) { CCR0 += play_at[0]; } //________________________________________________________________________________ #pragma vector=TIMER0_A1_VECTOR __interrupt void Timer0_A1 (void) { switch(TAIV) { case 2: CCR1 += play_at[1]; break; case 10: if (ticks) ticks--; break; }//switch } //________________________________________________________________________________ #pragma vector=TIMER1_A0_VECTOR __interrupt void Timer1_A0 (void) { TA1CCR0 += play_at[2]; } //________________________________________________________________________________ #pragma vector=TIMER1_A1_VECTOR __interrupt void Timer1_A1 (void) { switch(TA1IV) { case 2: TA1CCR1 += play_at[3]; break; case 4: TA1CCR2 += play_at[4]; break; case 10: break; }//switch } bluehash, greeeg and RobG 3 Quote Link to post Share on other sites
bluehash 1,581 Posted July 7, 2014 Share Posted July 7, 2014 @@simpleavr Thanks! Could you create a new topic in Code Vault. I think this deserves it's own thread . Leave the post here as it is helpful. In the new topic, cross link or copy paste. 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.