Electrical – MSP430 Problem with Multiple Interrupt Timers

interruptsmpptmsp430

I am currently investigating the use of pulse density modulation (PDM) combined PWM (In short, PDM-PWM) as a substitute for conventional PWM in the application of maximum power point tracking (MPPT) for solar panels. To achieve this, I am using the MSP430F5529 micro-controller where the PDM is implemented in software using an interrupt timer and the output is sent to a digital I/O pin so that it can be observed. A part of my code (with most non-critical components removed, also the MPPT algorithm code is replaced with a simple constant 0.5 duty cycle for testing purposes) is given at the bottom of this post. Its composition can be summarized as follows:

  1. The Vcore stuff is to set the SMCLK of the MSP430 to be running at 25MHz rather than the default 1 or so MHz.
  2. Interrupt Timer 1A is used for the PDM implementation and runs at 200 kHz.
  3. Interrupt Timer 2A is used for the MPPT algorithm at runs at 50 Hz.
  4. interrupt Timer B is used for the low pass filter (LPF) loop that runs at 500 Hz. It is needed to filter the noisy ADC voltage and current.

Now comes the main issue. When I run the code, it seems that the Timers are interrupting each other and blocking each other's operation. In particular (and the one that impacts my application the most), Timer B is interrupting the high frequency Timer 1A which is clear when the digital output from the PDM is observed on the oscilloscope as shown in the image below. Those long "gaps" between the short pulses are occurring every 2ms which is matching the 500 Hz of the LPF and obviously, I don't want to have these "gaps". They also don't appear when I remove the LPF from my code though unfortunately, I need to have the LPF for my application.

enter image description here

What can I do to stop this from happening? I cannot afford to have these temporal stoppage of the PDM since it greatly affect the dynamics of my MPPT system.

The Code:

#include <msp430f5529.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define Num_Channels 6
#define Num_Samples 3

#define LED BIT5

volatile float A_Results[Num_Channels][Num_Samples];
// For Low Pass Filter
volatile float A_ResultsR[Num_Channels][Num_Samples];

// For Average
// volatile unsigned long A_Results_T[Num_Channels] ;
int i_ADC = 0;



// Define Electrical Quantity Variables
#define NumInputs 2
#define NumRecords 3
// Output Side
float I_Out;
float V_Out;
// Input Side
float I[NumInputs][NumRecords];
float Vi[NumInputs][NumRecords];
const float D_Start[NumInputs] = {1, 1};
float D[NumInputs] = { 0.01, 0.01 };
// Calibration Formula
float I_V_REF[NumInputs + 1] ; // +1 to include the output measurements.

// Other Constants
const float B2V = (3.3 / 4095);

// Initialize PDM Related Variables
int X1[2] ;
int X2[2] ;
int E1 ;
int E2[2] ;
int U[2] ;
int Scaler = 30000 ;
int D_S ;

int PWM_MaxCounter = 500 ; // Set PWM Frequency Here.

// Load the Functions as defined at the bottom of this code.
void SetVcoreUp(unsigned int level); // SetVcoreUp Function
void ObtainAnalogData();
// sign function, very straight forward.
int sign(float value) {
    return (value > 0) - (value < 0);
}

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

// Code to Set up the 25 MHz Clock for use
P1DIR |= BIT0;                            // ACLK set out to pins
P1SEL |= BIT0;
P2DIR |= BIT2;                            // SMCLK set out to pins
P2SEL |= BIT2;
P7DIR |= BIT7;                            // MCLK set out to pins
P7SEL |= BIT7;

// Increase Vcore setting to level3 to support fsystem=25MHz
// NOTE: Change core voltage one level at a time..
SetVcoreUp(0x01);
SetVcoreUp(0x02);
SetVcoreUp(0x03);

UCSCTL3 = SELREF_2;                       // Set DCO FLL reference = REFO
UCSCTL4 |= SELA_2;                        // Set ACLK = REFO

__bis_SR_register(SCG0);                  // Disable the FLL control loop
UCSCTL0 = 0x0000;                         // Set lowest possible DCOx, MODx
UCSCTL1 = DCORSEL_7;                     // Select DCO range 50MHz operation
UCSCTL2 = FLLD_0 + 762;                   // Set DCO Multiplier for 25MHz
                                          // (N + 1) * FLLRef = Fdco
                                          // (762 + 1) * 32768 = 25MHz
                                          // Set FLL Div = fDCOCLK/2
