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.

CCS/MSP430G2553: PID control algorithm

Part Number: MSP430G2553
Other Parts Discussed in Thread: MSP-IQMATHLIB

Tool/software: Code Composer Studio

I have written a code for implementing PID control on a motor. The code is as follows:

#include <msp430g2553.h>
/**
* main.c
*/
int main(void) {

WDTCTL = WDTPW + WDTHOLD; //disabling the watch-dog timer

P2DIR = BIT2; //selecting and setting the output bits for timer module
P2SEL = BIT2;
BCSCTL1 = CALBC1_16MHZ; //setting the clock frequency to 16MHz
DCOCTL = CALDCO_16MHZ;

//Timer module
TA1CCR0 = 1000; //giving a count for total time of the PWM to be generated
TA1CCTL1 = OUTMOD_7; //setting the timer mode to count in set-reset mode
TA1CTL = MC_1 + ID_0 + TASSEL_2 + TACLR; //MC_1 is mode control 1 it implies that timer counts in up-mode
//ID_0 is clock divider by 1, TASSEL_2 selects SMCLK and TACLR clears the timer(this makes the timer count from 0)

//ADC module
ADC10CTL0 = ADC10ON + ADC10IE + SREF_1 + REFON + REF2_5V + MSC; //enabling interrupts, setting reference to be 2.5V
ADC10CTL1 = ADC10DIV_0 + CONSEQ_2; //clock division and CONSEQ_2 is for single channel convert repeatedly
ADC10AE0 = INCH_0; //input available on channel 0 i.e pin A0

ADC10CTL0 |= ENC + ADC10SC; //start conversion

_BIS_SR(GIE); //Global interrupt enable

while(1);

}


#pragma vector = ADC10_VECTOR
__interrupt void adc10_interrupt(void) {

P2DIR |= 0x01; //Set P2.0
P2OUT |= 0x01; //Set P2.0 as high(for checking time taken for loop to run)
float set_point = 2.24, t_on; //set point is 2.24V
float kp = 1, ki = 1, p_term, imax = 2.24, imin = 0, pi_term, pi_min = 0, pi_max = 2;
static float i_term = 0;

int adc_val = ADC10MEM; //assigning value of ADC to a variable
float adc_out = (float) (adc_val * 2.5/1024); //scaling the hex value to voltage according to reference given
float error = set_point - adc_out; //getting error from ADC and set-point

//proportional term
p_term = kp * error;

//integral term
static float i_temp = 0;
i_temp = i_temp + error; //calculating the integral term

if(i_temp > imax) //integral anti-windup
i_temp = imax;

else if(i_temp < imin) //integral anti-windup
i_temp = imin;

i_term = ki * i_temp; //final i_term

//derivative term
/*float d_temp = error; //check this for static
d_term = kd * (d_temp-error); //check the calculations*/

//pi term
pi_term = p_term + i_term;

//clipping the output pi_term
if(pi_term > pi_max)
pi_term = pi_max;
else if(pi_term < pi_min)
pi_term = pi_min;


t_on = (pi_term * 1000)/pi_max;

TA1CCR1 = t_on; //setting the ON time according to pi_term
P2OUT ^= 0x01; //setting P2.0 to low (for checking time taken for loop to run)
_BIS_SR(GIE); //global interrupt enable

}

The problems I am facing are:

1) I have to calculate the time taken for the PI loop to run. To do so, I have made a pin high at the beginning of the control loop and toggled it at the end. I have checked the output of this pin on a DSO. Surprisingly, the interrupt is being called only once. So, I see only one spike on the oscilloscope as soon as I run the code.

2) I have hence tried putting the control loop in a while(1){} loop. Now, when I repeat the same procedure here, to toggle a pin, I observe a completely different and unexpected behavior. When I keep the pin high in the beginning, and toggle it at the end, the DSO shows me a timing diagram where a high pulse sustains for a long time of 280 microseconds and the low pulse for 0.5 micro. This seems to be correct. But when I do it the other way round, i.e at the beginning of the loop, the pin is set to low. The DSO shows me a pulse with a duty cycle of 50% and the time period is (2*280) microsec.

