7
Mar 13

Power Feet Booster Pack

This write-up is for the power fet booster pack (PFBP) which is up for sale on the products page. It's sold as a kit of parts which requires assembly. This video is a quick over view of the board and a demonstration of it pulse-width-modulating and LED strip. The source code is discussed below.

So now that you've seen the system running lets check out the nuts and bolts....aka the source code and some schematics!

/*
 * LED strip pulse width modulation firmware
 *
 *
 *
 *
 * Robbie Valentine
 *
 */
#include "msp430g2553.h"

#define DUTY_CYCLE TA1CCR2

volatile int pwm_done_blinky = 0;

void main(void) {

The first things I do in the C file is include the MSP430's library file, define a name and initialize a global "volatile" variable. We then jump into main(). The include file holds all the register names and what not that the compiler will be looking for. The chip I'm using is the MSP430G2553 so that will be the header file I need to include. Next, I define duty cycle to by register TA1CCR2. I so this so the code is a littlemore readable. TA1CCR2 is the capture compare register that determines the duty cycle. We will get into this later. The last thing I do is define the global variable pwm_done_blinky. This isn't necessary as I don't use it in the source code so don't worry if you download the source and don't see it, but there is an important point to mention here. The keyword volatile denotes that this variable can change outside of normal program execution and lets the compiler know. These are used with variables that change in interrupts. Since they can change at any moment, outside of normal code execution, we have to give the compiler a heads up. Now, on to main and configuring registers.

void main(void) {


	WDTCTL = WDTPW + WDTHOLD;             // Stop watchdog timer


	DCOCTL = CALDCO_8MHZ;	// MCLK = 8Mhz
	BCSCTL1 = CALBC1_8MHZ;
	// Configure Submain clock for TimerA
	BCSCTL2 &= ~SELM0;	// MCLK comes from DCO
	BCSCTL2 &= ~SELM1;
	BCSCTL2 &= ~SELS;	// Sub main clock comes from DCO
	BCSCTL2 |= DIVS0 + DIVS1;	// SMCLK = 1 MHz (DC0/8)



	// Enable push button (P1.3)
	P1REN |= BIT3;		// pullup required for launchpad push button
	P1OUT |= BIT3;		// clear output pin 1.3
	P1DIR &=~ BIT3;		// P1.3 = input
	P1IES |= BIT3;		// interrupt on high to low transition
	P1IFG &=~ BIT3;		// clear inerrupt flag
	P1IE |= BIT3;		// Enable interrupt.


	// Enable IO for LEDs on power fet booster pack
	P1OUT &=~ BIT5;	// 1.5
	P1DIR |= BIT5;
	P2OUT &=~ (BIT2 + BIT3 + BIT4);	// 2.2, 2.3, 2.4
	P2DIR |= (BIT2 + BIT3 + BIT4);

	// Enable launchpad led
	P1OUT &=~ BIT0;
	P1DIR |= BIT0;

	// Enable J6 FET pins
	P2DIR |= BIT0;
	P2OUT &=~ BIT0;






	// Configure TimerA0
	/*	TimerA0 is setup to run at 125Khz by dividing the 1Mhz SMCLK down to
	 * 125kHz.  CCR0 controls the duty cycle increase
	 */
	TA0CTL |= TASSEL1;	// Sets TimerA0 to use SMCLK as Timer clock
	TA0CTL |= ID0 + ID1;	// Divide Timer clock by 8 to yield 125khz
	TA0CTL |= MC1;	// set timer to continuous mode.

	// CCR0	- PWM DutyCycle control
	TA0CCTL0 |= CCIE;	// enable CCR0 interrupt
	TA0CCTL0 &=~ CCIFG;	// clear CCR0 interrupt flag
	TA0CCR0 = 5000;		// interrupt 25x / sec

	// CCR1	- Dim on blinky LED
	//TA0CCTL1 |= CCIE;	// enable CCR1 interrupt
	TA0CCTL1 &=~ CCIFG;	// clear CCR1 interrupt flag
	TA0CCR1 = 6250;		// 20x/sec

	// CCR2 - not in use
	//TA0CCTL2 |= CCIE;	// enable CCR1 interrupt
	//TA0CCTL2 &=~ CCIFG;	// clear CCR1 interrupt flag
	//TA0CCR2 = 1;		// ~20x/sec



	// Configure TimerA1
	/*	TimerA0 is setup to run at 1Mhz by dividing the 1Mhz SMCLK by
	 * 1.  CCR0 controls the frequency and CCR2 controls the duty cycle.
	 */

	TA1CTL |= TASSEL1;	// Sets TimerA0 to use SMCLK as Timer clock
	TA1CTL |= MC_1;	// set timer to up mode.

	// CCR0 - not in use atm
	//TA1CCTL0 |= CCIE;	// enable CCR0 interrupt
	//TA1CCTL0 &=~ CCIFG;	// clear CCR0 interrupt flag
	TA1CCR0 = 200;		// interrupt 5000x / sec (5kHz)


	// CCR2 - PWM
	//TA1CCTL2 |= CCIE;	// enable CCR2 interrupt
	//TA1CCTL2 &=~ CCIFG;	// clear CC2 interrupt flag
	TA1CCTL2 |= OUTMOD_6;	// Toggle Set mode
	TA1CCR2 = 0;	// CCR2 is the Pulse Width

	// Enable I/O for P2.5 - TA1.2 (set auxiliary function and direction) for PWM output
	P2DIR |= BIT5;
	P2SEL |= BIT5;




	// Global interrupt enable + Sleep mode
	_BIS_SR(LPM0_bits + GIE);

	
}

Starting at the top, the first thing you want to do is turn off the watch-dog timer. This is an internal timer that resets the MCU when it expires. This is usually used to safegaurd against software or communication hang-ups, but we don't need it here. Next, configure the DCO and clocks. The DCO is the internal digitally controlled oscillator. This module generates a main clock which can be broken down by dividers and applied to MCLK and SMCLK (main clock and sub-main clock). I configure the DCO to 8 Mhz ands set MCLK to the DCO, dividing by 1 which gives me a MCLK speed of 8 Mhz. This is what the CPU runs off of. The SMCLK runs my timers and peripherals so I want that to be a little slower. To accomplish this I divide it by 8 giving me a SMCLK speed of 1 Mhz. Quick note, the DCO does take some power and if you configure it to 16 MHZ immediately you could have start-up or power issues. It's a good idea to give the DCO time to settle when setting it to 16Mhz. Especially if the chip is on a power-rail that was just powered up and may not have enough current to supply the chip immediately.

After this, I setup the push-button on the launchpad (S2) to generate an interrupt on P1.3 with the falling edge of the button. This makes sense because the button connects the pin to ground when pushed. The tricky part here is that you have to enable a weak pull-up because the pin is not pulled up with hardware. Think of this as an internal pullup resistor for P1.3. If you don't do this the pin will be floating and randomly interrupt the processor. Now, the pin will be "eakly" pulled up to Vcc (3.3V) and upon pushing the button, shorted to ground until the button is released. This creates the falling edge that gives us the interrupt.

	// Enable push button (P1.3)
	P1REN |= BIT3;		// pullup required for launchpad push button
	P1OUT |= BIT3;		// clear output pin 1.3
	P1DIR &=~ BIT3;		// P1.3 = input
	P1IES |= BIT3;		// interrupt on high to low transition
	P1IFG &=~ BIT3;		// clear inerrupt flag
	P1IE |= BIT3;		// Enable interrupt.

This section enables the pins connected to the four LEDs on the booster pack to be digital I/O. We can then use these pins to toggle the LEDs on and off. The last part enables the pins that drive the FET (J6) to be digital I/O as well, but in this program we are not using J6 so these pins and this functionality isn't used. The led on the launch pad is also configured and this is used in a later interrupt routine.

	// Enable IO for LEDs on power fet booster pack
	P1OUT &=~ BIT5;	// 1.5
	P1DIR |= BIT5;
	P2OUT &=~ (BIT2 + BIT3 + BIT4);	// 2.2, 2.3, 2.4
	P2DIR |= (BIT2 + BIT3 + BIT4);

	// Enable launchpad led
	P1OUT &=~ BIT0;
	P1DIR |= BIT0;

	// Enable J6 FET pins
	P2DIR |= BIT0;
	P2OUT &=~ BIT0;

This timer TA0 is pretty simple since it isn't doing any modulation. The purpose of this timer is to dim the LEDs on the LED strip on. CCR0 keeps track of the duty-cycle and increments it. The only thing to really mention is that CCR1 is configured, but enabled (by enabling it's interrupt) by the DutyCycle logic in CCR0.

	// Configure TimerA0
	/*	TimerA0 is setup to run at 125Khz by dividing the 1Mhz SMCLK down to
	 * 125kHz.  CCR0 controls the duty cycle increase
	 */
	TA0CTL |= TASSEL1;	// Sets TimerA0 to use SMCLK as Timer clock
	TA0CTL |= ID0 + ID1;	// Divide Timer clock by 8 to yield 125khz
	TA0CTL |= MC1;	// set timer to continuous mode.

	// CCR0	- PWM DutyCycle control
	TA0CCTL0 |= CCIE;	// enable CCR0 interrupt
	TA0CCTL0 &=~ CCIFG;	// clear CCR0 interrupt flag
	TA0CCR0 = 5000;		// interrupt 25x / sec

	// CCR1	- configured to blink LED but not enabled yet
	//TA0CCTL1 |= CCIE;	// enable CCR1 interrupt
	TA0CCTL1 &=~ CCIFG;	// clear CCR1 interrupt flag
	TA0CCR1 = 6250;		// 20x/sec

	// CCR2 - not in use
	//TA0CCTL2 |= CCIE;	// enable CCR1 interrupt
	//TA0CCTL2 &=~ CCIFG;	// clear CCR1 interrupt flag
	//TA0CCR2 = 1;		// ~20x/sec