__bic_SR_register(SCG0);                  // Enable the FLL control loop

// Worst-case settling time for the DCO when the DCO range bits have been
// changed is n x 32 x 32 x f_MCLK / f_FLL_reference. See UCS chapter in 5xx
// UG for optimization.
// 32 x 32 x 25 MHz / 32,768 Hz ~ 780k MCLK cycles for DCO to settle
__delay_cycles(782000);

// Loop until XT1,XT2 & DCO stabilizes - In this case only DCO has to stabilize
do {
    UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG);
    // Clear XT2,XT1,DCO fault flags
    SFRIFG1 &= ~OFIFG;                      // Clear fault flags
} while (SFRIFG1 & OFIFG);                   // Test oscillator fault flag

// Test LED Setup
P2DIR |= LED;
P2OUT &= ~LED; // Turn it Off First
// Timer Setup
// Timer 1A0 Specifications. This one is for the MPPT sampling loop
TA2CTL = TASSEL_1 + MC_1 + TACLR + ID_0; // Set the timer A to ACLK, Continuous
TA2CCR0 = 640; // Makes it 50 Hz :)
TA2CCTL0 |= CCIE;
// Timer 2A0 Specifications. This one is for the low pass filtering loop.
  TB0CCTL0 = CCIE;                          // CCR0 interrupt enabled
  TB0CCR0 = 64; // Make is 500 Hz
  TB0CTL = TBSSEL_1 + MC_1 + TBCLR;         // SMCLK, contmode, clear TAR
// ADC Setup Code
P6SEL |= BIT0 + BIT1 + BIT2 + BIT3 + BIT4 + BIT5; // Enable A/D channel inputs, P6.0 to P6.5
// ADC12_A ref control registers
ADC12CTL0 = ADC12ON + ADC12MSC + ADC12SHT0_10;//+ ADC12REFON + ADC12REF2_5V ; // Turn on ADC12, extend sampling time
                                              // to avoid overflow of results
ADC12CTL1 = ADC12SHP + ADC12CONSEQ_3 + ADC12SSEL_3 + ADC12DIV_0 ; // Use sampling timer, repeated sequence

ADC12MCTL0 = ADC12INCH_0 + ADC12SREF_0;           // ref+=AVcc, channel = A2
ADC12MCTL1 = ADC12INCH_1 + ADC12SREF_0;           // ref+=AVcc, channel = A3
ADC12MCTL2 = ADC12INCH_2 + ADC12SREF_0;           // ref+=AVcc, channel = A4
ADC12MCTL3 = ADC12INCH_3 + ADC12SREF_0;
ADC12MCTL4 = ADC12INCH_4 + ADC12SREF_0;
ADC12MCTL5 = ADC12INCH_5 + ADC12SREF_0 + ADC12EOS; // ref+=AVcc, channel = A12, end seq.
ADC12IE = 0x08;                           // Enable ADC12IFG.3
ADC12CTL0 |= ADC12ENC;                    // Enable conversions
ADC12CTL0 |= ADC12SC;                     // Start convn - software trigger

// PWM Setup Code
// Use P1.2 and P1.3 for PWM Output
P1DIR |= BIT3 + BIT4;
P1SEL |= BIT3 + BIT4;
TA0CCR0 = PWM_MaxCounter ;             // PWM Period, TACCR0 = 500 makes 50KHz =)
TA0CCTL2 |= OUTMOD_7;      // CCR2 reset/set
TA0CCR2 = 0 ;               // CCR1 PWM duty cycle
TA0CCTL3 |= OUTMOD_7;      // CCR3 reset/set
TA0CCR3 = D[1] * TA0CCR0 ;
TA0CTL = TASSEL_2 + MC_1 + TACLR;   // SMCLK, up mode
// PDM Setup Code, Use T1A Timer
P1DIR |= BIT5 ;
P2OUT &= BIT5 ;
TA1CTL = TASSEL_2 + MC_1 + TACLR + ID_0 ;
TA1CCR0 = 125 ; // Makes it 200kHz :)
TA1CCTL0 |= CCIE;

