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.

PWM and Timers Help

Other Parts Discussed in Thread: MSP430G2553, DRV8850, DRV8850EVM

I'm more of a hardware guy and am having trouble coming up with a work around on a program change. I have a modified program from the DRV8850 EVM which uses TA1.1 and TA1.2. I didn't review the code closely enough before doing our board layout and I modified the connections between the DRV8850 and the MSP430G2553.

I now see that they separated the signals going to the DRV8850 so the high side signals (IN1H/IN2H) were connected to TA1.2 and the timer register was set to using OUTMOD6 (toggle/set mode). The low side signals (IN1L/IN2L) were connected to TA1.1 and the timer register was set to using OUTMOD2 (toggle/reset).

The way I routed my board was with these connections and an easy PCB fix is also proposed in the last column.

Signal Name

DRV8850EVM

Proto

Easy PCB Change

IN1H

P2.5 (TA1.2)

P2.3 (TA1.0)

P2.5 (TA1.2)

IN1L

P2.1 (TA1.1)

P2.4 (TA1.2)

P2.4 (TA1.2)

IN2H

P2.4 (TA1.2)

P2.1 (TA1.1)

P2.1 (TA1.1)

IN2L

P2.2 (TA1.1)

P2.2 (TA1.1)

P2.2 (TA1.1)

I think if I was a better programmer I could come up with a fix but I'm not having any luck so I thought I'd put up a post and see if someone can think of a clever way to modify the code to match the board layout.

Attached is the code.

Thanks,

George

3823.main_g2553_threader_proto.c
#include "msp430g2553.h"


// Port 1 output definitions
#define STATUS_LED		BIT7				// P1.7

// Port 2 output definitions

#define IN1H			BIT3				// P2.3
#define IN1L			BIT5				// P2.5
#define IN2H			BIT1				// P2.1
#define IN2L			BIT2				// P2.2

// Port 1 REN definitions (set unused to REN)
#define UNUSED			BIT4 | BIT 3		// P1.4 and P1.3 are unused

//
#define SPEED_REF		BIT5				// P1.5 is ADC read location

// Port 2 inputs
#define DIR				BIT7				// P2.7 is direction

// TIMER defines
// use 8
#define DUTY_CYCLE_TIMER_MAX 127	// max timer count value  -- total is 256 for up/down
#define DEADTIME		1			// software deadtime loop
									// set to 0 to use internal deadtime
#define MIN_DC 4					// sets the minimum duty cycle threshold to spin the motor
									// 1 - 50%
									// 2 - 25%
									// 3 - 12.5%
									// 4 - 6.25%
									// 5 - 3.125%
#define MAX_DC 5					// sets the maximum duty cycle threshold to spin the motor
									// 1 - 50%
									// 2 - 75%
									// 3 - 87.5%
									// 4 - 93.75%
									// 5 - 96.875%
#define DC_TOLERANCE	1			// Masked ADC value must change by DC_TOLERANCE + 1 before an update will occur
									// Value to change is ((DC_TOLERANCE + 1) * Vcc)/1024/8) Volts

// direction
#define FORWARD			0

#define ALL_LOW			0x00
#define ALL_HIGH		0xFF

volatile unsigned int LEDcounter=0;			// Initialize LED timing counter
unsigned int loop_counter;
unsigned int Direction, PriorDirection;
signed int DutyCycle, PriorDutyCycle;

unsigned int delayloop1;
unsigned int delayloop2;


