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.

How to optimally use the ADC in a power converter control application (ISR)

Other Parts Discussed in Thread: CONTROLSUITE

Hi guys,

I am using the F28335 controlCARD I would like to implement a closed loop control for a simple buck converter. In open loop, I successfully generate the necessary PWM signals and I managed to get some ADC readings, which directly brings me to my question:

How and where is the ADC reading optimally implemented in a closed loop control?

I generate my PWM signals with the ePWM module (UP/DOWN count) and enter the ISR for the ePWM module twice in every switching period (i.e. when the counter hits PRD and 0). Within the ePWM ISR, I perform the ADC reading AND the PWM generation.

Going through the examples in controlSUITE, the ADC reading uses its own ISR, which is triggered by the ePWM module. This aspect confuses me since it is different from my approach.

Could you maybe elaborate on how a proper implementation of the ADC is done (does the ADC reading have to have its own ISR or is it just as fine to perform the ADC reading within the ePWM ISR?)

Thank you very much.

Below my current implementation for the F28335 DSP:

#include "DSP28x_Project.h"     // Device Headerfile and Examples Include File
#include <math.h>

// Prototype statements for functions found within this file.
void InitEPwm2(void);
void InitEPwm6(void);
void InitializeADC(void);

__interrupt void epwm2_isr(void);

// User defined
long freq;
double ADCgain;
double PRD;

#define RESULTS_BUFFER_SIZE 256 //buffer for storing conversion results
Uint16 AdcaResults[RESULTS_BUFFER_SIZE];
Uint16 resultsIndex;
volatile Uint16 bufferFull;


void main(void)
{

// Step 1. Initialize System Control:
// PLL, WatchDog, enable Peripheral Clocks
    InitSysCtrl();

	// Set the switching PRD [kHz] in freq
	freq = 16;
	// Calculate the running phase: 2^32/(2*fsw)*50; -> 2^32/(16000*2)*50 = 6710886
	PRD = 150000/(2*freq);
	ADCgain = PRD/4096;


	   // Define ADCCLK clock frequency ( less than or equal to 25 MHz )
	   // Assuming InitSysCtrl() has set SYSCLKOUT to 150 MHz
	   EALLOW;
	   SysCtrlRegs.HISPCP.all = 3;
	   EDIS;

// Step 2. Initialize GPIO:
// This example function is found in the F2837xS_Gpio.c file and
// illustrates how to set the GPIO to its default state.
    InitGpio();


// For this case just init GPIO pins for ePWM1, ePWM2, ePWM3
// These functions are in the F2837xS_EPwm.c file
	InitEPwm2Gpio();
	InitEPwm6Gpio();
	InitAdc();  // For this example, init the ADC

// Step 3. Clear all interrupts and initialize PIE vector table:
// Disable CPU interrupts
    DINT;

// Initialize the PIE control registers to their default state.
// The default state is all PIE interrupts disabled and flags
// are cleared.
// This function is found in the F2837xS_PieCtrl.c file.
    InitPieCtrl();

// Disable CPU interrupts and clear all CPU interrupt flags:
    IER = 0x0000;
    IFR = 0x0000;

// Initialize the PIE vector table with pointers to the shell Interrupt
// Service Routines (ISR).
// This will populate the entire table, even if the interrupt
// is not used in this example.  This is useful for debug purposes.
// The shell ISR routines are found in F2837xS_DefaultIsr.c.
// This function is found in F2837xS_PieVect.c.
    InitPieVectTable();

// Interrupts that are used in this example are re-mapped to
// ISR functions found within this file.
	EALLOW; // This is needed to write to EALLOW protected registers
	PieVectTable.EPWM2_INT = &epwm2_isr;	//function for ePWM2 interrupt
	EDIS;   // This is needed to disable write to EALLOW protected registers

// Step 4. Initialize the Device Peripherals:
// For this example, only initialize the ePWM and ADC

	EALLOW;
	SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;
	EDIS;

	InitEPwm2();
	InitEPwm6();
	InitializeADC();


    //Initialize results buffer
        for(resultsIndex = 0; resultsIndex < RESULTS_BUFFER_SIZE; resultsIndex++)
        {
        	AdcaResults[resultsIndex] = 0;
        }
        resultsIndex = 0;
        bufferFull = 0;

	EALLOW;
	SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC =1;
	EDIS;

// Enable CPU INT3 which is connected to EPWM1-3 INT:
	IER |= M_INT3;

// Enable EPWM INTn in the PIE: Group 3 interrupt 1-3
	PieCtrlRegs.PIEIER3.bit.INTx2 = 1;


// Enable global Interrupts and higher priority real-time debug events:
    EINT;  // Enable Global interrupt INTM
    ERTM;  // Enable Global realtime interrupt DBGM
// Step 6. IDLE loop. Just sit and loop forever (optional):
    //take conversions indefinitely in loop
        do
        {
        	//wait while ePWM causes ADC conversions, which then cause interrupts,
        	//which fill the results buffer, eventually setting the bufferFull
        	//flag
        	while(!bufferFull);
        	bufferFull = 0; //clear the buffer full flag
        }while(1);
}