#include <msp430g2553.h>
/**
* main.c
*/
int main(void) {

WDTCTL = WDTPW + WDTHOLD; //disabling the watch-dog timer

P2DIR = BIT2; //selecting and setting the output bits for timer module
P2SEL = BIT2;
BCSCTL1 = CALBC1_16MHZ; //setting the clock frequency to 16MHz
DCOCTL = CALDCO_16MHZ;

//Timer module
TA1CCR0 = 1000; //giving a count for total time of the PWM to be generated
TA1CCTL1 = OUTMOD_7; //setting the timer mode to count in set-reset mode
TA1CTL = MC_1 + ID_0 + TASSEL_2 + TACLR; //MC_1 is mode control 1 it implies that timer counts in up-mode
//ID_0 is clock divider by 1, TASSEL_2 selects SMCLK and TACLR clears the timer(this makes the timer count from 0)

//ADC module
ADC10CTL0 = ADC10ON + ADC10IE + SREF_1 + REFON + REF2_5V + MSC; //enabling interrupts, setting reference to be 2.5V
ADC10CTL1 = ADC10DIV_0 + CONSEQ_2; //clock division and CONSEQ_2 is for single channel convert repeatedly
ADC10AE0 = INCH_0; //input available on channel 0 i.e pin A0

ADC10CTL0 |= ENC + ADC10SC; //start conversion

while(1)
{

P2DIR |= 0x01; //Set P2.0
P2OUT |= 0x00; //Set P2.0 as low(for checking time taken for loop to run)
float set_point = 2.24, t_on; //set point is 2.24V
float kp = 1, ki = 1, p_term, imax = 2.24, imin = 0, pi_term, pi_min = 0, pi_max = 2;
static float i_term = 0;

int adc_val = ADC10MEM; //assigning value of ADC to a variable
float adc_out = (float) (adc_val * 2.5/1024); //scaling the hex value to voltage according to reference given
float error = set_point - adc_out; //getting error from ADC and set-point

//proportional term
p_term = kp * error;

//integral term
static float i_temp = 0;
i_temp = i_temp + error; //calculating the integral term

if(i_temp > imax) //integral anti-windup
i_temp = imax;

else if(i_temp < imin) //integral anti-windup
i_temp = imin;

i_term = ki * i_temp; //final i_term

//derivative term
/*float d_temp = error; //check this for static
d_term = kd * (d_temp-error); //check the calculations*/

//pi term
pi_term = p_term + i_term;

//clipping the output pi_term
if(pi_term > pi_max)
pi_term = pi_max;
else if(pi_term < pi_min)
pi_term = pi_min;


t_on = (pi_term * 1000)/pi_max;

TA1CCR1 = t_on; //setting the ON time according to pi_term
P2OUT ^= 0x01; //setting P2.0 flip (for checking time taken for loop to run)
// _BIS_SR(GIE); //Global interrupt enable
}
}

  • Hey Shariva,

    I believe you are getting the 50% duty cycle because you are trying to clear the bit using an OR.  Basically, you are just enabling the GPIO and only toggling it at the end.  

    P2OUT |= 0x00; //Set P2.0 as low(for checking time taken for loop to run) 
    
    //Needs to be
    
    P2OUT &= ~0x01; //Set P2.0 as low(for checking time taken for loop to run)

    Thanks,

    JD

  • Thanks JD

    As I've mentioned earlier, this 50% duty cycle is appearing when the pin is set to low in the beginning. So how is it that I get different results when it is set to high in the beginning? Although I agree with the solution you have provided. I will definitely try that out.

    Another question is, XOR, NOT and AND are all logical operators. So should it matter if any of the one is used to toggle the pin?


    Also, does the control loop seem to be correct? I ask this because when I build it on CCS, there are no errors or warnings but it is not working on the system I had to implement it on. The integral term should make the final PWM to rise up to its max value but it gets saturated midway.


    There is this another thing that pops up on every line saying: Multiplication is done on a device that has no hardware multiplier. Is there a resolve for this?

    Thanks and Regards,

    Shariva

  • Hey Shariva,

    Shariva Dhekane said:
    As I've mentioned earlier, this 50% duty cycle is appearing when the pin is set to low in the beginning. So how is it that I get different results when it is set to high in the beginning? Although I agree with the solution you have provided. I will definitely try that out.

    Did you try out my solution?  I think the problem is that you are not actually setting the pin low with the code above.  

    Shariva Dhekane said:
    Another question is, XOR, NOT and AND are all logical operators. So should it matter if any of the one is used to toggle the pin?

    Typically to "toggle" XOR is used since it easily just flips the current state.  The others would be used to set the pin on a certain condition.  Can you provide an example of what you are thinking? 

    You also don't have to toggle the pin.  You could just set it high that the beginning and clear it at the end.  Then, whenever the pin is high you are in your loop. 

    Shariva Dhekane said:
    There is this another thing that pops up on every line saying: Multiplication is done on a device that has no hardware multiplier. Is there a resolve for this?

    Not, I don't believe there is any solution to this, the compiler is just warning you that the multiply functions will be demanding on the CPU (and also will use lots of clock cycles).  We do have a fixed point Math Library MSP-IQMATHLIB that has optimized math functions if you need to improve performance.  

    Thanks,

    JD

      

      

  • Hey Shariva,

    I also took a quick look at your code, but I'm not familiar with PID Control loops so I can't give you any advice there.

    Thanks,
    JD
  • In general you want to keep interrupt routines no longer than required. In this case you are doing a bunch of  floating point calculations which are slow. So you  run the risk of another interrupt coming along before the ISR completes.

    While your main code isn't doing anything, it would be best to just have the ISR get the ADC data and set a flag to tell the main routine that data is available. You can also put the CPU to sleep while waiting for data to save power. A strong point of the MSP430.

    It looks like you have configured the ADC10 module to use its internal oscillator (up to 6.3MHz) with no divider. This makes for very fast conversions. (Hopefully the signal source can support those short sample and hold times) Which is a problem since each one will generate an interrupt. There is no way that your code can keep up with that.

    The PID code doesn't need to run any more frequently than the PWM duty cycle.

  • One last thing I need help with. When I use an interrupt, the loop runs only once. But when I put it in a while(1) loop, it does the work. So, I'm not able to figure out what is wrong with the interrupt.

  • Generally this happens because you have the mcu return to a low power mode on return from the interrupt which prevents the main loop from running.
  • Yeah, as keith mentioned, either the device is going to low power mode and never waking up or your interrupt is only firing once. You might need to clear the ISR flag or re-enable it to fire again.
  • What changes have you made to your code? The data available interrupt is reset when the interrupt is serviced so it will fire the next time there is data.

    I like to use gdb to debug this sort of thing. I can stop execution and examine the device registers to see what is going on.

**Attention** This is a public forum