Jump to content
43oh

PWM using timer output toggle mode


Recommended Posts

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;                                   //
}                                               //
Link to post
Share on other sites

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
}


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...