MSP430 Problem mit mehreren Interrupt-Timern

Ich untersuche derzeit die Verwendung von Pulsdichtemodulation (PDM) kombinierter PWM (kurz PDM-PWM) als Ersatz für herkömmliche PWM bei der Anwendung von Maximum Power Point Tracking (MPPT) für Solarmodule. Um dies zu erreichen, verwende ich den Mikrocontroller MSP430F5529, bei dem das PDM in Software mit einem Interrupt-Timer implementiert ist und die Ausgabe an einen digitalen E / A-Pin gesendet wird, damit sie beobachtet werden kann. Ein Teil meines Codes (mit den meisten unkritischen Komponenten entfernt, auch der MPPT-Algorithmus-Code wird zu Testzwecken durch einen einfachen konstanten Arbeitszyklus von 0,5 ersetzt) ​​ist am Ende dieses Beitrags angegeben. Seine Zusammensetzung lässt sich wie folgt zusammenfassen:

  1. Das Vcore-Zeug besteht darin, den SMCLK des MSP430 so einzustellen, dass er mit 25 MHz läuft, anstatt mit dem Standardwert von etwa 1 MHz.
  2. Interrupt Timer 1A wird für die PDM-Implementierung verwendet und läuft mit 200 kHz.
  3. Interrupt Timer 2A wird für den MPPT-Algorithmus bei Läufen mit 50 Hz verwendet.
  4. Interrupt Timer B wird für die Tiefpassfilter (LPF)-Schleife verwendet, die mit 500 Hz läuft. Es wird benötigt, um die verrauschte ADC-Spannung und den Strom zu filtern.

Jetzt kommt das Hauptproblem. Wenn ich den Code ausführe, scheinen sich die Timer gegenseitig zu unterbrechen und den Betrieb des anderen zu blockieren. Insbesondere (und derjenige, der meine Anwendung am meisten beeinflusst) unterbricht Timer B den Hochfrequenz-Timer 1A, was deutlich wird, wenn der digitale Ausgang des PDM auf dem Oszilloskop beobachtet wird, wie in der Abbildung unten gezeigt. Diese langen "Lücken" zwischen den kurzen Impulsen treten alle 2 ms auf, was den 500 Hz des LPF entspricht, und offensichtlich möchte ich diese "Lücken" nicht haben. Sie werden auch nicht angezeigt, wenn ich das LPF aus meinem Code entferne, obwohl ich das LPF leider für meine Anwendung benötige.

Geben Sie hier die Bildbeschreibung ein

Was kann ich tun, um dies zu verhindern? Ich kann mir diese vorübergehende Unterbrechung des PDM nicht leisten, da sie die Dynamik meines MPPT-Systems stark beeinflusst.

Der 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;
}
Ihr Text spricht von den Timern 1A, 2A und B, aber Ihre Kommentare im Code sprechen von den Timern 2A0 und 1A0 und erwähnen niemals Timer B. Welcher Code tatsächlich bei "Timer B"-Ereignissen ausgeführt wird (mit denen Sie Probleme haben). )?
Aber im Allgemeinen denke ich, dass die Antwort lauten wird: "Machen Sie nicht so viel in Ihrem Interrupt-Handler von Timer B". Gleitkommaoperationen können länger dauern, als Sie denken. Kann Ihre Verarbeitung stattdessen in Festkomma erfolgen?
Der Interrupt-Timer mit "FilterAnalogData();" läuft auf Timer B. Ich habe vergessen, den Kommentar kurz vor dieser Interrupt-Funktion zu aktualisieren, es sollte Timer 0 B0 heißen. Ich habe die Timer für jede Funktion meines Codes ein wenig ausgetauscht, bevor ich diese Frage gestellt habe, um die Interrupt-Prioritäten zu testen. Entschuldigung für den Fehler.
Verwenden Sie Ihre Interrupt-Handler nicht, um schwere Verarbeitungsaufgaben zu erledigen. Setzen Sie einfach ein Flag vom Interrupt und holen Sie sich einige Registerwerte und fragen Sie dann das Flag in Ihrer Hauptschleife ab, um zu entscheiden, wann die Verarbeitung durchgeführt werden soll.

Antworten (1)

Die Kommentatoren haben Recht, Sie sollten ein paar Dinge tun:

  1. Implementieren Sie Festkomma-Mathematik . Es gibt mehrere Möglichkeiten, dies zu tun, aber das Ausführen von Float-Operationen auf einem MSP430 innerhalb eines Interrupts gehört zu den größten Sünden der Mikrocontroller-Codierung.

  2. Verwenden Sie eine Art Task-Manager für Ihre Niederfrequenz. Auf diese Weise ist der niederfrequente Timer unterbrechbar.

  3. Verwenden Sie für Ihren Niederfrequenzfilter eine Art Low-RAM- und schnelle Mittelungstechnik .

Ich habe dort drei Links platziert b/c Ich habe bereits einen Großteil davon gelöst, von denen die meisten Beispiele in Github (und in einigen Fällen auf dem MSP430) haben.

Bei Fragen können Sie mich gerne auf anderem Wege kontaktieren. Besonders der Task-Manager wird Ihnen dabei helfen, da er bedeutet, dass Sie nur einen Interrupt benötigen. Es ist sehr zuverlässig und relativ einfach zu verwenden, aber Sie müssen das Fett aus Ihrem Code entfernen (Float-Operationen entfernen), sonst blockieren sich die Aufgaben möglicherweise immer noch gegenseitig.