This is probably the most daunting part. Timer TA1 is what produces our pulse width modulated signal. It is configured to run at 1 Mhz so that we could get a 5 kHz signal and have more resolution (bigger value) for our period which gives us more control over the duty cycle. The timer is configured in up mode meaning that when TAR (the counter) hits the value stored in CCR0 the timer resets and starts counting over. I used CCR2 because the gate of the FET is connected tp pin TA1.2. This means you can configure that pin in outmode to automatically output the toggle/set signal from the datasheet. To get a visual aid here, look at page 372 of the family datasheet. In this configuration the Timer counts up to CCR0 as it's max value. When the counter gets to CCR2 (which should be less than CCR0 for a duty cycle less than 100%) the pin is set from high to low in hardware. The best part of all this is that after configuring it takes no software overhead to produce this signal. The last two instructions are important and found in the port section of the device specific datasheet. These instructions tell the pin to use the specific TA1.2 hard functionality.


	// Configure TimerA1
	/*	TimerA0 is setup to run at 1Mhz by dividing the 1Mhz SMCLK by
	 * 1.  CCR0 controls the frequency and CCR2 controls the duty cycle.
	 */

	TA1CTL |= TASSEL1;	// Sets TimerA0 to use SMCLK as Timer clock
	TA1CTL |= MC_1;	// set timer to up mode.

	// CCR0 - not in use atm
	//TA1CCTL0 |= CCIE;	// enable CCR0 interrupt
	//TA1CCTL0 &=~ CCIFG;	// clear CCR0 interrupt flag
	TA1CCR0 = 200;		// interrupt 5000x / sec (5kHz)


	// CCR2 - PWM
	//TA1CCTL2 |= CCIE;	// enable CCR2 interrupt
	//TA1CCTL2 &=~ CCIFG;	// clear CC2 interrupt flag
	TA1CCTL2 |= OUTMOD_6;	// Toggle Set mode
	TA1CCR2 = 0;	// CCR2 is the Pulse Width

	// Enable I/O for P2.5 - TA1.2 (set auxiliary function and direction) for PWM output
	P2DIR |= BIT5;
	P2SEL |= BIT5;

