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.

MSP430F5638: TB0 PWM and ISR Questions

Part Number: MSP430F5638

Using CCS v10, and currently have an MSP430F5638 running on a custom SBC. Using TB0 to create PWM for LED dimming. TB0 is working fine, getting the desired frequency and duty cycle on scope. But I have a number of questions. I have included portions of a simplified version that is working the same as the full version. The full program sends a lot of data to the attached LCD for test and development - that code has been removed for clarity.

void main(void)
{
	WDTCTL = WDTPW | WDTHOLD;	// stop watchdog timer
	
	// setup functions
//	setup_430_ports();
	setup_430_clocks();         // use XT2 @ 20 MHz

    // setup testing for LED PWM Dimmer
    set_LED_TB0_dim();
    // no code accessible after LED function runs
//    LCD_put_string_4_cc(170, 20, "AFTER LED TB0 CALL", red, white);
}
void set_LED_TB0_dim(void)
{
    float xt2 = 20e6, frequency, dutycycle;

    WDTCTL = WDTPW+WDTHOLD;              // Stop WDT

    P4SEL |= BIT6;                       // P4.6 peripheral option select
    P4DIR |= BIT6;                       // P4.6 output

    // left most 0 not required since only 1 TB
    TBCCR0 = 100;                        // set CCR0 cycle period register
    TBCCTL6 = OUTMOD_7;                  // set CCR6 Output mode, reset/set
    TBCCR6 = 50;                         // set CCR6 PWM duty cycle
    TBEX0 = 0;                           // has no effect, 0 = '/1' by default

    // enable CCR6 interrupt - required for ISR to run
    // but disables TB0, no PWM out
    //TBCCTL6 = CCIE;

    // ID_0 = /1 by default
    // add enable interrupt - not necessary (+ TBIE), removed
    TBCTL = TBSSEL_2 + MC_1 + ID_0 + TBCLR;   // SMCLK, up mode to CCR0, /1, TB clear

    // compute frequency & duty cycle for LCD diagnostics
    frequency = xt2/TBCCR0;
    // not accurate for > 50% and < 100%, need to cast int as float
    dutycycle = (float)TBCCR0/TBCCR6;

    // no timer output without this?
    __bis_SR_register(LPM0_bits + GIE);     // enter LPM0 with interrupts enabled, CPU off, runs in regular routines
    //__bis_SR_register(GIE);               // interrupts enabled, runs in regular routines
    //__bic_SR_register_on_exit(LPM0_bits);   // exit LPM0 - can only run from ISR's
}

// ISR from msp430f66xx_tb_03.c and modified
// Timer_B0 CCR6 Interrupt Vector (TBIV) handler
// changed from B1 to B0 - BOTH MUST BE B1 TO WORK????
#pragma vector=TIMERB1_VECTOR
__interrupt void TIMERB1_ISR(void)
{
  switch( __even_in_range(TBIV,14) )
  {
    case  0: break;                          // No interrupt
    case  2: break;                          // CCR1 not used
    case  4: break;                          // CCR2 not used
    case  6: break;                          // CCR3 not used
    case  8: break;                          // CCR4 not used
    case 10: break;                          // CCR5 not used
    case 12:                                 // CCR6 overflow
        {
            __bic_SR_register_on_exit(LPM0_bits);    // exit LPM0, can only run from ISR
            LCD_put_string_4_cc(170, 40, "IN TB0 ISR CASE 12", red, white);
            break;
        }
    case 14: break;                          // CCR7 not used
    default:
        {
            LCD_put_string_4_cc(190, 0, "IN TB0 ISR DEFAULT CASE", white, red);
            break;                          // no default
        }
  }
}


main() Questions

1. Why doesn't any code in main() run after the call to set_LED_TB0_dim()?

set_LED_TB0_dim() Questions

  1. MSP430F5638 only has one TB, so looks like 0 suffix is superfluous TBCCR0 and TB0CCR0 both work
  2. TBCCTL6 = CCIE required for ISR to run, but disables TB0, no PWM output
  3. Why does set_LED_TB0_dim() go into LPM mode? Won’t run without it.
  4. Why is CCIE required, but not TBIE?
  5. __bis_SR_register(LPM0_bits + GIE) Works with or without LPM0_bits (I’m not checking power in real-time – but looks the same 142 ma)

I really don't want TB0 to put the CPU into LPM, but code won't run without it.

ISR Questions