/// SUBROUTINES
// this routine is a simple method of using duty cycle
// it is calculating the duty cycle by using the DUTY_CYCLE_TIMER_MAX number as
// a multiple of the number of bits (the number of bits is 6 (64 bins), and the
// the multiple is MULT_FACTOR)
// by using a multiple of the number of bits, the duty cycle is a math calculation
// OUTMOD0 is static, uses state of BIT2 to set low or high
// OUTMOD2 is toggle/reset  -- use on low side
// OUTMOD6 is toggle/set    -- use on high side

 void UpdateSpeed(unsigned int dc)
{
// calculate the next high side and low side values, then set them to minimize time
	 // the truth table for using this section is shown below
	 // Dir/Speed		IN1L	IN1H	IN2L	IN2H
	 // 0				H		L		H		L
	 // FWD < 100%		PWM		PMW		H		L
	 // FWD - 100%		L		H		H		L
	 // REV < 100%		H		L		PWM		PWM
	 // REV - 100%		H		L		L		H

// set up for slow decay mode

// Stop and reset the timers
//		TA1CCTL1 &= ~CCIE;	// disable the interrupt
		TA1CTL  |=  TACLR;	// CLR the timer;
		if (dc < (DUTY_CYCLE_TIMER_MAX >> MIN_DC)) // do not start spinning until 12.5%
		{
			// clear IN1H and IN2H
			// set IN1L and IN2L high to stop motor
			TA1CCTL1 = OUTMOD_0 | BIT2;
			TA1CCTL2 = OUTMOD_0;
			P2SEL = BIT5 | BIT4 | BIT2 | BIT1;
		}
		else if (dc < DUTY_CYCLE_TIMER_MAX - (DUTY_CYCLE_TIMER_MAX >> MAX_DC))     // use this up to 88%
		{
			// enable interrupts
//			TACTL |= TAIE;
			TA1CCR1 = DUTY_CYCLE_TIMER_MAX - dc;
			TA1CCR2 = DUTY_CYCLE_TIMER_MAX + DEADTIME - dc;
//			TA1CCTL1 |= CCIE;
			// clear IN1H and IN2H
			// set IN1L and IN2L high to stop motor
			P2SEL = BIT4 | BIT2;
			TA1CCTL1 = OUTMOD_2;
			TA1CCTL2 = OUTMOD_6;
			// need to set bits to clear based on direction
			if (Direction == FORWARD)
			{
				P2SEL = BIT5 | BIT4;		// PWM IN1 and
				P2OUT = BIT2;				// SET IN2L high
			}
			else
			{
				P2SEL = BIT1 | BIT2;		// PWM IN2 and
				P2OUT = BIT4;				// SET IN1L high
			}

			// restart the timer
			TA1CTL   =  TASSEL_2 | ID_0 | MC_3 | TACLR;  // Timer Clock = SMCLK;

		}
		else  // 100%
		{
			TA1CCTL1 = OUTMOD_0 | BIT2;
			TA1CCTL2 = OUTMOD_0 | BIT2;
			if (Direction == 0)
			{
				P2SEL = BIT5 | BIT2;		// Set IN1H and IN2L high
				P2OUT &= ~(BIT4 | BIT1);	// Set IN1L and IN2H low
			}
			else
			{
				P2SEL = BIT1 | BIT4;		// Set IN2H and IN1L high
				P2OUT &= ~(BIT3 | BIT2);	// Set IN1H and IN2L low
			}
		}
}


// MAIN Routine