This is the first interrupt service routine. This routine is only for CCR0 since these MSP430's have two interrupt vectors for their timers. One is for CCR0 only and the other is for everything else...i.e CCR1, CCR2 and overflow. In this ISR we toggle two LEDs constantly (every time CCR0 throws an interrupt) and keep track of what the duty cycle is. We increment the duty cycle to 100%, a value of 200, every time CCR0 throws an interrupt as well. I also enable CCR1 to begin interrupting. This just blinks another LED denoting I'm increasing my duty cycle. Once at 100%, I shut off CCR1 and stop incrementing because the LED strip is now fully powered.

/* ***************************************************
* Timer0 CCR0
* Controls the duty cycle and how long LED D2 on PFBP
* blinks.  D2 Blinks while the duty cycle is increasing
* and then turns off when we reach a duty cycle of 100%.
* This is controlled by Timer0 CCR1
*
******************************************************/
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer0_A0_ISR(void)
{
	P1OUT ^= BIT5;	// D2
	P1OUT ^= BIT0;	// Launchpad LED 1

	// Fade on load connected to PFBP J5
	if (DUTY_CYCLE <= 200){
		DUTY_CYCLE += 2;	// append to duty cycle
		TA0CCTL1 |= CCIE;	// enable dim up blinky LED
	}else{
		TA0CCTL1 &=~ CCIE;	// disable dim up blinky LED
		P2OUT &=~ BIT2;		// D3
	}

	TA0CCR0 += 5000;
	TA0CCTL0 &=~ CCIFG;
}