1. Why does the ISR name require B1 in the vector and function name, and not B0? This is TB0, but won't work with B0.

  • I see two problems.

    The first is that the code to enable the timer interrupt is commented out. Which means that when you set LPM0, there is no ISR to cause an exit. Of course your code to enable the interrupt also trashes the previous settings in that register which is why the PWM stops. "|=" is probably what you meant.

    But if the interrupt were enabled, you don't have a lot of time to service the ISR. About 100 SMCLK cycles which I suspect is the same as MCLK. That LCD put string call could easily consume more than that. Which means that another timer interrupt fires before the ISR returns. A sure fire way to starve the main program of clock cycles.

  • Thanks for your response. I uncommented TBCCTL6 = CCIE;

    changed TBCTL = to |=

    and removed the LCD statements from the ISR. TB0 is not running, there isn't any output with the uncommented statement above.

  • I expect David meant 

    TBCCTL6 |= CCIE;     // Enable CCR6 IFG, but leave OUTMOD set

    TBIE is ignored due to:

    > case 14: break; 

    [Edit: To your last question, TIMERB1_VECTOR is formally TIMER0_B1_VECTOR, where the B0_VECTOR applies (only) to CCR0 and B1_VECTOR to all the others.]

  • Thank you Bruce, and David; now both TB0 PWM and ISR are running at the same time. I still have other questions: main 1, set_LED 1, 3, 5; and ISR 1.

  • main() 1: As David pointed out, you didn't set TB0CCTL6:CCIE, so the LPM never woke up.

    set_LED 1: TB0 is the canonical name. If you ever move your code to a device that has 2x TB-s, you'll have "TB" and "TB1", which will be hard to read.

    set_LED 3: Please describe your symptom.

    set_LED 5: Please describe your symptom.

    ISR 1: TIMERB1_VECTOR is formally TIMER0_B1_VECTOR, where the B0_VECTOR applies (only) to CCR0 and B1_VECTOR to all the others. [Ref User Guide (SLAU208Q) Sec 18.2.6]

    Unsolicited: As David pointed out, your timer is running very fast. You won't be able to do much else in the ISR without saturating the CPU. (You also won't be able to read an LCD which changes every 5 microseconds.)

  • Thanks Bruce,

    main() 1 -----------------------------------------------------------

    I'm now setting CCIE with TB0CCTL6 = OUTMOD_7 + CCIE;

    and my ISR case for this is

    case 12: // CCR6 overflow
    {
    __bic_SR_register_on_exit(LPM0_bits); // exit LPM0, can only run from ISR
    LCD_put_string_4_cc(170, 20, "IN TB0 ISR CASE 12", red, white);
    break;
    }

    The LCD statement runs, and if I comment it out, I still have the problem. Nothing in main, after the call to set_LED runs, I'm still doing something wrong with this.

    set_LED 3 & 5 ----------------------------------------------------

    This instrument will run on a 2 second period set by the RTC. It starts, reads data, does its thing, updates the LCD, and goes back to sleep. There are several buttons which each have their own ISR, mostly they just set a global var that the main 2 second loop deals with. But one switch will turn on a LED flashlight with successive pushes to set power level, and finally turn it off. this is why I didn't want the set_LED function to go into LPM. But the function won't run without it. Are there other options?

    timer running fast ----------------------------------------------

    TB0 must generate a 100 KHz to 1 MHz square wave signal to drive an LED driver IC. ACLK is too slow, but I can divide SMCLK by 8 and still get pulses out from 100 KHz to 800 KHz, or /4 to hit the range exactly. That should help somewhat.

  • Having a timer generate PWM at 200kHz (or even 1MHz) is fine. Having it interrupt at 200kHz (or 400kHz, if you're still setting TBIE) is something else, since each ISR costs around 30 CPU clocks just to enter and exit. main() isn't executing since all of your CPU is dedicated to the ISR calls.

    If that LCD call is updating a (human-readable) display, there's no value in updating it 200,000 times per second, since a human can't possibly read it that fast.

    I suggest you adopt another timer (you have 4x+RTC to work with) and run that at a human-speed pace, say 1-2 interrupts/second, to update the display. Let TB0 run on its own in the background (without interrupts).

  • Ok on the CPU being tied up from the interrupts.

    One of my original questions was: how can I use TB0 as a PWM generator without creating any interrupts. I never wanted a TB0 ISR in the first place. I want to use TB0 as a dumb pulse generator from when I turn it on, until I decide to turn it off.

    The LCD gets updated once per 2 second cycle. I only stuck a call in the ISR to see if it's actually getting there - it is.

    The two TI examples I've used for TB0 are: msp430f66xx_tb_03.c, and msp430f66xx_10.c, and the 10 example calls __bis_SR_register(LPM0_bits + GIE); Why does it call that?

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

    I figured it out: how to use TB0 as just a dumb PWM and no ISR's!

    Don't set CCIE, or TBIE, and comment out the __bis_SR call, the program flows fine, but TB0 does nothing. Well not true, there is this very brief flash of a pulse on the scope before main ends (if I'm looking at the scope at the right microsecond of time) and shuts off all peripherals. Added a loop to this test code to see the pulses steady state.

  • Many of the examples go into LPM at the end of main(), just because they run out of things to do. In example 10, it serves indirectly as a demonstration that once started, PWM runs by itself, and you don't have to do anything.

    Does your code still look like what you posted above? That code returns from main after the call to set_LED_TB0_dim, so it would end up in an enabled loop (in abort()). I don't think anything there will stop the timer, so the PWM would continue forever. I suspect it's something else that is stopping the PWM.

  • main() as listed above: the LCD call is now working - it was clobbered by too many interrupt calls - as you pointed out. TB0 is only creating a single pulse train, and then shuts off. If I add an endless loop after the LCD call, the pulses run forever.

    in set_LED() I just commented out the call to __bis_SR_register(). The ISR is not run.

    So the question is: once main ends, are the internal peripherals shutdown, or do they keep running? Looks to me like they are shut down (at least TB0). The power to the SBC drops about 4ma after main stops vs. running in a loop. This is just average current, can't see time dependent current.

  • In an embedded system you should never let main() exit as there is nowhere to exit to. The C runtime library will typically put something in place just in case main() exits. Usually just an infinite loop but it may throw in a low power mode. No way to be sure without examining the object code.

  • Thanks David and Bruce

    This was a very illuminating issue, lots of nuances. I'll close it.

**Attention** This is a public forum