/* Interrupt service routine reading the output voltage and generating the PWM */
__interrupt void epwm2_isr(void)
{

	AdcaResults[resultsIndex++] = AdcRegs.ADCRESULT0 >>4;
	if(RESULTS_BUFFER_SIZE <= resultsIndex)
	{
		resultsIndex = 0;
		bufferFull = 1;
	}

	// Calculates the duty cycles
		EPwm6Regs.CMPA.half.CMPA=AdcaResults[resultsIndex]*ADCgain;
		EPwm2Regs.CMPA.half.CMPA=0.5*PRD;


    // Clear INT flag for this timer
    EPwm2Regs.ETCLR.bit.INT = 1;
    // Acknowledge this interrupt to receive more interrupts from group 3
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP3;
}

void InitEPwm2()
{
    EPwm2Regs.TBPRD = PRD;                       // Set timer period
    EPwm2Regs.TBPHS.half.TBPHS = 0x0000;           // Phase is 0
    EPwm2Regs.TBCTR = 0x0000;                     // Clear counter

    // Setup TBCLK
    EPwm2Regs.ETSEL.bit.SOCAEN = 1;  			//enable SOCA
	EPwm2Regs.ETSEL.bit.SOCASEL	= 4;	        // Select SOC on up-count
	EPwm2Regs.ETPS.bit.SOCAPRD = 1;		        // Generate pulse on 1st event
    EPwm2Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; // Count up
    EPwm2Regs.TBCTL.bit.PHSEN = TB_ENABLE;        // Disable phase loading
    EPwm2Regs.TBCTL.bit.PRDLD = TB_SHADOW;
    EPwm2Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_IN;
    EPwm2Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW;
    EPwm2Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW;
    EPwm2Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO;
    EPwm2Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO;

    EPwm2Regs.TBCTL.bit.HSPCLKDIV = TB_DIV1;       // Clock ratio to SYSCLKOUT
    EPwm2Regs.TBCTL.bit.CLKDIV = TB_DIV1;          // Slow just to observe on the
                                                   // scope

    // Set actions
    EPwm2Regs.AQCTLA.bit.CAU = AQ_CLEAR;            // Set PWM2A on Zero
    EPwm2Regs.AQCTLA.bit.CAD = AQ_SET;

    EPwm2Regs.AQCTLB.bit.CAU = AQ_SET;          // Set PWM2A on Zero
    EPwm2Regs.AQCTLB.bit.CAD = AQ_CLEAR;

    // Active Low complementary PWMs - setup the deadband
    EPwm2Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE;
    EPwm2Regs.DBCTL.bit.POLSEL = DB_ACTV_HIC;
//    EPwm2Regs.DBCTL.bit.IN_MODE = DBA_ALL;
    EPwm2Regs.DBRED = 100;
    EPwm2Regs.DBFED = 100;

    // Interrupt where we will modify the deadband
    EPwm2Regs.ETSEL.bit.INTSEL = ET_CTR_ZERO;     // Select INT on Zero event
    EPwm2Regs.ETSEL.bit.INTEN = 1;                // Enable INT
    EPwm2Regs.ETPS.bit.INTPRD = ET_1ST;           // Generate INT on 1st event

}