This ISR is the one for everything else regarding TA0. The TA0IV table is an interrupt table holding all the flags for the possible interrupts here so switching on it is a clean way to handle them. Upon reading the table the highest priority flag is cleared so you don't have to manually clear them. CCR1 was enabled above and as you can see, when it's running, just blinks and LED on the booster pack.

/* ********************************************
* Timer0 CCR1-2
* ISR for TA0 CCR1, CCR2 and TA0 overflow
*
**********************************************/
#pragma vector=TIMER0_A1_VECTOR
__interrupt void Timer0_A1_ISR(void)
{

	switch(TA0IV){	// switch on table
		// CCR1
		case 0x02:
			P2OUT ^= BIT2;		// D3
			TA0CCR1 += 6250;	// append CCR1 displacement since we are in continuous mode
			break;

		// CCR2 not used
		case 0x04:
			// Null
			break;

		// overflow not used
		case 0x0A:
			// Null overflow
			break;
	}

}

This ISR isn't used, but I left it in so you could see that TA1 works the same way. Remember we don't need to use the ISR's here because the PWM signal is generated in hardware for us. All, you have to do is configure it.

/* ******************************************
* Timer1 CCR1-2
* This ISR is not used, but I left it in as
* an example.  Same as the TA0 ISR.
*
*********************************************/
#pragma vector=TIMER1_A1_VECTOR
__interrupt void Timer1_A1_ISR(void)
{

	switch(TA1IV){	// switch on table
		case 0x02:
			// Null CCR1
			break;

		case 0x04:
			// Null for CCR2
			break;


		case 0x0A:
			// Null overflow
			break;
	}

}

Last is the push button ISR. The way digital IO interrupts work is similar to the everything else ISR for the timers. One interrupt is thrown for port 1 and you can check the interrupt flag to see which pin it was. Since I'm using S2 on the launch pad (p1.3) I clear that flag when I'm done, but since thats the only IO I'm using I know that if I enter the routine it is because of P1.3. When I enter I reset the duty cycle and then hop out. You can see the result of this when I push the button in the demonstration and the LED strip dims back on.

/*******************************************************
* PORT1 Interrupt (P1.3)
* This is the pushbutton ISR for S2 on the launchpad.
* All I do in here is reset the duty cycle.  When this
* happens CCR0 starts adding 2 again until we reach
* a 100% DC.  This force the LED strip to DIM on again.
*
******************************************************/
#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR(void)
{
	DUTY_CYCLE = 0;		// Reset duty cycle variable


	P1IFG &=~ BIT3;		// clear flag
}

Below are the schematics. Hopefully this completes the picture.

regulator

Main

Layout:

layout