// UART Setup
P4SEL |= BIT4 + BIT5;                        // P4.4,5 = UART1 TXD/RXD
// configure USCI_A1 UART
UCA1CTL1 = UCSSEL_2;                      // MCLK
UCA1BR0 = 217;                         // 25MHz / 217 ~= 115200 Baud Rate =)
UCA1BR1 = 0x0;
UCA1MCTL = UCBRS_3 + UCBRF_0;               // Modulation UCBRSx = 3
UCA1CTL1 &= ~UCSWRST;                   // **Initialize USCI state machine**

__bis_SR_register(LPM4_bits + GIE);       // Enter LPM0, Enable interrupts

// Other Setup Code
// Set Current Sensor's Reference Voltage.
int k;
for (k = 0; k < NumInputs + 1; k++) {
    I_V_REF[k] = 1 ;
}
//CalibrateSensors() ;
// Program Loop
while (1) {
    // Transmission and Receiving from UART is conducted here. Not shown since it is irrelevant.
  }
 }

// Timer 1 A0 interrupt service routine
#pragma vector=TIMER2_A0_VECTOR
__interrupt void TIMER2_A0_ISR(void) {
// B2V, I_0Ref
// Calculate Current and Voltage for each input channel.
I[0][0] = (float) (A_Results[0][0] * B2V - I_V_REF[0]) * 0.2936 ;
Vi[0][0] = (float) A_Results[1][0] * B2V * 3.0263 ; // Voltage Range divided by resolution
I[1][0] = (float) (A_Results[2][0] * B2V - I_V_REF[1])  * 0.2936 ;
Vi[1][0] = (float) A_Results[3][0] * B2V * 3.0344 ;

// Obtain Current and Voltage on the Output Side
I_Out = (float) (A_Results[4][0] * B2V - I_V_REF[2]) * 0.285 ;
V_Out = (float) A_Results[5][0] * B2V * 3.0344 ;
// The MPPT Algorithm Fits in here.
// It is not shown for the sake of brevity and also since it is not important.
D[0] = 0.5 ;
// Update the Registers to generate the new PWM signal
D_S = D[0] * Scaler ;
TA0CCR3 = D[1] * TA0CCR0;
P2OUT ^= LED; // Blink LED to test Frequency on Oscilloscope, it shows 50Hz which is what I wanted, btw.
}


// Timer 0 B0 Interrupt Service Routine
// Timer0 interrupt service routine
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=TIMER0_B0_VECTOR
__interrupt void TIMER0_B0_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(TIMER0_B0_VECTOR))) TIMER0_B0_ISR (void)
#else
#error Compiler not supported!
#endif
{
   FilterAnalogData(); // Obtain Filtered Analog Data.
}

void FilterAnalogData() {
int x;
int y;
// Implement Low Pass Filter
// Update Historic Values of the Analog Data
for (x = 0; x < Num_Channels; x++) {
    for (y = 1; y < Num_Samples; y++) {
        A_ResultsR[x][y] = A_ResultsR[x][y - 1];
        A_Results[x][y] = A_Results[x][y - 1];
    }
}
// Apply the LPR Discretized Z-Domain Formula
for (x = 0; x < Num_Channels; x++) {
        A_Results[x][0] = 0.5 * A_Results[x][1] + 0.25 * (A_ResultsR[x][0] + A_ResultsR[x][1]); // 1st Order Designed
}
}

    // Timer 1A0 Interrupt Service Routine
// Timer 1 interrupt service routine
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=TIMER1_A0_VECTOR
__interrupt void TIMER1_A0_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(TIMER1_A0_VECTOR))) TIMER1_A0_ISR (void)
#else
#error Compiler not supported!
#endif
{
 // Integration Loop
  E1 = D_S - U[1] ;
  X1[0] = X1[1] + (E1 >> 3) ; // >> Stands for Bit Shift to Right (ie. Make Number Smaller)
  E2[0] = X1[0] - U[1] ;
  X2[0] = X2[1] + (E2[1] >> 1) ;
  // Quantizer
  if (X2[0] >= 0) {
      U[0] = Scaler ;
      P1OUT |= BIT5 ; // Turn on P1.5
      TA0CCR2 = PWM_MaxCounter ;
  } else {
      U[0] = 0 ;
      P1OUT &= ~BIT5; // Turn off P1.5
      TA0CCR2 = 0 ;
  }
  // Update History Variables
  X1[1] = X1[0] ;
  X2[1] = X2[0] ;
  E2[1] = E2[0] ;
  U[1] = U[0] ;
}