int main(void)
{
  WDTCTL = WDTPW + WDTHOLD;             	// Stop watchdog timer

// Initialization -- First set all GPIO as inputs with pulldown

  P1REN  = BIT7 + BIT6 + BIT5 + BIT4 + BIT3 + BIT2 + BIT1 + BIT0;
  P2REN  = BIT7 + BIT6 + BIT5 + BIT4 + BIT3 + BIT2 + BIT1 + BIT0;
  P3REN  = BIT7 + BIT6 + BIT5 + BIT4 + BIT3 + BIT2 + BIT1 + BIT0;

  P1OUT  = ALL_LOW;
  P2OUT  = ALL_LOW;
  P3OUT  = ALL_LOW;

// Now re-configure Port Directions and Peripherals as required
  P1DIR = STATUS_LED;			// Set Port 1 GPIO to output on P1.7
  P1REN &= ~STATUS_LED;			// Disable Resistor

// must clear P2.7 prior to using it as an input
  P2SEL = ALL_LOW;

// Now re-configure Port Directions and Peripherals as required
  P2DIR = IN1H | IN1L | IN2H | IN2L;			// Set Port 1 GPIO to output on P1.1 and P1.2
  P2REN &= ~(IN1H | IN1L | IN2H | IN2L);		// Disable resistors
//

// Configure ADC10 to supply reference voltage and read value of voltage divider on P1.5

  ADC10CTL0 |= ADC10SHT_2 + ADC10ON + ADC10IE;    // VR+ = Vcc, VR- =AVSS, 16x sample and conversion, enable ADC and ADC interrupt
  ADC10CTL1 |= INCH_5;						// Select ADC input on A5
  ADC10AE0 |= INCH_5;						// Enable ADC input on A5

//  BCSCTL3 =  LFXT1S_3;						// Register value that must be set to allow TAI functionality on XIN pin

// Timer count value needs to be load only once
  TA1CCR0 = DUTY_CYCLE_TIMER_MAX;				// set time
  TA1CCTL1 |= OUTMOD_2;							// set mode to toggle/set
  TA1CCTL2 |= OUTMOD_6;							// set mode to toggle/reset

// delay for some time prior to speeding the clock, approximately 250ms per outer loop
  for (delayloop2 = 2; delayloop2 > 0; delayloop2 --)
  {
	  for (delayloop1 = 20000; delayloop1 > 0; delayloop1--)
      {__no_operation();
	  }
  }


// Set the DCO to calibrated 12MHz, be sure to have MCLK/2 and SMCLK/2 set prior to these commands.
  BCSCTL2  |= DIVM_1 | DIVS_1;				// MCLK is DCO/2, SMCLK is DCO/2
  DCOCTL	= ALL_LOW;						// clear the DCO then read the values

  BCSCTL1 	= CALBC1_12MHZ;
  DCOCTL	= CALDCO_12MHZ;					// Now set clock frequency at 6MHz



//// End of initialization

// Main Body of Code
  _BIS_SR(GIE);								// Global interrupt enable

// set values to force compare
  PriorDirection = 12000;
  PriorDutyCycle = 12000;


// initial setup of direction to avoid time delay
  //Direction = ((P2IN & DIR) >> 7);	// read direction if 0 -- forward else reverse
  Direction = 0;	// No need to read a direction
  if (Direction != PriorDirection)
  	{
	  UpdateSpeed(0);					// stop the motor first then restart in new direction
										//Set Direction
	  PriorDirection = Direction;
  	}

// end of setup, beginning of main loop
		TA1CCTL1 = OUTMOD_0 | BIT2;
		TA1CCTL2 = OUTMOD_0 | BIT2;
//		UpdateSpeed(DutyCycle);  //Check Speed Wheel once before looping

  for (;;)
    {
	    //UpdateSpeed(0);  //Stop motor before changing Direction
	    Direction = 0;
		ADC10CTL0 |= ENC + ADC10SC;		// Read Speed value from ADC -- Sampling and conversion start
		UpdateSpeed(DutyCycle);  //Check Speed Wheel
		// Delay for 30ms per outer loop or 420ms
		for (delayloop2 = 14; delayloop2 > 0; delayloop2 --)
		{
		  for (delayloop1 = 24000; delayloop1 > 0; delayloop1--)
		  {__no_operation();
		  }
		}

		//UpdateSpeed(0);  //Stop motor before changing Direction
		Direction = 1;
		ADC10CTL0 |= ENC + ADC10SC;		// Read Speed value from ADC -- Sampling and conversion start
		UpdateSpeed(DutyCycle);  //Check Speed Wheel

		// Delay for 30ms per outer loop or 420ms
		for (delayloop2 = 14; delayloop2 > 0; delayloop2 --)
		{
		  for (delayloop1 = 24000; delayloop1 > 0; delayloop1--)
		  {__no_operation();
		  }
		}

    }
}

///////////////////////////////// Interrupt service routines ////////////////////////

// ADC10 interrupt service routine
#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void)
{
	  DutyCycle = (ADC10MEM & 0x3f8) >> 3;	// discard the last 3 bits ~25mV/bit and
	  	  	  	  	  	  	  	  	  	  	// shift right one to match 511 max
	  P1OUT ^= STATUS_LED;           		// Toggle LED
}



#pragma vector=PORT1_VECTOR, PORT2_VECTOR, USCIAB0TX_VECTOR, USCIAB0RX_VECTOR, \
		TIMER0_A1_VECTOR, TIMER0_A0_VECTOR, WDT_VECTOR, COMPARATORA_VECTOR, \
		TIMER1_A1_VECTOR, TIMER1_A0_VECTOR, NMI_VECTOR