void InitEPwm6()
{
    EPwm6Regs.TBPRD = PRD;                       // Set timer period
    EPwm6Regs.TBPHS.half.TBPHS = 0x0000;           // Phase is 0
    EPwm6Regs.TBCTR = 0x0000;                     // Clear counter

    // Setup TBCLK
    EPwm6Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; // Count up
    EPwm6Regs.TBCTL.bit.PHSEN = TB_ENABLE;        // Disable phase loading
    EPwm6Regs.TBCTL.bit.PRDLD = TB_SHADOW;
    EPwm6Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_IN;
    EPwm6Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW;
    EPwm6Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW;
    EPwm6Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO;
    EPwm6Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO;
    EPwm6Regs.TBCTL.bit.HSPCLKDIV = TB_DIV1;       // Clock ratio to SYSCLKOUT
    EPwm6Regs.TBCTL.bit.CLKDIV = TB_DIV1;          // Slow just to observe on the
                                                   // scope

    // Set actions
    EPwm6Regs.AQCTLA.bit.CAU = AQ_CLEAR;            // Set PWM2A on Zero
    EPwm6Regs.AQCTLA.bit.CAD = AQ_SET;

    EPwm6Regs.AQCTLB.bit.CAU = AQ_SET;          // Set PWM2A on Zero
    EPwm6Regs.AQCTLB.bit.CAD = AQ_CLEAR;

    // Active Low complementary PWMs - setup the deadband
    EPwm6Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE;
    EPwm6Regs.DBCTL.bit.POLSEL = DB_ACTV_HIC;
//    EPwm6Regs.DBCTL.bit.IN_MODE = DBA_ALL;
    EPwm6Regs.DBRED = 100;
    EPwm6Regs.DBFED = 100;
}
void InitializeADC()
{
	// Configure ADC
	   AdcRegs.ADCMAXCONV.all = 0x0001;       // Setup 2 conv's on SEQ1
	   AdcRegs.ADCCHSELSEQ1.bit.CONV00 = 0x0; // Setup ADCINA3 as 1st SEQ1 conv.
	   AdcRegs.ADCCHSELSEQ1.bit.CONV01 = 0x2; // Setup ADCINA2 as 2nd SEQ1 conv.
	   AdcRegs.ADCTRL2.bit.EPWM_SOCA_SEQ1 = 1;// Enable SOCA from ePWM to start SEQ1
	   AdcRegs.ADCTRL2.bit.INT_ENA_SEQ1 = 1;  // Enable SEQ1 interrupt (every EOS)
}

  • Hi Alex,

    Alexander A said:
    Could you maybe elaborate on how a proper implementation of the ADC is done (does the ADC reading have to have its own ISR or is it just as fine to perform the ADC reading within the ePWM ISR?)

    Depends Alex but I personally for all my applications either  use a CPU Timer ISR or ePWM timer ISR. I check for the ADC interrupt -> get the adc data -> process it and then finally handle the control to my scheduler. This happens every CPU Timer ISR.

    I hope you got an idea.

    Regards,

    Gautam

  • Hi Gautam,

    thank you very much for your answer. To my understanding, there is no "one" common way to do that. But could you please be so kind and elaborate a few lines on your previous answer:

    Gautam Iyer said:

    I check for the ADC interrupt -> get the adc data -> process it and then finally handle the control to my scheduler. This happens every CPU Timer ISR.

    In particular, what do you mean by you check for the ADC interrupt? It basically means that you do the ADC reading within the ADC ISR (which is triggered by the CPU timer or the ePWM timer), right? 

    Let me maybe rephrase my question:

    If the ADC reading can be triggered by the ePWM (as described in the ADC reference guide), what is the advantage of explicitly implementing an ADC ISR for the ADC reading over just doing the ADC reading in any other ISR (e.g. the ISR for the ePWM module like in the code I posted in the beginning)?

    I think this is not so clear-cut for me.

    Thank you,

    Alexander

  • Hi Alexander,

    You will typically enter the ADC ISR right when the ADC is done converting and the results are available. From here, you can read the ADC results and compute the new control loop outputs (usually ePMW duty cycle or period) right away. This gives you the most amount of time to compute the control loop outputs, and allows you to apply them as early as possible, as opposed to waiting for the next ePWM PRD or Zero event.
  • Hi Alexander,

    Devin and Gautam have pretty much resolved your question, but I did want add one thing...

    If the first thing in your main (control) ISR will be to look at ADC results, you may as well have the last useful ADC conversion trigger an ISR when it completes.  This ISR would then be your control loop ISR.

    If the first thing in your main ISR is to run some state machine code and/or if your system only requires one ADC result to be converted (and that ADC conversion is configured to have a short acquisition window), it may be more cycle efficient to have the PWM/CpuTimer generate the ISR directly.  The thought being that, by the time the PWM ISR needs the ADC result, the software/system designer will have verified that the new ADC result will be finished converting (by looking at ISR cycle counts, ISR context save/restore cycles, ADC timings, etc).

    The cycle difference between these two implementations is often not very large.  However, in some systems every cycle counts.


    Thank you,
    Brett

  • Dear all,
    I would like to thank you for your inputs. I am now following the approach to trigger the ADC with the ePWM module, and to have the ADC reading within the ADC ISR.

    I think that'll do the job.

    cheers,
  • Thank you Devin & Brett for handling this in my absence.

    Goodluck Alex!

    Regards,
    Gautam