// ADC Interrupt Service Routine
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=ADC12_VECTOR
__interrupt void ADC12ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(ADC12_VECTOR))) ADC12ISR (void)
#else
#error Compiler not supported!
#endif
{
switch (__even_in_range(ADC12IV, 34)) {
case 0:
    break;                           // Vector  0:  No interrupt
case 2:
    break;                           // Vector  2:  ADC overflow
case 4:
    break;                           // Vector  4:  ADC timing overflow
case 6:
    break;                           // Vector  6:  ADC12IFG0
case 8:
    break;                           // Vector  8:  ADC12IFG1
case 10:
    break;                           // Vector 10:  ADC12IFG2
case 12:                                  // Vector 12:  ADC12IFG3
    // Record A_ResultsX from the ADC Memory
    A_ResultsR[0][0] = ADC12MEM0; // I1
    A_ResultsR[1][0] = ADC12MEM1; // V1
    A_ResultsR[2][0] = ADC12MEM2; //I2
    A_ResultsR[3][0] = ADC12MEM3; // V2
    A_ResultsR[4][0] = ADC12MEM4; //I_OUT
    A_ResultsR[5][0] = ADC12MEM5; // V_Out
    __bic_SR_register_on_exit(LPM0_bits);
    break;
case 14:
    break;                           // Vector 14:  ADC12IFG4
case 16:
    break;                           // Vector 16:  ADC12IFG5
case 18:
    break;                           // Vector 18:  ADC12IFG6
case 20:
    break;                           // Vector 20:  ADC12IFG7
case 22:
    break;                           // Vector 22:  ADC12IFG8
case 24:
    break;                           // Vector 24:  ADC12IFG9
case 26:
    break;                           // Vector 26:  ADC12IFG10
case 28:
    break;                           // Vector 28:  ADC12IFG11
case 30:
    break;                           // Vector 30:  ADC12IFG12
case 32:
    break;                           // Vector 32:  ADC12IFG13
case 34:
    break;                           // Vector 34:  ADC12IFG14
default:
    break;
}
}

// VCore Function

void SetVcoreUp(unsigned int level) {
// Open PMM registers for write
PMMCTL0_H = PMMPW_H;
// Set SVS/SVM high side new level
SVSMHCTL = SVSHE + SVSHRVL0 * level + SVMHE + SVSMHRRL0 * level;
// Set SVM low side to new level
SVSMLCTL = SVSLE + SVMLE + SVSMLRRL0 * level;
// Wait till SVM is settled
while ((PMMIFG & SVSMLDLYIFG) == 0)
    ;
// Clear already set flags
PMMIFG &= ~(SVMLVLRIFG + SVMLIFG);
// Set VCore to new level
PMMCTL0_L = PMMCOREV0 * level;
// Wait till new level reached
if ((PMMIFG & SVMLIFG))
    while ((PMMIFG & SVMLVLRIFG) == 0)
        ;
// Set SVS/SVM low side to new level
SVSMLCTL = SVSLE + SVSLRVL0 * level + SVMLE + SVSMLRRL0 * level;
// Lock PMM registers for write access
PMMCTL0_H = 0x00;
}

Best Answer

The commenters are correct, you should do a few things:

  1. Implement fixed-point math. There are several ways to do this, but performing float operations on an MSP430 within an interrupt is amongst the highest sins of microcontroller coding.

  2. Use some sort of task manager for your low-frequency. This way, the low-frequency timer is interruptable.

  3. Use some sort of low-RAM and fast averaging technique for your low frequency filter.

I placed three links in there b/c I have already solved a majority of this, most of which I have examples for in github (and on the MSP430 in some cases).

Feel free to contact me via other means if you have any questions. The task manager in particular will help you since it means that you will only need one interrupt. It is very reliable and reasonably easy to use, but you have to trim the fat from your code (remove float operations) or the tasks may still block each other.