__interrupt void Trap_ISR(void)
{
	  // this is a trap ISR - check for the interrupt cause here by
	  // checking the interrupt flags, if necessary also clear the interrupt
	  // flag
}

  • Do the two timer outputs use the same CCR values, i.e., are the low/high side signals just the inverse of each other, without dead times?

    The routing of timer outputs to pins is fixed, so if you have a fixed pins, you must use a specific timer output. Your easy PCB change does not appear to help.

    If you cannot use timers, you have to use GPIOs, and toggle them from your code. This would be done from timer interrupt handlers, and works only if the delay from the interrupt execution is not too large for whatever it is you're controlling. (The worst case delay is the CPU wakeup time, or the execution time of your other interrupt handlers.)

  • Yes, the low/high side signals are the inverse of each other, they control high and low side FETs of an H-Bridge in the DRV8850. The PWM is used to control the speed of the motor so in summary the control conditions are this:

    // calculate the next high side and low side values, then set them to minimize time
    	 // the truth table for using this section is shown below
    	 // Dir/Speed		IN1L	IN1H	IN2L	IN2H
    	 // 0				H		L		H		L
    	 // FWD < 100%		PWM		PMW		H		L
    	 // FWD - 100%		L		H		H		L
    	 // REV < 100%		H		L		PWM		PWM
    	 // REV - 100%		H		L		L		H
    ...
    
    else if (dc < DUTY_CYCLE_TIMER_MAX - (DUTY_CYCLE_TIMER_MAX >> MAX_DC))     // use this up to 88%
    		{
    			// enable interrupts
    //			TACTL |= TAIE;
    			TA1CCR1 = DUTY_CYCLE_TIMER_MAX - dc;
    			TA1CCR2 = DUTY_CYCLE_TIMER_MAX + DEADTIME - dc;
    //			TA1CCTL1 |= CCIE;
    			// clear IN1H and IN2H
    			// set IN1L and IN2L high to stop motor
    			P2SEL = BIT4 | BIT2;
    			TA1CCTL1 = OUTMOD_2;
    			TA1CCTL2 = OUTMOD_6;
    			// need to set bits to clear based on direction
    			if (Direction == FORWARD)
    			{
    				P2SEL = BIT3 | BIT4;		// PWM IN1 and
    				P2OUT = BIT2;			// SET IN2L high
    			}
    			else
    			{
    				P2SEL = BIT1 | BIT2;		// PWM IN2 and
    				P2OUT = BIT4;			// SET IN1L high
    			}
    
    			// restart the timer
    			TA1CTL   =  TASSEL_2 | ID_0 | MC_3 | TACLR;  // Timer Clock = SMCLK;
    
    		}

    I was hoping that somebody could come up with a clever way to implement the PWM signals using the board connections. I don't have high hopes for that but I thought it wouldn't hurt to post the question.

  • After the PCB change, IN1H and IN2L are correct. Cut the connections of the other two pins, and solder blue wire to the correct signals.

    Or make a big PCB change.

    Or use the GPIO software solution (if it works).

  • Yeah, today we'll make the other 2 changes and test the code. When looking at a new board layout could we use TA1CCTL0? From my understanding we should be able to use CCTL0, 1, or 2, as long as we keep the lows on the same timer. I'm not sure using CCTL0 would help but at least I thought I should check in case it offers a better layout option.
  • Typically, CCR0 is already needed to set the PWM frequency.

  • No, I'm sorry, I meant if I could use Timer1_A3.TA0 on P2.0 and P2.3 for 1 set of signals. The DRV8850EVM used Timer1_A3.TA1 and Timer1_A3.TA2
  • You will have to run some pins in manual mode as 2H and 2L can not share same TA at the same time.
    You may not get all the features such as of fast/slow decay etc.

    Forward drive:

    IN1H=on, IN1L=off,
    IN2H=off,IN2L=on

    forward w/ pwm, you can pwm IN1H or IN2L.
    on proto TA1.0 can only do square-wave so P2.2 as TA1.1 low-side pwm it is.

  • Hmm, thanks for your input Tony. So TA1.0 is different than TA1.1 and TA1.2, did I miss that in the datasheet somewhere?

    If you don't PWM both IN1H and IN2L together wouldn't you have the potential to have both on at the same time which would then place the outputs in high Z?
  • The only time TA1.0 is like the other is when you use continuous mode.
    With PWM you need a shorter period than 65536 and TA1.0 is in charge of that value when in up-to mode.
    Can still use it as a toggle output, but it will always be a square wave of fixed width.

    To get speed control in forward and reverse you need to PWM just upper or lower mosfet.
    As two mosfet have to work in series to move the motor and if any one of them are pwm'ed you will reduce the speed.
    So one side can be left high all the time, using pin in manual gpio mode and the other is TA1.1 or TA1.2

    Going in reverse is just mirrored vertically.

**Attention** This is a public forum